【蓝桥杯嵌入式】第六届省赛程序题项目代码及讲解

蓝桥杯嵌入式第六届省赛

具体题目可见:蓝桥杯嵌入式第六届省题_是大毛吖的博客-CSDN博客
全部用户代码可见:用户代码
工程项目压缩包可见:https://download.csdn.net/download/weixin_46920037/85053104

介绍

需要用到的外设较多,但是逻辑简单。

配置

  • 首先当然是打开CubeMX
  • 选芯片G431RBx,配置RCC如下

RTC配置

见下图
|500
RTC开了之后,还需要去时钟树界面配置RTC时钟,选到对应的路线,参数按照默认的750即可。
在这里插入图片描述

配置ADC

其实ADC的功能算是挺复杂的,但是蓝桥杯的考法非常狭窄,直接把ADC2_IN15通道开了就OK

配置串口:

Asynchronous + 9600 + 开启串口中断 缺一不可

|300
|400

LED和按键的配置

找到对应的IO口,LED配置GPIO输出,按键配置GPIO输入
这里有几个注意的点:

  • 一开始我看题目只需要用一个LED,就想只初始化一个,但是后来发现如果不控制其它的LED,会出现随机亮的情况,所以还是全部都初始化并且进行全灭的初始化处理
  • 然后是我忘记了初始化PD2,导致后面测试的时候灯只能够操作一次,算是个小坑

然后可以生成项目了,名称和路径随意,符合命名规则即可

E2PROM

生成完项目工程后,移植IIC底层代码即可,顺便把LCD都移植了,放到文件夹里面后记得加载.C文件到项目里面
|100

注意!!!:配置过程中需要核对配置后对应的引脚,配置串口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

注意:把烧录配置一下
|300

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%,我们只需要把上面的函数一个个组装起来即可。当然在这个组装的过程中也是非常建议装一个测一个,不要全部写完来再测,但是下文为了不赘叙,就直接整个讲解完。

  1. ADC得到的电压根据标准闪烁
    1. if(flag_200ms && (adc_Value > (VDD * para.k)) && led_switch)
    2. 200ms间隔 && 是否大于 && 按键是否允许
  2. E2PROM存储k的默认值
    1. 使用union共用体来解决浮点数的存储问题
    2. 可以参考
      1. C语言-共用体(联合体)_FILWY_M的博客-CSDN博客_c语言联合体
  3. 串口接收解码和编码发送
    1. 这个部分是我调试最久的部分
    2. 因为是早年的题目,题目的要求明显要比近几年的要模糊,比如显示界面的行数和列数,这里也没指定是否换行以及\n是实体显示还是转义字符,我在这里考虑以实体显示处理
    3. HAL_UART_Transmit(&huart1, TxBuffer, sizeof(TxBuffer), 25);这里25ms不能再少了,再少了就无法全部发送完成
    4. 如果要发送\,程序里面需要写成\\
    5. 至于解码部分,直接看代码
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);
	}
}
  1. 按键完成各种功能的开关、转换等
    1. 比较简单,就省略了

用户代码

/* 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(&para.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(&para.k_c[i],0x10+i,1);	
		}
  }
}
  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值