diff --git a/Extras/PC Emulator BASIC Programs/CH32GPIO.BAS b/Extras/PC Emulator BASIC Programs/CH32GPIO.BAS new file mode 100644 index 000000000..66e1b70cc --- /dev/null +++ b/Extras/PC Emulator BASIC Programs/CH32GPIO.BAS @@ -0,0 +1,83 @@ + +REM QBASIC program to control CH32V003 I/O expander inside PC Emulator + +DECLARE FUNCTION HexByte$ (I) +DECLARE FUNCTION INT$ (I, L) +DECLARE FUNCTION ch32Available () +DECLARE FUNCTION ch32VersionMinor () +DECLARE FUNCTION ch32VersionMajor () +DECLARE SUB gpioSelect (gpio) +DECLARE SUB gpioConfig (cfg) +DECLARE SUB gpioSet (level) +DECLARE FUNCTION gpioGet () + +CLS + +REM check for I/O available +IF ch32Available = 0 THEN + PRINT "CH32V003 expander not available on this board!" + END +END IF + +PRINT "CH32V003 expander firmware version "; INT$(ch32VersionMajor, 0); "."; INT$(ch32VersionMinor, 0); " found" + +PRINT "Configure GPIO 6 as input pull-down" +gpioSelect (6) +gpioConfig (1) + +PRINT "Configure GPIO 7 as output" +gpioSelect (7) +gpioConfig (0) + +PRINT "Press any key to stop" + +LOCATE 10, 1 +WHILE LEN(INKEY$) = 0 + gpioSet (1) + t = TIMER + WHILE TIMER - t < 1: WEND + + gpioSet (0) + t = TIMER + WHILE TIMER - t < 1: WEND +WEND + +FUNCTION HexByte$ (I) + HexByte$ = RIGHT$("00" + HEX$(I), 2) +END FUNCTION + +FUNCTION INT$ (I, L) + R$ = LTRIM$(STR$(I)) + IF L <> 0 THEN + R$ = RIGHT$(STRING$(L, " ") + R$, L) + END IF + INT$ = R$ +END FUNCTION + +FUNCTION ch32Available + ch32Available = (INP(&HF0) AND 1) +END FUNCTION + +FUNCTION ch32VersionMinor + ch32VersionMinor = INP(&HFE) +END FUNCTION + +FUNCTION ch32VersionMajor + ch32VersionMajor = INP(&HFF) +END FUNCTION + +SUB gpioSelect (gpio) + OUT &HF1, gpio +END SUB + +SUB gpioConfig (cfg) + OUT &HF2, cfg +END SUB + +FUNCTION gpioGet + gpioGet = INP(&HF3) +END FUNCTION + +SUB gpioSet (level) + OUT &HF3, level +END SUB diff --git a/Extras/PC Emulator BASIC Programs/CH32I2C.BAS b/Extras/PC Emulator BASIC Programs/CH32I2C.BAS new file mode 100644 index 000000000..b3bcc11c7 --- /dev/null +++ b/Extras/PC Emulator BASIC Programs/CH32I2C.BAS @@ -0,0 +1,100 @@ + +REM QBASIC program to control CH32V003 I/O expander inside PC Emulator + +DECLARE FUNCTION HexByte$ (i) +DECLARE FUNCTION INT$ (i, l) +DECLARE FUNCTION ch32Available () +DECLARE FUNCTION ch32VersionMinor () +DECLARE FUNCTION ch32VersionMajor () +DECLARE SUB i2cInit (clock) +DECLARE SUB i2cSlave (slave) +DECLARE FUNCTION i2cReadReg (reg) +DECLARE SUB i2cWriteReg (reg, value) + +CLS + +REM check for I/O available +IF ch32Available = 0 THEN + PRINT "CH32V003 expander not available on this board!" + END +END IF + +PRINT "CH32V003 expander firmware version "; INT$(ch32VersionMajor, 0); "."; INT$(ch32VersionMinor, 0); " found" + +PRINT "Configure I2C clock to 300 KHz" +i2cInit (300) + +PRINT "Configure I2C slave Nunchuk " +CALL i2cSlave(&H52) +CALL i2cWriteReg(&HF0, &H55) + +PRINT "Press any key to stop" + +LOCATE 10, 1 +PRINT "Move the joystick..." +WHILE LEN(INKEY$) = 0 + LOCATE 11, 1 + PRINT "X = "; INT$(i2cReadReg(0), 3) + LOCATE 11, 10 + PRINT "Y = "; INT$(i2cReadReg(1), 3) +WEND + +REM CLS + +LOCATE 3, 1 +PRINT "Configure I2C slave RTC " +CALL i2cSlave(&H68) + +WHILE LEN(INKEY$) = 0 + LOCATE 10, 1 + CALL i2cWriteReg(&HE, &H3C) + PRINT "Time "; HexByte$(i2cReadReg(2)); ":"; HexByte$(i2cReadReg(1)); ":"; HexByte$(i2cReadReg(0)); + LOCATE 11, 1 + PRINT "Temperature "; INT$(i2cReadReg(17), 0); "."; INT$(i2cReadReg(18) / 256 * 100, 0); CHR$(248); "C " +WEND + +FUNCTION HexByte$ (i) + HexByte$ = RIGHT$("00" + HEX$(i), 2) +END FUNCTION + +FUNCTION INT$ (i, l) + R$ = LTRIM$(STR$(i)) + IF l <> 0 THEN + R$ = RIGHT$(STRING$(l, " ") + R$, l) + END IF + INT$ = R$ +END FUNCTION + +FUNCTION ch32Available + ch32Available = (INP(&HF0) AND 1) +END FUNCTION + +FUNCTION ch32VersionMinor + ch32VersionMinor = INP(&HFE) +END FUNCTION + +FUNCTION ch32VersionMajor + ch32VersionMajor = INP(&HFF) +END FUNCTION + +SUB i2cInit (clock) + REM LSB + OUT &HF4, (clock MOD 256) + REM MSB + OUT &HF5, (clock \ 256) +END SUB + +SUB i2cSlave (slave) + OUT &HF6, slave +END SUB + +FUNCTION i2cReadReg (reg) + OUT &HF7, reg + i2cReadReg = INP(&HF8) +END FUNCTION + +SUB i2cWriteReg (reg, value) + OUT &HF7, reg + OUT &HF8, value +END SUB + diff --git a/Extras/PC Emulator BASIC Programs/CH32SPI.BAS b/Extras/PC Emulator BASIC Programs/CH32SPI.BAS new file mode 100644 index 000000000..8f0486d6e --- /dev/null +++ b/Extras/PC Emulator BASIC Programs/CH32SPI.BAS @@ -0,0 +1,97 @@ + +REM QBASIC program to control CH32V003 I/O expander inside PC Emulator + +DECLARE FUNCTION HexByte$ (I) +DECLARE FUNCTION INT$ (I, L) +DECLARE FUNCTION ch32Available () +DECLARE FUNCTION ch32VersionMinor () +DECLARE FUNCTION ch32VersionMajor () +DECLARE SUB spiClock (clock) +DECLARE SUB spiMode (mode) +DECLARE FUNCTION spiTransfer8 (I) +DECLARE FUNCTION spiTransfer16 (I) + +CLS + +REM check for I/O available +IF ch32Available = 0 THEN + PRINT "CH32V003 expander not available on this board!" + END +END IF + +PRINT "CH32V003 expander firmware version "; INT$(ch32VersionMajor, 0); "."; INT$(ch32VersionMinor, 0); " found" + +PRINT "Configure SPI mode 3" +spiMode (3) + +PRINT "Configure SPI clock to 50 KHz" +spiClock (50) + +PRINT "Press any key to stop" + +LOCATE 10, 1 +WHILE LEN(INKEY$) = 0 + LOCATE 10, 1 + IF spiTransfer16(&H8200) AND 1 THEN + PRINT "X "; INT$(spiTransfer16(&H8300), 5) + END IF + + LOCATE 10, 10 + IF spiTransfer16(&H8400) AND 1 THEN + PRINT "Y "; INT$(spiTransfer16(&H8500), 5) + END IF + + LOCATE 10, 20 + IF spiTransfer16(&H8600) AND 1 THEN + PRINT "Z "; INT$(spiTransfer16(&H8700), 5) + END IF + + PRINT "Temperature "; INT$(spiTransfer16(&H8800) / 2 - 30, 0); CHR$(248); "C " +WEND + +FUNCTION HexByte$ (I) + HexByte$ = RIGHT$("00" + HEX$(I), 2) +END FUNCTION + +FUNCTION INT$ (I, L) + R$ = LTRIM$(STR$(I)) + IF L <> 0 THEN + R$ = RIGHT$(STRING$(L, " ") + R$, L) + END IF + INT$ = R$ +END FUNCTION + +FUNCTION ch32Available + ch32Available = (INP(&HF0) AND 1) +END FUNCTION + +FUNCTION ch32VersionMinor + ch32VersionMinor = INP(&HFE) +END FUNCTION + +FUNCTION ch32VersionMajor + ch32VersionMajor = INP(&HFF) +END FUNCTION + +SUB spiMode (mode) + OUT &HF9, mode +END SUB + +SUB spiClock (clock) + OUT &HFA, 50 +END SUB + +FUNCTION spiTransfer8 (I) + OUT &HFB, I + T = TIMER + WHILE TIMER - T < .1: WEND + spiTransfer8 = INP(&HFB) +END FUNCTION + +FUNCTION spiTransfer16 (I) + OUT &HFC, (I MOD 256) + OUT &HFD, (I \ 256) + T = TIMER + WHILE TIMER - T < .1: WEND + spiTransfer16 = INP(&HFC) + INP(&HFD) * 256 +END FUNCTION diff --git a/README.md b/README.md index fa07c56be..0cfb62676 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ You can support development by purchasing my own [development board](https://www.tindie.com/products/24612/) and [Serial Terminal](https://www.tindie.com/products/26801/). You may also support me donating hardware (boards, lab instruments, etc...). -================================================================================= +====================================================== License terms: @@ -25,7 +25,57 @@ Please contact fdivitto2013@gmail.com if you need a commercial license. **Please don't remove copyright and/or original author from FabGL examples (ie from screens, dialogs, etc..), even from derived works which use examples as base.** -================================================================================= +======================================== + +## How to install ## + +### Set up the Arduino environment ### + +1. Install and start Arduino IDE, get it from: https://www.arduino.cc/en/software + +2. Add the esp32 board support by Espressif using the Board Manager (Tools > Board > Board Manager and search for "esp32"). Use the package called “esp32” by Esoressif Systems and use the version selector to load a version 2.x.x! IMPORTANT! At the time of writing this, the FabGL library is NOT compatible with version 3.x.x or newer of the package. From the drop-down menu select version 2.0.x, we used 2.0.11. + +3. The Olimex fork of FabGL adds support for the ESP32-SBC-FabGL board and needs to be installed as a local library. You'll also need to first uninstall Fabrizio's FabGL library if it's already installed. + - Go to the Olimex FabGLrepo at GitHub: https://github.com/OLIMEX/FabGL + - Click "Download ZIP" from the green "Code" drop-down button, top-right. Save the ZIP file as "Olimex-FabGL.zip" so you don't get confused with Fabrizio's library and repo. + - Unzip and copy the contents to a new folder in the "Documents\Arduino\libraries" folder (e.g. "C:\Users\username\Documents\Arduino\libraries\Olimex-FabGL"). + +4. Close Arduino IDE. The next time the IDE is started, the local Olimex FabGL library will be available for use. + +### Compile and download examples to ESP32-SBC-FabGL ### + +1. Connect the ESP32-SBC-FabGL board to your desktop PC using a USB cable. The USB-C port on the board serves as both power and data. + +2. Start Arduino IDE. + +3. Verify the Olimex FabGL library is loaded by: + + - Opening the Library Manager (Tools > Manage Libraries). + - Typing "FabGL" in to the "filter" textbox. + - Changing the "Type" to "Installed". + - It should list "FabGL 1.0.9" (at the time of this writing) as one of the installed libraries. + +5. Select a FabGL demo from (File > Examples > FabGL). The FabGL examples will be at the bottom of a lengthy list. + +6. Configure the board. + - Select the "ESP32 Dev Module" board (Tools > Board > esp32 > ESP32 Dev Module). + - Set the board port to upload to (Tools > Port > COM#). + - Set the partition scheme (Tools > Partition > Huge APP). + - Disable PSRAM (Tools > PSRAM > Disabled). + - Set the Upload Speed (Tools > Upload Speed > 921600). + - If an upload error occurs, lower the transfer speed. + +8. Edit the demo if needed. Most demos are well-commented on what has to be edited. Some demos require also preparing an SD card or else in specific manner. + +9. Compile and upload to the board (Sketch > Upload). + + - If all goes well, the ESP32-SBC-FabGL board will reboot after the compilation and upload complete. + - If the wireless parameters were not set, a prompt asking to configure the wireless connection will appear. + - After that, the boot menu should show. + +If you have compilation problems with most of the examples you probably installed latest version of espressif package for ESP32. The most important part is to use ESP32 package version older than 3.x.x ESP32 package (we used Espressif 2.0.11). Also do NOT use the “Arduino ESP32 Boards” package! Use the “esp32” package by espressif systems! + +======================================= FabGL is mainly a Graphics Library for ESP32. It implements several display drivers (VGA output, PAL/NTSC Color Composite, I2C and SPI displays). diff --git a/examples/VGA/HardwareTest/HardwareTest.ino b/examples/VGA/HardwareTest/HardwareTest.ino index 93465858c..44d9ead24 100644 --- a/examples/VGA/HardwareTest/HardwareTest.ino +++ b/examples/VGA/HardwareTest/HardwareTest.ino @@ -38,6 +38,7 @@ #include "fabgl.h" #include "fabui.h" #include "devdrivers/MCP23S17.h" +#include "devdrivers/CH32V003.h" #include #include "esp_wifi.h" @@ -70,12 +71,15 @@ struct TestApp : public uiApp { uiLabel * extgpioLabel[16]; uiButton * extgpioPlay; uiLabel * extIntLabel; + uiLabel * extPwrLabel; + uiLabel * extBatLabel; int gpioIn, gpioOut; bool gpioInPrevState; int intOffDelay; fabgl::MCP23S17 mcp; + CH32V003 ch32v003; ~TestApp() { @@ -225,6 +229,55 @@ struct TestApp : public uiApp { y += 50; } + if (initCH32V003()) { + extPwrLabel = new uiLabel(frame, "PWR", Point(393, y - 45), Size(60, 22)); + extPwrLabel->labelStyle().textAlign = uiHAlign::Center; + extPwrLabel->labelStyle().backgroundColor = RGB888(64, 0, 0); + + extBatLabel = new uiLabel(frame, "BAT", Point(462, y - 45), Size(80, 22)); + extBatLabel->labelStyle().textAlign = uiHAlign::Center; + extBatLabel->labelStyle().backgroundColor = RGB888(64, 00, 0); + + new uiStaticLabel(frame, "EXT GPIO TEST:", Point(10, y)); + new uiStaticLabel(frame, "Outputs", Point(110, y - 13)); + new uiStaticLabel(frame, "Inputs", Point(200, y - 13)); + constexpr int w = 20; + constexpr int h = 22; + for (int i = GPIO_0; i <= GPIO_7; ++i) { + extgpioLabel[i] = new uiLabel(frame, "", Point(110 + i * (w + 2), y + 3), Size(w, h)); + extgpioLabel[i]->labelStyle().textAlign = uiHAlign::Center; + extgpioLabel[i]->setTextFmt("%c%d", 'V', i); + if (i > GPIO_3) { + // configure as IN + ch32v003.configureUEXT(i, DIRECTION_IN, PULL_DOWN); + extgpioLabel[i]->labelStyle().backgroundColor = RGB888(64, 0, 0); + // generate a flag/interrupt whenever input changes + ch32v003.enableUEXTInterrupt(i, FRONT_CHANGE); + } else { + // configure as OUT + ch32v003.configureUEXT(i, DIRECTION_OUT); + extgpioLabel[i]->labelStyle().backgroundColor = RGB888(0, 64, 0); + auto pch32v003 = &ch32v003; + extgpioLabel[i]->onClick = [=]() { + extGPIOSet(i, !pch32v003->readUEXT(i)); + }; + } + } + + // IO_EXP_IRQ pin + extIntLabel = new uiLabel(frame, "INT", Point(462, y + 3), Size(25, h)); + extIntLabel->labelStyle().textAlign = uiHAlign::Center; + extIntLabel->labelStyle().backgroundColor = RGB888(64, 64, 0); + + pinMode(IO_EXP_IRQ, INPUT); + + // play button + extgpioPlay = new uiButton(frame, "Play", Point(500, y + 3), Size(42, 22)); + extgpioPlay->onClick = [&]() { playExtGPIOS(); }; + + updatePower(); + y += 50; + } // wifi new uiStaticLabel(frame, "WIFI TEST:", Point(10, y)); @@ -265,6 +318,20 @@ struct TestApp : public uiApp { } } + void updatePower() { + if (ch32v003.powerSense()) { + extPwrLabel->labelStyle().backgroundColor = RGB888(0, 255, 0); + extBatLabel->labelStyle().backgroundColor = RGB888( 64, 0, 0); + extBatLabel->setText("BAT"); + } else { + extPwrLabel->labelStyle().backgroundColor = RGB888(64, 0, 0); + extBatLabel->labelStyle().backgroundColor = RGB888(0, 255, 0); + extBatLabel->setTextFmt("%d %%", ch32v003.batteryPercent()); + } + extPwrLabel->repaint(); + extBatLabel->repaint(); + } + void updateGPIOInState() { // read internal GPIOs if (gpioInPrevState != digitalRead(gpioIn)) { @@ -291,14 +358,46 @@ struct TestApp : public uiApp { } } } + + if (ch32v003.available()) { + if (digitalRead(IO_EXP_IRQ) == HIGH) { + extIntLabel->labelStyle().backgroundColor = RGB888(255, 255, 0); + extIntLabel->repaint(); + updatePower(); + intOffDelay = 2; + } else if (--intOffDelay == 0) { + extIntLabel->labelStyle().backgroundColor = RGB888(64, 64, 0); + extIntLabel->repaint(); + } + if (ch32v003.getUEXTIntFlags()) { + // read GPIOs + for (int i = GPIO_4; i <= GPIO_7; ++i) { + extgpioLabel[i]->labelStyle().backgroundColor = ch32v003.readUEXT(i) ? RGB888(255, 0, 0) : RGB888(64, 0, 0); + extgpioLabel[i]->repaint(); + } + } + } } bool initMCP() { return mcp.begin(); } + bool initCH32V003() { + if (ch32v003.begin()) { + ch32v003.initUEXT(); + return true; + } + return false; + } + void extGPIOSet(int gpio, bool value) { - mcp.writeGPIO(gpio, value); + if (mcp.available()) { + mcp.writeGPIO(gpio, value); + } + if (ch32v003.available()) { + ch32v003.writeUEXT(gpio, value); + } extgpioLabel[gpio]->labelStyle().backgroundColor = value ? RGB888(0, 255, 0) : RGB888(0, 64, 0); extgpioLabel[gpio]->repaint(); } @@ -307,44 +406,88 @@ struct TestApp : public uiApp { showWindow(extgpioPlay, false); // single left<->right for (int j = 0; j < 4; ++j) { - for (int i = MCP_A0; i <= MCP_B2; ++i) { - extGPIOSet(i, true); - if (i > MCP_A0) - extGPIOSet(i - 1, false); - processEvents(); - delay(80); + if (mcp.available()) { + for (int i = MCP_A0; i <= MCP_B2; ++i) { + extGPIOSet(i, true); + if (i > MCP_A0) + extGPIOSet(i - 1, false); + processEvents(); + delay(80); + } + for (int i = MCP_B2; i >= MCP_A0; --i) { + extGPIOSet(i, true); + if (i < MCP_B2) + extGPIOSet(i + 1, false); + processEvents(); + delay(80); + } } - for (int i = MCP_B2; i >= MCP_A0; --i) { - extGPIOSet(i, true); - if (i < MCP_B2) - extGPIOSet(i + 1, false); - processEvents(); - delay(80); + if (ch32v003.available()) { + for (int i = GPIO_0; i <= GPIO_3; ++i) { + extGPIOSet(i, true); + if (i > GPIO_0) + extGPIOSet(i - 1, false); + processEvents(); + delay(80); + } + for (int i = GPIO_3; i >= GPIO_0; --i) { + extGPIOSet(i, true); + if (i < GPIO_3) + extGPIOSet(i + 1, false); + processEvents(); + delay(80); + } } } // all together for (int j = 0; j < 4; ++j) { - for (int i = MCP_A0; i <= MCP_B2; ++i) { - extGPIOSet(i, true); - processEvents(); - delay(80); + if (mcp.available()) { + for (int i = MCP_A0; i <= MCP_B2; ++i) { + extGPIOSet(i, true); + processEvents(); + delay(80); + } + for (int i = MCP_A0; i <= MCP_B2; ++i) { + extGPIOSet(i, false); + processEvents(); + delay(80); + } } - for (int i = MCP_A0; i <= MCP_B2; ++i) { - extGPIOSet(i, false); - processEvents(); - delay(80); + if (ch32v003.available()) { + for (int i = GPIO_0; i <= GPIO_3; ++i) { + extGPIOSet(i, true); + processEvents(); + delay(80); + } + for (int i = GPIO_0; i <= GPIO_3; ++i) { + extGPIOSet(i, false); + processEvents(); + delay(80); + } } } // flashing for (int j = 0; j < 10; ++j) { - for (int i = MCP_A0; i <= MCP_B2; ++i) - extGPIOSet(i, true); - processEvents(); - delay(120); - for (int i = MCP_A0; i <= MCP_B2; ++i) - extGPIOSet(i, false); - processEvents(); - delay(120); + if (mcp.available()) { + for (int i = MCP_A0; i <= MCP_B2; ++i) + extGPIOSet(i, true); + processEvents(); + delay(120); + for (int i = MCP_A0; i <= MCP_B2; ++i) + extGPIOSet(i, false); + processEvents(); + delay(120); + } + if (ch32v003.available()) { + for (int i = GPIO_0; i <= GPIO_3; ++i) + extGPIOSet(i, true); + processEvents(); + delay(120); + for (int i = GPIO_0; i <= GPIO_3; ++i) + extGPIOSet(i, false); + processEvents(); + delay(120); + } } showWindow(extgpioPlay, true); } @@ -352,7 +495,14 @@ struct TestApp : public uiApp { void testSD() { // disable MCP (just because SD should be initialized before MCP) bool mcpAvailable = mcp.available(); - mcp.end(); + if (mcpAvailable) + mcp.end(); + + // disable CH32V003 (just because SD should be initialized before CH32V003) + bool ch32v003Available = ch32v003.available(); + if (ch32v003Available) + ch32v003.end(); + // mount test FileBrowser fb; fb.unmountSDCard(); @@ -362,6 +512,8 @@ struct TestApp : public uiApp { sdResultLabel->setText("Mount Failed!"); if (mcpAvailable) initMCP(); + if (ch32v003Available) + initCH32V003(); return; } // write test @@ -398,6 +550,8 @@ struct TestApp : public uiApp { fb.unmountSDCard(); if (mcpAvailable) initMCP(); + if (ch32v003Available) + initCH32V003(); if (!f || !ok) { sdResultLabel->labelStyle().textColor = Color::BrightRed; sdResultLabel->setText("Read Failed!"); diff --git a/examples/VGA/PCEmulator/bios.h b/examples/VGA/PCEmulator/bios.h index 1b45644fb..2325ceba0 100644 --- a/examples/VGA/PCEmulator/bios.h +++ b/examples/VGA/PCEmulator/bios.h @@ -174,6 +174,3 @@ class BIOS { }; - - - diff --git a/examples/VGA/PCEmulator/machine.cpp b/examples/VGA/PCEmulator/machine.cpp index 7e822c5df..bc42415cf 100644 --- a/examples/VGA/PCEmulator/machine.cpp +++ b/examples/VGA/PCEmulator/machine.cpp @@ -82,7 +82,28 @@ #define EXTIO_CONFIG_AVAILABLE 0x01 // 1 = external IO available, 0 = not available #define EXTIO_CONFIG_INT_POLARITY 0x02 // 1 = positive polarity, 0 = negative polarity (default) +// I/O expander (based on CH32V003) ports +#define CH32_STATUS 0x00f0 // 1 - CH32V003 available; 0 - CH32V003 NOT available; + +#define CH32_GPIO_SELECT 0x00f1 // GPIO select - (0 - UEXT pin 3; 1 - UEXT pin 4; ... 7 - UEXT pin 10) +#define CH32_GPIO_CONFIG 0x00f2 // bit 0 - direction (0 - output; 1 - input); bit 1 - (0 - pulldown; 1 - pullup) +#define CH32_GPIO_LEVEL 0x00f3 // R/W selecter GPIO level get/set + +#define CH32_I2C_CLOCK_L 0x00f4 // I2C clock in KHz - LSB (up to 400 KHz) +#define CH32_I2C_CLOCK_H 0x00f5 // I2C clock in KHz - MSB (up to 400 KHz) +#define CH32_I2C_ADDRESS 0x00f6 // I2C slave address from 0 to 127 +#define CH32_I2C_REGISTER 0x00f7 // I2C slave register +#define CH32_I2C_DATA 0x00f8 // I2C slave register value + +#define CH32_SPI_MODE 0x00f9 // SPI mode 0 - 3 +#define CH32_SPI_CLOCK 0x00fa // SPI clock up to 50 KHz +#define CH32_SPI_DATA 0x00fb // SPI 8-bit transfer data +#define CH32_SPI_DATA_L 0x00fc // SPI 16-bit transfer data LSB +#define CH32_SPI_DATA_H 0x00fd // SPI 16-bit transfer data MSB + +#define CH32_VERSION_L 0x00fe // CH32V003 firmware version - minor +#define CH32_VERSION_H 0x00ff // CH32V003 firmware version - major ////////////////////////////////////////////////////////////////////////////////////// // Machine @@ -149,6 +170,16 @@ void Machine::init() m_MCP23S17.begin(); m_MCP23S17Sel = 0; + m_CH32V003.begin(); + m_CH32V003_GPIO_Sel = 0; + m_CH32V003_I2C_Clock = 0; + m_CH32V003_I2C_Address = 0; + m_CH32V003_I2C_Register = 0; + m_CH32V003_SPI_Mode = 0; + m_CH32V003_SPI_Clock = 0; + m_CH32V003_SPI_Data8 = 0; + m_CH32V003_SPI_Data16 = 0; + m_BIOS.init(this); i8086::setCallbacks(this, readPort, writePort, writeVideoMemory8, writeVideoMemory16, readVideoMemory8, readVideoMemory16, interrupt); @@ -330,7 +361,7 @@ void Machine::runTask(void * pvParameters) #endif i8086::step(); - m->tick(); + m->tick(); } } @@ -667,6 +698,71 @@ void Machine::writePort(void * context, int address, uint8_t value) m->m_MCP23S17.writeGPIO(m->m_MCP23S17Sel, value); break; + // I/O expander CH32V003 GPIO + case CH32_GPIO_SELECT: + m->m_CH32V003_GPIO_Sel = value & 0x7; + break; + + case CH32_GPIO_CONFIG: + m->m_CH32V003.configureUEXT(m->m_CH32V003_GPIO_Sel, value & 1, (value & 2) != 0); + break; + + case CH32_GPIO_LEVEL: + m->m_CH32V003.writeUEXT(m->m_CH32V003_GPIO_Sel, value); + break; + + // I/O expander CH32V003 I2C + case CH32_I2C_CLOCK_L: + m->m_CH32V003_I2C_Clock &= (uint16_t)0xFF00; + m->m_CH32V003_I2C_Clock |= (uint16_t)value; + m->m_CH32V003.configureI2C(m->m_CH32V003_I2C_Clock * 1000); + break; + + case CH32_I2C_CLOCK_H: + m->m_CH32V003_I2C_Clock &= (uint16_t)0x00FF; + m->m_CH32V003_I2C_Clock |= (uint16_t)(value << 8); + m->m_CH32V003.configureI2C(m->m_CH32V003_I2C_Clock * 1000); + break; + + case CH32_I2C_ADDRESS: + m->m_CH32V003_I2C_Address = value; + break; + + case CH32_I2C_REGISTER: + m->m_CH32V003_I2C_Register = value; + break; + + case CH32_I2C_DATA: + m->m_CH32V003.writeRegI2C(m->m_CH32V003_I2C_Address, m->m_CH32V003_I2C_Register, value); + break; + + // I/O expander CH32V003 SPI + case CH32_SPI_MODE: + m->m_CH32V003_SPI_Mode = value; + m->m_CH32V003.configureSPI(m->m_CH32V003_SPI_Mode, m->m_CH32V003_SPI_Clock * 1000); + break; + + case CH32_SPI_CLOCK: + m->m_CH32V003_SPI_Clock = value; + m->m_CH32V003.configureSPI(m->m_CH32V003_SPI_Mode, m->m_CH32V003_SPI_Clock * 1000); + break; + + + case CH32_SPI_DATA: + m->m_CH32V003.transferSPI8(&value, &m->m_CH32V003_SPI_Data8, 1); + break; + + case CH32_SPI_DATA_L: + m->m_CH32V003_SPI_Data16 &= (uint16_t)0xFF00; + m->m_CH32V003_SPI_Data16 |= (uint16_t)value; + break; + + case CH32_SPI_DATA_H: + m->m_CH32V003_SPI_Data16 &= (uint16_t)0x00FF; + m->m_CH32V003_SPI_Data16 |= (uint16_t)(value << 8); + m->m_CH32V003.transferSPI16(&m->m_CH32V003_SPI_Data16, &m->m_CH32V003_SPI_Data16, 1); + break; + default: //printf("OUT %04x=%02x\n", address, value); break; @@ -793,6 +889,55 @@ uint8_t Machine::readPort(void * context, int address) case EXTIO_GPIO: return m->m_MCP23S17.readGPIO(m->m_MCP23S17Sel); + // I/O expander CH32V003 + case CH32_STATUS: + return m->m_CH32V003.available(); + + case CH32_VERSION_L: + return (uint8_t)(m->m_CH32V003.version() & 0xFF); + + case CH32_VERSION_H: + return (uint8_t)(m->m_CH32V003.version() >> 8); + + // I/O expander CH32V003 GPIO + case CH32_GPIO_SELECT: + return m->m_CH32V003_GPIO_Sel; + + case CH32_GPIO_LEVEL: + return m->m_CH32V003.readUEXT(m->m_CH32V003_GPIO_Sel); + + // I/O expander CH32V003 I2C + case CH32_I2C_CLOCK_L: + return (uint8_t)(m->m_CH32V003_I2C_Clock & 0x00FF); + + case CH32_I2C_CLOCK_H: + return (uint8_t)((m->m_CH32V003_I2C_Clock >> 8) & 0x00FF); + + case CH32_I2C_ADDRESS: + return m->m_CH32V003_I2C_Address; + + case CH32_I2C_REGISTER: + return m->m_CH32V003_I2C_Register; + + case CH32_I2C_DATA: + return m->m_CH32V003.readRegI2C(m->m_CH32V003_I2C_Address, m->m_CH32V003_I2C_Register); + + // I/O expander CH32V003 SPI + case CH32_SPI_MODE: + return m->m_CH32V003_SPI_Mode; + + case CH32_SPI_CLOCK: + return m->m_CH32V003_SPI_Clock; + + case CH32_SPI_DATA: + return m->m_CH32V003_SPI_Data8; + + case CH32_SPI_DATA_L: + return (uint8_t)(m->m_CH32V003_SPI_Data16 & 0x00FF); + + case CH32_SPI_DATA_H: + return (uint8_t)((m->m_CH32V003_SPI_Data16 >> 8) & 0x00FF); + } //printf("IN %04X\n", address); diff --git a/examples/VGA/PCEmulator/machine.h b/examples/VGA/PCEmulator/machine.h index ba5f900bd..e050e77b7 100644 --- a/examples/VGA/PCEmulator/machine.h +++ b/examples/VGA/PCEmulator/machine.h @@ -36,6 +36,7 @@ #include "emudevs/MC146818.h" #include "emudevs/PC8250.h" #include "devdrivers/MCP23S17.h" +#include "devdrivers/CH32V003.h" #include "bios.h" @@ -237,6 +238,19 @@ class Machine { MCP23S17 m_MCP23S17; uint8_t m_MCP23S17Sel; + // extended I/O (CH32V003) + CH32V003 m_CH32V003; + uint8_t m_CH32V003_GPIO_Sel; + + uint16_t m_CH32V003_I2C_Clock; + uint8_t m_CH32V003_I2C_Address; + uint8_t m_CH32V003_I2C_Register; + + uint8_t m_CH32V003_SPI_Mode; + uint8_t m_CH32V003_SPI_Clock; + uint8_t m_CH32V003_SPI_Data8; + uint16_t m_CH32V003_SPI_Data16; + uint8_t m_bootDrive; SysReqCallback m_sysReqCallback; diff --git a/examples/VGA/SpaceInvaders/SpaceInvaders.ino b/examples/VGA/SpaceInvaders/SpaceInvaders.ino index 2fd206d1e..0e8dd17b0 100644 --- a/examples/VGA/SpaceInvaders/SpaceInvaders.ino +++ b/examples/VGA/SpaceInvaders/SpaceInvaders.ino @@ -27,6 +27,8 @@ #include "fabgl.h" #include "fabutils.h" +#include "wiiNunchuk.h" + #include "sprites.h" #include "sounds.h" @@ -39,6 +41,7 @@ using fabgl::iclamp; fabgl::VGAController DisplayController; fabgl::Canvas canvas(&DisplayController); fabgl::PS2Controller PS2Controller; +fabgl::WiiNunchuk wii_nunchuk; SoundGenerator soundGenerator; @@ -52,7 +55,7 @@ struct IntroScene : public Scene { static const int TEXT_X = 130; static const int TEXT_Y = 122; - static int controller_; // 1 = keyboard, 2 = mouse + static int controller_; // 1 = keyboard, 2 = mouse, 3 = wii_nunchuk int textRow_ = 0; int textCol_ = 0; @@ -134,6 +137,8 @@ struct IntroScene : public Scene { canvas.drawText(80, 75, "Press [SPACE] to Play"); else if (mouse && mouse->isMouseAvailable()) canvas.drawText(105, 75, "Click to Play"); + else if (wii_nunchuk.isAvailable()) + canvas.drawText(105, 75, "Press a button to Play"); } // handle keyboard or mouse (after two seconds) @@ -142,6 +147,13 @@ struct IntroScene : public Scene { controller_ = 1; // select keyboard as controller else if (mouse && mouse->isMouseAvailable() && mouse->deltaAvailable() && mouse->getNextDelta(nullptr, 0) && mouse->status().buttons.left) controller_ = 2; // select mouse as controller + else if (wii_nunchuk.isAvailable()) { + fabgl::WiiNunchukStatus wii_status = wii_nunchuk.getStatus(); + if (wii_status.buttons.c || wii_status.buttons.z) { + controller_ = 3; // select wii_nunchuk as controller + } + } + starting_ = (controller_ > 0); // start only when a controller has been selected } } @@ -380,6 +392,8 @@ struct GameScene : public Scene { canvas.drawText(110, 100, "Press [SPACE]"); else if (IntroScene::controller_ == 2) canvas.drawText(93, 100, "Click to continue"); + else if (IntroScene::controller_ == 3) + canvas.drawText(93, 100, "Press a button"); // change state gameState_ = GAMESTATE_GAMEOVER; level_ = 1; @@ -473,8 +487,8 @@ struct GameScene : public Scene { gameState_ = GAMESTATE_PLAYING; } } - } else if (IntroScene::controller_ == 1 && playerVelX_ != 0) { - // move player using Keyboard + } else if ((IntroScene::controller_ == 1 || IntroScene::controller_ == 3) && playerVelX_ != 0) { + // move player using Keyboard or WiiNunchuk player_->x += playerVelX_; player_->x = iclamp(player_->x, 0, getWidth() - player_->getWidth()); updateSprite(player_); @@ -542,6 +556,14 @@ struct GameScene : public Scene { if (delta.buttons.left && !playerFire_->visible) // player fire? fire(); } + } else if (IntroScene::controller_ == 3) { + // WiiNunchuk controller + fabgl::WiiNunchukStatus wii_status = wii_nunchuk.getStatus(); + int joystick_x = wii_status.joystick.x / 48; + int wii_button = wii_status.buttons.c || wii_status.buttons.z; + playerVelX_ = joystick_x; + if (wii_button && !playerFire_->visible) // player fire? + fire(); } } @@ -563,8 +585,12 @@ struct GameScene : public Scene { player_->setFrame( player_->getFrameIndex() == 1 ? 2 : 1); // wait for SPACE or click from mouse - if ((IntroScene::controller_ == 1 && keyboard->isVKDown(fabgl::VK_SPACE)) || - (IntroScene::controller_ == 2 && mouse->deltaAvailable() && mouse->getNextDelta(nullptr, 0) && mouse->status().buttons.left)) { + if ((IntroScene::controller_ == 1 && keyboard->isVKDown(fabgl::VK_SPACE)) + || + (IntroScene::controller_ == 2 && mouse->deltaAvailable() && mouse->getNextDelta(nullptr, 0) && mouse->status().buttons.left) + || + (IntroScene::controller_ == 3 && wii_nunchuk.isAvailable() && (wii_nunchuk.getStatus().buttons.c || wii_nunchuk.getStatus().buttons.z)) + ) { stop(); DisplayController.removeSprites(); } @@ -661,9 +687,10 @@ int GameScene::score_ = 0; void setup() { PS2Controller.begin(PS2Preset::KeyboardPort0_MousePort1, KbdMode::GenerateVirtualKeys); + wii_nunchuk.begin(300000); DisplayController.begin(); - DisplayController.setResolution(VGA_320x200_75Hz); + DisplayController.setResolution(QVGA_320x240_60Hz); // adjust this to center screen in your monitor //DisplayController.moveScreen(20, -2); diff --git a/src/devdrivers/CH32V003.cpp b/src/devdrivers/CH32V003.cpp new file mode 100644 index 000000000..eb419f08c --- /dev/null +++ b/src/devdrivers/CH32V003.cpp @@ -0,0 +1,922 @@ +/** + * Created by Olimex - + * Copyright (c) 2023 Olimex Ltd + * All rights reserved. + * + * + * Please contact info@olimex.com if you need a commercial license. + * + * + * This library and related software is available under GPL v3. + * + * CH32V003 driver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CH32V003 driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CH32V003 driver. If not, see . + */ + +#include "CH32V003.h" +#include + +#ifdef CH32V003_DEBUG_ENABLED + #define PROTOCOL_BYTE_DELAY() (ets_delay_us(5000)) + #define PROTOCOL_MSG_DELAY() (ets_delay_us(5000)) + + #define PROTOCOL_SYNC_DELAY() (ets_delay_us(10000)) +#else + #define PROTOCOL_BYTE_DELAY() + #define PROTOCOL_MSG_DELAY() + + #define PROTOCOL_SYNC_DELAY() (ets_delay_us(500)) +#endif + +// Delay for ADC measurement +static uint32_t ADC_Delay = 150; + +// Delay for each byte when using I2C +// It is calculated at run time when I2C clock is set +static uint32_t I2C_Delay = 0; + +// Delay for each byte when using SPI +// It is calculated at run time when SPI clock is set +static uint32_t SPI_Delay = 0; + +// Delay for each byte when using UART +// It is calculated at run time when UART baudrate is set +static uint32_t UART_Delay = 0; +// ------------------------------------------- public ------------------------------------------- + +CH32V003::CH32V003 () : + CH_MISO(GPIO_NUM_NC), + CH_MOSI(GPIO_NUM_NC), + CH_CLK(GPIO_NUM_NC), + CH_CS(GPIO_NUM_NC), + CH_SPIHost(HSPI_HOST), + CH_SPIDevHandle(nullptr), + spi_acquired(0), + synced(false) +{ +} + +CH32V003::~CH32V003 () +{ + end(); +} + +bool CH32V003::available() +{ + return CH_SPIDevHandle != nullptr && synced; +} + +bool CH32V003::begin (gpio_num_t MISO, gpio_num_t MOSI, gpio_num_t CLK, gpio_num_t CS, int16_t CSActiveState, int16_t host) +{ + if (CSActiveState == -1) + CSActiveState = 1; + + CH_MISO = MISO; + CH_MOSI = MOSI; + CH_CLK = CLK; + CH_CS = CS; + CH_SPIHost = (spi_host_device_t) host; + + spi_bus_config_t busconf = { }; // zero init + busconf.mosi_io_num = CH_MOSI; + busconf.miso_io_num = CH_MISO; + busconf.sclk_io_num = CH_CLK; + busconf.quadwp_io_num = -1; + busconf.quadhd_io_num = -1; + busconf.flags = SPICOMMON_BUSFLAG_MASTER; + + auto result = spi_bus_initialize(CH_SPIHost, &busconf, CH_DMACHANNEL); + if (result == ESP_OK || result == ESP_ERR_INVALID_STATE) { + // ESP_ERR_INVALID_STATE, maybe spi_bus_initialize already called + spi_device_interface_config_t devconf = { }; // zero init + devconf.mode = 0; + devconf.clock_speed_hz = CH_SPI_FREQ; + devconf.spics_io_num = CH_CS; + devconf.flags = (CSActiveState == 1 ? SPI_DEVICE_POSITIVE_CS : 0); + devconf.queue_size = 1; + + result = spi_bus_add_device(CH_SPIHost, &devconf, &CH_SPIDevHandle); + if (result == ESP_OK) { + sync(); + if (synced) version(); + return synced; + } + } + + end (); + return false; +} + +void CH32V003::end() +{ + synced = false; + if (CH_SPIDevHandle) { + spi_bus_remove_device(CH_SPIDevHandle); + spi_bus_free(CH_SPIHost); + CH_SPIDevHandle = nullptr; + } +} + +/** + * Sample protocol message version + * + * | cmd | size | major | minor | + * | version | 0x02 | | | + * | 10111101 | 00000010 | 00000001 | 00000000 | + */ +uint16_t CH32V003::version() +{ + if (firmware_ver != 0x0000) { + return firmware_ver; + } + uint8_t spi_send[] = {0xBD, 0x02, 0, 0}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + uint8_t spi_receive[msg_size]; + transferProtocol (spi_send, spi_receive, msg_size); + uint16_t ver = (spi_receive[2] << 8) | spi_receive[3]; + if (ver == ((SYNC_RESPONSE << 8) | SYNC_RESPONSE)) { + ver = 0x0009; + } + firmware_ver = ver; + return firmware_ver; +} + +void CH32V003::uextPowerEnable() +{ + setGPIO(GPIO_PORTC, GPIO_3, 0); +} + +void CH32V003::uextPowerDisable() +{ + setGPIO(GPIO_PORTC, GPIO_3, 1); +} + +// ------------------------------------------- GPIO ------------------------------------------- + +/** + * Sample protocol message configure GPIO port + * + * | mode cmd dir | size | port | mask | dir | pullup | + * | GPIO init out | 0x04 | PortA | Pin2 and Pin0 | Pin2 in Pin0 out | no pullups | + * | 00 00001 0 | 00000100 | 00000001 | 00000101 | 00000100 | 00000000 | + */ +void CH32V003::configurePort(uint8_t port, uint8_t mask, uint8_t in_out, uint8_t pullup) +{ + uint8_t spi_send[] = {protocolHeader(MODE_GPIO, CMD_PORT_INIT, DIRECTION_OUT), 0x04, port, mask, in_out, pullup}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + transferProtocol (spi_send, nullptr, msg_size); +} + +/** + * Sample protocol message set GPIO port + * + * | mode cmd dir | size | port | mask | value | + * | GPIO set out | 0x03 | PortC | Pin2 | 1 | + * | 00 00010 0 | 00000011 | 00000011 | 00000100 | 00000100 | + */ +void CH32V003::setPort (uint8_t port, uint8_t mask, uint8_t value) +{ + uint8_t spi_send[] = {protocolHeader(MODE_GPIO, CMD_PORT_SET, DIRECTION_OUT), 0x03, port, mask, value}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + transferProtocol (spi_send, nullptr, msg_size); +} + +/** + * Sample protocol message get GPIO port + * + * | mode cmd dir | size | port | mask | value | + * | GPIO get in | 0x03 | PortD | Pin2 | 0 | + * | 00 00011 1 | 00000011 | 00000100 | 00000100 | 00000000 | + */ +uint8_t CH32V003::getPort(uint8_t port, uint8_t mask) +{ + uint8_t spi_send[] = {protocolHeader(MODE_GPIO, CMD_PORT_GET, DIRECTION_IN), 0x03, port, mask}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + + acquireSPI(); + transferProtocol (spi_send, nullptr, msg_size); + uint8_t data = transferByte(0x00); + releaseSPI(); + + return data; +} + +void CH32V003::configureGPIO (uint8_t port, uint8_t gpio, uint8_t dir, uint8_t pullup) +{ + configurePort (port, 1< BATTERY_MAX_MV) + mV = BATTERY_MAX_MV; + if (mV < BATTERY_MIN_MV) + mV = BATTERY_MIN_MV; + return (mV - BATTERY_MIN_MV) * 100 / (BATTERY_MAX_MV - BATTERY_MIN_MV); +} + +/** + * Sample protocol message set interrupt active + * + * | mode cmd dir | size | level | + * | GPIO int active out | 0x01 | high | + * | 00 10001 0 | 00000001 | 00000001 | + */ +void CH32V003::setIntActive(uint8_t level) +{ + uint8_t spi_send[] = {protocolHeader(MODE_GPIO, CMD_INT_ACTIVE, DIRECTION_OUT), 0x01, level}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + transferProtocol (spi_send, nullptr, msg_size); +} + +/** + * Sample protocol message enable interrupt + * + * | mode cmd dir | size | port | pin | trigger | + * | GPIO int enable out | 0x03 | A | 1 | on change | + * | 00 10010 0 | 00000011 | 00000001 | 00000001 | 00000011 | + */ +void CH32V003::enableInterrupt(uint8_t port, uint8_t pin, GPIO_INT_Trigger trigger) +{ + uint8_t spi_send[] = {protocolHeader(MODE_GPIO, CMD_INT_ENABLE, DIRECTION_OUT), 0x03, port, pin, (uint8_t)trigger}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + transferProtocol (spi_send, nullptr, msg_size); +} + +/** + * Sample protocol message disable interrupt + * + * | mode cmd dir | size | port | pin | + * | GPIO int disable out | 0x02 | C | 2 | + * | 00 10011 0 | 00000010 | 00000011 | 00000010 | + */ +void CH32V003::disableInterrupt(uint8_t port, uint8_t pin) +{ + uint8_t spi_send[] = {protocolHeader(MODE_GPIO, CMD_INT_DISABLE, DIRECTION_OUT), 0x02, port, pin}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + transferProtocol (spi_send, nullptr, msg_size); +} + +/** + * Sample protocol get interrupt flags (interrupt IS NOT cleared) + * + * | mode cmd dir | size | port | data | + * | GPIO int flags in | 0x02 | D | | + * | 00 10100 1 | 00000010 | 00000100 | 00000000 | + */ +uint8_t CH32V003::getPortIntFlags(uint8_t port) +{ + uint8_t spi_send[] = {protocolHeader(MODE_GPIO, CMD_INT_FLAGS, DIRECTION_IN), 0x02, port, 0}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + uint8_t spi_receive[msg_size]; + transferProtocol (spi_send, spi_receive, msg_size); + return spi_receive[msg_size-1]; +} + +/** + * Sample protocol get interrupt capture (interrupt IS cleared) + * + * | mode cmd dir | size | port | data | + * | GPIO int flags in | 0x02 | D | | + * | 00 10101 1 | 00000010 | 00000100 | 00000000 | + */ +uint8_t CH32V003::getPortIntCaptured(uint8_t port) +{ + uint8_t spi_send[] = {protocolHeader(MODE_GPIO, CMD_INT_CAPTURE, DIRECTION_IN), 0x02, port, 0}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + uint8_t spi_receive[msg_size]; + transferProtocol (spi_send, spi_receive, msg_size); + return spi_receive[msg_size-1]; +} + +// ------------------------------------------- I2C ------------------------------------------- + +/** + * Sample protocol message configure I2C + * + * | mode cmd dir | size | clock MSB | 1048576 | clock LSB | + * | I2C init out | 0x04 | | | | | + * | 01 00001 0 | 00000100 | 00000000 | 00010000 | 00000000 | 00000000 | + */ +void CH32V003::configureI2C (uint32_t clock) +{ + if (clock == 0) return; + + uint8_t spi_send[6] = {protocolHeader(MODE_I2C, CMD_I2C_INIT, DIRECTION_OUT), 0x04}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + + for (int i=0; i<4; i++) // byte 0 most significant BYTE, byte 3 least significant BYTE + spi_send[i+2] = (clock>>((3-i)*8)) & 0xFF; + transferProtocol (spi_send, nullptr, msg_size); + + // Microseconds needed for 1 byte to be transferred via I2C + // approximate time (in microseconds) for sending 1 byte of data through I2C + // Tbit = 1 000 000 / clock is the time for 1 bit of data + // Tbyte = 8 * Tbit = 8 * 1 000 000 / clock + // added 1 extra time for acknoledge bit + time for start and stop + some extra + I2C_Delay = 12000000 / clock; + if (version() < 0x0100) + I2C_Delay += 150; // 0.9v requires a little bit more delay between I2C bytes +} + +/** + * Sample protocol message write I2C + * + * | mode cmd dir | size | address | data 0 tx | data 1 tx | data 2 tx | + * | I2C write out | 0x04 | 0x10 | | | | + * | 01 00010 0 | 00000100 | 00010000 | 00000000 | 00000000 | 00000000 | + */ +void CH32V003::writeI2C (uint8_t address, uint8_t buffer[], uint8_t size) +{ + uint8_t spi_send[size+3] = {protocolHeader(MODE_I2C, CMD_I2C_WRITE, DIRECTION_OUT), size+1, address}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + + for (int i=0; i>((3-i)*8)) & 0xFF; + + transferProtocol (spi_send, nullptr, msg_size); + + uint32_t t = 1000000 / clock; // time for bit [us] + if (t < 20) { + t = 20; + } + // 8 bits * (time for bit + time for edges + time for calculating the data) + SPI_Delay = 8 * (t + 30 + 20); +} + +/** + * Sample protocol message transfer8 SPI + * + * | mode cmd dir | size | data 0 tx | data 1 tx | data 2 tx | | + * | SPI transfer8 out | 0x04 | | | | | + * | 10 00010 0 | 000000!0 | 00000000 | 00000000 | 00000000 | 00000000 | + * | | | | data 0 rx | data 1 rx | data 2 rx | + */ +void CH32V003::transferSPI8 (uint8_t tx_buffer[], uint8_t rx_buffer[], const uint8_t size) +{ + uint8_t spi_send[] = {protocolHeader(MODE_SPI, CMD_SPI_TRANSFER8, DIRECTION_OUT), size+1}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + + acquireSPI(); + transferProtocol (spi_send, nullptr, msg_size); + + uint8_t response; + for (int i=0; i<=size; i++) { + response = (i < size) ? + transferByte(tx_buffer[i]) + : + transferByte(0) + ; + + if (i > 0) { + rx_buffer[i-1] = response; + } + ets_delay_us(SPI_Delay); + } + releaseSPI(); +} + +/** + * Sample protocol message transfer16 SPI + * + * | mode cmd dir | size | data 0 tx | data 0 tx | data 1 tx | data 1 tx | | | + * | SPI transfer16 out | 0x06 | 0x00 | | | | | | + * | 10 00011 0 | 00000110 | 00000000 | 00000000 | 00000000 | 00000000 | 00000000 | 00000000 | + * | | | | | data 0 rx | data 0 rx | data 1 rx | data 1 rx | + */ +void CH32V003::transferSPI16 (uint16_t tx_buffer[], uint16_t rx_buffer[], const uint8_t size) +{ + uint8_t spi_send[] = {protocolHeader(MODE_SPI, CMD_SPI_TRANSFER16, DIRECTION_OUT), size*2+2}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + + acquireSPI(); + transferProtocol (spi_send, nullptr, msg_size); + + uint16_t response; + for (int i=0; i<=size; i++) { + response = (i < size) ? + transferWord(tx_buffer[i]) + : + transferWord(0) + ; + + if (i > 0) { + rx_buffer[i-1] = response; + } + ets_delay_us(SPI_Delay * 2); + } + releaseSPI(); +} + +// ------------------------------------------- UART ------------------------------------------- + +/** + * Sample protocol message configure UART + * + * | mode cmd dir | size | baud MSB | 115200 | baud LSB | stop_bits | parity | + * | UART init out | 0x06 | | | | | | | + * | 11 00001 0 | 00000110 | 00000000 | 00000001 | 11000010 | 00000000 | 00000001 | 00000000 | + */ +void CH32V003::configureUART (uint32_t baudrate, UART_StopBits stop_bits, UART_Parity parity) +{ + uint8_t spi_send[] = {protocolHeader(MODE_UART, CMD_UART_CONFIGURE, DIRECTION_OUT), 6, 0, 0, 0, 0, (uint8_t)stop_bits, (uint8_t)parity}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + + for (uint8_t i=0; i<4; i++) + { + spi_send[i+2] = (baudrate >> ((3-i)*8)) & 0xFF; + } + + transferProtocol (spi_send, nullptr, msg_size); + + // Time for 1 bit of data (in microseconds) = 1000000 / baudrate [us/bits] + // Delay (in us) for the whole byte = (start bit + 8 data bits + stop bit(s) + parity) * time for 1 bit [us] + UART_Delay = (1000000 / baudrate) * 10; +} + +/** + * Sample protocol message write UART + * + * | mode cmd dir | size | data 0 tx | data 1 tx | data 2 tx | data 3 tx | + * | UART write out | 0x04 | | | | | + * | 11 00010 0 | 00000100 | 00000000 | 00000000 | 00000000 | 00000000 | + */ +void CH32V003::writeUART (uint8_t buff[], uint8_t size) +{ + uint8_t spi_send[size+2] = {protocolHeader(MODE_UART, CMD_UART_WRITE, DIRECTION_OUT), size}; + const int msg_size = sizeof(spi_send)/sizeof(uint8_t); + + for (int i=0; i 0) { + spi_acquired--; + } + + if (spi_acquired == 0) { + spi_device_release_bus(CH_SPIDevHandle); + } +} + +uint8_t CH32V003::protocolHeader (uint8_t mode, uint8_t command, uint8_t direction) +{ + return ((mode&0x3)<<6) | ((command&0x1F)<<1) | (direction & 0x01); +} + +uint8_t CH32V003::transferByte (uint8_t send) +{ + if (CH_SPIDevHandle == nullptr) { + LOG_DEBUG("CH32V003 not available or not started" EOL); + return 0x00; + } + + uint8_t txdata[1] = { send }; + uint8_t rxdata[1]; + spi_transaction_t ta; + ta.flags = 0; + ta.length = 8; + ta.rxlength = 8; + ta.rx_buffer = rxdata; + ta.tx_buffer = txdata; + spi_device_transmit(CH_SPIDevHandle, &ta); + + return rxdata[0]; +} + +uint16_t CH32V003::transferWord (uint16_t send) +{ + uint16_t rx = transferByte(send >> 8) << 8; + PROTOCOL_BYTE_DELAY(); + + rx |= transferByte(send & 0xFF); + PROTOCOL_BYTE_DELAY(); + + return rx; +} + + +void CH32V003::transferProtocol(uint8_t txdata[], uint8_t rxdata[], uint16_t size) +{ + bool sync_detect; + + if (CH_SPIDevHandle == nullptr) { + if (rxdata != nullptr) { + for (int i=0; i SYNC_TIMEOUT) { + timeout = true; + break; + } + } while (response != SYNC_RESPONSE); + releaseSPI(); + + if (timeout) { + LOG_DEBUG("SYNC timeout." EOL); + } else { + LOG_DEBUG("SYNC done." EOL); + synced = true; + } +} diff --git a/src/devdrivers/CH32V003.h b/src/devdrivers/CH32V003.h new file mode 100644 index 000000000..db163dd12 --- /dev/null +++ b/src/devdrivers/CH32V003.h @@ -0,0 +1,625 @@ +/** + * Created by Olimex - + * Copyright (c) 2023 Olimex Ltd + * All rights reserved. + * + * + * Please contact info@olimex.com if you need a commercial license. + * + * + * This library and related software is available under GPL v3. + * + * CH32V003 driver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CH32V003 driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CH32V003 driver. If not, see . + */ + +#ifndef _CH32V003_H_ +#define _CH32V003_H_ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/timers.h" +#include +#include + +#include "hal/gpio_types.h" +#include "hal/spi_types.h" +#include "driver/spi_master.h" + +/** + * @file + * + * @brief This file contains the CH32V003 driver class + * + * This driver implements simple protocol from ESP32 to CH32V003 over SPI + * It allows to use pins located on UEXT connector as GPIO, I2C, SPI.or UART. + * + * It is NOT RECOMMENDED to use UEXT pins 5 and 6 as GPIOs. If you disable + * UEXT power by calling CH32V003::uextPowerDisable() they can not be used as + * I2C as well. + * + * Example usage: + * ----------------------------------------------------------------------------- + * + * CH32V003 expander; + * + * expander.begin(); + * + * expander.configureGPIO(UEXT_GPIO_10, DIRECTION_OUT); + * expander.configureGPIO(UEXT_GPIO_9, DIRECTION_IN, PULL_DOWN); + * expander.configureGPIO(UEXT_GPIO_3, DIRECTION_IN, PULL_UP); + * // same as + * expander.configureGPIO(GPIO_PORTD, GPIO_3, DIRECTION_OUT); + * expander.configureGPIO(GPIO_PORTD, GPIO_4, DIRECTION_IN, PULL_DOWN); + * expander.configureGPIO(GPIO_PORTD, GPIO_5, DIRECTION_IN, PULL_UP); + * // same as + * expander.configurePort( + * GPIO_PORTD, + * GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5, + * (GPIO_Pin_3 & PIN_OUT) | ((GPIO_Pin_4 | GPIO_Pin_5) & PIN_IN), + * (GPIO_Pin_4 & PIN_PULL_DOWN) | (GPIO_Pin_5 & PIN_PULL_UP) + * ); + * + * expander.setGPIO(GPIO_PORTD, GPIO_3, 1); + * uint8_t gpio4 = expander.getGPIO(UEXT_GPIO_9); + * + * expander.configureI2C(100000); + * uint8_t buff[5] = {1, 2, 3, 4, 5}; + * expander.writeI2C((0x15, buff, 5); + * expander.readI2C((0x15, buff, 5); + * + * expander.writeRegI2C(0x15, 0x10, 0x10); + * uint8_t reg = expander.readRegI2C(0x15, 0x10); + * + * Protocol description + * ----------------------------------------------------------------------------- + * 1 byte - Header + * 1 byte - Payload size + * 1-255 bytes - Payload + * + * Protocol sync + * ----------------------------------------------------------------------------- + * Master (ESP32) sends 0xAA and expects 0x55 as response from slave (CH32V003) + * + * Header description (most significant bits first) + * ----------------------------------------------------------------------------- + * 2 bits - mode + * GPIO = 00 + * I2C = 01 + * SPI = 10 + * UART = 11 + * + * 5 bits - command + * GPIO mode + * 00001 = Port Init + * 00010 = Port Get + * 00011 = Port Set + * + * 01001 = Power Sense + * 01010 = Battery Sense + * + * 10001 = Interrupt Set Active Level + * 10010 = Interrupt Enable + * 10011 = Interrupt Disable + * 10100 = Interrupt Get Flags + * 10101 = Interrupt Get Capture + * + * I2C mode + * 00001 = I2C Init + * 00010 = I2C Write + * 00011 = I2C Read + * 00100 = I2C Read Register + * + * SPI mode + * 00001 = SPI Init + * 00010 = SPI Transfer8 + * 00011 = SPI Transfer16 + * + * UART mode + * 00001 = UART Init + * 00010 = UART Write + * 00011 = UART Read + * + * 1 bit - direction + * 0 = out + * 1 = in + * + * Response to header byte must be 0xFA. If slave is out of sync then response + * will be 0x55 and master must initiate sync. + * + * For payload content description see comments in corresponding method impelemtation + * + */ + +//#define CH32V003_DEBUG_ENABLED +//#define LOG_DEBUG_ENABLED + +#ifdef LOG_DEBUG_ENABLED + #define LOG_DEBUG(f_, ...) printf((f_), ##__VA_ARGS__) +#else + #define LOG_DEBUG(f_, ...) +#endif + +#define EOL "\r\n" + +#define CH_SPI_FREQ 5000000 +#define CH_DMACHANNEL 2 + +typedef enum { + GPIO_PORTA = 1, + GPIO_PORTB = 2, + GPIO_PORTC = 3, + GPIO_PORTD = 4 +} GPIO_PORT_Index; + +typedef enum { + FRONT_RISING = 0x01, + FRONT_FALLING = 0x02, + FRONT_CHANGE = 0x03 +} GPIO_INT_Trigger; + +typedef enum { + UART_StopBits_1 = 0x01, + UART_StopBits_0_5 = 0x02, + UART_StopBits_2 = 0x03, + UART_StopBits_1_5 = 0x04 +} UART_StopBits; + +typedef enum { + UART_Parity_No = 0x00, + UART_Parity_Odd = 0x01, + UART_Parity_Even = 0x02 +} UART_Parity; + +/* GPIO_pins_define */ +#define GPIO_Pin_0 ((uint8_t)0x01) /* Pin 0 mask */ +#define GPIO_Pin_1 ((uint8_t)0x02) /* Pin 1 mask */ +#define GPIO_Pin_2 ((uint8_t)0x04) /* Pin 2 mask */ +#define GPIO_Pin_3 ((uint8_t)0x08) /* Pin 3 mask */ +#define GPIO_Pin_4 ((uint8_t)0x10) /* Pin 4 mask */ +#define GPIO_Pin_5 ((uint8_t)0x20) /* Pin 5 mask */ +#define GPIO_Pin_6 ((uint8_t)0x40) /* Pin 6 mask */ +#define GPIO_Pin_7 ((uint8_t)0x80) /* Pin 7 mask */ + +#define PIN_OUT 0x00 +#define PIN_IN 0xFF + +#define PIN_PULL_DOWN 0x00 +#define PIN_PULL_UP 0xFF + +#define GPIO_0 ((uint8_t)0x00) /* Pin 0 */ +#define GPIO_1 ((uint8_t)0x01) /* Pin 1 */ +#define GPIO_2 ((uint8_t)0x02) /* Pin 2 */ +#define GPIO_3 ((uint8_t)0x03) /* Pin 3 */ +#define GPIO_4 ((uint8_t)0x04) /* Pin 4 */ +#define GPIO_5 ((uint8_t)0x05) /* Pin 5 */ +#define GPIO_6 ((uint8_t)0x06) /* Pin 6 */ +#define GPIO_7 ((uint8_t)0x07) /* Pin 7 */ + +#define UEXT_GPIO_3 GPIO_PORTD, GPIO_5 +#define UEXT_GPIO_4 GPIO_PORTD, GPIO_6 +#define UEXT_GPIO_7 GPIO_PORTA, GPIO_2 +#define UEXT_GPIO_8 GPIO_PORTA, GPIO_1 +#define UEXT_GPIO_9 GPIO_PORTD, GPIO_4 +#define UEXT_GPIO_10 GPIO_PORTD, GPIO_3 + +#define DIRECTION_OUT 0x00 +#define DIRECTION_IN 0x01 + +#define PULL_DOWN 0x00 +#define PULL_UP 0x01 + +#define SYNC_MAGIC 0xAA +#define SYNC_RESPONSE 0x55 +#define SYNC_TIMEOUT 3000000 // 3 sec + +#define MODE_GPIO 0x00 +#define MODE_I2C 0x01 +#define MODE_SPI 0x02 +#define MODE_UART 0x03 + +#define CMD_PORT_INIT 0x01 +#define CMD_PORT_SET 0x02 +#define CMD_PORT_GET 0x03 + +#define CMD_PWR_SENSE 0x09 +#define CMD_BAT_SENSE 0x0A + +#define CMD_INT_ACTIVE 0x11 +#define CMD_INT_ENABLE 0x12 +#define CMD_INT_DISABLE 0x13 +#define CMD_INT_FLAGS 0x14 +#define CMD_INT_CAPTURE 0x15 + +#define CMD_I2C_INIT 0x01 +#define CMD_I2C_WRITE 0x02 +#define CMD_I2C_READ 0x03 +#define CMD_I2C_READREG 0x04 + +#define CMD_SPI_INIT 0x01 +#define CMD_SPI_TRANSFER8 0x02 +#define CMD_SPI_TRANSFER16 0x03 + +#define CMD_UART_CONFIGURE 0x01 +#define CMD_UART_WRITE 0x02 +#define CMD_UART_READ 0x03 + +#define IO_EXP_IRQ 36 + +#define BATTERY_MIN_MV 3500 +#define BATTERY_MAX_MV 4200 + +class CH32V003 { + +public: + CH32V003 (); + ~CH32V003 (); + + + bool available(); + + /** + * @brief Initializes CH32V003 driver + * + * @param MISO MISO pin + * @param MOSI MOSI pin + * @param CLK CLK pin + * @param CS CS pin + * @param CSActiveState CS active state + * @param host SPI host + * + * @return bool success + */ + bool begin(gpio_num_t MISO = GPIO_NUM_35, gpio_num_t MOSI = GPIO_NUM_12, gpio_num_t CLK = GPIO_NUM_14, gpio_num_t CS = GPIO_NUM_13, int16_t CSActiveState = -1, int16_t host = HSPI_HOST); + + /** + * @brief Deinitializes CH32V003 driver + */ + void end (); + + /** + * @brief + * Gets CH32V003 firmware version + * Most Significant Byte is major version. Least Significant Byte is minor version + * Example: + * 0x0100 = ver 1.0 + * 0x0101 = ver 1.1 + * 0x020A = ver 2.10 + * @return uint16_t MSB - major; LSB - minor + */ + uint16_t version(); + + /** + * @brief + * Turns ON power at UEXT (pin 1) and enables external pull-ups on UEXT pins 5 and 6 + * The power is ON by default + */ + void uextPowerEnable(); + + /** + * @brief + * Turns OFF power at UEXT (pin 1) + */ + void uextPowerDisable(); + + // GPIO + /** + * @brief + * Configures GPIO port at once. + * + * @param port GPIO_PORTx where x is one of A, C or D + * @param mask One or more of GPIO_Pin_x - pins to be configured as GPIO + * @param in_out Set corresponding bits as 0 - output; 1 - input + * @param pullup Set corresponding bits as 0 - pulldown; 1 - pullup + */ + void configurePort (uint8_t port, uint8_t mask, uint8_t in_out, uint8_t pullup); + + /** + * @brief + * Set GPIO port at once. + * + * @param port GPIO_PORTx where x is one of A, C or D + * @param mask One or more of GPIO_Pin_x - pins to be set + * @param value Set corresponding bits as 0 - LOW; 1 - HIGH + * + * value parameter has no effect on pins configured as input + */ + void setPort (uint8_t port, uint8_t mask, uint8_t value); + + /** + * @brief + * Get GPIO port at once. + * + * @param port GPIO_PORTx where x is one of A, C or D + * @param mask One or more of GPIO_Pin_x - pins to be get + * + * @return corresponding bits as 0 if input is LOW or 1 if input is HIGH + */ + uint8_t getPort (uint8_t port, uint8_t mask); + + /** + * @brief + * Configure individual GPIO pin + * + * @param port GPIO_PORTx where x is one of A, C or D + * @param gpio GPIO_x where x is one from 0 to 7 + * @param dir DIRECTION_IN or DIRECTION_OUT + * @param pullup if pin is input 1 - pullup; 0 - pulldown + * + * Macros UEXT_GPIO_x can be used as combination of port and gpio + */ + void configureGPIO (uint8_t port, uint8_t gpio, uint8_t dir, uint8_t pullup = 0x00); + + /** + * @brief + * Sets individual GPIO pin + * + * @param port GPIO_PORTx where x is one of A, C or D + * @param gpio GPIO_x where x is one from 0 to 7 + * @param value 0 - LOW; 1 - HIGH + * + * Macros UEXT_GPIO_x can be used as combination of port and gpio + */ + void setGPIO (uint8_t port, uint8_t gpio, uint8_t value); + + /** + * @brief + * Gets individual GPIO pin level + * + * @param port GPIO_PORTx where x is one of A, C or D + * @param gpio GPIO_x where x is one from 0 to 7 + * + * Macros UEXT_GPIO_x can be used as combination of port and gpio + * + * @return uint8_t 1 - pin level is HIGH; 0 - pin level is LOW + */ + uint8_t getGPIO (uint8_t port, uint8_t gpio); + + /** + * @brief + * Get Power Sense + * + * When battery is attached and external power is plugged in or + * out - IO_EXP_IRQ pin and interrupt flag for PortD pin 0 are set. + * + * The level of IO_EXP_IRQ pin by default is HIGH. This can be changed + * by calling CH32V003::setIntActive() + * + * Using this function will clear interrupt flag for PortD pin 0. + * IO_EXP_IRQ pin may remain set if there are other interrupts pending + * + * @return uint8_t 1 - external power is present; 0 - running on battery + */ + uint8_t powerSense(); + + /** + * @brief + * Get Battery Sense + * @return uint16_t battery level in mV + */ + uint16_t batterySense(); + + uint8_t batteryPercent (uint16_t bat_sense = 0); + + /** + * @brief + * Set level for IO_EXP_IRQ pin - default is HIGH on interrupt + */ + void setIntActive(uint8_t level); + + /** + * @brief + * Enable interrupt for specified pin + * Pin needs to be configured as input before calling this function. + * When interrupt is triggered IO_EXP_IRQ pin and interrupt flag for + * specified pin are set. + * + * The level of IO_EXP_IRQ pin by default is HIGH. This can be changed + * by calling CH32V003::setIntActive() + * + * The interrupt flag is cleared by reading the pin or by calling + * CH32V003::getPortIntCaptured(). IO_EXP_IRQ pin may remain set if + * there are other interrupts pending + */ + void enableInterrupt(uint8_t port, uint8_t pin, GPIO_INT_Trigger trigger); + + /** + * @brief + * Disable interrupt for specified pin + */ + void disableInterrupt(uint8_t port, uint8_t pin); + + /** + * @brief + * Get which pin triggered interrupt. Interrupt flags and IO_EXP_IRQ will NOT be cleared. + * @return uint8_t + */ + uint8_t getPortIntFlags(uint8_t port); + + /** + * @brief + * Get pin level at the time interrupt was triggered. Interrupt flags are cleared + * IO_EXP_IRQ IS cleared only if there ane no other interrups pending. + * @return uint8_t + */ + uint8_t getPortIntCaptured(uint8_t port); + + // I2C + /** + * @brief + * Configures I2C master at UEXT (pins 5 and 6) + */ + void configureI2C (uint32_t clock); + + /** + * @brief + * Sends data over I2C + * + * @param address slave address + * @param buffer data to be send + * @param size buffer size + */ + void writeI2C (uint8_t address, uint8_t buffer[], uint8_t size); + + /** + * @brief + * Receive data over I2C + * + * @param address slave address + * @param buffer data to be read + * @param size buffer size + */ + void readI2C (uint8_t address, uint8_t buffer[], uint8_t size); + + /** + * @brief + * Set register over I2C + * + * @param address slave address + * @param reg register num + * @param value value to be set + */ + void writeRegI2C (uint8_t address, uint8_t reg, uint8_t value); + + /** + * @brief + * Get register over I2C + * + * @param address slave address + * @param reg register num + * @return register value + */ + uint8_t readRegI2C (uint8_t address, uint8_t reg); + + // SPI + /** + * @brief + * Configures software SPI at UEXT (pins 7, 8, 9 and 10) + */ + void configureSPI (uint8_t spi_mode, uint32_t clock); + void transferSPI8 (uint8_t tx_buffer[], uint8_t rx_buffer[], const uint8_t size); + void transferSPI16 (uint16_t tx_buffer[], uint16_t rx_buffer[], const uint8_t size); + + // UART + /** + * @brief + * Configures UART at UEXT (pins 3 and 4) + * When the data is received IO_EXP_IRQ and interrupt flag for PortD pin 6 are set. + * Using readUART() will clear the interrupt flag if all data is acquired. + * IO_EXP_IRQ may remain set if there are other interrupts pending + */ + void configureUART (uint32_t baudrate, UART_StopBits stop_bits, UART_Parity parity); + + /** + * @brief + * Send data to UART + */ + void writeUART (uint8_t buff[], uint8_t size); + void strWriteUART(char message[]); + + /** + * @brief + * Read data from UART. The data is buffered (up to 254 bytes) and returned when requested. + * When there is unread data in the buffer - IO_EXP_IRQ and interrupt flag for PortD pin 6 are set. + * If the requested data is less than the buffered - IO_EXP_IRQ and interrupt flag for PortD pin 6 will remain set. + * @return uint8_t bytes actual bytes count readed - up to size + */ + uint8_t readUART (uint8_t buff[], uint8_t size); + + // UEXT as virtual GPIO port + /** + * @brief + * Enables UEXT power and clear interrupt flags and IO_EXP_IRQ + */ + void initUEXT (); + + /** + * @brief + * Configures virtual UEXT GPIO + * + * Pin mapping + * ------------------------------------------------- + * UEXT GPIO 0 - UEXT pin 3 - CH32V003 Port D pin 5 + * UEXT GPIO 1 - UEXT pin 4 - CH32V003 Port D pin 6 + * UEXT GPIO 2 - UEXT pin 5 - CH32V003 Port C pin 2 + * UEXT GPIO 3 - UEXT pin 6 - CH32V003 Port C pin 1 + * UEXT GPIO 4 - UEXT pin 7 - CH32V003 Port A pin 2 + * UEXT GPIO 5 - UEXT pin 8 - CH32V003 Port A pin 1 + * UEXT GPIO 6 - UEXT pin 9 - CH32V003 Port D pin 4 + * UEXT GPIO 7 - UEXT pin 10 - CH32V003 Port D pin 3 + * + * @param uint8_t gpio - GPIO_x where x is one from 0 to 7 + * @param uint8_t dir - DIRECTION_IN or DIRECTION_OUT + * @param uint8_t pullup - if pin is input 1 - pullup; 0 - pulldown + */ + void configureUEXT (uint8_t gpio, uint8_t dir, uint8_t pullup = 0x00); + + /** + * @brief + * Enable interrupt for specified GPIO on virtual UEXT port + * Pin needs to be configured as input before calling this function. + */ + void enableUEXTInterrupt (uint8_t gpio, GPIO_INT_Trigger trigger); + + /** + * @brief + * Disable interrupt for specified GPIO on virtual UEXT port + */ + void disableUEXTInterrupt (uint8_t gpio); + + /** + * @brief + * Get interrupt flags on virtual UEXT port + */ + uint8_t getUEXTIntFlags(); + + /** + * @brief + * Get GPIO level at time of interruptwas triggered on virtual UEXT port + */ + uint8_t getUEXTIntCaptured(); + + /** + * @brief + * Get GPIO level on virtual UEXT port + */ + uint8_t readUEXT (uint8_t gpio); + + /** + * @brief + * Set GPIO level on virtual UEXT port + */ + void writeUEXT (uint8_t gpio, uint8_t value); + +private: + gpio_num_t CH_MISO, CH_MOSI, CH_CLK, CH_CS; + spi_host_device_t CH_SPIHost; + spi_device_handle_t CH_SPIDevHandle; + uint8_t spi_acquired; + bool synced; + uint16_t firmware_ver = 0x0000; + + void uext2port(uint8_t uextGPIO, uint8_t *port, uint8_t *portGPIO); + + void acquireSPI(); + void releaseSPI(); + + uint8_t protocolHeader (uint8_t mode, uint8_t command, uint8_t direction); + + uint8_t transferByte (uint8_t send); + uint16_t transferWord (uint16_t send); + void transferProtocol(uint8_t txdata[], uint8_t rxdata[], uint16_t size); + void sync(); +}; + +#endif diff --git a/src/devdrivers/MCP23S17.cpp b/src/devdrivers/MCP23S17.cpp index 72e00a49e..0ed9a1e77 100644 --- a/src/devdrivers/MCP23S17.cpp +++ b/src/devdrivers/MCP23S17.cpp @@ -161,6 +161,7 @@ void MCP23S17::SPIEnd() void MCP23S17::writeReg(uint8_t addr, uint8_t value, uint8_t hwAddr) { + if (m_SPIDevHandle == nullptr) return; spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY); uint8_t txdata[3] = { (uint8_t)(0b01000000 | (hwAddr << 1)), addr, value }; @@ -178,6 +179,7 @@ void MCP23S17::writeReg(uint8_t addr, uint8_t value, uint8_t hwAddr) uint8_t MCP23S17::readReg(uint8_t addr, uint8_t hwAddr) { + if (m_SPIDevHandle == nullptr) return 0; spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY); uint8_t txdata[3] = { (uint8_t)(0b01000001 | (hwAddr << 1)), addr }; @@ -200,6 +202,7 @@ uint8_t MCP23S17::readReg(uint8_t addr, uint8_t hwAddr) void MCP23S17::writeReg16(uint8_t addr, uint16_t value, uint8_t hwAddr) { + if (m_SPIDevHandle == nullptr) return; spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY); uint8_t txdata[4] = { (uint8_t)(0b01000000 | (hwAddr << 1)), addr, (uint8_t)(value & 0xff), (uint8_t)(value >> 8) }; @@ -217,6 +220,7 @@ void MCP23S17::writeReg16(uint8_t addr, uint16_t value, uint8_t hwAddr) uint16_t MCP23S17::readReg16(uint8_t addr, uint8_t hwAddr) { + if (m_SPIDevHandle == nullptr) return 0; spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY); uint8_t txdata[4] = { (uint8_t)(0b01000001 | (hwAddr << 1)), addr }; @@ -311,6 +315,7 @@ void MCP23S17::disableInterrupt(int gpio, uint8_t hwAddr) void MCP23S17::writePort(int port, void const * buffer, size_t length, uint8_t hwAddr) { + if (m_SPIDevHandle == nullptr) return; // - disable sequential mode // - select bank 1 (to avoid switching between A and B registers) writeReg(MCP_IOCON, m_IOCON[hwAddr] | MCP_IOCON_SEQOP | MCP_IOCON_BANK); @@ -338,6 +343,7 @@ void MCP23S17::writePort(int port, void const * buffer, size_t length, uint8_t h void MCP23S17::readPort(int port, void * buffer, size_t length, uint8_t hwAddr) { + if (m_SPIDevHandle == nullptr) return; // - disable sequential mode // - select bank 1 (to avoid switching between A and B registers) writeReg(MCP_IOCON, m_IOCON[hwAddr] | MCP_IOCON_SEQOP | MCP_IOCON_BANK); diff --git a/src/devdrivers/wiiNunchuk.cpp b/src/devdrivers/wiiNunchuk.cpp new file mode 100644 index 000000000..5c6063a32 --- /dev/null +++ b/src/devdrivers/wiiNunchuk.cpp @@ -0,0 +1,233 @@ +/** + * Created by Olimex - + * Copyright (c) 2023 Olimex Ltd + * All rights reserved. + * + * + * Please contact info@olimex.com if you need a commercial license. + * + * + * This library and related software is available under GPL v3. + * + * WiiNunchuk driver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * WiiNunchuk driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WiiNunchuk driver. If not, see . + */ + +#include "wiiNunchuk.h" + +namespace fabgl { + +WiiNunchuk::WiiNunchuk() : + decrypt(false), + available(false), + controller(nullptr), + packetQueue(0), + updateTask(nullptr) +{ +} + +WiiNunchuk::~WiiNunchuk() { + end(); +} + +void WiiNunchuk::begin(uint32_t i2c_clock, bool original, bool createTask) { + decrypt = original; + status = {}; + +#ifdef USE_EXPANDER + controller = new CH32V003(); + bool ready = controller->begin(); +#else + controller = new I2C(); + bool ready = controller->begin(WII_NUNCHUK_SDA, WII_NUNCHUK_SCL); +#endif + + bool init = false; + bool ident = false; + + if (controller != nullptr && ready) { +#ifdef USE_EXPANDER + controller->configureI2C(i2c_clock); +#else + i2c_frequency = i2c_clock; +#endif + + // Init WiiNunchuk and read first WiiNunchukPacket + LOG_DEBUG("Initializing "); + if (decrypt) { + LOG_DEBUG("ORIGINAL\r\n"); + writeCommand(0x40, 0x00); + } else { + LOG_DEBUG("CLONE\r\n"); + writeCommand(0xF0, 0x55); + vTaskDelay(pdMS_TO_TICKS(100)); + writeCommand(0xFB, 0x00); + } + vTaskDelay(pdMS_TO_TICKS(100)); + init = true; + } + + ident = checkIdent(); + getNextPacket(); + + if (createTask) { + packetQueue = xQueueCreate(1, sizeof(WiiNunchukPacket)); + xTaskCreate(&WiiNunchukUpdate, "WiiNunchuk", WII_NUNCHUK_STACK, this, WII_NUNCHUK_PRIORITY, &updateTask); + } + + available = ( + controller != nullptr + && + ready && init && ident + && + (!createTask || packetQueue != 0) + && + (!createTask || updateTask != nullptr) + ); +} + +void WiiNunchuk::end() { + if (updateTask) { + vTaskDelete(updateTask); + updateTask = nullptr; + } + + if (packetQueue) { + vQueueDelete(packetQueue); + packetQueue = 0; + } + + if (controller) { + controller->end(); + delete controller; + controller = nullptr; + } + + available = false; +} + +bool WiiNunchuk::isAvailable() { + return available; +} + +bool WiiNunchuk::packetAvailable() { + if (packetQueue) { + return uxQueueMessagesWaiting(packetQueue) > 0; + } + return true; +} + +bool WiiNunchuk::getNextPacket() { + WiiNunchukPacket packet = {}; + bool res; + + if (packetQueue) { + res = xQueueReceive(packetQueue, &packet, msToTicks(WII_NUNCHUK_TIMEOUT)); + } else { + readPacket(0x00, &packet); + res = true; + } + + if (res) { + decodePacket(&packet); + } + + return res; +} + +WiiNunchukStatus & WiiNunchuk::getStatus() { + if (isAvailable()) { + getNextPacket(); + } + return status; +} + +void WiiNunchuk::emptyQueue() { + if (packetQueue) { + xQueueReset(packetQueue); + } +} + +void WiiNunchuk::writeCommand(uint8_t address, uint8_t data) { + if (controller) { + WiiNunchukPacket packet = {}; + packet.cmd[0] = address; + packet.cmd[1] = data; +#ifdef USE_EXPANDER + controller->writeI2C(WII_NUNCHUK_I2C_ADDRESS, packet.cmd, 2); +#else + controller->write(WII_NUNCHUK_I2C_ADDRESS, packet.cmd, 2, i2c_frequency, WII_NUNCHUK_TIMEOUT); +#endif + } +} + +void WiiNunchuk::readPacket(uint8_t address, WiiNunchukPacket * packet) { + if (controller) { + packet->cmd[0] = address; +#ifdef USE_EXPANDER + controller->writeI2C(WII_NUNCHUK_I2C_ADDRESS, packet->cmd, 1); + controller->readI2C(WII_NUNCHUK_I2C_ADDRESS, packet->data, WII_NUNCHUK_BUFF_SIZE); +#else + controller->write(WII_NUNCHUK_I2C_ADDRESS, packet->cmd, 1, i2c_frequency, WII_NUNCHUK_TIMEOUT); + controller->read(WII_NUNCHUK_I2C_ADDRESS, packet->data, WII_NUNCHUK_BUFF_SIZE, i2c_frequency, WII_NUNCHUK_TIMEOUT); +#endif + } +} + +bool WiiNunchuk::checkIdent() { + WiiNunchukPacket packet = {}; + WiiNunchuk::readPacket(0xFA, &packet); + LOG_DEBUG("IDENT 0x"); + for (int i=0; idata[i] = (packet->data[i] ^ 0x17) + 0x17; + } + } + + status.joystick.x = (int16_t) packet->data[0] - (int16_t) WII_NUNCHUK_X_ZERO; + status.joystick.y = (int16_t) packet->data[1] - (int16_t) WII_NUNCHUK_Y_ZERO; + + status.buttons.c = ((packet->data[5] >> 1) & 0x01) == 0; + status.buttons.z = ((packet->data[5] >> 0) & 0x01) == 0; + + status.accel.x = ((int16_t) (packet->data[2] << 2) | ((packet->data[5] >> 2) & 0x03)) - (int16_t) WII_NUNCHUK_XA_ZERO; + status.accel.y = ((int16_t) (packet->data[3] << 2) | ((packet->data[5] >> 4) & 0x03)) - (int16_t) WII_NUNCHUK_YA_ZERO; + status.accel.z = ((int16_t) (packet->data[4] << 2) | ((packet->data[5] >> 6) & 0x03)) - (int16_t) WII_NUNCHUK_ZA_ZERO; + + status.pitch = atan2((float) status.accel.y, (float) status.accel.z); + status.roll = atan2((float) status.accel.x, (float) status.accel.z); +} + +void WiiNunchuk::WiiNunchukUpdate(void * arg) { + WiiNunchuk * nunchuk = (WiiNunchuk*) arg; + WiiNunchukPacket packet = {}; + + while (true) { + if (nunchuk->isAvailable()) { + // Read following WiiNunchukPacket + nunchuk->readPacket(0x00, &packet); + xQueueOverwrite(nunchuk->packetQueue, &packet); + } + vTaskDelay(pdMS_TO_TICKS(1)); + } +} + +} // namespace \ No newline at end of file diff --git a/src/devdrivers/wiiNunchuk.h b/src/devdrivers/wiiNunchuk.h new file mode 100644 index 000000000..1f25f7948 --- /dev/null +++ b/src/devdrivers/wiiNunchuk.h @@ -0,0 +1,154 @@ +/** + * Created by Olimex - + * Copyright (c) 2023 Olimex Ltd + * All rights reserved. + * + * + * Please contact info@olimex.com if you need a commercial license. + * + * + * This library and related software is available under GPL v3. + * + * WiiNunchuk driver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * WiiNunchuk driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WiiNunchuk driver. If not, see . + */ + +#pragma once + +#include "freertos/FreeRTOS.h" +#include "freertos/timers.h" + +#include "fabglconf.h" +#include "fabutils.h" +#include "fabui.h" + +#define USE_EXPANDER + +#ifdef USE_EXPANDER + #include "CH32V003.h" +#else + #include "comdrivers/tsi2c.h" + #define WII_NUNCHUK_SDA GPIO_NUM_4 + #define WII_NUNCHUK_SCL GPIO_NUM_15 + + + #define LOG_DEBUG_ENABLED + + #ifdef LOG_DEBUG_ENABLED + #define LOG_DEBUG(f_, ...) printf((f_), ##__VA_ARGS__) + #else + #define LOG_DEBUG(f_, ...) + #endif +#endif + +#define WII_NUNCHUK_I2C_ADDRESS 0x52 +#define WII_NUNCHUK_CMD_SIZE 0x02 +#define WII_NUNCHUK_BUFF_SIZE 0x06 + +#define WII_NUNCHUK_X_ZERO 128 +#define WII_NUNCHUK_Y_ZERO 128 + +#define WII_NUNCHUK_XA_ZERO 512 +#define WII_NUNCHUK_YA_ZERO 512 +#define WII_NUNCHUK_ZA_ZERO 512 + +#define WII_NUNCHUK_STACK 1600 +#define WII_NUNCHUK_PRIORITY 5 +#define WII_NUNCHUK_TIMEOUT 20 + + + +namespace fabgl { + +/** + * @brief Contains raw data received from WiiNunchuk. + */ +typedef struct { + uint8_t cmd[WII_NUNCHUK_CMD_SIZE]; + uint8_t data[WII_NUNCHUK_BUFF_SIZE]; +} WiiNunchukPacket; + +typedef struct { + int16_t x, y; +} WiiNunchukJoystick; + +typedef struct { + int16_t x, y, z; +} WiiNunchukAccel; + +typedef struct { + uint8_t c, z; +} WiiNunchukButtons; + +/** + * @brief Contains WiiNunchuk status i.e. decoded WiiNunchukPacket + */ +typedef struct { + WiiNunchukJoystick joystick; + WiiNunchukAccel accel; + WiiNunchukButtons buttons; + float pitch; + float roll; +} WiiNunchukStatus; + +class WiiNunchuk { +public: + + WiiNunchuk(); + + ~WiiNunchuk(); + + void begin(uint32_t i2c_clock, bool original = false, bool createTask = true); + + void end(); + + bool isAvailable(); + + bool packetAvailable(); + + WiiNunchukStatus & getStatus(); + + /** + * @brief Empties the WiiNunchuk status queue + */ + void emptyQueue(); + +private: + + bool getNextPacket(); + void writeCommand(uint8_t address, uint8_t data); + void readPacket(uint8_t address, WiiNunchukPacket * packet); + bool checkIdent(); + void decodePacket(WiiNunchukPacket * packet); + static void WiiNunchukUpdate(void * arg); + +#ifdef USE_EXPANDER + CH32V003 * controller; +#else + I2C * controller; + uint32_t i2c_frequency; +#endif + + bool available; + + bool decrypt; + + TaskHandle_t updateTask; + + // queue of one WiiNunchukPacket + QueueHandle_t packetQueue; + + WiiNunchukStatus status; +}; + +} // namespace \ No newline at end of file