蓝桥杯电子类嵌入式第十届省赛

一、硬件设计

模拟输出原理图

在这里插入图片描述

LED灯原理图

在这里插入图片描述

LCD原理图

在这里插入图片描述

按键原理图

在这里插入图片描述

所用模块

8个LED
1个LCD
4个按键
1个模拟输入R37

所用引脚

LCD

PC0~PC7 --------- LCD_D0~D7 数据低8位
PC8~PC15 --------- LCD_D8~D15 数据高8位
PA8 --------- LCD_RD 读选通
PB5 --------- LCD_WR 写选通
PB8 --------- LCD_RS 寄存器选择
PB9 --------- LCD_CS 片选

LED

LED灯引脚:PC8~PC15 (分别对应G4板上LED灯的LD1~LD8)

锁存器

PD2:连接锁存器

按键

PA0 --------- B4(按键4)
PB0~PB2:分别对应按键1 — 按键3 即B1、B2、B3

模拟输入

PB15 -------- R37

二、STM32CubeMX配置

1、选择时钟源

外部高速时钟源(HSE)选择crystal/ceramic resonator(晶体/陶瓷晶振)。如图1.1所示
在这里插入图片描述

                                            图1.1
2、设置时钟频率

2.1 选择外部晶振8M
2.2 HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接
外部时钟源,频率范围为4MHz~48MHz。本次选择了8M
2.3 系统时钟SYSCLK(一般SYSCLK=PLLCLK=80MHz)
2.4 在HCLK(AHB总线时钟)一栏输入80,回车即可。如图2.1所示。
在这里插入图片描述

                                            图2.1
3、设置GPIO

注:“所有引脚采用默认设置” 即在右边的“Pinout view”界面设置完成后不用在“GPIO Mode and Configuration”界面更改GPIO的设置。如图3.1和3.2所示。
在这里插入图片描述

                                          图3.1

在这里插入图片描述

                                          图3.2             

LCD引脚:
“所有引脚采用默认设置”
GPIO输出电平:低电平
GPIO模式:推挽输出
GPIO上下拉:无上下拉
GPIO最大输出速度:低速
用户自定义标签:自己根据需求设置
例如:PA8设置如图3.3所示。
在这里插入图片描述

                                          图3.3

LED引脚:
所有引脚采用默认设置 同LCD
用户自定义标签:设置了PD2(LED锁存器)的别名为LEDLOCK

按键引脚B1~B4:
“所有引脚采用默认设置”
GPIO模式:输入
GPIO上下拉:无上下拉
用户自定义标签:设置了按键的别名为KEY1~KEY4

模拟输入R37:
GPIO模式:模拟输入
GPIO上下拉:无上下拉

4、设置ADC

1、勾选“IN15 Single-ended”(single-ended是单端信号)。如图4.1所示。
在这里插入图片描述

                                          图4.1

选择后PB15的ADC2_IN15(ADC2的通道15)变成绿色(即表示选用成功)。如图4.2所示。
在这里插入图片描述

                                          图4.2

可在图4.3中设置ADC2的参数,这里采用默认设置。
在这里插入图片描述

                                          图4.3
5、设置TIM

5.1 预分频系数(Prescaler)设置为1599
5.2 主计数器(Counter Period)设置为9999

知识补充:
a. APB2负责AD,I/O,高级TIM,串口1。
APB1负责DA,USB,SPI,I2C,CAN,串口2345,普通TIM
TIM1是高级定时器,包括了基本定时器和通用定时器的所有功能。

b. HZ是赫兹,频率单位,1MHZ(兆赫)=1000KHZ(千赫)=1000000HZ

c. 定时器发生中断时间的计算方法:
定时时间 = (Prescaler+1 ) X (Counter Period+1) X 1/ 定时器时钟频率(信号的周期)
时钟信号1KHz,Prescaler为9,Counter Period为999,定时时间?
定时时间=10x1000x1/1000=10s=10000ms

因此为了满足题意“以0.2s为间隔闪烁”,可将Prescaler设置为1599,Counter Period设置为9999。定时时间=1600x10000x1/80000000=0.2s
当然Prescaler和Counter Period的值可以改成其他数值,满足题意并且可实现就行。
由图5.1可知, 定时器时钟频率为80MHZ,即80000000HZ

在这里插入图片描述

                                          图5.1

选择内部时钟,Prescaler设置为1599,Counter Period设置为9999。如图5.2所示。
在这里插入图片描述

                                          图5.2

5.3 设置TIM中断
勾选“TIM1 update interrupt and TIM16 global interrupt”,即选用TIM1的更新中断和TIM16的全局中断。如图5.3所示。
在这里插入图片描述

                                          图5.3
6、工程管理设置

6.1 自己命名,如num101,表示蓝桥杯电子类嵌入式的第十届省赛题。如图6.1所示。
6.2 选择对应的IDE,如MDK-ARM。如图6.1所示。
在这里插入图片描述

                                          图6.1

6.3 代码生成一栏,勾选图中的“Generate peripheral initialization as a pair of '.c/.h files per peripheral”,即单独生成对应外设的.c和.h文件。如图6.2所示。
在这里插入图片描述

                                          图6.2
7、生成代码

点击右上角的“构建代码”。如图7.1所示。
在这里插入图片描述

                                          图7.1

三、添加LCD相关文件到工程中

1、将fonts.h和lcd.h添加到如下工程的文件夹中。如图1.1所示。
在这里插入图片描述

                                          图1.1

注:inc文件夹一般放.h文件

1、将lcd.c添加到如下工程的文件夹中。如图2.1所示。
在这里插入图片描述

                                          图2.1

四、Keil MDK 5配置

4.1 添加文件路径

注:(本次可省略添加文件路径的步骤,因为原工程已经添加了含有LCD相关文件的路径,以后要添加其他文件,一定要记得添加文件路径)
1、打开Keil5,点击魔术棒。如图1.1所示。在这里插入图片描述

                                          图1.1

2、点击“C/C++”。如图2.1所示。
在这里插入图片描述

                                          图2.1

3、点击“Include Paths”一栏的三个点,再点击“带黄色的图标”来添加路径。如图3.1所示。
在这里插入图片描述

                                          图3.1

4.2 设置Debug

按图中的配置。
在这里插入图片描述

4.3 添加文件到工程中

1、右击“User/Core”,点击“Manage Project Items…”。如图1.1所示。
在这里插入图片描述

                                          图1.1

2、点击“User/Core”那一栏,点击右下角的“Add Files…”。如图2.1所示。
在这里插入图片描述

                                          图2.1

3、找到lcd.c文件,点击一次“Add”即可(点击多次会重复添加,编译会报错)。如图3.1所示。
注:尽量选用“All files”来显示文件,不然有的文件存在但是看不到,如.s后缀的文件
在这里插入图片描述

                                          图3.1

4、添加成功后,点击“OK”即可。
在这里插入图片描述

五、软件设计

Kamimiao大佬的代码仓库
注:在Kamimiao大佬的代码上,添加了些注释,代码有所改动,整体逻辑结构不变

1、引用头文件

main.c中添加头文件。(注:所有代码放到BEGIN和END之间,防止重新生成代码时将原来的代码覆盖掉)
在这里插入图片描述

#include "lcd.h"
#include "stdio.h"

2、定义全局变量

在这里插入图片描述

char lcdbuf[20]; //用于存储显示到LCD上的字符串
double maxv=2.4; //电压的上限值
double minv=1.2; //电压的下限值
uint16_t upper=1; //电压超过上限的提醒指示灯的标号
uint16_t lower=2; //电压低于下限的提醒指示灯的标号
uint8_t setmode=0; //参数项的标号(参数项:电压的上限值、电压的下限值、电压超过上限的提醒指示灯和低于下限的提醒指示灯) 
uint8_t yemian=0; //页面号
char status[10];  //用于存储实时采集的电压状态
uint8_t ledmode=0; //led的闪烁选择
uint16_t tim_ok=0; //通过tim_ok值的变化,实现0.2s闪烁
//uint8_t voltclear=0; //电压恢复正常清空LED标志

3、函数声明

在这里插入图片描述

void led(int mode); //led的设置
double adcread(ADC_HandleTypeDef *hadc); //获取电压值
void ledboom(void); //led状态控制
void ledclear(void); //熄灭所有LED灯
void yemianmode(void); //页面切换
void setting(void); //按键功能
void lcdlive(void); //页面显示

4、函数定义

熄灭所有LED灯
//熄灭所有LED灯
void ledclear(void) 
{
	HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin, GPIO_PIN_SET); //锁存器开
	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET); //LED全部熄灭
	HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin, GPIO_PIN_RESET); //锁存器关
	
}
页面切换
//页面切换
//  当yemian=0时,切换到数据显示界面 ;     当yemian=1时,切换到参数配置界面
void yemianmode(void) 
{	
	if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET) //页面切换按键B1 
	{
		while(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET); 
		yemian++;
		if(yemian==2)
		{
			yemian=0;
		}
		LCD_Clear(White); //清屏为白色
		ledclear(); //熄灭所有LED灯
	}
}
按键功能
//按键功能
//在参数配置界面下,通过按键B2切换选择参数项,通过按键B3(加键)和按键B4(减键)对电压的上限值、电压的下限值、电压超过上限的提醒指示灯的标号、电压低于下限的提醒指示灯的标号进行加减操作
void setting(void) 
{
	if(yemian==1) //该判断保证了按键B2~B4仅在参数配置界面生效
	{
		if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET) //切换选择参数项按键B2
		{
			while(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET); //防止长按按键B2,松开按键,电平变为高电平,跳出while循环
			setmode++;
			if(setmode==5) //参数项的标号值为1~4
			{
				setmode=1;
			}
		}
		else if(HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET) //加键B3
		{
			while(HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET);
			if(setmode==1)      { maxv += 0.3; }
			else if(setmode==2) { minv += 0.3; }
			else if(setmode==3) { upper++; }
			else if(setmode==4) { lower++; }

		}
		else if(HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET) //减键B3
		{
			while(HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET);
			if(setmode==1)      { maxv -= 0.3; }
			else if(setmode==2) { minv -= 0.3; }
			else if(setmode==3) { upper--; }
			else if(setmode==4) { lower--; }

		}	
		//错误设置保护功能
		if(maxv > 3.3) { maxv = 0; } if(maxv < 0) { maxv = 3.3; } //确保电压上限值范围为0~3.3V
		if(minv > 3.3) { minv = 0; } if(minv < 0) { minv = 3.3; } //确保电压下限值范围为0~3.3V
		if(upper > 8) { upper = 1; } if(upper < 1) { upper = 8; } //确保电压上限提醒指示灯范围为1~8
		if(lower > 8) { lower = 1; } if(lower < 1) { lower = 8; } //确保电压下限提醒指示灯范围为1~8
		if(upper == lower) { lower -=1; } //禁止将上下限指示灯设置为同一个指示灯
	}
}
获取电压值
//获取电压值
double adcread(ADC_HandleTypeDef *hadc) 
{
	double adc = 0;
	HAL_ADC_Start(&hadc2);
	adc = (HAL_ADC_GetValue(&hadc2) *3.3)/4096; 
	if(adc<maxv && adc >minv) {sprintf(status,"Normal");led(0);} //0 = Normal 1 = Upper  2 = Lower   比较电压与设定值关系
	if(adc>maxv)              {sprintf(status,"Upper"); led(1);}
	if(adc<minv)              {sprintf(status,"Lower"); led(2);}
	return adc;
}
页面显示
//页面显示
void lcdlive(void)
{
	if(yemian == 0) //数据显示界面
	{

		LCD_DisplayStringLine(Line1, (u8 *)"        Main        ");
		sprintf(lcdbuf,"     Volt:%.2lfV ",adcread(&hadc2));
		LCD_DisplayStringLine(Line3, (u8 *)lcdbuf);
		sprintf(lcdbuf,"     Status:%s ",status);
		LCD_DisplayStringLine(Line5, (u8 *)lcdbuf);

//		sprintf(lcdbuf, "     tim_ok:%d ",  tim_ok);			//定时器测试
//		LCD_DisplayStringLine(Line7, (uint8_t *)lcdbuf);

	}
	if(yemian == 1) //参数配置界面
	{
		HAL_TIM_Base_Stop_IT(&htim1); //关闭定时器1中断    确保在参数配置界面下,灯全灭         另一种操作 //ledclear(); //熄灭所有LED灯		
		LCD_DisplayStringLine(Line1, (u8 *)"        Setting        ");
		sprintf(lcdbuf,"     Max Volt:%.1lfV",maxv);
		if(setmode != 1) { LCD_DisplayStringLine(Line3, (u8 *)lcdbuf); }
		sprintf(lcdbuf,"     Min Volt:%.1lfV",minv);
		if(setmode != 2) { LCD_DisplayStringLine(Line5, (u8 *)lcdbuf); }
		sprintf(lcdbuf,"     Upper:LD%d",upper);
		if(setmode != 3) { LCD_DisplayStringLine(Line7, (u8 *)lcdbuf); }
		sprintf(lcdbuf,"     Lower:LD%d",lower);
		if(setmode != 4) { LCD_DisplayStringLine(Line9, (u8 *)lcdbuf); }
		
		//被选择的参数项“高亮”显示
		LCD_SetBackColor(Blue); //蓝底
		LCD_SetTextColor(Red);  //红字
		
		if(setmode == 1)      { sprintf(lcdbuf,"     Max Volt:%.1lfV",maxv); LCD_DisplayStringLine(Line3, (u8 *)lcdbuf); }
		else if(setmode == 2) { sprintf(lcdbuf,"     Min Volt:%.1lfV",minv); LCD_DisplayStringLine(Line5, (u8 *)lcdbuf); }
		else if(setmode == 3) { sprintf(lcdbuf,"     Upper:LD%d",upper);     LCD_DisplayStringLine(Line7, (u8 *)lcdbuf); }
		else if(setmode == 4) { sprintf(lcdbuf,"     Lower:LD%d",lower);     LCD_DisplayStringLine(Line9, (u8 *)lcdbuf); }
		
		LCD_SetBackColor(White);  //白底
		LCD_SetTextColor(Blue);   //蓝字
	}
	
}
led的设置
//led的设置
void led(int mode) 
{
	if(mode == 0) //0 = Normal 电压正常
	{
		HAL_TIM_Base_Stop_IT(&htim1); //关闭定时器1中断  确保灯全灭
		__HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE); //清除定时器1的更新中断标志   1492   742
		//ledmode = 0;  //led不闪烁
		ledclear(); //熄灭所有LED灯

	}
	else if(mode == 1)  //1 = Upper  超过电压上限值
	{
		HAL_TIM_Base_Start_IT(&htim1); //开启定时器1中断
		ledmode=1; //电压超过上限的提醒指示灯闪烁的标志位
	}
	else if(mode == 2) //  2 = Lower 低于电压下限值
	{
		HAL_TIM_Base_Start_IT(&htim1); //开启定时器1中断
		ledmode=2; //电压低于下限的提醒指示灯闪烁的标志位
	}
	

}
led状态控制
//led状态控制
void ledboom(void)
{
	uint16_t ledpc = 0; 
	if(tim_ok == 0) 
	{
			ledclear(); //熄灭所有LED灯
	
	}
	else if(tim_ok == 1)
	{
		if(ledmode == 1) //电压超过上限的提醒指示灯闪烁的标志位
		{
			ledpc = upper+7;  //闪烁的电压超过上限的提醒指示灯标号
		}
		else if(ledmode == 2) //电压低于下限的提醒指示灯闪烁的标志位
		{
			ledpc = lower+7; //闪烁的电压低于下限的提醒指示灯标号
		}
		
		HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin, GPIO_PIN_SET); //锁存器开
		
		//对应灯亮,其他灯全灭
		if(ledpc == 8 )       { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET); }
		else if(ledpc == 9 )  { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET); }
		else if(ledpc == 10 ) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET); }
		else if(ledpc == 11 ) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET); }
		else if(ledpc == 12 ) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET); }
		else if(ledpc == 13 ) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET); }
		else if(ledpc == 14 ) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_15, GPIO_PIN_SET); }
		else if(ledpc == 15 ) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_15, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14, GPIO_PIN_SET); }
		
		HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin, GPIO_PIN_RESET); //锁存器关
		
	}
}	

while循环前

在这里插入图片描述

	LCD_Init(); //LCD初始化
	LCD_Clear(White); //清屏为白色
	LCD_SetBackColor(White); //白底
	LCD_SetTextColor(Blue); //蓝字
	
	ledclear(); //熄灭所有LED灯
	__HAL_TIM_CLEAR_IT(&htim1,TIM_IT_UPDATE); //清除定时器1的更新中断标志   确保while循环之前不重复进入定时器中断
while循环里

在这里插入图片描述

	yemianmode(); //页面切换
	setting(); //按键功能
	lcdlive(); //页面显示
while循环后

注:回调函数无需在main函数前声明
在这里插入图片描述

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)  //周期运行回调,配置定时进入中断
{
	if(htim->Instance == TIM1) 
	{
		//通过tim_ok值的变化,实现0.2s闪烁
		if(tim_ok == 1) { tim_ok = 0; } 
		else tim_ok =1; 
		
		//翻转对应灯的电平状态
		ledboom(); //led状态控制
	}
	
}

六、函数位置

TIM

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#define __HAL_TIM_CLEAR_IT(__HANDLE__, __INTERRUPT__)      ((__HANDLE__)->Instance->SR = ~(__INTERRUPT__))

HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
HAL_StatusTypeDef HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim);

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);

七、实验现象

初始界面。电压过小,电压低于下限的提醒指示灯闪烁。(注:初始界面和LED灯是否亮灭会随电位器的初始状态改变)
在这里插入图片描述
转动电位器R37,调节电压后。电压正常,灯全灭。
在这里插入图片描述
按下按键2,切换到参数设置界面,默认设置如下图。
在这里插入图片描述
在参数设置界面通过B2~B4按键设置后,各参数如下图。
在这里插入图片描述

八、代码下载

Kamimiao大佬的代码仓库

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaolan@

亲,打赏随意

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值