一、硬件设计
模拟输出原理图
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按键设置后,各参数如下图。