Carga electrónica inteligente basada en HT32F52352, proyecto abierto de Holtek Cup

Carga electrónica inteligente basada en HT32F52352 – Proyecto open-source para la competencia Holtek Cup, incluye esquemático, PCB, código fuente e informe.

Tercer premio en la 10ª edición 2023 del Guangdong University Holtek Cup Microcontroller Application Design Contest.

Hecho a contrarreloj en un mes (con muchas clases y poco tiempo disponible), quedó bastante regular, sin haters por favor.

Vídeo de demostración: https://www.bilibili.com/video/BV1sM4y1b7qu/

Este trabajo open-source es solo para consulta y estudio; no se recomienda replicarlo. ¡En la plataforma LCSC Open Source hay proyectos de cargas electrónicas mucho mejores y más completos!

Enlace al proyecto en LCSC Open Source: https://url.zeruns.com/xvvF8

Guía de instalación y configuración del entorno de desarrollo para Holtek HT32: https://blog.zeruns.com/archives/709.html

Grupo de Telegram (electrónica y microcontroladores): 2169025065

¿Qué es una carga electrónica?

Una carga electrónica es un dispositivo que simula una carga real para probar el comportamiento de fuentes de alimentación o circuitos. Frente a las resistencias de alta potencia o bobinas de horno tradicionales, ofrece ajuste de parámetros y comodidad de uso. Es imprescindible tanto en laboratorios profesionales como para aficionados.

Según el tipo de fuente a probar se dividen en cargas de CA y de CC. Por funciones, las más comunes son: corriente constante, tensión constante, resistencia constante y potencia constante. Al ser la mayoría de nuestras fuentes de tipo CC con tensión constante, lo habitual es probar su capacidad de entrega de corriente; por eso predomina la carga electrónica CC de corriente constante. Por método de control existen las analógicas y las digitales; estas últimas, controladas por microcontrolador, permiten ajustes visuales, más funciones, fácil expansión y automatización de pruebas.

Resumen del proyecto

Utiliza el microcontrolador Holtek HT32F52352 como CPU; se alimenta con una batería 18650 para portabilidad.

El control se realiza mediante un PWM generado por el MCU que, tras pasar por un filtro paso-bajo, produce una tensión DC de referencia (función DAC). Esta referencia se compara en un amplificador operacional con la señal de corriente/tensión muestreada y amplificada; la salida del opamp gobierna el MOSFET, logrando modo constante-corriente o constante-tensión.

Pantalla táctil serial de 2,8" de Taojingchi, modelo TJC3224T028_011R.

Fotos reales

No saqué muchas; aquí van un par. Para más detalles, véase el vídeo de demostración.


Descarga de documentación

Incluye: esquemáticos, proyecto LCSC-EDA, archivos GERBER, firmware, proyecto de pantalla serial y datasheets.

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

BaiduPan: https://url.zeruns.com/XP241 Código: i9bl

Compra de componentes

Se recomienda comprar los componentes en LCSC: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Esquemáticos

Placa de potencia



Placa de alimentación

Placa de control

PCB

Placa de potencia

Top

Inner 1

Bottom

(No se muestra la capa GND.)

Placa de alimentación

Top

Bottom

Placa de control

Top

Bottom

Código principal

Archivo main.c

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

#define KEY1 GPIO_ReadInBit(HT_GPIOA, GPIO_PIN_10)

// Definición de estados del pulsador
typedef enum
{
  KS_RELEASE = 0, // Suelto
  KS_SHAKE,       // Rebote
  KS_PRESS,       // Estable pulsado
} KEY_STATUS;

// Estado al final del bucle (máquina de estados)
#define g_keyStatus 0
// Estado actual (igual que g_keyStatus tras cada bucle)
#define g_nowKeyStatus 1
// Estado anterior (para detectar transiciones)
#define g_lastKeyStatus 2

uint8_t KEY_Status[4][3]; // Estado de cada tecla
uint8_t key[4];           // 1 = pulsado estable, 0 = no pulsado
char *temp;
```// Definir enumeración para las páginas de modo
enum mode_type
{
  menu = 0, // menú
  CC,       // corriente constante
  CV,       // voltaje constante
  CR,       // resistencia constante
  CW        // potencia constante
};

uint8_t Eload_Out = 0;                // estado de encendido/apagado de la carga electrónica
uint8_t mode = menu;                  // modo actual
uint8_t voltage_dw = 0;               // rango de muestreo de voltaje, 0 es 0.0325×, 2 es 0.6175×, 1 es 0.0947×
float YVF, YIF1, YIF2, YIF;           // voltaje y corriente actuales
float ISET, VSET, RSET, PSET;         // valores de ajuste de corriente, voltaje, resistencia y potencia
float VDD = 3.3;                      // voltaje de alimentación del microcontrolador
uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM; // sumas para cálculo de promedio de voltaje y corriente
uint8_t AVG_count = 0;                // contador de acumulación para promedio de corriente
uint8_t YVF_AVG_count = 0;            // contador de acumulación para promedio de voltaje
uint8_t Key_ONOFF = 0;                // estado del botón de encendido/apagado de la carga electrónica

void HMI_GetData(void);
void HMI_Display(void);
void AdcFb(void);
void ONOFF(void);
void FAN(void);
void key_status_check(uint8_t key_num, uint8_t KEY);
void CW_mode(void);
void CR_mode(void);
void OFF(void); // apagar
void ON(void);  // encender

int main(void)
{
  GPIO_Configuration();     // inicializar GPIO
  BFTM0_Configuration();    // inicializar temporizador BFTM0
  GPTM0_Configuration();    // inicializar temporizador GPTM0
  GPTM1_Configuration();    // inicializar temporizador GPTM1
  MCTM0_Configuration();    // inicializar temporizador MCTM0
  WDT_Configuration();      // inicializar watchdog
  OLED_Init();              // inicializar OLED
  ADC_Configuration();      // inicializar ADC
  RETARGET_Configuration(); // redirigir función printf al puerto serie
  USART1_Configuration();   // inicializar puerto serie 1
  USART0_Configuration();   // inicializar puerto serie 0

  uint16_t count1 = 0;

  Serial_SendHMILCD("page 0");      // cambiar a página de inicio
  GPTM0_CH0_DisablePWMOutput(1);    // desactivar salida PWM del canal CH0 (voltaje constante) y poner nivel alto
  GPTM0_CH2_DisablePWMOutput(0);    // desactivar salida PWM del canal CH2 (corriente constante 1) y poner nivel bajo
  GPTM0_CH3_DisablePWMOutput(0);    // desactivar salida PWM del canal CH3 (corriente constante 2) y poner nivel bajo
  MCTM0_CH0_DisablePWMOutput(0);    // desactivar salida PWM del canal MCTM0_CH0 (ventilador) y poner nivel bajo
  Serial_SendHMILCD("CC.x0.val=0"); // borrar valor de corriente en pantalla
  Serial_SendHMILCD("CV.x0.val=0"); // borrar valor de voltaje en pantalla
  Serial_SendHMILCD("CR.x0.val=0"); // borrar valor de resistencia en pantalla
  Serial_SendHMILCD("CW.x0.val=0"); // borrar valor de potencia en pantalla

  GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET); // MCU_G0, rango de muestreo voltaje 0.6175×
  GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET); // MCU_G1, rango de muestreo voltaje 0.0947×

  while (1)
  {
    if (HT_CKCU->APBCCR1 & (1 << 4)) // verificar si el reloj del watchdog está habilitado
      WDT_Restart();                 // reiniciar contador del watchdog

    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, RESET);

    if (bftm0_ct3 >= 1) // ejecutar cada 1 ms
    {
      AdcFb(); // procesamiento de datos ADC
      bftm0_ct3 = 0;
    }

    HMI_GetData();

    if (bftm0_ct2 >= 40) // ejecutar cada 40 ms
    {
      HMI_Display(); // mostrar datos en pantalla serie
      bftm0_ct2 = 0;
    }
    ONOFF();
    FAN();

    if (bftm0_ct >= 50) // ejecutar cada 50 ms
    {
      key_status_check(0, KEY1); // escaneo de teclas
      if (mode == CW)
      {
        CW_mode();
      }
      if (mode == CR)
      {
        CR_mode();
      }
      bftm0_ct = 0;
    }

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

    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, SET);
    /*
        float Voltage0 = (AD_Value[0] & 0x0000FFFF) / 4096.0 * 3.3; // convertir valor ADC a voltaje
        OLED_ShowNum(1, 4, (uint8_t)Voltage0, 1);                   // mostrar parte entera
        OLED_ShowString(1, 5, ".");
        OLED_ShowString(1, 9, "V");
        OLED_ShowNum(1, 6, (uint16_t)(Voltage0 * 1000) % 1000, 3); // mostrar parte decimal

        float Voltage1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * 3.3; // convertir valor ADC a voltaje
        OLED_ShowNum(2, 4, (uint8_t)Voltage1, 1);                   // mostrar parte entera
        OLED_ShowString(2, 5, ".");
        OLED_ShowString(2, 9, "V");
        OLED_ShowNum(2, 6, (uint16_t)(Voltage1 * 1000) % 1000, 3); // mostrar parte decimal

        float Voltage2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * 3.3; // convertir valor ADC a voltaje
        OLED_ShowNum(3, 4, (uint8_t)Voltage2, 1);                   // mostrar parte entera
        OLED_ShowString(3, 5, ".");
        OLED_ShowString(3, 9, "V");
        OLED_ShowNum(3, 6, (uint16_t)(Voltage2 * 1000) % 1000, 3); // mostrar parte decimal

        VDD = (AD_Value[3] & 0x0000FFFF) / 4096.0 * 3.3; // convertir valor ADC a voltaje
        OLED_ShowNum(4, 4, (uint8_t)VDD, 1);             // mostrar parte entera
        OLED_ShowString(4, 5, ".");
        OLED_ShowString(4, 9, "V");
        OLED_ShowNum(4, 6, (uint16_t)(VDD * 1000) % 1000, 3); // mostrar parte decimal

        VDD = (AD_Value[4] & 0x0000FFFF) / 4096.0 * 3.3; // convertir valor ADC a voltaje
        OLED_ShowNum(4, 11, (uint8_t)VDD, 1);            // mostrar parte entera
        OLED_ShowString(4, 12, ".");
        OLED_ShowString(4, 16, "V");
        OLED_ShowNum(4, 13, (uint16_t)(VDD * 1000) % 1000, 3); // mostrar parte decimal
        count1++;*/

			if(flag_start==1) // procesamiento de datos puerto serie 1
			{
				if(++cishu==7)  {flag_over=1;}
							  Delay_ms(1);
			}
			if(flag_over==1)
		{
					flag_over=0;
			 if (Data[0] == 'A') // actualmente en página del teclado numérico
    {

			char *temp = Data;
      temp++;                      // incrementar dirección en 1
      uint16_t temp2 = atoi(temp); // convertir cadena a entero
//      if (mode == CC)
//      {
//        if (temp2 > 10000)
//          temp2 = 10000;
//        ISET = temp2 / 1000.0;
//        Serial_SendHMILCD("CC.x0.val=%d", temp2);

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

    }
			switch (Data[0]){
				case '6':
				Serial_SendHMILCD("page menu");          // cambiar a página de menú
        mode = menu;
				break;
				case '5':
		              Serial_SendHMILCD("page CW"); // cambiar a página de potencia constante
                  mode = CW;                    // establecer modo actual como potencia constante
				break;
				case '4':
		              Serial_SendHMILCD("page CR"); // cambiar a página de resistencia constante
                  mode = CR;                    // establecer modo actual como resistencia constante
				break;
				case '3':
		              Serial_SendHMILCD("page CV"); // cambiar a página de voltaje constante
                  mode = CV;                    // establecer modo actual como voltaje constante
				break;
        case '2':
		              Serial_SendHMILCD("page CC"); // cambiar a página de modo de corriente constante
                  mode = CC;                    // establecer modo actual como corriente constante
					break;
				case '1':
		      ON(); // encender
					break;
				case '0':
          OFF(); // apagar
					break;
				default:
					break;
			}

			  cishu=0;
				len=0;
				flag_start=0;
	 }
  }
}void ON(void)//encender
{
		if (mode == CC) // modo de corriente constante
    {
      if (Eload_Out == 0) // cuando la salida de carga actual está apagada
      {
        Serial_SendHMILCD("CC.t1.txt=\"ON\"");   // mostrar ON en el cuadro de título superior derecho de la pantalla
        Serial_SendHMILCD("CC.b1.txt=\"关闭\""); // mostrar “关闭” en el botón inferior derecho de la pantalla
        Eload_Out = 1;                           // establecer el estado de salida de carga como encendido
        if (ISET <= 2.5)                         // cuando el valor de corriente fijado es menor a 2.5 A, activar solo un MOSFET
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput();   // habilitar PWM del canal CH2 (IREF1)
          GPTM0_CH3_DisablePWMOutput(0); // deshabilitar PWM del canal CH3 (IREF2) y forzar salida baja
        }
        else
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput(); // habilitar PWM del canal CH2
          GPTM0_CH3_EnablePWMOutput(); // habilitar PWM del canal CH3
        }
        GPTM0_CH0_DisablePWMOutput(0); // VREF a nivel bajo
      }
      
    }
		
		else if (mode == CV) // modo de voltaje constante
    {
      if (Eload_Out == 0) // cuando la salida de carga actual está apagada
      {
        Serial_SendHMILCD("CV.t1.txt=\"ON\"");
        Serial_SendHMILCD("CV.b1.txt=\"关闭\"");
        Eload_Out = 1;
        if (voltage_dw == 0)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
        }
        else if (voltage_dw == 1)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
        }
        else if (voltage_dw == 2)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
        }
        GPTM0_CH0_EnablePWMOutput();
        GPTM0_CH2_DisablePWMOutput(1);
        GPTM0_CH3_DisablePWMOutput(1);
      }
    }
		
		else if (mode == CR) // modo de resistencia constante
    {
      if (Eload_Out == 0) // cuando la salida de carga actual está apagada
      {
        Serial_SendHMILCD("CR.t1.txt=\"ON\"");
        Serial_SendHMILCD("CR.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Rtemp = YVF / RSET;
        if (Rtemp > 10)
          Rtemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // habilitar PWM del canal CH2
        GPTM0_CH3_EnablePWMOutput(); // habilitar PWM del canal CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF a nivel bajo
      }
    }
		
		else if (mode == CW) // modo de potencia constante
    {
      if (Eload_Out == 0) // cuando la salida de carga actual está apagada
      {
        Serial_SendHMILCD("CW.t1.txt=\"ON\"");
        Serial_SendHMILCD("CW.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Ptemp = PSET / YVF;
        if (Ptemp > 10)
          Ptemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // habilitar PWM del canal CH2
        GPTM0_CH3_EnablePWMOutput(); // habilitar PWM del canal CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF a nivel bajo
      }
    }
}

void OFF(void)//apagar
{
	      
	if (mode == CC) // modo de corriente constante
		{
					if (Eload_Out == 1) // cuando la salida de carga actual está encendida
				{
					Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
					Serial_SendHMILCD("CC.b1.txt=\"开启\"");
					Eload_Out = 0;
					GPTM0_CH0_DisablePWMOutput(1); // VREF a nivel alto
					GPTM0_CH2_DisablePWMOutput(0); // IREF1 a nivel bajo
					GPTM0_CH3_DisablePWMOutput(0); // IREF2 a nivel bajo
				}
			}
			
			else if (mode == CV) // modo de voltaje constante
     {
		 
		  if (Eload_Out == 1) // cuando la salida de carga actual está encendida
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF a nivel alto
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 a nivel bajo
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 a nivel bajo
      }
    }
	 
	 else if (mode == CR) // modo de resistencia constante
     {  
		 if (Eload_Out == 1) // cuando la salida de carga actual está encendida
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF a nivel alto
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 a nivel bajo
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 a nivel bajo
      }
		}
		else if (mode == CW) // modo de potencia constante
    {  
		 if (Eload_Out == 1) // cuando la salida de carga actual está encendida
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF a nivel alto
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 a nivel bajo
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 a nivel bajo
      }
		}
}

/*procesar datos recibidos desde la pantalla serial*/
void HMI_GetData(void)
{
  if (Serial_RxFlag == 1)
  {
    if (Serial_RxPacket[0] == 0x01) // actualmente en la página del menú principal
    {
      if (Serial_RxPacket[1] == 0x10) // botón de corriente constante presionado
      {
        Serial_SendHMILCD("page CC"); // cambiar a la página del modo de corriente constante
        mode = CC;                    // establecer el modo actual como corriente constante
      }
      else if (Serial_RxPacket[1] == 0x11) // botón de voltaje constante presionado
      {
        Serial_SendHMILCD("page CV"); // cambiar a la página de voltaje constante
        mode = CV;                    // establecer el modo actual como voltaje constante
      }
      else if (Serial_RxPacket[1] == 0x12) // botón de resistencia constante presionado
      {
        Serial_SendHMILCD("page CR"); // cambiar a la página de resistencia constante
        mode = CR;                    // establecer el modo actual como resistencia constante
      }
      else if (Serial_RxPacket[1] == 0x13) // botón de potencia constante presionado
      {
        Serial_SendHMILCD("page CW"); // cambiar a la página de potencia constante
        mode = CW;                    // establecer el modo actual como potencia constante
      }
    }
    else if (Serial_RxPacket[0] == 0x02) // actualmente en la página del modo de corriente constante
    {
      if (Serial_RxPacket[1] == 0x10) // botón de menú presionado
      {
        Serial_SendHMILCD("CC.t1.txt=\"OFF\"");  // mostrar OFF en el cuadro de título superior derecho de la pantalla
        Serial_SendHMILCD("CC.b1.txt=\"开启\""); // mostrar “开启” en el botón inferior derecho de la pantalla
        Eload_Out = 0;                           // establecer el estado de salida de carga como apagado
        GPTM0_CH0_DisablePWMOutput(1);           // deshabilitar salida PWM del canal CH0 (voltaje constante) y forzar nivel alto
        GPTM0_CH2_DisablePWMOutput(0);           // deshabilitar salida PWM del canal CH2 (corriente constante 1) y forzar nivel bajo
        GPTM0_CH3_DisablePWMOutput(0);           // deshabilitar salida PWM del canal CH3 (corriente constante 2) y forzar nivel bajo
        Serial_SendHMILCD("page menu");          // cambiar a la página del menú
        mode = menu;
      }
      else if (Serial_RxPacket[1] == 0x11) // botón de encendido presionado y la salida de carga actual está apagada
      {
        Key_ONOFF = 1;
      }
    }
    else if (Serial_RxPacket[0] == 0x03) // actualmente en la página del modo de voltaje constante
    {
      if (Serial_RxPacket[1] == 0x10) // botón de menú presionado
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // deshabilitar salida PWM del canal CH0 (voltaje constante) y forzar nivel alto
        GPTM0_CH2_DisablePWMOutput(0); // deshabilitar salida PWM del canal CH2 (corriente constante 1) y forzar nivel bajo
        GPTM0_CH3_DisablePWMOutput(0); // deshabilitar salida PWM del canal CH3 (corriente constante 2) y forzar nivel bajo
        Serial_SendHMILCD("page menu");
        mode = menu;
      }
      if (Serial_RxPacket[1] == 0x11) // botón de encendido presionado y la salida de carga actual está apagada
      {
        Key_ONOFF = 1;
      }
    }
    else if (Serial_RxPacket[0] == 0x04) // actualmente en la página del modo de resistencia constante
    {
      if (Serial_RxPacket[1] == 0x10) // botón de menú presionado
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // deshabilitar salida PWM del canal CH0 (voltaje constante) y forzar nivel alto
        GPTM0_CH2_DisablePWMOutput(0); // deshabilitar salida PWM del canal CH2 (corriente constante 1) y forzar nivel bajo
        GPTM0_CH3_DisablePWMOutput(0); // deshabilitar salida PWM del canal CH3 (corriente constante 2) y forzar nivel bajo
        Serial_SendHMILCD("page menu");
        mode = menu;
      }
      if (Serial_RxPacket[1] == 0x11) // botón de encendido presionado y la salida de carga actual está apagada
      {
        Key_ONOFF = 1;
      }
    }
    else if (Serial_RxPacket[0] == 0x05) // actualmente en la página del modo de potencia constante
    {
      if (Serial_RxPacket[1] == 0x10) // botón de menú presionado
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // deshabilitar salida PWM del canal CH0 (voltaje constante) y forzar nivel alto
        GPTM0_CH2_DisablePWMOutput(0); // deshabilitar salida PWM del canal CH2 (corriente constante 1) y forzar nivel bajo
        GPTM0_CH3_DisablePWMOutput(0); // deshabilitar salida PWM del canal CH3 (corriente constante 2) y forzar nivel bajo
        Serial_SendHMILCD("page menu");
        mode = menu;
      }
      if (Serial_RxPacket[1] == 0x11) // botón de encendido presionado y la salida de carga actual está apagada
      {
        Key_ONOFF = 1;
      }
    }

    }else if (Serial_RxPacket[0] == 0xAA) // actualmente en la página del teclado numérico
    {
      char *temp = Serial_RxPacket;
      temp++;                      // incrementar dirección en 1
      uint16_t temp2 = atoi(temp); // convertir cadena a entero
      if (mode == CC)
      {
        if (temp2 > 10000)
          temp2 = 10000;
        ISET = temp2 / 1000.0;
        Serial_SendHMILCD("CC.x0.val=%d", temp2);

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

/*mostrar parámetros en pantalla serial*/
void HMI_Display(void)
{
  if (mode != menu)
  {
    Serial_SendHMILCD("x1.val=%d", (uint16_t)(YVF * 100));       // mostrar voltaje
    Serial_SendHMILCD("x2.val=%d", (uint16_t)(YIF * 1000));      // mostrar corriente
    Serial_SendHMILCD("x3.val=%d", (uint32_t)(YIF * YVF * 100)); // mostrar potencia
    Serial_SendHMILCD("x4.val=%d", (uint32_t)(YVF / YIF * 100)); // mostrar resistencia
  }
}

/*procesamiento de datos ADC*/
void AdcFb(void)
{
  VDD = (AD_Value[4] & 0x0000FFFF) * 1.2482 / (AD_Value[3] & 0x0000FFFF); // convertir valor muestreado ADC a voltaje
                                                                          // VDD = (AD_Value[4] & 0x0000FFFF)  / 4096.0*3.3; // convertir valor muestreado ADC a voltaje
  // VDD = 3.3;

  if (voltage_dw == 0) // rango de muestreo de voltaje 0.0325x
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0325;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
    if (YVF <= 32) // cambiar rango si el voltaje es menor a 32V
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
      voltage_dw = 1;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
    }
  }
  else if (voltage_dw == 1) // rango de voltaje 0.0947x
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0947;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;

    if (YVF >= 34)
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
      voltage_dw = 0;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
    }
    else if (YVF <= 5.0) // cambiar rango si el voltaje es menor a 5V
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, SET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
      voltage_dw = 2;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;
    }
  }
  else if (voltage_dw == 2) // rango de voltaje 0.6175x
  {
    if (YVF_AVG_count < 15)
    {
      YVF_SUM += (AD_Value[0] & 0x0000FFFF);
      YVF_AVG_count++;
    }
    if (YVF_AVG_count == 15)
    {
      YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.6175;
      if (YVF < 0.2)
      {
        YVF = 0;
      }
      YVF_AVG_count = 0;
      YVF_SUM = 0;
    }
    // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;

    if (YVF > 5.1) // cambiar rango si el voltaje es mayor a 5.1V
    {
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
      GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
      GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
      voltage_dw = 1;
      YVF_AVG_count = 0;
      YVF_SUM = 0;
      // YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
    }
  }

  if (AVG_count < 15)
  {
    YIF1_SUM += (AD_Value[1] & 0x0000FFFF); // acumular corriente del MOSFET 1
    YIF2_SUM += (AD_Value[2] & 0x0000FFFF); // acumular corriente del MOSFET 2
    AVG_count++;
  }
  if (AVG_count == 15)
  {
    YIF1 = YIF1_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
    YIF2 = YIF2_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
    YIF = YIF1 + YIF2;
    AVG_count = 0;
    YIF1_SUM = 0;
    YIF2_SUM = 0;
  }
  // YIF1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
  // YIF2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
  // YIF = YIF1 + YIF2;
}```c
/*Botón de encendido/apagado de la carga electrónica*/
void ONOFF(void)
{
  if (Key_ONOFF == 1 | key[0] == 1) // Se presiona el botón de encendido
  {
    if (mode == CC) // Modo de corriente constante
    {
      if (Eload_Out == 0) // Cuando el estado de salida de la carga está apagado
      {
        Serial_SendHMILCD("CC.t1.txt=\"ON\"");   // Mostrar ON en el cuadro de título superior derecho de la pantalla
        Serial_SendHMILCD("CC.b1.txt=\"关闭\""); // Mostrar "关闭" en el botón inferior derecho de la pantalla
        Eload_Out = 1;                           // Establecer el estado de salida de la carga como encendido
        if (ISET <= 2.5)                         // Cuando el valor de corriente establecido es menor a 2.5A, solo se activa un MOSFET
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput();   // Activar el PWM del canal CH2(IREF1)
          GPTM0_CH3_DisablePWMOutput(0); // Desactivar el PWM del canal CH3(IREF2) y enviar señal baja
        }
        else
        {
          GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
          GPTM0_CH2_EnablePWMOutput(); // Activar el PWM del canal CH2
          GPTM0_CH3_EnablePWMOutput(); // Activar el PWM del canal CH3
        }
        GPTM0_CH0_DisablePWMOutput(0); // VREF envía señal baja
      }
      else if (Eload_Out == 1) // Cuando el estado de salida de la carga está encendido
      {
        Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CC.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF envía señal alta
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 envía señal baja
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 envía señal baja
      }
    }
    else if (mode == CV) // Modo de voltaje constante
    {
      if (Eload_Out == 0) // Cuando el estado de salida de la carga está apagado
      {
        Serial_SendHMILCD("CV.t1.txt=\"ON\"");
        Serial_SendHMILCD("CV.b1.txt=\"关闭\"");
        Eload_Out = 1;
        if (voltage_dw == 0)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
        }
        else if (voltage_dw == 1)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
        }
        else if (voltage_dw == 2)
        {
          GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
        }
        GPTM0_CH0_EnablePWMOutput();
        GPTM0_CH2_DisablePWMOutput(1);
        GPTM0_CH3_DisablePWMOutput(1);
      }
      else if (Eload_Out == 1) // Cuando el estado de salida de la carga está encendido
      {
        Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CV.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF envía señal alta
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 envía señal baja
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 envía señal baja
      }
    }
    else if (mode == CR) // Modo de resistencia constante
    {
      if (Eload_Out == 0) // Cuando el estado de salida de la carga está apagado
      {
        Serial_SendHMILCD("CR.t1.txt=\"ON\"");
        Serial_SendHMILCD("CR.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Rtemp = YVF / RSET;
        if (Rtemp > 10)
          Rtemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // Activar el PWM del canal CH2
        GPTM0_CH3_EnablePWMOutput(); // Activar el PWM del canal CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF envía señal baja
      }
      else if (Eload_Out == 1) // Cuando el estado de salida de la carga está encendido
      {
        Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CR.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF envía señal alta
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 envía señal baja
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 envía señal baja
      }
    }
    else if (mode == CW) // Modo de potencia constante
    {
      if (Eload_Out == 0) // Cuando el estado de salida de la carga está apagado
      {
        Serial_SendHMILCD("CW.t1.txt=\"ON\"");
        Serial_SendHMILCD("CW.b1.txt=\"关闭\"");
        Eload_Out = 1;
        float Ptemp = PSET / YVF;
        if (Ptemp > 10)
          Ptemp = 10;
        GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
        GPTM0_CH2_EnablePWMOutput(); // Activar el PWM del canal CH2
        GPTM0_CH3_EnablePWMOutput(); // Activar el PWM del canal CH3

        GPTM0_CH0_DisablePWMOutput(0); // VREF envía señal baja
      }
      else if (Eload_Out == 1) // Cuando el estado de salida de la carga está encendido
      {
        Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
        Serial_SendHMILCD("CW.b1.txt=\"开启\"");
        Eload_Out = 0;
        GPTM0_CH0_DisablePWMOutput(1); // VREF envía señal alta
        GPTM0_CH2_DisablePWMOutput(0); // IREF1 envía señal baja
        GPTM0_CH3_DisablePWMOutput(0); // IREF2 envía señal baja
      }
    }
		
    Key_ONOFF = 0;
    key[0] = 0;
  }
// https://blog.zeruns.com
  if (Eload_Out == 0)
  {
    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, SET);
  }
  else if (Eload_Out == 1)
  {
    GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, RESET);
  }
}

/*Control del ventilador de refrigeración*/
void FAN(void)
{
  uint16_t P = (uint16_t)(YIF * YVF);
  if (P >= 6) // Activar el ventilador cuando la potencia es mayor a 6W
  {
    MCTM0_CH0_EnablePWMOutput(); // Activar el ventilador
    if (P < 15)
    {
      MCTM0_CH0_SetOnduty(50); // Ciclo de trabajo del control del ventilador al 50%
    }
    else if (P >= 15 && P < 20)
    {
      MCTM0_CH0_SetOnduty(60);
    }
    else if (P >= 20 && P < 25)
    {
      MCTM0_CH0_SetOnduty(70);
    }
    else if (P >= 25 && P < 30)
    {
      MCTM0_CH0_SetOnduty(80);
    }
    else if (P >= 30 && P < 35)
    {
      MCTM0_CH0_SetOnduty(90);
    }
    else if (P >= 35)
    {
      MCTM0_CH0_SetOnduty(100);
    }
  }
  else if (P <= 5)
  {
    MCTM0_CH0_DisablePWMOutput(0); // Desactivar la salida PWM del canal MCTM0_CH0 (ventilador) y enviar señal baja
  }
}

/*Modo de potencia constante*/
void CW_mode(void)
{
  float Ptemp = PSET / YVF;
  if (Ptemp > 10)
    Ptemp = 10;
  if (Eload_Out == 1)
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
  else
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
}

/*Modo de resistencia constante*/
void CR_mode(void)
{
  float Rtemp = YVF / RSET;
  if (Rtemp > 10)
    Rtemp = 10;
  if (Eload_Out == 1)
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
  else
  {
    GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
    GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
  }
}

// Programa de máquina de estados del botón
void key_status_check(uint8_t key_num, uint8_t KEY)
{
  switch (KEY_Status[key_num][g_keyStatus])
  {
  // Botón liberado (estado inicial)
  case KS_RELEASE:
  {
    // Detectar nivel bajo, realizar antirrebote primero
    if (KEY == 0)
    {
      KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
    }
  }
  break;

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

  // Presión corta estable
  case KS_PRESS:
  {
    // Detectar nivel alto, realizar antirrebote primero
    if (KEY == 1)
    {
      KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
    }
  }
  break;

  default:
    break;
  }

  if (KEY_Status[key_num][g_keyStatus] != KEY_Status[key_num][g_nowKeyStatus])
  {
    // El estado actual es liberado y el estado anterior es presionado
    if ((KEY_Status[key_num][g_keyStatus] == KS_RELEASE) && (KEY_Status[key_num][g_lastKeyStatus] == KS_PRESS))
    {
      key[key_num] = 1;
    }
    KEY_Status[key_num][g_lastKeyStatus] = KEY_Status[key_num][g_nowKeyStatus];
    KEY_Status[key_num][g_nowKeyStatus] = KEY_Status[key_num][g_keyStatus];
  }
}

Otros proyectos de código abierto recomendados

Recomendaciones de lectura- Recomendaciones de VPS/Servidores en la nube de alta relación calidad-precio y económicos: https://blog.zeruns.com/archives/383.html