STM32CubeMX实战教学(1):基于STM32的PID温控系统设计(直流)

        在工业生产和科学实验中,温度常常是一个关键的因素,这时就要用到温控系统,在本文中将会基于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 */

五,效果:

都看到这里了点点关注和赞再走吧

交流Q_qun:659512171

  • 40
    点赞
  • 151
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值