在工业生产和科学实验中,温度常常是一个关键的因素,这时就要用到温控系统,在本文中将会基于STM32设计一个温控系统
Github:SuiXinSc/STM32 (github.com)
程序放在Q_qun:659512171
目录
一,基础知识:
二,程序设计:
三,STM32CubeMX配置:
四,Keil编程:
1,测试:
(1)串口:
(2)ADC,PWM:
2,实际工程:
(1)电路设计:
(2)温度曲线拟合:
(3)PID控温:
(4)人机交互:
五,效果:
一,基础知识:
因为是温控系统,所以需要用一个温敏电阻(或热电偶)来检测实时温度(或者温度传感器,精度更高),而温敏电阻和热电偶都需要一个ADC来检测电压值计算温度,我在前面的一篇文章中讲过ADC+DMA读取电压 传送门
PID简介: PID,就是“比例(proportional)、积分(integral)、微分(derivative)”,是一种很常见的控制算法,目的是让被控制的量保持稳定。P,I,D是三种不同的调节作用,可以分开用(P,I,D)也可以组合使用(PI,PD,PID)
P:P像一个弹簧,当目标与现有的差距越大时,P的力度就越大,就可以使现有值更快相目标值靠近;如果差距不大,P的力度也就较小,使现有值缓慢的向目标值逼近。P的系数KP越大,调节速度越快,但是越不容易稳住。有了P的作用,会使实际值在目标值附近抖动。
D:D就像刹车,使被控制的物理量不再抖动,D的系数KD越大,物理量的波动越稳,但是调节速度越慢。
似乎只需要PD两个就可以实现控制了,那么I 是干什么的?
I:I 的作用是修正误差,举个例子,当烧水时外界很冷,热水热量散逸速度等于P的加热速度,温度就上不去了,D又寻思着温度没啥变化,它也不用干活,温度就一直上不去,这时就需要I 这个值来修正了。I 是一个积分量,只要误差存在,就会不断积分,最后反映在修正力度上。I的系数KI越大,积分修正效果越明显。
所以可以得出,P、I、D分别对应的是“现在,过去,未来”,PID算法结合了这三者,实现精准的调控,常听到的PID调参调的其实就是P、I、D的系数,使得更加精确
另外,还需要明白如何实现温控,既然是温控,就需要有加热器(有时候还需要冷却器),要想实现高精度控制就需要控制功率,这里根据电源类型又可以分为两种:直流和交流
直流:直流调功比较简单,使用PWM脉宽调制再加一个驱动电路(三极管)即可,如果想要得到电压调节(即直流调压)的效果,只需在后面串接一个电感。但是有一点要注意,直流控制功率不能使用可控硅,因为可控硅有一个保持电流,只要流过的电流不低于这个值就不会断开(类似于自锁开关)。
交流:交流调功相对而言就比较复杂了,通常而言,交流控制电路需要一个过零检测来提供过零信号,并且常常使用双向可控硅,根据控制方式又可将其分为两类:相位控制和零位控制。
相位控制:使用相位控制的可控硅电力控制器叫做调压器,通过改变每个正弦波正半波和负半波的导通角来控制电压的大小,进而调节输出电压和功率大小。它的优点是控制精度高,输出时电表不会抖动,但缺点是当负载容量较大时,会产生谐波污染电网。适用于定阻抗负载,变阻抗负载,感性负载,灯管等。
零位控制:也叫周波控制,使用零位控制的可控硅电力控制器叫做调功器,其原理是将交流电分为多个周期(比如每1800°为一个周期),在一个周期内负载与电源接通几个周波再断开几个周波,通过控制接通周波与断开周波之比控制功率。应用简单且不会产生谐波干扰,但是控制精度有所降低,输出时电表会抖动。多用于电热丝等定阻抗负载。
在这一篇里只做直流调功,以后会专门出一篇讲解交流控制。
当然,既然是温控,肯定少不了执行器,这里使用PWM脉宽调制,也就是调节PWM信号的占空比来调节功率,如何调节PWM占空比和频率在之前的博文里讲过 传送门
二,程序设计:
设计思路如下:使用ADC模块读取温敏电阻输出的电压值,计算出温度,然后使用PID算法计算PWM占空比。
三,STM32CubeMX配置:
打开STM32CubeMX,选择MCU,新建工程,配置RCC和调试
开启ADC和PWM,配置ADC的DMA
PWM里面的几个值我在之前的博文里都讲过,这里不再重复 传送门
为了方便调试,这里把串口打开
配置时钟树
配置工程
然后点击右上角的GENERATE CODE生成文件,点击Open Project打开工程。
四,Keil编程:
1,测试:
(1)串口:
打开工程,先编译一次,没有报错,打开usart.c,编写串口重定向
/* USER CODE BEGIN 1 */
//发送的重定向,重定向以后可以使用printf等函数
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
//接收的重定向,重定向以后可以使用scanf等函数
int fgetc(FILE *f)
{
int ch;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);
return (ch);
}
/* USER CODE END 1 */
如果使用printf函数需要使用微库(Micro LIB),可在魔术棒里打开
(2)ADC,PWM:
然后再打开main.c,编写测试程序
/* USER CODE BEGIN 2 */
uint32_t adc = 0;
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_ADC_Start_DMA(&hadc1,&adc,1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_ADC_Start_DMA(&hadc1,&adc,1);
printf("%d\r\n",adc);
HAL_Delay(500);
}
/* USER CODE END 3 */
然后编译,下载,连接好串口调试助手,观察现象
可以看到,ADC已经正常读取了(这时还没有接入温敏电阻) ,然后更改程序,测试PWM脉宽调制
/* USER CODE BEGIN 2 */
uint32_t adc = 0;
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_ADC_Start_DMA(&hadc1,&adc,1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_ADC_Start_DMA(&hadc1,&adc,1);
printf("%d\r\n",adc);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,adc/41);
HAL_Delay(500);
}
/* USER CODE END 3 */
在PA0上接一个LED,改变PA1的电压,可以看到LED的亮度跟随电压的变化而变化,这里将adc的值除以41是因为adc的最大值为4095,但由于我们设置的PWM自动重装值为100,当脉冲长度设为100时,已经是100%占空比了,所以需要除以一个41
2,实际工程:
(1)电路设计:
测试结束,接下来需要先将外围电路设计好
这里面R1是温敏电阻的分压电阻,当Rt的阻值改变时,PA1的电压也会改变,R1根据使用的温敏电阻参数选择, 一般选取与温敏电阻常温阻值接近的电阻(当时选错了电阻,用的是100k的温敏电阻和47k的定值电阻)
(2)温度曲线拟合:
温敏电阻的阻值变化不是线性的,其电阻与温度的关系可近似于二次函数的一部分,所以需要先连接好电路,使用测试程序测得在不同温度下的ADC值(一定要有两个近似于极端值的值,不然拟合出来的结果使用时可能会出错),列表记录多组数据(不少于3组),然后找一个网站拟合曲线得到函数解析式(以ADC值为x,温度为F(x) ),以后会出一篇文章讲讲最小二乘拟合函数
使用这种方法不需要将ADC变换为阻值再对照出温度,直接用ADC的值就可以计算出温度
计算出解析式后,放到程序中,测试输出
可以看到,拟合精度还是比较高的
(3)PID控温:
创建两个文件,control.c 和control.h 用于编写控制函数(不创建在main.c中写也可以,取决于个人喜好),编写PID控制程序
control.c:
#include "control.h"
#include "tim.h"
#define KP 3.0 // 比例系数
#define KI 0.1 // 积分系数
#define KD 0.03 // 微分系数
double PWM = 0.0; //控制信号
double integral = 0.0; // 积分项,历史误差
double derivative = 0.0; // 微分项,变化趋势
double Error = 0.0; //当前误差
double LastError = 0.0; //上次误差
void PID_Control(double Now,double Set){
/*PID算法*/
Error = Set - Now;
integral += Error;
derivative = Error - LastError;
PWM = KP * Error + KI * integral + KD * derivative;//
LastError = Error;
/*约束占空比的值*/
if(PWM > 100){
PWM = 100;
}else if(PWM < 0){
PWM = 0;
}
/*更新占空比*/
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,PWM);
}
control.h:
#ifndef __CONTROL_H_
#define __CONTROL_H_
#include "main.h"
#include "math.h"
void PID_Control(double Now,double Set);
#endif
然后在主程序中包括 control.h,应用PID_Control函数
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "control.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 2 */
uint32_t adc = 0;
int temp;
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_ADC_Start_DMA(&hadc1,&adc,1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_ADC_Start_DMA(&hadc1,&adc,1);
temp = 0.0000031352*adc*adc+0.000414*adc+8.715;
printf("%d\r\n",temp);
PID_Control(temp,20);
HAL_Delay(500);
}
/* USER CODE END 3 */
连接好外围电路,编译,下载,运行,可以发现温度已经稳定到设定温度了,下一步就是写一个程序来更改设定温度(也就是人机交互),这里直接在Keil中定义几个GPIO,使用按键更改,通过串口返回当前设定值
(4)人机交互:
gpio.c:
void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef GPIO_Initstruct;
GPIO_Initstruct.Pin = GPIO_PIN_12|GPIO_PIN_13;
GPIO_Initstruct.Mode = GPIO_MODE_INPUT;
GPIO_Initstruct.Pull = GPIO_PULLUP;
GPIO_Initstruct.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(GPIOB,&GPIO_Initstruct);
}
main.c:
/* USER CODE BEGIN 2 */
uint32_t adc = 0;
double set_temp,temp;
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_ADC_Start_DMA(&hadc1,&adc,1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/*查询按键是否按下*/
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12) == 0){
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12) == 0){
set_temp += 1;
while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12) == 0);
}
}else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13) == 0){
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13) == 0){
set_temp -= 1;
while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13) == 0);
}
}
/*约束温度设定值*/
if(set_temp > 50){
set_temp = 50;
}else if(set_temp < 0){
set_temp = 0;
}
/*读取ADC,串口返回数据,PID控制*/
HAL_ADC_Start_DMA(&hadc1,&adc,1);
temp = 0.0000031352*adc*adc+0.000414*adc+8.715;
printf("Set temputer: %d\r\n",(int)set_temp);
printf("Now temputer: %d\r\n",(int)temp);
printf("\r");
PID_Control(temp,set_temp);
HAL_Delay(80);
}
/* USER CODE END 3 */
五,效果:
都看到这里了点点关注和赞再走吧文章来源:https://www.toymoban.com/news/detail-815245.html
交流Q_qun:659512171文章来源地址https://www.toymoban.com/news/detail-815245.html
到了这里,关于STM32CubeMX实战教学(1):基于STM32的PID温控系统设计(直流)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!