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
- Placa de desarrollo HT32F52352: https://s.click.taobao.com/XmpTGpt
- MCU HT32F52352 suelto: https://s.click.taobao.com/sG9xeot
- INA199A1: https://s.click.taobao.com/XLuweot
- Kit de resistencias SMD 0805: https://s.click.taobao.com/p8YSGpt
- Kit de condensadores SMD 0805: https://u.jd.com/9uvZoBd
- XL6007E1: https://s.click.taobao.com/14Oueot
- Pantalla serial: https://s.click.taobao.com/pyzleot
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
- He creado un medidor trifásico de energía de código abierto, que permite monitorear fácilmente el consumo eléctrico del hogar: https://blog.zeruns.com/archives/771.html
- Plantilla de proyecto STM32F407 con biblioteca U8g2 para gráficos, usando la biblioteca estándar: https://blog.zeruns.com/archives/722.html
- Placa mínima de sistema CH32V307VCT6 de Qinheng, de código abierto: https://blog.zeruns.com/archives/726.html
- Módulo de fuente de alimentación DCDC ajustable con conversión automática buck-boost LM25118: https://blog.zeruns.com/archives/727.html
- Módulo de elevación de potencia de gran potencia con rectificación síncrona EG1164, eficiencia máxima del 97%, de código abierto: https://blog.zeruns.com/archives/730.html
- Nodo de monitoreo ambiental 4G basado en Air700E de Hezhou (datos de temperatura, humedad, presión, etc.), carga a la plataforma IoT de Alibaba Cloud mediante MQTT: https://blog.zeruns.com/archives/747.html
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
- Tutorial para crear un servidor de Minecraft: https://blog.zeruns.com/tag/mc/
- ¡Crea un blog sin código! Tutorial súper detallado para montar un blog personal: https://blog.zeruns.com/archives/783.html
- Tutorial para montar un servidor de penetración de red local, guía de instalación y uso de NPS: https://blog.zeruns.com/archives/741.html
- Tutorial para montar SD (Stable Diffusion) en un servidor GPU de YuYu Cloud, crea tu propio sitio web de dibujo con IA: https://blog.zeruns.com/archives/768.html
- Revisión de la cámara del Huawei Pura70Pro+, comparación con el Mate40Pro: https://blog.zeruns.com/archives/782.html













