Открытый проект интеллектуальной электронной нагрузки на базе CH32V307VCT6, работа-участник конкурса встраиваемых систем, выложена схема, плата, исходники и отчёт.
2023, прикладная ветка конкурса «Встраиваемые микросхемы и системное проектирование», диплом II степени.
За полмесяца переделал ранее собранную версию на HT32, перенёс прошивку на CH32 и добавил RT-Thread, немного оптимизировал код и успел закончить к дедлайну. Сделано на скорую руку, не ругайте.
Демо-видео: https://www.bilibili.com/video/BV1Zu4y1m7Zd/
Проект на HT32F52352 (Holtek Cup) тут: https://blog.zeruns.com/archives/784.html
Материалы выложены только для обучения, самому собирать не рекомендую — на платформе LCSC есть куда более продуманные открытые электронные нагрузки!
Ссылка на проект в LCSC Open Source: https://url.zeruns.com/Et4x4
Технический чат по электронике/МК: 2169025065
На сайте WCH можно бесплатно заказать образцы плат: https://url.zeruns.com/h9a99
Что такое электронная нагрузка
Электронная нагрузка — это устройство, имитирующее реальную нагрузку для испытания источников питания и схем. В отличие от громоздких балластных резисторов или «печёк», она даёт точную, быструю и удобную настройку параметров. Это must-have и для инженеров, и для радиолюбителей.
По типу питания бывают переменные и постоянные; по режимам — стабилизация по току, напряжению, сопротивлению и мощности. Поскольку большинство блоков питания — стабилизированные постоянного напряжения, чаще всего используется именно постоянная токовая нагрузка. По способу управления различают аналоговые и цифровые (数控) устройства; последние проще в настройке, функциональнее и легко интегрируются в автоматизированные стенды.
Описание проекта
Головной микроконтроллер — CH32V307VCT6 (WCH). Питание от аккумулятора 18650, компактно и портативно.
Принцип: ЦАП МК выдаёт опорное напряжение; усилитель сравнивает его с напряжением, пропорциональным току/напряжению на входе, и управляет ключом MOS. Так реализуются режимы стабилизации тока и напряжения.
Сенсорный экран — 2,8" UART-дисплей Taojingchi TJC3224T028_011R.
Радиатор — боковой 2U-серверный под LGA-1356/1366.
Разработка прошивки — RT-Thread Studio, схема и плата — EasyEDA.
Максимальные параметры: 100 V, 10 A, 200 Вт.
Фото готового устройства
Фотографий мало, смотрите демо-видео.
Скачать файлы
В архиве: схема, проект EasyEDA, Gerber, исходники, прошивка экрана, даташиты.
123 Pan (без ограничения скорости): https://www.123pan.com/ps/2Y9Djv-6NevH.html
Baidu Pan: https://pan.baidu.com/s/17YSlBZ6F1M18k7JGa7FlVA?pwd=buxx код: buxx
Где купить компоненты
- CH32V307VCT6: https://s.click.taobao.com/T8MSZot
- Девелопмент-боард CH32V307: https://s.click.taobao.com/2JBSZot
- INA199A1: https://s.click.taobao.com/XLuweot
- Набор SMD-резисторов 0805: https://s.click.taobao.com/p8YSGpt
- Набор SMD-конденсаторов 0805: https://u.jd.com/9uvZoBd
- XL1509: https://s.click.taobao.com/DOcRZot
- UART-дисплей: https://s.click.taobao.com/pyzleot
Компоненты удобнее всего брать в LCSC: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html
Схема
Силовая плата
Плата питания
Контроллер
PCB
Силовая плата
Top
Bottom
Плата питания
Top
Bottom
Контроллер
Top
Bottom
Другие открытые проекты- Сделал открытый трёхфазный измеритель электроэнергии, удобно отслеживать домашнее потребление: https://blog.zeruns.com/archives/771.html
- Портированный шаблон проекта STM32F407 на стандартной библиотеке с графической библиотекой U8g2: https://blog.zeruns.com/archives/722.html
- Открытая минимальная плата системы на базе Qinheng 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 IoT через MQTT: https://blog.zeruns.com/archives/747.html
Основной код
Файл main.c
/********************************** (C) COPYRIGHT *******************************
* File Name : main.c
* Author : WCH
* Version : V1.0.0
* Date : 2021/06/06
* Description : Main program body.
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* SPDX-License-Identifier: Apache-2.0
* https://blog.zeruns.com
*******************************************************************************/
#include "ch32v30x.h"
#include<rtthread.h>
#include <rtdevice.h>
#include <stdlib.h>
#include <rthw.h>
#include "drivers/pin.h"
#include <board.h>
#include <rtdbg.h>
#include <u8g2_port.h>
#include <qpid.h>
#include "USART.h"
#include "KEY.h"
#include "DAC.h"
#include "PWM.h"
/* Глобальный typedef */
/* Глобальное определение */
#define WDT_DEVICE_NAME "wdt" /* Имя устройства сторожевого таймера */
static rt_device_t wdg_dev; /* Дескриптор устройства сторожевого таймера */
/* Опорное напряжение АЦП */
#define VREF 3.3
/* Напряжение питания */
#define VCC 3.3
/* Данные калибровки компенсации */
#define V0_COMP 1.000 // Коэффициент 0.0325 напряжения
#define V1_COMP 1.000 // Коэффициент 0.0947 напряжения
#define V2_COMP 1.000 // Коэффициент 0.6175 напряжения
#define YIF1_COMP 1.00 // Компенсация тока датчика MOS 1
#define YIF2_COMP 1.00 // Компенсация тока датчика MOS 2
#define DAC1_COMP 1.00 // Коэффициент выхода DAC1(VREF)
#define IREF2_COMP 1.00 // Коэффициент выхода IREF2
#define DAC2_COMP 1.00 // Компенсация выхода DAC2(IREF1)
/* Количество усреднений выборок АЦП */
#define ADC_count 3
/* Коэффициент фильтрации ФНЧ первого порядка */
#define dPower1 0.5
/* Номера выводов, определяются через drv_gpio.c */
#define OLED_I2C_PIN_SCL 22 //PB6
#define OLED_I2C_PIN_SDA 23 //PB7
#define LED2 59 //PD11
#define LED1 60 //PD12
#define MCU_G0 62 //PD14
#define MCU_G1 63 //PD14
/* Глобальные переменные */
u8g2_t u8g2; // Структура u8g2
rt_uint16_t AD_Value[4]; // Данные выборок АЦП
// Перечисление режимов
enum mode_type
{
menu = 0, // Меню
CC, // Постоянный ток
CV, // Постоянное напряжение
CR, // Постоянное сопротивление
CW // Постоянная мощность
};
volatile uint8_t Eload_Out = 0; // Состояние выхода электронной нагрузки
volatile uint8_t mode = menu; // Текущий режим
volatile uint8_t voltage_dw = 0; // Диапазон выборки напряжения: 0=0.0325×, 2=0.6175×, 1=0.0947×
volatile double YVF, YIF1, YIF2, YIF, VBAT; // Текущие напряжение и ток
volatile double ISET, VSET, RSET, PSET; // Установки тока, напряжения, сопротивления, мощности
volatile uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM, VBAT_SUM; // Суммы для усреднения
volatile uint8_t AVG_count = 0; // Счётчик усреднения тока
volatile uint8_t YVF_AVG_count = 0; // Счётчик усреднения напряжения
volatile uint8_t VBAT_count = 0; // Счётчик усреднения напряжения батареи
volatile uint8_t Key_ONOFF = 0; // Флаг нажатия кнопки вкл/выкл
static qpid_t qpid_CC; // Указатель данных ПИД-регулятора
static qpid_t qpid_CV;
static qpid_t qpid_CR;
static qpid_t qpid_CW;
static double I_SET, V_SET, R_SET, P_SET;
/* Прототипы функций */
void OLED_Init(void);
static int IWDG_Init();
static void thread1_sysLED_entry(void *parameter);
static void thread2_OLED_entry(void *parameter);
static void thread3_ADC_entry(void *parameter);
static void thread4_HMI_GetDate_entry(void *parameter);
static void thread5_ONOFF_entry(void *parameter);
static void thread7_HMI_Display_entry(void *parameter);
static void thread8_FAN_entry(void *parameter);
static void thread9_CWCR_entry(void *parameter);
static void thread10_BlueTooth_entry(void *parameter);
void CW_mode(void);
void CR_mode(void);
void key123(void);
void Thread_Init(void);
void SYS_Init(void);
void PID(void);
/*********************************************************************
* @fn main
*
* @brief Главная программа.
*
* @return none
*/
int main(void)
{
rt_kprintf("MCU: CH32V307\n");
rt_kprintf("SysClk: %dHz\n", SystemCoreClock);
SYS_Init();
while (1)
{
// Защита по току и мощности
if (YIF > 10 | YVF * YIF > 300)
{
if (Eload_Out == 1)
{
Key_ONOFF = 1;
}
}
rt_thread_mdelay(20);
}
}
/* Инициализация системы */
void SYS_Init(void)
{
IWDG_Init(); // Инициализировать сторожевой таймер
UART_Init(); // Инициализировать UART3
HMILCD_Send("page 0"); // Перейти на стартовую страницу
Dac_Init(); // Инициализировать ЦАП
PWM_Init(); // Инициализировать ШИМ
rt_pin_mode(MCU_G0, PIN_MODE_OUTPUT); // Настроить пин на выход
rt_pin_mode(MCU_G1, PIN_MODE_OUTPUT);
rt_pin_write(MCU_G0, PIN_LOW); // Установить низкий уровень
rt_pin_write(MCU_G1, PIN_LOW);
Thread_Init(); // Создать потоки
}
/* Инициализация потоков */
void Thread_Init(void)
{
rt_thread_t tid = NULL; //Указатель блока управления потоком
/* Создать поток */
tid = rt_thread_create("SYS_LED", thread1_sysLED_entry, NULL, 512, 30, 5);
//Создать поток SYS_LED, функция входа thread1_sysLED_entry, параметр NULL, стек 256 байт, приоритет 30, квант 5 тиков
if (tid != RT_NULL) // Проверка успешности создания
{
if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
LOG_D("thread sysLED create success");
}
else
{
LOG_E("thread1 sysLED create failed...");
}
/*
tid = rt_thread_create("OLED_Display", thread2_OLED_entry, NULL, 2048, 25, 30);
if (tid != RT_NULL)
{
if (rt_thread_startup(tid) == RT_EOK) // 启动线程
LOG_D("thread2 OLED create success");
}
else
{
LOG_E("thread2_OLED create failed...");
}*/
tid = rt_thread_create("ADC", thread3_ADC_entry, NULL, 1536, 18, 30);
if (tid != RT_NULL)
{
if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
LOG_D("thread3 ADC create success");
}
else
{
LOG_E("thread3 ADC create failed...");
}
tid = rt_thread_create("HMI_GetDate", thread4_HMI_GetDate_entry, NULL, 2048, 23, 30);
if (tid != RT_NULL)
{
if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
LOG_D("thread4 HMI_GetDate create success");
}
else
{
LOG_E("thread4 HMI_GetDate create failed...");
}
tid = rt_thread_create("ONOFF", thread5_ONOFF_entry, NULL, 1024, 15, 25);
if (tid != RT_NULL)
{
if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
LOG_D("thread5 ONOFF create success");
}
else
{
LOG_E("thread5 ONOFF create failed...");
}
tid = rt_thread_create("KEY", thread6_KEY_entry, NULL, 512, 20, 20);
if (tid != RT_NULL)
{
if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
LOG_D("thread6 KEY create success");
}
else
{
LOG_E("thread6 KEY create failed...");
}
tid = rt_thread_create("HMI_Display", thread7_HMI_Display_entry, NULL, 1024, 25, 30);
if (tid != RT_NULL)
{
if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
LOG_D("thread7 HMI_Display create success");
}
else
{
LOG_E("thread7 HMI_Display create failed...");
}
tid = rt_thread_create("FAN", thread8_FAN_entry, NULL, 512, 26, 15);
if (tid != RT_NULL)
{
if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
LOG_D("thread8 FAN create success");
}
else
{
LOG_E("thread8 FAN create failed...");
}
tid = rt_thread_create("CWCR", thread9_CWCR_entry, NULL, 512, 19, 15);
if (tid != RT_NULL)
{
if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
LOG_D("thread9 CWCR create success");
}
else
{
LOG_E("thread9 CWCR create failed...");
}
tid = rt_thread_create("BlueTooth", thread10_BlueTooth_entry, NULL, 2048, 23, 30);
if (tid != RT_NULL)
{
if (rt_thread_startup(tid) == RT_EOK) // Запустить поток
LOG_D("thread10 BlueTooth create success");
}
else
{
LOG_E("thread10 BlueTooth create failed...");
}
}/* Первичный фильтр нижних частот
* Возвращаемое значение: iData — отфильтрованное по первому порядку значение выборки */
double lowV1(double com1)
{
static double iLastData1; // предыдущее значение
double iData1; // текущее вычисленное значение
iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData1; // вычисление
iLastData1 = iData1; // сохранение текущих данных
return iData1; // возврат данных
}
double lowV2(double com1)
{
static double iLastData2; // предыдущее значение
double iData1; // текущее вычисленное значение
iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData2; // вычисление
iLastData2 = iData1; // сохранение текущих данных
return iData1; // возврат данных
}
u16 lowV3(u16 com1)
{
static u16 iLastData3; // предыдущее значение
u16 iData1; // текущее вычисленное значение
iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData3; // вычисление
iLastData3 = iData1; // сохранение текущих данных
return iData1; // возврат данных
}
u16 lowV4(u16 com1)
{
static u16 iLastData3; // предыдущее значение
u16 iData1; // текущее вычисленное значение
iData1 = (com1 * 0.1) + (1 - 0.1) * iLastData3; // вычисление
iLastData3 = iData1; // сохранение текущих данных
return iData1; // возврат данных
}
static void idle_hook(void)
{
/* в callback-функции потока простоя «подкармливаем» сторожевой таймер */
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
//rt_kprintf("feed the dog!\n ");
}
static int IWDG_Init()
{
rt_err_t ret = RT_EOK;
rt_uint32_t timeout = 1; /* время переполнения, секунды */
/* ищем сторожевой таймер по имени устройства, получаем дескриптор */
wdg_dev = rt_device_find(WDT_DEVICE_NAME);
if (!wdg_dev)
{
rt_kprintf("find %s failed!\n", WDT_DEVICE_NAME);
return RT_ERROR;
}
/* инициализируем устройство */
rt_device_init(wdg_dev);
/* устанавливаем время переполнения сторожевого таймера */
ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
if (ret != RT_EOK)
{
rt_kprintf("set %s timeout failed!\n", WDT_DEVICE_NAME);
return RT_ERROR;
}
/* запускаем сторожевой таймер */
ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_START, RT_NULL);
if (ret != RT_EOK)
{
rt_kprintf("start %s failed!\n", WDT_DEVICE_NAME);
return -RT_ERROR;
}
/* устанавливаем callback-функцию потока простоя */
rt_thread_idle_sethook(idle_hook);
return ret;
}
/* функция потока 1: мигание системного светодиода */
static void thread1_sysLED_entry(void *parameter)
{
/* пин LED1 в режиме выхода */
rt_pin_mode(LED1, PIN_MODE_OUTPUT);
/* по умолчанию низкий уровень */
rt_pin_write(LED1, PIN_LOW);
while (1)
{
/* поток 1 работает с низким приоритетом, постоянно мигает LED1 */
rt_pin_write(LED1, !rt_pin_read(LED1));
rt_thread_mdelay(500);
}
}
/* функция потока 2: вывод информации на OLED-дисплей */
static void thread2_OLED_entry(void *parameter)
{
// инициализация
u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_rtthread_hw_i2c, u8x8_gpio_and_delay_rtthread);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
while (1)
{
char String[26];
u8g2_ClearBuffer(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_wqy15_t_chinese3); // китайский набор символов
float V0 = AD_Value[0] * VREF / 4096.0;
sprintf(String, "AD0:%d V:%d.%d%d%d", AD_Value[0], (uint8_t) V0, (uint16_t)(V0 * 10.0) % 10,
(uint16_t)(V0 * 100.0) % 100 % 10, (uint16_t)(V0 * 1000.0) % 1000 % 100 % 10); // форматирование
u8g2_DrawStr(&u8g2, 0, 15, String);
float V1 = AD_Value[1] * VREF / 4096.0;
sprintf(String, "AD1:%d V:%d.%d%d%d", AD_Value[1], (uint8_t) V1, (uint16_t)(V1 * 10.0) % 10,
(uint16_t)(V1 * 100.0) % 100 % 10, (uint16_t)(V1 * 1000.0) % 1000 % 100 % 10); // форматирование
u8g2_DrawStr(&u8g2, 0, 31, String);
float V2 = AD_Value[2] * VREF / 4096.0;
sprintf(String, "AD2:%d V:%d.%d%d%d", AD_Value[2], (uint8_t) V2, (uint16_t)(V2 * 10.0) % 10,
(uint16_t)(V2 * 100.0) % 100 % 10, (uint16_t)(V2 * 1000.0) % 1000 % 100 % 10); // форматирование
u8g2_DrawStr(&u8g2, 0, 47, String);
float V3 = AD_Value[3] * VREF / 4096.0;
sprintf(String, "AD3:%d V:%d.%d%d%d", AD_Value[3], (uint8_t) V3, (uint16_t)(V3 * 10.0) % 10,
(uint16_t)(V3 * 100.0) % 100 % 10, (uint16_t)(V3 * 1000.0) % 1000 % 100 % 10); // форматирование
u8g2_DrawStr(&u8g2, 0, 63, String);
u8g2_SendBuffer(&u8g2); // отправка буфера
rt_thread_mdelay(100); // задержка 100 мс
}
}
/* функция потока 3: обработка данных АЦП */
static void thread3_ADC_entry(void *parameter)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // тактирование GPIOA и ADC
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // тактирование DMA1
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // делитель ADC 6 (72 МГц/6=12 МГц), частота ≤14 МГц
GPIO_InitTypeDef GPIO_InitStructure = { 0 }; // структура для GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; // выводы
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // аналоговый вход
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // канал 0, 239.5 циклов
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); // канал 1
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); // канал 2
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_239Cycles5); // канал 3
ADC_InitTypeDef ADC_InitStructure = { 0 };
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // независимый режим
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // сканирование включено
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // непрерывное преобразование
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // программный запуск
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // выравнивание вправо
ADC_InitStructure.ADC_NbrOfChannel = 4; // 4 канала
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE); // включить ADC1
ADC_ResetCalibration(ADC1); // сброс калибровки
while (ADC_GetResetCalibrationStatus(ADC1))
; // ждём окончания сброса
ADC_StartCalibration(ADC1); // начать калибровку
while (ADC_GetCalibrationStatus(ADC1))
; // ждём окончания
DMA_DeInit(DMA1_Channel1); // сброс DMA
DMA_InitTypeDef DMA_InitStructure; // структура DMA
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) &ADC1->RDATAR; // адрес регистра данных ADC
DMA_InitStructure.DMA_MemoryBaseAddr = (u32) AD_Value; // адрес памяти
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // источник — периферия
DMA_InitStructure.DMA_BufferSize = 4; // размер буфера
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // без инкремента периферии
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // инкремент памяти
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 16 бит
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 16 бит
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // циклический режим
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // высокий приоритет
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // mem-to-mem выключен
DMA_Init(DMA1_Channel1, &DMA_InitStructure); // инициализация канала 1 DMA1
DMA_Cmd(DMA1_Channel1, ENABLE); // запустить DMA1_Channel1
ADC_DMACmd(ADC1, ENABLE); // разрешить DMA-запросы ADC
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // программный старт преобразования
qpid_init(&qpid_CC); // инициализация PID-регулятора
qpid_set_lmt(&qpid_CC, 0, 10); // установка ограничений
qpid_set_ratio(&qpid_CC, 1, 0.001, 0.1); // коэффициенты
qpid_init(&qpid_CV); // инициализация PID-регулятора
qpid_set_lmt(&qpid_CV, 31.0, 100); // установка ограничений
qpid_set_ratio(&qpid_CV, 0.35, 0.005, 0.001); // коэффициенты
}qpid_init(&qpid_CW); // инициализация данных ПИД-регулирования
qpid_set_lmt(&qpid_CW, 0.1, 200); // установка пределов ПИД
qpid_set_ratio(&qpid_CW, 0.5, 0.003, 0.001); // установка коэффициентов управления
qpid_init(&qpid_CR); // инициализация данных ПИД-регулирования
qpid_set_lmt(&qpid_CR, 0.1, 1000); // установка пределов ПИД
qpid_set_ratio(&qpid_CR, 0.35, 0.003, 0.001); // установка коэффициентов управления
while (1)
{
/*rt_kprintf("AD0:%d V:%f\n", AD_Value[0], AD_Value[0] / 4095.0 * VREF);
rt_kprintf("AD1:%d V:%4.3f\n", AD_Value[1], (float) AD_Value[1] / 4095.0 * VREF);
rt_kprintf("AD2:%d V:%4.3f\n", AD_Value[2], AD_Value[2] / 4095.0 * VREF);*/
if (voltage_dw == 0) // коэффициент деления напряжения 0.0325
{
if (YVF_AVG_count < ADC_count) // при значении меньше 15 накопление выборок
{
YVF_SUM += AD_Value[0];
YVF_AVG_count++;
}
if (YVF_AVG_count == ADC_count)
{
YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.0325 * V0_COMP; // расчёт напряжения
YVF_AVG_count = 0;
YVF_SUM = 0;
}
if (YVF <= 31.0) // при напряжении ниже 31 В переключение диапазона
{
rt_pin_write(MCU_G0, PIN_LOW);
rt_pin_write(MCU_G1, PIN_HIGH); // делитель 0.0947
if (Eload_Out == 1)
{
DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // установка выхода DAC1, управление постоянным напряжением
}
voltage_dw = 1;
YVF_AVG_count = 0;
YVF_SUM = 0;
qpid_set_lmt(&qpid_CV, 4.6, 33.5); // установка пределов ПИД
qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005); // установка коэффициентов управления
}
}
else if (voltage_dw == 1) // делитель 0.0947
{
if (YVF_AVG_count < ADC_count)
{
YVF_SUM += AD_Value[0];
YVF_AVG_count++;
}
if (YVF_AVG_count == ADC_count)
{
YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.0947 * V1_COMP;
YVF_AVG_count = 0;
YVF_SUM = 0;
}
if (YVF >= 33.5) // при напряжении выше 33.5 В переключение на 0.0325
{
rt_pin_write(MCU_G0, PIN_LOW);
rt_pin_write(MCU_G1, PIN_LOW);
if (Eload_Out == 1)
{
DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // установка выхода DAC1, управление постоянным напряжением
}
voltage_dw = 0;
YVF_AVG_count = 0;
YVF_SUM = 0;
qpid_set_lmt(&qpid_CV, 31.0, 100); // установка пределов ПИД
qpid_set_ratio(&qpid_CV, 0.5, 0.005, 0.0005); // установка коэффициентов управления
}
else if (YVF <= 4.6) // при напряжении ниже 4.6 В переключение диапазона
{
rt_pin_write(MCU_G0, PIN_HIGH);
rt_pin_write(MCU_G1, PIN_LOW);
if (Eload_Out == 1)
{
uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
if (vset_pwm > 4065)
vset_pwm = 4095;
DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // установка выхода DAC1, управление постоянным напряжением
}
voltage_dw = 2;
YVF_AVG_count = 0;
YVF_SUM = 0;
qpid_set_lmt(&qpid_CV, 0.01, 5.1); // установка пределов ПИД
qpid_set_ratio(&qpid_CV, 0.26, 0.0025, 0.0005); // установка коэффициентов управления
}
}
else if (voltage_dw == 2) // делитель 0.6175
{
if (YVF_AVG_count < ADC_count)
{
YVF_SUM += AD_Value[0];
YVF_AVG_count++;
}
if (YVF_AVG_count == ADC_count)
{
YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.6175 * V2_COMP;
if (YVF < 0.15)
YVF = 0;
YVF_AVG_count = 0;
YVF_SUM = 0;
}
if (YVF >= 5.1) // при напряжении выше 5.1 В переключение диапазона
{
rt_pin_write(MCU_G0, PIN_LOW);
rt_pin_write(MCU_G1, PIN_HIGH);
if (Eload_Out == 1)
{
DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // установка выхода DAC1, управление постоянным напряжением
}
voltage_dw = 1;
YVF_AVG_count = 0;
YVF_SUM = 0;
qpid_set_lmt(&qpid_CV, 4.6, 33.5); // установка пределов ПИД
qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005); // установка коэффициентов управления
}
}
if (AVG_count < ADC_count)
{
YIF1_SUM += AD_Value[1]; // накопление тока MOS1
YIF2_SUM += AD_Value[2]; // накопление тока MOS2
AVG_count++;
}
if (AVG_count == ADC_count)
{
YIF1 = YIF1_SUM / AVG_count * VREF / 4096.0 / 50 / 0.01 * YIF1_COMP - 0.007;
YIF2 = YIF2_SUM / AVG_count * VREF / 4096.0 / 50 / 0.01 * YIF2_COMP - 0.007;
YIF = YIF1 + YIF2;
if (YIF < 0.008)
YIF = 0;
AVG_count = 0;
YIF1_SUM = 0;
YIF2_SUM = 0;
PID();
}
if (VBAT_count < 5)
{
VBAT_SUM += lowV4(AD_Value[3]); // накопление выборок напряжения батареи
VBAT_count++;
}
if (VBAT_count == 5)
{
VBAT = VBAT_SUM / VBAT_count * VREF / 4096.0 / 0.3535;
VBAT_count = 0;
VBAT_SUM = 0;
}
rt_thread_mdelay(5);
}
}
// https://blog.zeruns.com/* Функция входа потока 4, обрабатывающая данные, присланные с экрана */
static void thread4_HMI_GetDate_entry(void *parameter)
{
HMILCD_Send("CC.x0.val=0"); // Сброс отображаемого на экране заданного значения тока
HMILCD_Send("CV.x0.val=0"); // Сброс отображаемого на экране заданного значения напряжения
HMILCD_Send("CR.x0.val=0"); // Сброс отображаемого на экране заданного значения сопротивления
HMILCD_Send("CW.x0.val=0"); // Сброс отображаемого на экране заданного значения мощности
while (1)
{
if (Serial3_RxFlag == 1)
{
if (Serial3_RxPacket[0] == 0x01) // Текущая страница — главное меню
{
if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка постоянного тока
{
HMILCD_Send("page CC"); // Переключиться на страницу режима постоянного тока
mode = CC; // Установить текущий режим — постоянный ток
}
else if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка постоянного напряжения
{
HMILCD_Send("page CV"); // Переключиться на страницу постоянного напряжения
mode = CV; // Установить текущий режим — постоянное напряжение
}
else if (Serial3_RxPacket[1] == 0x12) // Нажата кнопка постоянного сопротивления
{
HMILCD_Send("page CR"); // Переключиться на страницу постоянного сопротивления
mode = CR; // Установить текущий режим — постоянное сопротивление
}
else if (Serial3_RxPacket[1] == 0x13) // Нажата кнопка постоянной мощности
{
HMILCD_Send("page CW"); // Переключиться на страницу постоянной мощности
mode = CW; // Установить текущий режим — постоянная мощность
}
}
else if (Serial3_RxPacket[0] == 0x02) // Текущая страница — режим постоянного тока
{
if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка меню
{
HMILCD_Send("CC.t1.txt=\"OFF\""); // В правом верхнем углу экрана отобразить OFF
HMILCD_Send("CC.b1.txt=\"开启\""); // В правом нижнем углу экрана кнопка покажет «开启»
Eload_Out = 0; // Состояние выхода нагрузки — выключено
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключает постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключает постоянный ток
PWM_SetCCR4(0); // Установить IREF2
HMILCD_Send("page menu"); // Переключиться на страницу меню
mode = menu;
}
else if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка включения и текущее состояние выхода — выключено
{
Key_ONOFF = 1;
}
}
else if (Serial3_RxPacket[0] == 0x03) // Текущая страница — режим постоянного напряжения
{
if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка меню
{
HMILCD_Send("CV.t1.txt=\"OFF\"");
HMILCD_Send("CV.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключает постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключает постоянный ток
PWM_SetCCR4(0); // Установить IREF2
HMILCD_Send("page menu");
mode = menu;
}
if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка включения и текущее состояние выхода — выключено
{
Key_ONOFF = 1;
}
}
else if (Serial3_RxPacket[0] == 0x04) // Текущая страница — режим постоянного сопротивления
{
if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка меню
{
HMILCD_Send("CR.t1.txt=\"OFF\"");
HMILCD_Send("CR.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключает постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключает постоянный ток
PWM_SetCCR4(0); // Установить IREF2
HMILCD_Send("page menu");
mode = menu;
}
if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка включения и текущее состояние выхода — выключено
{
Key_ONOFF = 1;
}
}
else if (Serial3_RxPacket[0] == 0x05) // Текущая страница — режим постоянной мощности
{
if (Serial3_RxPacket[1] == 0x10) // Нажата кнопка меню
{
HMILCD_Send("CW.t1.txt=\"OFF\"");
HMILCD_Send("CW.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключает постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключает постоянный ток
PWM_SetCCR4(0); // Установить IREF2
HMILCD_Send("page menu");
mode = menu;
}
if (Serial3_RxPacket[1] == 0x11) // Нажата кнопка включения и текущее состояние выхода — выключено
{
Key_ONOFF = 1;
}
}
else if (Serial3_RxPacket[0] == 0xAA) // Текущая страница — цифровая клавиатура
{
char *temp = Serial3_RxPacket;
temp++; // Адрес увеличен на 1
uint16_t temp2 = atoi(temp); // Преобразование строки в целое
if (mode == CC)
{
if (temp2 > 1000)
temp2 = 1000;
ISET = temp2 / 100.0;
HMILCD_Send("CC.x0.val=%d", temp2);
if (Eload_Out == 1)
{
DAC_SetChannel1Data(DAC_Align_12b_R, 0);
if (ISET <= 2.5)
{
// Установить значение выхода DAC2 для управления постоянным током, +0.5 — округление
DAC_SetChannel2Data(DAC_Align_12b_R,
(uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4(0); // Установить IREF2
}
else
{
// Установить значение выхода DAC2 для управления постоянным током, +0.5 — округление
DAC_SetChannel2Data(DAC_Align_12b_R,
(uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
}
}
}
else if (mode == CV)
{
VSET = temp2 / 100.0;
HMILCD_Send("CV.x0.val=%d", temp2);
if (Eload_Out == 1)
{
DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Установить IREF1
PWM_SetCCR4(50000); // Установить IREF2
if (voltage_dw == 0)
{
DAC_SetChannel1Data(DAC_Align_12b_R,
(uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Установить значение выхода DAC1 для управления постоянным напряжением
}
else if (voltage_dw == 1)
{
DAC_SetChannel1Data(DAC_Align_12b_R,
(uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Установить значение выхода DAC1 для управления постоянным напряжением
}
else if (voltage_dw == 2)
{
uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
if (vset_pwm > 4065)
vset_pwm = 4095;
DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // Установить значение выхода DAC1 для управления постоянным напряжением
}
}
}
else if (mode == CR)
{
RSET = temp2 / 100.0;
HMILCD_Send("CR.x0.val=%d", temp2);
//CR_mode();
}
else if (mode == CW)
{
PSET = temp2 / 100.0;
HMILCD_Send("CW.x0.val=%d", temp2);
//CW_mode();
}
}
Serial3_RxFlag = 0;
}
key123();
rt_thread_mdelay(35);
}
}/*Постоянная мощность режим*/
void CW_mode(void)
{
double Ptemp = PSET / YVF;
if (Ptemp > 10)
Ptemp = 10;
if (Eload_Out == 1)
{
// Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Ptemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
}
else
{
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
}
/*Постоянное сопротивление режим*/
void CR_mode(void)
{
double Rtemp = YVF / RSET;
if (Rtemp > 10)
Rtemp = 10;
if (Eload_Out == 1)
{
// Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Rtemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
}
else
{
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
}
/* PID-регулирование */
void PID(void)
{
double Ptemp, Rtemp;
if (mode == CC && Eload_Out == 1)
{
qpid_set_dst(&qpid_CC, ISET); // Установить целевое значение PID
I_SET = qpid_cal_pos(&qpid_CC, YIF); // Расчёт PID
if (ISET <= 2.5)
{
// Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
}
else
{
// Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4((uint16_t)(I_SET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
}
}
if (mode == CV && Eload_Out == 1)
{
qpid_set_dst(&qpid_CV, VSET); // Установить целевое значение PID
V_SET = qpid_cal_pos(&qpid_CV, YVF); // Расчёт PID
if (voltage_dw == 0)
{
DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
// Установить выходное значение DAC1, управление постоянным напряжением
}
else if (voltage_dw == 1)
{
DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
// Установить выходное значение DAC1, управление постоянным напряжением
}
else if (voltage_dw == 2)
{
DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
// Установить выходное значение DAC1, управление постоянным напряжением
}
}
if (mode == CW && Eload_Out == 1)
{
qpid_set_dst(&qpid_CW, PSET); // Установить целевое значение PID
P_SET = qpid_cal_pos(&qpid_CW, YIF * YVF); // Расчёт PID
Ptemp = P_SET / YVF;
if (Ptemp > 10)
Ptemp = 10;
// Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Ptemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
}
if (mode == CR && Eload_Out == 1)
{
qpid_set_dst(&qpid_CR, RSET); // Установить целевое значение PID
R_SET = qpid_cal_pos(&qpid_CR, YVF / YIF); // Расчёт PID
Rtemp = YVF / R_SET;
if (Rtemp > 10)
Rtemp = 10;
// Установить выходное значение DAC2, управление постоянным током, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Rtemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
}
}
/* Функция обработки кнопок */
void key123(void)
{
if (key[2] == 1) // Кнопка 2, переключение
{
if (mode == menu)
{
HMILCD_Send("page CC"); // Перейти на страницу постоянного тока
mode = CC; // Установить текущий режим постоянного тока
}
else if (mode == CC)
{
if (Eload_Out == 1)
{
HMILCD_Send("CC.t1.txt=\"OFF\"");
HMILCD_Send("CC.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
HMILCD_Send("page CV"); // Перейти на страницу постоянного напряжения
mode = CV; // Установить текущий режим постоянного напряжения
}
else if (mode == CV)
{
if (Eload_Out == 1)
{
HMILCD_Send("CV.t1.txt=\"OFF\"");
HMILCD_Send("CV.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
HMILCD_Send("page CR"); // Перейти на страницу постоянного сопротивления
mode = CR; // Установить текущий режим постоянного сопротивления
}
else if (mode == CR)
{
if (Eload_Out == 1)
{
HMILCD_Send("CR.t1.txt=\"OFF\"");
HMILCD_Send("CR.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
HMILCD_Send("page CW"); // Перейти на страницу постоянной мощности
mode = CW; // Установить текущий режим постоянной мощности
}
else if (mode == CW)
{
if (Eload_Out == 1)
{
HMILCD_Send("CW.t1.txt=\"OFF\"");
HMILCD_Send("CW.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
HMILCD_Send("page menu"); // Перейти на страницу меню
mode = menu; // Установить текущий режим меню
}
key[2] = 0;
}
if (key[3] == 1) // Кнопка 3, меню
{
if (Eload_Out == 1)
Key_ONOFF = 1;
rt_thread_mdelay(15);
HMILCD_Send("page menu"); // Перейти на страницу меню
mode = menu; // Установить текущий режим меню
key[3] = 0;
}
}/* Функция входа потока 5, обработка кнопки включения/выключения электронной нагрузки */
static void thread5_ONOFF_entry(void *parameter)
{
/* Пин LED2 в режиме выхода */
rt_pin_mode(LED2, PIN_MODE_OUTPUT);
/* По умолчанию высокий уровень */
rt_pin_write(LED2, PIN_HIGH);
while (1)
{
if (Key_ONOFF == 1 | key[1] == 1) // Нажата кнопка включения
{
if (mode == CC) // Постоянный ток
{
if (Eload_Out == 0) // Текущее состояние выхода нагрузки выключено
{
HMILCD_Send("CC.t1.txt=\"ON\""); // В правом верхнем углу экрана отображается ON
HMILCD_Send("CC.b1.txt=\"关闭\""); // В правом нижнем углу экрана кнопка отображает "关闭"
Eload_Out = 1; // Установить состояние выхода нагрузки в включено
DAC_SetChannel1Data(DAC_Align_12b_R, 0);
if (ISET <= 2.5)
{
// Установить значение выхода DAC2 для управления постоянным током, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R,
(uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4(0); // Установить IREF2
}
else
{
// Установить значение выхода DAC2 для управления постоянным током, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R,
(uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
}
}
else if (Eload_Out == 1) // Текущее состояние выхода нагрузки включено
{
HMILCD_Send("CC.t1.txt=\"OFF\"");
HMILCD_Send("CC.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдает высокий уровень, выключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдает низкий уровень, выключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
}
else if (mode == CV) // Постоянное напряжение
{
if (Eload_Out == 0) // Текущее состояние выхода нагрузки выключено
{
HMILCD_Send("CV.t1.txt=\"ON\"");
HMILCD_Send("CV.b1.txt=\"关闭\"");
Eload_Out = 1;
DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Установить IREF1
PWM_SetCCR4(50000); // Установить IREF2
if (voltage_dw == 0)
{
DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
// Установить значение выхода DAC1 для управления постоянным напряжением
}
else if (voltage_dw == 1)
{
DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
// Установить значение выхода DAC1 для управления постоянным напряжением
}
else if (voltage_dw == 2)
{
DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
// Установить значение выхода DAC1 для управления постоянным напряжением
}
}
else if (Eload_Out == 1) // Текущее состояние выхода нагрузки включено
{
HMILCD_Send("CV.t1.txt=\"OFF\"");
HMILCD_Send("CV.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдает высокий уровень, выключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдает низкий уровень, выключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
}
else if (mode == CR) // Постоянное сопротивление
{
if (Eload_Out == 0) // Текущее состояние выхода нагрузки выключено
{
HMILCD_Send("CR.t1.txt=\"ON\"");
HMILCD_Send("CR.b1.txt=\"关闭\"");
Eload_Out = 1;
DAC_SetChannel1Data(DAC_Align_12b_R, 0);
CR_mode();
}
else if (Eload_Out == 1) // Текущее состояние выхода нагрузки включено
{
HMILCD_Send("CR.t1.txt=\"OFF\"");
HMILCD_Send("CR.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдает высокий уровень, выключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдает низкий уровень, выключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
}
else if (mode == CW) // Постоянная мощность
{
if (Eload_Out == 0) // Текущее состояние выхода нагрузки выключено
{
HMILCD_Send("CW.t1.txt=\"ON\"");
HMILCD_Send("CW.b1.txt=\"关闭\"");
Eload_Out = 1;
DAC_SetChannel1Data(DAC_Align_12b_R, 0);
CW_mode();
}
else if (Eload_Out == 1) // Текущее состояние выхода нагрузки включено
{
HMILCD_Send("CW.t1.txt=\"OFF\"");
HMILCD_Send("CW.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдает высокий уровень, выключить постоянное напряжение
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдает низкий уровень, выключить постоянный ток
PWM_SetCCR4(0); // Установить IREF2
}
}
Key_ONOFF = 0;
key[1] = 0;
}
if (Eload_Out == 0)
{
rt_pin_write(LED2, SET);
}
else if (Eload_Out == 1)
{
rt_pin_write(LED2, RESET);
}
rt_thread_mdelay(35);
}
}
/* Функция входа потока 7, отображение параметров на экране */
static void thread7_HMI_Display_entry(void *parameter)
{
while (1)
{
if (mode != menu)
{
double V = lowV1(YVF);
double I = lowV2(YIF);
HMILCD_Send("x1.val=%d", (uint16_t)(V * 100)); // Отобразить напряжение
HMILCD_Send("x2.val=%d", (uint16_t)(I * 1000)); // Отобразить ток
HMILCD_Send("x3.val=%d", (uint32_t)(I * V * 100)); // Отобразить мощность
HMILCD_Send("x4.val=%d", (uint32_t)(V / I * 100)); // Отобразить сопротивление
HMILCD_Send("x5.val=%d", (uint32_t)(VBAT * 100)); // Отобразить сопротивление
}
rt_thread_mdelay(50);
}
}
/* Функция входа потока 8, управление вентилятором охлаждения */
static void thread8_FAN_entry(void *parameter)
{
while (1)
{
uint16_t P = (uint16_t)(YIF * YVF + 0.5);
if (P >= 13) // Запустить вентилятор при мощности >13 Вт
{
FAN_PWM_ON();
if (P < 20)
{
FAN_PWM_SetCCR(20); // Скважность управления вентилятором 20%
}
else if (P >= 20 && P < 25)
{
FAN_PWM_SetCCR(30);
}
else if (P >= 25 && P < 30)
{
FAN_PWM_SetCCR(40);
}
else if (P >= 30 && P < 35)
{
FAN_PWM_SetCCR(50);
}
else if (P >= 35 && P < 40)
{
FAN_PWM_SetCCR(60);
}
else if (P >= 40 && P < 45)
{
FAN_PWM_SetCCR(70);
}
else if (P >= 45 && P < 50)
{
FAN_PWM_SetCCR(80);
}
else if (P >= 50 && P < 60)
{
FAN_PWM_SetCCR(90);
}
else if (P >= 60)
{
FAN_PWM_SetCCR(100);
}
}
else if (P <= 8)
{
FAN_PWM_SetCCR(0); // Выключить ШИМ вентилятора и выдать низкий уровень
FAN_PWM_OFF();
}
rt_thread_mdelay(200);
}
}
/* Функция входа потока 9, управление режимами постоянной мощности и сопротивления */
static void thread9_CWCR_entry(void *parameter)
{
while (1)
{
if (mode == CW)
{
CW_mode();
}
if (mode == CR)
{
CR_mode();
}
rt_thread_mdelay(50);
}
}/* Функция входа потока 10, обрабатывающая данные, полученные по Bluetooth */
static void thread10_BlueTooth_entry(void *parameter)
{
HMILCD_Send("CC.x0.val=0"); // Сброс отображаемого на экране заданного значения тока
HMILCD_Send("CV.x0.val=0"); // Сброс отображаемого на экране заданного значения напряжения
HMILCD_Send("CR.x0.val=0"); // Сброс отображаемого на экране заданного значения сопротивления
HMILCD_Send("CW.x0.val=0"); // Сброс отображаемого на экране заданного значения мощности
while (1)
{
if (Serial5_RxFlag == 1)
{
if (Serial5_RxPacket[0] == 0x01) // Текущая страница — главное меню
{
if (Serial5_RxPacket[1] == 0x10) // Нажата кнопка постоянного тока
{
if (mode == CV)
{
HMILCD_Send("CV.t1.txt=\"OFF\"");
HMILCD_Send("CV.b1.txt=\"开启\"");
}
else if (mode == CR)
{
HMILCD_Send("CR.t1.txt=\"OFF\"");
HMILCD_Send("CR.b1.txt=\"开启\"");
}
else if (mode == CW)
{
HMILCD_Send("CW.t1.txt=\"OFF\"");
HMILCD_Send("CW.b1.txt=\"开启\"");
}
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключение постоянного тока
PWM_SetCCR4(0); // Установка IREF2
Eload_Out = 0;
HMILCD_Send("page CC"); // Переключение на страницу режима постоянного тока
mode = CC; // Установка текущего режима — постоянный ток
}
else if (Serial5_RxPacket[1] == 0x11) // Нажата кнопка постоянного напряжения
{
if (mode == CC)
{
HMILCD_Send("CC.t1.txt=\"OFF\"");
HMILCD_Send("CC.b1.txt=\"开启\"");
}
else if (mode == CR)
{
HMILCD_Send("CR.t1.txt=\"OFF\"");
HMILCD_Send("CR.b1.txt=\"开启\"");
}
else if (mode == CW)
{
HMILCD_Send("CW.t1.txt=\"OFF\"");
HMILCD_Send("CW.b1.txt=\"开启\"");
}
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключение постоянного тока
PWM_SetCCR4(0); // Установка IREF2
Eload_Out = 0;
HMILCD_Send("page CV"); // Переключение на страницу постоянного напряжения
mode = CV; // Установка текущего режима — постоянное напряжение
}
else if (Serial5_RxPacket[1] == 0x12) // Нажата кнопка постоянного сопротивления
{
if (mode == CC)
{
HMILCD_Send("CC.t1.txt=\"OFF\"");
HMILCD_Send("CC.b1.txt=\"开启\"");
}
else if (mode == CV)
{
HMILCD_Send("CV.t1.txt=\"OFF\"");
HMILCD_Send("CV.b1.txt=\"开启\"");
}
else if (mode == CW)
{
HMILCD_Send("CW.t1.txt=\"OFF\"");
HMILCD_Send("CW.b1.txt=\"开启\"");
}
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключение постоянного тока
PWM_SetCCR4(0); // Установка IREF2
Eload_Out = 0;
HMILCD_Send("page CR"); // Переключение на страницу постоянного сопротивления
mode = CR; // Установка текущего режима — постоянное сопротивление
}
else if (Serial5_RxPacket[1] == 0x13) // Нажата кнопка постоянной мощности
{
if (mode == CV)
{
HMILCD_Send("CV.t1.txt=\"OFF\"");
HMILCD_Send("CV.b1.txt=\"开启\"");
}
else if (mode == CR)
{
HMILCD_Send("CR.t1.txt=\"OFF\"");
HMILCD_Send("CR.b1.txt=\"开启\"");
}
else if (mode == CC)
{
HMILCD_Send("CC.t1.txt=\"OFF\"");
HMILCD_Send("CC.b1.txt=\"开启\"");
}
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключение постоянного тока
PWM_SetCCR4(0); // Установка IREF2
Eload_Out = 0;
HMILCD_Send("page CW"); // Переключение на страницу постоянной мощности
mode = CW; // Установка текущего режима — постоянная мощность
}
}
else if (Serial5_RxPacket[0] == 0x08) // Возврат в меню
{
if (mode == CC)
{
HMILCD_Send("CC.t1.txt=\"OFF\""); // В правом верхнем углу экрана заголовок OFF
HMILCD_Send("CC.b1.txt=\"开启\""); // В правом нижнем углу кнопка «开启»
Eload_Out = 0; // Состояние выхода нагрузки — выключено
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключение постоянного тока
PWM_SetCCR4(0); // Установка IREF2
HMILCD_Send("page menu"); // Переключение на страницу меню
mode = menu;
}
else if (mode == CV)
{
HMILCD_Send("CV.t1.txt=\"OFF\"");
HMILCD_Send("CV.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключение постоянного тока
PWM_SetCCR4(0); // Установка IREF2
HMILCD_Send("page menu");
mode = menu;
}
else if (mode == CR)
{
HMILCD_Send("CR.t1.txt=\"OFF\"");
HMILCD_Send("CR.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключение постоянного тока
PWM_SetCCR4(0); // Установка IREF2
HMILCD_Send("page menu");
mode = menu;
}
else if (mode == CW)
{
HMILCD_Send("CW.t1.txt=\"OFF\"");
HMILCD_Send("CW.b1.txt=\"开启\"");
Eload_Out = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1 выдаёт высокий уровень, отключение постоянного напряжения
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // DAC2 выдаёт низкий уровень, отключение постоянного тока
PWM_SetCCR4(0); // Установка IREF2
HMILCD_Send("page menu");
mode = menu;
}
}
else if (Serial5_RxPacket[0] == 0x09) // Кнопка включения
{
Key_ONOFF = 1;
}
else if (Serial5_RxPacket[0] == 0xAA) // Текущая страница — цифровая клавиатура
{
char *temp = Serial5_RxPacket;
temp++; // Адрес увеличен на 1
uint16_t temp2 = atoi(temp); // Преобразование строки в целое
if (mode == CC)
{
if (temp2 > 1000)
temp2 = 1000;
ISET = temp2 / 100.0;
HMILCD_Send("CC.x0.val=%d", temp2);```c
if (Eload_Out == 1)
{
DAC_SetChannel1Data(DAC_Align_12b_R, 0);
if (ISET <= 2.5)
{
// Установить выходное значение DAC2 для управления стабилизацией тока, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R,
(uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4(0); // Установить IREF2
}
else
{
// Установить выходное значение DAC2 для управления стабилизацией тока, +0.5 для округления
DAC_SetChannel2Data(DAC_Align_12b_R,
(uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // Установить IREF2
}
}
}
else if (mode == CV)
{
VSET = temp2 / 100.0;
HMILCD_Send("CV.x0.val=%d", temp2);
if (Eload_Out == 1)
{
DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // Установить IREF1
PWM_SetCCR4(50000); // Установить IREF2
if (voltage_dw == 0)
{
DAC_SetChannel1Data(DAC_Align_12b_R,
(uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // Установить выходное значение DAC1 для управления стабилизацией напряжения
}
else if (voltage_dw == 1)
{
DAC_SetChannel1Data(DAC_Align_12b_R,
(uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // Установить выходное значение DAC1 для управления стабилизацией напряжения
}
else if (voltage_dw == 2)
{
uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
if (vset_pwm > 4065)
vset_pwm = 4095;
DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // Установить выходное значение DAC1 для управления стабилизацией напряжения
}
}
}
else if (mode == CR)
{
RSET = temp2 / 100.0;
HMILCD_Send("CR.x0.val=%d", temp2);
CR_mode();
}
else if (mode == CW)
{
PSET = temp2 / 100.0;
HMILCD_Send("CW.x0.val=%d", temp2);
CW_mode();
}
}
Serial5_RxFlag = 0;
}
rt_thread_mdelay(40);
}
}
Рекомендуем к прочтению
- Рекомендации по 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-сервере YuYu, создание собственного сайта для рисования ИИ: https://blog.zeruns.com/archives/768.html
- Краткий обзор коммутатора 2.5G TL-SE2109, 8 портов 2.5G + 1 порт 10G SFP+: https://blog.zeruns.com/archives/780.html















