嵌入式设计与开发项目-液位检测告警系统

一、实现的功能

  • ①液位检测,通过ADC外设检测电位器的电压模拟液位传感器,并通过公式计算出液位高度;
  • ②阈值设置,通过按键外设设置液位的阈值;
  • ③阈值保存,通过IIC外设把数据保存到AT12C02存储器中;
  • ④串口的查询和输出,通过串口外设发送特定的字符‘C’、‘S’分别查询液位高度和等级以及阈值。液位等级改变时串口发送实时液位数据;
  • ⑤状态指示,通过LED外设的LD1以1S间隔闪烁表示运行状态指示灯,LD2以0.2S间隔闪烁5次表示液位等级发送变化,LD3以0.2S间隔闪烁5次表示串口接收到信息;

二、根据功能实现代码

1、主文件main.c

#include "stm32f10x.h"
#include "lcd.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "i2c.h"
#include "adc.h"

typedef unsigned char uchar;
typedef unsigned int  uint;

uint uiAdc_Val;																		//R37模拟液位传感器的ADC值
uchar ucHeight,ucLevel,ucLevel1;									//液位高度、液位等级
uchar ucState,pucStr[21],pucTh[3],pucRcv[1];			//LCD显示缓冲区、液位阈值、串口缓冲区
uchar ucLed,ucLd2,ucLd3,ucNum = 10;								//点亮LED
uchar ucSec,ucSec1,ucKey_Long;									  //保存ms、s,长按标志位
unsigned long ulTick_ms;													//滴答定时器的计数

void KEY_Proc(void);
void LCD_Proc(void);
void ADC_Proc(void);
void UART_Proc(void);

//Main Body
int main(void)
{
    
    
	SysTick_Config(SystemCoreClock/1000);			//设置系统滴答时钟1ms并使能中断

	KEY_Init();			//按键初始化
	LED_Init();			//LED初始化
	
	STM3210B_LCD_Init();			 //LCD初始化		
	LCD_Clear(Blue);					 //把屏幕刷新为蓝色
	LCD_SetBackColor(Blue);		 //设置字体背景为蓝色
	LCD_SetTextColor(White);	 //字体设置为白色
	
	USART2_Init(9600);				 //串口初始化
	i2c_init();							 	 //IIC初始化
	ADC1_Init();  						 //ADC初始化
	
	i2c_read(pucTh,0,3);			 //AT24C02读取从地址0开始的3个字节的数据
	if(pucTh[0] > 100)	
		pucTh[0] = 10;
	if(pucTh[1] > 100)	
		pucTh[0] = 20;
	if(pucTh[2] > 100)	
		pucTh[0] = 30;
	
	while(1)
	{
    
    
		KEY_Proc();				//按键相对应的功能
		LCD_Proc();			  //LCD相对应的功能
		ADC_Proc();				//ADC采集相对应的功能
		UART_Proc();			//串口相对应的功能
	}
}

/**********************************************************************************************
*函数:void KEY_Proc(void)
*功能:按键B1设置键,B2切换键,B3加键,B4减键
*输入:无
*输出:无
*特殊说明:各按键互不影响
**********************************************************************************************/
void KEY_Proc(void)
{
    
    
	uchar ucKey_Val;
	
	ucKey_Val = KEY_Scan();				//检测那个按键按下
	if(ucKey_Val != ucKey_Long)
		ucKey_Long = ucKey_Val;			
	else
		ucKey_Val = 0;							//防止长按
	switch(ucKey_Val)
	{
    
    
		case 1:											//B1:设置键
			if(!ucState)
				ucState = 1;
			else
			{
    
    
				if((pucTh[0]) < pucTh[1] && pucTh[1] < pucTh[2])
				{
    
    
					LCD_DisplayStringLine(Line3,(u8*)"        ");
					
					i2c_write(pucTh , 0 , 3);					//pucTh阈值存到AT24C02
					ucState = 0;
				}
				else
				{
    
    
					LCD_SetTextColor(Red);
					LCD_DisplayStringLine(Line3,(u8*)" Threshold Error ");		//设置阈值有误
					LCD_SetTextColor(White); 
				}
			}
			break;
		case 2:							//B2:切换键
			if(ucState)
				if(++ucState == 4)
					ucState = 1;
				break;
		case 3:							//B3:阈值加
			if(ucState)
				if(pucTh[ucState-1] < 95)
					pucTh[ucState-1] +=5;
				break;
		case 4:							//B3:阈值减
			if(ucState)
				if(pucTh[ucState-1] > 5)
					pucTh[ucState-1] -=5;
				break;
	}
}
/**********************************************************************************************
*函数:void LCD_Proc(void)
*功能:LCD屏幕显示函数
*输入:无
*输出:无
*特殊说明:无
**********************************************************************************************/
void LCD_Proc(void)
{
    
    
	float temp;
	
	if(!ucState)			//0显示状态界面,不等于0显示设置界面
	{
    
    
		LCD_DisplayStringLine(Line1,(u8*)"    Liquid Level    ");
		
		sprintf((char*)pucStr,"    Height:%03ucm    ",ucHeight);
		LCD_DisplayStringLine(Line4,pucStr);
		
		temp = (float)uiAdc_Val * 3.3 /4095;
		sprintf((char*)pucStr,"    VR37: %4.3fV     ",temp);
		LCD_DisplayStringLine(Line6,pucStr);
		
		sprintf((char*)pucStr,"    Level: %01u        ",ucLevel);
		LCD_DisplayStringLine(Line8,pucStr);
	}
	else
	{
    
    
		LCD_DisplayStringLine(Line1,(u8*)"   Threshold Setup    ");
		
		//索引在第1个阈值设置
		sprintf((char*)pucStr,"   Threshold 1: %02ucm    ",pucTh[0]);
		if(ucState == 1)
			LCD_SetBackColor(Red);
		LCD_DisplayStringLine(Line4,pucStr);
		LCD_SetBackColor(Blue);
		
		//索引在第2个阈值设置
		sprintf((char*)pucStr,"   Threshold 2: %02ucm    ",pucTh[1]);
		if(ucState == 2)
			LCD_SetBackColor(Red);
		LCD_DisplayStringLine(Line6,pucStr);
		LCD_SetBackColor(Blue);
		
		//索引在第3个阈值设置
		sprintf((char*)pucStr,"   Threshold 3: %02ucm    ",pucTh[2]);
		if(ucState == 3)
			LCD_SetBackColor(Red);
		LCD_DisplayStringLine(Line8,pucStr);
		LCD_SetBackColor(Blue);
	}
}
/**********************************************************************************************
*函数:void ADC_Proc(void)
*功能:ADC采集电位器R37的电压信号
*输入:无
*输出:无
*特殊说明:无
**********************************************************************************************/
void ADC_Proc(void)
{
    
    
	if(!ucState && ucSec != ucSec1)
	{
    
    
		ucSec1 = ucSec;
		
		uiAdc_Val = ADC1_Conv();				//获取装换值
		ucHeight = uiAdc_Val*100/4095;	//计算液位高度 ADC_Val = V * 4095 / 3.3
		
		if(ucHeight < pucTh[0])
			ucLevel = 0;
		else if(ucHeight < pucTh[1])
			ucLevel = 1;
		else if(ucHeight < pucTh[2])
			ucLevel = 2;
		else
			ucLevel = 3;
		
		if(ucLevel != ucLevel1)
		{
    
    
			if(ucLevel > ucLevel1)				//液位升高
				printf("A:H%02u+L%01u+U\r\n",ucHeight,ucLevel);
			else													//液位减低
				printf("A:H%02u+L%01u+D\r\n",ucHeight,ucLevel);
			
			ucLevel1 = ucLevel;
			ucLd2 = 1;										//等级改变
		}
	}
}
/**********************************************************************************************
*函数:void UART_Proc(void)
*功能:串口查询与发送函数
*输入:无
*输出:无
*特殊说明:无
**********************************************************************************************/
void UART_Proc(void)
{
    
    
	if(pucRcv[0] == 'C')
		printf("C:H%02u+L%01u\r\n",ucHeight,ucLevel);
	if(pucRcv[0] == 'S')
		printf("S:TL%02u+TM%02u+TH%02u\r\n",pucTh[0],pucTh[1],pucTh[2]);
	if(pucRcv[0] == 'C' || pucRcv[0] == 'S')
		ucLd3  = 1;			//串口查询
	pucRcv[0] = 0;
}
/**********************************************************************************************
*函数:void SysTick_Handler(void)
*功能:滴答定时器中断函数
*输入:无
*输出:无
*特殊说明:每1模式进入一次
**********************************************************************************************/
void SysTick_Handler(void)
{
    
    
	ulTick_ms++;
	
	if(ulTick_ms%1000 == 0)
	{
    
    
		ucSec++;
		ucLed ^=1;						//LD1闪烁
	}
	
	if(ucLd2)
	{
    
    
		if(ulTick_ms%200 == 0)
		{
    
    
			if(ucNum--)
				ucLed ^= 2;			//LD2闪烁5次
			else
			{
    
    
				ucLd2 = 0;
				ucNum = 10;
			}
		}
	}
	if(ucLd3)
	{
    
    
		if(ulTick_ms%200 == 0)
		{
    
    
			if(ucNum--)
				ucLed ^= 4;			//LD3闪烁5次
			else
			{
    
    
				ucLd3 = 0;
				ucNum = 10;
			}
		}
	}
	LED_Disp(ucLed);
}
/**********************************************************************************************
*函数:void USART2_IRQHandler( void )
*功能:接收串口2的数据
*输入:无
*输出:无
*特殊说明:接收串口2的中断服务函数
**********************************************************************************************/
void USART2_IRQHandler(void)
{
    
    
	pucRcv[0] = USART_ReceiveData(USART2);
}


主函数分析:❤️ ❤️ ❤️

  1. 定义全局变量,在当前源文件的函数可以交叉赋值和使用。
  2. 主控芯片的每个外设尽量模块化,每个函数负责一个外设的功能。
  3. 定义了三个ucLevel1、ucSec1、ucKey_Long变量,分别保存上一次的液位等级、时间、按键键值,从而实时判断液位等级延时1S防止长按的功能。

2、void KEY_Proc(void)按键处理程序设计


/**********************************************************************************************
*函数:void KEY_Proc(void)
*功能:按键B1设置键,B2切换键,B3加键,B4减键
*输入:无
*输出:无
*特殊说明:各按键互不影响
**********************************************************************************************/
void KEY_Proc(void)
{
    
    
	uchar ucKey_Val;
	
	ucKey_Val = KEY_Scan();				//检测那个按键按下
	if(ucKey_Val != ucKey_Long)
		ucKey_Long = ucKey_Val;			
	else
		ucKey_Val = 0;							//防止长按
	switch(ucKey_Val)
	{
    
    
		case 1:											//B1:设置键
			if(!ucState)
				ucState = 1;
			else
			{
    
    
				if((pucTh[0]) < pucTh[1] && pucTh[1] < pucTh[2])
				{
    
    
					LCD_DisplayStringLine(Line3,(u8*)"        ");
					
					i2c_write(pucTh , 0 , 3);					//pucTh阈值存到AT24C02
					ucState = 0;
				}
				else
				{
    
    
					LCD_SetTextColor(Red);
					LCD_DisplayStringLine(Line3,(u8*)" Threshold Error ");		//设置阈值有误
					LCD_SetTextColor(White); 
				}
			}
			break;
		case 2:							//B2:切换键
			if(ucState)
				if(++ucState == 4)
					ucState = 1;
				break;
		case 3:							//B3:阈值加
			if(ucState)
				if(pucTh[ucState-1] < 95)
					pucTh[ucState-1] +=5;
				break;
		case 4:							//B3:阈值减
			if(ucState)
				if(pucTh[ucState-1] > 5)
					pucTh[ucState-1] -=5;
				break;
	}
}

按键KEY简要分析:❤️ ❤️

  1. 定义ucKey_Long标志位,防止长按一直执行该按键的功能;
  2. 定义pucTh[3]数据可以进行函数i2c_write的(unsigned char*)传参,把数据按顺序存到AT24C02存储器中;
  3. 如果ucState大于0,判断是设置界面,从而可以执行切换键、阈值加、阈值减的操作。
  4. 巧妙地使用ucState判断当前切换选择的液位等级阈值设置,并可使用到阈值数组pucTh的设置。

3、LCD处理程序设计

/**********************************************************************************************
*函数:void LCD_Proc(void)
*功能:LCD屏幕显示函数
*输入:无
*输出:无
*特殊说明:无
**********************************************************************************************/
void LCD_Proc(void)
{
    
    
	float temp;
	
	if(!ucState)			//0显示状态界面,不等于0显示设置界面
	{
    
    
		LCD_DisplayStringLine(Line1,(u8*)"    Liquid Level    ");
		
		sprintf((char*)pucStr,"    Height:%03ucm    ",ucHeight);
		LCD_DisplayStringLine(Line4,pucStr);
		
		temp = (float)uiAdc_Val * 3.3 /4095;
		sprintf((char*)pucStr,"    VR37: %4.3fV     ",temp);
		LCD_DisplayStringLine(Line6,pucStr);
		
		sprintf((char*)pucStr,"    Level: %01u        ",ucLevel);
		LCD_DisplayStringLine(Line8,pucStr);
	}
	else
	{
    
    
		LCD_DisplayStringLine(Line1,(u8*)"   Threshold Setup    ");
		
		//索引在第1个阈值设置
		sprintf((char*)pucStr,"   Threshold 1: %02ucm    ",pucTh[0]);
		if(ucState == 1)
			LCD_SetBackColor(Red);
		LCD_DisplayStringLine(Line4,pucStr);
		LCD_SetBackColor(Blue);
		
		//索引在第2个阈值设置
		sprintf((char*)pucStr,"   Threshold 2: %02ucm    ",pucTh[1]);
		if(ucState == 2)
			LCD_SetBackColor(Red);
		LCD_DisplayStringLine(Line6,pucStr);
		LCD_SetBackColor(Blue);
		
		//索引在第3个阈值设置
		sprintf((char*)pucStr,"   Threshold 3: %02ucm    ",pucTh[2]);
		if(ucState == 3)
			LCD_SetBackColor(Red);
		LCD_DisplayStringLine(Line8,pucStr);
		LCD_SetBackColor(Blue);
	}
}

LCD屏幕简要分析:❤️ ❤️

  1. 如果ucState为0,是液位状态显示界面,否则是设置界面。
  2. 巧妙使用空格重叠另一个界面显示数据;

5、ADC处理程序设计

/**********************************************************************************************
*函数:void ADC_Proc(void)
*功能:ADC采集电位器R37的电压信号
*输入:无
*输出:无
*特殊说明:无
**********************************************************************************************/
void ADC_Proc(void)
{
    
    
	if(!ucState && ucSec != ucSec1)
	{
    
    
		ucSec1 = ucSec;
		
		uiAdc_Val = ADC1_Conv();				//获取装换值
		ucHeight = uiAdc_Val*100/4095;	//计算液位高度 ADC_Val = V * 4095 / 3.3
		
		if(ucHeight < pucTh[0])
			ucLevel = 0;
		else if(ucHeight < pucTh[1])
			ucLevel = 1;
		else if(ucHeight < pucTh[2])
			ucLevel = 2;
		else
			ucLevel = 3;
		
		if(ucLevel != ucLevel1)
		{
    
    
			if(ucLevel > ucLevel1)				//液位升高
				printf("A:H%02u+L%01u+U\r\n",ucHeight,ucLevel);
			else													//液位减低
				printf("A:H%02u+L%01u+D\r\n",ucHeight,ucLevel);
			
			ucLevel1 = ucLevel;
			ucLd2 = 1;										//等级改变
		}
	}
}

ADC模拟液位电压简要分析:❤️ ❤️

  1. 在液位显示界面,进行1S采集一次模拟液位电压电位器;
  2. 根据获取到的AD值进行公式转换为高度,判断高度在液位的那个阈值范围内,从而得知当前的液位等级;
  3. 通过ucLevel1判断液位等级是否发送改变,根据液位改变给串口发送相对应的消息,并进行Ld2的5次闪烁

6、UART处理程序设计

/**********************************************************************************************
*函数:void UART_Proc(void)
*功能:串口查询与发送函数
*输入:无
*输出:无
*特殊说明:无
**********************************************************************************************/
void UART_Proc(void)
{
    
    
	if(pucRcv[0] == 'C')
		printf("C:H%02u+L%01u\r\n",ucHeight,ucLevel);
	if(pucRcv[0] == 'S')
		printf("S:TL%02u+TM%02u+TH%02u\r\n",pucTh[0],pucTh[1],pucTh[2]);
	if(pucRcv[0] == 'C' || pucRcv[0] == 'S')
		ucLd3  = 1;			//串口查询
	pucRcv[0] = 0;
}

串口UART简要分析:❤️ ❤️

  1. 判断串口接收到的字符,是否和系统设置参数一致,执行相对应的功能。
  2. 接收完之后,重新把pucRcv缓冲区恢复默认值。

7、LED处理程序设计

/**********************************************************************************************
*函数:void SysTick_Handler(void)
*功能:滴答定时器中断函数
*输入:无
*输出:无
*特殊说明:每1模式进入一次
**********************************************************************************************/
void SysTick_Handler(void)
{
    
    
	ulTick_ms++;
	
	if(ulTick_ms%1000 == 0)
	{
    
    
		ucSec++;
		ucLed ^=1;						//LD1闪烁
	}
	
	if(ucLd2)
	{
    
    
		if(ulTick_ms%200 == 0)
		{
    
    
			if(ucNum--)
				ucLed ^= 2;			//LD2闪烁5次
			else
			{
    
    
				ucLd2 = 0;
				ucNum = 10;
			}
		}
	}
	if(ucLd3)
	{
    
    
		if(ulTick_ms%200 == 0)
		{
    
    
			if(ucNum--)
				ucLed ^= 4;			//LD3闪烁5次
			else
			{
    
    
				ucLd3 = 0;
				ucNum = 10;
			}
		}
	}
	LED_Disp(ucLed);
}

系统滴答定时器简要分析:❤️ ❤️

  1. 设置了1ms,中断进入这个函数一次,实现精准的计时。
  2. 如果液位等级发送变化,则LD2闪烁5次;如果接收到串口的指令,则LD3进行闪烁5次,闪烁完后清除标志位。

8、USART2处理程序设计

/**********************************************************************************************
*函数:void USART2_IRQHandler( void )
*功能:接收串口2的数据
*输入:无
*输出:无
*特殊说明:接收串口2的中断服务函数
**********************************************************************************************/
void USART2_IRQHandler(void)
{
    
    
	pucRcv[0] = USART_ReceiveData(USART2);
}

简要分析:❤️ ❤️

  1. 串口中断函数,串口接收到数据进入中断把数据保存到pucRcv数组中。

三、实现功能过程的注意与学习点

1、注意点

如何定义全局变量,使各个外设功能耦合在一起!

2、学习的知识点

  1. 实现模块化编程,每个外设的功能单独定义一个函数;
  2. 巧妙地定义一个数组,保存液位各个等级的阈值,还实现阈值的加减操作;
  3. 学会定义标志位,判断当前状态是否发生改变,防止长按按键、液位等级变化检测等功能;
  4. 实现串口的中断接收服务函数;

❤️ ❤️ ❤️ ❤️ ❤️ ❤️

猜你喜欢

转载自blog.csdn.net/a6662580/article/details/125400880