Open-source Intelligent Electronic Load based on HT32F52352, Holtek Cup entry open-sourced, includes schematic, PCB, source code, and project report.
Third-prize work in the 10th Guangdong Province University Student Holtek Cup Microcontroller Application Design Competition 2023.
Rushed out in one month (with lots of classes and little time), very mediocre, please don’t roast.
Demo video: https://www.bilibili.com/video/BV1sM4y1b7qu/
This open-source project is for reference and study only; replication is not recommended. Much better and more complete electronic-load projects are available on the Lichuang open-source platform!
Lichuang open-source project link: https://url.zeruns.com/xvvF8
Holtek HT32 MCU development environment setup guide: https://blog.zeruns.com/archives/709.html
Electronics / MCU tech chat group: 2169025065
What is an Electronic Load
An electronic load is a device that simulates real load conditions to test the performance of power supplies or electronic circuits. Compared with traditional passive loads such as high-power adjustable resistors or heater wires, electronic loads offer adjustable parameters and ease of use. Whether for professional electronic engineering development or hobbyist projects, an electronic load is an essential instrument.
By the type of power supply under test, electronic loads are divided into AC electronic loads and DC electronic loads. Functionally, the common types are constant-current, constant-voltage, constant-resistance, and constant-power. Since most common power supplies are constant-voltage DC sources, testing mainly focuses on their current output capability; therefore, in most applications, a DC constant-current electronic load is the most common type. By control method, electronic loads are classified as either digital or analog. Compared with purely analog-controlled electronic loads, digital-controlled electronic loads provide more intuitive parameter adjustment, richer functions, easier expansion, and convenient automation of tests.
Project Overview
This work uses the HT32F52352 Holtek MCU as the main controller. It is powered by an 18650 Li-ion battery for portability.
Control method: the MCU outputs a PWM wave to a low-pass filter to generate a relatively stable DC voltage (DAC function via PWM + low-pass filter) as the reference voltage for the op-amp, which compares it with the amplified voltage/current sample; the op-amp output drives the MOSFET, achieving constant-voltage/constant-current operation.
The touch screen is a 2.8-inch serial screen from Taojingchi, model TJC3224T028_011R.
Photos
Didn’t take many photos at the time—only these two were found; check the demo video for more.
Download Links
The packages below contain: circuit schematic, Lichuang EDA project files, PCB fabrication files, source code, serial-screen project files, and chip datasheets.
123 Cloud Drive: https://www.123pan.com/s/2Y9Djv-ZNevH.html
Baidu NetDisk: https://url.zeruns.com/XP241 code: i9bl
Component Purchase Links
- HT32F52352 dev board: https://s.click.taobao.com/XmpTGpt
- HT32F52352 MCU chip: https://s.click.taobao.com/sG9xeot
- INA199A1 chip: https://s.click.taobao.com/XLuweot
- 0805 SMD resistor sample book: https://s.click.taobao.com/p8YSGpt
- 0805 SMD capacitor sample book: https://u.jd.com/9uvZoBd
- XL6007E1 chip: https://s.click.taobao.com/14Oueot
- Serial screen: https://s.click.taobao.com/pyzleot
It is recommended to buy components from LCSC: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html
Schematics
Power Board
Power-supply Board
Control Board
PCB
Power Board
Top Layer
Inner Layer 1
Bottom Layer
Skipped the GND layer screenshot.
Power-supply Board
Top Layer
Bottom Layer
Control Board
Top Layer
Bottom Layer
Main Code
main.c
#include "ht32.h"
#include "GPIO.h"
#include "BFTM0.h"
#include "GPTM0.h"
#include "GPTM1.h"
#include "MCTM0.h"
#include "delay.h"
#include "OLED.h"
#include "WDT.h"
#include "ADC.h"
#include "USART.h"
#include "string.h"
#define KEY1 GPIO_ReadInBit(HT_GPIOA, GPIO_PIN_10)
// Define key-state enumeration
typedef enum
{
KS_RELEASE = 0, // key released
KS_SHAKE, // key bouncing
KS_PRESS, // stable press
} KEY_STATUS;
// State-machine state at end of current cycle
#define g_keyStatus 0
// Current state (synced with g_keyStatus after each cycle)
#define g_nowKeyStatus 1
// Previous state (to distinguish state source)
#define g_lastKeyStatus 2
uint8_t KEY_Status[4][3]; // records key states
uint8_t key[4]; // records stable key press, 1 = pressed, 0 = not pressed
char *temp;
``````c
// Define mode page enum
enum mode_type
{
menu = 0, // Menu
CC, // Constant Current
CV, // Constant Voltage
CR, // Constant Resistance
CW // Constant Power
};
uint8_t Eload_Out = 0; // Electronic load output on/off state
uint8_t mode = menu; // Current mode
uint8_t voltage_dw = 0; // Voltage sampling range: 0 = 0.0325×, 2 = 0.6175×, 1 = 0.0947×
float YVF, YIF1, YIF2, YIF; // Present voltage & currents
float ISET, VSET, RSET, PSET; // Current, voltage, resistance, power setpoints
float VDD = 3.3; // MCU supply voltage
uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM; // Summation for averaging voltage/current
uint8_t AVG_count = 0; // Counter for current averaging
uint8_t YVF_AVG_count = 0; // Counter for voltage averaging
uint8_t Key_ONOFF = 0; // State of load on/off key press
void HMI_GetData(void);
void HMI_Display(void);
void AdcFb(void);
void ONOFF(void);
void FAN(void);
void key_status_check(uint8_t key_num, uint8_t KEY);
void CW_mode(void);
void CR_mode(void);
void OFF(void); // Turn off
void ON(void); // Turn on
int main(void)
{
GPIO_Configuration(); // Init GPIO
BFTM0_Configuration(); // Init BFTM0 timer
GPTM0_Configuration(); // Init GPTM0 timer
GPTM1_Configuration(); // Init GPTM1 timer
MCTM0_Configuration(); // Init MCTM0 timer
WDT_Configuration(); // Init watchdog
OLED_Init(); // Init OLED
ADC_Configuration(); // Init ADC
RETARGET_Configuration(); // Redirect printf to UART
USART1_Configuration(); // Init USART1
USART0_Configuration(); // Init USART0
uint16_t count1 = 0;
Serial_SendHMILCD("page 0"); // Switch to boot page
GPTM0_CH0_DisablePWMOutput(1); // Disable CH0 (CV) PWM, output high
GPTM0_CH2_DisablePWMOutput(0); // Disable CH2 (CC1) PWM, output low
GPTM0_CH3_DisablePWMOutput(0); // Disable CH3 (CC2) PWM, output low
MCTM0_CH0_DisablePWMOutput(0); // Disable MCTM0_CH0 (fan) PWM, output low
Serial_SendHMILCD("CC.x0.val=0"); // Clear current setpoint on screen
Serial_SendHMILCD("CV.x0.val=0"); // Clear voltage setpoint on screen
Serial_SendHMILCD("CR.x0.val=0"); // Clear resistance setpoint on screen
Serial_SendHMILCD("CW.x0.val=0"); // Clear power setpoint on screen
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET); // MCU_G0, voltage range 0.6175×
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET); // MCU_G1, voltage range 0.0947×
while (1)
{
if (HT_CKCU->APBCCR1 & (1 << 4)) // Watchdog clock enabled?
WDT_Restart(); // Reload watchdog
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, RESET);
if (bftm0_ct3 >= 1) // Every 1 ms
{
AdcFb(); // Process ADC data
bftm0_ct3 = 0;
}
HMI_GetData();
if (bftm0_ct2 >= 40) // Every 40 ms
{
HMI_Display(); // Update HMI screen
bftm0_ct2 = 0;
}
ONOFF();
FAN();
if (bftm0_ct >= 50) // Every 50 ms
{
key_status_check(0, KEY1); // Scan keys
if (mode == CW)
{
CW_mode();
}
if (mode == CR)
{
CR_mode();
}
bftm0_ct = 0;
}
if (YIF > 10 || YVF * YIF > 100)
{
if (Eload_Out == 1)
{
Key_ONOFF = 1;
}
}
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, SET);
/*
float Voltage0 = (AD_Value[0] & 0x0000FFFF) / 4096.0 * 3.3; // ADC to voltage
OLED_ShowNum(1, 4, (uint8_t)Voltage0, 1); // Integer part
OLED_ShowString(1, 5, ".");
OLED_ShowString(1, 9, "V");
OLED_ShowNum(1, 6, (uint16_t)(Voltage0 * 1000) % 1000, 3); // Fraction
float Voltage1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * 3.3;
OLED_ShowNum(2, 4, (uint8_t)Voltage1, 1);
OLED_ShowString(2, 5, ".");
OLED_ShowString(2, 9, "V");
OLED_ShowNum(2, 6, (uint16_t)(Voltage1 * 1000) % 1000, 3);
float Voltage2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * 3.3;
OLED_ShowNum(3, 4, (uint8_t)Voltage2, 1);
OLED_ShowString(3, 5, ".");
OLED_ShowString(3, 9, "V");
OLED_ShowNum(3, 6, (uint16_t)(Voltage2 * 1000) % 1000, 3);
VDD = (AD_Value[3] & 0x0000FFFF) / 4096.0 * 3.3;
OLED_ShowNum(4, 4, (uint8_t)VDD, 1);
OLED_ShowString(4, 5, ".");
OLED_ShowString(4, 9, "V");
OLED_ShowNum(4, 6, (uint16_t)(VDD * 1000) % 1000, 3);
VDD = (AD_Value[4] & 0x0000FFFF) / 4096.0 * 3.3;
OLED_ShowNum(4, 11, (uint8_t)VDD, 1);
OLED_ShowString(4, 12, ".");
OLED_ShowString(4, 16, "V");
OLED_ShowNum(4, 13, (uint16_t)(VDD * 1000) % 1000, 3);
count1++;*/
if(flag_start==1) // USART1 data processing
{
if(++cishu==7) {flag_over=1;}
Delay_ms(1);
}
if(flag_over==1)
{
flag_over=0;
if (Data[0] == 'A') // Numeric keypad page
{
char *temp = Data;
temp++; // Skip 'A'
uint16_t temp2 = atoi(temp); // String to int
// if (mode == CC)
// {
// if (temp2 > 10000)
// temp2 = 10000;
// ISET = temp2 / 1000.0;
// Serial_SendHMILCD("CC.x0.val=%d", temp2);
// if (ISET <= 2.5)
// {
// if (Eload_Out == 1)
// {
// GPTM0_CH3_DisablePWMOutput(0);
// GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
// }
// else
// {
// GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
// }
// }
// else
// {
// if (Eload_Out == 1)
// {
// GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
// GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
// GPTM0_CH3_EnablePWMOutput();
// }
// else
// {
// GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
// GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
// }
// }
// }
// else if (mode == CV)
// {
// VSET = temp2 / 100.0;
// Serial_SendHMILCD("CV.x0.val=%d", temp2);
// if (voltage_dw == 0)
// {
// GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
// }
// else if (voltage_dw == 1)
// {
// GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
// }
// else if (voltage_dw == 2)
// {
// GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
// }
// }
// else if (mode == CR)
// {
// RSET = temp2 / 100.0;
// Serial_SendHMILCD("CR.x0.val=%d", temp2);
// CR_mode();
// }
if (mode == CW)
{
PSET = temp2/100.0;
Serial_SendHMILCD("CW.x0.val=%d", temp2);
CW_mode();
}
}
switch (Data[0]){
case '6':
Serial_SendHMILCD("page menu"); // Switch to menu page
mode = menu;
break;
case '5':
Serial_SendHMILCD("page CW"); // Switch to constant-power page
mode = CW; // Set mode to constant power
break;
case '4':
Serial_SendHMILCD("page CR"); // Switch to constant-resistance page
mode = CR; // Set mode to constant resistance
break;
case '3':
Serial_SendHMILCD("page CV"); // Switch to constant-voltage page
mode = CV; // Set mode to constant voltage
break;
case '2':
Serial_SendHMILCD("page CC"); // Switch to constant-current page
mode = CC; // Set mode to constant current
break;
case '1':
ON(); // Turn on
break;
case '0':
OFF(); // Turn off
break;
default:
break;
}
cishu=0;
len=0;
flag_start=0;
}
}
}
``````c
void ON(void) // Turn on
{
if (mode == CC) // Constant-current mode
{
if (Eload_Out == 0) // When the current load output is off
{
Serial_SendHMILCD("CC.t1.txt=\"ON\""); // Show “ON” in the top-right title box
Serial_SendHMILCD("CC.b1.txt=\"OFF\""); // Show “OFF” in the bottom-right button
Eload_Out = 1; // Set load output state to on
if (ISET <= 2.5) // If current set-point ≤ 2.5 A, enable only one MOSFET
{
GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2 (IREF1)
GPTM0_CH3_DisablePWMOutput(0); // Disable PWM on CH3 (IREF2) and output low
}
else
{
GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3
}
GPTM0_CH0_DisablePWMOutput(0); // VREF output low
}
}
else if (mode == CV) // Constant-voltage mode
{
if (Eload_Out == 0) // When the current load output is off
{
Serial_SendHMILCD("CV.t1.txt=\"ON\"");
Serial_SendHMILCD("CV.b1.txt=\"OFF\"");
Eload_Out = 1;
if (voltage_dw == 0)
{
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
}
else if (voltage_dw == 1)
{
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
}
else if (voltage_dw == 2)
{
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
}
GPTM0_CH0_EnablePWMOutput();
GPTM0_CH2_DisablePWMOutput(1);
GPTM0_CH3_DisablePWMOutput(1);
}
}
else if (mode == CR) // Constant-resistance mode
{
if (Eload_Out == 0) // When the current load output is off
{
Serial_SendHMILCD("CR.t1.txt=\"ON\"");
Serial_SendHMILCD("CR.b1.txt=\"OFF\"");
Eload_Out = 1;
float Rtemp = YVF / RSET;
if (Rtemp > 10)
Rtemp = 10;
GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3
GPTM0_CH0_DisablePWMOutput(0); // VREF output low
}
}
else if (mode == CW) // Constant-power mode
{
if (Eload_Out == 0) // When the current load output is off
{
Serial_SendHMILCD("CW.t1.txt=\"ON\"");
Serial_SendHMILCD("CW.b1.txt=\"OFF\"");
Eload_Out = 1;
float Ptemp = PSET / YVF;
if (Ptemp > 10)
Ptemp = 10;
GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3
GPTM0_CH0_DisablePWMOutput(0); // VREF output low
}
}
}
void OFF(void) // Turn off
{
if (mode == CC) // Constant-current mode
{
if (Eload_Out == 1) // When the current load output is on
{
Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
Serial_SendHMILCD("CC.b1.txt=\"ON\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF output high
GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
}
}
else if (mode == CV) // Constant-voltage mode
{
if (Eload_Out == 1) // When the current load output is on
{
Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
Serial_SendHMILCD("CV.b1.txt=\"ON\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF output high
GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
}
}
else if (mode == CR) // Constant-resistance mode
{
if (Eload_Out == 1) // When the current load output is on
{
Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
Serial_SendHMILCD("CR.b1.txt=\"ON\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF output high
GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
}
}
else if (mode == CW) // Constant-power mode
{
if (Eload_Out == 1) // When the current load output is on
{
Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
Serial_SendHMILCD("CW.b1.txt=\"ON\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF output high
GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
}
}
}
/*Process data sent from the serial screen*/
void HMI_GetData(void)
{
if (Serial_RxFlag == 1)
{
if (Serial_RxPacket[0] == 0x01) // Currently on main-menu page
{
if (Serial_RxPacket[1] == 0x10) // Constant-current button pressed
{
Serial_SendHMILCD("page CC"); // Switch to CC-mode page
mode = CC; // Set current mode to CC
}
else if (Serial_RxPacket[1] == 0x11) // Constant-voltage button pressed
{
Serial_SendHMILCD("page CV"); // Switch to CV page
mode = CV; // Set current mode to CV
}
else if (Serial_RxPacket[1] == 0x12) // Constant-resistance button pressed
{
Serial_SendHMILCD("page CR"); // Switch to CR page
mode = CR; // Set current mode to CR
}
else if (Serial_RxPacket[1] == 0x13) // Constant-power button pressed
{
Serial_SendHMILCD("page CW"); // Switch to CW page
mode = CW; // Set current mode to CW
}
}
else if (Serial_RxPacket[0] == 0x02) // Currently on CC-mode page
{
if (Serial_RxPacket[1] == 0x10) // Menu button pressed
{
Serial_SendHMILCD("CC.t1.txt=\"OFF\""); // Show “OFF” in top-right title box
Serial_SendHMILCD("CC.b1.txt=\"ON\""); // Show “ON” in bottom-right button
Eload_Out = 0; // Set load output state to off
GPTM0_CH0_DisablePWMOutput(1); // Disable CH0 (CV) PWM output and set high
GPTM0_CH2_DisablePWMOutput(0); // Disable CH2 (CC1) PWM output and set low
GPTM0_CH3_DisablePWMOutput(0); // Disable CH3 (CC2) PWM output and set low
Serial_SendHMILCD("page menu"); // Switch to menu page
mode = menu;
}
else if (Serial_RxPacket[1] == 0x11) // ON button pressed while load output is off
{
Key_ONOFF = 1;
}
}
else if (Serial_RxPacket[0] == 0x03) // Currently on CV-mode page
{
if (Serial_RxPacket[1] == 0x10) // Menu button pressed
{
Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
Serial_SendHMILCD("CV.b1.txt=\"ON\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // Disable CH0 (CV) PWM output and set high
GPTM0_CH2_DisablePWMOutput(0); // Disable CH2 (CC1) PWM output and set low
GPTM0_CH3_DisablePWMOutput(0); // Disable CH3 (CC2) PWM output and set low
Serial_SendHMILCD("page menu");
mode = menu;
}
if (Serial_RxPacket[1] == 0x11) // ON button pressed while load output is off
{
Key_ONOFF = 1;
}
}
else if (Serial_RxPacket[0] == 0x04) // Currently on CR-mode page
{
if (Serial_RxPacket[1] == 0x10) // Menu button pressed
{
Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
Serial_SendHMILCD("CR.b1.txt=\"ON\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // Disable CH0 (CV) PWM output and set high
GPTM0_CH2_DisablePWMOutput(0); // Disable CH2 (CC1) PWM output and set low
GPTM0_CH3_DisablePWMOutput(0); // Disable CH3 (CC2) PWM output and set low
Serial_SendHMILCD("page menu");
mode = menu;
}
if (Serial_RxPacket[1] == 0x11) // ON button pressed while load output is off
{
Key_ONOFF = 1;
}
}
else if (Serial_RxPacket[0] == 0x05) // Currently on CW-mode page
{
if (Serial_RxPacket[1] == 0x10) // Menu button pressed
{
Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
Serial_SendHMILCD("CW.b1.txt=\"ON\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // Disable CH0 (CV) PWM output and set high
GPTM0_CH2_DisablePWMOutput(0); // Disable CH2 (CC1) PWM output and set low
GPTM0_CH3_DisablePWMOutput(0); // Disable CH3 (CC2) PWM output and set low
Serial_SendHMILCD("page menu");
mode = menu;
}
if (Serial_RxPacket[1] == 0x11) // ON button pressed while load output is off
{
Key_ONOFF = 1;
}
}
}
}
``````c
else if (Serial_RxPacket[0] == 0xAA) // currently on numeric keypad page
{
char *temp = Serial_RxPacket;
temp++; // increment address by 1
uint16_t temp2 = atoi(temp); // convert string to integer
if (mode == CC)
{
if (temp2 > 10000)
temp2 = 10000;
ISET = temp2 / 1000.0;
Serial_SendHMILCD("CC.x0.val=%d", temp2);
if (ISET <= 2.5)
{
if (Eload_Out == 1)
{
GPTM0_CH3_DisablePWMOutput(0);
GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
}
else
{
GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
}
}
else
{
if (Eload_Out == 1)
{
GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_EnablePWMOutput();
}
else
{
GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
}
}
}
else if (mode == CV)
{
VSET = temp2 / 100.0;
Serial_SendHMILCD("CV.x0.val=%d", temp2);
if (voltage_dw == 0)
{
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
}
else if (voltage_dw == 1)
{
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
}
else if (voltage_dw == 2)
{
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
}
}
else if (mode == CR)
{
RSET = temp2 / 100.0;
Serial_SendHMILCD("CR.x0.val=%d", temp2);
CR_mode();
}
else if (mode == CW)
{
PSET = temp2 / 100.0;
Serial_SendHMILCD("CW.x0.val=%d", temp2);
CW_mode();
}
}
Serial_RxFlag = 0;
}
}
/* HMI screen display parameters */
void HMI_Display(void)
{
if (mode != menu)
{
Serial_SendHMILCD("x1.val=%d", (uint16_t)(YVF * 100)); // display voltage
Serial_SendHMILCD("x2.val=%d", (uint16_t)(YIF * 1000)); // display current
Serial_SendHMILCD("x3.val=%d", (uint32_t)(YIF * YVF * 100)); // display power
Serial_SendHMILCD("x4.val=%d", (uint32_t)(YVF / YIF * 100)); // display resistance
}
}
/* ADC data processing */
void AdcFb(void)
{
VDD = (AD_Value[4] & 0x0000FFFF) * 1.2482 / (AD_Value[3] & 0x0000FFFF); // convert ADC sample to voltage
// VDD = (AD_Value[4] & 0x0000FFFF) / 4096.0*3.3; // convert ADC sample to voltage
// VDD = 3.3;
if (voltage_dw == 0) // voltage sampling range 0.0325x
{
if (YVF_AVG_count < 15)
{
YVF_SUM += (AD_Value[0] & 0x0000FFFF);
YVF_AVG_count++;
}
if (YVF_AVG_count == 15)
{
YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0325;
YVF_AVG_count = 0;
YVF_SUM = 0;
}
// YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
if (YVF <= 32) // switch range when voltage below 32V
{
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
voltage_dw = 1;
YVF_AVG_count = 0;
YVF_SUM = 0;
// YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
}
}
else if (voltage_dw == 1) // voltage range 0.0947x
{
if (YVF_AVG_count < 15)
{
YVF_SUM += (AD_Value[0] & 0x0000FFFF);
YVF_AVG_count++;
}
if (YVF_AVG_count == 15)
{
YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.0947;
YVF_AVG_count = 0;
YVF_SUM = 0;
}
// YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
if (YVF >= 34)
{
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
voltage_dw = 0;
YVF_AVG_count = 0;
YVF_SUM = 0;
// YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0325;
}
else if (YVF <= 5.0) // switch range when voltage below 5V
{
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, SET);
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET);
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
voltage_dw = 2;
YVF_AVG_count = 0;
YVF_SUM = 0;
// YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;
}
}
else if (voltage_dw == 2) // voltage range 0.6175x
{
if (YVF_AVG_count < 15)
{
YVF_SUM += (AD_Value[0] & 0x0000FFFF);
YVF_AVG_count++;
}
if (YVF_AVG_count == 15)
{
YVF = YVF_SUM / YVF_AVG_count / 4096.0 * VDD / 0.6175;
if (YVF < 0.2)
{
YVF = 0;
}
YVF_AVG_count = 0;
YVF_SUM = 0;
}
// YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.6175;
if (YVF > 5.1) // switch range when voltage above 5.1V
{
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET);
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, SET);
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
voltage_dw = 1;
YVF_AVG_count = 0;
YVF_SUM = 0;
// YVF = (AD_Value[0] & 0x0000FFFF) / 4096.0 * VDD / 0.0947;
}
}
if (AVG_count < 15)
{
YIF1_SUM += (AD_Value[1] & 0x0000FFFF); // accumulate MOSFET 1 current
YIF2_SUM += (AD_Value[2] & 0x0000FFFF); // accumulate MOSFET 2 current
AVG_count++;
}
if (AVG_count == 15)
{
YIF1 = YIF1_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
YIF2 = YIF2_SUM / AVG_count / 4096.0 * VDD / 50 / 0.01;
YIF = YIF1 + YIF2;
AVG_count = 0;
YIF1_SUM = 0;
YIF2_SUM = 0;
}
// YIF1 = (AD_Value[1] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
// YIF2 = (AD_Value[2] & 0x0000FFFF) / 4096.0 * VDD / 50 / 0.01;
// YIF = YIF1 + YIF2;
}
```/*Electronic load ON/OFF button*/
void ONOFF(void)
{
if (Key_ONOFF == 1 | key[0] == 1) // Power-on button pressed
{
if (mode == CC) // Constant-current mode
{
if (Eload_Out == 0) // Load output currently OFF
{
Serial_SendHMILCD("CC.t1.txt=\"ON\""); // Show ON in top-right title box
Serial_SendHMILCD("CC.b1.txt=\"关闭\""); // Show “关闭” on bottom-right button
Eload_Out = 1; // Set load output to ON
if (ISET <= 2.5) // Only one MOSFET when current ≤ 2.5 A
{
GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2 (IREF1)
GPTM0_CH3_DisablePWMOutput(0); // Disable PWM on CH3 (IREF2), output low
}
else
{
GPTM0_CH2_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(ISET / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3
}
GPTM0_CH0_DisablePWMOutput(0); // VREF output low
}
else if (Eload_Out == 1) // Load output currently ON
{
Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
Serial_SendHMILCD("CC.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF output high
GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
}
}
else if (mode == CV) // Constant-voltage mode
{
if (Eload_Out == 0) // Load output currently OFF
{
Serial_SendHMILCD("CV.t1.txt=\"ON\"");
Serial_SendHMILCD("CV.b1.txt=\"关闭\"");
Eload_Out = 1;
if (voltage_dw == 0)
{
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0325 / VDD * 50000));
}
else if (voltage_dw == 1)
{
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.0947 / VDD * 50000));
}
else if (voltage_dw == 2)
{
GPTM0_CH0_SetOnduty((uint16_t)(VSET * 0.6175 / VDD * 50000));
}
GPTM0_CH0_EnablePWMOutput();
GPTM0_CH2_DisablePWMOutput(1);
GPTM0_CH3_DisablePWMOutput(1);
}
else if (Eload_Out == 1) // Load output currently ON
{
Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
Serial_SendHMILCD("CV.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF output high
GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
}
}
else if (mode == CR) // Constant-resistance mode
{
if (Eload_Out == 0) // Load output currently OFF
{
Serial_SendHMILCD("CR.t1.txt=\"ON\"");
Serial_SendHMILCD("CR.b1.txt=\"关闭\"");
Eload_Out = 1;
float Rtemp = YVF / RSET;
if (Rtemp > 10)
Rtemp = 10;
GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3
GPTM0_CH0_DisablePWMOutput(0); // VREF output low
}
else if (Eload_Out == 1) // Load output currently ON
{
Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
Serial_SendHMILCD("CR.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF output high
GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
}
}
else if (mode == CW) // Constant-power mode
{
if (Eload_Out == 0) // Load output currently OFF
{
Serial_SendHMILCD("CW.t1.txt=\"ON\"");
Serial_SendHMILCD("CW.b1.txt=\"关闭\"");
Eload_Out = 1;
float Ptemp = PSET / YVF;
if (Ptemp > 10)
Ptemp = 10;
GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // Enable PWM on CH2
GPTM0_CH3_EnablePWMOutput(); // Enable PWM on CH3
GPTM0_CH0_DisablePWMOutput(0); // VREF output low
}
else if (Eload_Out == 1) // Load output currently ON
{
Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
Serial_SendHMILCD("CW.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF output high
GPTM0_CH2_DisablePWMOutput(0); // IREF1 output low
GPTM0_CH3_DisablePWMOutput(0); // IREF2 output low
}
}
Key_ONOFF = 0;
key[0] = 0;
}
// https://blog.zeruns.com
if (Eload_Out == 0)
{
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, SET);
}
else if (Eload_Out == 1)
{
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_15, RESET);
}
}
/*Cooling-fan control*/
void FAN(void)
{
uint16_t P = (uint16_t)(YIF * YVF);
if (P >= 6) // Start fan when power ≥ 6 W
{
MCTM0_CH0_EnablePWMOutput(); // Turn on fan
if (P < 15)
{
MCTM0_CH0_SetOnduty(50); // 50 % duty cycle
}
else if (P >= 15 && P < 20)
{
MCTM0_CH0_SetOnduty(60);
}
else if (P >= 20 && P < 25)
{
MCTM0_CH0_SetOnduty(70);
}
else if (P >= 25 && P < 30)
{
MCTM0_CH0_SetOnduty(80);
}
else if (P >= 30 && P < 35)
{
MCTM0_CH0_SetOnduty(90);
}
else if (P >= 35)
{
MCTM0_CH0_SetOnduty(100);
}
}
else if (P <= 5)
{
MCTM0_CH0_DisablePWMOutput(0); // Disable PWM on MCTM0_CH0 (fan), output low
}
}
/*Constant-power mode*/
void CW_mode(void)
{
float Ptemp = PSET / YVF;
if (Ptemp > 10)
Ptemp = 10;
if (Eload_Out == 1)
{
GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
}
else
{
GPTM0_CH2_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VDD * 50000));
}
}
/*Constant-resistance mode*/
void CR_mode(void)
{
float Rtemp = YVF / RSET;
if (Rtemp > 10)
Rtemp = 10;
if (Eload_Out == 1)
{
GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
}
else
{
GPTM0_CH2_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
GPTM0_CH3_SetOnduty((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VDD * 50000));
}
}
// Key state-machine routine
void key_status_check(uint8_t key_num, uint8_t KEY)
{
switch (KEY_Status[key_num][g_keyStatus])
{
// Key released (initial state)
case KS_RELEASE:
{
// Detect low level, start debounce
if (KEY == 0)
{
KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
}
}
break;
// Debounce
case KS_SHAKE:
{
if (KEY == 1)
{
KEY_Status[key_num][g_keyStatus] = KS_RELEASE;
}
else
{
KEY_Status[key_num][g_keyStatus] = KS_PRESS;
}
}
break;
// Stable short press
case KS_PRESS:
{
// Detect high level, start debounce
if (KEY == 1)
{
KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
}
}
break;
default:
break;
}
if (KEY_Status[key_num][g_keyStatus] != KEY_Status[key_num][g_nowKeyStatus])
{
// Current state is released AND previous state was pressed
if ((KEY_Status[key_num][g_keyStatus] == KS_RELEASE) && (KEY_Status[key_num][g_lastKeyStatus] == KS_PRESS))
{
key[key_num] = 1;
}
KEY_Status[key_num][g_lastKeyStatus] = KEY_Status[key_num][g_nowKeyStatus];
KEY_Status[key_num][g_nowKeyStatus] = KEY_Status[key_num][g_keyStatus];
}
}
Other open-source projects recommended
- Built a 3-phase energy meter and open-sourced it, handy for home power monitoring: https://blog.zeruns.com/archives/771.html
- STM32F407 StdPeriph template with U8g2 graphics library already ported: https://blog.zeruns.com/archives/722.html
- Qinheng CH32V307VCT6 minimum system board open-sourced: https://blog.zeruns.com/archives/726.html
- LM25118 auto buck-boost adjustable DC-DC power module: https://blog.zeruns.com/archives/727.html
- EG1164 high-power synchronous-rectification boost module open-sourced, up to 97 % efficiency: https://blog.zeruns.com/archives/730.html
- 4G environment-monitoring node based on Air700E (temp, humidity, pressure, etc.) uploads to Alibaba Cloud IoT via MQTT: https://blog.zeruns.com/archives/747.html
Recommended reading- High Cost-Performance and Cheap VPS/Cloud Server Recommendations: https://blog.zeruns.com/archives/383.html
- Minecraft server setup tutorial: https://blog.zeruns.com/tag/mc/
- Build a blog site without coding! Ultra-detailed personal blog setup tutorial: https://blog.zeruns.com/archives/783.html
- Intranet penetration server setup tutorial, NPS installation and usage guide: https://blog.zeruns.com/archives/741.html
- Yuyun GPU cloud server setup tutorial for SD (Stable Diffusion), build your own AI painting website: https://blog.zeruns.com/archives/768.html
- Huawei Pura70Pro+ camera review, comparison with Mate40Pro: https://blog.zeruns.com/archives/782.html













