文章目錄
- 原題展示
- 原題分析
- 原題題解
- LED相關
- LCD相關
- 按鍵相關
- ADC相關
- 定時器相關
- PWM
- 輸入捕獲
- 小結
- 文章福利
原題展示
原題分析
今年的第一場比賽絕對np,官方将串口直接省掉了,将其替換成很多小功能,如:切換計時、頻率均勻變化、鎖機制等等,總的來說本屆賽題的難度提升了不少。
本屆試題需要用到的功能模塊有LCD、LED、按鍵、定時器輸入捕獲、定時器PWM輸出、ADC獲取,雖然這屆試題模塊簡單,但是功能實現一點也不簡單,感覺跟十二屆省賽一樣😂😂😂。
還值得注意的是:本屆試題有三個地方需要計時,即模式切換、LED閃爍與長按鍵,,這可能是藍橋杯爲了提升難度的一個方向。(小編感覺這計時真的是惡心🤣🤣🤣)
原題題解
LED相關
通過查詢産品手冊知,LED的引腳爲PC8~PC15,外加鎖存器74HC573需要用到的引腳PD2。(由于題目要求除題目要求需要使用的LED外其他LED都處于熄滅狀态,此處特意将所有的LED都初始化以便于管理其他的LED燈)
CubeMX配置:
代碼樣例
由于G431的所有LED都跟鎖存器74HC573連接,因此每次更改LED狀态時都需要先打開鎖存器,寫入數據後再關閉鎖存器。
/***************************************************** * 函數功能:改變所有LED的狀态 * 函數參數: * char LEDSTATE: 0-表示關閉 1-表示打開 * 函數返回值:無 ******************************************************/ 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)); //打開鎖存器 準備寫入數據 HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); //關閉鎖存器 鎖存器的作用爲 使得鎖存器輸出端的電平一直維持在一個固定的狀态 HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); } /***************************************************** * 函數功能:根據LED的位置打開或者是關閉LED * 函數參數: * uint16_t LEDLOCATION:需要操作LED的位置 * char LEDSTATE: 0-表示關閉 1-表示打開 * 函數返回值:無 ******************************************************/ 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); }
試題要求的LED顯示其條件都比較單一,在滿足點亮條件時直接點亮,否則,就直接熄滅即可,至于閃爍的周期控制,可以借助與sysTick中斷實現,如果就此再開一個定時器就有點浪費資源了。(雖然小編以前經常這樣子幹🤣🤣🤣)
小編寫的LED工作邏輯函數:
/*************************************** * 函數功能:LED顯示邏輯函數 * 函數參數:無 * 函數返回值:無 ***************************************/ static void ledWork(void) { // 數據界面LED1電量 if(mod == 0) changeLedStateByLocation(LED1,1); else changeLedStateByLocation(LED1,0); // 切換期間 LED2閃爍 if(LED2Flag && sysCount[1] >= 100) { rollbackLedByLocation(LED2); sysCount[1] = 0; } else if(!LED2Flag) changeLedStateByLocation(LED2,0); // 鎖定模式下 LED3電量 if(lock) changeLedStateByLocation(LED3,1); else changeLedStateByLocation(LED3,0); }
LCD相關
樣例代碼
由于LCD的相關代碼在官方給的比賽資源數據包中存在,因此,可以直接調用資源包中的.c、.h文件來完成LCD的相關初始化以及顯示。這是一個簡單的LCD初始化函數,其功能是将LCD顯示屏初始化爲一個背景色爲黑色、字體顔色爲白色的屏幕,具體代碼如下:
/****************************************************************************** * 函數功能:LCD初始化 * 函數參數:無 * 函數返回值:無 *******************************************************************************/ void lcdInit(void) { //HAL庫的初始化 LCD_Init(); //設置LCD的背景色 LCD_Clear(Black); //設置LCD字體顔色 LCD_SetTextColor(White); //設置LCD字體的背景色 LCD_SetBackColor(Black); }
在顯示時,可以借助于sprintf()函數将需要顯示的數據格式成一個字符串,再在LCD上顯示這個字符串。
char temp[20]; sprintf(temp," M=%c ",mTable[M]); LCD_DisplayStringLine(Line3,(u8*)temp);
爲了操作LED與LCD顯示方便,不讓其相互幹擾,小編這裏對LCD進行了部分源碼改寫,使得每次LCD顯示時不改變LED的顯示狀态,具體的方法各位可以點擊查看【藍橋杯】一文解決藍橋杯嵌入式開發闆(STM32G431RBT6)LCD與LED顯示沖突問題,并講述LCD翻轉顯示。
下面附上小編完成的LCD部分的詳細代碼:
/*************************************** * 函數功能:LCD顯示數據 * 函數參數:無 * 函數返回值:無 ***************************************/ static void lcdDisplay() { char temp[20]; extern uint32_t cclValue ; // 數據顯示界面 if(mod == 0) { LCD_DisplayStringLine(Line1,(u8*)" DATA "); sprintf(temp," M=%c ",mTable[M]); LCD_DisplayStringLine(Line3,(u8*)temp); sprintf(temp," P=%d%% ",pa1Zhan[1]); LCD_DisplayStringLine(Line4,(u8*)temp); sprintf(temp," V=%.1f ",V); LCD_DisplayStringLine(Line5,(u8*)temp); } // 參數顯示界面 else if(mod == 1) { LCD_DisplayStringLine(Line1,(u8*)" PARA "); sprintf(temp," R=%d ",RK[0]); LCD_DisplayStringLine(Line3,(u8*)temp); sprintf(temp," K=%d ",RK[1]); LCD_DisplayStringLine(Line4,(u8*)temp); LCD_ClearLine(Line5); } // 統計界面 else if(mod == 2) { LCD_DisplayStringLine(Line1,(u8*)" RECD "); sprintf(temp," N=%d ",N); LCD_DisplayStringLine(Line3,(u8*)temp); sprintf(temp," MH=%.1f ",MH); LCD_DisplayStringLine(Line4,(u8*)temp); sprintf(temp," ML=%.1f ",ML); LCD_DisplayStringLine(Line5,(u8*)temp); } }
(是不是非常簡單粗暴。哈哈哈哈)
按鍵相關
通過查詢産品手冊知,開發闆上的四個按鍵引腳爲PB0~PB2、PA0。
CubeMX配置
代碼樣例
由于題中涉及到長按鍵,因此此處将不再使用延時消抖,可以使用三行按鍵完成長按鍵與短按鍵設計,其核心代碼就是三行邏輯運算完成消抖等一系列操作,但是在輪詢系統中可能會存在漏檢的問題。其完整代碼如下:
// 聲明獲取按鍵的狀态值 #define getKeysState() ( HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) // 獲取按鍵狀态 uint16_t state = getKeysState(); // 對按鍵的值進行異或處理 一次判斷 uint8_t key_temp = 0xFF ^ (0xF0 | state); /*** 通過邏輯運算處理消抖 **/ // 按下狀态 keyFalling = key_temp & (key_temp ^ keyOldState); // 松開狀态 keyRising = ~key_temp & (key_temp ^ keyOldState); // 保存本次按鍵的值 keyOldState = key_temp; } signed char i = 0; // 按鍵掃描 keyRefresh(); // 按鍵按下 并且按鍵的值等于8也就是B4 if(keyFalling == 8) uwKeyTick = HAL_GetTick(); switch(keyRising) { // 按鍵B1 case 1: mod++; // 每次進去參數界面默認參數爲R if(mod == 1) rkCount = 0; // 退出參數界面 if(mod != 1 ) { // 遍曆參數 并且刷新 for(i=0;i sysCount[0] = 0; LED2Flag = 1; } // 參數界面 if(mod == 1) rkCount ^= 1; break; // 按鍵B3 case 4: // 參數界面 加1 if(mod == 1) if(++RKOld[rkCount] == 11) RKOld[rkCount] = 1; break; // 按鍵B4 case 8: // 數據界面 if(mod == 0 ) // 長按鍵鎖住 if(HAL_GetTick() - uwKeyTick 2000) lock = 1; // 短按鍵解鎖 else lock = 0; // 參數界面 減1 else if(mod == 1) if(--RKOld[rkCount] == 0) RKOld[rkCount] = 10; break; // 其他 default: break; } // 延時5秒切換 if(LED2Flag&& sysCount[0]=5000) { M ^= 1; // 切換次數增加 N++; LED2Flag = 0; } } unsigned int value = 0,i = 0; //開啓轉換ADC并且獲取值 HAL_ADC_Start(hadc); for(i=0;i HAL_ADC_PollForConversion(hadc,10); value += HAL_ADC_GetValue(hadc); } //ADC值的轉換 3.3V是電壓 4096是ADC的精度爲12位也就是2^12=4096 return value/10*3.3/4096; } if(0 __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,pa1F[M]*pa1Zhan[1]/100); pa1Zhan[0] = pa1Zhan[1]; } } // 保存TIMx_CCR的值 uint32_t cclValue = 0; // 定時器3時執行該段 if(htim-Instance == TIM3) { cclValue = __HAL_TIM_GET_COUNTER(&htim3); __HAL_TIM_SetCounter(&htim3, 0); f = 1000000 / cclValue; HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2); } } V = (f*2*3.14*RK[0]*1.0)/(100*RK[1]); fOld = f; sysCount[2] = 0; }