HT32F52352-Based Intelligent Electronic Load Open Source, Holtek Cup Project Open Source

Open-source Intelligent Electronic Load based on HT32F52352, Holtek Cup entry open-sourced, includes schematic, PCB, source code, and project report.

Third-prize work in the 10th Guangdong Province University Student Holtek Cup Microcontroller Application Design Competition 2023.

Rushed out in one month (with lots of classes and little time), very mediocre, please don’t roast.

Demo video: https://www.bilibili.com/video/BV1sM4y1b7qu/

This open-source project is for reference and study only; replication is not recommended. Much better and more complete electronic-load projects are available on the Lichuang open-source platform!

Lichuang open-source project link: https://url.zeruns.com/xvvF8

Holtek HT32 MCU development environment setup guide: https://blog.zeruns.com/archives/709.html

Electronics / MCU tech chat group: 2169025065

What is an Electronic Load

An electronic load is a device that simulates real load conditions to test the performance of power supplies or electronic circuits. Compared with traditional passive loads such as high-power adjustable resistors or heater wires, electronic loads offer adjustable parameters and ease of use. Whether for professional electronic engineering development or hobbyist projects, an electronic load is an essential instrument.

By the type of power supply under test, electronic loads are divided into AC electronic loads and DC electronic loads. Functionally, the common types are constant-current, constant-voltage, constant-resistance, and constant-power. Since most common power supplies are constant-voltage DC sources, testing mainly focuses on their current output capability; therefore, in most applications, a DC constant-current electronic load is the most common type. By control method, electronic loads are classified as either digital or analog. Compared with purely analog-controlled electronic loads, digital-controlled electronic loads provide more intuitive parameter adjustment, richer functions, easier expansion, and convenient automation of tests.

Project Overview

This work uses the HT32F52352 Holtek MCU as the main controller. It is powered by an 18650 Li-ion battery for portability.

Control method: the MCU outputs a PWM wave to a low-pass filter to generate a relatively stable DC voltage (DAC function via PWM + low-pass filter) as the reference voltage for the op-amp, which compares it with the amplified voltage/current sample; the op-amp output drives the MOSFET, achieving constant-voltage/constant-current operation.

The touch screen is a 2.8-inch serial screen from Taojingchi, model TJC3224T028_011R.

Photos

Didn’t take many photos at the time—only these two were found; check the demo video for more.


Download Links

The packages below contain: circuit schematic, Lichuang EDA project files, PCB fabrication files, source code, serial-screen project files, and chip datasheets.

123 Cloud Drive: https://www.123pan.com/s/2Y9Djv-ZNevH.html

Baidu NetDisk: https://url.zeruns.com/XP241 code: i9bl

Component Purchase Links

It is recommended to buy components from LCSC: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Schematics

Power Board



Power-supply Board

Control Board

PCB

Power Board

Top Layer

Inner Layer 1

Bottom Layer

Skipped the GND layer screenshot.

Power-supply Board

Top Layer

Bottom Layer

Control Board

Top Layer

Bottom Layer

Main Code

main.c

#include "ht32.h"
#include "GPIO.h"
#include "BFTM0.h"
#include "GPTM0.h"
#include "GPTM1.h"
#include "MCTM0.h"
#include "delay.h"
#include "OLED.h"
#include "WDT.h"
#include "ADC.h"
#include "USART.h"
#include "string.h"

#define KEY1 GPIO_ReadInBit(HT_GPIOA, GPIO_PIN_10)

// Define key-state enumeration
typedef enum
{
  KS_RELEASE = 0, // key released
  KS_SHAKE,       // key bouncing
  KS_PRESS,       // stable press
} KEY_STATUS;

// State-machine state at end of current cycle
#define g_keyStatus 0
// Current state (synced with g_keyStatus after each cycle)
#define g_nowKeyStatus 1
// Previous state (to distinguish state source)
#define g_lastKeyStatus 2

uint8_t KEY_Status[4][3]; // records key states
uint8_t key[4];           // records stable key press, 1 = pressed, 0 = not pressed
char *temp;
``````c
// Define mode page enum
enum mode_type
{
  menu = 0, // Menu
  CC,       // Constant Current
  CV,       // Constant Voltage
  CR,       // Constant Resistance
  CW        // Constant Power
};

uint8_t Eload_Out = 0;                // Electronic load output on/off state
uint8_t mode = menu;                  // Current mode
uint8_t voltage_dw = 0;               // Voltage sampling range: 0 = 0.0325×, 2 = 0.6175×, 1 = 0.0947×
float YVF, YIF1, YIF2, YIF;           // Present voltage & currents
float ISET, VSET, RSET, PSET;         // Current, voltage, resistance, power setpoints
float VDD = 3.3;                      // MCU supply voltage
uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM; // Summation for averaging voltage/current
uint8_t AVG_count = 0;                // Counter for current averaging
uint8_t YVF_AVG_count = 0;            // Counter for voltage averaging
uint8_t Key_ONOFF = 0;                // State of load on/off key press

void HMI_GetData(void);
void HMI_Display(void);
void AdcFb(void);
void ONOFF(void);
void FAN(void);
void key_status_check(uint8_t key_num, uint8_t KEY);
void CW_mode(void);
void CR_mode(void);
void OFF(void); // Turn off
void ON(void);  // Turn on

int main(void)
{
  GPIO_Configuration();     // Init GPIO
  BFTM0_Configuration();    // Init BFTM0 timer
  GPTM0_Configuration();    // Init GPTM0 timer
  GPTM1_Configuration();    // Init GPTM1 timer
  MCTM0_Configuration();    // Init MCTM0 timer
  WDT_Configuration();      // Init watchdog
  OLED_Init();              // Init OLED
  ADC_Configuration();      // Init ADC
  RETARGET_Configuration(); // Redirect printf to UART
  USART1_Configuration();   // Init USART1
  USART0_Configuration();   // Init USART0

  uint16_t count1 = 0;

  Serial_SendHMILCD("page 0");      // Switch to boot page
  GPTM0_CH0_DisablePWMOutput(1);    // Disable CH0 (CV) PWM, output high
  GPTM0_CH2_DisablePWMOutput(0);    // Disable CH2 (CC1) PWM, output low
  GPTM0_CH3_DisablePWMOutput(0);    // Disable CH3 (CC2) PWM, output low
  MCTM0_CH0_DisablePWMOutput(0);    // Disable MCTM0_CH0 (fan) PWM, output low
  Serial_SendHMILCD("CC.x0.val=0"); // Clear current setpoint on screen
  Serial_SendHMILCD("CV.x0.val=0"); // Clear voltage setpoint on screen
  Serial_SendHMILCD("CR.x0.val=0"); // Clear resistance setpoint on screen
  Serial_SendHMILCD("CW.x0.val=0"); // Clear power setpoint on screen

  GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET); // MCU_G0, voltage range 0.6175×
  GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET); // MCU_G1, voltage range 0.0947×

  while (1)
  {
    if (HT_CKCU->APBCCR1 & (1 << 4)) // Watchdog clock enabled?
      WDT_Restart();                 // Reload watchdog

    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, RESET);

    if (bftm0_ct3 >= 1) // Every 1 ms
    {
      AdcFb(); // Process ADC data
      bftm0_ct3 = 0;
    }

    HMI_GetData();

    if (bftm0_ct2 >= 40) // Every 40 ms
    {
      HMI_Display(); // Update HMI screen
      bftm0_ct2 = 0;
    }
    ONOFF();
    FAN();

    if (bftm0_ct >= 50) // Every 50 ms
    {
      key_status_check(0, KEY1); // Scan keys
      if (mode == CW)
      {
        CW_mode();
      }
      if (mode == CR)
      {
        CR_mode();
      }
      bftm0_ct = 0;
    }

    if (YIF > 10 || YVF * YIF > 100)
    {
      if (Eload_Out == 1)
      {
        Key_ONOFF = 1;
      }
    }

    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, SET);
    /*
        float Voltage0 = (AD_Value[0] & 0x0000FFFF) / 4096.0 * 3.3; // ADC to voltage
        OLED_ShowNum(1, 4, (uint8_t)Voltage0, 1);                   // Integer part
        OLED_ShowString(1, 5, ".");
        OLED_ShowString(1, 9, "V");
        OLED_ShowNum(1, 6, (uint16_t)(Voltage0 * 1000) % 1000, 3); // Fraction

        float Voltage1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * 3.3;
        OLED_ShowNum(2, 4, (uint8_t)Voltage1, 1);
        OLED_ShowString(2, 5, ".");
        OLED_ShowString(2, 9, "V");
        OLED_ShowNum(2, 6, (uint16_t)(Voltage1 * 1000) % 1000, 3);

        float Voltage2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * 3.3;
        OLED_ShowNum(3, 4, (uint8_t)Voltage2, 1);
        OLED_ShowString(3, 5, ".");
        OLED_ShowString(3, 9, "V");
        OLED_ShowNum(3, 6, (uint16_t)(Voltage2 * 1000) % 1000, 3);

        VDD = (AD_Value[3] & 0x0000FFFF) / 4096.0 * 3.3;
        OLED_ShowNum(4, 4, (uint8_t)VDD, 1);
        OLED_ShowString(4, 5, ".");
        OLED_ShowString(4, 9, "V");
        OLED_ShowNum(4, 6, (uint16_t)(VDD * 1000) % 1000, 3);

        VDD = (AD_Value[4] & 0x0000FFFF) / 4096.0 * 3.3;
        OLED_ShowNum(4, 11, (uint8_t)VDD, 1);
        OLED_ShowString(4, 12, ".");
        OLED_ShowString(4, 16, "V");
        OLED_ShowNum(4, 13, (uint16_t)(VDD * 1000) % 1000, 3);
        count1++;*/

    if(flag_start==1) // USART1 data processing
    {
      if(++cishu==7)  {flag_over=1;}
                      Delay_ms(1);
    }
    if(flag_over==1)
    {
            flag_over=0;
         if (Data[0] == 'A') // Numeric keypad page
    {

      char *temp = Data;
      temp++;                      // Skip 'A'
      uint16_t temp2 = atoi(temp); // String to int
//      if (mode == CC)
//      {
//        if (temp2 > 10000)
//          temp2 = 10000;
//        ISET = temp2 / 1000.0;
//        Serial_SendHMILCD("CC.x0.val=%d", temp2);

//        if (ISET <= 2.5)
//        {
//          if (Eload_Out == 1)
//          {
//            GPTM0_CH3_DisablePWMOutput(0);
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
//          }
//          else
//          {
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
//          }
//        }
//        else
//        {
//          if (Eload_Out == 1)
//          {
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//            GPTM0_CH3_EnablePWMOutput();
//          }
//          else
//          {
//            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
//          }
//        }
//      }
//      else if (mode == CV)
//      {
//        VSET = temp2 / 100.0;
//        Serial_SendHMILCD("CV.x0.val=%d", temp2);
//        if (voltage_dw == 0)
//        {
//          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
//        }
//        else if (voltage_dw == 1)
//        {
//          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
//        }
//        else if (voltage_dw == 2)
//        {
//          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
//        }
//      }
//      else if (mode == CR)
//      {
//        RSET = temp2 / 100.0;
//        Serial_SendHMILCD("CR.x0.val=%d", temp2);
//        CR_mode();
//      }
       if (mode == CW)
      {
        PSET = temp2/100.0;
        Serial_SendHMILCD("CW.x0.val=%d", temp2);
        CW_mode();
      }

    }
      switch (Data[0]){
        case '6':
        Serial_SendHMILCD("page menu");          // Switch to menu page
        mode = menu;
        break;
        case '5':
                      Serial_SendHMILCD("page CW"); // Switch to constant-power page
                  mode = CW;                    // Set mode to constant power
        break;
        case '4':
                      Serial_SendHMILCD("page CR"); // Switch to constant-resistance page
                  mode = CR;                    // Set mode to constant resistance
        break;  
        case '3':
                      Serial_SendHMILCD("page CV"); // Switch to constant-voltage page
                  mode = CV;                    // Set mode to constant voltage
        break;  
        case '2':
                      Serial_SendHMILCD("page CC"); // Switch to constant-current page
                  mode = CC;                    // Set mode to constant current
          break;        
        case '1':
          ON(); // Turn on
          break;
        case '0':
          OFF(); // Turn off
          break;
        default:
          break;
      }

        cishu=0;
        len=0;
        flag_start=0;
   }
  }
}
``````c
void ON(void) // Turn on
{
    if (mode == CC) // Constant-current mode
    {
        if (Eload_Out == 0) // When the current load output is off
        {
            Serial_SendHMILCD("CC.t1.txt=\"ON\"");   // Show “ON” in the top-right title box
            Serial_SendHMILCD("CC.b1.txt=\"OFF\"");  // Show “OFF” in the bottom-right button
            Eload_Out = 1;                           // Set load output state to on
            if (ISET <= 2.5)                         // If current set-point ≤ 2.5 A, enable only one MOSFET
            {
                GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
                GPTM0_CH2_EnablePWMOutput();   // Enable PWM on CH2 (IREF1)
                GPTM0_CH3_DisablePWMOutput(0); // Disable PWM on CH3 (IREF2) and output low
            }
            else
            {
                GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
                GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
                GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
                GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3
            }
            GPTM0_CH0_DisablePWMOutput(0); // VREF output low
        }
    }

    else if (mode == CV) // Constant-voltage mode
    {
        if (Eload_Out == 0) // When the current load output is off
        {
            Serial_SendHMILCD("CV.t1.txt=\"ON\"");
            Serial_SendHMILCD("CV.b1.txt=\"OFF\"");
            Eload_Out = 1;
            if (voltage_dw == 0)
            {
                GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
            }
            else if (voltage_dw == 1)
            {
                GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
            }
            else if (voltage_dw == 2)
            {
                GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
            }
            GPTM0_CH0_EnablePWMOutput();
            GPTM0_CH2_DisablePWMOutput(1);
            GPTM0_CH3_DisablePWMOutput(1);
        }
    }

    else if (mode == CR) // Constant-resistance mode
    {
        if (Eload_Out == 0) // When the current load output is off
        {
            Serial_SendHMILCD("CR.t1.txt=\"ON\"");
            Serial_SendHMILCD("CR.b1.txt=\"OFF\"");
            Eload_Out = 1;
            float Rtemp = YVF / RSET;
            if (Rtemp > 10)
                Rtemp = 10;
            GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
            GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3

            GPTM0_CH0_DisablePWMOutput(0); // VREF output low
        }
    }

    else if (mode == CW) // Constant-power mode
    {
        if (Eload_Out == 0) // When the current load output is off
        {
            Serial_SendHMILCD("CW.t1.txt=\"ON\"");
            Serial_SendHMILCD("CW.b1.txt=\"OFF\"");
            Eload_Out = 1;
            float Ptemp = PSET / YVF;
            if (Ptemp > 10)
                Ptemp = 10;
            GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
            GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3

            GPTM0_CH0_DisablePWMOutput(0); // VREF output low
        }
    }
}

void OFF(void) // Turn off
{
    if (mode == CC) // Constant-current mode
    {
        if (Eload_Out == 1) // When the current load output is on
        {
            Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
            Serial_SendHMILCD("CC.b1.txt=\"ON\"");
            Eload_Out = 0;
            GPTM0_CH0_DisablePWMOutput(1); // VREF output high
            GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
            GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
        }
    }

    else if (mode == CV) // Constant-voltage mode
    {
        if (Eload_Out == 1) // When the current load output is on
        {
            Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
            Serial_SendHMILCD("CV.b1.txt=\"ON\"");
            Eload_Out = 0;
            GPTM0_CH0_DisablePWMOutput(1); // VREF output high
            GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
            GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
        }
    }

    else if (mode == CR) // Constant-resistance mode
    {
        if (Eload_Out == 1) // When the current load output is on
        {
            Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
            Serial_SendHMILCD("CR.b1.txt=\"ON\"");
            Eload_Out = 0;
            GPTM0_CH0_DisablePWMOutput(1); // VREF output high
            GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
            GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
        }
    }
    else if (mode == CW) // Constant-power mode
    {
        if (Eload_Out == 1) // When the current load output is on
        {
            Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
            Serial_SendHMILCD("CW.b1.txt=\"ON\"");
            Eload_Out = 0;
            GPTM0_CH0_DisablePWMOutput(1); // VREF output high
            GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
            GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
        }
    }
}

/*Process data sent from the serial screen*/
void HMI_GetData(void)
{
    if (Serial_RxFlag == 1)
    {
        if (Serial_RxPacket[0] == 0x01) // Currently on main-menu page
        {
            if (Serial_RxPacket[1] == 0x10) // Constant-current button pressed
            {
                Serial_SendHMILCD("page CC"); // Switch to CC-mode page
                mode = CC;                    // Set current mode to CC
            }
            else if (Serial_RxPacket[1] == 0x11) // Constant-voltage button pressed
            {
                Serial_SendHMILCD("page CV"); // Switch to CV page
                mode = CV;                    // Set current mode to CV
            }
            else if (Serial_RxPacket[1] == 0x12) // Constant-resistance button pressed
            {
                Serial_SendHMILCD("page CR"); // Switch to CR page
                mode = CR;                    // Set current mode to CR
            }
            else if (Serial_RxPacket[1] == 0x13) // Constant-power button pressed
            {
                Serial_SendHMILCD("page CW"); // Switch to CW page
                mode = CW;                    // Set current mode to CW
            }
        }
        else if (Serial_RxPacket[0] == 0x02) // Currently on CC-mode page
        {
            if (Serial_RxPacket[1] == 0x10) // Menu button pressed
            {
                Serial_SendHMILCD("CC.t1.txt=\"OFF\"");  // Show “OFF” in top-right title box
                Serial_SendHMILCD("CC.b1.txt=\"ON\"");   // Show “ON” in bottom-right button
                Eload_Out = 0;                           // Set load output state to off
                GPTM0_CH0_DisablePWMOutput(1);           // Disable CH0 (CV) PWM output and set high
                GPTM0_CH2_DisablePWMOutput(0);           // Disable CH2 (CC1) PWM output and set low
                GPTM0_CH3_DisablePWMOutput(0);           // Disable CH3 (CC2) PWM output and set low
                Serial_SendHMILCD("page menu");          // Switch to menu page
                mode = menu;
            }
            else if (Serial_RxPacket[1] == 0x11) // ON button pressed while load output is off
            {
                Key_ONOFF = 1;
            }
        }
        else if (Serial_RxPacket[0] == 0x03) // Currently on CV-mode page
        {
            if (Serial_RxPacket[1] == 0x10) // Menu button pressed
            {
                Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
                Serial_SendHMILCD("CV.b1.txt=\"ON\"");
                Eload_Out = 0;
                GPTM0_CH0_DisablePWMOutput(1); // Disable CH0 (CV) PWM output and set high
                GPTM0_CH2_DisablePWMOutput(0); // Disable CH2 (CC1) PWM output and set low
                GPTM0_CH3_DisablePWMOutput(0); // Disable CH3 (CC2) PWM output and set low
                Serial_SendHMILCD("page menu");
                mode = menu;
            }
            if (Serial_RxPacket[1] == 0x11) // ON button pressed while load output is off
            {
                Key_ONOFF = 1;
            }
        }
        else if (Serial_RxPacket[0] == 0x04) // Currently on CR-mode page
        {
            if (Serial_RxPacket[1] == 0x10) // Menu button pressed
            {
                Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
                Serial_SendHMILCD("CR.b1.txt=\"ON\"");
                Eload_Out = 0;
                GPTM0_CH0_DisablePWMOutput(1); // Disable CH0 (CV) PWM output and set high
                GPTM0_CH2_DisablePWMOutput(0); // Disable CH2 (CC1) PWM output and set low
                GPTM0_CH3_DisablePWMOutput(0); // Disable CH3 (CC2) PWM output and set low
                Serial_SendHMILCD("page menu");
                mode = menu;
            }
            if (Serial_RxPacket[1] == 0x11) // ON button pressed while load output is off
            {
                Key_ONOFF = 1;
            }
        }
        else if (Serial_RxPacket[0] == 0x05) // Currently on CW-mode page
        {
            if (Serial_RxPacket[1] == 0x10) // Menu button pressed
            {
                Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
                Serial_SendHMILCD("CW.b1.txt=\"ON\"");
                Eload_Out = 0;
                GPTM0_CH0_DisablePWMOutput(1); // Disable CH0 (CV) PWM output and set high
                GPTM0_CH2_DisablePWMOutput(0); // Disable CH2 (CC1) PWM output and set low
                GPTM0_CH3_DisablePWMOutput(0); // Disable CH3 (CC2) PWM output and set low
                Serial_SendHMILCD("page menu");
                mode = menu;
            }
            if (Serial_RxPacket[1] == 0x11) // ON button pressed while load output is off
            {
                Key_ONOFF = 1;
            }
        }
    }
}
``````c
else if (Serial_RxPacket[0] == 0xAA) // currently on numeric keypad page
    {
      char *temp = Serial_RxPacket;
      temp++;                      // increment address by 1
      uint16_t temp2 = atoi(temp); // convert string to integer
      if (mode == CC)
      {
        if (temp2 > 10000)
          temp2 = 10000;
        ISET = temp2 / 1000.0;
        Serial_SendHMILCD("CC.x0.val=%d", temp2);

        if (ISET <= 2.5)
        {
          if (Eload_Out == 1)
          {
            GPTM0_CH3_DisablePWMOutput(0);
            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          }
          else
          {
            GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          }
        }
        else
        {
          if (Eload_Out == 1)
          {
            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH3_EnablePWMOutput();
          }
          else
          {
            GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
            GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          }
        }
      }
      else if (mode == CV)
      {
        VSET = temp2 / 100.0;
        Serial_SendHMILCD("CV.x0.val=%d", temp2);
        if (voltage_dw == 0)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
        }
        else if (voltage_dw == 1)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
        }
        else if (voltage_dw == 2)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
        }
      }
      else if (mode == CR)
      {
        RSET = temp2 / 100.0;
        Serial_SendHMILCD("CR.x0.val=%d", temp2);
        CR_mode();
      }
      else if (mode == CW)
      {
        PSET = temp2 / 100.0;
        Serial_SendHMILCD("CW.x0.val=%d", temp2);
        CW_mode();
      }
    }
    Serial_RxFlag = 0;
  }
}

/* HMI screen display parameters */
void HMI_Display(void)
{
  if (mode != menu)
  {
    Serial_SendHMILCD("x1.val=%d", (uint16_t)(YVF * 100));       // display voltage
    Serial_SendHMILCD("x2.val=%d", (uint16_t)(YIF * 1000));      // display current
    Serial_SendHMILCD("x3.val=%d", (uint32_t)(YIF * YVF * 100)); // display power
    Serial_SendHMILCD("x4.val=%d", (uint32_t)(YVF / YIF * 100)); // display resistance
  }
}

/* ADC data processing */
void AdcFb(void)
{
  VDD = (AD_Value[4] & 0x0000FFFF) * 1.2482 / (AD_Value[3] & 0x0000FFFF); // convert ADC sample to voltage
                                                                          // VDD = (AD_Value[4] & 0x0000FFFF)  / 4096.0*3.3; // convert ADC sample to voltage
  // VDD = 3.3;

  if (voltage_dw == 0) // voltage sampling range 0.0325x
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0325;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
    if (YVF <= 32) // switch range when voltage below 32V
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
      voltage_dw = 1;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
    }
  }
  else if (voltage_dw == 1) // voltage range 0.0947x
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0947;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;

    if (YVF >= 34)
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
      voltage_dw = 0;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
    }
    else if (YVF <= 5.0) // switch range when voltage below 5V
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, SET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
      voltage_dw = 2;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;
    }
  }
  else if (voltage_dw == 2) // voltage range 0.6175x
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.6175;
      if (YVF < 0.2)
      {
        YVF = 0;
      }
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;

    if (YVF > 5.1) // switch range when voltage above 5.1V
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
      voltage_dw = 1;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
    }
  }

  if (AVG_count < 15)
  {
    YIF1_SUM += (AD_Value[1] & 0x0000FFFF); // accumulate MOSFET 1 current
    YIF2_SUM += (AD_Value[2] & 0x0000FFFF); // accumulate MOSFET 2 current
    AVG_count++;
  }
  if (AVG_count == 15)
  {
    YIF1 = YIF1_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
    YIF2 = YIF2_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
    YIF = YIF1 + YIF2;
    AVG_count = 0;
    YIF1_SUM = 0;
    YIF2_SUM = 0;
  }
  // YIF1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
  // YIF2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
  // YIF = YIF1 + YIF2;
}
```/*Electronic load ON/OFF button*/
void ONOFF(void)
{
  if (Key_ONOFF == 1 | key[0] == 1) // Power-on button pressed
  {
    if (mode == CC) // Constant-current mode
    {
      if (Eload_Out == 0) // Load output currently OFF
      {
        Serial_SendHMILCD("CC.t1.txt=\"ON\"");   // Show ON in top-right title box
        Serial_SendHMILCD("CC.b1.txt=\"关闭\""); // Show “关闭” on bottom-right button
        Eload_Out = 1;                           // Set load output to ON
        if (ISET <= 2.5)                         // Only one MOSFET when current ≤ 2.5 A
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput();   // Enable PWM on CH2 (IREF1)
          GPTM0_CH3_DisablePWMOutput(0); // Disable PWM on CH3 (IREF2), output low
        }
        else
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
          GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3
        }
        GPTM0_CH0_DisablePWMOutput(0); // VREF output low
      }
      else if (Eload_Out == 1) // Load output currently ON
      {
        Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CC.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF output high
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
      }
    }
    else if (mode == CV) // Constant-voltage mode
    {
      if (Eload_Out == 0) // Load output currently OFF
      {
        Serial_SendHMILCD("CV.t1.txt=\"ON\"");
        Serial_SendHMILCD("CV.b1.txt=\"关闭\"");
        Eload_Out = 1;
        if (voltage_dw == 0)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
        }
        else if (voltage_dw == 1)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
        }
        else if (voltage_dw == 2)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
        }
        GPTM0_CH0_EnablePWMOutput();
        GPTM0_CH2_DisablePWMOutput(1);
        GPTM0_CH3_DisablePWMOutput(1);
      }
      else if (Eload_Out == 1) // Load output currently ON
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF output high
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
      }
    }
    else if (mode == CR) // Constant-resistance mode
    {
      if (Eload_Out == 0) // Load output currently OFF
      {
        Serial_SendHMILCD("CR.t1.txt=\"ON\"");
        Serial_SendHMILCD("CR.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Rtemp = YVF / RSET;
        if (Rtemp > 10)
          Rtemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
        GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF output low
      }
      else if (Eload_Out == 1) // Load output currently ON
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF output high
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
      }
    }
    else if (mode == CW) // Constant-power mode
    {
      if (Eload_Out == 0) // Load output currently OFF
      {
        Serial_SendHMILCD("CW.t1.txt=\"ON\"");
        Serial_SendHMILCD("CW.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Ptemp = PSET / YVF;
        if (Ptemp > 10)
          Ptemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
        GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF output low
      }
      else if (Eload_Out == 1) // Load output currently ON
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF output high
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
      }
    }
		
    Key_ONOFF = 0;
    key[0] = 0;
  }
// https://blog.zeruns.com
  if (Eload_Out == 0)
  {
    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, SET);
  }
  else if (Eload_Out == 1)
  {
    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, RESET);
  }
}

/*Cooling-fan control*/
void FAN(void)
{
  uint16_t P = (uint16_t)(YIF * YVF);
  if (P >= 6) // Start fan when power ≥ 6 W
  {
    MCTM0_CH0_EnablePWMOutput(); // Turn on fan
    if (P < 15)
    {
      MCTM0_CH0_SetOnduty(50); // 50 % duty cycle
    }
    else if (P >= 15 && P < 20)
    {
      MCTM0_CH0_SetOnduty(60);
    }
    else if (P >= 20 && P < 25)
    {
      MCTM0_CH0_SetOnduty(70);
    }
    else if (P >= 25 && P < 30)
    {
      MCTM0_CH0_SetOnduty(80);
    }
    else if (P >= 30 && P < 35)
    {
      MCTM0_CH0_SetOnduty(90);
    }
    else if (P >= 35)
    {
      MCTM0_CH0_SetOnduty(100);
    }
  }
  else if (P <= 5)
  {
    MCTM0_CH0_DisablePWMOutput(0); // Disable PWM on MCTM0_CH0 (fan), output low
  }
}

/*Constant-power mode*/
void CW_mode(void)
{
  float Ptemp = PSET / YVF;
  if (Ptemp > 10)
    Ptemp = 10;
  if (Eload_Out == 1)
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
  else
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
}

/*Constant-resistance mode*/
void CR_mode(void)
{
  float Rtemp = YVF / RSET;
  if (Rtemp > 10)
    Rtemp = 10;
  if (Eload_Out == 1)
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
  else
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
}

// Key state-machine routine
void key_status_check(uint8_t key_num, uint8_t KEY)
{
  switch (KEY_Status[key_num][g_keyStatus])
  {
  // Key released (initial state)
  case KS_RELEASE:
  {
    // Detect low level, start debounce
    if (KEY == 0)
    {
      KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
    }
  }
  break;

  // Debounce
  case KS_SHAKE:
  {
    if (KEY == 1)
    {
      KEY_Status[key_num][g_keyStatus] = KS_RELEASE;
    }
    else
    {
      KEY_Status[key_num][g_keyStatus] = KS_PRESS;
    }
  }
  break;

  // Stable short press
  case KS_PRESS:
  {
    // Detect high level, start debounce
    if (KEY == 1)
    {
      KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
    }
  }
  break;

  default:
    break;
  }

  if (KEY_Status[key_num][g_keyStatus] != KEY_Status[key_num][g_nowKeyStatus])
  {
    // Current state is released AND previous state was pressed
    if ((KEY_Status[key_num][g_keyStatus] == KS_RELEASE) && (KEY_Status[key_num][g_lastKeyStatus] == KS_PRESS))
    {
      key[key_num] = 1;
    }
    KEY_Status[key_num][g_lastKeyStatus] = KEY_Status[key_num][g_nowKeyStatus];
    KEY_Status[key_num][g_nowKeyStatus] = KEY_Status[key_num][g_keyStatus];
  }
}

Other open-source projects recommended

Recommended reading- High Cost-Performance and Cheap VPS/Cloud Server Recommendations: https://blog.zeruns.com/archives/383.html