第七届蓝桥杯省赛 嵌入式设计与开发科目“模拟液位检测

~QQ:3020889729

~小蔡

第七届蓝桥杯省赛 嵌入式设计与开发科目“模拟液位检测”

(我也才备赛,可能会有写的或者说得不太对的地方。如果有错误的地方或者有什么好的建议,大家可以评论一下
——集思广益,共同进步。)

该题功能要求:
“模拟液位检测告警系统”通过采集模拟电压信号计算液位高度,并根据用户设定的液
位阈值执行报警动作,在液位等级发生变化时,通过串行通讯接口将液位信息发送到 PC 机。

题目系统框图

系统框图

设计任务以及要求简要说明

1.液位测量

通过电位器 R37 模拟液位传感器输出电压信号,设备以 1 秒为间隔采集 R37 输出电压(需要软件滤波,结果保留两位小数),并与用户设定的液位阈值进行比较——确定液面高度等级。
电压与液面高度的关系——(3.3V对应100cm)
					                                              H = V R37 *K cm

2.液位阈值设定——液位等级关系

设备可设定三个液位阈值,对应四个液位等级,阈值由用户通过按键输入,设备将保存阈值到 E2PROM 中,并在重新上电时读取。

前后依次为阈值1~3与液位等级的关系

3.液位阈值设定——按键功能

B1 按键:“设置”按键,按下后进入阈值设定界面,并高显——再按退出设置界面。

如图

B2 按键:切换参数选项。
B3 按键:增加当前参数项,增值为5——最高到95。
B4 按键:减小当前参数项,减值为5——最低到5。

4.串口查询/输出功能

①查询

通过 PC 机向设备发送字符‘C’,设备返回当前液位高度和液位等级;

‘C’数据格式举例

通过 PC 机向设备发送字符‘S’,设备返回当前设定的三个阈值。

‘S’数据格式举例
②输出

当液位等级发生变化时,设备自动向 PC 机发送当前液位等级、液位高度和液位变
化趋势(上升或下降)。

输出格式

5.LED状态指示

LED代表的相关状态

程序流程图·设计

简易流程图

解读:

在题目要求下,将必要的基本配置配置好。
(主要是:ADC初始化配置,LED灯的初始化配置,24C02的初始化配置,按键的初始化配置,串口的初始化配置。)

LCD显示部分的思路:

主要工作界面上的设计思路:
然后首先处理的就是ADC的测量——得到电压值和液位高度。
将得到的液位高度与设置的三个阈值作比较——确定等级,并在LCD上显示。
在(由B1按下后进入)设置界面的设计思路:
通过B2切换选项,高显,并通过另外的两个按键来进行调整——最后再通过B1退出设置界面回到工作界面。

串口部分的思路:

无命令输入时:
实时获取液位变化,当液位等级发生变化时,及时发送数据到PC端。
当串口发送查询命令时:
返回当前命令下的数据到PC端。

LED部分的思路

在串口中,当数据接收,串口接收中断发生时,激活接收指示命令——使得LED3闪烁-0.2s一次,闪烁5次;
而当液位等级改变时,触发液位指示命令——LED2闪烁-0.2s一次,闪烁5次;

状态图·设计

状态图

解读:

由LCD主持工作界面的显示,由按键控制界面切换以及阈值的设定。整个过程程序始终运行——当串口发生时,直接进行命令的读取和反馈;当液位变化使得等级发生变化时,向串口发送相关数据。
(LED在这个事件过程中属于指示项,可对应拼接在具体的某一个触发函数中——进行嵌套即可。)

主要代码分析

1.通用初始化部分

ps:我写的代码是按模块完成的——尽量使得主函数.c部分更简单。

①按键初始化:

ps:按键初始化很简单,主要是对后边按键的读取处理,才算重点。

(.c部分)

#include "key.h"
void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

}

(.h部分)

#ifndef _KEY_H
#define _KEY_H
typedef struct
{
	u8 set_flag;//设置标志
	u8 set_up_flag;//上调标志
	u8 set_down_flag;//下调标志
	u8 set_num_flag;//当前设置项
	u8 data_left_flag;//数据存储标志
	u8 level_change_led_flag;//液位变化指示灯标志
}FLAG;

typedef struct
{
	u8 Threshold1;//液位阈值
	u8 Threshold2;
	u8 Threshold3;
	u8 Level;//液位等级
}VAL;

#define KEY1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//按键宏定义

#define KEY2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8)
 
#define KEY3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)

#define KEY4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)

void KEY_Init(void);//按键初始化

//按键扫描
u8 key_scan(void);

//按键状态读取操作
void key_read(void);
//改变阈值
void key_val_set_change(void);
//等级评定测量
void level_juge(void);
//控制液位灯
void level_led_work(void);

#endif

②LED初始化

ps:此处的灯由于LCD共用引脚,所以呢,需要在每次使用LCD后,都要及时将LED全部拉高,不然会导致LED灯全部打开。

(.c部分)

#include "led.h"

void LED_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOD, &GPIO_InitStructure);//锁存器的引脚初始化
	
	GPIO_InitStructure.GPIO_Pin = 0XFF00;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);//8个LED灯引脚的初始化
	
	GPIOD-> ODR |= (1<<2);//锁存器使能引脚控制
	GPIOC-> ODR = 0XFFFF;//初始使能引脚拉高,灯灭
	GPIOD-> ODR &=~(1<<2);

}

(.h部分)

#ifndef _LED_H
#define _LED_H
#include "stm32f10x.h"
//对LED灯的宏定义
//从GPIO引脚复制过来,改宏定义
#define LED1                 ((uint16_t)0x0100)  /*!< Pin 8 selected */
#define LED2                 ((uint16_t)0x0200)  /*!< Pin 9 selected */
#define LED3                ((uint16_t)0x0400)  /*!< Pin 10 selected */
#define LED4                ((uint16_t)0x0800)  /*!< Pin 11 selected */
#define LED5                ((uint16_t)0x1000)  /*!< Pin 12 selected */
#define LED6                ((uint16_t)0x2000)  /*!< Pin 13 selected */
#define LED7                ((uint16_t)0x4000)  /*!< Pin 14 selected */
#define LED8                ((uint16_t)0x8000)  /*!< Pin 15 selected */

void LED_Init(void);
//全灭
void LED_ALL_OFF(void);//控制LED灯灭,避免LCD导致的全亮
//控制灯
void LED_Control(u16 LEDx,u8 state);
//检测用,固定灯5亮灭
void LED_LIANGMIE_CHECK(void);
#endif

③ADC初始化:

ps:初始化部分都是固定的,需要注意的是后期的adc取值,这里这次使用了软件滤波——所以呢,我就用了中值滤波,
当然还有许多滤波方法,大家可以选择合适的记忆一下就好。

(.c部分)
(我就简单解释下,ADC初始化部分的含义)

#include "adc.h"
void ADC1_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_ADC1, ENABLE);
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式开启
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;//扫描模式不开启
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//练习扫描不开启
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//触发不开启
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//采样数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;
	//规则转换通道数目,当前只采样8通道所以设置为1——如果多通道就对应几条通道设置成多少就好
	
	ADC_Init( ADC1, &ADC_InitStructure);
	
	ADC_Cmd( ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1);//复位校验
	while(ADC_GetResetCalibrationStatus(ADC1));
		
	ADC_StartCalibration(ADC1);//ADC启动校验
	while(ADC_GetCalibrationStatus(ADC1));
}

(.h部分)

#ifndef _ADC_H
#define _ADC_H

#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "uart.h"

void ADC1_Init(void);
//读取adc值
u16 Get_Adc(u8 ADC_Channel_x);

//中位值算法滤波
u32 work_adc_val_real(void);

//获得电压值和液位高度
void V_High_val(void);

#endif

④串口初始化:

ps:由于我们是使用供电线接口处所在的串口进行通讯,所以只能用USART2来进行通讯。需要注意的是,在配置中断时,
最好是应该留一个接受完成标志位,并同时将中断关闭使能——这样,在后边利用标志位,把数据读取后,将标志位置零,
再把中断打开。
这样子,可以避免在不需要接收数据时接收到数据或者读取到一些不必要的串口信息。

(.c部分)

#include "uart.h"

void USART2_Init(u32 bound)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//输出推完
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//输入用浮空
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_InitStructure.USART_BaudRate = bound;//波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据位数
	USART_InitStructure.USART_StopBits = USART_StopBits_1;。。一位停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//写入和读取模式都开启
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不需要硬件控制
	USART_Init(USART2, &USART_InitStructure);
		
	USART_Cmd(USART2,ENABLE);
	USART_ITConfig( USART2, USART_IT_RXNE,ENABLE);
}

(.h部分)

#ifndef _UART_H
#define _UART_H

#include "stm32f10x.h"
#include "key.h"
#include "stdio.h"
#include "adc.h"
#include "led.h"

void USART2_Init(u32 bound);
//串口数据发送
void USART2_SendString(u8 *str);

//串口数据读取
void uart_check(void);

#endif

⑤24C02初始化

ps:在蓝桥杯比赛的开发板中,存储数据需要用到**i2c**总线下的24c02,所以需要使用**i2c**对**24c02**进行控制。

(.c部分)

#include"24c02.h"
//写入24c02的指定地址的指定数据的命令
void _24c02_Write(u8 address,u8 data)
{
	I2CStart();
	I2CSendByte(0xa0);
	I2CWaitAck();
	I2CSendByte(address);
	I2CWaitAck();
	I2CSendByte(data);
	I2CWaitAck();
	I2CStop();
}
//读取24c02的指定地址的数据的命令
u8 _24c02_Read(u8 address)
{
	u8 temp;
	I2CStart();
	I2CSendByte(0xa0);
	I2CWaitAck();
	I2CSendByte(address);
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0xa1);
	I2CWaitAck();
	temp = I2CReceiveByte();
	I2CWaitAck();
	I2CStop();
	return temp;

}

(.h部分)

#ifndef _24C02_H
#define _24C02_H

#include "stm32f10x.h"
#include "i2c.h"
#include "key.h"
//写入24c02的指定地址的指定数据的命令
void _24c02_Write(u8 address,u8 data);
//读取24c02的指定地址的数据的命令
u8 _24c02_Read(u8 address);

//写入存储数据
//带延时
void _24c02_data_write_threshold(u8 address,u8 data);
//读取存储数据
void _24c02_data_read_threshold(void);

//24c02延时单元
void _24c02_delay_ms(u32 nTime);

#endif

2.主要功能函数部分

1.按键功能部分:
(功能放在对应的.c里边)

FLAG flag;//建立标志位结构体
VAL Threshold;//建立液压阈值结构体

u8 key_scan_flag=0;//按键允许扫描的标志——允许,按键方有效
u8 key_scan_delay=5;//扫描延时时间,作为消抖的部分,也是按键读取的周期
//按键扫描
u8 key_scan(void)
{
	static u8 key_up=1;
	if(key_up&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))
	{
		if(key_scan_flag)//判断是否允许按键读取
		{
			key_scan_flag=0;
			key_scan_delay=5;//参数初始化
			
			key_up=0;
			if(KEY1==0) return 1;//每一个按键按下对应各自的返回值情况
			if(KEY2==0) return 2;
			if(KEY3==0) return 3;
			if(KEY4==0) return 4;
		}
	}//判断按键弹起,按键弹起后方可以进行下一次按键判断
	else if(key_up==0&&KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1)
		key_up=1;
	
	return 0;
}
//按键状态读取操作
void key_read(void)
{
	u8 key_down=0;
	key_down=key_scan();//获取按键值
	switch(key_down)
	{
		case 1:
			flag.set_flag=!flag.set_flag;//第一次按下进入设置界面,第二次按下退出设置界面
			if(flag.set_flag==0)//从设置界面退出的时候,就将存储标志置1
				flag.data_left_flag=1;//允许存储数据
			break;
		case 2:
			flag.set_num_flag++;//移动到指定设置项
			if(flag.set_num_flag>2)
				flag.set_num_flag=0;//回到第一项——0-1,1-2,2-3
			break;
		case 3://项值加5
			flag.set_up_flag=1;//执行一次加命令
			break;
		case 4://项值减5
			flag.set_down_flag=1;//执行一次减命令
			break;
		default:
			break;
	}
}
//改变阈值
void key_val_set_change(void)
{
	switch(flag.set_num_flag)
	{
		case 0://设置参数项为第一项时,按键B3/B4按下就是对该项的加或者减
			if(Threshold.Threshold1>=95)//判断当前是否可加
			{
				flag.set_up_flag=0;
				Threshold.Threshold1=95;
			}
			if(Threshold.Threshold1==5)//判断当前是否可减
			{
				flag.set_down_flag=0;
				Threshold.Threshold1=5;
			}
			if(flag.set_up_flag)//满足加
			{
				flag.set_up_flag=0;
				Threshold.Threshold1+=5;//阈值1的值加5
			}
			if(flag.set_down_flag)//满足减
			{
				flag.set_down_flag=0;
				Threshold.Threshold1-=5;//阈值1的值减5
			}
			_24c02_data_write_threshold(0x01,Threshold.Threshold1);//写入当前项的数据
			break;
		case 1://执行方式同case 0一样,先判断再改变值
			if(Threshold.Threshold2>=95)//判断当前是否可加,可减
			{
				flag.set_up_flag=0;
				Threshold.Threshold2=95;
			}
			if(Threshold.Threshold2==5)
			{
				flag.set_down_flag=0;
				Threshold.Threshold2=5;
			}
			
			if(flag.set_up_flag)//加
			{
				flag.set_up_flag=0;
				Threshold.Threshold2+=5;
			}
			if(flag.set_down_flag)//减
			{
				flag.set_down_flag=0;
				Threshold.Threshold2-=5;
			}
			_24c02_data_write_threshold(0x02,Threshold.Threshold2);//写入数据
			break;
		case 2:
			if(Threshold.Threshold3>=95)//判断当前是否可加,可减
			{
				flag.set_up_flag=0;
				Threshold.Threshold3=95;
			}
			if(Threshold.Threshold3==5)
			{
				flag.set_down_flag=0;
				Threshold.Threshold3=5;
			}
			if(flag.set_up_flag)//加
			{
				flag.set_up_flag=0;
				Threshold.Threshold3+=5;
			}
			if(flag.set_down_flag)//减
			{
				flag.set_down_flag=0;
				Threshold.Threshold3-=5;
			}
			_24c02_data_write_threshold(0x03,Threshold.Threshold3);//写入数据
			break;
		default:
			break;
	}
}

extern u8 high_val;//实时液位高度
extern 	u8 led2_counts;//闪烁次数
//extern 引用自adc.h
u8 level_toward_flag=0;//1-up_change,2-down_change

//液面等级评定测量
void level_juge(void)
{
	static u8 last_level=0;//保留上一次的液面高度等级
	u8 val_get_flag=0;//液面判决结束标志
	
	u8 temp;//暂存液面高度值
	
	temp=high_val;//将液面高度值传递给temp
	
	if(val_get_flag==0&&temp>Threshold.Threshold3)//判断等级3
	{
		val_get_flag=1;//不再进行其它等级的判断
		Threshold.Level=3;
	}
	if(val_get_flag==0&&temp>Threshold.Threshold2&&temp<=Threshold.Threshold3)//判断等级2
	{
		val_get_flag=1;//不再进行其它等级的判断
		Threshold.Level=2;
	}
	if(val_get_flag==0&&temp>Threshold.Threshold1&&temp<=Threshold.Threshold2)//判断等级1
	{
		val_get_flag=1;//不再进行其它等级的判断
		Threshold.Level=1;
	}
	if(val_get_flag==0&&temp<=Threshold.Threshold1)//判断等级0
	{
		val_get_flag=1;//不再进行其它等级的判断
		Threshold.Level=0;
	}
	
	if(last_level!=Threshold.Level)//判断是否发生高度等级变化
	{
		if(last_level>Threshold.Level)//等级变化方向判定
			level_toward_flag=2;//下降态
		if(last_level<Threshold.Level)
			level_toward_flag=1;//上升态
		
		led2_counts=0;//闪烁次数——题目要求闪烁5次——即亮暗共10次,每次变化发生就将计数重新归零计数
		flag.level_change_led_flag=1;//允许发生液位高度等级变化灯的闪烁的标志
	}
	else
	{
		level_toward_flag=0;//液体高度等级指示灯关闭
	}
	
	last_level=Threshold.Level;//赋给本次高度等级的值,留给下一次比较用
	val_get_flag=0;//判绝状态初始化,下一次可以重新判决
}

2.LED功能部分

//灯全灭,用以改变LCD引脚共用导致的LED全亮显示
void LED_ALL_OFF(void)
{
	
	GPIOD-> ODR |= (1<<2);
	GPIOC-> ODR = 0XFFFF;
	GPIOD-> ODR &=~(1<<2);
}
//控制灯——指定等开启
void LED_Control(u16 LEDx,u8 state)
{
	if(state)
	{
		GPIOD-> ODR |= (1<<2);
		GPIO_ResetBits(GPIOC,LEDx);
		GPIOD-> ODR &=~(1<<2);
	}
	else
	{
		GPIOD-> ODR |= (1<<2);
		GPIO_SetBits(GPIOC,LEDx);
		GPIOD-> ODR &=~(1<<2);
	}
}

//检测BUG用,固定灯5的亮灭
//可用于非软件仿真调试的BUG的方式
void LED_LIANGMIE_CHECK(void)
{
	static u8 led_falg=0;
	led_falg=!led_falg;
	if(led_falg)
	{
		LED_Control(LED5,1);
	}
	else
	{
		LED_Control(LED5,0);
	}
	
}

3.ADC功能部分


//读取adc值
u16 Get_Adc(u8 ADC_Channel_x)//读取指定通道的ADC值
{
	u16 temp;
	ADC_RegularChannelConfig(ADC1,ADC_Channel_x, 1,ADC_SampleTime_239Cycles5);
	//最大采样周期ADC_SampleTime_239Cycles5
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == 0);
	temp = ADC_GetConversionValue(ADC1);
	ADC_SoftwareStartConvCmd(ADC1, DISABLE);
	return temp;
}

//中位值算法滤波
u32 work_adc_val_real(void)
{
	u32 temp=0;
	u8 i;
	for(i=0;i<10;i++)//取平均值,次数自定,合理即可
	{
		temp+=Get_Adc(ADC_Channel_8);//加起来,求平均
	}
	return temp/10;
}

double v_val=0;//电压值,题目要求保留两位小数,所以使用double定义
u8 high_val=0;//液位高度

u8 get_adc_flag=0;//获取adc的允许标志位——被引用到滴答中断中,定时置1
u16 get_adc_delay=1000;//adc采样时间,确定adc采样允许的周期——被引用到滴答中断中定时控制adc取值标志

u8 delay_led2_200_ms=200;//指示LED2灯的亮灭延时长度单元——液位高度等级变化
u8 delay_led3_200_ms=200;//指示LED3灯的亮灭延时长度单元——设备接收指示灯

u8 led2_on_flag=0;//LED2点亮标志——被引用到key.c中,当液面变化就置1
u8 led3_on_flag=0;//LED3点亮标志——被引用到uart.c中,当接收到pc端数据,就置1

u8 led2_counts=0;//指示灯闪烁次数——被引用到key.c中
u8 led3_counts=0;//指示灯闪烁次数——被引用到uart.c中

extern FLAG flag;//建立标志位结构体
extern u8 uart_receive_flag;//数据接收标志
//引用自key.c

//获得电压值和液位高度
//以及运行指示灯的闪烁
void V_High_val(void)
{
	static u8 led_control=0;//led控制单元,为1时亮,为0时不亮
	static u8 led_2_control=0;
	static u8 led_3_control=0;
	
	u16 temp;//存储adc的值
	
	if(get_adc_flag)
	{
		get_adc_flag=0;
		get_adc_delay=1000;
		
		temp=work_adc_val_real();//取adc值
		v_val=(double)temp*3.3/4095;//得到电压值
		high_val=v_val*100/3.3;//得到液位值
		
		led_control=!led_control;//翻转一次状态——实现1s间隔的亮灭
		if(led_control)
			LED_Control(LED1,1);//LED指示运行
		else
			LED_Control(LED1,0);	
	}
	
	//ps:在led2_on_flag=0;执行时,就不再判断周期达到就发生亮灭变化,而是直接依赖后边控制灯全灭的函数实现该灯的关闭
	if(flag.level_change_led_flag)//允许液位灯闪烁
	{
		if(led2_counts==10)//完成5次完成的亮灭闪烁
		{
			flag.level_change_led_flag=0;//不再闪烁
			led2_on_flag=0;//该LED不再亮
		}
		if(led2_on_flag)//在未达到指定次数时,每次间隔周期一到,就被允许亮或暗
		{
			led2_on_flag=0;//该次指令结束
			
			led2_counts++;//计数
			delay_led2_200_ms=200;//延时单元初始化
			led_2_control=!led_2_control;//翻转一次状态——实现0.2s间隔的亮灭
			if(led_2_control)
				LED_Control(LED2,1);//LED指示灯运行
			else
				LED_Control(LED2,0);	
		}	
	}
	//ps:在led3_on_flag=0;执行时,就不再判断周期达到就发生亮灭变化,而是直接依赖后边控制灯全灭的函数实现该灯的关闭
	if(uart_receive_flag)//串口有效工作灯允许动作
	{
		if(led3_counts==10)//完成5次完成的亮灭闪烁
		{
			uart_receive_flag=0;//不再闪烁
			led3_on_flag=0;//该LED不再亮
		}
		if(led3_on_flag)//在未达到指定次数时,每次间隔周期一到,就被允许亮或暗
		{
			led3_on_flag=0;//该次指令结束
			
			delay_led3_200_ms=200;//延时单元初始化
			led3_counts++;//计数
			led_3_control=!led_3_control;//翻转一次状态——实现0.2s间隔的亮灭
			if(led_3_control)
				LED_Control(LED3,1);//LED指示运行
			else
				LED_Control(LED3,0);
			
		}
		
	}

4.串口功能部分

u8 uart_rec_over_flag=0;//串口接收完成的标志
u8 uart_string[20];//串口数据内存,存储串口传输到设备的数据——数据将在中断中被存储

//数据发送函数
void USART2_SendString(u8 *str)
{
	u8 index = 0;
	do
	{
		USART_SendData(USART2,str[index]);
		while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == 0);
		index++;
	}while(str[index] != 0);
}
//串口中断服务函数
void USART2_IRQHandler(void)
{
	static u8 counts=0;//数据计位
	u8 temp;
	if(USART_GetITStatus(USART2,USART_IT_RXNE))
	{
		USART_ClearITPendingBit(USART2,USART_IT_RXNE);
		temp=USART_ReceiveData(USART2);
		if(temp=='\n')
		{
			uart_rec_over_flag=1;//接收完成
			counts=0;
			USART_ITConfig(USART2,USART_IT_RXNE,DISABLE);//暂时关闭,在数据接受处理后打开
		}
		else
		{
			uart_string[counts]=temp;//数据存入
			counts++;//下标加1,进入下一个数据的接收
		}
		
	}
}

extern u8 high_val;//液位高度
extern VAL Threshold;//建立液压阈值结构体
//引用自key.c
extern u8 level_toward_flag;//液高走向

extern u8 led3_counts;//闪烁次数

u8 uart_send_string[2][20];//发送数据的存储结构

u8 uart_receive_flag=0;//数据接收标志

//串口数据读取/发送函数
void uart_check(void)
{
	if(uart_rec_over_flag)//接收完成
	{
		USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//重新打开
		
		led3_counts=0;//计数归零
		
		uart_receive_flag=1;//数据接收
		uart_rec_over_flag=0;//接收标志初始化
		if(uart_string[0]=='C')
		{
			sprintf((char*)uart_send_string[0],"C:H%d+L%d\r\n",high_val,Threshold.Level);//返回液压高度/等级
			USART2_SendString(uart_send_string[0]);//发送数据
		}
		if(uart_string[0]=='S')
		{
			sprintf((char*)uart_send_string[0],"S:TL%d+TM%d+TH%d\r\n",Threshold.Threshold1,Threshold.Threshold2,Threshold.Threshold3);//返回液高阈值
			USART2_SendString(uart_send_string[0]);//发送数据
		}
	}
	
	if(level_toward_flag!=0)//当液高等级发生变化
	{
		if(level_toward_flag==1)//上走
		sprintf((char*)uart_send_string[1],"A:H%d+L%d+U\r\n",high_val,Threshold.Level);//返回液压变化高度,以及走向
		
		if(level_toward_flag==2)//下走
		sprintf((char*)uart_send_string[1],"A:H%d+L%d+D\r\n",high_val,Threshold.Level);//返回液压变化高度,以及走向
		
		USART2_SendString(uart_send_string[1]);//发送当前变化的数据
	}
}

5.24c02功能部分


extern VAL Threshold;//建立液压阈值结构体
//引用自key.c
//写入存储数据的函数
//带延时
void _24c02_data_write_threshold(u8 address,u8 data)
{
	_24c02_Write(address,data);//写入当前位置的数据
	_24c02_delay_ms(5);//等待写入完成
}

//读取存储数据的函数
void _24c02_data_read_threshold(void)
{
	Threshold.Threshold1=_24c02_Read(0x01);//读取指定地址的数据并返回该值
	_24c02_delay_ms(5);//等待读取完成
	Threshold.Threshold2=_24c02_Read(0x02);
	_24c02_delay_ms(5);
	Threshold.Threshold3=_24c02_Read(0x03);
	_24c02_delay_ms(5);
}

u32 _24c02_TimingDelay;
//24c02延时单元
void _24c02_delay_ms(u32 nTime)//用来完成读取和写入的延时部分
{
	_24c02_TimingDelay = nTime;
	while(_24c02_TimingDelay != 0);	
}

3.中断部分

1.滴答中断部分

ps:这个在stm32f10x_it.c中,我也是直接将一些延时和标志位引用过来,实现延时或者等待一定时长的周期性动作。
//在stm32f10x_it.c中添加引用的.h文件
#include "key.h"
#include "adc.h"
#include "24c02.h"

/*引用的各项参数*/
extern u32 _24c02_TimingDelay;//24c02计时的计数单元
//引用自24c02.c

extern u8 key_scan_flag;//按键扫描标志
extern u8 key_scan_delay;//按键扫描延时
//引用自key.c

extern u8 get_adc_flag;//adc采样标志
extern u16 get_adc_delay;//adc采样延时
extern u8 delay_led2_200_ms;//LED2灯闪烁的延时单元
extern u8 delay_led3_200_ms;//LED3灯闪烁的延时单元
extern u8 led2_on_flag;//LED2点亮标志
extern u8 led3_on_flag;//LED3点亮标志
//引用自adc.c

void SysTick_Handler(void)//中断一次1ms
{
	
	TimingDelay--;//主函数本体中定义的那个延时函数的计时单元
	
	_24c02_TimingDelay--;//24c02的延时计时单元
	
	if(--key_scan_delay==0)//按键计时
	{
		key_scan_flag=1;//允许按键读值
	}
	
	if(--get_adc_delay==0)//adc取值计时
	{
		get_adc_flag=1;//允许adc读值
	}
	
	if(--delay_led2_200_ms==0)//led2灯亮灭延时周期
	{
		led2_on_flag=1;//允许led2灯的状态改变——亮灭状态切换
	}
	
	if(--delay_led3_200_ms==0)//led3灯亮灭延时周期
	{
		led3_on_flag=1;//允许led3灯的状态改变——亮灭状态切换
	}
}

4.LCD显示部分

ps:我把显示部分写在lcd.c中的,所以写完之后需要该函数添加声明到lcd.h中。
/*******参数引用********/
//ps:还需要在lcd.h中添加#include "stdio.h"     #include "key.h"      #include "adc.h"
//ps:在lcd.h中添加:             void LCD_SHOW_LEVEL_THRESHOLD(void);//进行函数声明

extern FLAG flag;//建立标志位结构体
extern VAL Threshold;//建立液压阈值结构体
//引用自key.c
extern u8 high_val;//液压高度
extern double v_val;//采集电压值
//引用自adc.c

u8 lcd_show_data_string[4][20];//lcd需要显示的字符串

//显示当前显示内容
void LCD_SHOW_LEVEL_THRESHOLD(void)
{
	if(flag.set_flag)//进入设置界面显示
	{
		//flag.set_num_flag————0-1,1-2,2-3,标志值与参数项对应的关系
		
		LCD_SetTextColor(White);//标题显示颜色
		sprintf((char*)lcd_show_data_string[0],"   Parameter Setup          ");//标题显示内容
		sprintf((char*)lcd_show_data_string[1]," Threshold 1: %d cm            ",Threshold.Threshold1);//液压高度阈值1显示内容
		sprintf((char*)lcd_show_data_string[2]," Threshold 2: %d cm            ",Threshold.Threshold2);//液压高度阈值2显示内容
		sprintf((char*)lcd_show_data_string[3]," Threshold 3: %d cm            ",Threshold.Threshold3);//液压高度阈值3显示内容
		
		LCD_DisplayStringLine(Line1,lcd_show_data_string[0]);//显示标题内容
		
		if(flag.set_num_flag==0)//该判断用于判别该项是否是所选项,是否需要高显
		{
			LCD_SetTextColor(White);//白色高显,也可以更改,根据个人喜好吧
		}
		else
		{
			LCD_SetTextColor(Black);//黑色普通显示
		}
		LCD_DisplayStringLine(Line3,lcd_show_data_string[1]);//显示阈值1
		
		if(flag.set_num_flag==1)//该判断用于判别该项是否是所选项,是否需要高显
		{
			LCD_SetTextColor(White);//白色高显
		}
		else
		{
			LCD_SetTextColor(Black);//黑色普通显示
		}
		LCD_DisplayStringLine(Line5,lcd_show_data_string[2]);//显示阈值2
		
		if(flag.set_num_flag==2)//该判断用于判别该项是否是所选项,是否需要高显
		{
			LCD_SetTextColor(White);//白色高显
		}
		else
		{
			LCD_SetTextColor(Black);//黑色普通显示
		}
		LCD_DisplayStringLine(Line7,lcd_show_data_string[3]);//显示阈值3
		
	}
	else//工作界面
	{
		LCD_SetTextColor(White);//标题显示颜色
		sprintf((char*)lcd_show_data_string[0],"     Liquid Level          ");//标题内容
		sprintf((char*)lcd_show_data_string[1],"   Height:  %d cm            ",high_val);//液压高度内容
		sprintf((char*)lcd_show_data_string[2],"   ADC_put: %.2f V            ",v_val);//电压值内容
		sprintf((char*)lcd_show_data_string[3],"   Level:   %d              ",Threshold.Level);//液压等级内容
		
		LCD_DisplayStringLine(Line1,lcd_show_data_string[0]);//显示标题
		LCD_DisplayStringLine(Line3,lcd_show_data_string[1]);//显示液压高度
		LCD_DisplayStringLine(Line5,lcd_show_data_string[2]);//显示电压值
		LCD_DisplayStringLine(Line7,lcd_show_data_string[3]);//显示液压等级
		
	}
}

5.主函数体部分

#include "stm32f10x.h"
#include "lcd.h"//引用LCD的相关函数
#include "i2c.h"//引用IIC的相关函数
#include "uart.h"//引用串口的相关函数
#include "led.h"//引用LED灯的相关函数
#include "24c02.h"//引用24c02的相关函数
#include "adc.h"//引用ADC的相关函数
#include "key.h"//引用按键的相关函数
#include "stdio.h"//引用该函数,允许调用sprintf来创建指定字符串

u32 TimingDelay;//延时计数单元

//延时函数
void Delay_Ms(u32 nTime);

//Main Body
int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组
	STM3210B_LCD_Init();
	LCD_Clear(Blue);
	LCD_SetBackColor(Blue);
	LCD_SetTextColor(White);
	
	SysTick_Config(SystemCoreClock/1000);//滴答分频——默认1ms1次
	
	i2c_init();//初始化I2C——才能使用24c02
	_24c02_data_read_threshold();//读取数据
	ADC1_Init();//ADC初始化
	USART2_Init(9600);//串口初始化
	LED_Init();//LED初始化
	KEY_Init();//按键初始化
	
	while(1)
	{
		LCD_SHOW_LEVEL_THRESHOLD();//LCD显示界面
		
		LED_ALL_OFF();//熄灯——解决LCD引脚共用导致的LED灯乱显
		
		V_High_val();//液位测量/运行指示灯
		
		level_juge();//判断当前液位等级
		
		uart_check();//串口数据读取
		
		key_read();//按键状态读取
		
		key_val_set_change();//处理按键下的阈值变化
	}
}

//延时函数
void Delay_Ms(u32 nTime)
{
	TimingDelay = nTime;
	while(TimingDelay != 0);	
}

练习总结

1.模块总结

(对使用的模块的一个回顾)

  1. 按键:

     初始化配置后,就是*按键扫描*配置,在基本功能函数写完——就要开始具体功能函数部分的设计。按键主体部分
     就是对应按键按下后,*对相应的状态进行标记* **(我是用状态来执行的,所以呢,大家如果对状态标志不是很
     喜欢的,可以直接对应把具体的功能动作写在该按键按下的部分。)**。在标记完成后,执行相应的*高度等级阈
     值设置/液位高度等级的判定*——当然这些个函数也是可以写在任意部分的,只是呢,我习惯于把逻辑相关放在一
     起,由于要设置值会用到按键所以就放在按键这里,值确定,相应的等级判定也放在这里就好。
    
  2. LED:

     LED部分除了初始化,就是对LED的控制函数的设计,实现LED指定点亮和LED全灭——排除LCD的引脚共用导致的LED
     乱显。
     至于,代码中的检测部分——亮灭函数,是用来检测函数bug的(检测bug还可以用返回值在LCD上显示等方法)。
    
  3. ADC:

     在初始化配置结束之后,主要就是对adc值的读取——以及对adc值的处理(这里用到了软件滤波),转换实际意义的
     值。
    
  4. 串口:

     初始化配置结束后,就是对向pc端的发送数据的函数的编写,以及中断接收(pc端发来的数据)串口数据——以及数
     据的分析处理:就是对传来的数据进行判断,哪一个数据对应发送什么内容。
    
  5. 24C02:

     这个部分就没什么东西了,有的只是将一些具体存储的函数的设计和定义,比较简单,基本都是固定的,需要好好
     记住基本功能的配置——写入和读取。
    

2.逻辑回顾

(对一些逻辑思考的简单回顾)

从adc读取电压值模拟液面高度,由按键设置好并储存的阈值判断确定液面高度等级,在LCD上显示。
在按键进入设置界面后,通过相应按键切换选中参数项,实现加或者减值的功能,并在LCD上显示。
并且实时关注液面高度等级变化,实时通过串口发送信息给pc端,并且可以在任意时刻,由pc端发送'S'或'C'来实现查询。

结束

我也知道,这是我第一次写blog,可能有很多地方做的不好,不过呢本意就是让自己学习到的东西和一些学习经验分享给
大家,同时也是换一种方式记录自己的经验和成长。
如果大家在阅读时发现任何问题,都可以评论或者其他方式联系我。
集思广益,共同进步。
发布了4 篇原创文章 · 获赞 4 · 访问量 44

猜你喜欢

转载自blog.csdn.net/weixin_44604887/article/details/103977080