CH32V307VCT6 기반 스마트 전자 부하 오픈소스, 임베디드 대회 작품 오픈소스, 회로도, PCB, 프로그램 소스 코드, 작품 보고서 포함.
2023년 임베디드 칩 및 시스템 설계 경쟁 응용 트랙, 국가 2등급 작품.
반달 동안 이전에 만든 합태 버전의 전자 부하를 기반으로 개선했으며, CH32 마이크로컨트롤러로 프로그램을 포팅하고 RT-Thread 시스템을 사용했으며, 코드를 최적화하고 서둘러 완성했습니다. 품질이 낮으니 비판하지 마세요.
작품 시연 영상: https://www.bilibili.com/video/BV1Zu4y1m7Zd/
HT32F52352 기반 스마트 전자 부하 오픈소스, 합태컵 작품 오픈소스: https://blog.zeruns.com/archives/784.html
본 오픈소스 작품은 참고 학습용이며, 복제를 권장하지 않습니다. 리창 오픈소스 플랫폼에는 더 많고 더 우수하며 더 완벽한 전자 부하 오픈소스 작품이 있습니다!
본 프로젝트의 리창 오픈소스 플랫폼 오픈소스 링크: https://url.zeruns.com/Et4x4
전자/마이크로컨트롤러 기술 교류 그룹: 2169025065
친헝 공식 웹사이트에서 개발 보드 샘플을 무료로 신청할 수 있습니다: https://url.zeruns.com/h9a99
전자 부하란 무엇인가
전자 부하는 실제 부하 환경을 시뮬레이션하여 전원 또는 전자 회로의 성능을 테스트하는 데 사용되는 전자 장치입니다. 대용량 조정 가능한 저항이나 전열선 등 기존의 수동 부하를 사용하는 것과 비교하면, 전자 부하는 매개변수 조정 가능, 사용 편의성 등 많은 장점을 가지고 있습니다. 전문적인 전자 공학 프로젝트 개발이든 취미 전자 애호가든, 전자 부하 측정기는 필수 장비 중 하나입니다.
전자 부하는 테스트 전원의 종류에 따라 교류 전자 부하와 직류 전자 부하로 나눌 수 있습니다. 기능상으로 분류하면 일반적으로 정전류, 정전압, 정저항, 정전력의 네 가지 유형이 있습니다. 우리가 흔히 보는 대부분의 전원은 정전압 직류 전원이기 때문에, 이러한 전원을 테스트할 때 주로 테스트하는 것은 전류 출력 능력입니다. 따라서 대부분의 응용 시나리오에서 직류 정전류 전자 부하가 가장 일반적인 유형입니다. 전자 부하를 제어 방식으로 분류하면 수치 제어와 아날로그 두 가지 유형으로 나눌 수 있습니다. 순수 아날로그 회로 제어를 사용하는 전자 부하와 비교하면, 수치 제어 전자 부하는 디지털 제어를 사용하여 매개변수 조정이 더 직관적이고, 기능이 풍부하며, 확장이 간단하고, 테스트 자동화를 편리하게 구현할 수 있습니다.
프로젝트 소개
본 작품은 CH32V307VCT6 친헝 마이크로컨트롤러를 메인 제어 칩으로 설계한 전자 부하이며, 전원 공급 방식은 18650 리튬 배터리로 공급되어 휴대하기 편합니다.
제어 방식은 마이크로컨트롤러 DAC에서 직류 전압을 출력하여 기준 전압으로 사용하고, 연산 증폭기와 전류/전압 샘플링 증폭 후의 전압을 비교하며, 연산 증폭기 출력이 MOS 트랜지스터를 제어하여 정전압/정전류를 구현합니다.
터치 스크린은 타오징치의 2.8인치 직렬 포트 스크린이며, 모델은 TJC3224T028_011R입니다.
방열판은 2U 서버의 1356/1366 핀 방열판이며, 측면 송풍식입니다.
프로젝트 프로그램은 RT-Thread Studio로 개발되었습니다. 회로 설계는 리창 EDA 소프트웨어를 사용했습니다.
최대 입력 전압 전류는 100V/10A이며, 최대 전력은 200W입니다.
실물 사진
당시 사진을 많이 찍지 않아 이 몇 장만 찾았습니다. 시연 영상을 확인해 보세요.
![]()
자료 다운로드 주소
아래 링크의 자료에는 회로 원리도, 리창 EDA 프로젝트 파일, PCB 제판 파일, 프로그램 소스 코드, 직렬 포트 스크린 프로젝트 파일, 칩 매뉴얼이 포함되어 있습니다.
123 클라우드 디스크 무제한 속도 다운로드 주소: https://www.123pan.com/ps/2Y9Djv-6NevH.html
바이두 네트디스크 다운로드 주소: https://pan.baidu.com/s/17YSlBZ6F1M18k7JGa7FlVA?pwd=buxx 추출 코드: buxx
부품 구매 주소
- CH32V307VCT6 칩: https://s.click.taobao.com/T8MSZot
- CH32V307VCT6 개발 보드: https://s.click.taobao.com/2JBSZot
- INA199A1 칩: https://s.click.taobao.com/XLuweot
- 0805 칩 저항 샘플 북: https://s.click.taobao.com/p8YSGpt
- 0805 칩 커패시터 샘플 북: https://u.jd.com/9uvZoBd
- XL1509 칩: https://s.click.taobao.com/DOcRZot
- 직렬 포트 스크린: https://s.click.taobao.com/pyzleot
리창 몰에서 부품을 구매하는 것을 권장합니다: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html
원리도
전력 보드
전원 보드
제어 보드
PCB
전력 보드
상층
하층
전원 보드
상층
하층
제어 보드
상층
하층
기타 오픈소스 프로젝트 추천- 3상 전력량 수집기를 오픈소스로 공개했으며, 집의 전력 사용 상황을 편리하게 모니터링할 수 있습니다: 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
- LM25118 자동 승강압 조절 가능 DCDC 전원 모듈: https://blog.zeruns.com/archives/727.html
- EG1164 고전력 동기 정류 승압 모듈 오픈소스, 최고 효율 97%: https://blog.zeruns.com/archives/730.html
- Hezhou Air700E 기반 4G 환경 모니터링 노드(온습도, 기압 등 데이터), MQTT를 통해 Alibaba Cloud IoT 플랫폼으로 업로드: 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
``````c
#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; /* 워치독 장치 핸들 */
/* ADC 참조 전압 */
#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) 출력 보정
/* ADC 샘플링 평균값 계산 횟수 */
#define ADC_count 3
/* 1차 저역통과 필터 필터링 계수*/
#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]; //ADC 샘플링 데이터
// 모드 페이지의 열거형 변수 정의
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; // PID 제어 데이터 포인터
static qpid_t qpid_CV; // PID 제어 데이터 포인터
static qpid_t qpid_CR; // PID 제어 데이터 포인터
static qpid_t qpid_CW; // PID 제어 데이터 포인터
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 없음
*/
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(); // DAC 초기화
PWM_Init(); // PWM 초기화
rt_pin_mode(MCU_G0, PIN_MODE_OUTPUT); // IO 포트를 출력 모드로 설정
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 tick
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...");
}
}
``````c
/* 1차 저역통과 필터
* 반환값: iData 1차 필터링 후의 샘플 값 */
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)
{
/* 유휴 스레드의 콜백 함수에서 워치독 피드 */
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;
}
/* 유휴 스레드 콜백 함수 설정 */
rt_thread_idle_sethook(idle_hook);
return ret;
}
/* 스레드 1의 진입 함수, 시스템 실행 상태 LED 깜빡임 */
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의 진입 함수, ADC 데이터 처리 */
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분주 (72Mhz/6=12Mhz), ADC 클록 주파수는 14Mhz를 초과할 수 없음
GPIO_InitTypeDef GPIO_InitStructure = { 0 }; // GPIO 설정 구조체 정의
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; // GPIO 포트 설정
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // GPIO 모드를 아날로그 입력으로 설정
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // ADC 규칙 그룹 설정, 규칙 그룹의 시퀀스 1에 채널 0 작성, 샘플링 시간 55.5 사이클
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); // ADC 규칙 그룹 설정, 규칙 그룹의 시퀀스 2에 채널 1 작성, 샘플링 시간 55.5 사이클
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); // ADC 규칙 그룹 설정, 규칙 그룹의 시퀀스 3에 채널 2 작성, 샘플링 시간 55.5 사이클
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_239Cycles5); // ADC 규칙 그룹 설정, 규칙 그룹의 시퀀스 4에 채널 3 작성, 샘플링 시간 55.5 사이클
ADC_InitTypeDef ADC_InitStructure = { 0 };
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // ADC를 독립 모드로 설정
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 데이터 우측 정렬 설정
ADC_InitStructure.ADC_NbrOfChannel = 4; // 규칙 변환의 ADC 채널 수
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE); // ADC1 활성화
ADC_ResetCalibration(ADC1); // ADC1 캘리브레이션 레지스터 리셋
while (ADC_GetResetCalibrationStatus(ADC1))
; // 리셋 캘리브레이션 완료 대기
ADC_StartCalibration(ADC1); // AD 캘리브레이션 시작
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; // 메모리 주소를 ADC 값 읽기 주소로 설정
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 데이터 소스를 주변 장치로 설정, 즉 DMA 전송 방식은 주변 장치에서 메모리로
DMA_InitStructure.DMA_BufferSize = 4; // DMA 데이터 버퍼 크기 설정
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // DMA 주변 장치 증가 모드 비활성화
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // DMA 메모리 증가 모드 활성화
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 주변 장치 데이터 크기를 반 워드로 설정, 즉 2바이트
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 메모리 데이터 크기를 반 워드로 설정, 즉 2바이트
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // DMA 모드를 순환 전송 모드로 설정
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // DMA 전송 채널 우선순위를 높음으로 설정, 하나의 DMA 채널을 사용할 때 우선순위 설정은 영향을 주지 않음
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 이 DMA 전송 방식이 주변 장치에서 메모리로이므로 메모리에서 메모리로의 전송 방식 비활성화
DMA_Init(DMA1_Channel1, &DMA_InitStructure); // DMA1의 채널 1 초기화, ADC1의 하드웨어 트리거는 DMA1의 채널 1에 연결되므로 DMA1 채널 1을 반드시 사용해야 함
DMA_Cmd(DMA1_Channel1, ENABLE); // DMA1 채널 1 시작
ADC_DMACmd(ADC1, ENABLE); // ADC DMA 요청 활성화
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 외부 트리거를 사용하지 않으므로 소프트웨어 트리거를 사용하여 ADC 변환 시작
qpid_init(&qpid_CC); // PID 제어 데이터 초기화
qpid_set_lmt(&qpid_CC, 0, 10); // PID 제한값 설정
qpid_set_ratio(&qpid_CC, 1, 0.001, 0.1); // 제어 비율 계수 설정
qpid_init(&qpid_CV); // PID 제어 데이터 초기화
qpid_set_lmt(&qpid_CV, 31.0, 100); // PID 제한값 설정
qpid_set_ratio(&qpid_CV, 0.35, 0.005, 0.001); // 제어 비율 계수 설정
```qpid_init(&qpid_CW); // PID 제어 데이터 초기화
qpid_set_lmt(&qpid_CW, 0.1, 200); // PID 제한값 설정
qpid_set_ratio(&qpid_CW, 0.5, 0.003, 0.001); // 제어 비율 계수 설정
qpid_init(&qpid_CR); // PID 제어 데이터 초기화
qpid_set_lmt(&qpid_CR, 0.1, 1000); // PID 제한값 설정
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) // 전압이 31V 미만일 때 기어 전환
{
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); // PID 제한값 설정
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.5V 이상일 때 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); // PID 제한값 설정
qpid_set_ratio(&qpid_CV, 0.5, 0.005, 0.0005); // 제어 비율 계수 설정
}
else if (YVF <= 4.6) // 전압이 4.6V 미만일 때 기어 전환
{
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); // PID 제한값 설정
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.1V 이상일 때 기어 전환
{
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); // PID 제한값 설정
qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005); // 제어 비율 계수 설정
}
}
if (AVG_count < ADC_count)
{
YIF1_SUM += AD_Value[1]; // MOS 관 1 전류 누적
YIF2_SUM += AD_Value[2]; // MOS 관 2 전류 누적
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```c
/* 스레드 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);
}
}
``````c
/*Constant Power Mode*/
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 설정
}
}
/*Constant Resistance Mode*/
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;
}
}
``````c
/* 스레드 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) // 전력이 13W 이상일 때 팬 시작
{
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); // 팬의 PWM 출력 끄기, 낮은 전압 출력
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의 진입 함수, 블루투스에서 받은 데이터 처리 */
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
- 마인크래프트 서버 개설 튜토리얼: https://blog.zeruns.com/tag/mc/
- 코드 없이 블로그 웹사이트 구축! 초상세 개인 블로그 구축 튜토리얼: https://blog.zeruns.com/archives/783.html
- 내부 네트워크 관통 서버 구축 튜토리얼, NPS 구축 및 사용 튜토리얼: https://blog.zeruns.com/archives/741.html
- 우윤 GPU 클라우드 서버 구축 SD(Stable Diffusion) 튜토리얼, 자신의 AI 그림 그리기 웹사이트 구축: https://blog.zeruns.com/archives/768.html
- 2.5G 스위치 TL-SE2109 간단한 개봉 평가, 8개의 2.5G 전기 포트 + 1개의 10G 광 포트(SFP+): https://blog.zeruns.com/archives/780.html














