CH32V003J4M6 - Low power modes

So far, I discussed many projects that are breadboard or printed circuit board-based. These projects are powered by USB, so we do not really care about power saving or power consumption. However, if we want to make something portable or standalone, it is a good idea to use one of the low-power modes of the microcontroller. So, in this article, I will discuss some of the possible ways to save power with this microcontroller. I picked the J4M6 microcontroller again because it is more likely to be used in low-power projects, but the principles are applicable to other CH32V003 microcontrollers as well.

 

Introduction

So, the microcontroller offers two low-power modes: sleep mode and standby mode.

In sleep mode, the core stops running, but all peripherals are kept on, and their clocks are still enabled. This means that all GPIOs are still in run mode (normal operation). For further power-saving, they should also be disabled. The advantage of this sleep mode is that it is the easiest to wake up from. Typically, we can expect around 600 uA current consumption (@3.3 V) when all peripherals are disabled and the HSI is set to 8 MHz.

In standby mode, all clocks are stopped. In standby mode an optional low-frequency (128 kHz) oscillator (LSI) can be used to automatically and periodically wake up the microcontroller. Based on whether the LSI is enabled or not, we can expect around 10.5-9 uA current consumption (@3.3 V). It is worth keeping in mind that the LSI is not very stable, its typical frequency is between 100-128-150 (min-typ-max) kHz. This obviously affects the wake-up period, but I will discuss this later.

Regarding clocks, let’s not forget that we use the J4M6 chip that does not use an external clock source (HSE), so we need to modify the system_ch32v00x.c file in order to have the proper main clock frequency. To further conserve power, instead of the default 48 MHz, I see the internal clock frequency (HSI) to 8 MHz. For simple tasks, this is enough.

#define SYSCLK_FREQ_8MHz_HSI    8000000
 

CH32V003J4M6 chip on a CR2032 battery. Based on the battery’s ~225 mAh capacity, the chip could be in standby for about 22500 hours, or 2.5 years.

 

Sleep mode

I set up a very simple code to test the sleep mode and demonstrate the power consumption of the chip.

So, as I mentioned, in sleep mode, the peripherals stay awake. To wake the device up, I will use an interrupt, defined on port C, pin 1 (PC1). This requires some setup around the external interrupt (EXTI) and the interrupt vector (NVIC), but it is nothing extraordinary, I already showed these in an earlier article. The extra thing that must be done to conserve more energy is to disable all GPIOs by setting them into GPIO_Mode_IPU mode. IPU stands for internal pull-up. This prevents the pins from picking up noise or toggling between low and high states which can cause unnecessary current flow through the internal circuits. Alternatively, we could set the pins as analogue inputs, however, this is a bit more complicated because not all pins support it.

The code goes to sleep after all the peripherals are set up and the while(1) is performed once. Since I don’t use the USART, the only way I can see the microcontroller (other than measuring the current consumption) entering and exiting sleep mode is to switch an LED. When the code enters the while(1) it turns the LED on and it waits 2 seconds. Then it turns the LED off and enters sleep. It will stay in sleep mode until I press the button. Once the button is pressed, the code returns to the while(1), toggles the LED and goes back to sleep.

 while(1)
 {
    GPIO_WriteBit(GPIOC, GPIO_Pin_1, Bit_SET);
    Delay_Ms(2000);
    GPIO_WriteBit(GPIOC, GPIO_Pin_1, Bit_RESET);
    __WFI();
 }

This kind of behaviour can be ideal for devices that need to perform something when the user interacts with the circuit and once the interaction is completed, the circuit goes back to sleep mode. An external interrupt can be other than a button press, as long as it changes the state of the GPIO pin. So, for example, a motion sensor’s output can be connected to the GPIO and the microcontroller could control some automatic lights. Or, a real-time clock’s interrupt signal can be connected to the GPIO and for every interrupt from the RTC, the chip could refresh the time and other data, such as temperature, on an e-ink display. The possibilities are endless.

After the chip enters sleep mode, its power consumption becomes ~633 uA which is about the same value as the 0.6 mA stated in the datasheet.

It is probably not the best sleep mode if you want to operate something from a CR2032 coin cell (225 mAh) because the expected battery life is rather short.

 
void GPIOConfig()
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; 
    GPIO_Init(GPIOC, &GPIO_InitStructure);
}
void EXTI2_INT_INIT(void)
{
    EXTI_InitTypeDef EXTI_InitStructure = {0};
    NVIC_InitTypeDef NVIC_InitStructure = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2); 
    EXTI_InitStructure.EXTI_Line = EXTI_Line2;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI7_0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
 

Standby mode

This mode is the real power-saving mode. I configured the microcontroller in two different ways:

  • one way uses an external interrupt (EXTI) event which is created by an internal timer (LSI)

  • another way uses an external interrupt (EXTI) created by pressing a button, similar to the sleep mode’s example

The timer-based wake-up routine is based on the internal low-speed clock (LSI) which is ticking at 128 kHz. The period of this timer is 7.812 us. We can apply a prescaler (AWUPSC) on the clock speed to increase the timer period. This is very useful if we want to have longer sleep periods. The auto-wakeup window (the time while the chip is sleeping) is controlled by a 6-bit register value, AWUWR. This value is used to compare with the up counter value. As we learned in the timer lesson, a timer can count up to a certain period. This is exactly what we define when we define a prescaler value. Together with the prescaler, we create a period of time and then this period of time has to pass X times (AWUWR times) to trigger a wake-up event in the microcontroller. Once the microcontroller is awake, it returns to the while(1) loop and performs whatever is in the loop. The sleep mode must be started manually again. However, as I mentioned earlier, the manufacturer defines a rather wide range for the LSI frequency: between 100-150 kHz. This means that the period can actually range between 10 us down to 6.67 us. When using the prescaler of 10240 and wakeup register value of 25, this can lead to large discrepancies in wakeup times (2.662 s vs. 1.775 s)! So, don’t use this mode for time-critical applications.

The wakeup timer has its own interrupt routine. According to the reference manual’s 2.3.4 section, the AWU interrupt function can be triggered via a rising or falling edge trigger of the 9th line connected internally to the EXTI module (EXTI_Line9).

The other approach does not utilize any timers. We simply put the chip in standby mode and then the chip is woken up when an interrupt is created when we press a button. The same interrupt is used that we use for the sleep mode.

In both cases, the microcontroller is put in standby mode using the same line of code. The only difference between the “auto wakeup” and the “button wakeup” modes is the use of LSI.

while(1)
{
    GPIO_WriteBit(GPIOC, GPIO_Pin_1, Bit_SET);
    Delay_Ms(2000);
    GPIO_WriteBit(GPIOC, GPIO_Pin_1, Bit_RESET);
    PWR_EnterSTANDBYMode(PWR_STANDBYEntry_WFE);
}

With LSI on, the device consumes 11 uA in standby mode. With LSI off, the device consumes 9.6 uA. Both values match the datasheet values well. Considering 11 uA standby current, nearly 22500 hours can be squeezed out from a CR2032 battery in standby mode which is more than 2 years.

 
void EXTI9_INT_INIT(void)
{
    EXTI_InitTypeDef EXTI_InitStructure = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    EXTI_InitStructure.EXTI_Line = EXTI_Line9;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}
void standbyConfig()
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    RCC_LSICmd(ENABLE);
    while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
    PWR_AWU_SetPrescaler(PWR_AWU_Prescaler_61440); 
    PWR_AWU_SetWindowValue(25); 
    PWR_AutoWakeUpCmd(ENABLE);
}
void EXTI7_0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

void EXTI7_0_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line2)!=RESET)
    {
        EXTI_ClearITPendingBit(EXTI_Line2);
    }
}

Extra resources

If you found this content useful, please consider joining my YouTube channel’s membership or leaving a donation.

Also, please consider using my affiliate links when buying relevant gadgets.

Previous
Previous

CPU and RAM monitor, literally

Next
Next

CH32V003J4M6 - Breadboard voltmeter