IOX-77 - An ESP32 based SuperBoard (75+ GPIOs)
The IOX-77 is a powerful ESP32-C3 based devboard. It comes with 75 GPIOs, which is even more than the Arduino Mega while being smaller and embedding WiFi and Bluetooth capabilities. It's like a 6 core configuration with 5 cheap CH32v003 MCUs communicating with the main core, the ESP32. With this devboard, I'll never run out of IOs for all my future projects, even those requiring dozens of IOs. This board is an upgraded ESP 32 devboard, gathering the computational power of the ESP32 and the many GPIOs offered by all the CH32. I hope you'll enjoy ! 😉
Created by
Clém
Tier 3
28 views
0 followers
Clém
added to the journal ago
IOX-77 CH32 cluster code Part 5
Now that I had a working I²C code basis, I built bit by bit a working communication protocol between the ESP32 and CH32s, and started adding more and more features. I decided to use directly the driver/i2c.h library, instead of relying on the wire.h library. but I still created 2 functions to make things easier: sendI2CCommand (CMD) and retrieveI2CData ().
The ESP32 can send 2 types of command, sendI2CCommand (CMD), where the ESP32 asks for the CH32 to do something, and one retrieveI2CData (), where the ESP32 retrieves the data prepared beforehand by the CH32. For example, the digitalWrite function only requires to send an order, so it works like this:
uint8_t CMD [SIZE] = {0x10, pin, state, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
sendI2CCommand (CMD);
where 0x10 is the name of the command (digitalWrite()), with the pin and state being parameters. I filled the remaining bytes with 0xFF since I decided that all I²C transmissions will consist of 8 bytes, which should be enough for most commands.
So I implemented the IOX_pinMode(), IOX_analogWritePWM(), IOX_digitalWrite() functions this way, using sendI2CCommand ()
For the digitalRead() function, I decided to go a little fancy with more than just a digital reading... I thought it would be cool for the ESP32 to read many digital readings at once (the 32 last readings in time) in one go instead of sending an I²C command every time the ESP32 needed the state of one pin. So I did something different that just the 'ESP32: Send me the pin state on pin XX -> CH32: here it is: X', and implemented the following:
If the ESP32 wants the pin XX to be read every X ms, he first sends a command using the function: IOX_digitalReadSample ()
void IOX_digitalReadSample (uint8_t pin, uint16_t interval, uint8_t multiplier) {
uint8_t CMD [SIZE] = {0x13, pin, (interval >> 8) & 0xFF, interval & 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
sendI2CCommand (CMD);
}
where the pin and reading interval (ms) can be chosen.
Then when the ESP32 wants the 32 last readings (so each reading is spaced in time by interval ms), the ESP32 can call IOX_digitalReadBuffer ():
uint32_t IOX_digitalReadBuffer (uint8_t pin) {
uint32_t initTime = micros();
uint8_t CMD [SIZE] = {0x14, pin, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
sendI2CCommand (CMD);
while (initTime + 500 > micros());
retrieveI2CData();
uint32_t reading = (uint32_t)RxData[2] << 24 | (uint32_t)RxData[3] << 16 | (uint32_t)RxData[4] << 8 | (uint32_t)RxData[5];
return reading;
}
With this setup, it's the CH32 that does all the readings by itself, and only send the last 32 readings when the ESP32 needs them, all in one go. I'll do a similar approach for the ADC, this way I could add some post processing (like averaging the reading) before sending it to the ESP32.
So the I²C communication for retrieving data is implemented this way:
Whenever the ESP32 wants to get some data back, it first sends a command to the CH32 indicating that he need to add the required data in the TxData buffer, ready to be retrieved by the ESP32 with the function retrieveI2CData();. It's to ensure the CH32 has enough time to update the TxData buffer that I add a small delay before doing retrieveI2CData(); (plus it prevent the bus from being overloaded)
On the CH32 side, if the IOX_digitalReadSample() has been received, the CH32 sample the desired pin constantly, every interval ms. For that I used a structure with all the possible GPIOs (13), with multiple variables (PINSTATE, PREV_MS, ..., INTERVAL...)
typedef struct {
uint32_t PINSTATE;
uint32_t PREV_MS;
uint16_t INTERVAL;
uint8_t ENABLE;
} digitalReadPinState;
digitalReadPinState digitalSampling[] = {
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0}
};
So... for the CH32 to digitalRead() each pin at a precise rate (set by the INTERVAL value), I needed to check the time in the main () loop, so everyone would have use millis() (or micros()) in the Arduino IDE... except that the CH32 HAL didn't have any millis() or similar function... thankfully it wasn't too hard to implement a similar behavior, with the Systick timer. (hopefully I found a code online that did almost exactly what I wanted!). With too main functions being:
uint32_t ms = 0;
void SysTick_init (u32 counter) {
NVIC_EnableIRQ (SysTicK_IRQn);
SysTick->SR &= ~(1 << 0);
SysTick->CMP = (counter - 1);
SysTick->CNT = 0;
SysTick->CTLR = 0x000F;
}
void SysTick_Handler (void) {
ms++;
SysTick->SR = 0;
}
Hum... ok I won't descibe everything in detail, since it's start being a lot with the 500+ lines on the CH32 code, but in a nutshell, the main loop consists of handleI2C(); and digitalRead_sample();, where digitalRead_sample(); sample the GPIOs states if the it's enabled for the pin X:
void digitalRead_sample() {
for (uint8_t p = 0; p <= 13; p++) {
if (digitalSampling[p].ENABLE == 1 && (ms - digitalSampling[p].PREV_MS)>= digitalSampling[p].INTERVAL) {
digitalSampling[p].PREV_MS += digitalSampling[p].INTERVAL;
uint8_t newBit = IOXdigitalRead (p);
digitalSampling[p].PINSTATE = digitalSampling[p].PINSTATE << 1;
digitalSampling[p].PINSTATE = digitalSampling[p].PINSTATE | newBit;
IOXdigitalWrite (2, toggle);
toggle = !toggle;
}
}
}
you'll notice that I tested the 'pace' of my function toggling the built in LED (on PC0 aka 2 according to my pinMap[] structure), making sure it worked correctly, and I indeed got a sampling frequency of 1 kHz when I set the interval to 1ms (IOX_digitalReadSample (6, 1);)
(I read 500Hz on my multimeter, and the LED is toggled everytime the function is entered)
Here is what it looks like when you print the PINSTATE uint32_t retrieved by the ESP32 at each IOX_digitalReadBuffer(): when I press / release the button on PC6, the state of the pin change from 0 to 1 or from 1 to 0, and the new value (measured by the CH32) is added in the 32 bit queue, thus getting rid of the 32nd oldest reading. This is why the 1 and 0 seem to move to the left at each new print on the serial monitor:

Whenever a packet is received over the I²C, the packets are saved in the RxData[] array, then at the end of the transaction, buffered in RxDataBuffer[]. the flag cmd_ready is set to 1, meaning that the command is ready to be analyzed by the function handleI2C();, which determines what the CH32 has to do depending on the command and parameters (with switch () case: syntaxes)
Another interesting thing to note, is that as I'm limited with packets of 1 byte transiting on the I2C bus, variables that are longer than that (uint16_t or uint32_t) are split in smaller packets, then reconstructed on the other side, with for example the variable freqOUT or duty_cyclefor the PWM:
void IOX_analogWritePWM (uint8_t pin, uint16_t duty_cycle, uint32_t freqOUT) {
uint32_t initTime = micros();
uint8_t CMD [SIZE] = {0x11, pin, (duty_cycle >> 8) & 0xFF, duty_cycle & 0xFF, (freqOUT >> 24) & 0xFF, (freqOUT >> 16) & 0xFF, (freqOUT >> 8) & 0xFF, freqOUT & 0xFF};
sendI2CCommand (CMD);
while (initTime + 1000 >= micros());
}
then on the CH32 side:
uint16_t duty_cycle = (uint16_t)RxDataBuffer[2] << 8 | (uint16_t)RxDataBuffer[3];
uint32_t freqOUT = (uint32_t)RxDataBuffer[4] << 24 | (uint32_t)RxDataBuffer[5] << 16 | (uint32_t)RxDataBuffer[6] << 8 | (uint32_t)RxDataBuffer[7];
It took me quite a while to get to this point, trying many things in C on the CH32 code (like for example pushing the CH32 to it's limit, implementing a micros variable like I did with millis, but it's not optimal with the interrupt of the Systick, since it would have been entered way too many times (1 millions times per seconds!) and it was a bit laggy, I couldn't get the digitalRead sampling to be at a higher frequency that 4.5KHz using micros instead of millis, probably because the micros interrupt was taking way too long.
Anyway I'm not explaining everything I've done in the firmware because I'm already talking too much, but in a nutshell (yeah, this time I stop digressing 😅), I implemented the functions IOX_pinMode(), IOX_analogWritePWM(), IOX_digitalWrite(), IOX_digitalReadSample() and IOX_digitalReadBuffer() over I²C, so I'm now able to fully control the GPIOs on one of the CH32 by simply coding the ESP32! You can't imagine how happy and relieved I was to see the first IOX_digitalWrite() working over I²C! After so much struggle getting the I²C to work! That was so fun seing an LED blynk-over-I²C 😂
Now, the last thing to do is to tackle the ADC reading on the CH32 side, and I'll then just have to make some HAL on the ESP32 side, cuz I'll have to do a few things for controlling all those GPIOs easily, without messing up which CH32 control which pin 😂
NOTE: the advancement of the code is in the Github repo, to avoid overloading the Blueprint journal
Clém
added to the journal ago
IOX-77 CH32 cluster code Part 4
Ok... so... I decided to deal with the I2C, and it was definitely harder than expected... I couldn't really take inspiration from the curious scientist website, since he only use the CH32V003 as master (I needed it as a slave), so I tried to look online, searching for code that already existed to use the MCU as a slave, and... I spent two afternoon, (~8 hours 😭) going from searching resources online with little to no results at all, vibe coding it with chatGPT (which was so bad at it that it mixed everything up, adding lines of code in C for the STM family 😭), documenting myself on I2C, testing the communication with the ESP32, went back to searching online, vibecoding again using another approach, doing smaller tests in I2C between the ESP32 and an Arduino (which took me a while since I didn't even knew how to assign the SDA and SCL pins in the wire.h library (turned out I just had to add the pin number as parameters in the wire.begin(10, 8); line), I even thought at some point that my design was cooked since I used GPIO10 on the ESP32 (which is used by the flash, so I thought for a moment it would never work... [this is me from the future: Actually I made it working, but I don't know if it's reliable, but... whatever] ... This endless loop of FAIIIILURES helped me learning stuff about I2C... but didn't helped me build my code for this IOX-77 project... I thought multiple times that choosing I2C wasn't a good idea, that my IOX-77 V1 would never work and that I should design the V2 using SPI (easier to implement and faster...) but I couldn't give up, not now, after all these hours working on this project...
So I kept going, searching online again and again to a solution for my problem, and I stumbled upon a reddit talking about using the CH32 as a slave. To be honest I had already seen this thread multiple times, but they only mentioned (or it's what I thought at first) the CH32fun library, which didn't fitted my requirements, so... But they showed a link to the official code example made by WCH, and I was so happy to see that the example provided was exactly what I needed: It implemented the I2C both for a slave, and for a master! I finally had a working resources to build my custom code around it!!! without further ado, I tested the code with 2 of the CH32, one as slave and one as master, and (obviously) it worked just fine!. So I tried to replace the Master CH32 with the ESP32 as master (this time I vibecoded it with chatGPT, because I really wanted to see if it would work, and as I gave him the ressources (CH32 I2C example code), it was easier for him to generate a working code for the ESP32 (using wire.h) and he also created a near bare metal code without using the wire.h library, and they both worked just fine! ... or at least they ended up working just fine 😅 because obviously the drop in replacement code didn't worked the first time... It took me a while for me to figure out (or let's say for ChatGPT to figure out) that there were an address mismatch, because the CH32 shifts addresses internally (for like no reason at all 😭), so the address 0x02 isn't the same on the ESP32 code as on the CH32 code... So for it to work I just replaced the 0x02 by 0x04 on the CH32 code, (shifting it to the left), and IT WORKED!!! ... (I mean I haven't looked at it enough, maybe some transmissions ends up generating errors, but at least I was able to get some data to show up in the Serial Monitor!!!!) So now that I'm sure it's possible to get a working I2C com between these 2 MCUs, I'll look deeper into the code to fully understand it, and modify it for my needs. I'm so relieved the I2C is finally working!!!!
Yeah, so here are some previews of the code I used:
Code for the ESP32 (using wire.h):

Code for the ESP32 (near bare metal):

Extract of the code for the CH32 (slightly modified demo from WCH (I added checkpoints via UART for debugging)):

I think I'll build the code around the near bare metal code for the ESP32, because it's way more explicit that the wire.h library, which does everything by itself so everything is hidden from my comprehension. So I'll try to build a reliable communication in I2C using these ressources, and hopefully I'll get something working perfectly!
(note: I'm not taking in account the 8 hours I lost searching-online/vibecoding/debugging/pulling-my-hairs-out/going-back-to-online-search/ending-up-vibecoding-again..., because it didn't gave me any results... I really started doing useful things once I had found the miraculous demo example from WCH)
Clém
added to the journal ago
IOX-77 CH32 cluster code Part 3
I then started dealing with PWM. It was a bit more complicated than digitalWrite() or digitalRead() functions, as it needs Timers, but following The Curious Scientist code, I modified it so that I can choose on which PIN the PWM is outputted, and instead of taking as parameters PRSC, ARR and CCR, it directly calculates these values from the desired output frequency and the duty cycle.
Extract of the PWM course by The Curious Scientist:
https://curiousscientist.tech/blog/ch32v003f4p6-timers-and-pwm?rq=PWM


And here is the snippet for handling PWM (just after the digitalWrite() function):
%20PWM.jpg)
So after checking that the desired PIN is PWM capable, it calculates the values for the Timer (PRSC (prescaler), CCR, and ARR) using the given formulas, and sets up the PWM while differentiating the actions depending on the pins (on which Timer (1 or 2) and on which channel (1 - 4) is this pin?) but basically do the exact same thing as explained in the guide by The Curious Scientist (huge shout out to him, by the way, his guides are priceless!)
And the main program, with the LEDs fading in and out:
%20PWM%20main.jpg)
So after tweaking a few things to make it work perfectly (I first forgot to differentiate the channel depending on the pin, so it didn't worked on all pins), I got something that seems to work just fine: I haven't looked at the outputted signal (I don't have any oscilloscope), so I don't know if the frequency/duty cycle is correct, but at least fading the LED works well.
So overall I'm really happy with this function, because it still make the main program way more readable, with a similar HAL that in the Arduino environment (one line for controling PWM), but it's also way more powerful: a 4 times higher precision (0 - 1023) is achievable on this faster MCU, while even being able to control the outputted frequency.
EDIT: Actually I just remembered that my multimeter could measure frequency and duty cycle (tbh I've never used this feature before), and after checking on the pins PC0 (the LED of each CH32), it was indeed around 1KHz (it displayed smth like 994Hz, so I consider this result as a Pass) For the duty cycle, the results were unreadeable, which is obvious since the LED are fading so constantly changing their duty cycle, so I think I'll just make another small code to ensure it works well.
Clém
added to the journal ago
IOX-77 CH32 cluster code Part 2
The next function to implement was digitalRead(). And while the function itself was pretty straightforward, it showed me a problem that took me a while to debug...
So here is the function, and to test whether it worked or not, I made a quick test with a button (the LED lights up when the button is pressed). the first GPIO that I found accessible was chip #D, PA1, one of the 4 CH32 pins broken out on the classical 2.54 headers, so I quickly uploaded the test.
.jpg)
And... when I uploaded the script, quickly after hooking up a button to the right pin, it... didn't worked... So obviously I first thought that there were an error in the software, that I missed something or did something incorrectly, but after triple checking the (simple) code, and letting AI having a look at my work, everything seemed just fine... So I tried with PA2, the pin just next to it, and same problem... what could have been wrong? I tried to see if the pin was faulty, by setting it to OUTPUT, and blinking it, like with the LED on PC0, and instead of getting 0V then 3.3V alterning, I got a poor lil 0.57V 😭. Maybe the PORT A was fried on this IC? no problem, I've got another 15 ready for testing 🤣 but still 0.57V continuous... I even ended up thinking for a brief moment that my multimeter was just hallucinating 🤣! But obviously it wasn't wrong since I tried other ports too, and it was working on PC7... I checked the PCB layout, to ensure it was well connected, that the .5V wasn't induced by nearby traces, and at the moment I started running out of ideas, I looked at the CH32 pinout, and realized that they could be used as an alternate function: OSC, 2 pins for an external oscillator... maybe that was the problem?
So I was like 'hey, chatGPT, do you think this problem could be related to the alternate function of the pins PA1 and PA2?' and it went '✅ you nailed it, that's very likely the cause'. It meant that the IC was configured by default to accept an external oscillator, which made PA1 and PA2 unusable. So instead of going through all the lines of code in all the .c files associated with the CH32V003 IDE, whithout really knowing what could cause the problem, I gave the codes to Chat GPT and he found pretty quickly in the system_ch32v00x.c file, that the uncommented line was #define SYSCLK_FREQ_48MHz_HSE 48000000, so a 48MHz external oscillator, instead of the #define SYSCLK_FREQ_48MHZ_HSI 48000000 I needed. Ok, to be fair, I could have found it myself, it was actually self explainatory 😅 (even neatly explained with comments), but yeah, it's definitely faster when AI does the job, and I didn't wanted to read 700+ lines while the IOX-77 was stuck at the very beginning of the development phase...
.jpg)
With that being solved, the test code for the digitalRead() was working just fine, and even though it seems like a very small victory, I'm really happy it finally works... You definitely experience a one of a kind feeling once you've solved a Hardware/software problem, after debugging it for way to long 😅
Clém
added to the journal ago
IOX-77 CH32 cluster code Part 1 (out of many...)
I started the code for one of the CH32, starting with the GPIO basis.
Each CH32 will be communicating with the ESP32 via I²C, giving orders like 'set PA2 as Pull up' or 'write HIGH on PB2'... so on the ESP32 side, I'll have every major GPIO related functions (pinMode(), digitalWrite(), analogRead() and so on) customized for the IOX-77, each of these functions will send basic commands via I2C, and then the CH32 executes it. I'll thus need a 'custom' pinMode(), digitalWrite(), etc. on the CH32 side, this is why I'm starting with the custom pinMode() function.
I really want to build the firmware myself, without vibecoding it, but as the documentation is very minimal (only a few guides explains how the CH32V003 environment works), ChatGPT is helping me to learn how to make it work, especially understand all the GPIO_InitStructure... and other weird looking lines like RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE) and all the other things I'm not used to. Then once I think I understand what each functions does and how it behaves, I try rewriting it on Mounriver Studio, tweaking it a bit so that it does exactly what I need, before asking ChatGPT to correct me if something won't work or if there is any syntax error. I already know that with all the hours I'll have to spend on coding the software from the ground up, I'll learn a lot of things along the way!
So for the custom pinMode function, I needed a function with 2 parameters, the pin name, and the mode (OUTPUT PUSH PULL, OUTPUT OPEN DRAIN, ANALOG INPUT, and so on...). For the pin name, the CH32 syntax needs the PORT and PIN NUMBERseparately, so I added a pinMap which links a number (the pin name) to two variables, the PORT and the PIN NUMBER.
I also created the custom pinMode() function and a custom digitalWrite() function.
So now with these two functions, I should still be able to make the LED blink, but with a lighter (visually) code. And... yeah it still works just fine. Perfect!
Here is what this snippet looks like:

Testing the other boards...
I also tested the 2 other boards this weekend, and the 10 CH32V003 blinked perfectly. It's also interesting to notice, like bitluni did before me, that the clock of each CH32 becames out of sync pretty fast, and the '1000s' blink becames a mesmerizing random light ballet. that's actually kinda fun!

Clém
added to the journal ago
Let's start with the CH32 cluster!

So, I started learning how to program those cheap Risc-V MCUs, and it actually seems fairly complicated... Here is what the 'blink' program looks like... nothing to do with the Arduino Framework...

Actually, I think I'll enjoy programming those cuz I feel like I'm more professional that with Arduino... I feel like I'm doing bare metal programming (which actually isn't), but I mean I'm way more into hardware, not just 'digitalWriting' and 'pinModeing' stuff 😉...
Learning how to code them properly will be quite time consuming, so I don't know if I'll have enough background before Blueprint ends to have a fully working IOX-77, because I don't want to go straight to the fairly complicated I2C program, but instead learning progressively how to code those chips.
This is why I started with the blink program, which is not less than 33 lines 😓
Anyway, I don't have the 3 boards with me, only one, so I tested it with an FPC adapter and jumper wires, but only 3 ICs uploaded, and looking more carefully, the other 2 IC's SWIO pins are not well soldered, so it's not responding. Or they are just fried, but it's less likely I think... I'll try to resolder them properly one day, but I'll also try the other 2 boards to see if it's better.
I also uploaded a basic sketch on the ESP32 to reset all the chips at boot up, and hold their NRST pins HIGH once it's fully initialized:

Now that I've discovered the Mounriver Studio IDE, I'll progressively learn how to code them, thanks to the few really well documented websites (like the Curious Scientis. This article is for getting started with GPIOs: https://curiousscientist.tech/blog/ch32v003f4p6-setup-and-gpios?rq=CH32)
Clém
added to the journal ago
Building the devboard... and encountering the first bug...
This timeline isn't exact, because in reality I assembled 2 boards soon after I received the components and PCBs. It took me about 1 and an half hour to get them assembled, covering the pads with solder thanks to the stencil (what a time saver, considering that for all my previous projects I dispensed solder paste on each pad by hand... relaxing, but it's time consuming and more prone to bridges...)
I then placed all the components and reflow soldered the 2 boards. The first one wasn't looking great, since the GND and 3V3 were shorted (turns out it was the ESP soldering that didn't went well)... which wasn't the case on the second board. So I plugged it onto the computer, rushing to upload my first 'blank' code onto the ESP32... which was horrific since the board wasn't even recognized by the PC... I was so disappointed that it didn't work the first time, considering that I was using a prebuilt ESP32 C3 module, not the bare chip! what could have gone wrong! After the first moment of deception, I tried to figure out what wasn't working: I checked all the PWR rails, 4.90V on the 5V rail: perfect considering the voltage drop caused by the diode, and 3.28V on the 3V3 rail... Ok the board was powered correctly. I remembered that to programm ESP32, you need to execute a serie of button presses (Hold BOOT, plug the board into the computer, press and release RESET, release BOOT), so I tried multiple times, without succeeding... It was the end of the Weekend, so I told myself: You know what? screw it: I'll deal with this during the holidays...
So a few weeks later, I got back into it: I soldered another board, with only the required parts (ESP32 + USB + LDO), maybe the problem came from the CH32s or other mistake on the board... I tried a few more things, checking the schematic, D+ and D- routing, and eventually asked on slack, on #electronics, and huge shout out to @grimsteel for finding the error, and thanks too to @Madhave for his precious help.
I completely messed up the wiring of the BOOT and ENABLE (RST) buttons: I connected the pairs the wrong way, resulting in 2 wires instead of 2 buttons... No wonder the ESP32 couldn't upload, if it was held in Reset forever!
So I desoldered the two buttons on each board, and just tilted them to 60°, to realign the pairs correctly. Actually, I like them this way 😂! unfortunately, the ESP32 on the 'half soldered' board was recognized but failed to upload, stuck at 'connecting...' maybe I fried it with the numerous reflow rework (all the pads underneath the ESP32 module are everything but maker friendly...),
However, I tried on the 2 other boards, and they were uploading perfectly. I was so happy it finally worked 🎉! I soldered a third board, which uploaded well too, so I was able to get 3 fully assembled and working IOX-77 boards out of 4 expected: 75% success rate, not that bad honestly 😂😅! ... at least for the ESP32 part... Haven't tried yet the main feature of this devboard: the CH32 cluster...
%20BLP.jpg)
I desoldered the two buttons on each board, and just tilted them to 60°

I connected the pairs the wrong way, resulting in 2 wires instead of 2 buttons...
I uploaded a basic Arduino test code, to see if I could toggle a GPIO (IO0), and whether the serial communication worked, and it worked just fine on the 3 boards.
Now, the last thing to do is to figure out how to programm the CH32V003, and make them behave as smart GPIO expanders with the ESP32...
Until I advance a bit more on it, here are a few pictures of these 3 IOX-77 superboards:
Gallery:
%20BLP.jpg)
.jpg)
.jpg)
.jpg)
.jpg)
%20BLP.jpg)
Clém
added to the journal ago
we've got the PCBs 🎉
I received the PCBs, and they look so cool!
To be honest, I though they would be a bit bigger, the pcb always looks big on the screen when designing it, but I guess having a small board footprint is always a good news 😊


Here is a size comparison with the Raspberry pi, so that you've got an idea on how small it is with it's 76 GPIOs :

Hope they aren't any electrical mistake, 'cause the inner layers are kind of inaccessibles 🤣 but I guess we'll find out this WE when the PCB will be soldered...
Clém
added to the journal ago
Ordered the components
I've already ordered the PCB and stencil a while ago, and now I placed the LCSC order, with the electronical components. I'm combining multiple LCSC order, so I don't have to pay multiple times shipping cost and handling fees, so I had to make a top-up form, pay for the additionnal cost in a donation to HCB (15 bucks of other components, for other projects or connectors to make custom shields for the IOX-77 devboard), and I've now placed the LCSC order which adds up to $57.28 ($40.29 worth of components for the IOX-77 project, and $15.49 in additional component).
Can't wait for the components to arrive! 😁
Here are the parts required for the IOX-77 project:





And here the additional parts (for other projects)



So in total:


Iamalive 🚀
approved IOX-77 - An ESP32 based SuperBoard (75+ GPIOs) ago
Tier approved: 3
Grant approved: $63.00
Looks good!
Clém
added to the journal ago
Added the pinout
There are so many pins on this devboard! Thus, I created a PDF with the pinout neatly explained. (the PDF also available on the Github Repo)



Clém
submitted IOX-77 - An ESP32 based SuperBoard (75+ GPIOs) for ship review ago
Iamalive 🚀
requested changes for IOX-77 - An ESP32 based SuperBoard (75+ GPIOs) ago
It seems like your project description is partially made by AI, which we do not tolerate. Please fix this!
Clém
submitted IOX-77 - An ESP32 based SuperBoard (75+ GPIOs) for ship review ago
Clém
added to the journal ago
Preparing orders

As I said earlier, I've already made a few PCBs using SMD components, so I'm used to solder QFN20 and other SMD packages. I'll order the components on LCSC, and the PCBs/Stencil on JLCPCB. It's cheaper than choosing PCBA option, and let's be honest, it's way funnier to build your PCB, once you've spent so many hours designing it on a screen😉! In addition to that, I'll need to order a SWIO USB programmer, to programm the CH32V003 (we can find them for pretty cheap on aliexpress).
Here is the BOM for the electronic components: (one PCB)

And here are the additional parts (and other hardware):
Here is the JLCPCB order: 5 PCBs and a stencil. Thanks to the small size of the stencil (I chose 100mm by 100mm), the cost is as low as 3$! Making PCBs has became so cheap these days! 🤗

When ordering components on LCSC, an important part of the cost comes from shipping (even though, it can be reduced to 9 bucks with Global Standard Direct Line). Moreover, the major part of the components are ordered in multiples of 10 or even 100 (0402 res and other). So at the end the only components that I can really choose the quantity are the ESP32, the two 1.27 headers and the five CH32V003 (all the other have a MOQ of at least 5pcs, or more). It means that while building one PCB cost you around 25 bucks in components, building 2 PCBs will only cost you like 5 bucks more...
So I though about it and made some calculations, and I think that building 4 PCBs is the sweet spot: I'm using 4 of the 5 PCBs, the total cost is still relatively low, and most importantly, the per board cost is way lower.
Here is the simulation:

The total cost takes in account the JLCPCB, Aliexpress and LCSC orders, with the components needed to build the board. However, as I used uncommon FPCs and 1.27mm pitched headers to give my devboard a smaller footprint, I also need to buy a few extra components (male headers, FPC cables, and FPC connectors) in order to implement them on custom Shields that I'll design later on.
Here is the LCSC order (IOX-77 devboard components + extra connectors for the upcoming shields):






So the total cost for 4 PCBs is ... less than 60 $ 🥳

(Small update: while checking whether everything was ok, I noticed that I completely forgot to add to the cart 0402 Resistors for the Ideal Diode Controller. I need 2 Res, with values varying from 50k to 2M (50K, 100K, 200K, 500K, 1M, 2M), this way I can tune the reverse current blocking speed vs continuous current consumption ratio.
Not a big deal, the cost increase by a itty bitty 30 cents 😅 )
Clém
added to the journal ago
Made some cool renders on Fusion 360
All the previous images were taken from Easy EDA viewing tool.
To finish in style, I decided to make a few higher quality renders.
Here are some renders of the IOX-77 made with Fusion 360:






Clém
added to the journal ago
Cleaning a bit the silkscreen
I cleaned a bit the silkscreen layer by removing all original silkscreens around the components, and the board looks way better now. I also added custom silkscreen to highlight the GND and +3.3V pins, and a custom logo for my IOS-77 board. (Yeah, I changed the inital name because I thought IOX-77 was cooler, and yes, the devboard actually has 75 GPIOs if we only count the usable GPIOs, but hey, come on! 77 is a way cooler number 😅)
In the meantime, I also changed a few things on the PCB layout to make sure everything runs smoothly (I tried to remove all "bottlenecks" in the power planes, so that all power path are wide enough.)
I really like how this board turned out, and I can't wait to assemble the PCBs and see if it even works.
As it's not my first PCB project, I'm used to solder QFN-20 packages and 0603, but I leveled up the game with 0402 ones this time. I even have two ESD diodes in a SOD882 package (1mm by 0.6mm... I don’t even know if I'll be able to solder them... well, we have to try. And if I can't, all this work will be useless... No seriously, with a good stencil and precise hands, it's fine: the surface tension does all the job!


So here are the 4 Copper Layers of the PCB design:
Top Copper Layer:

Inner Layer 1:

Inner Layer 2:

Bottom Copper Layer:

Clém
added to the journal ago
Designing the PCB Part 6: Everything is connected 🥳
The PCB is finally done! Hurrah! 🥳
So, it took me quite a while to make sure everything was connected (the ratline layer should be empty then), and for the last 20 or so traces, I needed to move other traces to free up some space... Everything is quite tightly packed... But at the end of the day, I've got a neat and well designed PCB.
I added a few extra components (a TVS diode and decoupling cap near the ESP32-C3) and I changed the FPC connector on the right: It wasn't the same ref as the other ones, so it looked awkward. Now they are all the same so the board is more consistent.
I've designed many PCBs for my electronics project over the last 3 years (2L PCBs), but I have to admit that the routing on this project was harder than on the previous ones. In total, there are 71 components, 490 pads, 112 nets, and ... wait, what did I just read ? ... 289 vias! No wonder if the routing wasn't easy with all these vias on the way 😂! Fun fact: the total length of all the traces is more than 4 meters!
Have a look at this overkill devboard:




So, I'm really happy with how this turned out, and the last thing to do before submitting is arranging the silkscreen layer a bit, so the board is neat and pleasant to look at.
Clém
added to the journal ago
Designing the PCB Part 5: Routing progress 90%
I've spent another 2 and a half hours adding and routing the remaining components of the circuit (Power Management, USB C passives...).
While doing this, I noticed an error in the schematic: The VBUS coming out of the USB C wasn't connected to anything, so I replaced it with a +5V tag, as it should have been.
I even added two GND and +3.3V planes on the top and bottom layers, but I still need to add some prohibited regions because the initial plane created by Easy EDA isn't perfect (it is trying to seep through every single part of the circuit, while it shouldn't)
Here’s a preview:


Clém
added to the journal ago
Designing the PCB Part 4: GPIOs Routing 100% completed 🥳
Hurray! All the GPIOs are now connected to the 5 MCUs! 🥳
It's taking shape slowly but surely. Actually, I think the final board will look amazing, with all those little 8mil traces running everywhere on the 4 Layer PCB (yeah, I know, the inner layer won't be visible, but still, they are on the design file 😊)




Clém
added to the journal ago
Designing the PCB Part 3: Still adding GPIO traces
So, it's moving forward! I rerouted everything, linking the two left MCUs to both the FPC and headers. It was a quite long and tedious task, but still it was fun and I didn't even notice the time passing...
As I said, I had to make some modifications in the schematic to ensure that the traces align well with the header pads without having too much crossing. It was sometimes quite mind-boggling, but I eventually found out how to route everything.
Next time it should be easier, because I don't need to bother connecting the FPC, as the other MCUs only have their GPIOs connected to the 2 headers.
Clém
added to the journal ago
Designing the PCB Part 3: I'm changing my mind
So... Hum... Yeah:

After another hour of routing the PCB, I definitely know it won't make it: the connections Headers-MCUs are just not efficient at all: when one pin of an MCU goes on the left on the top header, the next pin goes to the opposite side! So obviously, it makes routing way more difficult because the traces need to cross each other... So I'm adopting a different approach: I'll route every MCU pin to the closest Header pin, then I'll change the nets on the schematic according to this disposition, and hopefully I'll get better results.
Let's try it!
Clém
added to the journal ago
Designing the PCB Part 2
I added the 5 CH32 and their passive components. I started routing the GPIOs to the FPC/Headers, and I know it's going to be pretty difficult to fit all these traces even on a 4 layers board...
Here is the progression:



Clém
added to the journal ago
Designing the PCB Part 1 : Overall shape
I started the PCB by creating the board outline and by placing the major components (ESP32, GPIO interfaces...). I tried to have something practical (placing the FPCs, USB... on the sides of the board) while still being visually pleasant, and I think that this first sketch is pretty cool. We'll see in the future if we need more space for all the traces, but for the moment, I think it's not that bad! Have a look:


Clém
added to the journal ago
Schematic Part 4: Power Management
The last thing to add in our schematic is the Power Management.
The ESP32 operates at 3.3V, so the CH32 run at 3.3V too. There are 3 ways of powering the board:
(A) - By the USB-C at 5V
(B) - By a dedicated port between 3.5V and 5.5V (but the "+5V" rail will be at this voltage, so if it's not exactly 5V, we have to be careful about what we connect to it)
(C) - By another dedicated port with a steady 3.3V (directly fed into the +3.3V rail).
For the options (A) and (B), there is a 3.3V LDO voltage regulator (TLV75733PDBVR) who provides a steady 3.3V output. I chose this one because it had a small quiescent current (25µA). However, this IC wasn't protected against reverse current: If I power the board directly with 3.3V (option (C)), the output of the LDO would be at 3.3V, while the IN of the LDO would still be at 0V (no input connected), and this scenario could damage the LDO. So it took me a while to figure out what to do, but I ended up using an Ideal Diode Controller (the DZDH0401DW) along with a ultra-low RDSon P-channel MOSFET(the SI2393DS). This way, current can only flow in one direction, from the LDO to the +3.3V rail, without inducing an important voltage drop (it would have been the case with a Schottky diode)
So here are the schematics for the LDO and the Ideal Diode Controller:


And the whole schematic :

So, I think the Schematic is good to go, next time I'll start the best part of this project: Routing! 🤗
Clém
added to the journal ago
Schematic Part 3: Accessing the IOs
Now that the 5 CH32 offer their 70 GPIOs, it's time to arrange them on the devboard. I've decided to use multiple access points :
- A 40 Pins .5mm FPC connector gives access to 32 of the GPIOs.
- Two 2x34 small pitch female Dupont Headers (1.27mm pitch, instead of the typical 2.54mm ones), with all 70 GPIOs, the remaining ESP32's IOs, I2C lines, multiple 3.3V and GND pins, and even a +5V pin (the +5V is accessible when the devboard is powered via the USB)
- a more user-friendly 2.54mm 2x8 header, with the ESP32 IOs, I2C lines, Power pins and 4 CH32 (E) IOs (including two 5V tolerant pins)
- A 6P .5mm FPC with I2C and UART (from the ESP32)
- Lastly, another .5mm FPC (8P) with all SWIO pins, which will be used for programming.
I think that with all of that, adding sensors, shields, modules, etc, will be very neat and easy.
Here are some screenshots:
The 2.54mm header, and two 6P and 8P FPCs:

The two 1.27mm header:

And the 40P FPC:

Browsing parts on LCSC and adding all the nets to the schematic was really fun, even if it was indeed a repetitive task. However, thinking about routing all this mess makes me a bit nervous... 😅 but hey, one step at a time!
And here is the updated progression of the schematic:

Now, the last thing to do on the schematic is handling the Power management.
Clém
added to the journal ago
Schematic Part 2: The CH32V003
I added 5 CH32v003F4U6 (QFN-20). They all share the same I2C bus with the Master (ESP32). I gave each CH32V003 an orange 0603 LED: it could be really useful for debugging...
Each CH32V003 has the ability to go in Standby Mode, where the current consumption is a tiny 10µA (so 50µA in total). The idea is that when we want to limit the overall current consumption, the ESP32 sends a signal to each CH32V003 Via I2C, ordering them to enter Standby Mode. Once the ESP32 needs them again, it can wake them up by resetting them: It's the ESP32 that commands the 5 NRST pins (Negative Resets pins). This way, if one of the CH32V003 isn't responding anymore, the ESP32 can force the reset, and hopefully, it's going to work again. It's also practical when we want to reset the entire devboard: The ESP32 can handle everything, no need to have one button for each CH32.
PC5 and PC6 are 5V tolerant, so I added the suffix "FT".
The CH32 can be programmed using the SWIO protocol, with a specialized programmer (the WCH-Link) I will later on add a header (or an FPC connector) to access all 5 SWIO pins, +3.3V, and GND for programming.
This is what one of the CH32 implementations looks like:

And here is the progression of the schematic:

Two hours is quite long for implementing 5 CH32v003, but I needed to do some research to ensure that the CH32V003 could act as I wanted it to, so I went through part of the CH32V003 datasheet, and checked multiple CH32V003 devboard schematics.
Clém
added to the journal ago
Started the Schematic: Basic ESP32 board
I started the schematic of the devboard by adding all the minimal components for the ESP32-C3-MINI-1-N4 module, like the USB C, a bunch of 0402 resistors, decoupling caps, ESD protection diodes, the boot and Chip Enable Buttons... During these first 2 hours, I also documented myself by looking at other ESP-C3-MINI devboards (like the Adafruit Rust) and the schematic example provided by ESPRESSIF.
I chose the ESP32-C3-MINI-1-N4 module, instead of the bare ESP32 QFN chip, because I preferred not to deal with the Antenna stuff, as the RF tuning is way too complicated for me, and without tuning, the devboard isn't optimized, and the RF range can be reduced.
Here is the first part of the schematic:

Clém
started IOX-77 - An ESP32 based SuperBoard (75+ GPIOs) ago
11/29/2025 12 PM - Started the Schematic: Basic ESP32 board
I started the schematic of the devboard by adding all the minimal components for the ESP32-C3-MINI-1-N4 module, like the USB C, a bunch of 0402 resistors, decoupling caps, ESD protection diodes, the boot and Chip Enable Buttons... During these first 2 hours, I also documented myself by looking at other ESP-C3-MINI devboards (like the Adafruit Rust) and the schematic example provided by ESPRESSIF.
I chose the ESP32-C3-MINI-1-N4 module, instead of the bare ESP32 QFN chip, because I preferred not to deal with the Antenna stuff, as the RF tuning is way too complicated for me, and without tuning, the devboard isn't optimized, and the RF range can be reduced.
Here is the first part of the schematic:

11/29/2025 5 PM - Schematic Part 2: The CH32V003
I added 5 CH32v003F4U6 (QFN-20). They all share the same I2C bus with the Master (ESP32). I gave each CH32V003 an orange 0603 LED: it could be really useful for debugging...
Each CH32V003 has the ability to go in Standby Mode, where the current consumption is a tiny 10µA (so 50µA in total). The idea is that when we want to limit the overall current consumption, the ESP32 sends a signal to each CH32V003 Via I2C, ordering them to enter Standby Mode. Once the ESP32 needs them again, it can wake them up by resetting them: It's the ESP32 that commands the 5 NRST pins (Negative Resets pins). This way, if one of the CH32V003 isn't responding anymore, the ESP32 can force the reset, and hopefully, it's going to work again. It's also practical when we want to reset the entire devboard: The ESP32 can handle everything, no need to have one button for each CH32.
PC5 and PC6 are 5V tolerant, so I added the suffix "FT".
The CH32 can be programmed using the SWIO protocol, with a specialized programmer (the WCH-Link) I will later on add a header (or an FPC connector) to access all 5 SWIO pins, +3.3V, and GND for programming.
This is what one of the CH32 implementations looks like:

And here is the progression of the schematic:

Two hours is quite long for implementing 5 CH32v003, but I needed to do some research to ensure that the CH32V003 could act as I wanted it to, so I went through part of the CH32V003 datasheet, and checked multiple CH32V003 devboard schematics.
11/30/2025 10 AM - Schematic Part 3: Accessing the IOs
Now that the 5 CH32 offer their 70 GPIOs, it's time to arrange them on the devboard. I've decided to use multiple access points :
- A 40 Pins .5mm FPC connector gives access to 32 of the GPIOs.
- Two 2x34 small pitch female Dupont Headers (1.27mm pitch, instead of the typical 2.54mm ones), with all 70 GPIOs, the remaining ESP32's IOs, I2C lines, multiple 3.3V and GND pins, and even a +5V pin (the +5V is accessible when the devboard is powered via the USB)
- a more user-friendly 2.54mm 2x8 header, with the ESP32 IOs, I2C lines, Power pins and 4 CH32 (E) IOs (including two 5V tolerant pins)
- A 6P .5mm FPC with I2C and UART (from the ESP32)
- Lastly, another .5mm FPC (8P) with all SWIO pins, which will be used for programming.
I think that with all of that, adding sensors, shields, modules, etc, will be very neat and easy.
Here are some screenshots:
The 2.54mm header, and two 6P and 8P FPCs:

The two 1.27mm header:

And the 40P FPC:

Browsing parts on LCSC and adding all the nets to the schematic was really fun, even if it was indeed a repetitive task. However, thinking about routing all this mess makes me a bit nervous... 😅 but hey, one step at a time!
And here is the updated progression of the schematic:

Now, the last thing to do on the schematic is handling the Power management.
11/30/2025 3 PM - Schematic Part 4: Power Management
The last thing to add in our schematic is the Power Management.
The ESP32 operates at 3.3V, so the CH32 run at 3.3V too. There are 3 ways of powering the board:
(A) - By the USB-C at 5V
(B) - By a dedicated port between 3.5V and 5.5V (but the "+5V" rail will be at this voltage, so if it's not exactly 5V, we have to be careful about what we connect to it)
(C) - By another dedicated port with a steady 3.3V (directly fed into the +3.3V rail).
For the options (A) and (B), there is a 3.3V LDO voltage regulator (TLV75733PDBVR) who provides a steady 3.3V output. I chose this one because it had a small quiescent current (25µA). However, this IC wasn't protected against reverse current: If I power the board directly with 3.3V (option (C)), the output of the LDO would be at 3.3V, while the IN of the LDO would still be at 0V (no input connected), and this scenario could damage the LDO. So it took me a while to figure out what to do, but I ended up using an Ideal Diode Controller (the DZDH0401DW) along with a ultra-low RDSon P-channel MOSFET(the SI2393DS). This way, current can only flow in one direction, from the LDO to the +3.3V rail, without inducing an important voltage drop (it would have been the case with a Schottky diode)
So here are the schematics for the LDO and the Ideal Diode Controller:


And the whole schematic :

So, I think the Schematic is good to go, next time I'll start the best part of this project: Routing! 🤗
11/30/2025 5 PM - Designing the PCB Part 1 : Overall shape
I started the PCB by creating the board outline and by placing the major components (ESP32, GPIO interfaces...). I tried to have something practical (placing the FPCs, USB... on the sides of the board) while still being visually pleasant, and I think that this first sketch is pretty cool. We'll see in the future if we need more space for all the traces, but for the moment, I think it's not that bad! Have a look:


11/30/2025 11 PM - Designing the PCB Part 2
I added the 5 CH32 and their passive components. I started routing the GPIOs to the FPC/Headers, and I know it's going to be pretty difficult to fit all these traces even on a 4 layers board...
Here is the progression:



12/2/2025 6 PM - Designing the PCB Part 3: I'm changing my mind
So... Hum... Yeah:

After another hour of routing the PCB, I definitely know it won't make it: the connections Headers-MCUs are just not efficient at all: when one pin of an MCU goes on the left on the top header, the next pin goes to the opposite side! So obviously, it makes routing way more difficult because the traces need to cross each other... So I'm adopting a different approach: I'll route every MCU pin to the closest Header pin, then I'll change the nets on the schematic according to this disposition, and hopefully I'll get better results.
Let's try it!
12/2/2025 10 PM - Designing the PCB Part 3: Still adding GPIO traces
So, it's moving forward! I rerouted everything, linking the two left MCUs to both the FPC and headers. It was a quite long and tedious task, but still it was fun and I didn't even notice the time passing...
As I said, I had to make some modifications in the schematic to ensure that the traces align well with the header pads without having too much crossing. It was sometimes quite mind-boggling, but I eventually found out how to route everything.
Next time it should be easier, because I don't need to bother connecting the FPC, as the other MCUs only have their GPIOs connected to the 2 headers.
12/3/2025 - Designing the PCB Part 4: GPIOs Routing 100% completed 🥳
Hurray! All the GPIOs are now connected to the 5 MCUs! 🥳
It's taking shape slowly but surely. Actually, I think the final board will look amazing, with all those little 8mil traces running everywhere on the 4 Layer PCB (yeah, I know, the inner layer won't be visible, but still, they are on the design file 😊)




12/4/2025 - Designing the PCB Part 5: Routing progress 90%
I've spent another 2 and a half hours adding and routing the remaining components of the circuit (Power Management, USB C passives...).
While doing this, I noticed an error in the schematic: The VBUS coming out of the USB C wasn't connected to anything, so I replaced it with a +5V tag, as it should have been.
I even added two GND and +3.3V planes on the top and bottom layers, but I still need to add some prohibited regions because the initial plane created by Easy EDA isn't perfect (it is trying to seep through every single part of the circuit, while it shouldn't)
Here’s a preview:


12/5/2025 - Designing the PCB Part 6: Everything is connected 🥳
The PCB is finally done! Hurrah! 🥳
So, it took me quite a while to make sure everything was connected (the ratline layer should be empty then), and for the last 20 or so traces, I needed to move other traces to free up some space... Everything is quite tightly packed... But at the end of the day, I've got a neat and well designed PCB.
I added a few extra components (a TVS diode and decoupling cap near the ESP32-C3) and I changed the FPC connector on the right: It wasn't the same ref as the other ones, so it looked awkward. Now they are all the same so the board is more consistent.
I've designed many PCBs for my electronics project over the last 3 years (2L PCBs), but I have to admit that the routing on this project was harder than on the previous ones. In total, there are 71 components, 490 pads, 112 nets, and ... wait, what did I just read ? ... 289 vias! No wonder if the routing wasn't easy with all these vias on the way 😂! Fun fact: the total length of all the traces is more than 4 meters!
Have a look at this overkill devboard:




So, I'm really happy with how this turned out, and the last thing to do before submitting is arranging the silkscreen layer a bit, so the board is neat and pleasant to look at.
12/6/2025 - Cleaning a bit the silkscreen
I cleaned a bit the silkscreen layer by removing all original silkscreens around the components, and the board looks way better now. I also added custom silkscreen to highlight the GND and +3.3V pins, and a custom logo for my IOS-77 board. (Yeah, I changed the inital name because I thought IOX-77 was cooler, and yes, the devboard actually has 75 GPIOs if we only count the usable GPIOs, but hey, come on! 77 is a way cooler number 😅)
In the meantime, I also changed a few things on the PCB layout to make sure everything runs smoothly (I tried to remove all "bottlenecks" in the power planes, so that all power path are wide enough.)
I really like how this board turned out, and I can't wait to assemble the PCBs and see if it even works.
As it's not my first PCB project, I'm used to solder QFN-20 packages and 0603, but I leveled up the game with 0402 ones this time. I even have two ESD diodes in a SOD882 package (1mm by 0.6mm... I don’t even know if I'll be able to solder them... well, we have to try. And if I can't, all this work will be useless... No seriously, with a good stencil and precise hands, it's fine: the surface tension does all the job!


So here are the 4 Copper Layers of the PCB design:
Top Copper Layer:

Inner Layer 1:

Inner Layer 2:

Bottom Copper Layer:

12/7/2025 - Made some cool renders on Fusion 360
All the previous images were taken from Easy EDA viewing tool.
To finish in style, I decided to make a few higher quality renders.
Here are some renders of the IOX-77 made with Fusion 360:






12/9/2025 - Preparing orders

As I said earlier, I've already made a few PCBs using SMD components, so I'm used to solder QFN20 and other SMD packages. I'll order the components on LCSC, and the PCBs/Stencil on JLCPCB. It's cheaper than choosing PCBA option, and let's be honest, it's way funnier to build your PCB, once you've spent so many hours designing it on a screen😉! In addition to that, I'll need to order a SWIO USB programmer, to programm the CH32V003 (we can find them for pretty cheap on aliexpress).
Here is the BOM for the electronic components: (one PCB)

And here are the additional parts (and other hardware):
Here is the JLCPCB order: 5 PCBs and a stencil. Thanks to the small size of the stencil (I chose 100mm by 100mm), the cost is as low as 3$! Making PCBs has became so cheap these days! 🤗

When ordering components on LCSC, an important part of the cost comes from shipping (even though, it can be reduced to 9 bucks with Global Standard Direct Line). Moreover, the major part of the components are ordered in multiples of 10 or even 100 (0402 res and other). So at the end the only components that I can really choose the quantity are the ESP32, the two 1.27 headers and the five CH32V003 (all the other have a MOQ of at least 5pcs, or more). It means that while building one PCB cost you around 25 bucks in components, building 2 PCBs will only cost you like 5 bucks more...
So I though about it and made some calculations, and I think that building 4 PCBs is the sweet spot: I'm using 4 of the 5 PCBs, the total cost is still relatively low, and most importantly, the per board cost is way lower.
Here is the simulation:

The total cost takes in account the JLCPCB, Aliexpress and LCSC orders, with the components needed to build the board. However, as I used uncommon FPCs and 1.27mm pitched headers to give my devboard a smaller footprint, I also need to buy a few extra components (male headers, FPC cables, and FPC connectors) in order to implement them on custom Shields that I'll design later on.
Here is the LCSC order (IOX-77 devboard components + extra connectors for the upcoming shields):






So the total cost for 4 PCBs is ... less than 60 $ 🥳

(Small update: while checking whether everything was ok, I noticed that I completely forgot to add to the cart 0402 Resistors for the Ideal Diode Controller. I need 2 Res, with values varying from 50k to 2M (50K, 100K, 200K, 500K, 1M, 2M), this way I can tune the reverse current blocking speed vs continuous current consumption ratio.
Not a big deal, the cost increase by a itty bitty 30 cents 😅 )
12/13/2025 - Added the pinout
There are so many pins on this devboard! Thus, I created a PDF with the pinout neatly explained. (the PDF also available on the Github Repo)



1/1/2026 - Ordered the components
I've already ordered the PCB and stencil a while ago, and now I placed the LCSC order, with the electronical components. I'm combining multiple LCSC order, so I don't have to pay multiple times shipping cost and handling fees, so I had to make a top-up form, pay for the additionnal cost in a donation to HCB (15 bucks of other components, for other projects or connectors to make custom shields for the IOX-77 devboard), and I've now placed the LCSC order which adds up to $57.28 ($40.29 worth of components for the IOX-77 project, and $15.49 in additional component).
Can't wait for the components to arrive! 😁
Here are the parts required for the IOX-77 project:





And here the additional parts (for other projects)



So in total:


1/14/2026 - we've got the PCBs 🎉
I received the PCBs, and they look so cool!
To be honest, I though they would be a bit bigger, the pcb always looks big on the screen when designing it, but I guess having a small board footprint is always a good news 😊


Here is a size comparison with the Raspberry pi, so that you've got an idea on how small it is with it's 76 GPIOs :

Hope they aren't any electrical mistake, 'cause the inner layers are kind of inaccessibles 🤣 but I guess we'll find out this WE when the PCB will be soldered...
2/17/2026 - Building the devboard... and encountering the first bug...
This timeline isn't exact, because in reality I assembled 2 boards soon after I received the components and PCBs. It took me about 1 and an half hour to get them assembled, covering the pads with solder thanks to the stencil (what a time saver, considering that for all my previous projects I dispensed solder paste on each pad by hand... relaxing, but it's time consuming and more prone to bridges...)
I then placed all the components and reflow soldered the 2 boards. The first one wasn't looking great, since the GND and 3V3 were shorted (turns out it was the ESP soldering that didn't went well)... which wasn't the case on the second board. So I plugged it onto the computer, rushing to upload my first 'blank' code onto the ESP32... which was horrific since the board wasn't even recognized by the PC... I was so disappointed that it didn't work the first time, considering that I was using a prebuilt ESP32 C3 module, not the bare chip! what could have gone wrong! After the first moment of deception, I tried to figure out what wasn't working: I checked all the PWR rails, 4.90V on the 5V rail: perfect considering the voltage drop caused by the diode, and 3.28V on the 3V3 rail... Ok the board was powered correctly. I remembered that to programm ESP32, you need to execute a serie of button presses (Hold BOOT, plug the board into the computer, press and release RESET, release BOOT), so I tried multiple times, without succeeding... It was the end of the Weekend, so I told myself: You know what? screw it: I'll deal with this during the holidays...
So a few weeks later, I got back into it: I soldered another board, with only the required parts (ESP32 + USB + LDO), maybe the problem came from the CH32s or other mistake on the board... I tried a few more things, checking the schematic, D+ and D- routing, and eventually asked on slack, on #electronics, and huge shout out to @grimsteel for finding the error, and thanks too to @Madhave for his precious help.
I completely messed up the wiring of the BOOT and ENABLE (RST) buttons: I connected the pairs the wrong way, resulting in 2 wires instead of 2 buttons... No wonder the ESP32 couldn't upload, if it was held in Reset forever!
So I desoldered the two buttons on each board, and just tilted them to 60°, to realign the pairs correctly. Actually, I like them this way 😂! unfortunately, the ESP32 on the 'half soldered' board was recognized but failed to upload, stuck at 'connecting...' maybe I fried it with the numerous reflow rework (all the pads underneath the ESP32 module are everything but maker friendly...),
However, I tried on the 2 other boards, and they were uploading perfectly. I was so happy it finally worked 🎉! I soldered a third board, which uploaded well too, so I was able to get 3 fully assembled and working IOX-77 boards out of 4 expected: 75% success rate, not that bad honestly 😂😅! ... at least for the ESP32 part... Haven't tried yet the main feature of this devboard: the CH32 cluster...
%20BLP.jpg)
I desoldered the two buttons on each board, and just tilted them to 60°

I connected the pairs the wrong way, resulting in 2 wires instead of 2 buttons...
I uploaded a basic Arduino test code, to see if I could toggle a GPIO (IO0), and whether the serial communication worked, and it worked just fine on the 3 boards.
Now, the last thing to do is to figure out how to programm the CH32V003, and make them behave as smart GPIO expanders with the ESP32...
Until I advance a bit more on it, here are a few pictures of these 3 IOX-77 superboards:
Gallery:
%20BLP.jpg)
.jpg)
.jpg)
.jpg)
.jpg)
%20BLP.jpg)
2/24/2026 - Let's start with the CH32 cluster!

So, I started learning how to program those cheap Risc-V MCUs, and it actually seems fairly complicated... Here is what the 'blink' program looks like... nothing to do with the Arduino Framework...

Actually, I think I'll enjoy programming those cuz I feel like I'm more professional that with Arduino... I feel like I'm doing bare metal programming (which actually isn't), but I mean I'm way more into hardware, not just 'digitalWriting' and 'pinModeing' stuff 😉...
Learning how to code them properly will be quite time consuming, so I don't know if I'll have enough background before Blueprint ends to have a fully working IOX-77, because I don't want to go straight to the fairly complicated I2C program, but instead learning progressively how to code those chips.
This is why I started with the blink program, which is not less than 33 lines 😓
Anyway, I don't have the 3 boards with me, only one, so I tested it with an FPC adapter and jumper wires, but only 3 ICs uploaded, and looking more carefully, the other 2 IC's SWIO pins are not well soldered, so it's not responding. Or they are just fried, but it's less likely I think... I'll try to resolder them properly one day, but I'll also try the other 2 boards to see if it's better.
I also uploaded a basic sketch on the ESP32 to reset all the chips at boot up, and hold their NRST pins HIGH once it's fully initialized:

Now that I've discovered the Mounriver Studio IDE, I'll progressively learn how to code them, thanks to the few really well documented websites (like the Curious Scientis. This article is for getting started with GPIOs: https://curiousscientist.tech/blog/ch32v003f4p6-setup-and-gpios?rq=CH32)
3/7/2026 2 PM - IOX-77 CH32 cluster code Part 1 (out of many...)
I started the code for one of the CH32, starting with the GPIO basis.
Each CH32 will be communicating with the ESP32 via I²C, giving orders like 'set PA2 as Pull up' or 'write HIGH on PB2'... so on the ESP32 side, I'll have every major GPIO related functions (pinMode(), digitalWrite(), analogRead() and so on) customized for the IOX-77, each of these functions will send basic commands via I2C, and then the CH32 executes it. I'll thus need a 'custom' pinMode(), digitalWrite(), etc. on the CH32 side, this is why I'm starting with the custom pinMode() function.
I really want to build the firmware myself, without vibecoding it, but as the documentation is very minimal (only a few guides explains how the CH32V003 environment works), ChatGPT is helping me to learn how to make it work, especially understand all the GPIO_InitStructure... and other weird looking lines like RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE) and all the other things I'm not used to. Then once I think I understand what each functions does and how it behaves, I try rewriting it on Mounriver Studio, tweaking it a bit so that it does exactly what I need, before asking ChatGPT to correct me if something won't work or if there is any syntax error. I already know that with all the hours I'll have to spend on coding the software from the ground up, I'll learn a lot of things along the way!
So for the custom pinMode function, I needed a function with 2 parameters, the pin name, and the mode (OUTPUT PUSH PULL, OUTPUT OPEN DRAIN, ANALOG INPUT, and so on...). For the pin name, the CH32 syntax needs the PORT and PIN NUMBERseparately, so I added a pinMap which links a number (the pin name) to two variables, the PORT and the PIN NUMBER.
I also created the custom pinMode() function and a custom digitalWrite() function.
So now with these two functions, I should still be able to make the LED blink, but with a lighter (visually) code. And... yeah it still works just fine. Perfect!
Here is what this snippet looks like:

Testing the other boards...
I also tested the 2 other boards this weekend, and the 10 CH32V003 blinked perfectly. It's also interesting to notice, like bitluni did before me, that the clock of each CH32 becames out of sync pretty fast, and the '1000s' blink becames a mesmerizing random light ballet. that's actually kinda fun!

3/7/2026 8 PM - IOX-77 CH32 cluster code Part 2
The next function to implement was digitalRead(). And while the function itself was pretty straightforward, it showed me a problem that took me a while to debug...
So here is the function, and to test whether it worked or not, I made a quick test with a button (the LED lights up when the button is pressed). the first GPIO that I found accessible was chip #D, PA1, one of the 4 CH32 pins broken out on the classical 2.54 headers, so I quickly uploaded the test.
.jpg)
And... when I uploaded the script, quickly after hooking up a button to the right pin, it... didn't worked... So obviously I first thought that there were an error in the software, that I missed something or did something incorrectly, but after triple checking the (simple) code, and letting AI having a look at my work, everything seemed just fine... So I tried with PA2, the pin just next to it, and same problem... what could have been wrong? I tried to see if the pin was faulty, by setting it to OUTPUT, and blinking it, like with the LED on PC0, and instead of getting 0V then 3.3V alterning, I got a poor lil 0.57V 😭. Maybe the PORT A was fried on this IC? no problem, I've got another 15 ready for testing 🤣 but still 0.57V continuous... I even ended up thinking for a brief moment that my multimeter was just hallucinating 🤣! But obviously it wasn't wrong since I tried other ports too, and it was working on PC7... I checked the PCB layout, to ensure it was well connected, that the .5V wasn't induced by nearby traces, and at the moment I started running out of ideas, I looked at the CH32 pinout, and realized that they could be used as an alternate function: OSC, 2 pins for an external oscillator... maybe that was the problem?
So I was like 'hey, chatGPT, do you think this problem could be related to the alternate function of the pins PA1 and PA2?' and it went '✅ you nailed it, that's very likely the cause'. It meant that the IC was configured by default to accept an external oscillator, which made PA1 and PA2 unusable. So instead of going through all the lines of code in all the .c files associated with the CH32V003 IDE, whithout really knowing what could cause the problem, I gave the codes to Chat GPT and he found pretty quickly in the system_ch32v00x.c file, that the uncommented line was #define SYSCLK_FREQ_48MHz_HSE 48000000, so a 48MHz external oscillator, instead of the #define SYSCLK_FREQ_48MHZ_HSI 48000000 I needed. Ok, to be fair, I could have found it myself, it was actually self explainatory 😅 (even neatly explained with comments), but yeah, it's definitely faster when AI does the job, and I didn't wanted to read 700+ lines while the IOX-77 was stuck at the very beginning of the development phase...
.jpg)
With that being solved, the test code for the digitalRead() was working just fine, and even though it seems like a very small victory, I'm really happy it finally works... You definitely experience a one of a kind feeling once you've solved a Hardware/software problem, after debugging it for way to long 😅
3/8/2026 - IOX-77 CH32 cluster code Part 3
I then started dealing with PWM. It was a bit more complicated than digitalWrite() or digitalRead() functions, as it needs Timers, but following The Curious Scientist code, I modified it so that I can choose on which PIN the PWM is outputted, and instead of taking as parameters PRSC, ARR and CCR, it directly calculates these values from the desired output frequency and the duty cycle.
Extract of the PWM course by The Curious Scientist:
https://curiousscientist.tech/blog/ch32v003f4p6-timers-and-pwm?rq=PWM


And here is the snippet for handling PWM (just after the digitalWrite() function):
%20PWM.jpg)
So after checking that the desired PIN is PWM capable, it calculates the values for the Timer (PRSC (prescaler), CCR, and ARR) using the given formulas, and sets up the PWM while differentiating the actions depending on the pins (on which Timer (1 or 2) and on which channel (1 - 4) is this pin?) but basically do the exact same thing as explained in the guide by The Curious Scientist (huge shout out to him, by the way, his guides are priceless!)
And the main program, with the LEDs fading in and out:
%20PWM%20main.jpg)
So after tweaking a few things to make it work perfectly (I first forgot to differentiate the channel depending on the pin, so it didn't worked on all pins), I got something that seems to work just fine: I haven't looked at the outputted signal (I don't have any oscilloscope), so I don't know if the frequency/duty cycle is correct, but at least fading the LED works well.
So overall I'm really happy with this function, because it still make the main program way more readable, with a similar HAL that in the Arduino environment (one line for controling PWM), but it's also way more powerful: a 4 times higher precision (0 - 1023) is achievable on this faster MCU, while even being able to control the outputted frequency.
EDIT: Actually I just remembered that my multimeter could measure frequency and duty cycle (tbh I've never used this feature before), and after checking on the pins PC0 (the LED of each CH32), it was indeed around 1KHz (it displayed smth like 994Hz, so I consider this result as a Pass) For the duty cycle, the results were unreadeable, which is obvious since the LED are fading so constantly changing their duty cycle, so I think I'll just make another small code to ensure it works well.
3/22/2026 - IOX-77 CH32 cluster code Part 4
Ok... so... I decided to deal with the I2C, and it was definitely harder than expected... I couldn't really take inspiration from the curious scientist website, since he only use the CH32V003 as master (I needed it as a slave), so I tried to look online, searching for code that already existed to use the MCU as a slave, and... I spent two afternoon, (~8 hours 😭) going from searching resources online with little to no results at all, vibe coding it with chatGPT (which was so bad at it that it mixed everything up, adding lines of code in C for the STM family 😭), documenting myself on I2C, testing the communication with the ESP32, went back to searching online, vibecoding again using another approach, doing smaller tests in I2C between the ESP32 and an Arduino (which took me a while since I didn't even knew how to assign the SDA and SCL pins in the wire.h library (turned out I just had to add the pin number as parameters in the wire.begin(10, 8); line), I even thought at some point that my design was cooked since I used GPIO10 on the ESP32 (which is used by the flash, so I thought for a moment it would never work... [this is me from the future: Actually I made it working, but I don't know if it's reliable, but... whatever] ... This endless loop of FAIIIILURES helped me learning stuff about I2C... but didn't helped me build my code for this IOX-77 project... I thought multiple times that choosing I2C wasn't a good idea, that my IOX-77 V1 would never work and that I should design the V2 using SPI (easier to implement and faster...) but I couldn't give up, not now, after all these hours working on this project...
So I kept going, searching online again and again to a solution for my problem, and I stumbled upon a reddit talking about using the CH32 as a slave. To be honest I had already seen this thread multiple times, but they only mentioned (or it's what I thought at first) the CH32fun library, which didn't fitted my requirements, so... But they showed a link to the official code example made by WCH, and I was so happy to see that the example provided was exactly what I needed: It implemented the I2C both for a slave, and for a master! I finally had a working resources to build my custom code around it!!! without further ado, I tested the code with 2 of the CH32, one as slave and one as master, and (obviously) it worked just fine!. So I tried to replace the Master CH32 with the ESP32 as master (this time I vibecoded it with chatGPT, because I really wanted to see if it would work, and as I gave him the ressources (CH32 I2C example code), it was easier for him to generate a working code for the ESP32 (using wire.h) and he also created a near bare metal code without using the wire.h library, and they both worked just fine! ... or at least they ended up working just fine 😅 because obviously the drop in replacement code didn't worked the first time... It took me a while for me to figure out (or let's say for ChatGPT to figure out) that there were an address mismatch, because the CH32 shifts addresses internally (for like no reason at all 😭), so the address 0x02 isn't the same on the ESP32 code as on the CH32 code... So for it to work I just replaced the 0x02 by 0x04 on the CH32 code, (shifting it to the left), and IT WORKED!!! ... (I mean I haven't looked at it enough, maybe some transmissions ends up generating errors, but at least I was able to get some data to show up in the Serial Monitor!!!!) So now that I'm sure it's possible to get a working I2C com between these 2 MCUs, I'll look deeper into the code to fully understand it, and modify it for my needs. I'm so relieved the I2C is finally working!!!!
Yeah, so here are some previews of the code I used:
Code for the ESP32 (using wire.h):

Code for the ESP32 (near bare metal):

Extract of the code for the CH32 (slightly modified demo from WCH (I added checkpoints via UART for debugging)):

I think I'll build the code around the near bare metal code for the ESP32, because it's way more explicit that the wire.h library, which does everything by itself so everything is hidden from my comprehension. So I'll try to build a reliable communication in I2C using these ressources, and hopefully I'll get something working perfectly!
(note: I'm not taking in account the 8 hours I lost searching-online/vibecoding/debugging/pulling-my-hairs-out/going-back-to-online-search/ending-up-vibecoding-again..., because it didn't gave me any results... I really started doing useful things once I had found the miraculous demo example from WCH)
4/1/2026 - IOX-77 CH32 cluster code Part 5
Now that I had a working I²C code basis, I built bit by bit a working communication protocol between the ESP32 and CH32s, and started adding more and more features. I decided to use directly the driver/i2c.h library, instead of relying on the wire.h library. but I still created 2 functions to make things easier: sendI2CCommand (CMD) and retrieveI2CData ().
The ESP32 can send 2 types of command, sendI2CCommand (CMD), where the ESP32 asks for the CH32 to do something, and one retrieveI2CData (), where the ESP32 retrieves the data prepared beforehand by the CH32. For example, the digitalWrite function only requires to send an order, so it works like this:
uint8_t CMD [SIZE] = {0x10, pin, state, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
sendI2CCommand (CMD);
where 0x10 is the name of the command (digitalWrite()), with the pin and state being parameters. I filled the remaining bytes with 0xFF since I decided that all I²C transmissions will consist of 8 bytes, which should be enough for most commands.
So I implemented the IOX_pinMode(), IOX_analogWritePWM(), IOX_digitalWrite() functions this way, using sendI2CCommand ()
For the digitalRead() function, I decided to go a little fancy with more than just a digital reading... I thought it would be cool for the ESP32 to read many digital readings at once (the 32 last readings in time) in one go instead of sending an I²C command every time the ESP32 needed the state of one pin. So I did something different that just the 'ESP32: Send me the pin state on pin XX -> CH32: here it is: X', and implemented the following:
If the ESP32 wants the pin XX to be read every X ms, he first sends a command using the function: IOX_digitalReadSample ()
void IOX_digitalReadSample (uint8_t pin, uint16_t interval, uint8_t multiplier) {
uint8_t CMD [SIZE] = {0x13, pin, (interval >> 8) & 0xFF, interval & 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
sendI2CCommand (CMD);
}
where the pin and reading interval (ms) can be chosen.
Then when the ESP32 wants the 32 last readings (so each reading is spaced in time by interval ms), the ESP32 can call IOX_digitalReadBuffer ():
uint32_t IOX_digitalReadBuffer (uint8_t pin) {
uint32_t initTime = micros();
uint8_t CMD [SIZE] = {0x14, pin, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
sendI2CCommand (CMD);
while (initTime + 500 > micros());
retrieveI2CData();
uint32_t reading = (uint32_t)RxData[2] << 24 | (uint32_t)RxData[3] << 16 | (uint32_t)RxData[4] << 8 | (uint32_t)RxData[5];
return reading;
}
With this setup, it's the CH32 that does all the readings by itself, and only send the last 32 readings when the ESP32 needs them, all in one go. I'll do a similar approach for the ADC, this way I could add some post processing (like averaging the reading) before sending it to the ESP32.
So the I²C communication for retrieving data is implemented this way:
Whenever the ESP32 wants to get some data back, it first sends a command to the CH32 indicating that he need to add the required data in the TxData buffer, ready to be retrieved by the ESP32 with the function retrieveI2CData();. It's to ensure the CH32 has enough time to update the TxData buffer that I add a small delay before doing retrieveI2CData(); (plus it prevent the bus from being overloaded)
On the CH32 side, if the IOX_digitalReadSample() has been received, the CH32 sample the desired pin constantly, every interval ms. For that I used a structure with all the possible GPIOs (13), with multiple variables (PINSTATE, PREV_MS, ..., INTERVAL...)
typedef struct {
uint32_t PINSTATE;
uint32_t PREV_MS;
uint16_t INTERVAL;
uint8_t ENABLE;
} digitalReadPinState;
digitalReadPinState digitalSampling[] = {
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0},
{0x00000000, 0, 0, 0}
};
So... for the CH32 to digitalRead() each pin at a precise rate (set by the INTERVAL value), I needed to check the time in the main () loop, so everyone would have use millis() (or micros()) in the Arduino IDE... except that the CH32 HAL didn't have any millis() or similar function... thankfully it wasn't too hard to implement a similar behavior, with the Systick timer. (hopefully I found a code online that did almost exactly what I wanted!). With too main functions being:
uint32_t ms = 0;
void SysTick_init (u32 counter) {
NVIC_EnableIRQ (SysTicK_IRQn);
SysTick->SR &= ~(1 << 0);
SysTick->CMP = (counter - 1);
SysTick->CNT = 0;
SysTick->CTLR = 0x000F;
}
void SysTick_Handler (void) {
ms++;
SysTick->SR = 0;
}
Hum... ok I won't descibe everything in detail, since it's start being a lot with the 500+ lines on the CH32 code, but in a nutshell, the main loop consists of handleI2C(); and digitalRead_sample();, where digitalRead_sample(); sample the GPIOs states if the it's enabled for the pin X:
void digitalRead_sample() {
for (uint8_t p = 0; p <= 13; p++) {
if (digitalSampling[p].ENABLE == 1 && (ms - digitalSampling[p].PREV_MS)>= digitalSampling[p].INTERVAL) {
digitalSampling[p].PREV_MS += digitalSampling[p].INTERVAL;
uint8_t newBit = IOXdigitalRead (p);
digitalSampling[p].PINSTATE = digitalSampling[p].PINSTATE << 1;
digitalSampling[p].PINSTATE = digitalSampling[p].PINSTATE | newBit;
IOXdigitalWrite (2, toggle);
toggle = !toggle;
}
}
}
you'll notice that I tested the 'pace' of my function toggling the built in LED (on PC0 aka 2 according to my pinMap[] structure), making sure it worked correctly, and I indeed got a sampling frequency of 1 kHz when I set the interval to 1ms (IOX_digitalReadSample (6, 1);)
(I read 500Hz on my multimeter, and the LED is toggled everytime the function is entered)
Here is what it looks like when you print the PINSTATE uint32_t retrieved by the ESP32 at each IOX_digitalReadBuffer(): when I press / release the button on PC6, the state of the pin change from 0 to 1 or from 1 to 0, and the new value (measured by the CH32) is added in the 32 bit queue, thus getting rid of the 32nd oldest reading. This is why the 1 and 0 seem to move to the left at each new print on the serial monitor:

Whenever a packet is received over the I²C, the packets are saved in the RxData[] array, then at the end of the transaction, buffered in RxDataBuffer[]. the flag cmd_ready is set to 1, meaning that the command is ready to be analyzed by the function handleI2C();, which determines what the CH32 has to do depending on the command and parameters (with switch () case: syntaxes)
Another interesting thing to note, is that as I'm limited with packets of 1 byte transiting on the I2C bus, variables that are longer than that (uint16_t or uint32_t) are split in smaller packets, then reconstructed on the other side, with for example the variable freqOUT or duty_cyclefor the PWM:
void IOX_analogWritePWM (uint8_t pin, uint16_t duty_cycle, uint32_t freqOUT) {
uint32_t initTime = micros();
uint8_t CMD [SIZE] = {0x11, pin, (duty_cycle >> 8) & 0xFF, duty_cycle & 0xFF, (freqOUT >> 24) & 0xFF, (freqOUT >> 16) & 0xFF, (freqOUT >> 8) & 0xFF, freqOUT & 0xFF};
sendI2CCommand (CMD);
while (initTime + 1000 >= micros());
}
then on the CH32 side:
uint16_t duty_cycle = (uint16_t)RxDataBuffer[2] << 8 | (uint16_t)RxDataBuffer[3];
uint32_t freqOUT = (uint32_t)RxDataBuffer[4] << 24 | (uint32_t)RxDataBuffer[5] << 16 | (uint32_t)RxDataBuffer[6] << 8 | (uint32_t)RxDataBuffer[7];
It took me quite a while to get to this point, trying many things in C on the CH32 code (like for example pushing the CH32 to it's limit, implementing a micros variable like I did with millis, but it's not optimal with the interrupt of the Systick, since it would have been entered way too many times (1 millions times per seconds!) and it was a bit laggy, I couldn't get the digitalRead sampling to be at a higher frequency that 4.5KHz using micros instead of millis, probably because the micros interrupt was taking way too long.
Anyway I'm not explaining everything I've done in the firmware because I'm already talking too much, but in a nutshell (yeah, this time I stop digressing 😅), I implemented the functions IOX_pinMode(), IOX_analogWritePWM(), IOX_digitalWrite(), IOX_digitalReadSample() and IOX_digitalReadBuffer() over I²C, so I'm now able to fully control the GPIOs on one of the CH32 by simply coding the ESP32! You can't imagine how happy and relieved I was to see the first IOX_digitalWrite() working over I²C! After so much struggle getting the I²C to work! That was so fun seing an LED blynk-over-I²C 😂
Now, the last thing to do is to tackle the ADC reading on the CH32 side, and I'll then just have to make some HAL on the ESP32 side, cuz I'll have to do a few things for controlling all those GPIOs easily, without messing up which CH32 control which pin 😂
NOTE: the advancement of the code is in the Github repo, to avoid overloading the Blueprint journal
