STM32——PID恒温控制

原理

元件

   stm32f103核心板、L298N模块(当然用MOS管更好)、led一个、NPN三极管一个、蜂鸣器一个、DHT11一个、LCD1602一个、电阻200欧两个、可调电阻10K一个、加热丝一个

功能描述

  用DHT11检测当前环境温湿度,并将数据显示在LCD1602上,在用设定温度与当前温度相减,通过PID算法计算出当前输出脉宽,并将其加在L298N模块中,使加热丝发热,形成一个闭环,经过一段时间温度稳定在设定值。由于我的初衷是做一个恒温箱孵蛋,所以加了湿度报警。

原理图

在这里插入图片描述
注:此图只做原理图使用。

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频率很低时可以形象的按照上述所说来理解,在中高频时,PWM控制电热丝发热原理并不是上述那般,而是通过改变占空比达到改变输出电压的原理。
在这里插入图片描述
PWM调压是指调节平均电压,在图aaa中,这是一个幅值为12v12v12v、周期为ttt、占空比为 101010%\%%的PWM信号,在0.1t0.1t0.1t时间内输出是12v12v12v,那么这部分的面积(图aaa中矩形的面积)为S=0.1t∗12=1.2tS=0.1t*12=1.2tS=0.1t12=1.2t,那么这就相当于ttt时间内输出1.2t/t=1.2v1.2t/t=1.2v1.2t/t=1.2v的电压,如图bbb所示。

注:通电时间和断电时间可以理解为高电平持续时间和低电平持续时间。

补充: 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越大微分作用越强,即抑制效果越明显。控制系统容易受到外界的一些干扰,这些干扰往往都是小幅高频信号,比如N=0.1sin(1000t)N=0.1sin(1000t)N=0.1sin(1000t),对其微分求导后为n=100cos(1000t)n=100cos(1000t)n=100cos(1000t),此时的干扰幅值被放大了100010001000倍,所以微分控制容易受到干扰影响,在一些追求可靠性或者干扰严重的环境下,会避免使用微分控制。

整合后的PID算法公式如下:
在这里插入图片描述
或简写为:
blog.csdnimg.cn/73eedee622844cccba53d55582ff6fac.png)
  调节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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/545625.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第 6-2 课:SpringMVC 核心 + 面试题

Spring MVC 介绍 Spring MVC(Spring Web MVC)是 Spring Framework 提供的 Web 组件,它的实现基于 MVC 的设计模式:Controller(控制层)、Model(模型层)、View(视图层),提供了前端路由映射、视图解析等功能,让 Java Web 开发变得更加简单,也属于 Java 开发中必须要…

Python | 查找字符串中每个字符的频率

Given a string and we have to find the frequency of each character of the string in Python. 给定一个字符串&#xff0c;我们必须在Python中查找该字符串的每个字符的频率。 Example: 例&#xff1a; Input: "hello"Output:{o: 1, h: 1, e: 1, l: 2}Python c…

Lync2013 升级错误总结8 Lync2013 日志总是提示进程 RtcHost(5724) 收到了一个无效的客户端证书...

错误提示&#xff1a;解决方法&#xff1a;1打开注册表引导到&#xff1a;HKLM\System\CurrentControlSet\Control\SecurityProviders\Schannel2 新建一个DWORD键值&#xff1a;值的名称&#xff1a;EnableSessionTicket3 右键这个值点编辑讲数值数据修改成&#xff1a;24 重新…

简易的遍历文件加密解密

功能描述 将生成的可执行程序放在指定的文件夹内&#xff0c;双击后将该目录下所有文件包括子文件夹内文件全部加密&#xff0c;再次双击运行后将进行解密。 加密解密实现 主要运用了异或与取反操作&#xff0c;异或&#xff1a;两个值不同为1&#xff0c;相同为0。取反就是将该…

oracle 查看用户、权限、角色

查看用户、权限、角色的命令和视图1.查看所有用户&#xff1a; select * from dba_users; select * from all_users; select * from user_users; 2.查看用户系统权限&#xff1a; select * from dba_sys_privs; select * from session_privs; select * from user_sys_privs; 3.…

Java StackTraceElement getClassName()方法与示例

StackTraceElement类的getClassName()方法 (StackTraceElement Class getClassName() method) getClassName() method is available in java.lang package. getClassName()方法在java.lang包中可用。 getClassName() method is used to retrieve the fully qualified name of t…

django post and get?

GET是通过URL传给服务器的,POST是通过HTTP头传给服务器的&#xff0c;post的数据是不跟在请求的url后&#xff0c;而是在http头中&#xff0c;get是在url中 post的安全性比get高&#xff0c;因为有的服务器会缓存get数据&#xff0c;post数据不会被缓存&#xff0c;而且当你再次…

操作系统 测试题

一、单选 1、下面哪项不是常用调度算法 A、FCFS B、SJF C、HRN D、ABC 2、响应比的计算方法是 A、&#xff08;作业等待时间作业执行时间&#xff09;/作业执行时间 B、&#xff08;作业等待时间作业执行时间&#xff09;/作业等待时间 C、&#xff08;作业等待时间&#xf…

安卓手机使用linux(含图形界面)——Aid Learning

以前再安卓手机上使用linux系统都是使用Termux&#xff0c;安装上很麻烦&#xff0c;而且还是黑乎乎的窗口&#xff0c;没有图形界面&#xff0c;对于初学linux者来说并不友好&#xff0c;而Aid Learning就更人性化了&#xff0c;他是一种模拟的linux,其安装十分简易&#xff0…

第 6-1 课:Spring 核心 + 面试题

Spring Framework 简称 Spring,是 Java 开发中最常用的框架,地位仅次于 Java API,就连近几年比较流行的微服务框架 SpringBoot,也是基于 Spring 实现的,SpringBoot 的诞生是为了让开发者更方便地使用 Spring,因此 Spring 在 Java 体系中的地位可谓首屈一指。当然,如果想…

Java SecurityManager getThreadGroup()方法与示例

SecurityManager类的getThreadGroup()方法 (SecurityManager Class getThreadGroup() method) getThreadGroup() method is available in java.lang package. getThreadGroup()方法在java.lang包中可用。 getThreadGroup() method is used to return the thread group into whi…

通知主线程刷新

dispatch_async(dispatch_get_main_queue(), ^{ //回调或者说是通知主线程刷新转载于:https://www.cnblogs.com/ejllen/p/4153118.html

简易花式流水灯

先看看效果 具体思路 实现流水灯的效果其实就是控制相应的I/O口&#xff0c;以P2为例&#xff0c;通过有规律的改变P2各I/O口的状态就可实现相应规律的流水灯效果&#xff0c;这其中需要用到与、或、异或、左移、右移等操作。   流水灯向左闪烁点亮就是将P2最低位的1不断左移…

数据库 范式

关系数据库中的关系必须满足一定的要求。满足不同程度要求的为不同范式。数据库的设计范式是数据库设计所需要满足的规范。只有理解数据库的设计范式&#xff0c;才能设计出高效率、优雅的数据库&#xff0c;否则可能会设计出错误的数据库.目前&#xff0c;主要有六种范式&…

第 6-4 课:MyBatis 核心和面试题(上)

MyBatis 介绍 MyBatis 是一款优秀的 ORM(Object Relational Mapping,对象关系映射)框架,它可以通过对象和数据库之间的映射,将程序中的对象自动存储到数据库中。它是 Apache 提供的一个开源项目,之前的名字叫做 iBatis,2010 年迁移到了 Google Code,并且将名字改为我们…

Java LineNumberReader getLineNumber()方法及示例

LineNumberReader类的getLineNumber()方法 (LineNumberReader Class getLineNumber() method) getLineNumber() method is available in java.io package. getLineNumber()方法在java.io包中可用。 getLineNumber() method is used to return the present line number in this …

STM32——直流电机PI调速

所需元件 STM32F103开发板、L298N一个、带编码器的直流电机一个&#xff08;如下图所示&#xff0c;淘宝上有很多&#xff09; 系统框图 通过系统框图&#xff0c;我们需要做两件事&#xff0c;一是要测速&#xff0c;二是要调节。测速目前流行的就是通过编码器测速&#xff…

JAVA设计模式--简单介绍

2019独角兽企业重金招聘Python工程师标准>>> &#xfeff;一、简介 Design pattern 是众多软件开发人员经过漫长的试验和错误总结出来的在软件开发过程中面临一般问题的解决方案&#xff0c;代表着最佳实践。使用设计模式是为了重用代码、让代码更容易被他人理解、保…

简单函数

【【【2013-9-13】】】--模糊查询 关键字 like--与通配符联合使用&#xff08;_任意一个字符&#xff1b;%任意长度的字符&#xff1b;转义字符/和关键字escape联用&#xff09;select * from emp where job like %/%% escape /;select * from emp where comm is not null;com…

第 6-3 课:SpringBoot 核心 + 面试题

为什么要用 Spring Boot? Spring Boot 来自于 Spring 大家族,是 Spring 官方团队(Pivotal 团队)提供的全新框架,它的诞生解决了 Spring 框架使用较为繁琐的问题。Spring Boot 的核心思想是约定优于配置,让开发人员不需要配置任何 XML 文件,就可以像 Maven 整合 Jar 包一…