Открытый проект интеллектуальной электронной нагрузки на базе HT32F52352, работа на конкурс «合泰杯», включая схему, PCB, исходный код и отчёт.
Третье место на 10-м конкурсе студенческих проектов по применению микроконтроллеров «合泰杯» в Гуандуне, 2023 г.
Сделано в спешке за месяц (при полной загрузке парами), качество посредственное — не ругайте строго.
Демонстрационное видео: https://www.bilibili.com/video/BV1sM4y1b7qu/
Проект выложен в открытый доступ исключительно в образовательных целях; повторять не рекомендуется — на платформе 立创 есть более продвинутые и доработанные решения!
Ссылка на страницу проекта: https://url.zeruns.com/xvvF8
Инструкция по установке среды разработки для HT32: https://blog.zeruns.com/archives/709.html
Технический чат по электронике/МК: 2169025065
Что такое электронная нагрузка
Электронная нагрузка — это устройство, имитирующее реальную нагрузку для испытания источников питания или схем. В отличие от громоздких балластных резисторов или «печёк», она позволяет точно задавать параметры и удобна в эксплуатации. Незаменима как в профессиональной разработке, так и в любительской практике.
По типу питания различают переменную и постоянную нагрузки; по режиму работы — стабилизацию тока, напряжения, сопротивления и мощности. Для типичных стабилизированных БП наиболее востребована постоянная нагрузка в режиме стабильного тока. По способу управления бывают аналоговые и цифровые; последние проще настраивать, легко расширяются и позволяют автоматизировать измерения.
Кратко о проекте
Основа — микроконтроллер Holtek HT32F52352. Питание от аккумулятора 18650, компактность обеспечена.
Управление: МК формирует ШИМ → фильтр нижних частот (получаем стабильное DAC) → ОУ сравнивает опорное напряжение с усиленным сигналом датчиков тока/напряжения → выход ОУ управляет ключом MOS, реализуя стабилизацию тока/напряжения.
Сенсорный экран — 2,8" UART-дисплей TaoJingChi, модель TJC3224T028_011R.
Фото готового устройства
Снимков мало, больше — в видео-обзоре.
Скачать материалы
В архиве: схема, проект в 立创EDA, Gerber-файлы, исходники прошивки, проект экрана, даташиты.
123-диск: https://www.123pan.com/s/2Y9Djv-ZNevH.html
Baidu NetDisk: https://url.zeruns.com/XP241 код: i9bl
Где брать компоненты
- Плата-отладка HT32F52352: https://s.click.taobao.com/XmpTGpt
- Микросхема HT32F52352: https://s.click.taobao.com/sG9xeot
- INA199A1: https://s.click.taobao.com/XLuweot
- Набор резисторов 0805: https://s.click.taobao.com/p8YSGpt
- Набор конденсаторов 0805: https://u.jd.com/9uvZoBd
- XL6007E1: https://s.click.taobao.com/14Oueot
- UART-дисплей: https://s.click.taobao.com/pyzleot
Комплектующие удобнее всего покупать в 立创商城: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html
Схемы
Силовая плата
Плата питания
Контроллерная плата
PCB
Силовая плата
Top
Inner 1
Bottom
Слой GND не показан.
Плата питания
Top
Bottom
Контроллерная плата
Top
Bottom
Основной код
Файл 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)
// 定义按键状态的枚举变量类型
typedef enum
{
KS_RELEASE = 0, // 按键松开
KS_SHAKE, // 按键抖动
KS_PRESS, // 稳定按下
} KEY_STATUS;
// 当前循环结束的(状态机的)状态
#define g_keyStatus 0
// 当前状态(每次循环后与g_keyStatus保持一致)
#define g_nowKeyStatus 1
// 上次状态(用于记录前一状态以区分状态的来源)
#define g_lastKeyStatus 2
uint8_t KEY_Status[4][3]; // 记录各按键状态
uint8_t key[4]; // 记录各按键是否稳定按下,1表示按键已按下,0表示按键没被按下
char *temp;
```// Определение перечисления для страниц режимов
enum mode_type
{
menu = 0, // Меню
CC, // Постоянный ток
CV, // Постоянное напряжение
CR, // Постоянное сопротивление
CW // Постоянная мощность
};
uint8_t Eload_Out = 0; // Состояние включения/выключения электронной нагрузки
uint8_t mode = menu; // Текущий режим
uint8_t voltage_dw = 0; // Диапазон выборки напряжения: 0 — 0,0325×, 2 — 0,6175×, 1 — 0,0947×
float YVF, YIF1, YIF2, YIF; // Текущие напряжение и токи
float ISET, VSET, RSET, PSET; // Установки тока, напряжения, сопротивления и мощности
float VDD = 3.3; // Напряжение питания микроконтроллера
uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM; // Суммы для усреднения напряжения и токов
uint8_t AVG_count = 0; // Счётчик накопления для усреднения тока
uint8_t YVF_AVG_count = 0; // Счётчик накопления для усреднения напряжения
uint8_t Key_ONOFF = 0; // Флаг нажатия кнопки включения/выключения нагрузки
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); // Выключить
void ON(void); // Включить
int main(void)
{
GPIO_Configuration(); // Инициализация GPIO
BFTM0_Configuration(); // Инициализация таймера BFTM0
GPTM0_Configuration(); // Инициализация таймера GPTM0
GPTM1_Configuration(); // Инициализация таймера GPTM1
MCTM0_Configuration(); // Инициализация таймера MCTM0
WDT_Configuration(); // Инициализация сторожевого таймера
OLED_Init(); // Инициализация OLED
ADC_Configuration(); // Инициализация АЦП
RETARGET_Configuration(); // Перенаправление printf в UART
USART1_Configuration(); // Инициализация UART1
USART0_Configuration(); // Инициализация UART0
uint16_t count1 = 0;
Serial_SendHMILCD("page 0"); // Переключиться на стартовый экран
GPTM0_CH0_DisablePWMOutput(1); // Отключить ШИМ канала CH0 (постоянное напряжение), выдать высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // Отключить ШИМ канала CH2 (постоянный ток 1), выдать низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // Отключить ШИМ канала CH3 (постоянный ток 2), выдать низкий уровень
MCTM0_CH0_DisablePWMOutput(0); // Отключить ШИМ канала MCTM0_CH0 (вентилятор), выдать низкий уровень
Serial_SendHMILCD("CC.x0.val=0"); // Обнулить отображаемое значение тока на экране
Serial_SendHMILCD("CV.x0.val=0"); // Обнулить отображаемое значение напряжения на экране
Serial_SendHMILCD("CR.x0.val=0"); // Обнулить отображаемое значение сопротивления на экране
Serial_SendHMILCD("CW.x0.val=0"); // Обнулить отображаемое значение мощности на экране
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_10, RESET); // MCU_G0, коэффициент выборки напряжения 0,6175×
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_11, RESET); // MCU_G1, коэффициент выборки напряжения 0,0947×
while (1)
{
if (HT_CKCU->APBCCR1 & (1 << 4)) // Проверка, включён ли такт сторожевого таймера
WDT_Restart(); // Сброс счётчика сторожевого таймера
GPIO_WriteOutBits(HT_GPIOC, GPIO_PIN_14, RESET);
if (bftm0_ct3 >= 1) // Выполнять каждую 1 мс
{
AdcFb(); // Обработка данных АЦП
bftm0_ct3 = 0;
}
HMI_GetData();
if (bftm0_ct2 >= 40) // Выполнять каждые 40 мс
{
HMI_Display(); // Вывод данных на экран
bftm0_ct2 = 0;
}
ONOFF();
FAN();
if (bftm0_ct >= 50) // Выполнять каждые 50 мс
{
key_status_check(0, KEY1); // Опрос кнопок
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; // Преобразование значения АЦП в напряжение
OLED_ShowNum(1, 4, (uint8_t)Voltage0, 1); // Вывод целой части
OLED_ShowString(1, 5, ".");
OLED_ShowString(1, 9, "V");
OLED_ShowNum(1, 6, (uint16_t)(Voltage0 * 1000) % 1000, 3); // Вывод дробной части
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)//Обработка данных UART1
{
if(++cishu==7) {flag_over=1;}
Delay_ms(1);
}
if(flag_over==1)
{
flag_over=0;
if (Data[0] == 'A') // Текущая страница — цифровая клавиатура
{
char *temp = Data;
temp++; // Увеличить адрес на 1
uint16_t temp2 = atoi(temp); // Преобразовать строку в целое
// 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"); // Переключиться на страницу меню
mode = menu;
break;
case '5':
Serial_SendHMILCD("page CW"); // Переключиться на страницу постоянной мощности
mode = CW; // Установить режим постоянной мощности
break;
case '4':
Serial_SendHMILCD("page CR"); // Переключиться на страницу постоянного сопротивления
mode = CR; // Установить режим постоянного сопротивления
break;
case '3':
Serial_SendHMILCD("page CV"); // Переключиться на страницу постоянного напряжения
mode = CV; // Установить режим постоянного напряжения
break;
case '2':
Serial_SendHMILCD("page CC"); // Переключиться на страницу постоянного тока
mode = CC; // Установить режим постоянного тока
break;
case '1':
ON();//Включить
break;
case '0':
OFF();//Выключить
break;
default:
break;
}
cishu=0;
len=0;
flag_start=0;
}
}
}void ON(void)//включить
{
if (mode == CC) // постоянный ток
{
if (Eload_Out == 0) // если выход нагрузки сейчас выключен
{
Serial_SendHMILCD("CC.t1.txt=\"ON\""); // в правом верхнем углу экрана заголовок ON
Serial_SendHMILCD("CC.b1.txt=\"关闭\""); // в правом нижнем углу кнопка «关闭»
Eload_Out = 1; // выход нагрузки установлен в ON
if (ISET <= 2.5) // если заданный ток ≤ 2,5 А, использовать только один MOS
{
GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // включить ШИМ канала CH2(IREF1)
GPTM0_CH3_DisablePWMOutput(0); // выключить ШИМ канала CH3(IREF2), низкий уровень
}
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(); // включить ШИМ CH2
GPTM0_CH3_EnablePWMOutput(); // включить ШИМ CH3
}
GPTM0_CH0_DisablePWMOutput(0); // VREF – низкий уровень
}
}
else if (mode == CV) // постоянное напряжение
{
if (Eload_Out == 0) // если выход нагрузки сейчас выключен
{
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) // постоянное сопротивление
{
if (Eload_Out == 0) // если выход нагрузки сейчас выключен
{
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(); // включить ШИМ CH2
GPTM0_CH3_EnablePWMOutput(); // включить ШИМ CH3
GPTM0_CH0_DisablePWMOutput(0); // VREF – низкий уровень
}
}
else if (mode == CW) // постоянная мощность
{
if (Eload_Out == 0) // если выход нагрузки сейчас выключен
{
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(); // включить ШИМ CH2
GPTM0_CH3_EnablePWMOutput(); // включить ШИМ CH3
GPTM0_CH0_DisablePWMOutput(0); // VREF – низкий уровень
}
}
}
void OFF(void)//выключить
{
if (mode == CC) // постоянный ток
{
if (Eload_Out == 1) // если выход нагрузки сейчас включён
{
Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
Serial_SendHMILCD("CC.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF – высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // IREF1 – низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // IREF2 – низкий уровень
}
}
else if (mode == CV) // постоянное напряжение
{
if (Eload_Out == 1) // если выход нагрузки сейчас включён
{
Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
Serial_SendHMILCD("CV.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF – высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // IREF1 – низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // IREF2 – низкий уровень
}
}
else if (mode == CR) // постоянное сопротивление
{
if (Eload_Out == 1) // если выход нагрузки сейчас включён
{
Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
Serial_SendHMILCD("CR.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF – высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // IREF1 – низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // IREF2 – низкий уровень
}
}
else if (mode == CW) // постоянная мощность
{
if (Eload_Out == 1) // если выход нагрузки сейчас включён
{
Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
Serial_SendHMILCD("CW.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF – высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // IREF1 – низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // IREF2 – низкий уровень
}
}
}
/*обработка данных, пришедших с HMI-экрана*/
void HMI_GetData(void)
{
if (Serial_RxFlag == 1)
{
if (Serial_RxPacket[0] == 0x01) // текущая страница – главное меню
{
if (Serial_RxPacket[1] == 0x10) // нажата кнопка «постоянный ток»
{
Serial_SendHMILCD("page CC"); // перейти на страницу CC
mode = CC; // установить режим – постоянный ток
}
else if (Serial_RxPacket[1] == 0x11) // нажата кнопка «постоянное напряжение»
{
Serial_SendHMILCD("page CV"); // перейти на страницу CV
mode = CV; // установить режим – постоянное напряжение
}
else if (Serial_RxPacket[1] == 0x12) // нажата кнопка «постоянное сопротивление»
{
Serial_SendHMILCD("page CR"); // перейти на страницу CR
mode = CR; // установить режим – постоянное сопротивление
}
else if (Serial_RxPacket[1] == 0x13) // нажата кнопка «постоянная мощность»
{
Serial_SendHMILCD("page CW"); // перейти на страницу CW
mode = CW; // установить режим – постоянная мощность
}
}
else if (Serial_RxPacket[0] == 0x02) // текущая страница – постоянный ток
{
if (Serial_RxPacket[1] == 0x10) // нажата кнопка «меню»
{
Serial_SendHMILCD("CC.t1.txt=\"OFF\""); // заголовок – OFF
Serial_SendHMILCD("CC.b1.txt=\"开启\""); // кнопка – «开启»
Eload_Out = 0; // выход нагрузки – OFF
GPTM0_CH0_DisablePWMOutput(1); // выключить ШИМ CH0(пост. напряжение), высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // выключить ШИМ CH2(пост. ток 1), низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // выключить ШИМ CH3(пост. ток 2), низкий уровень
Serial_SendHMILCD("page menu"); // перейти в меню
mode = menu;
}
else if (Serial_RxPacket[1] == 0x11) // нажата кнопка «включить» и выход выключен
{
Key_ONOFF = 1;
}
}
else if (Serial_RxPacket[0] == 0x03) // текущая страница – постоянное напряжение
{
if (Serial_RxPacket[1] == 0x10) // нажата кнопка «меню»
{
Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
Serial_SendHMILCD("CV.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // выключить ШИМ CH0(пост. напряжение), высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // выключить ШИМ CH2(пост. ток 1), низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // выключить ШИМ CH3(пост. ток 2), низкий уровень
Serial_SendHMILCD("page menu");
mode = menu;
}
if (Serial_RxPacket[1] == 0x11) // нажата кнопка «включить» и выход выключен
{
Key_ONOFF = 1;
}
}
else if (Serial_RxPacket[0] == 0x04) // текущая страница – постоянное сопротивление
{
if (Serial_RxPacket[1] == 0x10) // нажата кнопка «меню»
{
Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
Serial_SendHMILCD("CR.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // выключить ШИМ CH0(пост. напряжение), высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // выключить ШИМ CH2(пост. ток 1), низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // выключить ШИМ CH3(пост. ток 2), низкий уровень
Serial_SendHMILCD("page menu");
mode = menu;
}
if (Serial_RxPacket[1] == 0x11) // нажата кнопка «включить» и выход выключен
{
Key_ONOFF = 1;
}
}
else if (Serial_RxPacket[0] == 0x05) // текущая страница – постоянная мощность
{
if (Serial_RxPacket[1] == 0x10) // нажата кнопка «меню»
{
Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
Serial_SendHMILCD("CW.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // выключить ШИМ CH0(пост. напряжение), высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // выключить ШИМ CH2(пост. ток 1), низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // выключить ШИМ CH3(пост. ток 2), низкий уровень
Serial_SendHMILCD("page menu");
mode = menu;
}
if (Serial_RxPacket[1] == 0x11) // нажата кнопка «включить» и выход выключен
{
Key_ONOFF = 1;
}
}
```c
else if (Serial_RxPacket[0] == 0xAA) // текущая страница — цифровая клавиатура
{
char *temp = Serial_RxPacket;
temp++; // адрес увеличен на 1
uint16_t temp2 = atoi(temp); // преобразование строки в целое
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;
}
}
/* отображение параметров на экране */
void HMI_Display(void)
{
if (mode != menu)
{
Serial_SendHMILCD("x1.val=%d", (uint16_t)(YVF * 100)); // напряжение
Serial_SendHMILCD("x2.val=%d", (uint16_t)(YIF * 1000)); // ток
Serial_SendHMILCD("x3.val=%d", (uint32_t)(YIF * YVF * 100)); // мощность
Serial_SendHMILCD("x4.val=%d", (uint32_t)(YVF / YIF * 100)); // сопротивление
}
}
/* обработка данных АЦП */
void AdcFb(void)
{
VDD = (AD_Value[4] & 0x0000FFFF) * 1.2482 / (AD_Value[3] & 0x0000FFFF); // преобразование отсчёта АЦП в напряжение
// VDD = (AD_Value[4] & 0x0000FFFF) / 4096.0*3.3; // преобразование отсчёта АЦП в напряжение
// VDD = 3.3;
if (voltage_dw == 0) // коэффициент деления напряжения 0.0325
{
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) // при напряжении ниже 32 В переключить делитель
{
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) // коэффициент деления напряжения 0.0947
{
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) // при напряжении ниже 5 В переключить делитель
{
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) // коэффициент деления напряжения 0.6175
{
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) // при напряжении выше 5.1 В переключить делитель
{
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); // накопление тока MOSFET 1
YIF2_SUM += (AD_Value[2] & 0x0000FFFF); // накопление тока 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;
}
```/*Кнопка включения/выключения электронной нагрузки*/
void ONOFF(void)
{
if (Key_ONOFF == 1 | key[0] == 1) // Нажата кнопка включения
{
if (mode == CC) // Постоянный ток
{
if (Eload_Out == 0) // Текущее состояние выхода нагрузки — выключено
{
Serial_SendHMILCD("CC.t1.txt=\"ON\""); // В правом верхнем углу экрана отображается ON
Serial_SendHMILCD("CC.b1.txt=\"关闭\""); // В правом нижнем углу экрана кнопка отображает «关闭»
Eload_Out = 1; // Установить состояние выхода нагрузки в «включено»
if (ISET <= 2.5) // Если установленное значение тока ≤ 2,5 А, использовать только один MOS-транзистор
{
GPTM0_CH2_SetOnduty((uint16_t)(ISET * 0.01 * 50 / VDD * 50000));
GPTM0_CH2_EnablePWMOutput(); // Включить ШИМ канала CH2 (IREF1)
GPTM0_CH3_DisablePWMOutput(0); // Выключить ШИМ канала CH3 (IREF2) и установить низкий уровень
}
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(); // Включить ШИМ канала CH2
GPTM0_CH3_EnablePWMOutput(); // Включить ШИМ канала CH3
}
GPTM0_CH0_DisablePWMOutput(0); // VREF — низкий уровень
}
else if (Eload_Out == 1) // Текущее состояние выхода нагрузки — включено
{
Serial_SendHMILCD("CC.t1.txt=\"OFF\"");
Serial_SendHMILCD("CC.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF — высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // IREF1 — низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // IREF2 — низкий уровень
}
}
else if (mode == CV) // Постоянное напряжение
{
if (Eload_Out == 0) // Текущее состояние выхода нагрузки — выключено
{
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) // Текущее состояние выхода нагрузки — включено
{
Serial_SendHMILCD("CV.t1.txt=\"OFF\"");
Serial_SendHMILCD("CV.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF — высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // IREF1 — низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // IREF2 — низкий уровень
}
}
else if (mode == CR) // Постоянное сопротивление
{
if (Eload_Out == 0) // Текущее состояние выхода нагрузки — выключено
{
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(); // Включить ШИМ канала CH2
GPTM0_CH3_EnablePWMOutput(); // Включить ШИМ канала CH3
GPTM0_CH0_DisablePWMOutput(0); // VREF — низкий уровень
}
else if (Eload_Out == 1) // Текущее состояние выхода нагрузки — включено
{
Serial_SendHMILCD("CR.t1.txt=\"OFF\"");
Serial_SendHMILCD("CR.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF — высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // IREF1 — низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // IREF2 — низкий уровень
}
}
else if (mode == CW) // Постоянная мощность
{
if (Eload_Out == 0) // Текущее состояние выхода нагрузки — выключено
{
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(); // Включить ШИМ канала CH2
GPTM0_CH3_EnablePWMOutput(); // Включить ШИМ канала CH3
GPTM0_CH0_DisablePWMOutput(0); // VREF — низкий уровень
}
else if (Eload_Out == 1) // Текущее состояние выхода нагрузки — включено
{
Serial_SendHMILCD("CW.t1.txt=\"OFF\"");
Serial_SendHMILCD("CW.b1.txt=\"开启\"");
Eload_Out = 0;
GPTM0_CH0_DisablePWMOutput(1); // VREF — высокий уровень
GPTM0_CH2_DisablePWMOutput(0); // IREF1 — низкий уровень
GPTM0_CH3_DisablePWMOutput(0); // IREF2 — низкий уровень
}
}
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);
}
}
/*Управление вентилятором охлаждения*/
void FAN(void)
{
uint16_t P = (uint16_t)(YIF * YVF);
if (P >= 6) // Вентилятор запускается при мощности ≥ 6 Вт
{
MCTM0_CH0_EnablePWMOutput(); // Включить вентилятор
if (P < 15)
{
MCTM0_CH0_SetOnduty(50); // Коэффициент заполнения ШИМ 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); // Выключить ШИМ канала MCTM0_CH0 (вентилятор) и установить низкий уровень
}
}
/*Режим постоянной мощности*/
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));
}
}
/*Режим постоянного сопротивления*/
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));
}
}
// Программа конечного автомата кнопок
void key_status_check(uint8_t key_num, uint8_t KEY)
{
switch (KEY_Status[key_num][g_keyStatus])
{
// Кнопка отпущена (исходное состояние)
case KS_RELEASE:
{
// Обнаружен низкий уровень — начать подавление дребезга
if (KEY == 0)
{
KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
}
}
break;
// Дребезг
case KS_SHAKE:
{
if (KEY == 1)
{
KEY_Status[key_num][g_keyStatus] = KS_RELEASE;
}
else
{
KEY_Status[key_num][g_keyStatus] = KS_PRESS;
}
}
break;
// Стабильное короткое нажатие
case KS_PRESS:
{
// Обнаружен высокий уровень — начать подавление дребезга
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])
{
// Текущее состояние — отпущено, предыдущее — нажато
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];
}
}
Другие рекомендуемые проекты с открытым исходным кодом
- Сделал трёхфазный измеритель электроэнергии и открыл исходники — удобно следить за потреблением дома: https://blog.zeruns.com/archives/771.html
- Шаблон проекта STM32F407 на стандартной библиотеке с портированной графической библиотекой U8g2: https://blog.zeruns.com/archives/722.html
- Минимальная плата для CH32V307VCT6 от 沁恒 с открытым железом: https://blog.zeruns.com/archives/726.html
- Автоматический повышающе-понижающий DC-DC на LM25118 с регулировкой: https://blog.zeruns.com/archives/727.html
- Синхронный повышающий модуль на EG1164 мощностью до 97 % КПД, исходники открыты: https://blog.zeruns.com/archives/730.html
- 4G-узел мониторинга окружающей среды (температура, влажность, давление и др.) на базе Air700E, выгрузка в Alibaba Cloud через MQTT: https://blog.zeruns.com/archives/747.html
Рекомендуем к прочтению- Рекомендации по VPS/облачным серверам с высокой производительностью и низкой ценой: https://blog.zeruns.com/archives/383.html
- Руководство по созданию сервера Minecraft: https://blog.zeruns.com/tag/mc/
- Создание блога без кода! Подробное руководство по созданию персонального блога: https://blog.zeruns.com/archives/783.html
- Руководство по настройке сервера для проксирования в локальную сеть, установка и использование NPS: https://blog.zeruns.com/archives/741.html
- Руководство по настройке SD (Stable Diffusion) на GPU-сервере Yun, создание собственного сайта для рисования с ИИ: https://blog.zeruns.com/archives/768.html
- Обзор камеры Huawei Pura70Pro+ в сравнении с Mate40Pro: https://blog.zeruns.com/archives/782.html













