原理

元件
   stm32f103核心板、L298N模块(当然用MOS管更好)、led一个、NPN三极管一个、蜂鸣器一个、DHT11一个、LCD1602一个、电阻200欧两个、可调电阻10K一个、加热丝一个
功能描述
  用DHT11检测当前环境温湿度,并将数据显示在LCD1602上,在用设定温度与当前温度相减,通过PID算法计算出当前输出脉宽,并将其加在L298N模块中,使加热丝发热,形成一个闭环,经过一段时间温度稳定在设定值。由于我的初衷是做一个恒温箱孵蛋,所以加了湿度报警。
原理图

注:此图没有做stm32的最小系统,故只做原理图使用,不可仿真。

DHT11时序图

总体时序图

  DHT11与MCU通讯一次时间在4ms左右,数据分小数部分和整数部分,一次完整的数据传输为40bit,高位先出。
  数据格式: 8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和
  数据传送正确时校验和数据等于“ 8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据” 所得结果的末8位
  MCU发送一次开始信后,DHT11从低功耗模式转换到高速模式等待主机开始信号结后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据。从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送的开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。

初始化

  总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。 DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送80us低电平响应信号.主机发送开始信号结束后,延时等待20-40us后, 读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可, 总线由上拉电阻拉高。

信号“0”

  总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据,每一bit数据都以50us低电平时隙开始,高电平持续的时间在26-28us之间表示0

信号“1”

  总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据,每一bit数据都以50us低电平时隙开始,高电平持续的时间达到70us表示1

PWM脉宽调制
  我们要控制箱内温度就要控制电热丝的发热量,通电时电热丝发热,其向箱内输入的热量大于箱子向外散失的热量,箱内温度升高;断电时,电热丝不在产热,但其仍有余温,其依然能向箱内输入热量,如果在断电前电热丝的温度已经很高了,则在断电后的前一段时间内依然会向箱子输入大量的热量,箱内温度还会上升,但是一段时间后,电热丝自身温度降低,向箱内输入的热量小于箱子向外散失的热量,箱内温度就会降低。故而通过调节通电时间和断电时间就可以控制电热丝的发热量和箱子散热量。以PWM控制开关器件从而控制电热丝能够满足上述要求,调节占空比就可以达到调节通电时间和断电时间的目的。
  PWM中还有一个重要的参数就是频率,尽管不能计算出这个值到底是多少,但也不能随意设置,频率过高会导致开关器件的开关损耗增大,发热较为严重;频率过低会导致输出响应速度变慢,系统调节时间增长,因为在频率很低的情况下,电热丝的通电时间或者断电时间就会变得很长,如果通电时间长则断电时间就会相应的缩短,那么电热丝产生的热量就会很多,甚至温度已经超过设定值电热丝仍在发热,而即使此时电热丝断电,在自身的高温下箱内温度还会持续上升,甚至电热丝温度还未下降多少又开始加热了;同理,如果断电时间很长的情况下,箱内温度已经低于设定值很多了但断电时间还没过去,温度还会持续下降,即使此时电热丝开始加热,温度还未回升可能电热丝就断电了。如此往复,温度值震荡的会很厉害,调节时间会变得很长。或许你会问,系统及时做出反应,修正占空比不就可以避免温度过高或者过低了吗?系统的及时性是有限度的,系统每采样一次就会做出一次修正,但是采样周期不能小于PWM周期,过于频繁的更改占空比不仅会导致控制器输出波形变形,还会使电热丝来不及做出反应(箱内温度变化较慢)。
  在PWM频率很低时可以形象的按照上述所说来理解,在中高频时,PWM控制电热丝发热原理并不是上述那般,而是通过改变占空比达到改变输出电压的原理,调低占空比,那么高电平时间减小,故而电热丝两端电压降低,则发热量降低。
注:通电时间和断电时间可以理解为高电平持续时间和低电平持续时间。

补充: MOS管驱动
  后来有朋友问我MOS管驱动电路的问题,于是就在做些补充,介绍几种MOS驱动电路。当然如果用MOS管驱动芯片那是最好的。之所以需要MOS管驱动电路是因为单片机IO口输出电压电流都比较小,驱动能力低,如果单片机输出PWM信号来控制MOS管,驱动波形有可能会有所形变,最重要的是栅极电压低不能使MOS管完全导通,其自身内阻较大,发热严重。

  上图是IRF3205的输出特性曲线图,竖轴是ID电流,横轴是UDS电压,图中的几条曲线分别代表UGS=4.5v、5v、5.5v、6v、7v、8v、10v、15v时ID随UDS变化而变化的情况,在UDS=10v、UGS=8v时,ID大概在300A左右了,说明此时MOS管内阻已经很小了,继续增大UGS,ID还能继续增大,UGS=15v时,ID大概在600A,为了方便使用,UGS=12v就可以了。

  其中IRF4905使P沟道MOS管,IRF3205是N沟道MOS管,s8050是NPN三极管,s9012是PNP三极管。5个电路中三极管都工作在饱和区,所以基极与信号输入之间串联的电阻不可取得太大,但也不能没有,否则IO口输出高电平导通三极管后,电压被强行钳位在Ube上,串联一个电阻不仅可以限流还可以分压;在每个电路中,信号输入后面都有一个二极管,它可以起到保护单片机IO口的作用(在一次实验中,我还没有给单片机通电,但先通了12V的电源,因为单片机用电和后面电热丝用电没有隔离,接通12V电源瞬间stm32核心板上面的指示灯也亮了,赶紧断电然后加上了二极管,之后就没出现这种情况了,所以低压控制高压最好用隔离器件,像继电器、光耦,在频率不是很高但功率较大的情况下,可以用光耦驱动MOS管,MOS管带负载);图中所有MOS管的G极并了一个12V的稳压二极管,这个二极管可以不要;图中所有MOS管的G极上都有一个二极管与10欧电阻并联,这个二极管可起到迅速关断MOS管的效果,当然这是对N沟道MOS管而言,对P沟道MOS管能不能起到迅速导通还未可知,有时间试验一下;三极管集电极上串联的电阻有限流分压作用,阻值过大会导致MOS管驱动能力不足,过小三极管发热严重,经过实验,取4~5K合适(这是对12V电源而言的);还有就是P沟道的MOS管电流要从S极流入,D极流出,N沟道MOS管相反。
  我们以图a为例分析一下电路,这个三极管当作开关管使用,当高电平信号来临时,三极管Q2导通,其集电极电压拉低,MOS管G极也为低MOS管导通;当低电平信号来临时,三极管Q2截至,其集电极电压被拉高,MOS管G极也为高,MOS管截止。

PID算法

框图

比例控制
  用户设定值Sv表示最终将温度稳定在Sv,从系统运行开始每隔一段时间就采集当前环境内的温度,得样本如下:

这些样本数据也就是程序中的Pv,通过这些样本数据我们可以知道当前环境温度与用户设定值之间的差值Ek,即Ek=Sv-Xk,Ek有三种情况,如表所示:

Ek 说明
Ek > 0 当前环境温度未达标
Ek = 0 当前环境温度满足要求
Ek < 0 当前环境温度已超标

  由于我们是通过改变PWM的占空比来调节电热丝输出功率的,PID算法所计算出的数值OUT(最终输出值,OUT=Pout+Iout+Dout) 就是PWM的脉宽。故而在Ek > 0时我们需要加大Pout从而加大OUT来提高电热丝输出功率,Ek < 0时则降低Pout从而降低电热丝输出功率,得:

这种算法称为比例控制算法,out0是一个常数,可设置为1,以避免Ek = 0时Out也等于0,Kp表示比例系数,其大小将直接影响系统的响应速度,不难理解,如果Kp很大,那么一个小的差值Ek也会得到一个较大的数值Pout,那么系统将会出现剧烈震荡,很难达到稳定,同样的道理,Kp过小则系统的响应速度太慢,尽管有一个很大的差值Ek也只能得到一个较小的数值Pout,故而系统需要很长一段时间才能达到稳定状态附近。

积分控制
  通过用户设定值Sv与我们采集的环境温度数据做差,我们得到了差值Ek,由于连续的温度采集,于是有了一系列的差值样本:

其中:

将这些差值进行累加,得:

同样,SE也有三种情况,如表:

SE 说明
SE > 0 历史数据大多数未达标甚至从未达标
SE = 0 控制效果较理想
SE < 0 历史数据大多数超标甚至是一直在超标

有此可知,SE的正负可以反映出历史温度处于哪种阶段,对于SE > 0,历史温度大多数未达标则将加大Iout从而加大OUT来提高电热丝输出功率,同理,SE < 0时,历史温度大多数超标则将降低Iout从而*降低电热丝输出功率。这种算法称为积分控制算法。公式如下:

在单片机中,对于积分运算可近似变换,即:

我们知道求积分其实就是求面积,如下图所示,abcd所围城图形的面积就是积分所求的面积,矩形abce的面积 Ek * T 就是近似变换球的的面积,其中 T 表示采样时间。

由上式可得积分控制算法可写为:

Kp表示比例系数,T表示采样时间,out0是一个常数,避免历史差值积分为0时无输出值,Ti表示积分时间,其大小会影响 Iout 的大小,从而影响OUT的大小,当Ti的值很大时,环境温度需要较长的时间才能回到设定值,无论当前环境温度大于还是小于设定值,当Ti较小时,环境温度波动会比较大,且震荡衰减小。

微分控制
  前面我们获得了差值样本,那么最近两次差值之差可表示为:

同样,Dk也有三种情况,如下表:

Dk 说明
Dk > 0 差值有增大趋势
Dk = 0 差值趋势平稳
Dk< 0 差值有减小趋势

  Dk能反应最近两次采样的温度的状态变化趋势,Dk的绝对值越大表明温度变化速率越大,由Dk的公式可知,温度呈上增长时,Dk为负数,增长速率越大,Dk越小,当温度呈下降低时,Dk为正数,下降速率越大,Dk越大。由此可知Dk具有抑制温度变化的功能,并使其趋于稳定。数学模型可表达为:

在单片机中,微分可做近似变换,即:

从而有:

Kp表示比例系数,out0是一个常数,避免Dk为0时取输出信号,T表示采样时间,Td表示微分时间,Td越大微分作用越强,即抑制效果越明显。

整合后的PID算法公式如下:

或简写为:

  调节Kp、Ki、Kd三个值从而调节系统输出,以改善系统响应效果。

比例系数与积分时间的大小对曲线的影响如下:

注:以上均属个人片面之理解,有误之处请留言,我很愿意将这篇文章完善的更好

程序

LedAndBeep.h

#ifndef _LEDANDBEEP_H
#define _LEDANDBEEP_H#include "sys.h"
#include "DHT11.h"#define led_1 GPIO_SetBits(GPIOB,GPIO_Pin_0)
#define led_0 GPIO_ResetBits(GPIOB,GPIO_Pin_0)#define beep_1 GPIO_SetBits(GPIOB,GPIO_Pin_1)
#define beep_0 GPIO_ResetBits(GPIOB,GPIO_Pin_1)void GPIO_init_Alert(void);
void Delay_ms(int k);
void Alert(void);#endif

LedAndBeep.c

#include "LedAndBeep.h"
#include "PID.h"void GPIO_init_Alert()
{GPIO_InitTypeDef Alert_GPIO;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);Alert_GPIO.GPIO_Mode = GPIO_Mode_Out_PP;Alert_GPIO.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;Alert_GPIO.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &Alert_GPIO);led_0;beep_0;
}void Alert()
{if((DHT_Data[0]>70)||(DHT_Data[0]==70)||(DHT_Data[0]<45)||(DHT_Data[0]==45))//湿度不在45~70之间就报警{led_1;if(pid.C10ms<(pid.T/2))//pid.C10ms在中断函数中,蜂鸣器响的时间小于250msbeep_1;elsebeep_0;}else{led_0;beep_0;}
}

DHT11.h

#ifndef __DHT11_H
#define __DHT11_H
#include "sys.h"   extern char DHT_Data[5];//IO方向设置
#define DHT11_IO_IN()  {GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=8<<12;}
#define DHT11_IO_OUT() {GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=3<<12;}
IO操作函数
#define DHT11_DQ_OUT PBout(11) //数据端口   PB11输出
#define DHT11_DQ_IN  PBin(11)  //数据端口   PB11输入u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(void);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11
#endif

DHT11.c

#include "DHT11.h"
#include "delay.h"
#include "PID.h"char DHT_Data[5]={0}; // DHT_Data[0]、DHT_Data[1]存储湿度数据
//DHT_Data[2]、DHT_Data[3]存储温度数据
void DHT11_Rst(void)
{                 DHT11_IO_OUT();   //SET OUTPUTDHT11_DQ_OUT=0;    //拉低DQdelay_ms(20);     //拉低至少18msDHT11_DQ_OUT=1;  //DQ=1 delay_us(30);       //主机拉高20~40us
}u8 DHT11_Check(void)
{   u8 retry=0;DHT11_IO_IN();//SET INPUT    while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us{retry++;delay_us(1);};     if(retry>=100)return 1;else retry=0;while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us{retry++;delay_us(1);}if(retry>=100)return 1;       return 0;
}u8 DHT11_Read_Bit(void)
{u8 retry=0;while(DHT11_DQ_IN&&retry<100)//等待变为低电平{retry++;delay_us(1);}retry=0;while(!DHT11_DQ_IN&&retry<100)//等待变高电平{retry++;delay_us(1);}delay_us(40);//等待40usif(DHT11_DQ_IN)return 1;else return 0;
}u8 DHT11_Read_Byte(void)
{        u8 i,dat;dat=0;for (i=0;i<8;i++) {dat<<=1; dat|=DHT11_Read_Bit();}                          return dat;
}u8 DHT11_Read_Data(void)
{        u8 i;DHT11_Rst();if(DHT11_Check()==0){for(i=0;i<5;i++)//读取40位数据{DHT_Data[i]=DHT11_Read_Byte();}if((DHT_Data[0]+DHT_Data[1]+DHT_Data[2]+DHT_Data[3])==DHT_Data[4]){pid.Pv=DHT_Data[2]+(DHT_Data[3]/10);return 0;     }}elsereturn 1;return 0;
}u8 DHT11_Init(void)
{    GPIO_InitTypeDef  GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  //使能PG端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;               //PG11端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;         //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);              //初始化IO口GPIO_SetBits(GPIOB,GPIO_Pin_11);                        //PG11 输出高DHT11_Rst();  //复位DHT11return DHT11_Check();//等待DHT11的回应
} 

LCD1602.h

#ifndef LCD1602_H
#define LCD1602_H#include "sys.h"#define RS GPIO_Pin_8    //设置PB8为RS
#define RW GPIO_Pin_6   //PB6为RW
#define EN GPIO_Pin_7   //PB7为EN使能void ReadBusy(void);
void LCD_WRITE_CMD( char CMD );
void LCD_WRITE_StrDATA( char *StrData, char row, char col );
void LCD_WRITE_ByteDATA( char ByteData );
void LCD_INIT(void);
void GPIO_INIT(void);#endif

LCD1602.c

#include "LCD1602.h"
#include "delay.h"void GPIO_INIT(void)
{       //GPIO初始化GPIO_InitTypeDef GPIO;GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //禁用jtagRCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC, ENABLE );GPIO.GPIO_Pin = EN|RW|RS;GPIO.GPIO_Mode = GPIO_Mode_Out_PP;GPIO.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO);GPIO.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;GPIO.GPIO_Mode = GPIO_Mode_Out_PP;GPIO.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO);}void LCD_INIT(void)
{   //初始化GPIO_INIT();       GPIO_Write(GPIOA, 0x0000);      GPIO_Write(GPIOB, 0x0000);delay_us(500);    LCD_WRITE_CMD(0x38);LCD_WRITE_CMD(0x0d);    //开启光标和闪烁LCD_WRITE_CMD(0x06);LCD_WRITE_CMD(0x01);
}void LCD_WRITE_CMD(char CMD)
{//写入命令函数ReadBusy();GPIO_ResetBits(GPIOB, RS);GPIO_ResetBits(GPIOB, RW);GPIO_ResetBits(GPIOB, EN);GPIO_Write(GPIOA, CMD);       //GPIO_SetBits(GPIOB, EN);GPIO_ResetBits(GPIOB, EN);
}void LCD_WRITE_ByteDATA(char ByteData )
{   //写入单个Byte函数ReadBusy();GPIO_SetBits(GPIOB, RS);GPIO_ResetBits(GPIOB, RW);GPIO_ResetBits(GPIOB, EN);GPIO_Write(GPIOA, ByteData);GPIO_SetBits(GPIOB, EN);GPIO_ResetBits(GPIOB, EN);
}void LCD_WRITE_StrDATA(char *StrData,char row, char col)
{//写入字符串char baseAddr = 0x00;          //定义256位地址if (row){baseAddr = 0xc0;}else{baseAddr = 0x80;                                                                                }    baseAddr += col;while (*StrData != '\0'){LCD_WRITE_CMD( baseAddr );LCD_WRITE_ByteDATA( *StrData);  baseAddr++;             StrData++;}
}void ReadBusy(void)
{       //读忙函数,读忙之前记得更改引脚的工作方式!!!因为STM32的IO不是准双向IOGPIO_InitTypeDef GPIO;GPIO_Write(GPIOA, 0x00ff);  GPIO.GPIO_Pin = GPIO_Pin_7;        //选定GPIOA的第七PinGPIO.GPIO_Mode = GPIO_Mode_IN_FLOATING; //第七Pin的工作方式为浮空输入模式,用于检测LCD1602的忙状态GPIO.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO);GPIO_ResetBits(GPIOB, RS);//RS拉低GPIO_SetBits(GPIOB, RW);//RW拉高GPIO_SetBits(GPIOB, EN);    //使能开while( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7 )); //读第七Pin状态,如果一直为1则循环等待GPIO_ResetBits(GPIOB, EN);//使能关GPIO.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;       //使GPIOA的状态还原成推挽模式GPIO.GPIO_Mode = GPIO_Mode_Out_PP;GPIO.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO);
}

PID.h

#ifndef PID_H_
#define PID_H_typedef struct Pid
{float Sv;//用户设定值float Pv;float Kp;int T;  //PID计算周期--采样周期float Ti;float Td; float Ek;  //本次偏差float Ek_1;//上次偏差float SEk; //历史偏差之和float Iout;float Pout;float Dout;float OUT0;float OUT;int C1ms;int pwmcycle;//pwm周期int times;
}PID;extern PID pid;void PID_Init(void);
void PID_Calc(void);#endif

PID.c

#include "PID.h"PID pid;void PID_Init()
{pid.Sv=38;//用户设定温度pid.Kp=30;pid.T=400;//PID计算周期pid.Ti=4000000;//积分时间pid.Td=1000;//微分时间pid.pwmcycle=200;//pwm周期200pid.OUT0=1;pid.C1ms=0;
}void PID_Calc()  //pid计算
{float DelEk;float ti,ki;float td;float kd;float out;if(pid.C1ms<(pid.T))  //计算周期未到{return ;}pid.Ek=pid.Sv-pid.Pv;   //得到当前的偏差值pid.Pout=pid.Kp*pid.Ek;      //比例输出pid.SEk+=pid.Ek;        //历史偏差总和DelEk=pid.Ek-pid.Ek_1;  //最近两次偏差之差ti=pid.T/pid.Ti;ki=ti*pid.Kp;pid.Iout=ki*pid.SEk;  //积分输出td=pid.Td/pid.T;kd=pid.Kp*td;pid.Dout=kd*DelEk;    //微分输出out= pid.Pout+ pid.Iout+ pid.Dout;if(out>pid.pwmcycle){pid.OUT=pid.pwmcycle;}else if(out<=0){pid.OUT=pid.OUT0; }else {pid.OUT=out;}pid.Ek_1=pid.Ek;  //更新偏差pid.C1ms=0;
}

PWMOUT.h

#ifndef PWMOUT_H
#define PWMOUT_H#include "sys.h"void Time_init(void);
void TimePwm_init(int arr,int psc);#endif

PWMOUT.c

#include "PWMOUT.h"
#include "PID.h"
#include "LedAndBeep.h"void Time_init(void)
{NVIC_InitTypeDef  NVIC_InitStructure;TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 自动装载的计数值   1msTIM_TimeBaseStructure.TIM_Period = 1000; // 10KHzTIM_TimeBaseStructure.TIM_Prescaler = (72 - 1); // 1MHzTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);TIM_ClearITPendingBit(TIM2, TIM_IT_Update);TIM_Cmd(TIM2,ENABLE);
}void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2,TIM_IT_Update)){pid.C1ms++;Alert();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);   //清除中断标志}
}void TimePwm_init(int arr,int psc)
{GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_OCInitTypeDef  TIM_OCInitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseStructure.TIM_Period = arr; TIM_TimeBaseStructure.TIM_Prescaler =psc; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;TIM_OC2Init(TIM3, &TIM_OCInitStructure);TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  TIM_Cmd(TIM3, ENABLE);
}

main.c

#include "LCD1602.h"
#include "DHT11.h"
#include "LedAndBeep.h"
#include "PID.h"
#include "PWMOUT.h"
#include "delay.h"
#include <string.h>
#include <stdio.h>#define PERIOD    400
#define PRESCALER 36000
void Situation()
{char hum[5]={0},temp[5]={0},PWM[10]={0},arr[5]={0x20,0x20,0x20,0x20,0x20};sprintf(hum,"%d.%d",DHT_Data[0],DHT_Data[1]);sprintf(temp,"%d.%d",DHT_Data[2],DHT_Data[3]);//显示湿度LCD_WRITE_StrDATA( hum,0,5 );   LCD_WRITE_StrDATA("%",0,9 ); //显示温度LCD_WRITE_StrDATA( temp,0,11); LCD_WRITE_StrDATA("C",0,15 );//显示pid.outLCD_WRITE_StrDATA("pid.out:",1,0);  sprintf(PWM,"%f",pid.OUT);PWM[6]='\0';LCD_WRITE_StrDATA(PWM,1,9);
}int main()
{unsigned int num=0;GPIO_init_Alert();Time_init();DHT11_Init();PID_Init();LCD_INIT();  LCD_WRITE_CMD( 0x80 );              LCD_WRITE_CMD(0x0C);    LCD_WRITE_StrDATA( "situ:",0,0 ); TimePwm_init(PERIOD-1,PRESCALER);while(1){while(DHT11_Read_Data());PID_Calc();num=(((pid.OUT*PERIOD)/pid.pwmcycle)-1);TIM_SetCompare2(TIM3,num);Situation();                                                                                                                                                                                   }
}

工程此处下载
链接:https://pan.baidu.com/s/1idVRZF63PP-Yq9c3-uDhPQ
提取码:2j3v

STM32——PID恒温控制相关推荐

  1. [STM32]PID恒温加热台(DIY回流焊)

    考试结束之后在家,终于觉得有必要把之前鸽了很久得ESP32驱动墨水屏的项目提上日程. 但是俗话说的好,工欲善其事,必先利其器,我加工好了PCB,在某宝买焊锡浆的时候瞥到一个220V某仙子的加热台,10 ...

  2. 【毕业设计】基于stm32的智能水杯 - 恒温控制 饮水杯 单片机 物联网 嵌入式

    文章目录 0 简介 1 项目介绍 2 系统设计 3 恒温控制实现 3.1 功能描述 3.2 PID算法原理 3.2.1 P:比例 3.2.2 I:积分 3.2.3 D:微分 3.3 温控代码实现 4 ...

  3. 【毕业设计】基于stm32的智能水杯(恒温控制 饮水杯) - 单片机 物联网 嵌入式

    文章目录 0 简介 1 项目介绍 2 系统设计 3 恒温控制实现 3.1 功能描述 3.2 PID算法原理 3.2.1 P:比例 3.2.2 I:积分 3.2.3 D:微分 3.3 温控代码实现 4 ...

  4. 【毕业设计】stm32智能水杯设计与实现(恒温控制) - 单片机 物联网 嵌入式

    文章目录 0 简介 1 项目介绍 2 系统设计 3 恒温控制实现 3.1 功能描述 3.2 PID算法原理 3.2.1 P:比例 3.2.2 I:积分 3.2.3 D:微分 3.3 温控代码实现 4 ...

  5. 毕业设计 stm32智能恒温水杯 - 单片机 物联网 嵌入式

    文章目录 0 简介 1 项目介绍 2 系统设计 3 恒温控制实现 3.1 功能描述 3.2 PID算法原理 3.2.1 P:比例 3.2.2 I:积分 3.2.3 D:微分 3.3 温控代码实现 4 ...

  6. c语言程序设计电加热炉,基于80C52单片机的电加热数字恒温控制系统设计

    1 控制方案设计 温度场是一个梯度场,温度的上升或下降随时间缓慢变化.电加热炉温度控制过程可以用自然降温.程序升温和恒温保持3个分过程来描述.自然降温:停止加热,环境温度在整个过程中保持不变,受控温度 ...

  7. GCKontrol与嵌入式系统:STM32实现电机控制案例

    摘要:系统设计与仿真软件GCKontrol能够对控制系统进行建模.仿真与测试.软件基于模型自动生成的代码能够满足嵌入式系统要求.GCKontrol的模型属于"应用层软件",能够与& ...

  8. 基于西门子200smart系列化工反应釜程序 系统进行两路PID恒温升压调节

    基于西门子200smart系列化工反应釜程序,该程序仅用于学习探讨. 功能: 1.系统进行两路PID恒温升压调节 : 2.两路PID手自动切换: 3.压力.温度等检测. 具有如下控制: 参数设置.报警 ...

  9. 单片机毕业设计-02:PID恒温算法之温控器及原理讲解

    上一篇文章提到 单片机毕业设计-01:基于涂鸦APP控制的无线控制灯 可以扩展为涂鸦APP + 风扇,空气净化器,宠物用品相关等等,都是比较简单的项目.项目虽小,五脏俱全,完全可以扩展成一个毕业设计. ...

最新文章

  1. ylbtech-LanguageSamples-SimpleVariance
  2. 数据清洗的基本流程_数据分析小白学习之路(三)——Excel多练熟能生巧
  3. 奥委会主席巴赫与马云对谈:阿里巴巴能将奥运精神带进科技时代
  4. spring boot基础配置
  5. 那个好好玩的特效,每个人都可以做到哦
  6. 群同态基本定理证明_群论(7): 群代数, 群表示基础
  7. 00002-两数之和-leetcode-1.暴力法(枚举法),2.哈希表法,目前更新了枚举法
  8. ccf认证俄罗斯方块java_CCF认证历年试题 - osc_h3robkrt的个人空间 - OSCHINA - 中文开源技术交流社区...
  9. 新炬首架梁铭图:从70万字SRE神作提炼出7千字精华与君共勉
  10. sql获取某列出现频次最多的值_那些SQL里面踩过的坑
  11. 用python画皮卡丘-利用Python绘制萌萌哒的皮卡丘
  12. 博科 HP H3C 华为FCSAN交换机配置业务zone以及联级配置
  13. 【C语言学习】sscanf的简单使用.
  14. TEST语言编译器--语法分析
  15. H264/HEVC 常用缩写词及其含义
  16. 单片机:c语言实现秒表计数(按键开始,结束,重置)
  17. java 10个随机数排序_java随机数排序的问题
  18. 【数据挖掘】十大算法之SVM支持向量机分类算法
  19. spi总线 上层调用_spi总线的mmc卡驱动调试总结 | 学步园
  20. 上海亚商投顾:科创50指数录得6连阳 芯片股掀涨停潮

热门文章

  1. PHP 开启或关闭错误提示
  2. textmate bundle for jquery
  3. typescript 学习
  4. linux系统编程之文件与I/O(六):fcntl 函数与文件锁
  5. C# 值类型与引用类型(1)
  6. win2003服务器 虚拟主机安全配置
  7. 这几天惨遭Delphi类型转换折磨,请问怎么把double转成int类型
  8. catcti监控linux主机,CentOS7搭建Prometheus 监控Linux主机
  9. python语言中内置的字符串排版方法_Python14之字符串(各种奇葩的内置方法)
  10. Java RandomAccessFile getFilePointer()方法与示例