Open-source intelligent electronic load based on CH32V307, embedded contest project open-sourced

Open-source Intelligent Electronic Load Based on CH32V307VCT6, Including Schematics, PCB, Source Code, and Project Report.

2023 Embedded-Chip & System-Design Contest – Application Track, National Second Prize.

Built in half a month by porting the previous Holtek electronic-load firmware to the CH32 MCU, adding RT-Thread, and tidying up the code—rushed job, nothing fancy, no harsh comments please.

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

Holtek HT32F52352 version & “Holtek Cup” files: https://blog.zeruns.com/archives/784.html

This project is shared for study only—replication not recommended; far better electronic-load designs already exist on LCSC Open-Source Platform!

Project page on LCSC: https://url.zeruns.com/Et4x4

MCU/Electronics QQ group: 2169025065

Free CH32 dev-board samples from WCH: https://url.zeruns.com/h9a99

What Is an Electronic Load?

An electronic load simulates real-world loading to test power supplies or circuits. Compared with bulky power resistors or heater wire, it offers adjustable parameters and convenience. Whether for professional development or hobbyists, an electronic load is essential gear.

By input type: AC or DC.
By mode: constant-current, constant-voltage, constant-resistance, constant-power.
Most supplies are DC and constant-voltage, so CC mode is the most common.
Control can be analog or digital; digital (microcontroller-based) gives intuitive adjustment, rich features, easy expansion, and automated testing.

Project Overview

MCU: CH32V307VCT6 (WCH)
Power: one 18650 Li-ion cell, portable.
Control method: MCU DAC generates a reference voltage; op-amp compares it with amplified current/voltage sample and drives the MOSFET for CC/CV regulation.
Touch screen: Taojingchi 2.8" serial TFT, model TJC3224T028_011R.
Heatsink: 2U server cooler for LGA1356/1366, side-blow.
IDE: RT-Thread Studio.
EDA: EasyEDA (LCSC).
Specs: 100 V, 10 A, 200 W max.

Photos

Only a few shots—see the demo video.





Downloads

Package includes: schematics, EasyEDA project, Gerber, source code, HMI project, datasheets.

123 Pan (no speed limit): https://www.123pan.com/ps/2Y9Djv-6NevH.html
Baidu Netdisk: https://pan.baidu.com/s/17YSlBZ6F1M18k7JGa7FlVA?pwd=buxx code: buxx

Part Purchase Links

Buy parts from LCSC商城: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Schematics

Power Board



Supply Board

Control Board

PCBs

Power Board

Top

Bottom

Supply Board

Top

Bottom

Control Board

Top

Bottom

More Open-Source Projects- Open-sourced a three-phase power collector for easy home electricity monitoring: https://blog.zeruns.com/archives/771.html

Main Code

main.c file

/********************************** (C) COPYRIGHT *******************************
 * File Name          : main.c
 * Author             : WCH
 * Version            : V1.0.0
 * Date               : 2021/06/06
 * Description        : Main program body.
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * SPDX-License-Identifier: Apache-2.0
 * https://blog.zeruns.com
 *******************************************************************************/
#include "ch32v30x.h"
#include<rtthread.h>
#include <rtdevice.h>
#include <stdlib.h>
#include <rthw.h>
#include "drivers/pin.h"
#include <board.h>
#include <rtdbg.h>
#include <u8g2_port.h>
#include <qpid.h>

#include "USART.h"
#include "KEY.h"
#include "DAC.h"
#include "PWM.h"

/* Global typedef */

/* Global define */
#define WDT_DEVICE_NAME    "wdt"    /* Watchdog device name */
static rt_device_t wdg_dev; /* Watchdog device handle */
/* ADC reference voltage */
#define VREF 3.3
/* Power supply voltage */
#define VCC 3.3
/* Compensation calibration data */
#define V0_COMP 1.000   // 0.0325× voltage range
#define V1_COMP 1.000   // 0.0947× voltage range
#define V2_COMP 1.000   // 0.6175× voltage range
#define YIF1_COMP 1.00  // MOS 1 current-sampling compensation
#define YIF2_COMP 1.00  // MOS 2 current-sampling compensation
#define DAC1_COMP  1.00    // DAC1(VREF) output compensation factor
#define IREF2_COMP  1.00  // IREF2 output compensation factor
#define DAC2_COMP   1.00   // DAC2(IREF1) output compensation
/* ADC sampling average count */
#define ADC_count 3
/* First-order low-pass filter coefficient */
#define dPower1 0.5
/* Pin numbers, determined by checking drv_gpio.c */
#define OLED_I2C_PIN_SCL    22  //PB6
#define OLED_I2C_PIN_SDA    23  //PB7
#define LED2                59  //PD11
#define LED1                60  //PD12
#define MCU_G0              62  //PD14
#define MCU_G1              63  //PD14

/* Global Variable */
u8g2_t u8g2;                    // u8g2 structure variable
rt_uint16_t AD_Value[4];           // ADC sampled data

// Define mode-page enumeration
enum mode_type
{
    menu = 0, // Menu
    CC,       // Constant Current
    CV,       // Constant Voltage
    CR,       // Constant Resistance
    CW        // Constant Power
};

volatile uint8_t Eload_Out = 0;                 // Electronic load output on/off state
volatile uint8_t mode = menu;                   // Current mode
volatile uint8_t voltage_dw = 0;                // Voltage sampling range: 0=0.0325×, 2=0.6175×, 1=0.0947×
volatile double YVF, YIF1, YIF2, YIF, VBAT;     // Present voltage & current
volatile double ISET, VSET, RSET, PSET;         // Current, voltage, resistance, power set values
volatile uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM, VBAT_SUM;  // Sums for averaging
volatile uint8_t AVG_count = 0;                 // Current average counter
volatile uint8_t YVF_AVG_count = 0;             // Voltage average counter
volatile uint8_t VBAT_count = 0;                      // Battery-voltage average counter
volatile uint8_t Key_ONOFF = 0;                 // ON/OFF key pressed state

static qpid_t qpid_CC;                          // PID control data pointer
static qpid_t qpid_CV;                          // PID control data pointer
static qpid_t qpid_CR;                          // PID control data pointer
static qpid_t qpid_CW;                          // PID control data pointer
static double I_SET, V_SET, R_SET, P_SET;

/* Function declarations */
void OLED_Init(void);
static int IWDG_Init();
static void thread1_sysLED_entry(void *parameter);
static void thread2_OLED_entry(void *parameter);
static void thread3_ADC_entry(void *parameter);
static void thread4_HMI_GetDate_entry(void *parameter);
static void thread5_ONOFF_entry(void *parameter);
static void thread7_HMI_Display_entry(void *parameter);
static void thread8_FAN_entry(void *parameter);
static void thread9_CWCR_entry(void *parameter);
static void thread10_BlueTooth_entry(void *parameter);
void CW_mode(void);
void CR_mode(void);
void key123(void);
void Thread_Init(void);
void SYS_Init(void);
void PID(void);

/*********************************************************************
 * @fn      main
 *
 * @brief   Main program.
 *
 * @return  none
 */
int main(void)
{
    rt_kprintf("MCU: CH32V307\n");
    rt_kprintf("SysClk: %dHz\n", SystemCoreClock);
    SYS_Init();

    while (1)
    {
        // Over-current & over-power protection
        if (YIF > 10 | YVF * YIF > 300)
        {
            if (Eload_Out == 1)
            {
                Key_ONOFF = 1;
            }
        }
        rt_thread_mdelay(20);
    }
}

/* System initialization */
void SYS_Init(void)
{
    IWDG_Init();    // Initialize watchdog
    UART_Init();   // Initialize UART3
    HMILCD_Send("page 0");   // Switch to startup page
    Dac_Init();     // Initialize DAC
    PWM_Init();     // Initialize PWM

    rt_pin_mode(MCU_G0, PIN_MODE_OUTPUT);   // Set IO as output
    rt_pin_mode(MCU_G1, PIN_MODE_OUTPUT);
    rt_pin_write(MCU_G0, PIN_LOW);        // Output low
    rt_pin_write(MCU_G1, PIN_LOW);

    Thread_Init();   // Create threads
}

/* Thread initialization */
void Thread_Init(void)
{
    rt_thread_t tid = NULL; //Define a thread control block pointer
    /* Create thread */
    tid = rt_thread_create("SYS_LED", thread1_sysLED_entry, NULL, 512, 30, 5);
    //Create a thread named SYS_LED, entry function thread1_sysLED_entry, parameter NULL, stack 256 bytes, priority 30, time slice 5 ticks
    if (tid != RT_NULL) // Check if thread created successfully
    {
        if (rt_thread_startup(tid) == RT_EOK) // Start thread
            LOG_D("thread sysLED create success");
    }
    else
    {
        LOG_E("thread1 sysLED create failed...");
    }
    /*
     tid = rt_thread_create("OLED_Display", thread2_OLED_entry, NULL, 2048, 25, 30);
     if (tid != RT_NULL)
     {
     if (rt_thread_startup(tid) == RT_EOK) // Start thread
     LOG_D("thread2 OLED create success");
     }
     else
     {
     LOG_E("thread2_OLED create failed...");
     }*/

    tid = rt_thread_create("ADC", thread3_ADC_entry, NULL, 1536, 18, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Start thread
            LOG_D("thread3 ADC create success");
    }
    else
    {
        LOG_E("thread3 ADC create failed...");
    }

    tid = rt_thread_create("HMI_GetDate", thread4_HMI_GetDate_entry, NULL, 2048, 23, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Start thread
            LOG_D("thread4 HMI_GetDate create success");
    }
    else
    {
        LOG_E("thread4 HMI_GetDate create failed...");
    }

    tid = rt_thread_create("ONOFF", thread5_ONOFF_entry, NULL, 1024, 15, 25);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Start thread
            LOG_D("thread5 ONOFF create success");
    }
    else
    {
        LOG_E("thread5 ONOFF create failed...");
    }
    tid = rt_thread_create("KEY", thread6_KEY_entry, NULL, 512, 20, 20);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Start thread
            LOG_D("thread6 KEY create success");
    }
    else
    {
        LOG_E("thread6 KEY create failed...");
    }

    tid = rt_thread_create("HMI_Display", thread7_HMI_Display_entry, NULL, 1024, 25, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Start thread
            LOG_D("thread7 HMI_Display create success");
    }
    else
    {
        LOG_E("thread7 HMI_Display create failed...");
    }

    tid = rt_thread_create("FAN", thread8_FAN_entry, NULL, 512, 26, 15);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Start thread
            LOG_D("thread8 FAN create success");
    }
    else
    {
        LOG_E("thread8 FAN create failed...");
    }

    tid = rt_thread_create("CWCR", thread9_CWCR_entry, NULL, 512, 19, 15);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Start thread
            LOG_D("thread9 CWCR create success");
    }
    else
    {
        LOG_E("thread9 CWCR create failed...");
    }

    tid = rt_thread_create("BlueTooth", thread10_BlueTooth_entry, NULL, 2048, 23, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // Start thread
            LOG_D("thread10 BlueTooth create success");
    }
    else
    {
        LOG_E("thread10 BlueTooth create failed...");
    }

}/* First-order low-pass filter
 * Return value: iData is the filtered sample value */
double lowV1(double com1)
{
    static double iLastData1;    // Previous value
    double iData1;               // Current calculated value
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData1; // Calculate
    iLastData1 = iData1;                                     // Store current data
    return iData1;                                         // Return data
}
double lowV2(double com1)
{
    static double iLastData2;    // Previous value
    double iData1;               // Current calculated value
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData2; // Calculate
    iLastData2 = iData1;                                     // Store current data
    return iData1;                                         // Return data
}
u16 lowV3(u16 com1)
{
    static u16 iLastData3;    // Previous value
    u16 iData1;               // Current calculated value
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData3; // Calculate
    iLastData3 = iData1;                                     // Store current data
    return iData1;                                         // Return data
}
u16 lowV4(u16 com1)
{
    static u16 iLastData3;    // Previous value
    u16 iData1;               // Current calculated value
    iData1 = (com1 * 0.1) + (1 - 0.1) * iLastData3; // Calculate
    iLastData3 = iData1;                                     // Store current data
    return iData1;                                         // Return data
}

static void idle_hook(void)
{
    /* Feed the watchdog in the idle thread callback */
    rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
    //rt_kprintf("feed the dog!\n ");
}

static int IWDG_Init()
{
    rt_err_t ret = RT_EOK;
    rt_uint32_t timeout = 1; /* Overflow time in seconds */
    /* Find watchdog device by name and get device handle */
    wdg_dev = rt_device_find(WDT_DEVICE_NAME);
    if (!wdg_dev)
    {
        rt_kprintf("find %s failed!\n", WDT_DEVICE_NAME);
        return RT_ERROR;
    }
    /* Initialize device */
    rt_device_init(wdg_dev);
    /* Set watchdog timeout */
    ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
    if (ret != RT_EOK)
    {
        rt_kprintf("set %s timeout failed!\n", WDT_DEVICE_NAME);
        return RT_ERROR;
    }
    /* Start watchdog */
    ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_START, RT_NULL);
    if (ret != RT_EOK)
    {
        rt_kprintf("start %s failed!\n", WDT_DEVICE_NAME);
        return -RT_ERROR;
    }
    /* Set idle thread callback function */
    rt_thread_idle_sethook(idle_hook);

    return ret;
}

/* Thread 1 entry function: system status LED blinking */
static void thread1_sysLED_entry(void *parameter)
{
    /* Configure LED1 pin as output */
    rt_pin_mode(LED1, PIN_MODE_OUTPUT);
    /* Default low level */
    rt_pin_write(LED1, PIN_LOW);
    while (1)
    {
        /* Thread 1 runs at low priority, keeps blinking LED1 */
        rt_pin_write(LED1, !rt_pin_read(LED1));
        rt_thread_mdelay(500);
    }
}

/* Thread 2 entry function: OLED display information */
static void thread2_OLED_entry(void *parameter)
{
    // Initialization
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_rtthread_hw_i2c, u8x8_gpio_and_delay_rtthread);
    u8g2_InitDisplay(&u8g2);
    u8g2_SetPowerSave(&u8g2, 0);

    u8g2_InitDisplay(&u8g2);
    u8g2_SetPowerSave(&u8g2, 0);
    while (1)
    {
        char String[26];
        u8g2_ClearBuffer(&u8g2);
        u8g2_SetFont(&u8g2, u8g2_font_wqy15_t_chinese3); // Set Chinese character set

        float V0 = AD_Value[0] * VREF / 4096.0;
        sprintf(String, "AD0:%d V:%d.%d%d%d", AD_Value[0], (uint8_t) V0, (uint16_t)(V0 * 10.0) % 10,
                (uint16_t)(V0 * 100.0) % 100 % 10, (uint16_t)(V0 * 1000.0) % 1000 % 100 % 10); // Format string output to string variable
        u8g2_DrawStr(&u8g2, 0, 15, String);

        float V1 = AD_Value[1] * VREF / 4096.0;
        sprintf(String, "AD1:%d V:%d.%d%d%d", AD_Value[1], (uint8_t) V1, (uint16_t)(V1 * 10.0) % 10,
                (uint16_t)(V1 * 100.0) % 100 % 10, (uint16_t)(V1 * 1000.0) % 1000 % 100 % 10); // Format string output to string variable
        u8g2_DrawStr(&u8g2, 0, 31, String);

        float V2 = AD_Value[2] * VREF / 4096.0;
        sprintf(String, "AD2:%d V:%d.%d%d%d", AD_Value[2], (uint8_t) V2, (uint16_t)(V2 * 10.0) % 10,
                (uint16_t)(V2 * 100.0) % 100 % 10, (uint16_t)(V2 * 1000.0) % 1000 % 100 % 10); // Format string output to string variable
        u8g2_DrawStr(&u8g2, 0, 47, String);

        float V3 = AD_Value[3] * VREF / 4096.0;
        sprintf(String, "AD3:%d V:%d.%d%d%d", AD_Value[3], (uint8_t) V3, (uint16_t)(V3 * 10.0) % 10,
                (uint16_t)(V3 * 100.0) % 100 % 10, (uint16_t)(V3 * 1000.0) % 1000 % 100 % 10); // Format string output to string variable
        u8g2_DrawStr(&u8g2, 0, 63, String);
        u8g2_SendBuffer(&u8g2); // Send buffer data
        rt_thread_mdelay(100);  // Delay 100 milliseconds
    }
}

/* Thread 3 entry function: ADC data processing */
static void thread3_ADC_entry(void *parameter)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // Enable GPIOA and ADC clocks
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);      // Enable DMA1 clock

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);       // ADC clock division configuration, 6 division (72MHz/6=12MHz), ADC clock must not exceed 14MHz

    GPIO_InitTypeDef GPIO_InitStructure = { 0 };                // Define structure to configure GPIO
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;    // Set GPIO pins
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // GPIO mode is analog input
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // Configure ADC regular group, write channel 0 to sequence 1 of regular group, sampling time 55.5 cycles
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); // Configure ADC regular group, write channel 1 to sequence 2 of regular group, sampling time 55.5 cycles
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); // Configure ADC regular group, write channel 2 to sequence 3 of regular group, sampling time 55.5 cycles
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_239Cycles5); // Configure ADC regular group, write channel 3 to sequence 4 of regular group, sampling time 55.5 cycles

    ADC_InitTypeDef ADC_InitStructure = { 0 };
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  // Configure ADC as independent mode
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;        // Enable scan mode in multi-channel mode
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // Enable continuous conversion mode
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // Set conversion not triggered externally, software trigger
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // Set ADC data right-aligned
    ADC_InitStructure.ADC_NbrOfChannel = 4;                // Number of ADC channels for regular conversion
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_Cmd(ADC1, ENABLE);      // Enable ADC1

    ADC_ResetCalibration(ADC1); // Reset ADC1 calibration registers.

    while (ADC_GetResetCalibrationStatus(ADC1))
        ; // Wait for reset calibration to complete

    ADC_StartCalibration(ADC1); // Start ADC calibration

    while (ADC_GetCalibrationStatus(ADC1))
        ;      // Wait for calibration to complete

    DMA_DeInit(DMA1_Channel1); // Reset DMA controller
    DMA_InitTypeDef DMA_InitStructure;                      // Define structure to configure DMA
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) &ADC1->RDATAR; // Configure peripheral address as ADC data register address
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32) AD_Value;          // Configure memory address as ADC value read address
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;              // Configure data source as peripheral, DMA transfer mode is peripheral to memory
    DMA_InitStructure.DMA_BufferSize = 4;                           // Set DMA data buffer size
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                           // Disable DMA peripheral increment mode
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;         // Enable DMA memory increment mode
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // Set peripheral data size as half-word (2 bytes)
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // Set memory data size as half-word (2 bytes)
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;     // Set DMA mode as circular transfer
    DMA_InitStructure.DMA_Priority = DMA_Priority_High; // Set DMA transfer channel priority as high, priority setting has no effect when using one DMA channel
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;        // Since DMA transfer mode is peripheral to memory, disable memory-to-memory transfer
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);        // Initialize DMA1 channel 1, ADC1 hardware trigger is connected to DMA1 channel 1, so DMA1 channel 1 must be used

    DMA_Cmd(DMA1_Channel1, ENABLE); // Start DMA1 channel 1
    ADC_DMACmd(ADC1, ENABLE);       // Enable ADC DMA request
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);       // Since external trigger is not used, start ADC conversion with software trigger

    qpid_init(&qpid_CC);            // Initialize PID control data
    qpid_set_lmt(&qpid_CC, 0, 10);  // Set PID limits
    qpid_set_ratio(&qpid_CC, 1, 0.001, 0.1);    // Set control ratio coefficients

    qpid_init(&qpid_CV);                // Initialize PID control data
    qpid_set_lmt(&qpid_CV, 31.0, 100);  // Set PID limits
    qpid_set_ratio(&qpid_CV, 0.35, 0.005, 0.001);    // Set control ratio coefficientsqpid_init(&qpid_CW);                // Initialize PID control data
    qpid_set_lmt(&qpid_CW, 0.1, 200);  // Set PID limits
    qpid_set_ratio(&qpid_CW, 0.5, 0.003, 0.001);    // Set control ratio coefficients

    qpid_init(&qpid_CR);                // Initialize PID control data
    qpid_set_lmt(&qpid_CR, 0.1, 1000);  // Set PID limits
    qpid_set_ratio(&qpid_CR, 0.35, 0.003, 0.001);    // Set control ratio coefficients

    while (1)
    {
        /*rt_kprintf("AD0:%d V:%f\n", AD_Value[0], AD_Value[0] / 4095.0 * VREF);
         rt_kprintf("AD1:%d V:%4.3f\n", AD_Value[1], (float) AD_Value[1] / 4095.0 * VREF);
         rt_kprintf("AD2:%d V:%4.3f\n", AD_Value[2], AD_Value[2] / 4095.0 * VREF);*/

        if (voltage_dw == 0) // When voltage sampling range is 0.0325x
        {
            if (YVF_AVG_count < ADC_count) // Accumulate samples when less than 15
            {
                YVF_SUM += AD_Value[0];
                YVF_AVG_count++;
            }
            if (YVF_AVG_count == ADC_count)
            {
                YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.0325 * V0_COMP;    // Calculate voltage value
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }
            if (YVF <= 31.0) // Switch range when voltage is less than 31V
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_HIGH); // Voltage sampling range 0.0947x
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Set DAC1 output value for constant voltage control
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // Set PID limits
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // Set control ratio coefficients
            }
        }
        else if (voltage_dw == 1) // When voltage range is 0.0947x
        {
            if (YVF_AVG_count < ADC_count)
            {
                YVF_SUM += AD_Value[0];
                YVF_AVG_count++;
            }
            if (YVF_AVG_count == ADC_count)
            {
                YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.0947 * V1_COMP;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }

            if (YVF >= 33.5)  // Switch to 0.0325x range when voltage is greater than 33.5V
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_LOW);
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Set DAC1 output value for constant voltage control
                }
                voltage_dw = 0;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 31.0, 100); // Set PID limits
                qpid_set_ratio(&qpid_CV, 0.5, 0.005, 0.0005);    // Set control ratio coefficients
            }
            else if (YVF <= 4.6) // Switch range when voltage is less than 4.6V
            {
                rt_pin_write(MCU_G0, PIN_HIGH);
                rt_pin_write(MCU_G1, PIN_LOW);
                if (Eload_Out == 1)
                {
                    uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
                    if (vset_pwm > 4065)
                        vset_pwm = 4095;
                    DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // Set DAC1 output value for constant voltage control
                }
                voltage_dw = 2;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 0.01, 5.1); // Set PID limits
                qpid_set_ratio(&qpid_CV, 0.26, 0.0025, 0.0005);    // Set control ratio coefficients
            }
        }
        else if (voltage_dw == 2) // When voltage range is 0.6175x
        {
            if (YVF_AVG_count < ADC_count)
            {
                YVF_SUM += AD_Value[0];
                YVF_AVG_count++;
            }
            if (YVF_AVG_count == ADC_count)
            {
                YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.6175 * V2_COMP;
                if (YVF < 0.15)
                    YVF = 0;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }

            if (YVF >= 5.1) // Switch range when voltage is greater than 5.1V
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_HIGH);
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Set DAC1 output value for constant voltage control
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // Set PID limits
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // Set control ratio coefficients
            }
        }

        if (AVG_count < ADC_count)
        {
            YIF1_SUM += AD_Value[1]; // Accumulate MOSFET 1 current
            YIF2_SUM += AD_Value[2]; // Accumulate MOSFET 2 current
            AVG_count++;
        }
        if (AVG_count == ADC_count)
        {
            YIF1 = YIF1_SUM / AVG_count * VREF / 4096.0 / 50 / 0.01 * YIF1_COMP - 0.007;
            YIF2 = YIF2_SUM / AVG_count * VREF / 4096.0 / 50 / 0.01 * YIF2_COMP - 0.007;
            YIF = YIF1 + YIF2;
            if (YIF < 0.008)
                YIF = 0;
            AVG_count = 0;
            YIF1_SUM = 0;
            YIF2_SUM = 0;
            PID();
        }

        if (VBAT_count < 5)
        {
            VBAT_SUM += lowV4(AD_Value[3]); // Accumulate battery voltage samples
            VBAT_count++;
        }
        if (VBAT_count == 5)
        {
            VBAT = VBAT_SUM / VBAT_count * VREF / 4096.0 / 0.3535;
            VBAT_count = 0;
            VBAT_SUM = 0;
        }
        rt_thread_mdelay(5);
    }
}

// https://blog.zeruns.com/* Entry function of thread 4, processes data sent from the serial screen */
static void thread4_HMI_GetDate_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // Clear the current setpoint displayed on the screen
    HMILCD_Send("CV.x0.val=0"); // Clear the voltage setpoint displayed on the screen
    HMILCD_Send("CR.x0.val=0"); // Clear the resistance setpoint displayed on the screen
    HMILCD_Send("CW.x0.val=0"); // Clear the power setpoint displayed on the screen
    while (1)
    {
        if (Serial3_RxFlag == 1)
        {
            if (Serial3_RxPacket[0] == 0x01) // Currently on the main menu page
            {
                if (Serial3_RxPacket[1] == 0x10) // Constant current button pressed
                {
                    HMILCD_Send("page CC"); // Switch to constant current mode page
                    mode = CC;                    // Set current mode to constant current
                }
                else if (Serial3_RxPacket[1] == 0x11) // Constant voltage button pressed
                {
                    HMILCD_Send("page CV"); // Switch to constant voltage page
                    mode = CV;                    // Set current mode to constant voltage
                }
                else if (Serial3_RxPacket[1] == 0x12) // Constant resistance button pressed
                {
                    HMILCD_Send("page CR"); // Switch to constant resistance page
                    mode = CR;                    // Set current mode to constant resistance
                }
                else if (Serial3_RxPacket[1] == 0x13) // Constant power button pressed
                {
                    HMILCD_Send("page CW"); // Switch to constant power page
                    mode = CW;                    // Set current mode to constant power
                }
            }
            else if (Serial3_RxPacket[0] == 0x02) // Currently on the constant current mode page
            {
                if (Serial3_RxPacket[1] == 0x10) // Menu button pressed
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // Display OFF in the top-right title box
                    HMILCD_Send("CC.b1.txt=\"开启\""); // Display "开启" in the bottom-right button
                    Eload_Out = 0;                           // Set load output state to off
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");          // Switch to menu page
                    mode = menu;
                }
                else if (Serial3_RxPacket[1] == 0x11) // ON button pressed and load output is off
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x03) // Currently on the constant voltage mode page
            {
                if (Serial3_RxPacket[1] == 0x10) // Menu button pressed
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // ON button pressed and load output is off
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x04) // Currently on the constant resistance mode page
            {
                if (Serial3_RxPacket[1] == 0x10) // Menu button pressed
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // ON button pressed and load output is off
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x05) // Currently on the constant power mode page
            {
                if (Serial3_RxPacket[1] == 0x10) // Menu button pressed
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // ON button pressed and load output is off
                {
                    Key_ONOFF = 1;
                }
            }

            else if (Serial3_RxPacket[0] == 0xAA) // Currently on the numeric keypad page
            {
                char *temp = Serial3_RxPacket;
                temp++;                      // Address increment by 1
                uint16_t temp2 = atoi(temp); // String to integer
                if (mode == CC)
                {
                    if (temp2 > 1000)
                        temp2 = 1000;
                    ISET = temp2 / 100.0;
                    HMILCD_Send("CC.x0.val=%d", temp2);

                    if (Eload_Out == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                        if (ISET <= 2.5)
                        {
                            // Set DAC2 output to control CC, +0.5 for rounding
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4(0);  // Set IREF2
                        }
                        else
                        {
                            // Set DAC2 output to control CC, +0.5 for rounding
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Set IREF2
                        }

                    }

                }
                else if (mode == CV)
                {
                    VSET = temp2 / 100.0;
                    HMILCD_Send("CV.x0.val=%d", temp2);
                    if (Eload_Out == 1)
                    {
                        DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Set IREF1
                        PWM_SetCCR4(50000);  // Set IREF2
                        if (voltage_dw == 0)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Set DAC1 output to control CV
                        }
                        else if (voltage_dw == 1)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Set DAC1 output to control CV
                        }
                        else if (voltage_dw == 2)
                        {
                            uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
                            if (vset_pwm > 4065)
                                vset_pwm = 4095;
                            DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // Set DAC1 output to control CV
                        }
                    }
                }
                else if (mode == CR)
                {
                    RSET = temp2 / 100.0;
                    HMILCD_Send("CR.x0.val=%d", temp2);
                    //CR_mode();
                }
                else if (mode == CW)
                {
                    PSET = temp2 / 100.0;
                    HMILCD_Send("CW.x0.val=%d", temp2);
                    //CW_mode();
                }
            }
            Serial3_RxFlag = 0;
        }
        key123();
        rt_thread_mdelay(35);
    }

}/*Constant Power Mode*/
void CW_mode(void)
{
    double Ptemp = PSET / YVF;
    if (Ptemp > 10)
        Ptemp = 10;
    if (Eload_Out == 1)
    {
        // Set DAC2 output value to control constant current, +0.5 for rounding
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Ptemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Set IREF2
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable constant voltage
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable constant current
        PWM_SetCCR4(0);  // Set IREF2
    }
}

/*Constant Resistance Mode*/
void CR_mode(void)
{
    double Rtemp = YVF / RSET;
    if (Rtemp > 10)
        Rtemp = 10;
    if (Eload_Out == 1)
    {
        // Set DAC2 output value to control constant current, +0.5 for rounding
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Rtemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Set IREF2
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable constant voltage
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable constant current
        PWM_SetCCR4(0);  // Set IREF2
    }
}

/* PID Control  */
void PID(void)
{
    double Ptemp, Rtemp;

    if (mode == CC && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CC, ISET);   // Set PID target value
        I_SET = qpid_cal_pos(&qpid_CC, YIF);    // PID calculation
        if (ISET <= 2.5)
        {
            // Set DAC2 output value to control constant current, +0.5 for rounding
            DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        }
        else
        {
            // Set DAC2 output value to control constant current, +0.5 for rounding
            DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
            PWM_SetCCR4((uint16_t)(I_SET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Set IREF2
        }
    }
    if (mode == CV && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CV, VSET);           // Set PID target value
        V_SET = qpid_cal_pos(&qpid_CV, YVF);    // PID calculation
        if (voltage_dw == 0)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
            // Set DAC1 output value to control constant voltage
        }
        else if (voltage_dw == 1)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
            // Set DAC1 output value to control constant voltage
        }
        else if (voltage_dw == 2)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
            // Set DAC1 output value to control constant voltage
        }
    }
    if (mode == CW && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CW, PSET);   // Set PID target value
        P_SET = qpid_cal_pos(&qpid_CW, YIF * YVF);    // PID calculation
        Ptemp = P_SET / YVF;
        if (Ptemp > 10)
            Ptemp = 10;
        // Set DAC2 output value to control constant current, +0.5 for rounding
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Ptemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Set IREF2
    }
    if (mode == CR && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CR, RSET);   // Set PID target value
        R_SET = qpid_cal_pos(&qpid_CR, YVF / YIF);    // PID calculation
        Rtemp = YVF / R_SET;
        if (Rtemp > 10)
            Rtemp = 10;
        // Set DAC2 output value to control constant current, +0.5 for rounding
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Rtemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Set IREF2
    }

}

/* Key Handler Function */
void key123(void)
{
    if (key[2] == 1)    // Key 2, switch key
    {
        if (mode == menu)
        {
            HMILCD_Send("page CC"); // Switch to constant current mode page
            mode = CC;              // Set current mode to constant current
        }
        else if (mode == CC)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CC.t1.txt=\"OFF\"");
                HMILCD_Send("CC.b1.txt=\"ON\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable constant voltage
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable constant current
                PWM_SetCCR4(0);  // Set IREF2
            }
            HMILCD_Send("page CV"); // Switch to constant voltage mode page
            mode = CV;              // Set current mode to constant voltage
        }
        else if (mode == CV)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CV.t1.txt=\"OFF\"");
                HMILCD_Send("CV.b1.txt=\"ON\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable constant voltage
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable constant current
                PWM_SetCCR4(0);  // Set IREF2
            }
            HMILCD_Send("page CR"); // Switch to constant resistance mode page
            mode = CR;              // Set current mode to constant resistance
        }
        else if (mode == CR)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CR.t1.txt=\"OFF\"");
                HMILCD_Send("CR.b1.txt=\"ON\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable constant voltage
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable constant current
                PWM_SetCCR4(0);  // Set IREF2
            }
            HMILCD_Send("page CW"); // Switch to constant power mode page
            mode = CW;              // Set current mode to constant power
        }
        else if (mode == CW)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CW.t1.txt=\"OFF\"");
                HMILCD_Send("CW.b1.txt=\"ON\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable constant voltage
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable constant current
                PWM_SetCCR4(0);  // Set IREF2
            }
            HMILCD_Send("page menu"); // Switch to menu page
            mode = menu;              // Set current mode to menu
        }
        key[2] = 0;
    }
    if (key[3] == 1)    // Key 3, menu key
    {
        if (Eload_Out == 1)
            Key_ONOFF = 1;
        rt_thread_mdelay(15);
        HMILCD_Send("page menu"); // Switch to menu page
        mode = menu;              // Set current mode to menu
        key[3] = 0;
    }
}/* Thread 5 entry function, electronic load ON/OFF key handling */
static void thread5_ONOFF_entry(void *parameter)
{
    /* LED2 pin set to output mode */
    rt_pin_mode(LED2, PIN_MODE_OUTPUT);
    /* Default high level */
    rt_pin_write(LED2, PIN_HIGH);
    while (1)
    {
        if (Key_ONOFF == 1 | key[1] == 1) // ON button pressed
        {
            if (mode == CC) // Constant current mode
            {
                if (Eload_Out == 0) // When load output is currently off
                {
                    HMILCD_Send("CC.t1.txt=\"ON\"");   // Display ON in top-right title box
                    HMILCD_Send("CC.b1.txt=\"关闭\"");    // Display “关闭” in bottom-right button
                    Eload_Out = 1;                           // Set load output state to on
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    if (ISET <= 2.5)
                    {
                        // Set DAC2 output to control constant current, +0.5 for rounding
                        DAC_SetChannel2Data(DAC_Align_12b_R,
                                (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                        PWM_SetCCR4(0);  // Set IREF2
                    }
                    else
                    {
                        // Set DAC2 output to control constant current, +0.5 for rounding
                        DAC_SetChannel2Data(DAC_Align_12b_R,
                                (uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                        PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // Set IREF2
                    }
                }
                else if (Eload_Out == 1) // When load output is currently on
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");
                    HMILCD_Send("CC.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high, disable constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low, disable constant current
                    PWM_SetCCR4(0);  // Set IREF2
                }
            }
            else if (mode == CV) // Constant voltage mode
            {
                if (Eload_Out == 0) // When load output is currently off
                {
                    HMILCD_Send("CV.t1.txt=\"ON\"");
                    HMILCD_Send("CV.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Set IREF1
                    PWM_SetCCR4(50000);  // Set IREF2
                    if (voltage_dw == 0)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Set DAC1 output to control constant voltage
                    }
                    else if (voltage_dw == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Set DAC1 output to control constant voltage
                    }
                    else if (voltage_dw == 2)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Set DAC1 output to control constant voltage
                    }
                }
                else if (Eload_Out == 1) // When load output is currently on
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high, disable constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low, disable constant current
                    PWM_SetCCR4(0);  // Set IREF2
                }
            }
            else if (mode == CR) // Constant resistance mode
            {
                if (Eload_Out == 0) // When load output is currently off
                {
                    HMILCD_Send("CR.t1.txt=\"ON\"");
                    HMILCD_Send("CR.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    CR_mode();
                }
                else if (Eload_Out == 1) // When load output is currently on
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high, disable constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low, disable constant current
                    PWM_SetCCR4(0);  // Set IREF2
                }
            }
            else if (mode == CW) // Constant power mode
            {
                if (Eload_Out == 0) // When load output is currently off
                {
                    HMILCD_Send("CW.t1.txt=\"ON\"");
                    HMILCD_Send("CW.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    CW_mode();
                }
                else if (Eload_Out == 1) // When load output is currently on
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high, disable constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low, disable constant current
                    PWM_SetCCR4(0);  // Set IREF2
                }
            }
            Key_ONOFF = 0;
            key[1] = 0;
        }

        if (Eload_Out == 0)
        {
            rt_pin_write(LED2, SET);
        }
        else if (Eload_Out == 1)
        {
            rt_pin_write(LED2, RESET);
        }
        rt_thread_mdelay(35);
    }

}

/* Thread 7 entry function, serial screen parameter display */
static void thread7_HMI_Display_entry(void *parameter)
{
    while (1)
    {
        if (mode != menu)
        {
            double V = lowV1(YVF);
            double I = lowV2(YIF);
            HMILCD_Send("x1.val=%d", (uint16_t)(V * 100));       // Display voltage
            HMILCD_Send("x2.val=%d", (uint16_t)(I * 1000));      // Display current
            HMILCD_Send("x3.val=%d", (uint32_t)(I * V * 100)); // Display power
            HMILCD_Send("x4.val=%d", (uint32_t)(V / I * 100)); // Display resistance
            HMILCD_Send("x5.val=%d", (uint32_t)(VBAT * 100));     // Display resistance
        }
        rt_thread_mdelay(50);
    }
}

/* Thread 8 entry function, cooling fan control */
static void thread8_FAN_entry(void *parameter)
{
    while (1)
    {
        uint16_t P = (uint16_t)(YIF * YVF + 0.5);
        if (P >= 13) // Start fan when power exceeds 13 W
        {
            FAN_PWM_ON();
            if (P < 20)
            {
                FAN_PWM_SetCCR(20); // Fan duty cycle 20 %
            }
            else if (P >= 20 && P < 25)
            {
                FAN_PWM_SetCCR(30);
            }
            else if (P >= 25 && P < 30)
            {
                FAN_PWM_SetCCR(40);
            }
            else if (P >= 30 && P < 35)
            {
                FAN_PWM_SetCCR(50);
            }
            else if (P >= 35 && P < 40)
            {
                FAN_PWM_SetCCR(60);
            }
            else if (P >= 40 && P < 45)
            {
                FAN_PWM_SetCCR(70);
            }
            else if (P >= 45 && P < 50)
            {
                FAN_PWM_SetCCR(80);
            }
            else if (P >= 50 && P < 60)
            {
                FAN_PWM_SetCCR(90);
            }
            else if (P >= 60)
            {
                FAN_PWM_SetCCR(100);
            }
        }
        else if (P <= 8)
        {
            FAN_PWM_SetCCR(0); // Turn off fan PWM and output low
            FAN_PWM_OFF();
        }
        rt_thread_mdelay(200);
    }
}

/* Thread 9 entry function, constant power and constant resistance mode control */
static void thread9_CWCR_entry(void *parameter)
{
    while (1)
    {
        if (mode == CW)
        {
            CW_mode();
        }
        if (mode == CR)
        {
            CR_mode();
        }
        rt_thread_mdelay(50);
    }
}/* Thread 10 entry function, processes data received via Bluetooth */
static void thread10_BlueTooth_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // Clear current setpoint displayed on screen
    HMILCD_Send("CV.x0.val=0"); // Clear voltage setpoint displayed on screen
    HMILCD_Send("CR.x0.val=0"); // Clear resistance setpoint displayed on screen
    HMILCD_Send("CW.x0.val=0"); // Clear power setpoint displayed on screen
    while (1)
    {
        if (Serial5_RxFlag == 1)
        {
            if (Serial5_RxPacket[0] == 0x01) // Currently on main menu page
            {
                if (Serial5_RxPacket[1] == 0x10) // Constant-current button pressed
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"开启\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"开启\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CC"); // Switch to constant-current mode page
                    mode = CC;                    // Set current mode to constant-current
                }
                else if (Serial5_RxPacket[1] == 0x11) // Constant-voltage button pressed
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"开启\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"开启\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CV"); // Switch to constant-voltage page
                    mode = CV;                    // Set current mode to constant-voltage
                }
                else if (Serial5_RxPacket[1] == 0x12) // Constant-resistance button pressed
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"开启\"");

                    }
                    else if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"开启\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CR"); // Switch to constant-resistance page
                    mode = CR;                    // Set current mode to constant-resistance
                }
                else if (Serial5_RxPacket[1] == 0x13) // Constant-power button pressed
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"开启\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"开启\"");

                    }
                    else if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CW"); // Switch to constant-power page
                    mode = CW;                    // Set current mode to constant-power
                }
            }
            else if (Serial5_RxPacket[0] == 0x08)    // Return to menu
            {
                if (mode == CC)
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // Display OFF in top-right title box
                    HMILCD_Send("CC.b1.txt=\"开启\""); // Display "开启" in bottom-right button
                    Eload_Out = 0;                           // Set load output state to off
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");          // Switch to menu page
                    mode = menu;
                }
                else if (mode == CV)
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CR)
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CW)
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, disable CV
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, disable CC
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
            }
            else if (Serial5_RxPacket[0] == 0x09)    // ON/OFF button
            {
                Key_ONOFF = 1;
            }

            else if (Serial5_RxPacket[0] == 0xAA) // Currently on numeric keypad page
            {
                char *temp = Serial5_RxPacket;
                temp++;                      // Address increment by 1
                uint16_t temp2 = atoi(temp); // String to integer
                if (mode == CC)
                {
                    if (temp2 > 1000)
                        temp2 = 1000;
                    ISET = temp2 / 100.0;
                    HMILCD_Send("CC.x0.val=%d", temp2);if (Eload_Out == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                        if (ISET <= 2.5)
                        {
                            // Set DAC2 output value to control constant current; +0.5 for rounding
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4(0);  // Set IREF2
                        }
                        else
                        {
                            // Set DAC2 output value to control constant current; +0.5 for rounding
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Set IREF2
                        }

                    }

                }
                else if (mode == CV)
                {
                    VSET = temp2 / 100.0;
                    HMILCD_Send("CV.x0.val=%d", temp2);
                    if (Eload_Out == 1)
                    {
                        DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Set IREF1
                        PWM_SetCCR4(50000);  // Set IREF2
                        if (voltage_dw == 0)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Set DAC1 output value to control constant voltage
                        }
                        else if (voltage_dw == 1)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Set DAC1 output value to control constant voltage
                        }
                        else if (voltage_dw == 2)
                        {
                            uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
                            if (vset_pwm > 4065)
                                vset_pwm = 4095;
                            DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // Set DAC1 output value to control constant voltage
                        }
                    }
                }
                else if (mode == CR)
                {
                    RSET = temp2 / 100.0;
                    HMILCD_Send("CR.x0.val=%d", temp2);
                    CR_mode();
                }
                else if (mode == CW)
                {
                    PSET = temp2 / 100.0;
                    HMILCD_Send("CW.x0.val=%d", temp2);
                    CW_mode();
                }
            }
            Serial5_RxFlag = 0;
        }
        rt_thread_mdelay(40);
    }

}

Recommended Reading