Carga electrónica inteligente basada en CH32V307 de código abierto, proyecto del Concurso de Sistemas Empotrados liberado

Open Source Intelligent Electronic Load Based on CH32V307VCT6, Embedded Competition Work Open Source, Including Schematics, PCB, Program Source Code, and Project Report.

2023 Embedded Chip and System Design Competition Application Track, Second Prize Work.

Modified in half a month based on the Holtek version electronic load done previously, ported the program to the CH32 microcontroller, and used the RT-Thread system, optimized the code, rushed to complete it, done very generally, no criticism please.

Project demonstration video: https://www.bilibili.com/video/BV1Zu4y1m7Zd/

Open source intelligent electronic load based on HT32F52352, Holtek Cup work open source: https://blog.zeruns.com/archives/784.html

This open source work is for reference and learning only, reproduction is not recommended. There are more, better, and more complete electronic load open source works on the LiChuang Open Source Platform!

Open source link of this project on LiChuang Open Source Platform: https://url.zeruns.com/Et4x4

Electronics/Microcontroller Technology Exchange Group: 2169025065

Qinheng official website can apply for development board samples for free: https://url.zeruns.com/h9a99

What is an Electronic Load

An electronic load is an electronic device used to simulate real load environments to test the performance of power supplies or electronic circuits. Compared to traditional passive loads such as high-power adjustable resistors or heating wires, electronic loads have many advantages such as adjustable parameters and convenient use. Whether for professional electronic engineering project development or amateur electronics enthusiasts, electronic load meters are one of the essential equipment.

Electronic loads can be divided into AC electronic loads and DC electronic loads based on the type of power supply being tested. From a functional perspective, common types include constant current, constant voltage, constant resistance, and constant power. Since most power supplies we commonly see are constant voltage DC power supplies, when testing this type of power supply, the main test is its current output capability. Therefore, in most application scenarios, DC constant current electronic loads are the most common type. Electronic loads can also be divided into digital control and analog types based on control methods. Compared to electronic loads using pure analog circuit control, digital control electronic loads use digital control, making parameter adjustment more intuitive, with rich functions and simple expansion, and can conveniently achieve test automation.

Project Introduction

This work uses the CH32V307VCT6 Qinheng microcontroller as the main control chip to design an electronic load, powered by 18650 lithium batteries for easy portability.

The control method is through the microcontroller DAC output a DC voltage as a reference voltage to the operational amplifier to compare with the amplified voltage of current/voltage sampling, the operational amplifier output controls the MOS tube, thereby achieving constant voltage/constant current.

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

The heat sink is a 2U server 1356/1366 pin heat sink, side-blowing type.

The project program is developed using RT-Thread Studio. Circuit design uses LiChuang EDA software.

Maximum input voltage and current are 100V/10A, maximum power 200W.

Physical Photos

I didn’t take many photos at the time, only found these few, you can check out the demonstration video.

Data Download Address

The links below contain: circuit schematics, LiChuang EDA project files, PCB manufacturing files, program source code, serial screen project files, and chip manuals.

123 Cloud Drive unlimited speed download address: https://www.123pan.com/ps/2Y9Djv-6NevH.html

Baidu Netdisk download address: https://pan.baidu.com/s/17YSlBZ6F1M18k7JGa7FlVA?pwd=buxx Extraction code: buxx

Component Purchase Address

It is recommended to purchase components in LiChuang Mall: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Schematics

Power Board



Power Supply Board

Control Board

PCB

Power Board

Top Layer

Bottom Layer

Power Supply Board

Top Layer

Bottom Layer

Control Board

Top Layer

Bottom Layer

Other Recommended Open Source Projects- Creé un colector de energía trifásica de código abierto para monitorear fácilmente el consumo de electricidad del hogar: https://blog.zeruns.com/archives/771.html

Código principal

Archivo main.c

/********************************** (C) COPYRIGHT *******************************
 * File Name          : main.c
 * Author             : WCH
 * Version            : V1.0.0
 * Date               : 2021/06/06
 * Description        : Cuerpo del programa principal.
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * SPDX-License-Identifier: Apache-2.0
 * https://blog.zeruns.com
 *******************************************************************************/
#include "ch32v30x.h"
#include
``````c
#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"

/* Definición de tipos globales */

/* Definición global */
#define WDT_DEVICE_NAME    "wdt"    /* Nombre del dispositivo perro guardián */
static rt_device_t wdg_dev; /* Identificador del dispositivo perro guardián */
/* Voltaje de referencia ADC */
#define VREF 3.3
/* Voltaje de alimentación */
#define VCC 3.3
/* Datos de compensación de calibración */
#define V0_COMP 1.000   // Rango de voltaje 0.0325x
#define V1_COMP 1.000   // Rango de voltaje 0.0947x
#define V2_COMP 1.000   // Rango de voltaje 0.6175x
#define YIF1_COMP 1.00  // Compensación de muestreo de corriente del transistor MOS 1
#define YIF2_COMP 1.00  // Compensación de muestreo de corriente del transistor MOS 2
#define DAC1_COMP  1.00    // Coeficiente de compensación de salida DAC1(VREF)
#define IREF2_COMP  1.00  // Coeficiente de compensación de salida IREF2
#define DAC2_COMP   1.00   // Compensación de salida DAC2(IREF1)
/* Número de cálculos de valor promedio de muestreo ADC */
#define ADC_count 3
/* Coeficiente de filtro de paso bajo de primer orden */
#define dPower1 0.5
/* Número de pin, determinado consultando el archivo de controlador 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

/* Variable global */
u8g2_t u8g2;                    // Variable de estructura u8g2
rt_uint16_t AD_Value[4];           // Datos de muestreo ADC

// Definir variable de enumeración de página de modo
enum mode_type
{
    menu = 0, // Menú
    CC,       // Corriente constante
    CV,       // Voltaje constante
    CR,       // Resistencia constante
    CW        // Potencia constante
};

volatile uint8_t Eload_Out = 0;                 // Estado de encendido/apagado de salida de carga electrónica
volatile uint8_t mode = menu;                   // Modo actual
volatile uint8_t voltage_dw = 0;                // Rango de muestreo de voltaje, 0 es 0.0325x, 2 es 0.6175x, 1 es 0.0947x
volatile double YVF, YIF1, YIF2, YIF, VBAT;     // Voltaje y corriente actuales
volatile double ISET, VSET, RSET, PSET;         // Valores de configuración de corriente, voltaje, resistencia y potencia
volatile uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM, VBAT_SUM;  // Suma de cálculo para promediar voltaje y corriente
volatile uint8_t AVG_count = 0;                 // Valor de conteo acumulativo de cálculo de valor promedio de corriente
volatile uint8_t YVF_AVG_count = 0;             // Valor de conteo acumulativo de cálculo de valor promedio de voltaje
volatile uint8_t VBAT_count = 0;                      // Valor de conteo acumulativo de cálculo de valor promedio de voltaje de batería
volatile uint8_t Key_ONOFF = 0;                 // Estado de si se presionó el botón de encendido/apagado de carga electrónica

static qpid_t qpid_CC;                          // Puntero de datos de control PID
static qpid_t qpid_CV;                          // Puntero de datos de control PID
static qpid_t qpid_CR;                          // Puntero de datos de control PID
static qpid_t qpid_CW;                          // Puntero de datos de control PID
static double I_SET, V_SET, R_SET, P_SET;

/* Declaración de funciones */
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   Programa principal.
 *
 * @return  ninguno
 */
int main(void)
{
    rt_kprintf("MCU: CH32V307\n");
    rt_kprintf("SysClk: %dHz\n", SystemCoreClock);
    SYS_Init();

    while (1)
    {
        // Protección contra sobrecorriente y sobrepotencia
        if (YIF > 10 | YVF * YIF > 300)
        {
            if (Eload_Out == 1)
            {
                Key_ONOFF = 1;
            }
        }
        rt_thread_mdelay(20);
    }
}

/* Inicialización del sistema */
void SYS_Init(void)
{
    IWDG_Init();    // Inicializar perro guardián
    UART_Init();   // Inicializar UART3
    HMILCD_Send("page 0");   // Cambiar a página de inicio
    Dac_Init();     // Inicializar DAC
    PWM_Init();     // Inicializar PWM

    rt_pin_mode(MCU_G0, PIN_MODE_OUTPUT);   // Configurar puerto IO como modo de salida
    rt_pin_mode(MCU_G1, PIN_MODE_OUTPUT);
    rt_pin_write(MCU_G0, PIN_LOW);        // Salida de nivel bajo
    rt_pin_write(MCU_G1, PIN_LOW);

    Thread_Init();   // Crear hilos
}

/* Inicialización de hilos */
void Thread_Init(void)
{
    rt_thread_t tid = NULL; // Definir puntero de bloque de control de hilo
    /* Crear hilos */
    tid = rt_thread_create("SYS_LED", thread1_sysLED_entry, NULL, 512, 30, 5);
    // Crear un hilo llamado SYS_LED, función de entrada thread1_sysLED_entry, parámetro NULL, tamaño de pila 256 bytes, prioridad 30, intervalo de tiempo 5 ticks
    if (tid != RT_NULL) // Verificar si el hilo se creó correctamente
    {
        if (rt_thread_startup(tid) == RT_EOK) // Iniciar hilo
            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) // Iniciar hilo
     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) // Iniciar hilo
            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) // Iniciar hilo
            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) // Iniciar hilo
            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) // Iniciar hilo
            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) // Iniciar hilo
            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) // Iniciar hilo
            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) // Iniciar hilo
            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) // Iniciar hilo
            LOG_D("thread10 BlueTooth create success");
    }
    else
    {
        LOG_E("thread10 BlueTooth create failed...");
    }

}
``````c
/* Filtro paso bajo de primer orden
 * Valor de retorno: iData valor de muestreo después del filtrado de primer orden */
double lowV1(double com1)
{
    static double iLastData1;    // Valor anterior
    double iData1;               // Valor calculado en esta ocasión
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData1; // Cálculo
    iLastData1 = iData1;                                     // Almacenar datos de esta ocasión
    return iData1;                                         // Retornar datos
}
double lowV2(double com1)
{
    static double iLastData2;    // Valor anterior
    double iData1;               // Valor calculado en esta ocasión
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData2; // Cálculo
    iLastData2 = iData1;                                     // Almacenar datos de esta ocasión
    return iData1;                                         // Retornar datos
}
u16 lowV3(u16 com1)
{
    static u16 iLastData3;    // Valor anterior
    u16 iData1;               // Valor calculado en esta ocasión
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData3; // Cálculo
    iLastData3 = iData1;                                     // Almacenar datos de esta ocasión
    return iData1;                                         // Retornar datos
}
u16 lowV4(u16 com1)
{
    static u16 iLastData3;    // Valor anterior
    u16 iData1;               // Valor calculado en esta ocasión
    iData1 = (com1 * 0.1) + (1 - 0.1) * iLastData3; // Cálculo
    iLastData3 = iData1;                                     // Almacenar datos de esta ocasión
    return iData1;                                         // Retornar datos
}

static void idle_hook(void)
{
    /* Alimentar al perro en la función de devolución de llamada del hilo inactivo */
    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; /* Tiempo de desbordamiento, unidad: segundos */
    /* Buscar el dispositivo perro guardián por nombre de dispositivo, obtener el identificador del dispositivo */
    wdg_dev = rt_device_find(WDT_DEVICE_NAME);
    if (!wdg_dev)
    {
        rt_kprintf("find %s failed!\n", WDT_DEVICE_NAME);
        return RT_ERROR;
    }
    /* Inicializar dispositivo */
    rt_device_init(wdg_dev);
    /* Establecer tiempo de desbordamiento del perro guardián */
    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;
    }
    /* Iniciar perro guardián */
    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;
    }
    /* Establecer función de devolución de llamada del hilo inactivo */
    rt_thread_idle_sethook(idle_hook);

    return ret;
}

/* Función de entrada del hilo 1, parpadeo del LED de estado del sistema */
static void thread1_sysLED_entry(void *parameter)
{
    /* El pin LED1 está en modo de salida */
    rt_pin_mode(LED1, PIN_MODE_OUTPUT);
    /* Nivel bajo por defecto */
    rt_pin_write(LED1, PIN_LOW);
    while (1)
    {
        /* El hilo 1 se ejecuta con baja prioridad, parpadeando continuamente LED1 */
        rt_pin_write(LED1, !rt_pin_read(LED1));
        rt_thread_mdelay(500);
    }
}

/* Función de entrada del hilo 2, pantalla OLED mostrando información */
static void thread2_OLED_entry(void *parameter)
{
    // Inicialización
    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); // Establecer conjunto de caracteres chinos

        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); // Formatear cadena de salida a variable de cadena
        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); // Formatear cadena de salida a variable de cadena
        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); // Formatear cadena de salida a variable de cadena
        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); // Formatear cadena de salida a variable de cadena
        u8g2_DrawStr(&u8g2, 0, 63, String);
        u8g2_SendBuffer(&u8g2); // Enviar datos del búfer
        rt_thread_mdelay(100);  // Retraso de 100 milisegundos
    }
}

/* Función de entrada del hilo 3, procesamiento de datos ADC */
static void thread3_ADC_entry(void *parameter)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // Habilitar reloj GPIOA y ADC
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);      // Habilitar reloj DMA1

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);       // Configuración de distribución de reloj ADC, división de 6 (72Mhz/6=12Mhz), la frecuencia del reloj ADC no puede exceder 14Mhz

    GPIO_InitTypeDef GPIO_InitStructure = { 0 };                // Definir estructura para configurar GPIO
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;    // Establecer puerto GPIO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // Modo GPIO como entrada analógica
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // Configurar grupo regular ADC, escribir canal 0 en secuencia 1 del grupo regular, tiempo de muestreo 55.5 ciclos
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); // Configurar grupo regular ADC, escribir canal 1 en secuencia 2 del grupo regular, tiempo de muestreo 55.5 ciclos
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); // Configurar grupo regular ADC, escribir canal 2 en secuencia 3 del grupo regular, tiempo de muestreo 55.5 ciclos
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_239Cycles5); // Configurar grupo regular ADC, escribir canal 3 en secuencia 4 del grupo regular, tiempo de muestreo 55.5 ciclos

    ADC_InitTypeDef ADC_InitStructure = { 0 };
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  // Configurar ADC en modo independiente
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;        // Habilitar modo de escaneo en modo multicanal
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // Establecer modo de conversión continua habilitado
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // Establecer que la conversión no se inicia por disparo externo, disparo por software
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // Establecer alineación derecha de datos ADC
    ADC_InitStructure.ADC_NbrOfChannel = 4;                // Número de canales ADC para conversión regular
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_Cmd(ADC1, ENABLE);      // Habilitar ADC1

    ADC_ResetCalibration(ADC1); // Restablecer registro de calibración ADC1

    while (ADC_GetResetCalibrationStatus(ADC1))
        ; // Esperar a que finalice el restablecimiento de calibración

    ADC_StartCalibration(ADC1); // Iniciar calibración AD

    while (ADC_GetCalibrationStatus(ADC1))
        ;      // Esperar a que finalice la calibración

    DMA_DeInit(DMA1_Channel1); // Restablecer controlador DMA
    DMA_InitTypeDef DMA_InitStructure;                      // Definir estructura para configurar DMA
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) &ADC1->RDATAR; // Configurar dirección periférica como dirección de registro de datos ADC
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32) AD_Value;          // Configurar dirección de memoria como dirección de lectura de valor ADC
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;              // Configurar fuente de datos como periférico, es decir, modo de transferencia DMA de periférico a memoria
    DMA_InitStructure.DMA_BufferSize = 4;                           // Establecer tamaño de búfer de datos DMA
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                           // Establecer modo de incremento periférico DMA deshabilitado
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;         // Establecer modo de incremento de memoria DMA habilitado
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // Establecer tamaño de datos periféricos como media palabra, es decir, dos bytes
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // Establecer tamaño de datos de memoria como media palabra, es decir, dos bytes
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;     // Establecer modo DMA como modo de transferencia circular
    DMA_InitStructure.DMA_Priority = DMA_Priority_High; // Establecer prioridad del canal de transferencia DMA como alta, cuando se utiliza un canal DMA, la configuración de prioridad no afecta
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;        // Porque el modo de transferencia DMA es de periférico a memoria, por lo tanto deshabilitar modo de transferencia de memoria a memoria
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);        // Inicializar canal 1 de DMA1, el disparo de hardware de ADC1 está en canal 1 de DMA1, por lo que debe usar canal 1 de DMA1

    DMA_Cmd(DMA1_Channel1, ENABLE); // Iniciar canal 1 de DMA1
    ADC_DMACmd(ADC1, ENABLE);       // Habilitar solicitud DMA de ADC
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);       // Dado que no se utiliza disparo externo, usar disparo por software para conversión ADC

    qpid_init(&qpid_CC);            // Inicializar datos de control PID
    qpid_set_lmt(&qpid_CC, 0, 10);  // Establecer límites PID
    qpid_set_ratio(&qpid_CC, 1, 0.001, 0.1);    // Establecer coeficientes de relación de control

    qpid_init(&qpid_CV);                // Inicializar datos de control PID
    qpid_set_lmt(&qpid_CV, 31.0, 100);  // Establecer límites PID
    qpid_set_ratio(&qpid_CV, 0.35, 0.005, 0.001);    // Establecer coeficientes de relación de control
```qpid_init(&qpid_CW);                // Inicializar datos de control PID
    qpid_set_lmt(&qpid_CW, 0.1, 200);  // Establecer límites PID
    qpid_set_ratio(&qpid_CW, 0.5, 0.003, 0.001);    // Establecer coeficientes de relación de control

    qpid_init(&qpid_CR);                // Inicializar datos de control PID
    qpid_set_lmt(&qpid_CR, 0.1, 1000);  // Establecer límites PID
    qpid_set_ratio(&qpid_CR, 0.35, 0.003, 0.001);    // Establecer coeficientes de relación de control

    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) // Cuando el rango de muestreo de voltaje es 0.0325 veces
        {
            if (YVF_AVG_count < ADC_count) // Acumular valores de muestreo cuando es menor a 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;    // Calcular valor de voltaje
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }
            if (YVF <= 31.0) // Cambiar rango cuando el voltaje es menor a 31V
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_HIGH); // Rango de muestreo de voltaje 0.0947 veces
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Establecer valor de salida DAC1, controlar voltaje constante
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // Establecer límites PID
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // Establecer coeficientes de relación de control
            }
        }
        else if (voltage_dw == 1) // Cuando el rango de voltaje es 0.0947 veces
        {
            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)  // Cambiar a rango 0.0325 veces cuando el voltaje es mayor a 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)); // Establecer valor de salida DAC1, controlar voltaje constante
                }
                voltage_dw = 0;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 31.0, 100); // Establecer límites PID
                qpid_set_ratio(&qpid_CV, 0.5, 0.005, 0.0005);    // Establecer coeficientes de relación de control
            }
            else if (YVF <= 4.6) // Cambiar rango cuando el voltaje es menor a 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); // Establecer valor de salida DAC1, controlar voltaje constante
                }
                voltage_dw = 2;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 0.01, 5.1); // Establecer límites PID
                qpid_set_ratio(&qpid_CV, 0.26, 0.0025, 0.0005);    // Establecer coeficientes de relación de control
            }
        }
        else if (voltage_dw == 2) // Cuando el rango de voltaje es 0.6175 veces
        {
            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) // Cambiar rango cuando el voltaje es mayor a 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)); // Establecer valor de salida DAC1, controlar voltaje constante
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // Establecer límites PID
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // Establecer coeficientes de relación de control
            }
        }

        if (AVG_count < ADC_count)
        {
            YIF1_SUM += AD_Value[1]; // Acumular corriente del transistor MOS 1
            YIF2_SUM += AD_Value[2]; // Acumular corriente del transistor MOS 2
            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]); // Acumular valor de muestreo de voltaje de batería
            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```c
/* Entry function for thread 4, processes data from the serial screen */
static void thread4_HMI_GetDate_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // Clear the current setting value displayed on the screen
    HMILCD_Send("CV.x0.val=0"); // Clear the voltage setting value displayed on the screen
    HMILCD_Send("CR.x0.val=0"); // Clear the resistance setting value displayed on the screen
    HMILCD_Send("CW.x0.val=0"); // Clear the power setting value 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 constant current mode page
            {
                if (Serial3_RxPacket[1] == 0x10) // Menu button pressed
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // Display OFF in the title box at the top right of the screen
                    HMILCD_Send("CC.b1.txt=\"开启\""); // Display "Turn On" on the button at the bottom right of the screen
                    Eload_Out = 0;                           // Set load output status to off
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 outputs high level, turn off constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, turn off constant current
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");          // Switch to menu page
                    mode = menu;
                }
                else if (Serial3_RxPacket[1] == 0x11) // Turn on button pressed and current load output status is off
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x03) // Currently on 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, turn off constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, turn off constant current
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // Turn on button pressed and current load output status is off
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x04) // Currently on 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, turn off constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, turn off constant current
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // Turn on button pressed and current load output status is off
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x05) // Currently on 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, turn off constant voltage
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 outputs low level, turn off constant current
                    PWM_SetCCR4(0);  // Set IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // Turn on button pressed and current load output status is off
                {
                    Key_ONOFF = 1;
                }
            }

            else if (Serial3_RxPacket[0] == 0xAA) // Currently on numeric keypad page
            {
                char *temp = Serial3_RxPacket;
                temp++;                      // Address increment by 1
                uint16_t temp2 = atoi(temp); // Convert 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 is 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 is 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();
                }
            }
            Serial3_RxFlag = 0;
        }
        key123();
        rt_thread_mdelay(35);
    }

}
``````c
/*Modo de potencia constante*/
void CW_mode(void)
{
    double Ptemp = PSET / YVF;
    if (Ptemp > 10)
        Ptemp = 10;
    if (Eload_Out == 1)
    {
        // Establecer el valor de salida de DAC2, controlar corriente constante, +0.5 es para redondeo
        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));  // Establecer IREF2
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactivar voltaje constante
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactivar corriente constante
        PWM_SetCCR4(0);  // Establecer IREF2
    }
}

/*Modo de resistencia constante*/
void CR_mode(void)
{
    double Rtemp = YVF / RSET;
    if (Rtemp > 10)
        Rtemp = 10;
    if (Eload_Out == 1)
    {
        // Establecer el valor de salida de DAC2, controlar corriente constante, +0.5 es para redondeo
        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));  // Establecer IREF2
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactivar voltaje constante
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactivar corriente constante
        PWM_SetCCR4(0);  // Establecer IREF2
    }
}

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

    if (mode == CC && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CC, ISET);   // Establecer valor objetivo de PID
        I_SET = qpid_cal_pos(&qpid_CC, YIF);    // Cálculo de PID
        if (ISET <= 2.5)
        {
            // Establecer el valor de salida de DAC2, controlar corriente constante, +0.5 es para redondeo
            DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        }
        else
        {
            // Establecer el valor de salida de DAC2, controlar corriente constante, +0.5 es para redondeo
            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));  // Establecer IREF2
        }
    }
    if (mode == CV && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CV, VSET);           // Establecer valor objetivo de PID
        V_SET = qpid_cal_pos(&qpid_CV, YVF);    // Cálculo de PID
        if (voltage_dw == 0)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
            // Establecer el valor de salida de DAC1, controlar voltaje constante
        }
        else if (voltage_dw == 1)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
            // Establecer el valor de salida de DAC1, controlar voltaje constante
        }
        else if (voltage_dw == 2)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
            // Establecer el valor de salida de DAC1, controlar voltaje constante
        }
    }
    if (mode == CW && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CW, PSET);   // Establecer valor objetivo de PID
        P_SET = qpid_cal_pos(&qpid_CW, YIF * YVF);    // Cálculo de PID
        Ptemp = P_SET / YVF;
        if (Ptemp > 10)
            Ptemp = 10;
        // Establecer el valor de salida de DAC2, controlar corriente constante, +0.5 es para redondeo
        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));  // Establecer IREF2
    }
    if (mode == CR && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CR, RSET);   // Establecer valor objetivo de PID
        R_SET = qpid_cal_pos(&qpid_CR, YVF / YIF);    // Cálculo de PID
        Rtemp = YVF / R_SET;
        if (Rtemp > 10)
            Rtemp = 10;
        // Establecer el valor de salida de DAC2, controlar corriente constante, +0.5 es para redondeo
        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));  // Establecer IREF2
    }

}

/* Función de procesamiento de teclas */
void key123(void)
{
    if (key[2] == 1)    // Tecla 2, tecla de cambio
    {
        if (mode == menu)
        {
            HMILCD_Send("page CC"); // Cambiar a página de modo de corriente constante
            mode = CC;              // Establecer modo actual a modo de corriente constante
        }
        else if (mode == CC)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CC.t1.txt=\"OFF\"");
                HMILCD_Send("CC.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactivar voltaje constante
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactivar corriente constante
                PWM_SetCCR4(0);  // Establecer IREF2
            }
            HMILCD_Send("page CV"); // Cambiar a página de modo de voltaje constante
            mode = CV;              // Establecer modo actual a modo de voltaje constante
        }
        else if (mode == CV)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CV.t1.txt=\"OFF\"");
                HMILCD_Send("CV.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactivar voltaje constante
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactivar corriente constante
                PWM_SetCCR4(0);  // Establecer IREF2
            }
            HMILCD_Send("page CR"); // Cambiar a página de modo de resistencia constante
            mode = CR;              // Establecer modo actual a modo de resistencia constante
        }
        else if (mode == CR)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CR.t1.txt=\"OFF\"");
                HMILCD_Send("CR.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactivar voltaje constante
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactivar corriente constante
                PWM_SetCCR4(0);  // Establecer IREF2
            }
            HMILCD_Send("page CW"); // Cambiar a página de modo de potencia constante
            mode = CW;              // Establecer modo actual a modo de potencia constante
        }
        else if (mode == CW)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CW.t1.txt=\"OFF\"");
                HMILCD_Send("CW.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactivar voltaje constante
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactivar corriente constante
                PWM_SetCCR4(0);  // Establecer IREF2
            }
            HMILCD_Send("page menu"); // Cambiar a página de menú
            mode = menu;              // Establecer modo actual a modo de menú
        }
        key[2] = 0;
    }
    if (key[3] == 1)    // Tecla 3, tecla de menú
    {
        if (Eload_Out == 1)
            Key_ONOFF = 1;
        rt_thread_mdelay(15);
        HMILCD_Send("page menu"); // Cambiar a página de menú
        mode = menu;              // Establecer modo actual a modo de menú
        key[3] = 0;
    }
}
``````c
/* Función de entrada del hilo 5, manejo del botón de encendido/apagado de la carga electrónica */
static void thread5_ONOFF_entry(void *parameter)
{
    /* Pin LED2 configurado como salida */
    rt_pin_mode(LED2, PIN_MODE_OUTPUT);
    /* Nivel alto por defecto */
    rt_pin_write(LED2, PIN_HIGH);
    while (1)
    {
        if (Key_ONOFF == 1 | key[1] == 1) // Botón de encendido presionado
        {
            if (mode == CC) // Modo de corriente constante
            {
                if (Eload_Out == 0) // Estado actual de salida de carga es apagado
                {
                    HMILCD_Send("CC.t1.txt=\"ON\"");   // Mostrar ON en el cuadro de título de la esquina superior derecha
                    HMILCD_Send("CC.b1.txt=\"关闭\"");    // Mostrar apagar en el botón de la esquina inferior derecha
                    Eload_Out = 1;                           // Establecer estado de salida de carga como encendido
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    if (ISET <= 2.5)
                    {
                        // Establecer valor de salida DAC2, controlar corriente constante, +0.5 es para redondeo
                        DAC_SetChannel2Data(DAC_Align_12b_R,
                                (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                        PWM_SetCCR4(0);  // Establecer IREF2
                    }
                    else
                    {
                        // Establecer valor de salida DAC2, controlar corriente constante, +0.5 es para redondeo
                        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));  // Establecer IREF2
                    }
                }
                else if (Eload_Out == 1) // Estado actual de salida de carga es encendido
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");
                    HMILCD_Send("CC.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, apagar voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, apagar corriente constante
                    PWM_SetCCR4(0);  // Establecer IREF2
                }
            }
            else if (mode == CV) // Modo de voltaje constante
            {
                if (Eload_Out == 0) // Estado actual de salida de carga es apagado
                {
                    HMILCD_Send("CV.t1.txt=\"ON\"");
                    HMILCD_Send("CV.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Establecer IREF1
                    PWM_SetCCR4(50000);  // Establecer IREF2
                    if (voltage_dw == 0)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Establecer valor de salida DAC1, controlar voltaje constante
                    }
                    else if (voltage_dw == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Establecer valor de salida DAC1, controlar voltaje constante
                    }
                    else if (voltage_dw == 2)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
                        // Establecer valor de salida DAC1, controlar voltaje constante
                    }
                }
                else if (Eload_Out == 1) // Estado actual de salida de carga es encendido
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, apagar voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, apagar corriente constante
                    PWM_SetCCR4(0);  // Establecer IREF2
                }
            }
            else if (mode == CR) // Modo de resistencia constante
            {
                if (Eload_Out == 0) // Estado actual de salida de carga es apagado
                {
                    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) // Estado actual de salida de carga es encendido
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, apagar voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, apagar corriente constante
                    PWM_SetCCR4(0);  // Establecer IREF2
                }
            }
            else if (mode == CW) // Modo de potencia constante
            {
                if (Eload_Out == 0) // Estado actual de salida de carga es apagado
                {
                    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) // Estado actual de salida de carga es encendido
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, apagar voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, apagar corriente constante
                    PWM_SetCCR4(0);  // Establecer 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);
    }

}

/* Función de entrada del hilo 7, mostrar parámetros en pantalla serie */
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));       // Mostrar voltaje
            HMILCD_Send("x2.val=%d", (uint16_t)(I * 1000));      // Mostrar corriente
            HMILCD_Send("x3.val=%d", (uint32_t)(I * V * 100)); // Mostrar potencia
            HMILCD_Send("x4.val=%d", (uint32_t)(V / I * 100)); // Mostrar resistencia
            HMILCD_Send("x5.val=%d", (uint32_t)(VBAT * 100));     // Mostrar resistencia
        }
        rt_thread_mdelay(50);
    }
}

/* Función de entrada del hilo 8, control del ventilador de disipación de calor */
static void thread8_FAN_entry(void *parameter)
{
    while (1)
    {
        uint16_t P = (uint16_t)(YIF * YVF + 0.5);
        if (P >= 13) // Iniciar ventilador cuando la potencia es mayor a 13W
        {
            FAN_PWM_ON();
            if (P < 20)
            {
                FAN_PWM_SetCCR(20); // Control del ventilador con ciclo de trabajo del 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); // Apagar la salida PWM del ventilador y enviar nivel bajo
            FAN_PWM_OFF();
        }
        rt_thread_mdelay(200);
    }
}

/* Función de entrada del hilo 9, control del modo de potencia constante y resistencia constante */
static void thread9_CWCR_entry(void *parameter)
{
    while (1)
    {
        if (mode == CW)
        {
            CW_mode();
        }
        if (mode == CR)
        {
            CR_mode();
        }
        rt_thread_mdelay(50);
    }
}
``````c
/* Función de entrada del hilo 10, procesa datos recibidos del Bluetooth */
static void thread10_BlueTooth_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // Borra el valor de configuración de corriente mostrado en pantalla
    HMILCD_Send("CV.x0.val=0"); // Borra el valor de configuración de voltaje mostrado en pantalla
    HMILCD_Send("CR.x0.val=0"); // Borra el valor de configuración de resistencia mostrado en pantalla
    HMILCD_Send("CW.x0.val=0"); // Borra el valor de configuración de potencia mostrado en pantalla
    while (1)
    {
        if (Serial5_RxFlag == 1)
        {
            if (Serial5_RxPacket[0] == 0x01) // Página de menú principal actual
            {
                if (Serial5_RxPacket[1] == 0x10) // Botón de corriente constante presionado
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"Activar\"");

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

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

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactiva voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactiva corriente constante
                    PWM_SetCCR4(0);  // Configura IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CC"); // Cambia a página de modo de corriente constante
                    mode = CC;                    // Configura el modo actual a corriente constante
                }
                else if (Serial5_RxPacket[1] == 0x11) // Botón de voltaje constante presionado
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"Activar\"");

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

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

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactiva voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactiva corriente constante
                    PWM_SetCCR4(0);  // Configura IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CV"); // Cambia a página de voltaje constante
                    mode = CV;                    // Configura el modo actual a voltaje constante
                }
                else if (Serial5_RxPacket[1] == 0x12) // Botón de resistencia constante presionado
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"Activar\"");

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

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

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactiva voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactiva corriente constante
                    PWM_SetCCR4(0);  // Configura IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CR"); // Cambia a página de resistencia constante
                    mode = CR;                    // Configura el modo actual a resistencia constante
                }
                else if (Serial5_RxPacket[1] == 0x13) // Botón de potencia constante presionado
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"Activar\"");

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

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

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactiva voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactiva corriente constante
                    PWM_SetCCR4(0);  // Configura IREF2
                    Eload_Out = 0;
                    HMILCD_Send("page CW"); // Cambia a página de potencia constante
                    mode = CW;                    // Configura el modo actual a potencia constante
                }
            }
            else if (Serial5_RxPacket[0] == 0x08)    // Volver al menú
            {
                if (mode == CC)
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // Marco de título en esquina superior derecha muestra OFF
                    HMILCD_Send("CC.b1.txt=\"Activar\""); // Botón en esquina inferior derecha muestra Activar
                    Eload_Out = 0;                           // Configura estado de salida de carga a desactivado
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactiva voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactiva corriente constante
                    PWM_SetCCR4(0);  // Configura IREF2
                    HMILCD_Send("page menu");          // Cambia a página de menú
                    mode = menu;
                }
                else if (mode == CV)
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"Activar\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactiva voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactiva corriente constante
                    PWM_SetCCR4(0);  // Configura IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CR)
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"Activar\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactiva voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactiva corriente constante
                    PWM_SetCCR4(0);  // Configura IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CW)
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"Activar\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 salida nivel alto, desactiva voltaje constante
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2 salida nivel bajo, desactiva corriente constante
                    PWM_SetCCR4(0);  // Configura IREF2
                    HMILCD_Send("page menu");
                    mode = menu;
                }
            }
            else if (Serial5_RxPacket[0] == 0x09)    // Botón Activar
            {
                Key_ONOFF = 1;
            }

            else if (Serial5_RxPacket[0] == 0xAA) // Página de teclado numérico actual
            {
                char *temp = Serial5_RxPacket;
                temp++;                      // Incrementa dirección en 1
                uint16_t temp2 = atoi(temp); // Convierte cadena a entero
                if (mode == CC)
                {
                    if (temp2 > 1000)
                        temp2 = 1000;
                    ISET = temp2 / 100.0;
                    HMILCD_Send("CC.x0.val=%d", temp2);
``````c
if (Eload_Out == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                        if (ISET <= 2.5)
                        {
                            // Establecer el valor de salida de DAC2, controlar la corriente constante, +0.5 es para redondeo
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4(0);  // Establecer IREF2
                        }
                        else
                        {
                            // Establecer el valor de salida de DAC2, controlar la corriente constante, +0.5 es para redondeo
                            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)); // Establecer 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); // Establecer IREF1
                        PWM_SetCCR4(50000);  // Establecer IREF2
                        if (voltage_dw == 0)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Establecer el valor de salida de DAC1, controlar voltaje constante
                        }
                        else if (voltage_dw == 1)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Establecer el valor de salida de DAC1, controlar voltaje constante
                        }
                        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); // Establecer el valor de salida de DAC1, controlar voltaje constante
                        }
                    }
                }
                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);
    }

}

Lecturas recomendadas