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



    0 简介

    Hi,大家好,这里是丹成学长,今天向大家介绍一个 单片机项目

    基于stm32的智能水杯 - 恒温控制

    大家可用于 课程设计 或 毕业设计


    单片机-嵌入式毕设选题大全及项目分享:

    https://blog.csdn.net/m0_71572576/article/details/125409052


    1 项目介绍

    今天向大家介绍学长设计的一个毕设项目,基于STM32单片机控制的智能水杯,可利用插口式电源或无线充电底座为加热器提供能量,并在达到某种饮品所需温度时进行保温。 水杯内置充电电池,可选用 USB 接口或无线充电。 在水杯内部设置无线模块,用户利用上位机与水杯进行匹配进行加热操作,加热完后水杯会通过上位机和液晶显示屏实时反馈液体温度,通过指示灯显示电量情况等,给用户进行提醒。

    2 系统设计

    该智能水杯控制系统是由 STM32 单片机作为主要控制芯片,接受和发送信号给温度传感器,进行温度检测;温度检测是由温度传感器来执行,实时采集水杯内液体温度并利用 LCD 屏显示; 电池检测由电池电压指示电路进行操控, 实时检测电池电量, 并通过 LED 灯进行提示;内置充电电池充电可提供无线和 USB接口充电方式;控温及发热系统由 PTC 发热体构成, 并利用闭环控制使水杯液体温度恒温;利用无线模块,接收来自手机App所发出的指令, 通过手机App与水杯进行匹配进行加热操作,并反馈水杯的使用状态

    系统设计

    在这里插入图片描述

    3 恒温控制实现

    智能水杯的核心功能是水温的恒温控制,为了实现这一点,学长选用了以下元器件

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

    3.1 功能描述

    用DHT11检测当前环境温湿度,并将数据显示在LCD1602上,在用设定温度与当前温度相减,通过PID算法计算出当前输出脉宽,并将其加在L298N模块中,使加热丝发热,形成一个闭环,经过一段时间温度稳定在设定值。

    在这里插入图片描述

    3.2 PID算法原理

    在这里插入图片描述

    3.2.1 P:比例

    成比例地反映控制系统的偏差信号e(t),偏差一旦产生,控制器立即产生控制作用,以减小偏差。当仅有比例控制时系统输出存在稳态误差(Steady-state error)。
    P参数越小比例作用越强,动态响应越快,消除误差的能力越强。通常将P参数由大向小调,以能达到最快响应又无超调(或无大的超调)为最佳参数。

    3.2.2 I:积分

    为消除静差,提高系统的无差度。积分作用的强弱取决于积分时间常数T,T越大,积分作用越弱,反之则越强。

    3.2.3 D:微分

    反映偏差信号的变化趋势,并能在偏差信号变得太大之前,在系统中引入一个有效的早期修正信号,从而加快系统的动作速度,减少调节时间。在微分控制中,控制器的输出与输入误差信号的微分(即误差的变化率)成正比关系。
    D越大,微分作用越强,D越小,微分作用越弱。系统调试时通常把D从小往大调,具体参数由试验决定。

    3.3 温控代码实现

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    LCD1602.c

    #include "LCD1602.h"
    #include "delay.h"
    
    
    void GPIO_INIT(void)
    {		//GPIO初始化
    	GPIO_InitTypeDef GPIO;
    	
    	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);	//禁用jtag
    	RCC_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不是准双向IO
    	GPIO_InitTypeDef GPIO;
    	GPIO_Write(GPIOA, 0x00ff);	
    	
    	GPIO.GPIO_Pin = GPIO_Pin_7;		//选定GPIOA的第七Pin
    	GPIO.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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    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周期200
    	pid.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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    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.out
    		LCD_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();                                                                                                                                                                                   
    	}       
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    4 实现效果

    在这里插入图片描述

    在这里插入图片描述


    单片机-嵌入式毕设选题大全及项目分享:

    https://blog.csdn.net/m0_71572576/article/details/125409052


    5 最后

  • 相关阅读:
    如何把项目部署到云服务器
    [Android开发学iOS系列] 快速上手UIKit
    【场景化解决方案】“云上管车”连接货运系统,帮助企业高效调度车辆
    token过期 如何使用refresh_token实现无感刷新页面?
    spring boot 实现mock平台
    docker基础命令
    Java 8 新特性 (#Lambda表达式 #函数式(Functional)接口 #方法引用与构造器引用 #Stream API #Optional类)
    72道Java线程面试题,一题一答案,不搞花里胡哨
    面向对象编程(C++篇2)——构造
    java-php-net-python-图书馆选择计算机毕业设计程序
  • 原文地址:https://blog.csdn.net/m0_71572576/article/details/125594132