STM32——PID恒温控制

元件
stm32f1核心板、L298M模块、led一个、三极管一个、蜂鸣器一个、DHT11一个、LCD1602一个、电阻10欧10K欧、可调电阻10K、加热丝
功能描述
用DHT11检测当前环境温湿度,并将数据显示在LCD1602上,在用设定温度与当前温度相减,通过PID算法计算出当前输出脉宽,并将其加在L298N模块中,使加热丝发热,形成一个闭环,经过一段时间温度稳定在设定值。由于我的初衷是做一个恒温箱孵蛋,所以加了湿度报警。
电路图
STM32——PID恒温控制
DHT11时序图
总体时序图
STM32——PID恒温控制
初始化
STM32——PID恒温控制
信号“0”
STM32——PID恒温控制
信号“1”
STM32——PID恒温控制
PID算法
STM32——PID恒温控制

程序
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在中断函数中,蜂鸣器响的时间小于250ms
				beep_1;
			else
				beep_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 OUTPUT
  DHT11_DQ_OUT=0; 	//拉低DQ
  delay_ms(20);    	//拉低至少18ms
  DHT11_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);//等待40us
	if(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;	 
		}
	}
	else
		return 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();  //复位DHT11
	return DHT11_Check();//等待DHT11的回应
} 

LCD1602.h

#ifndef LCD1602_H
#define LCD1602_H

#include "sys.h"

#define BUSY 0x80		//忙标志
#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);
void WUserImg(char pos,char *ImgInfo);

#endif

LCD1602.c

#include "LCD1602.h"
#include "delay.h"


void GPIO_INIT(void)
{		//GPIO初始化
	GPIO_InitTypeDef PB;
	GPIO_InitTypeDef PA;
	
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);	//禁用jtag,不然写入程序和程序执行都会受影响
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );		//打开GPIOA~C
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE );
	
	PB.GPIO_Pin = EN|RW|RS;
	PB.GPIO_Mode = GPIO_Mode_Out_PP;
	PB.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &PB);
	
	PA.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;
	PA.GPIO_Mode = GPIO_Mode_Out_PP;
	PA.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &PA);
	
}

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不是准双向IO
	GPIO_InitTypeDef p;
	GPIO_Write( GPIOA, 0x00ff );	
	
	p.GPIO_Pin = GPIO_Pin_7;		//选定GPIOA的第七Pin
	p.GPIO_Mode = GPIO_Mode_IN_FLOATING;	//第七Pin的工作方式为浮空输入模式,用于检测LCD1602的忙状态
	p.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOA, &p );
	
	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 );//使能关
	
	p.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的状态还原成推挽模式
	p.GPIO_Mode = GPIO_Mode_Out_PP;
	p.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOA, &p  );
}

void WUserImg(char pos,char *ImgInfo)
{ //写入用户自定义图像
	char cgramAddr;			//CGRAM的用户自定义字符位
	
	if( pos <= 1 ) cgramAddr = 0x40;		// 
	if( pos > 1 && pos <= 3 ) cgramAddr = 0x50;
	if( pos > 3 && pos <= 5 ) cgramAddr = 0x60;
	if( pos > 5 && pos <= 7 ) cgramAddr = 0x70;

	LCD_WRITE_CMD( (cgramAddr + (pos%2) * 8) );	//指定字模写入的地址,一般从0x40开始,0x78结束
	
	while( *ImgInfo != '\0' )
	{		//循环写入tem数据,即用户取模的数据
		LCD_WRITE_ByteDATA( *ImgInfo );
		ImgInfo++;
	}
}

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 C10ms;
	
 	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=500;//PID计算周期
  pid.Ti=5000000;//积分时间
	pid.Td=1000;//微分时间
	pid.pwmcycle=200;//pwm周期200
	pid.OUT0=1;
	pid.C10ms=0;
}

void PID_Calc()  //pid计算
{
 	float DelEk;
	float ti,ki;
	float td;
	float kd;
	float out;
 	if(pid.C10ms<(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*pid.Kp;  //积分输出

 	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.C10ms=0;
}

PWMOUT.h

#ifndef PWMOUT_H
#define PWMOUT_H

#include "sys.h"

#define PWMOUT_1 GPIO_SetBits(GPIOB,GPIO_Pin_4)
#define PWMOUT_0 GPIO_ResetBits(GPIOB,GPIO_Pin_4)

void Time_init(void);
void PWM_OUT(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);
	
	// 自动装载的计数值   1ms
	TIM_TimeBaseStructure.TIM_Period = 1000; // 10KHz
	TIM_TimeBaseStructure.TIM_Prescaler = (72 - 1); // 1MHz
	TIM_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.C10ms++;
				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_CH2
GPIO_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>
unsigned int num=0;
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.out
		LCD_WRITE_StrDATA("pid.out:",1,0);	
		sprintf(PWM,"%f",pid.OUT);
		PWM[6]='\0';
		LCD_WRITE_StrDATA(PWM,1,9);		
}


int main() 
{
	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(399,35999);
	while(1)
	{
		while(DHT11_Read_Data());
		PID_Calc();
		num=(((pid.OUT*400)/pid.pwmcycle)-1);
		TIM_SetCompare2(TIM3,num);
		situation();                                                                                                                                                                                   
	}       
}

工程此处下载
链接:https://pan.baidu.com/s/1cMzrPxOLoRH08F9JMwMEfQ
提取码:m6f1

上一篇:STM32:F407步进电机梯形加减速算法的实现


下一篇:第十一届蓝桥杯嵌入式省赛题准备经历及源码详解