STM32LED--基于HAL库(第七届蓝桥杯嵌入式省赛)


前言

相关说明:

开发板:CT117E-M4(STM32G431RB 蓝桥杯嵌入式比赛板)
开发环境: CubeMX+Keil5
涉及题目:第七届蓝桥杯嵌入式省赛
题目难点:LCD与LED冲突处理,保证LED正常闪烁频率
(本次项目编写运用了模块化编程,更有利于代码阅读与理解。)


CubeMX配置、主要函数代码及说明:

一、CubeMX配置(第七届模拟题完整版)

1.使能外部高速时钟:在这里插入图片描述

2.配置时钟树:在这里插入图片描述
3.GPIO:

在这里插入图片描述

4.ADC(检测R37电压值):在这里插入图片描述

5.TIM6:
在这里插入图片描述

6.I2C:在这里插入图片描述

7.USART:在这里插入图片描述

二、代码相关定义、声明

1.函数声明

main.c
void EEPROM_WriteBuff(uint8_t addr,uint8_t *writeBuff,uint16_t numByteToWrite);	//EEPROM写数组函数
void EEPROM_ReadBuff(uint8_t addr,uint8_t *readBuff,uint16_t numByteToRead);	//EEPROM读数组函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//定时器中断函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);	//串口接收中断
void LCD_Init_Show();					//LCD初始化显示界面
void LCD_Refresh(uint8_t page);			//LCD更新
void Level_change();					//检测Level是否改变
void Data_Read();						//数据读取
void Data_Save();						//数据保存

gpio.h
void KEY_Scan(void);					//按键扫描
void LED_AllClose(uint8_t *LED_Close);	//LED关闭函数

adc.h
double ADC_GetValue(void);				//获取R37电压值

2.宏定义

gpio.h
#define LED_GPIO_PORT 	GPIOC
#define LED1_GPIO_PIN	GPIO_PIN_8
#define LED2_GPIO_PIN	GPIO_PIN_9
#define LED3_GPIO_PIN	GPIO_PIN_10
#define LED4_GPIO_PIN	GPIO_PIN_11
#define LED5_GPIO_PIN	GPIO_PIN_12
#define LED6_GPIO_PIN	GPIO_PIN_13
#define LED7_GPIO_PIN	GPIO_PIN_14
#define LED8_GPIO_PIN	GPIO_PIN_15

#define	ON 	GPIO_PIN_RESET
#define OFF	GPIO_PIN_SET

#define LED1(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED1_GPIO_PIN,a)
#define LED2(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED2_GPIO_PIN,a)
#define LED3(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED3_GPIO_PIN,a)
#define LED4(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED4_GPIO_PIN,a)
#define LED5(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED5_GPIO_PIN,a)
#define LED6(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED6_GPIO_PIN,a)
#define LED7(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED7_GPIO_PIN,a)
#define LED8(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED8_GPIO_PIN,a)

#define KEY1_GPIO_PORT GPIOB
#define KEY1_GPIO_PIN  GPIO_PIN_0	
#define KEY2_GPIO_PORT GPIOB
#define KEY2_GPIO_PIN  GPIO_PIN_1
#define KEY3_GPIO_PORT GPIOB
#define KEY3_GPIO_PIN  GPIO_PIN_2	
#define KEY4_GPIO_PORT GPIOA
#define KEY4_GPIO_PIN  GPIO_PIN_0	

3.变量定义

main.c
uint8_t Threshold_1;		//阈值1
uint8_t Threshold_2;		//阈值2
uint8_t Threshold_3;		//阈值3
uint8_t Threshold_step=5;	//阈值每次改变值
uint8_t Threshold_max=95;	//阈值更改上限值
uint8_t Threshold_min=5;	//阈值更改下限值
uint8_t EE_T1_addr=0x00;	//T1在EEPROM中的存储地址
uint8_t EE_T2_addr=0x08;	//T2在EEPROM中的存储地址
uint8_t EE_T3_addr=0x10;	//T3在EEPROM中的存储地址

double v_value; 			//R37电压值
int heigh;					//液位高度
uint8_t level;				//液位等级
uint8_t level_old;			//上一次液位等级

uint8_t recDat;				//串口接收的数据

uint8_t LED3_Flag=0;		//LED3闪烁标志位
uint8_t LED2_Flag;			//LED2闪烁标志位
uint8_t interNum=0;			//进入中断次数 每次为0.1s 10次为1s
uint8_t LED2_intNum=0;		//LED2状态改变次数 每0.1s反转一次 到达10则完成5次闪烁
uint8_t LED3_intNum=0;		//LED3状态改变次数 每0.1s反转一次 到达10则完成5次闪烁

char str[30];				//用于组合字符串

uint8_t LED_Close[4]={
    
    0,0,1,1};	//LED灯关闭数组 值为1 则下标对应LED关闭

gpio.c
uint8_t		  line_now=96;//当前行对应坐标 下面同理
const uint8_t line_step=48;
const uint8_t line_beg=96;
const uint8_t line_end=192;

const uint8_t add=1;
const uint8_t sub=2;

extern uint8_t Threshold_1;
extern uint8_t Threshold_2;
extern uint8_t Threshold_3;
extern uint8_t Threshold_step;
extern uint8_t Threshold_max;
extern uint8_t Threshold_min;
extern uint8_t LED_Close[4];
extern uint8_t interNum;

三、主要函数

1.按键相关函数

将按键按下后执行的操作封装为一个个独立的函数进行调用,降低函数间的耦合度。

gpio.c
/* KEY */
void Setting_Mode()//设置模式
{
    
    
	line_now=line_beg;
	Line_change();
	while(1)
	{
    
    
		LED_AllClose(LED_Close);//更新LED
		if(interNum>9)//到达1s后重置中断次数标志位
			interNum=0;
		if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)//save data
		{
    
    
			HAL_Delay(10);
			if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)
			{
    
    
				while(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET);
				Data_Save();//保存数据
				LCD_Refresh(1);//LCD更新为第一页
				break;//退出循环
			}
		}
		
		else if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET)//change line
		{
    
    
			HAL_Delay(10);
			if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET)
			{
    
    
				while(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET);
				line_now+=line_step;//行增加
				if(line_now>line_end)//若大于结尾行则回到起始行
					line_now=line_beg;
				Line_change();//更新显示
			}
		}
		
		else if(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET)//++
		{
    
    
			HAL_Delay(10);
			if(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET)
			{
    
    
				while(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET);
				Data_change(add);//在加模式更改数据
				Line_change();//更新显示
			}
		}
		
		else if(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET)//--
		{
    
    
			HAL_Delay(10);
			if(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET)
			{
    
    
				while(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET);
				Data_change(sub);//在减模式更改数据
				Line_change();//更新显示
			}
		}
	}
}

void KEY_Scan()//按键扫描
{
    
    	
	if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)
	{
    
    
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)
		{
    
    
			while(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET);
			LCD_Refresh(2);//LCD更新为第二页
			Setting_Mode();//进入设置模式
		}
	}
}

2.换行

当前行高亮,前一行恢复。

gpio.c
void Line_change()
{
    
    
	char str1[30];
	char str2[30];
	switch(line_now)
	{
    
    
		case line_beg+0*line_step: 
			sprintf(str1,"  Threshold 1:%dcm  ",Threshold_1);//当前行
			sprintf(str2,"  Threshold 3:%dcm  ",Threshold_3);//上一行
			break;
		
		case line_beg+1*line_step: 
			sprintf(str1,"  Threshold 2:%dcm  ",Threshold_2);
			sprintf(str2,"  Threshold 1:%dcm  ",Threshold_1);
			break;

		case line_beg+2*line_step: 
			sprintf(str1,"  Threshold 3:%dcm  ",Threshold_3);
			sprintf(str2,"  Threshold 2:%dcm  ",Threshold_2);
			break;
	}
	
	LCD_SetBackColor(White);
	LCD_SetTextColor(Black);
	LCD_DisplayStringLine(line_now,(unsigned char*)str1);//当前行高亮
	
	LCD_SetBackColor(Black);
	LCD_SetTextColor(White);
	if(line_now==line_beg)//第一行的上一行为最后一行
		LCD_DisplayStringLine(line_end,(unsigned char*)str2);//上一行恢复
	else
	LCD_DisplayStringLine(line_now-line_step,(unsigned char*)str2);	
}

3.更改数据

mode判断加模式还是减模式,switch判断是哪种数据要被改变。

gpio.c
void Data_change(uint8_t mode)
{
    
    
	switch(line_now)
	{
    
    
		case line_beg+0*line_step: 
			if(mode==add)
			{
    
    
				if(Threshold_1+Threshold_step!=Threshold_2)//增加后不可与第二阈值相等
					Threshold_1+=Threshold_step;
			}
			else
			{
    
    
				Threshold_1-=Threshold_step;
				if(Threshold_1<Threshold_min)//减小后不可小于最低阈值
					Threshold_1=Threshold_min;
			}
			break;
		
		case line_beg+1*line_step:
			if(mode==add)
			{
    
    
				if(Threshold_2+Threshold_step!=Threshold_3)//增加后不可与第三阈值相等
					Threshold_2+=Threshold_step;
			}
			else
			{
    
    
				if(Threshold_2-Threshold_step!=Threshold_1)//减小后不可与第一阈值相等
					Threshold_2-=Threshold_step;
			}
			break;
		
		case line_beg+2*line_step:
			if(mode==add)
			{
    
    
				Threshold_3+=Threshold_step;
				if(Threshold_3>Threshold_max)//增加后不可大于最高阈值
					Threshold_3=Threshold_max;
			}
			else
			{
    
    
				if(Threshold_3-Threshold_step!=Threshold_2)//减小后不可与第二阈值相等
					Threshold_3-=Threshold_step;
			}
			break;
	}
}

4.检测液位等级是否改变

随电压值、阈值变化而变化,与电压值一样每一秒检测一次即可。

main.c
void Level_change()//检测Level是否改变
{
    
    
	static uint8_t first=1;//第一次进入函数标志位(开机初始化时调用)
	level_old=level;
	if(heigh<=Threshold_1)
	{
    
    
		level=0;
	}
	else if(Threshold_1<heigh && heigh<=Threshold_2)
	{
    
    
		level=1;
	}
	else if(Threshold_2<heigh && heigh<=Threshold_3)
	{
    
    
		level=2;
	}
	else if(Threshold_3<heigh)
	{
    
    
		level=3;
	}
	if(first)//第一次认为无改变
	{
    
    
		first=0;
		level_old=level;
	}
	else if(level!=level_old)//如果发生改变
	{
    
    
		LED2_Flag=1;		//LED2开启闪烁
		LED2_intNum=0;	//重置LED2状态改变次数
		if(level>level_old)
			printf("a:H%d+L%d+U\r\n",heigh,level);
		else
			printf("a:H%d+L%d+D\r\n",heigh,level);
	}
}

5.串口接收中断

接收到数据后开启LED3闪烁,并判断接收的指令进行反馈。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口接收中断
{
    
    
	LED3_Flag=1;	//LED3开启闪烁
	LED3_intNum=0;	//重置LED3状态改变次数
	if(recDat=='C') //如果接收的数据为C
	{
    
    
		printf("C:H%d+L%d\r\n",heigh,level);
	}
	else if(recDat=='S')	//如果接收的数据为S
	{
    
    
		printf("S:TL%d+TM%d+TH%d\r\n",Threshold_1,Threshold_2,Threshold_3);
	}
	HAL_TIM_Base_Start_IT(&htim6);
	HAL_UART_Receive_IT(&huart1,&recDat,1);
}

6.定时器中断

控制LED状态

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器中断函数
{
    
    
	interNum++;
	if(interNum>9)//到达10则重置
	{
    
    
		if(LED_Close[1]==1)
			LED_Close[1]=0;
		else
			LED_Close[1]=1;
	}
	if(LED2_Flag)//LED2开启闪烁
	{
    
    
		LED2_intNum++;
		if(LED_Close[2]==1)
			LED_Close[2]=0;
		else
			LED_Close[2]=1;
		HAL_GPIO_TogglePin(LED_GPIO_PORT,LED2_GPIO_PIN);
		if(LED2_intNum>9)
		{
    
    
			LED2_Flag=0;
			LED2_intNum=0;
			LED_Close[2]=1;
		}
	}
	if(LED3_Flag)//LED3开启闪烁
	{
    
    
		LED3_intNum++;
		if(LED_Close[3]==1)
			LED_Close[3]=0;
		else
			LED_Close[3]=1;
		HAL_GPIO_TogglePin(LED_GPIO_PORT,LED3_GPIO_PIN);
		if(LED3_intNum>9)
		{
    
    
			LED3_Flag=0;
			LED3_intNum=0;
			LED_Close[3]=1;
		}
	}
}

7.LED关闭函数

本题6(定时器中断)+7(LED关闭函数)即可实现闪烁频率的精准控制,6控制灯状态为灭还是亮,保存在数组里,LCD就影响不了,再调用7判断如果数组中值为1,则下标对应LED关闭,否则为开启,理解了逻辑很好写。

void LED_AllClose(uint8_t *LED_Close)//LED_Close数组中值为1 则下标对应LED关闭
{
    
    
	uint8_t i;
	LED1(ON);//默认开启LED1~LED3
	LED2(ON);
	LED3(ON);
	for(i=1;i<4;i++)
	{
    
    
		if(LED_Close[i])//若值为1则关闭 值为0则亮 通过修改数组值达到闪烁
		{
    
    
			switch(i)
			{
    
    
				case 1:
					LED1(OFF);
					break;
				
				case 2:
					LED2(OFF);
					break;
				
				case 3:
					LED3(OFF);
					break;
			}
		}
	}
	LED4(OFF);
	LED5(OFF);
	LED6(OFF);
	LED7(OFF);
	LED8(OFF);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}

8.Main函数

int main(void)
{
    
    
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC2_Init();
  MX_TIM6_Init();
  MX_USART1_UART_Init();
  I2CInit();
  /* USER CODE BEGIN 2 */
	LCD_Init();//LCD初始化
	HAL_ADC_Start(&hadc2);//ADC开启
	Data_Read();//从EEPROM中读取数据
	v_value=ADC_GetValue();//获取R37电压值
	heigh=v_value/3.3*100;//计算当前液位高度
	Level_change();//液位等级更新
	LCD_Init_Show();//LCD显示初始化 将以上数据进行显示
	TIM6->SR=0;//定时器更新标记清零
	TIM6->CNT=0;//重置计数值
	HAL_TIM_Base_Start_IT(&htim6);//开启定时器
	HAL_UART_Receive_IT(&huart1,&recDat,1);//开启串口接收中断
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    
    
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
		KEY_Scan();//按键扫描
		if(interNum>9)//如果进入10次中断 每次0.1s 即1s
		{
    
    
			interNum=0;//重置进入中断次数标记位
			v_value=ADC_GetValue();//更新R37电压值
			heigh=v_value/3.3*100;//更新液位高度
			Level_change();//更新液位等级
			LCD_Refresh(1);	//更新LCD显示
		}
		LED_AllClose(LED_Close);//实时检测是否需要更新LED状态 达到准确无误的闪烁频率
  }
  /* USER CODE END 3 */
}

四、实验结果

五、源码(转载请注明出处)

在这里插入图片描述


总结

以上就是全部内容,如有错误请批评指正。

猜你喜欢

转载自blog.csdn.net/Octopus1633/article/details/123621583