文章目录
蓝桥杯嵌入式第六届省赛
具体题目可见:蓝桥杯嵌入式第六届省题_是大毛吖的博客-CSDN博客
全部用户代码可见:用户代码
工程项目压缩包可见:https://download.csdn.net/download/weixin_46920037/85053104
介绍
需要用到的外设较多,但是逻辑简单。
配置
- 首先当然是打开CubeMX
- 选芯片G431RBx,配置RCC如下
RTC配置
见下图
RTC开了之后,还需要去时钟树界面配置RTC时钟,选到对应的路线,参数按照默认的750即可。
配置ADC
其实ADC的功能算是挺复杂的,但是蓝桥杯的考法非常狭窄,直接把ADC2_IN15通道开了就OK
配置串口:
Asynchronous + 9600 + 开启串口中断 缺一不可
LED和按键的配置
找到对应的IO口,LED配置GPIO输出,按键配置GPIO输入
这里有几个注意的点:
- 一开始我看题目只需要用一个LED,就想只初始化一个,但是后来发现如果不控制其它的LED,会出现随机亮的情况,所以还是全部都初始化并且进行全灭的初始化处理
- 然后是我忘记了初始化PD2,导致后面测试的时候灯只能够操作一次,算是个小坑
然后可以生成项目了,名称和路径随意,符合命名规则即可
E2PROM
生成完项目工程后,移植IIC底层代码即可,顺便把LCD都移植了,放到文件夹里面后记得加载.C文件到项目里面
注意!!!:配置过程中需要核对配置后对应的引脚,配置串口1后我的默认引脚为PC4、PC5,不是PA9、PA10,所以没有初始化成功,算一个小坑。
然后修改下官方的官方的底层驱动
//在i2c - hal.c文件中,注意这里 - 两边有空格
unsigned char I2CWaitAck(void)
{
unsigned short cErrTime = 5;
SDA_Input_Mode();
delay1(DELAY_TIME);
SCL_Output(1);
delay1(DELAY_TIME);
while(SDA_Input())
{
cErrTime--;
delay1(DELAY_TIME);
if (0 == cErrTime)
{
SDA_Output_Mode();
I2CStop();
return ERROR;
}
}
//SDA_Output_Mode(); //原本的位置
SCL_Output(0);
delay1(DELAY_TIME);
SDA_Output_Mode(); //修改后的位置
return SUCCESS;
}
配置完成之后,MX也别急着关,为了方便后面修改、添加配置
头文件
#include "main.h"
#include "adc.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#include "lcd.h"
#include "i2c - hal.h"
初始化
切记!!!:无论什么题目,都不要一来就傻傻的写逻辑,一定要按照最小单元的思想,一个一个地测试外设确保没有问题,这样无论是对于硬件有问题的情况还是后面写逻辑时检错都有帮助。
串口测试
本题目串口非常重要,为了偷懒,串口中断回调我一般按如下方法寻找:
uint8_t RxBuffer = 0;
uint8_t TxBuffer = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
if(huart == &huart1){
HAL_UART_Receive_IT(&huart1,&RxBuffer,1);
TxBuffer = RxBuffer;
HAL_UART_Transmit(&huart1,&TxBuffer,1,10);
}
}
/*放在main中的初始化后面*/
HAL_UART_Receive_IT(&huart1,&RxBuffer,1);
HAL_UART_Transmit(&huart1,"成功\r\n",6,10);
现象:成功是为了确保可以Transmit,回显可以确保Receive
注意:把烧录配置一下
LCD测试
由于LCD本身不存在逻辑,所以可以在测试的时候,直接按照规定的显示界面来设置测试。(当然在什么时候显示什么需要逻辑,不过这里只需要确定在什么位置显示什么即可)
uint8_t LCD_string[20] = {0};
void UI_Template(){
memset(LCD_string,0,sizeof(LCD_string));
LCD_SetTextColor(White);//设置文字颜色
LCD_SetBackColor(Black);//设置背景颜色
sprintf((char *)LCD_string," V1:%.2fV",1.84);
LCD_DisplayStringLine(Line2,LCD_string);memset(LCD_string,0,sizeof(LCD_string));
sprintf((char *)LCD_string," k:%.1f",0.1);
LCD_DisplayStringLine(Line4,LCD_string);memset(LCD_string,0,sizeof(LCD_string));
sprintf((char *)LCD_string," LED:%s","OFF");
LCD_DisplayStringLine(Line6,LCD_string);memset(LCD_string,0,sizeof(LCD_string));
sprintf((char *)LCD_string," T:%d-%d-%d",23,59,55);
LCD_DisplayStringLine(Line8,LCD_string);memset(LCD_string,0,sizeof(LCD_string));
}
/*放在初始化后面*/
LCD_Init();
LCD_Clear(Black);
UI_Template();
注意:为了确保显示正确,每次sprintf前我都会用memset清空一次字符串
RTC测试
虽然本题只使用到了时间,但是一定要把日期也初始化并且同时获取数据才行。
顺势也可以把定时的时间也给初始化了,再结合前面已经测试成功的显示屏/串口,就可以测试RTC是否在正常工作,这里就不贴结果了。
uint8_t ring_HH = 0;
uint8_t ring_MM = 0;
uint8_t ring_SS = 0;
//RTC结构体变量
RTC_TimeTypeDef T; //时间结构体
RTC_DateTypeDef D; //日期结构体
/*while循环中*/
while(1){
HAL_RTC_GetTime(&hrtc, &T, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &D, RTC_FORMAT_BIN);
if(ring_MM == T.Minutes)/*分钟匹配上了*/
{
sprintf((char *)LCD_string, "%02d-%02d-%02d\r\n",T.Hours,T.Minutes,T.Seconds);
HAL_UART_Transmit(&huart1, LCD_string, sizeof(LCD_string), 50);
}
HAL_Delay(200);
}
ADC测试
因为ADC使用简单,直接上模板代码,这个值可以直接放进上面LCD测试代码的函数中显示方便测试,而且也是题目需要,一举两得。
float adc_Value = 0;
float getADC(){
u16 temp_adc = 0;
HAL_ADC_Start(&hadc2);
temp_adc = HAL_ADC_GetValue(&hadc2);
return temp_adc*3.3/4096;
}
while(1){
adc_Value = getADC();
HAL_Delay(200);
UI_Template();
}
按键和LED测试
这两个外设更多依靠的是模板代码
LED的话按照模板,直接把闪烁的方式写上
但是遇到了一个坑:LED可以操作,但是会进入了HAL_Delay()死循环里面,没有找到什么解决方法,干脆直接使用定时器,用起来更加方便
/*初始化时候的全灭处理*/
GPIOC->ODR|=(0xFF)<<8;
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,0);
/*led1闪烁*/
void LED_kirakira(u8 led){
GPIOC->ODR|=(~led) << 8;
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,0);
}
while(1){
led = (led==0) ? 1 : 0;
LED_kirakira(led);
HAL_Delay(500);
}
按键的测试略过(其实是想偷懒直接用模板)
E2PROM测试
直接按照模板来写,没背下来就直接Game Over。
这里写个坑:无论是LCD还是E2PROM,使用的时候一开始都忘了使用LCD_init还有I2C_Init()进行初始阿虎。
逻辑代码编写
其实通过上面的测试,整个题目已经完成了70%,我们只需要把上面的函数一个个组装起来即可。当然在这个组装的过程中也是非常建议装一个测一个,不要全部写完来再测,但是下文为了不赘叙,就直接整个讲解完。
- ADC得到的电压根据标准闪烁
if(flag_200ms && (adc_Value > (VDD * para.k)) && led_switch)
- 200ms间隔 && 是否大于 && 按键是否允许
- E2PROM存储k的默认值
- 使用union共用体来解决浮点数的存储问题
- 可以参考
- 串口接收解码和编码发送
- 这个部分是我调试最久的部分
- 因为是早年的题目,题目的要求明显要比近几年的要模糊,比如显示界面的行数和列数,这里也没指定是否换行以及
\n
是实体显示还是转义字符,我在这里考虑以实体显示处理 HAL_UART_Transmit(&huart1, TxBuffer, sizeof(TxBuffer), 25);
这里25ms不能再少了,再少了就无法全部发送完成- 如果要发送
\
,程序里面需要写成\\
- 至于解码部分,直接看代码
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
if(huart == &huart1){
if(RxBuffer[0] == 'k')
if(RxBuffer[1] == '0')
if(RxBuffer[2] == '.')
if(RxBuffer[3] >= '1' && RxBuffer[3] <= '9')
if(RxBuffer[4] == 0x5C)
if(RxBuffer[5] == 0x6e){para.k = (float)(RxBuffer[3] - '0') / 10;k_change = 1;}
HAL_UART_Receive_IT(&huart1,RxBuffer,6);
}
}
- 按键完成各种功能的开关、转换等
- 比较简单,就省略了
用户代码
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "rtc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#include "lcd.h"
#include "i2c - hal.h"
/* USER CODE BEGIN PV */
#define VDD 3.3
/*****************按键部分*********************/
#define K1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define K2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define K3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define K4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
#define keymode(n,k1) switch(n){\
case 1:key[0] = k1;break;\
case 2:key[1] = k1;break;\
case 4:key[2] = k1;break;\
case 8:key[3] = k1;break;\
}
u8 key[8] = {0};
/*****************定时器部分*********************/
int count = 0;
uint8_t flag_20ms = 0;//按键
uint8_t flag_50ms = 0;//adc采集
uint8_t flag_200ms = 0;//LED闪烁
uint8_t flag_500ms = 0;//RTC的数据更新
/*****************显示部分*********************/
uint8_t LCD_string[20] = {0};
uint8_t UI_page = 0;
/*****************串口部分*********************/
uint8_t RxBuffer[6] = {0};
uint8_t TxBuffer[20] = {0};
/*****************时间部分*********************/
//RTC结构体变量
RTC_TimeTypeDef T; //时间结构体
RTC_DateTypeDef D; //日期结构体
uint8_t ring_HH = 0;
uint8_t ring_MM = 0;
uint8_t ring_SS = 0;
uint8_t ring_time_change = 1;
uint8_t limit_1s = 1;//限制电压上报1s内只能够发送一次
/*************电压值和K值部分******************/
float adc_Value = 0;
union parameter{
float k;
uint8_t k_c[4];
}para;
uint8_t k_change = 0;
/******************LED部分********************/
uint8_t led = 0;
uint8_t led_switch = 1;
/* USER CODE END PV */
void SystemClock_Config(void);
/*************************************************************
* @Function: key_scan
* @Description:按键扫描函数,支持短按单击,短按双击,长按
* @Parameter:
* @Return:void
* @Example:
**************************************************************/
void key_scan(){
static u8 lastkey = 0x0F;
static u8 flag = 0;
static u8 real_key = 0;
static u8 clear = 0;
static u8 clear2 = 0;
u8 tempkey = 0;
tempkey = K1 | (K2 << 1) | (K3 << 2) | (K4 << 3);//常态为0x0F
if(flag){//说明上次改变了,判别是否改变有效
if(lastkey == tempkey) flag = 2;//即维持在改变后状态,改变有效
else flag = 0;//改变无效
}
if(flag == 2){//当改变有效后正式开始分析判断按键
if(tempkey == 0x0F) {key[6] = 1;key[4] = 0;}//是松开有效
else {key[6] = 0;key[4] = 1;real_key = (~tempkey) & 0x0F;}//是按下有效 + 记录有效的值
flag = 0;
}
if(key[4] == 1){//为按下状态
key[5] += 1; //每过20ms+1
}
if(key[6] == 1)
{//为松开状态
if(clear || clear2){ //长按 或者 双击 的 松开是不需要进行判断的,在这里直接清零
key[6] = 0;clear = 0;clear2 = 0;
}
else key[7] += 1;//每过20ms+1
}
if(key[5] >= 25){//按下持续了500ms
keymode(real_key,3);//触发长按
for(int i = 4; i<8 ;i++) key[i] = 0;
clear = 1;
}
if(key[6] == 1 && key[7] >= 15){//松开300ms,没有第二次,为单击短按
keymode(real_key,1);//触发单击
for(int i = 4; i<8 ;i++) key[i] = 0;
}
if(key[4] == 1 && 0 < key[7] && key[7] < 15){//双击短按
keymode(real_key,2);//触发双击
for(int i = 4; i<8 ;i++) key[i] = 0;
clear2 = 1;
}
if(tempkey != lastkey){//说明改变了
flag = 1;//防抖标志位开启
}
lastkey = tempkey;//跟随按键用于判断是否改变
}
/*************************************************************
* @Function: LED_kirakira
* @Description:LED控制
* @Parameter:
* @led: 0xFF为全亮,0x00为全灭,8个led对应8个bit
* @Return:void
* @Example:
**************************************************************/
void LED_kirakira(uint8_t led){
GPIOC->ODR |= (~led) << 8;
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,0);
}
/*************************************************************
* @Function: getADC
* @Description:获取adc采集的电压值
* @Parameter:
* @Return:float
* @Example:
**************************************************************/
float getADC(){
u16 temp_adc = 0;
HAL_ADC_Start(&hadc2);
temp_adc = HAL_ADC_GetValue(&hadc2);
return temp_adc*3.3/4096;
}
/*************************************************************
* @Function: key_mode
* @Description: 对按键得到的数值进行模式判断
* @Parameter:
* @Return:void
* @Example:
**************************************************************/
void key_mode(){
if(key[0] == 0x01) {led_switch = led_switch^0x01;key[0]=0;}
if(key[1] == 0x01) {UI_page = UI_page ^ 0x01;key[1] = 0;}
if(UI_page){
if(key[2] == 0x01){
++ring_time_change;
key[2] = 0;
if(ring_time_change >= 4) ring_time_change = 1;
}
if(key[3] == 0x01){
key[3] = 0;
switch(ring_time_change){
case 1:++ring_HH;if(ring_HH >=24) ring_HH = 0;break;
case 2:++ring_MM;if(ring_MM >=60) ring_HH = 0;break;
case 3:++ring_SS;if(ring_SS >=60) ring_HH = 0;break;
default: break;
}
}
}
}
/*************************************************************
* @Function: UI_Template
* @Description: LCD显示
* @Parameter:
* @Return:void
* @Example:
**************************************************************/
void UI_Template(){
memset(LCD_string,0,sizeof(LCD_string));
LCD_SetTextColor(White);//设置文字颜色
LCD_SetBackColor(Black);//设置背景颜色
if(UI_page == 0){//固定界面
sprintf((char *)LCD_string," V1:%.2fV ",adc_Value);
LCD_DisplayStringLine(Line2,LCD_string);memset(LCD_string,0,sizeof(LCD_string));
sprintf((char *)LCD_string," k:%.1f ",para.k);
LCD_DisplayStringLine(Line4,LCD_string);memset(LCD_string,0,sizeof(LCD_string));
if(led_switch) sprintf((char *)LCD_string," LED:%s ","ON");
else sprintf((char *)LCD_string," LED:%s ","OFF");
LCD_DisplayStringLine(Line6,LCD_string);memset(LCD_string,0,sizeof(LCD_string));
sprintf((char *)LCD_string," T:%02d-%02d-%02d ",T.Hours,T.Minutes,T.Seconds);
LCD_DisplayStringLine(Line8,LCD_string); memset(LCD_string,0,sizeof(LCD_string));
}
else{
LCD_DisplayStringLine(Line2,(uint8_t *)" Setting ");
memset(LCD_string,0,sizeof(LCD_string));
sprintf((char *)LCD_string," %02d-%02d-%02d ",ring_HH,ring_MM,ring_SS);
LCD_DisplayStringLine(Line4,LCD_string);
memset(LCD_string,0,sizeof(LCD_string));
LCD_DisplayStringLine(Line6," ");
LCD_DisplayStringLine(Line8," ");
}
}
/*************************************************************
* @Function: E2P_Write
* @Description: 利用I2C通信向E2PROM写入数据
* @Parameter:
* @Return:void
* @Example:
**************************************************************/
void E2P_Write(u8 *string, u16 add, u16 num){
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(add);
I2CWaitAck();
while(num--){
I2CSendByte(*string++);
I2CWaitAck();
}
I2CStop();
HAL_Delay(5);
}
/*************************************************************
* @Function: E2P_Read
* @Description: 利用I2C通信从E2PROM读取数据
* @Parameter:
* @Return:void
* @Example:
**************************************************************/
void E2P_Read(u8 *string, u16 add, u16 num){
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(add);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
while(num--){
*string = I2CReceiveByte();
if(num) I2CSendAck();
else I2CSendNotAck();
}
I2CStop();
HAL_Delay(5);
}
/*************************************************************
* @Function: 串口中断回调
* @Description: 对接收到的数据进行解码
* @Parameter:
* @Return:void
* @Example:
**************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
if(huart == &huart1){
if(RxBuffer[0] == 'k')
if(RxBuffer[1] == '0')
if(RxBuffer[2] == '.')
if(RxBuffer[3] >= '1' && RxBuffer[3] <= '9')
if(RxBuffer[4] == 0x5C)
if(RxBuffer[5] == 0x6e){para.k = (float)(RxBuffer[3] - '0') / 10;k_change = 1;}
HAL_UART_Receive_IT(&huart1,RxBuffer,6);
}
}
/*************************************************************
* @Function: HAL_TIM_PeriodElapsedCallback
* @Description: 定时器中断回调,1ms一次
* @Parameter:
* @Return:void
* @Example:
**************************************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim == &htim1){
count++;
if(count%20 == 0) {flag_20ms = 1;}
if(count%50 == 0){flag_50ms = 1;}
if(count%200 == 0){flag_200ms = 1;}
if(count >= 500) {flag_500ms = 1;count = 0;}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC2_Init();
MX_RTC_Init();
MX_USART1_UART_Init();
MX_TIM1_Init();
I2CInit();
LCD_Init();
LCD_Clear(Black);
HAL_TIM_Base_Start_IT(&htim1);//定时器初始化
HAL_UART_Receive_IT(&huart1,RxBuffer,6);//串口初始化
for(int i=0;i<sizeof(float);i++) E2P_Read(¶.k_c[i],0x10+i,1);//读取K的值
while (1)
{
if(flag_20ms) {flag_20ms = 0;key_scan();key_mode();}//每20ms检测一次
if(flag_50ms) {flag_50ms = 0;adc_Value = getADC();UI_Template();}//每50ms采集一次并刷新屏幕一次
if(flag_200ms && (adc_Value > (VDD * para.k)) && led_switch) {flag_200ms = 0;led = (led==0) ? 1 : 0;LED_kirakira(led);}//间隔200ms闪烁
if((adc_Value <= (VDD * para.k)) || !led_switch)
{
led = 1;//熄灭
LED_kirakira(led);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,0);
}
if(flag_500ms){//每500ms获得当前时间,判断是否需要上报
HAL_RTC_GetTime(&hrtc, &T, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &D, RTC_FORMAT_BIN);
if(ring_SS == T.Seconds && ring_MM == T.Minutes && ring_HH == T.Hours && limit_1s )/*分钟匹配上了*/
{
limit_1s = 0;
memset(TxBuffer,0,sizeof(TxBuffer));
sprintf((char *)TxBuffer, "%.2f+%.1f+%02d%02d%02d\\n\r\n",adc_Value,para.k,T.Hours,T.Minutes,T.Seconds);
HAL_UART_Transmit(&huart1, TxBuffer, sizeof(TxBuffer), 25);
}
else limit_1s = 1;
flag_500ms = 0;
}
if(k_change) {//k值改变了就存储
k_change = 0;
HAL_UART_Transmit(&huart1,(uint8_t *)"ok\\n\r\n",6,15);
for(int i=0;i<sizeof(float);i++) E2P_Write(¶.k_c[i],0x10+i,1);
}
}
}