蓝桥杯- 第14届模拟题第一套
首先,我们来看一下题目的要求
都是一些常规的考题,考察LCD,ADC,PWM,KEY,UART和LED这几个。
从最简单的看起,我们先从按键和LED部分开始
打开Cubemx并选择芯片型号后我们正式开始。
选择Serial Wire
选择时钟源并配置时钟
根据开发板的配置要求选择对应的引脚并设置别名(定义的宏定义在main.h中)
其中LED部分设置为推挽输出模式,Key部分设置为浮空输入模式
设置完以后生成代码,我们先完成这部分外设的测试(遵循简单开发原则)
生成后我们会在main.h中找到我们生成的gpio配置(这部分配置了引脚的初始化过程)
void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOC, LED6_Pin|LED7_Pin|LED8_Pin|LED1_Pin |LED2_Pin|LED3_Pin|LED4_Pin|LED5_Pin, GPIO_PIN_SET); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_RESET); /*Configure GPIO pins : PCPin PCPin PCPin PCPin PCPin PCPin PCPin PCPin */ GPIO_InitStruct.Pin = LED6_Pin|LED7_Pin|LED8_Pin|LED1_Pin |LED2_Pin|LED3_Pin|LED4_Pin|LED5_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); /*Configure GPIO pin : PtPin */ GPIO_InitStruct.Pin = KEY4_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(KEY4_GPIO_Port, &GPIO_InitStruct); /*Configure GPIO pins : PBPin PBPin PBPin */ GPIO_InitStruct.Pin = KEY1_Pin|KEY2_Pin|KEY3_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /*Configure GPIO pin : PtPin */ GPIO_InitStruct.Pin = LED_CMD_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(LED_CMD_GPIO_Port, &GPIO_InitStruct);
//唯一需要注意的点,由于引入了锁存器的存在,所以需要在这里加上这两串来使配置后的引脚电平生效。 HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_RESET); }
接下来我们来写LED的点亮程序和LED闪烁程序。代码很简单不做过多废话
。void My_Led_On(uint8_t LED,uint8_t Mode) { if(LED == 1) { if(Mode) HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET); else HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET); } else if(LED == 2) { if(Mode) HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET); else HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET); } else if(LED == 3) { if(Mode) HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET); else HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET); } }
唯一需要注意的点是闪烁程序中对LED的控制由于锁存器的存在需要两次引用这段锁存代码来使得闪烁效果出现
void My_Led_Flash() { HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET); HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_RESET); HAL_Delay(100); HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET); HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_RESET); }
/*********************************************************************************/
接下来是按键的获取函数(使用了两个函数,一个采集值,一个用来确定是哪个值按下了)
uint8_t _Key_Get() { uint8_t Temp; if( HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == 0 ) Temp = KEY1; else if( HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == 0 ) Temp = KEY2; else if( HAL_GPIO_ReadPin(KEY3_GPIO_Port,KEY3_Pin) == 0 ) Temp = KEY3; else if( HAL_GPIO_ReadPin(KEY4_GPIO_Port,KEY4_Pin) == 0 ) Temp = KEY4; return Temp; }
uint32_t Key_Tick; //延时采用变量 uint8_t Old_Key,New_Key; //按键值临时变量 uint8_t Key_Down; //判断按键是否按下 uint8_t My_Key_Get() {
//按键非阻塞延时 if(uwTick - Key_Tick <100) return 0; else Key_Tick = uwTick; //这部分代码可以去其它博客了解。 New_Key = _Key_Get(); Key_Down = New_Key & (New_Key ^ Old_Key); Old_Key = New_Key ; return Key_Down; }
至此,按键和LED部分就写完了,但别忘了测试!
void Test() { uint8_t Key_Value; Key_Value = My_Key_Get(); if(Key_Value == KEY1) My_Led_On(1,1); }
//测试这部分没有问题以后再进行其它方面的设置,不要等到后面再来找DEBUG,不然会很痛苦的。
/*************************************************************************************************/
下一步我们来配置ADC
由于蓝桥杯开发板的ADC输出要求在PB15,即ADC2的通道15。我们对该通道进行配置
独立模式异步时钟2分频,其它默认即可。
当然,注意引脚IO口是否正确
配置完以后生成代码,会直接生成adc.c/h,我们在里面写一个函数来获取ADC值即可
uint16_t ADC_Get() { //启动ADC HAL_ADC_Start(&hadc2); //通过库函数获取对应通道的ADC值(0-4096) //由于题目要求范围在(0,3.3)之间,我这里直接转换了 ADC_3_3 = (HAL_ADC_GetValue(&hadc2)*3.3)/4096; return ADC_3_3; }
之后调用该函数就得到ADC值可以利用了。
/**************************************************************/
再配置串口UART
按照题目要求配置串口参数(一般只会要求波特率)
注意观察GPIO口是否已经使能,我在这里摔倒好几次了.....................
打开中断,配置中断优先级
之后我们生成usart.c/h文件再进行下一步的操作。
串口的处理重点在于串口回调函数的处理,我在其它博客中有对回调函数如何找做过说明。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
//判断是哪个串口 if(huart->Instance==USART1) {
//逐步采集需要的Rx_Temp值 Rx_Temp[Rx_Count] = Rx_Temp_Char; //识别到换行符或者其它结束条件则修改接收标志变量 if(Rx_Temp_Char == '\n') Rx_Flag = 1; else Rx_Count ++; //重新开启串口接收中断 HAL_UART_Receive_IT(&huart1,&Rx_Temp_Char,1); } }
对于串口,我们再重定义printf函数来方便我们的输出
int fputc(int c , FILE * stream) { HAL_UART_Transmit(&huart1,(uint8_t *)&c,1,10); return c; }
main函数中我们执行串口初始化和开启接收中断并对输入输出做出测试
至此,串口配置完毕
/*********************************************************************************************/
Tim PWM的输出
选择要输出的端口,这里我选择PA7(Tim17_CH1)
设置预分频值和自动重装值从而确定输出PWM波的频率
选择PWM模式1并设置比较寄存器的值(占空比配置)
配置完生成tim.c/h代码
不需要写任何函数,我们直接调用就可以输出PWM波了
MX_TIM17_Init(); //TIM初始化
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1); //启动PWM输出
这就是Cubemx的方便之处啊,节约了大量的时间不用去配置这些多余的代码。
至此所有外设都已经配置完毕了,下一步就是利用这些外设去逐步完成功能。(LCD部分不再累述)
功能实现
LCD部分
了解功能之后需要我们显示两个界面,数据界面和参数界面。我们写两个显示界面函数来分别显示这两个界面
我们通过形参将两个界面所要显示的参数都传输进来,并通过LCD去进行显示
void My_DATA_Show(float ADC_Value,uint32_t Freq) { //要求显示采集到ADC值和输出的信号频率 LCD_Clear(Black); sprintf(LCD_Show," DATA"); LCD_DisplayStringLine(Line2,LCD_Show); sprintf(LCD_Show," VR37:%1.2fV",ADC_Value); LCD_DisplayStringLine(Line3,LCD_Show); sprintf(LCD_Show," PA7:%dz",Freq); LCD_DisplayStringLine(Line4,LCD_Show); }
void My_PARA_Show(float VP_Value) { //要求显示当前参数大小 LCD_Clear(Black); sprintf(LCD_Show," PARA"); LCD_DisplayStringLine(Line3,LCD_Show); sprintf(LCD_Show," VP1:%1.1fV",VP_Value); LCD_DisplayStringLine(Line4,LCD_Show); }
uint8_t LCD_Mode;
uint8_t LCD_Show[255]={0};
void My_LCD_Show (float ADC_Value,uint32_t Freq,float VP_Value) {
//通过更改LCD_Mode来决定显示哪一个界面 if(LCD_Mode == DATA) My_DATA_Show(ADC_Value,Freq); else My_PARA_Show(VP_Value); }
之后是LED界面的配置,同样是写一个函数来完成相关的要求
void LED_Control(uint8_t LCD_Mode,float ADC_3_3,float VP_Value) { HAL_GPIO_WritePin(GPIOC, LED3_Pin|LED6_Pin|LED7_Pin|LED8_Pin|LED4_Pin|LED5_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_RESET); if(LCD_Mode == DATA) //处于数据界面 My_Led_On(LED1,1); else My_Led_On(LED1,0); if(LCD_Mode == PARA) //处于参数界面 My_Led_On(LED2,1); else My_Led_On(LED2,0); if( ADC_3_3 > VP_Value) //闪烁要求 My_Led_Flash(); else My_Led_On(LED3,0); HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_CMD_GPIO_Port, LED_CMD_Pin, GPIO_PIN_RESET); }
/****************************************************************************************************/
定时器部分 -由于需要对信号的频率进行更改,还需要两个函数来对PWM输出信号的频率和占空比进行调节
uint8_t Duty = 10; uint32_t Freq = 1000; //声明为外部变量 void My_Freq_Control() { //Freq在main函数中被更改,超过10000归1000 if(Freq > 10000) { __HAL_TIM_SET_COUNTER(&htim17, 1000); Freq = 1000; } else __HAL_TIM_SET_COUNTER(&htim17, 1000000/Freq ); } //根据串口发来的占空比控制占空比,串口会发送1-9随机一个字符来控制占空比大小10%-90% void My_Duty_Control(char Duty_Char) { Duty = (Duty_Char - '0')*10; printf("Duty:%d\r\n",Duty); __HAL_TIM_SET_COMPARE(&htim17, TIM_CHANNEL_1, (Freq *Duty/100)); printf("Compare:%d\r\n",(Freq *Duty/100) ); }
/***************************************************************************************************/
串口部分 -对串口采集到的数据进行处理
char Rx_Char =0; uint8_t UART_Control() { if(Rx_Flag ==1) //读取到串口数据 { Rx_Flag = 0; //printf("Rx_Temp:%c\r\n",Rx_Temp[0]); if( Rx_Temp[0]>'9' || Rx_Temp[0]<'1' ) { printf("error\r\n");memset(Rx_Temp,0,255); Rx_Count = 0; return 0xff;} //接收错误返回0xff else { Rx_Char = Rx_Temp[0]; printf("recv:%s\r\n",Rx_Temp); memset(Rx_Temp,0,255); Rx_Count = 0; return 1; //成功接收返回1 } } else //没有接收到数据 return 0xfe; }
/**********************************************************************************************************/
最后是main函数部分 -调用所有初始化函数和执行函数
#include "main.h"
#include "adc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "bsp/lcd_proj.h"
#include "bsp/tim_proj.h"
#include "bsp/uart_proj.h"
#include "bsp/led_proj.h"
int main(void) { HAL_Init(); SystemClock_Config(); MX_ADC2_Init(); MX_GPIO_Init(); //按键,LED初始化 MX_TIM17_Init(); My_LCD_Init(); MX_USART1_UART_Init(); HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1); //开启PWM输出 HAL_UART_Receive_IT(&huart1,&Rx_Temp_Char,1); //开启串口接收中断 //printf("usart OK \r\n"); //串口测试 uint8_t Key_Value=0; while (1) { //按键采集并对采集到的结果执行相对应的操作 Key_Value = My_Key_Get(); if(Key_Value == KEY1) LCD_Mode^=1; //改变显示界面 else if(Key_Value == KEY2) { if(LCD_Mode == PARA) VP_Value +=0.3f; if( VP_Value >=3.3f ) VP_Value =0; } else if(Key_Value == KEY3) { if(LCD_Mode == DATA) Freq+=1000; } My_Freq_Control(); if( UART_Control() == 1) //当识别到串口发送来的正确数据后 My_Duty_Control(Rx_Char); //控制占空比的大小 ADC_Get(); //采集ADC My_LCD_Show(ADC_3_3, Freq,VP_Value); //LCD显示 LED_Control(LCD_Mode,ADC_3_3,VP_Value); //LED控制 } /* USER CODE END 3 */ }
main.h中一些相关变量
#define LED6_Pin GPIO_PIN_13 #define LED6_GPIO_Port GPIOC #define LED7_Pin GPIO_PIN_14 #define LED7_GPIO_Port GPIOC #define LED8_Pin GPIO_PIN_15 #define LED8_GPIO_Port GPIOC #define KEY4_Pin GPIO_PIN_0 #define KEY4_GPIO_Port GPIOA #define KEY1_Pin GPIO_PIN_0 #define KEY1_GPIO_Port GPIOB #define KEY2_Pin GPIO_PIN_1 #define KEY2_GPIO_Port GPIOB #define KEY3_Pin GPIO_PIN_2 #define KEY3_GPIO_Port GPIOB #define LED1_Pin GPIO_PIN_8 #define LED1_GPIO_Port GPIOC #define LED2_Pin GPIO_PIN_9 #define LED2_GPIO_Port GPIOC #define LED3_Pin GPIO_PIN_10 #define LED3_GPIO_Port GPIOC #define LED4_Pin GPIO_PIN_11 #define LED4_GPIO_Port GPIOC #define LED5_Pin GPIO_PIN_12 #define LED5_GPIO_Port GPIOC #define LED_CMD_Pin GPIO_PIN_2 #define LED_CMD_GPIO_Port GPIOD enum {DATA = 1,PARA = 0}; enum { LED1=1,LED2=2,LED3=3}; enum {KEY1 = 1,KEY2=2,KEY3=3,KEY4=4};
总结
模拟题相比真题要显得简单很多,但也能学到些东西..........
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了