Posts

How to Use the Built-in Operational Amplifier in CH32V003

Image
The CH32V003 microcontrollers (as well as the CH32V006) feature a very interesting piece of hardware - a built-in operational amplifier (OPA). In this article, I will explore and practically demonstrate how to use it for measuring small voltages, such as reading data from various sensors or current shunts. Before we get to soldering, let’s take a quick look at the architecture. The built-in OPA has multiplexed inputs, meaning it can be software-switched to take a signal from different pairs of pins. For example, the TSSOP20 package offers two groups: Group 0 ( PA1 — OPN0, PA2 — OPP0) and Group 1 ( PD0 — OPN1, PD7 — OPP1). However, the output always remains the same — it is hardwired to the PD4 pin (OPO). The situation is slightly different with the smaller chips in the SOP8 and SOP16 packages. The multiplexing mechanism is still present in the silicon, but the required pins are simply not physically broken out, so there are effectively no switching options. (By the way, there is...

Understanding Rotary Encoders: Teardown, Oscilloscope Signals, and C Code for CH32V

Image
A Bit of Theory An encoder is an electromechanical component that helps electronics determine the speed and direction of a shaft’s rotation, and in some cases, accurately register its angular position. Therefore, if your project involves moving parts and you need to track their rotation or angle, you cannot do without this sensor. We don’t need to look at complex industrial machinery for examples - we encounter encoders constantly in everyday life. A computer mouse wheel, a car stereo volume knob, or a washing machine mode selector - all of these are convenient physical interfaces for interacting with technology. The simplest encoders can only register movement. In the case of a mouse, we don’t care about the exact position of the wheel - we just scroll it up or down to scroll the page. But for some processes, the exact position is crucial, which is where more complex absolute encoders are used. By design, these sensors are based on various physical principles. Inside, there migh...

Available WCH-Link Programmers

Image
Wch-Link V1 When I first paid attention to CH32V microcontrollers on AliExpress, I immediately bought a few different boards: a couple with the CH32V003 and a more powerful one with the CH32V307. However, a logical question immediately arose: how do I program them? My usual ST-Link definitely wouldn’t work for this architecture. So, I just found the first available programmer with the WCH label from the same seller and added it to my order. It turned out to be the WCH-Link v1, based on the CH549G chip. Its pros: Relatively low price: during sales, you can find them starting at $2. Versatility: it supports both RISC-V based WCH microcontrollers and ARM-compatible chips in DAP mode (although I haven’t tested the latter in practice yet). Built-in USB-UART: this is incredibly convenient for debugging, and it’s a feature I often missed in the standard ST-Link. However, there were also downsides that came as an unpleasant surprise. The main drawback is that it does not program th...

Bit-bang vs. Hardware I2C: Running U8x8 on CH32V003

Image
In a project with an OLED display and an STM32F103 microcontroller, a question arose: can we make this cheaper? What if we use the CH32V003? We don’t need many pins, so the SOP16 package will be enough. The main question is how it will work with the SSD1306 OLED. Let’s find out. As I wrote earlier, U8x8 should be easy to port and can work with software I2C (bit-bang). Porting the code from CMSIS went very quickly — a couple of cups of coffee, and I got the example working. Here is the code for the u8x8_gpio_and_delay_ch32v function: uint8_t u8x8_gpio_and_delay_ch32v(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch(msg) { case U8X8_MSG_GPIO_AND_DELAY_INIT: RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef init = {0}; init.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2; init.GPIO_Mode = GPIO_Mode_Out_OD; init.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &init); ...