原題展示







原題分析
??本屆國賽試題主要包含LCD、LED、按鍵、EEPROM、串口、模擬電壓輸入、脈沖輸入輸出七大部分,其中前面三個部分是藍橋杯嵌入式的“親兒子”(必考部分),而剩下的四個部分都為“干兒子”(考頻相對較高)。
??相對于本屆省賽兩套試題:
-
??本套試題串口數(shù)據(jù)接收出現(xiàn)一個較為復雜的問題:如何接收PC端發(fā)送變長數(shù)據(jù)?省賽的兩套試題PC發(fā)送的數(shù)據(jù)類型與長度都比較單一,要不發(fā)送7個字符的新舊密碼,要不直接發(fā)送一個"?"號,而本套試題PC發(fā)送的數(shù)據(jù)長度不定,要不是1位數(shù)據(jù),要不是3位數(shù)據(jù), 那么這就涉及到串口接收不定長度數(shù)據(jù),至于到底該怎么處理,此處小編先保密。??????
-
??本套試題中還出現(xiàn)了一個較為“新鮮的舊知識點”——LCD顯示數(shù)據(jù)翻轉,問題新鮮在于可能屏幕前的你是第一次碰到這個操作,舊知識點是因為很多LCD或LED都可以進行翻轉。其實,官方提供的LCD顯示代碼中提供了反向顯示的LCD翻轉指令:
0x0100
、0xA700
,具體使用方法大家可以看下文。?????? -
??本屆試題還出現(xiàn)了一個可能困惑大家的地方:SRAM至少記錄100條數(shù)據(jù)。
??小編咋一看,還以為是需要將數(shù)據(jù)存儲在一個掉電也不會丟失的內存中呢。(小編忘記ROM與RAM的區(qū)別啦??????)百度后才知道:SRAM,一種靜態(tài)隨機存取存儲器存儲器只要保持通電,里面儲存的數(shù)據(jù)就可以恒常保持;掉電后,數(shù)據(jù)還是會消失,這與在斷電后還能儲存資料的ROM或閃存是不同的。所以,也就是說,將這100條以上的數(shù)據(jù)存儲到數(shù)據(jù)中就好啦!?????? -
??本屆試題中的模擬電壓輸入也值得關注,因為本屆賽題考察的是ADC多通道采集,而并非十一屆賽題那種單通道采集。
-
??至于脈沖輸入輸出,主要還是定時器配置與使用,只要定時器配置與使用熟練掌握并且合理運用,脈沖輸入輸出問題不大。
詳細題解
在正式題解前,大家需要注意以下幾點:
- 由于LCD與LED有部分引腳是共用的,因此初始化完成LCD后最好手動關閉LED或者保存上一次LED的引腳的值;
- 由于每次LCD顯示的長度可能不同,因此在本次顯示前,要不先清屏,要不跟上次顯示一樣長;
- 使用CubeMX配置完成串口USART1后需要更改默認引腳為PA9、PA10;
LED模塊
????通過查詢產(chǎn)品手冊知,LED的引腳為PC8~PC15,外加鎖存器74HC573需要用到的引腳PD2。(由于題目要求除LED1、LED2、LED3、LED4外的其他LED都處于熄滅狀態(tài),此處特意將所有的LED都初始化以便于管理其他的LED燈)
CubeMX配置:
代碼樣例
????由于G431的所有LED都跟鎖存器74HC573連接,因此每次更改LED狀態(tài)時都需要先打開鎖存器,寫入數(shù)據(jù)后再關閉鎖存器。
/*****************************************************
* 函數(shù)功能:改變所有LED的狀態(tài)
* 函數(shù)參數(shù):
* char LEDSTATE: 0-表示關閉 1-表示打開
* 函數(shù)返回值:無
******************************************************/
void changeAllLedByStateNumber(char LEDSTATE)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_8
|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12,(LEDSTATE==1?GPIO_PIN_RESET:GPIO_PIN_SET));
//打開鎖存器 準備寫入數(shù)據(jù)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
//關閉鎖存器 鎖存器的作用為 使得鎖存器輸出端的電平一直維持在一個固定的狀態(tài)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
/*****************************************************
* 函數(shù)功能:根據(jù)LED的位置打開或者是關閉LED
* 函數(shù)參數(shù):
* uint16_t LEDLOCATION:需要操作LED的位置
* char LEDSTATE: 0-表示關閉 1-表示打開
* 函數(shù)返回值:無
******************************************************/
void changeLedStateByLocation(uint16_t LEDLOCATION,char LEDSTATE)
{
HAL_GPIO_WritePin(GPIOC,LEDLOCATION,(LEDSTATE==1?GPIO_PIN_RESET:GPIO_PIN_SET));
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
??試題要求中除了LED3,其他的LED燈要求比較簡單,只需要點亮即可;而LED3則需要計時閃爍,每次間隔100ms就需要閃爍,因此,計時最好將其放在定時器中完成。
/**************************************************
* 函數(shù)功能:LED工作函數(shù)
* 函數(shù)參數(shù):
* int mod1:LED3的工作模式
* int mod2:LED4的工作模式
* 函數(shù)返回值:無
***************************************************/
void ledPro(int mod1,int mod2)
{
// 倍頻
if(keyB4[0]%2 == 0)
changeLedStateByLocation(LED1,1);
else
changeLedStateByLocation(LED1,0);
// 分頻
if(keyB4[0]%2 == 1)
changeLedStateByLocation(LED2,1);
else
changeLedStateByLocation(LED2,0);
// 電壓
if(mod1%2 == 1)
{
rollbackLedByLocation(LED3);
LED3TimeFlag = 0;
}
else
changeLedStateByLocation(LED3,0);
// LCD正反
if(mod2%2 == 0)
changeLedStateByLocation(LED4,1);
else
changeLedStateByLocation(LED4,0);
}
按鍵模塊
????通過查詢產(chǎn)品手冊知,開發(fā)板上的四個按鍵引腳為PB0~PB2、PA0。
CubeMX配置
代碼樣例
????本次試題涉及到按鍵單擊以及長按短按,因此,按鍵掃描時就不能單一的按鍵按下后就返回按鍵值;而是在按鍵按下后,還需要記錄按鍵按下時間,以此來判斷按鍵是長按或者是短按。
- 第一步,獲取按鍵當前狀態(tài),并且判斷按鍵是否按下,如果按鍵松開,則不做任何處理;否則,重置按鍵時間,進入下一步。(這實際上是一個消抖步驟)
- 第二步,再次獲取按鍵狀態(tài),判斷按鍵是否按下,如果沒有按下,就重置程序狀態(tài),返回步驟一;否則,就跳轉到步驟三,開始判斷按鍵長短按、單雙。;
- 第三步,記錄按鍵按下時間,按鍵松開后,開始通過按鍵的時間判斷按鍵類別,如果在時間T1內按鍵按下了兩次,那么就屬于按鍵雙擊;否則,就為按鍵單擊。如果在時間T2(T2>>T1)內,按鍵一直處于按下狀態(tài),此時就屬于按鍵長按。如果按鍵不屬于這兩大類情況,那么按鍵就無效。
/****************************************************************************************************
* 函數(shù)功能:按鍵掃描函數(shù) 注意此函數(shù)放在定時器中斷(10ms)中的使用效果最佳 否則雙擊與長按會出現(xiàn)問題
* 函數(shù)參數(shù):無
* 函數(shù)返回值:無
*****************************************************************************************************/
void scanKeyUseStructAndTime(void)
{
static struct keyState _key[4];
//獲取按鍵的最新狀態(tài)
_key[0].keyState = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
_key[1].keyState = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
_key[2].keyState = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
_key[3].keyState = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
//處理按鍵的最新狀態(tài)
for(int i=0;i<4;i++){
switch (_key[i].judgeSate){
//按鍵第一次按下
case 0:
if(_key[i].keyState == 0){
//跳轉按鍵的狀態(tài)
_key[i].judgeSate=1;
//清空按鍵時間
_key[i].keyTime=0;
}
break;
//按鍵第二次按下 兩次相隔10ms可以起到消抖作用
case 1:
//按鍵再次按下 跳轉按鍵狀態(tài)
if(_key[i].keyState == 0)
_key[i].judgeSate=2;
//上一次按鍵按下是抖動按下 屬于無效狀態(tài) 應該退回最開始的狀態(tài)
else
_key[i].judgeSate=0;
break;
//確定按鍵按下后的處理過程
case 2:
//等待松開過程,且非長按鍵
if((_key[i].keyState==1) && _key[i].keyTime<30){
//可能雙擊按鍵的第一次,進入計時
if(_key[i].doubleClickTimerFlag == 0) {
_key[i].doubleClickTimerFlag = 1;
_key[i].doubleClickTime = 0;
}
//在計時范圍內又按了一次
else{
key[i].doubleFlag=1;//雙擊情況
_key[i].doubleClickTimerFlag = 0;
}
_key[i].judgeSate = 0;
}
//松開且是長按鍵
else if(_key[i].keyState==1 && _key[i].keyTime>=30)
{
_key[i].judgeSate = 0;
key[i].longFlag = 1;
}
//按下 且為長按鍵
else
_key[i].keyTime++;
break;
}
//按鍵單次按下
if(_key[i].doubleClickTimerFlag == 1 && _key[i].doubleClickTime >= 25) {
key[i].flag = 1;
_key[i].doubleClickTimerFlag = 0;
}
//按鍵雙擊 雙擊計時
else if(_key[i].doubleClickTimerFlag == 1){
_key[i].doubleClickTime++;
}
}
}
串口
????本次試題中,串口功能比較簡單,但是接收PC發(fā)送過來的數(shù)據(jù)時需要注意:PC發(fā)送不定長的數(shù)據(jù)。這時就需要按位儲存PC發(fā)送過來的數(shù)據(jù),每次處理完成后就清空歷史數(shù)據(jù)。
CubeMX配置
????配置時一定一定記得改引腳?。。?/strong>
代碼樣例
????HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)函數(shù)解析:
- UART_HandleTypeDef *huart:串口通道;
- uint8_t *pData:存放數(shù)據(jù)的buff;
- uint16_t Size:一次接收數(shù)據(jù)的長度
????不過使用時還需要初始化,否則不能夠進入中斷接收數(shù)據(jù);
// 定義變量存儲PC發(fā)送的數(shù)據(jù)
char Rxbuff[20],_Rxbuff;
uint16_t count = 0;
/***使用HAL_UART_Receive_IT中斷接收數(shù)據(jù) 每次接收完成數(shù)據(jù)后就會執(zhí)行該函數(shù)***/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
Rxbuff[count++%20] = _Rxbuff;
// 重新使能中斷
HAL_UART_Receive_IT(huart,(uint8_t *)&_Rxbuff,sizeof(_Rxbuff));
}
}
????這里需要注意,下圖中的代碼句1與代碼句2一定不要交換,不然串口接收的數(shù)據(jù)集就不會是你想要的。??????

/***使用HAL_UART_Receive_IT中斷接收數(shù)據(jù) 每次接收完成數(shù)據(jù)后就會執(zhí)行該函數(shù)***/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
// 重新使能中斷
if(ucRxbuff[0]=='P' && _ucRxbuff[0]=='A')
ucRxbuff[1] = _ucRxbuff[0];
else if((ucRxbuff[0]=='P' && ucRxbuff[1]=='A') && (_ucRxbuff[0]=='1' || _ucRxbuff[0]=='4' || _ucRxbuff[0]=='5'))
ucRxbuff[2] = _ucRxbuff[0];
else
ucRxbuff[0] = _ucRxbuff[0];
HAL_UART_Receive_IT(huart,(uint8_t *)&_ucRxbuff,sizeof(_ucRxbuff));
}
}
????這樣,串口數(shù)據(jù)接收就可以不必每次都接收到數(shù)據(jù)可能出現(xiàn)的最大值,大大提高了串口數(shù)據(jù)接收的精確性,但是也降低了串口數(shù)據(jù)接收的靈活性。
串口數(shù)據(jù)處理
??串口數(shù)據(jù)處理,是根據(jù)PC發(fā)送過來的數(shù)據(jù)進行特殊數(shù)據(jù)匹配,一旦匹配到合適的數(shù)據(jù),就給PC發(fā)送數(shù)據(jù)或者是執(zhí)行其他的一系列操作。
/**************************************************
* 函數(shù)功能:串口數(shù)據(jù)處理函數(shù)
* 函數(shù)參數(shù):無
* 函數(shù)返回值:無
***************************************************/
void usartPro(void)
{
char usartTemp[10];
// 驗證是否收到串口信息以及串口信息長度
// 參數(shù)
if(ucRxbuff[0] == 'X')
sprintf(usartTemp,"X:%d\r\n",para[0]);
else if(ucRxbuff[0] == 'Y')
sprintf(usartTemp,"Y:%d\r\n",para[1]);
// 顯示切換
else if(ucRxbuff[0] == '#')
setDisplayMod(++LCDMod%2);
// 數(shù)據(jù)
if(ucRxbuff[0] == 'P')
{
if(strcmp((char*)ucRxbuff,"PA1") == 0)
sprintf(usartTemp,"PA1:%d\r\n",P1));
else if(strcmp((char*)ucRxbuff,"PA4") == 0)
sprintf(usartTemp,"PA4:%.2f\r\n",data[0]);
else if(strcmp((char*)ucRxbuff,"PA5") == 0)
sprintf(usartTemp,"PA5:%.2f\r\n",data[1]);
}
// 是否發(fā)送數(shù)據(jù)
if(usartTemp[0]=='P' || usartTemp[0]=='X' || usartTemp[0]=='Y')
HAL_UART_Transmit(&huart1,(uint8_t*)usartTemp,sizeof(char)*strlen(usartTemp),10);
memset(ucRxbuff,0,sizeof(ucRxbuff));
}
LCD模塊
????LCD模塊官方會提供源碼,內含初始化,大家會用即可。如下面是一段將LCD初始化成——文字顏色為白色、背景為黑色的LCD屏:
/******************************************************************************
* 函數(shù)功能:LCD初始化
* 函數(shù)參數(shù):無
* 函數(shù)返回值:無
*******************************************************************************/
void lcdInit(void)
{
//HAL庫的初始化
LCD_Init();
//設置LCD的背景色
LCD_Clear(Blue);
//設置LCD字體顏色
LCD_SetTextColor(White);
//設置LCD字體的背景色
LCD_SetBackColor(Black);
}
LCD顯示翻轉
????LCD顯示翻轉,實質就是改變LCD刷新時數(shù)據(jù)刷新方向。詳細講解大家可以參考小編的這篇文章【藍橋杯】講述藍橋杯嵌入式開發(fā)板的LCD翻轉顯示。小編在此處就直接給出代碼啦:??????
/***************************************************
* 函數(shù)功能:設置LCD顯示模式
* 函數(shù)參數(shù):
* int mod:顯示模式 0-正向顯示 1-翻轉顯示
* 函數(shù)返回值:無
***************************************************/
void setDisplayMod(int mod)
{
// 反向顯示
if(mod == 1)
{
LCD_WriteReg( R1 ,0x0100 );
LCD_WriteReg( R96,0xA700 );
LCD_Clear(Black);
}
// 反向顯示
else
{
LCD_WriteReg( R1 ,0x0000 );
LCD_WriteReg( R96,0x2700 );
LCD_Clear(Black);
}
}
????不知道大家在LCD與LED同時顯示時,有沒有碰到LED顯示紊亂,這個是因為LCD與LED共用了部分引腳,每次LCD刷新時都有可能改變這些引腳的值,因此LCD顯示會出現(xiàn)紊亂。解決這個現(xiàn)象的方案小編放在這篇文章中辣,大家可以參考玩一玩。??????【藍橋杯】解決藍橋杯嵌入式開發(fā)板LCD與LED顯示沖突問題解決這個問題的實質就是:先保存本次LED顯示的值,再刷新LCD顯示,最后恢復LED顯示。
模擬電壓讀取(ADC)
????試題中要求時測量PA4和PA5的模擬電壓,咋一看是不是都不知道要干啥???????可以先去CubeMX里看看這兩個引腳的配置項有沒有ADC的通道,如果有直接配置使用就完事啦!
CubeMX配置

代碼樣例
????通過CubeMX配置咱可以清楚的知道:本次采集需要使用ADC多通道采集,而不是多ADC單通道采集。
/*******************************************************************
* 函數(shù)功能:獲取ADC多個通道的值
* 函數(shù)參數(shù):
* ADC_HandleTypeDef *hadc:ADC
* double*data:保存ADC的值
* int n:ADC通道的個數(shù)
* 函數(shù)返回值:無
*******************************************************************/
void getManyADC(ADC_HandleTypeDef *hadc,double*data,int n)
{
int i=0;
for(i=0;i<n;i++)
{
HAL_ADC_Start(hadc);
//等待轉換完成,第二個參數(shù)表示超時時間,單位ms
HAL_ADC_PollForConversion (hadc,50);
data[i] = ((double)HAL_ADC_GetValue(hadc)/4096)*3.3;
}
HAL_ADC_Stop(hadc);
}
??調用函數(shù)getManyADC()
來獲取一個ADC兩個通道的值,并且存儲到指定的結構體中。
/**************************************************
* 函數(shù)功能:電壓讀取函數(shù)
* 函數(shù)參數(shù):無
* 函數(shù)返回值:無
***************************************************/
void elePro(void)
{
int i=0;
// 獲取ADC數(shù)據(jù)
getManyADC(&hadc2,data,2);
// 將獲取到的ADC數(shù)據(jù)儲存到數(shù)組中
while(i < 2)
{
if(data[i] > paText[i].a)
paText[i].a = data[i];
if(data[i] < paText[i].t || paText[i].n==0)
paText[i].t = data[i];
paText[i].data[paText[i].n++%100] = data[i];
paText[i].h = (paText[i].h*(paText[i].n-1)+data[i])*1.0/paText[i].n;
i++;
}
}
脈沖輸入輸出
脈沖輸入
??脈沖輸入,采用定時器的輸入捕獲功能。
CubeMX配置

代碼樣例
??脈沖輸入,是通過定時器的輸入捕獲功能捕獲一定時間內的上升沿或者是下降沿的個數(shù),以此來測量輸入的頻率。詳細講解大家可以參考小編的這篇文章當我們身邊沒有示波器就無法測量頻率與占空比了?一招教你解決身邊沒有示波器時如何測量STM32定時器產(chǎn)生PWM的頻率與占空比
//輸入捕獲回調函數(shù)
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
uint32_t temp = 0;
//發(fā)生中斷的定時器為定時器2通道2
if(htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
{
//讀取定時器的計數(shù)值
temp = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
//將定時器的計數(shù)值設置成0
__HAL_TIM_SetCounter(htim,0);
//頻率<100時 鉗制在100
if(P1 < 100)
P1 = 100;
//頻率>10k時 鉗制在10k
else if(P1 > 10000)
P1 = 10000;
//重新開啟定時器
HAL_TIM_IC_Start_IT(htim,TIM_CHANNEL_2);
}
}
脈沖輸出
??此處的脈沖輸出可以理解為PWM輸出。
CubeMX配置
代碼樣例
??由于配置完成定時器的PWM輸出后,定時器可以周期性工作,因此們可以不用顯示調用。題目中要求脈沖輸出能夠完成分頻與倍頻操作,也只需要修改定時器的重裝載值來完成,具體代碼可見下述:
/**************************************************
* 函數(shù)功能:頻率工作函數(shù)
* 函數(shù)參數(shù):
* int mod: mod=0為倍頻 mod=1為分頻
* 函數(shù)返回值:無
***************************************************/
void FrePro(int mod)
{
int frd = 0;
// 倍頻 keyB4[0]%2
if(mod == 0)
frd = (int)(P1/para[0]-1);
// 分頻
else
frd = (int)(P1*para[0]-1);
//設置頻率
__HAL_TIM_SetAutoreload(&htim17,frd);
HAL_TIM_GenerateEvent(&htim17, TIM_EVENTSOURCE_UPDATE);
}
文章福利
下邊是小編個人整理出來免費的藍橋杯嵌入式福利,有需要的童鞋可以自取喲!??????文章來源:http://www.zghlxwxcb.cn/news/detail-781508.html
- 【藍橋杯嵌入式】第十一屆藍橋杯嵌入式省賽(第二場)程序設計試題及其題解
- 【藍橋杯嵌入式】第十二屆藍橋杯嵌入式省賽程序設計試題以及詳細題解
- 【藍橋杯嵌入式】第十三屆藍橋杯嵌入式省賽程序設計試題及其詳細題解
- 【藍橋杯嵌入式】第十三屆藍橋杯嵌入式省賽(第二場)程序設計試題及其題解
- 【藍橋杯】一文解決藍橋杯嵌入式開發(fā)板(STM32G431RBT6)LCD與LED顯示沖突問題,并講述LCD翻轉顯示
也歡迎大家留言或私信交流,共同進步喲!??????文章來源地址http://www.zghlxwxcb.cn/news/detail-781508.html
到了這里,關于【藍橋杯嵌入式】第十三屆藍橋杯嵌入式國賽程序設計試題以及詳細題解的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!