一、系統(tǒng)框架

按鍵輸入:通過(guò)按鍵來(lái)控制光標(biāo)并更改設(shè)定溫度值、Kp、Ki、Kd
ADC采集:采集NTC1的電壓,轉(zhuǎn)換成NTC1的電阻,再進(jìn)行分度值查表,得到當(dāng)前溫度
PWM輸出:將PID計(jì)算的結(jié)果通過(guò)PWM輸出到執(zhí)行器,控制板子加熱
串口調(diào)試:將實(shí)際溫度、設(shè)定溫度值、Kp、Ki、Kd參數(shù)打印到上位機(jī)進(jìn)行波形顯示
屏幕顯示:將實(shí)際溫度、設(shè)定溫度值、Kp、Ki、Kd、NTC1的電壓、PWM當(dāng)前的輸出占空比參數(shù)顯示到屏幕上
二、實(shí)驗(yàn)準(zhǔn)備及接線
1.)先準(zhǔn)備配套器材
主控:CW32L012開(kāi)發(fā)板
輔助器材:杜邦線若干(公對(duì)母,母對(duì)母)、萬(wàn)用表(調(diào)試)
供電:USB 線或直接杜邦線供電(給實(shí)驗(yàn)板 + CW32L012開(kāi)發(fā)板供電)
2.)基礎(chǔ)接線步驟
電源連接
實(shí)驗(yàn)板5V→ CW32L012開(kāi)發(fā)板 5V
實(shí)驗(yàn)板GND → CW32L012開(kāi)發(fā)板 GND(共地很重要,避免信號(hào)干擾)
傳感器接線
實(shí)驗(yàn)板NTC1排針接口 →CW32L012開(kāi)發(fā)板ADC轉(zhuǎn)換IO口PA4.
PWM輸出接線
實(shí)驗(yàn)板PWM接口 →CW32L012開(kāi)發(fā)板PWM輸出口PB9
串口接線
CW32L012開(kāi)發(fā)板PA9 →CH340或WCHlink的RX引腳
CW32L012開(kāi)發(fā)板PA10 →CH340或WCHlink的TX引腳
CW32L012開(kāi)發(fā)板3.3V →CH340或WCHlink的3.3V引腳
CW32L012開(kāi)發(fā)板GND →CH340或WCHlink的GNDV引腳
接線完成圖:



三、溫度閉環(huán)控制實(shí)現(xiàn)

通過(guò)按鍵或初始化給定一個(gè)目標(biāo)溫度值
//溫度控制系統(tǒng)的參數(shù):實(shí)際溫度,設(shè)定溫度,P、I、D參數(shù),PWM占空比 float Temp_Para[6] = {0,20,0,0,0,0}; void key_handle(uint8_t key) { switch(key) { case 1://按鍵1,對(duì)應(yīng)參數(shù)++ if(para_Index==3) Temp_Para[para_Index]+=0.5;//如果是積分,每次變化0.5 else Temp_Para[para_Index]++; if(Temp_Para[para_Index] > Temp_Para_Max[para_Index]) Temp_Para[para_Index] = Temp_Para_Min[para_Index]; break; case 2://按鍵2,對(duì)應(yīng)參數(shù)-- if(Temp_Para[para_Index] > Temp_Para_Min[para_Index]) { if(para_Index==3) Temp_Para[para_Index]-=0.5;//如果是積分,每次變化0.5 else Temp_Para[para_Index]--; } else Temp_Para[para_Index] = Temp_Para_Max[para_Index]; break; case 3://按鍵3,選擇要操作的參數(shù) para_Index_old = para_Index; para_Index++; if(para_Index >= 5) para_Index= 1; break; } }
ADC采集獲取當(dāng)前溫度
采集NTC1的電壓,轉(zhuǎn)換成NTC1的電阻,再進(jìn)行分度值查表,得到當(dāng)前溫度
部分代碼:
/**************************
獲取NTC1上端點(diǎn)電壓函數(shù)
返回值:電壓(單位V)
**************************/
float get_ntc_v(void)
{
return adc_result*3.3/4095;
}
/**************************
獲取NTC1電阻函數(shù)
傳入:NTC1上端點(diǎn)電壓值(單位V)
返回值:電阻(單位歐姆)
**************************/
float get_ntc_r(float Vadc)
{
return (Vadc*10000)/(5-Vadc);
}
//0攝氏度~100攝氏度時(shí)NTC的電阻值,共101個(gè)電阻數(shù)據(jù)
const uint16_t PID_NTC_Table[]={
32108, 30544, 29066, 27669, 26346, 25095, 23910, 22788, 21724, 20716,
19760, 18856, 17997, 17181, 16405, 15667, 14965, 14297, 13662, 13058,
12483, 11936, 11415, 10920, 10449, 10000, 9573, 9166, 8778, 8409,
8057, 7722, 7402, 7097, 6806, 6529, 6264, 6011, 5770, 5539,
5319, 5109, 4908, 4716, 4533, 4357, 4190, 4029, 3876, 3729,
3588, 3454, 3325, 3201, 3083, 2970, 2861, 2757, 2658, 2562,
2470, 2383, 2299, 2218, 2140, 2066, 1994, 1926, 1860, 1796,
1735, 1677, 1620, 1566, 1514, 1464, 1416, 1370, 1325, 1282,
1241, 1201, 1163, 1126, 1091, 1057, 1024, 992, 961, 932,
903, 876, 849, 824, 799, 775, 752, 730, 708, 687,
667
};
/**
* @brief 二分查找NTC電阻值對(duì)應(yīng)的下標(biāo),無(wú)精確值時(shí)線性插值返回浮點(diǎn)下標(biāo)
* @param target_res: 要查找的NTC電阻值(uint16_t)
* @retval 浮點(diǎn)型下標(biāo):
* - 精確匹配:返回整數(shù)下標(biāo)(如25.0,對(duì)應(yīng)25℃)
* - 插值匹配:返回小數(shù)下標(biāo)(如25.5,對(duì)應(yīng)25.5℃)
* - 超出范圍:返回0.0(電阻>最大值)或100.0(電阻 數(shù)組最大值(0℃對(duì)應(yīng)電阻)→ 返回0.0
if (target_res > PID_NTC_Table[0])
{
return 0.0f;
}
// 邊界2:目標(biāo)電阻 < 數(shù)組最小值(100℃對(duì)應(yīng)電阻)→ 返回100.0
if (target_res < PID_NTC_Table[100 - 1])
{
return 100.0f;
}
// 二分查找初始化
int32_t low = 0; // 左邊界下標(biāo)
int32_t high = 100 - 1; // 右邊界下標(biāo)
int32_t mid = 0;
// 二分查找核心循環(huán)(適配單調(diào)遞減數(shù)組)
while (low <= high)
{
mid = low + (high - low)/2; // 計(jì)算中間下標(biāo)(避免溢出可寫(xiě):low + (high - low)/2)
if (PID_NTC_Table[mid] == target_res)
{
// 精確匹配,返回浮點(diǎn)型下標(biāo)
return (float)mid;
}
else if (target_res < PID_NTC_Table[mid])
{
// 目標(biāo)電阻更小 → 對(duì)應(yīng)溫度更高 → 向右(大下標(biāo))查找
low = mid + 1;
}
else
{
// 目標(biāo)電阻更大 → 對(duì)應(yīng)溫度更低 → 向左(小下標(biāo))查找
high = mid - 1;
}
}
// 未找到精確值,執(zhí)行線性插值(此時(shí)high < low 且 high + 1 = low)
// 插值公式(單調(diào)遞減適配):
// 下標(biāo) = high + (目標(biāo)值 - 高下標(biāo)電阻) / (低下標(biāo)電阻 - 高下標(biāo)電阻)
float res_high = (float)PID_NTC_Table[high]; // high下標(biāo)對(duì)應(yīng)的電阻(更大)
float res_low = (float)PID_NTC_Table[low]; // low下標(biāo)對(duì)應(yīng)的電阻(更小)
float index = (float)high + (target_res - res_high) / (res_low - res_high);
return index;
}
對(duì)溫度進(jìn)行滑動(dòng)均值濾波
將數(shù)據(jù)打印到串口顯示波形,可以看到數(shù)據(jù)呈現(xiàn)明顯的震蕩,根據(jù)這個(gè)波形現(xiàn)象,我們采取合適的濾波,也就是滑動(dòng)均值濾波:


可以直觀看到濾波前后,波形明顯平滑了不少,說(shuō)明濾波效果好。
代碼:
#define TEMP_FILTER_WINDOW_SIZE 5 // 滑動(dòng)窗口大小(建議3~10,越大越平滑,實(shí)時(shí)性稍降)
// ******************************************************
/**
* @brief 溫度滑動(dòng)平均濾波函數(shù)
* @param rawTemp 輸入的原始浮點(diǎn)溫度值(如傳感器采集的溫度)
* @return 濾波后的浮點(diǎn)溫度值
* @note 函數(shù)內(nèi)部通過(guò)靜態(tài)變量維護(hù)濾波窗口,無(wú)需外部初始化/銷毀,調(diào)用即使用
*/
float tempMovingAverageFilter(float rawTemp)
{
// 靜態(tài)變量:僅第一次調(diào)用初始化,后續(xù)調(diào)用保留值(維護(hù)濾波窗口狀態(tài))
static float tempBuffer[TEMP_FILTER_WINDOW_SIZE] = {0.0f}; // 溫度緩沖區(qū)
static int bufferIndex = 0; // 下一個(gè)寫(xiě)入的索引(循環(huán)覆蓋)
static int dataCount = 0; // 已存入的有效數(shù)據(jù)個(gè)數(shù)
// 1. 將新溫度值寫(xiě)入緩沖區(qū)(循環(huán)覆蓋最舊數(shù)據(jù))
tempBuffer[bufferIndex] = rawTemp;
// 2. 更新索引(循環(huán):0→1→...→窗口大小-1→0)
bufferIndex = (bufferIndex + 1) % TEMP_FILTER_WINDOW_SIZE;
// 3. 更新有效數(shù)據(jù)數(shù)(窗口未滿時(shí)累加,滿后保持窗口大小)
if (dataCount < TEMP_FILTER_WINDOW_SIZE)
{
dataCount++;
}
// 4. 計(jì)算窗口內(nèi)所有數(shù)據(jù)的平均值(濾波核心)
float sum = 0.0f;
for (int i = 0; i < dataCount; i++)
{
sum += tempBuffer[i];
}
return sum / (float)dataCount;
}
PID計(jì)算
對(duì)采集到的溫度和設(shè)定的溫度進(jìn)行運(yùn)算,得出適合的加熱功率并輸出
代碼:
typedef struct
{
int16_t target; //目標(biāo)值
int16_t actual; //實(shí)際值
float out; //輸出值
float err; //偏差值
float err_last; //上一個(gè)偏差值
float integral; //積分值
float Kp;
float Ki;
float Kd;
}pid;
void set_pid_para(void)//更新pid參數(shù)
{
temper_pid.actual=Temp_Para[0];
temper_pid.target=Temp_Para[1];
temper_pid.Kp=Temp_Para[2];
temper_pid.Ki=Temp_Para[3];
temper_pid.Kd=Temp_Para[4];
}
uint16_t pid_control(void)
{
set_pid_para();//更新pid參數(shù)
temper_pid.err=temper_pid.target-temper_pid.actual;//誤差
if(temper_pid.err<=0) return 0;//設(shè)定溫度低于等于實(shí)際溫度,加熱關(guān)閉
else if(temper_pid.err?>5) return ARR_Value;//實(shí)際溫度與設(shè)定溫度相差大于5度,加熱輸出最大功率
else
{
temper_pid.integral+=temper_pid.err;//積分
temper_pid.integral=(temper_pid.integral>10)? 10:temper_pid.integral;//積分限幅
temper_pid.integral=(temper_pid.integral-10)? -10:temper_pid.integral;
temper_pid.out=temper_pid.Kp*temper_pid.err+temper_pid.Ki*temper_pid.integral+temper_pid.Kd*(temper_pid.err-temper_pid.err_last);
temper_pid.err_last=temper_pid.err;//更新上一次誤差
temper_pid.out=(temper_pid.out?>ARR_Value)? ARR_Value:temper_pid.out;//輸出限幅
return temper_pid.out;
}
}
PWM輸出
將控制算法結(jié)果作用到PWM,控制板子進(jìn)行加熱
void Set_pwm__ccr(uint32_t Angle)
{
Angle=(Angle>ARR_Value)? ARR_Value:Angle;
CW_GTIM1->CCR4 = Angle;
}
Set_pwm__ccr(pid_control());//輸出
四、代碼現(xiàn)象


屏幕:與屏幕對(duì)應(yīng)的變量含義:
Temp PID
實(shí)際溫度(Real Temp)
設(shè)定溫度(Set Temp)
P(Kp)
I(Ki)
D(Kd)
輸出的PWM占空比(PWM Duty)
采集到的NTC的電壓(Vntc)
按鍵:
按鍵1(左):對(duì)光標(biāo)所選中的參數(shù)進(jìn)行加操作
按鍵2(中):對(duì)光標(biāo)所選中的參數(shù)進(jìn)行減操作
按鍵3(右):切換光標(biāo)選擇的內(nèi)容

PWM輸出燈:LED亮的程度反應(yīng)了輸出PWM的占空比大小,占空比越大,燈越亮,反之則越暗

串口:使用VOFA+軟件連接串口,CW32會(huì)將設(shè)定、實(shí)際溫度,Kp,Ki,Kd幾個(gè)參數(shù)打印到上位機(jī),顯示波形

-
電阻
+關(guān)注
關(guān)注
88文章
5796瀏覽量
179738 -
溫度控制
+關(guān)注
關(guān)注
7文章
312瀏覽量
39153 -
adc
+關(guān)注
關(guān)注
100文章
7623瀏覽量
556534 -
PID
+關(guān)注
關(guān)注
38文章
1503瀏覽量
91609
發(fā)布評(píng)論請(qǐng)先 登錄
CW32L012的PID溫度控制——算法基礎(chǔ)
FOC控制中如何利用芯片內(nèi)部的運(yùn)放設(shè)計(jì)電流采樣電路?
堅(jiān)持繼續(xù)布局32位MCU,進(jìn)一步完善產(chǎn)品陣容,96Mhz主頻CW32L012新品發(fā)布!
**CW32L012****開(kāi)發(fā)評(píng)估板的第一個(gè)程序**
CW32L012小型機(jī)器人控制評(píng)估板活動(dòng) 四足機(jī)器人+智能小車 開(kāi)箱評(píng)測(cè)
溫度控制的PID算法及C程序實(shí)現(xiàn)
基于CW32的PID溫度控制案例分享
如何使用PID進(jìn)行溫度控制
基于PID控制的溫度調(diào)節(jié)系統(tǒng)
堅(jiān)持繼續(xù)布局32位MCU,進(jìn)一步完善產(chǎn)品陣容,96Mhz主頻CW32L012新品發(fā)布!
CW32電機(jī)控制基礎(chǔ)——PID控制原理
使用芯源CW32的CW32L012開(kāi)發(fā)評(píng)估板做了spi屏幕驅(qū)動(dòng)
CW32L012小機(jī)器人的電機(jī)控制
CW32L012與STM32F103的三角運(yùn)算性能對(duì)比
CW32L012與STM32G431的CORDIC三角函數(shù)運(yùn)算性能對(duì)比
CW32L012的PID溫度控制——實(shí)現(xiàn)思路及代碼
評(píng)論