15届蓝桥杯国赛嵌入式赛前准备

考纲

考前组委会临时通知,15届国赛不需要使用拓展版,所以考纲应该和省赛相同。

8a14eb0a051d1b8c69bf8e79c52774e

image-20240520195644904

image-20240520195706506

image-20240520195822933

客观题

设计题

LED操作

  • CubeMX中设置PC8~15为GPIO Output(与LCD引脚复用),PD2也为GPIO Output,不需要设置其他的

  • LED控制:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    /* USER CODE BEGIN PV */
    // led
    uint8_t ledBuffer = 0;
    uint16_t ledPinList[8] = {GPIO_PIN_8, GPIO_PIN_9, GPIO_PIN_10, GPIO_PIN_11, GPIO_PIN_12, GPIO_PIN_13, GPIO_PIN_14, GPIO_PIN_15};
    /* USER CODE END PV */


    /* USER CODE BEGIN PFP */
    void LED_Ctrl(uint8_t ctrl);
    /* USER CODE END PFP */


    /* USER CODE BEGIN 2 */
    LED_Ctrl(0);
    HAL_Delay(3000);
    for(int i = 0; i < 8; i++) // 功能:LED流水灯
    {
    ledBuffer = 0;
    ledBuffer |= (0x01 << i);
    LED_Ctrl(ledBuffer);
    HAL_Delay(500);
    }
    /* USER CODE END 2 */


    /* USER CODE BEGIN 4 */
    void LED_Ctrl(uint8_t ctrl){
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); // Unlock

    for(int i = 0; i < 8; i++){
    HAL_GPIO_WritePin(GPIOC, ledPinList[i], ((ctrl & (0x01 << i)) ? GPIO_PIN_RESET : GPIO_PIN_SET));
    }

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); // Lock
    }
    /* USER CODE END 4 */
  • LED隔500ms闪烁,持续5秒

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    // 在上述代码的基础上增加下面的内容

    // main.c
    uint8_t errorWarningFlag = 0;
    uint16_t errorWarningCount = 0;
    uint8_t blinkFlag = 0;


    void LED_Blink(void);


    ledBuffer = 0;
    LED_Ctrl(0);
    HAL_Delay(1000);

    errorWarningFlag = 1;

    while (1)
    {
    if(blinkFlag){
    blinkFlag = 0;
    LED_Blink();
    }
    }


    void LED_Blink(void){
    ledBuffer ^= 0x01; // 异或,闪烁
    LED_Ctrl(ledBuffer);
    }


    // stm32g4xx_it.c
    /* USER CODE BEGIN PV */
    // led
    extern uint8_t errorWarningFlag;
    extern uint16_t errorWarningCount;
    extern uint8_t blinkFlag;
    /* USER CODE END PV */


    /**
    * @brief This function handles System tick timer.
    */
    void SysTick_Handler(void)
    {
    /* USER CODE BEGIN SysTick_IRQn 0 */

    /* USER CODE END SysTick_IRQn 0 */
    HAL_IncTick();
    /* USER CODE BEGIN SysTick_IRQn 1 */
    if(errorWarningFlag){
    if(errorWarningCount == 4999){ // 5s
    errorWarningFlag = 0;
    errorWarningCount = 0;
    }else{
    errorWarningCount ++;
    if(errorWarningCount % 500 == 1){ // 500ms
    blinkFlag = 1; // Blink led
    }
    }
    }
    /* USER CODE END SysTick_IRQn 1 */
    }

STM32G431微控制器内部资源

IO

LED操作

中断

串口

ADC

  • CubeMX添加ADC,选择SingleEnded,添加GPIO_Analog,关闭Force DMA channels interrupts,关闭adc dma中断, 开启DMA,设置Circular,半字

  • 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // Init
    // ADC
    HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
    HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&R38_value, 1); // R38
    HAL_ADC_Start_DMA(&hadc2, (uint32_t *)&R37_value, 1); // R37

    // ADC
    float R37_voltage, R38_voltage;
    uint16_t R37_value, R38_value;
    void adc_sample()
    {
    R37_voltage = R37_value * 3.3f / 4096;
    R38_voltage = R38_value * 3.3f / 4096;
    // HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&R38_value, 1); // R38
    // HAL_ADC_Start_DMA(&hadc2, (uint32_t *)&R37_value, 1); // R37
    }

I2C

EEPROM

定时器

  • 基础定时

  • 输入捕获

    STM32 HAL库 CUBEMX 定时器双通道 高精度捕获PWM波 - 小小小p鱼 - 博客园 (cnblogs.com)

    • Clock Source: Internal Clock
    • Channel1: Input Capture direct mode
    • Channel2: Input Capture indirect mode
    • Prescaler: 80-1
    • auto-reload preload: Enable
    • Input Capture Channel 1 - Polarity Selection: Rising Edge
    • Input Capture Channel 2 - Polarity Selection: Falling Edge
    • NVIC: √

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      __HAL_TIM_SET_COUNTER(&htim2, 0);
      __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
      HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);

      void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
      {
      captureValue = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);

      __HAL_TIM_SET_COUNTER(&htim2, 0);
      HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
      }

      正负脉宽

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      // input capture 
      //[0]low [1]high
      uint16_t R39_CaptureValue[2];
      uint8_t R39_CaptureCount;
      uint16_t R40_CaptureValue[2];
      uint8_t R40_CaptureCount;

      uint16_t R39_PulseWidthPositive_us;
      uint16_t R39_PulseWidthNegative_us;
      uint16_t R39_Period_us;
      uint16_t R39_Frequency_Hz;
      float R39_DutyCycle;
      void compute_parameters_R39()
      {
      R39_Period_us = R39_CaptureValue[0];
      R39_PulseWidthPositive_us = R39_CaptureValue[1];
      R39_PulseWidthNegative_us = R39_Period_us - R39_PulseWidthPositive_us;
      R39_Frequency_Hz = 1000000 / R39_Period_us;
      R39_DutyCycle = (float)R39_PulseWidthPositive_us / R39_Period_us;
      }

      uint16_t R40_PulseWidthPositive_us;
      uint16_t R40_PulseWidthNegative_us;
      uint16_t R40_Period_us;
      uint16_t R40_Frequency_Hz;
      float R40_DutyCycle;
      void compute_parameters_R40()
      {
      R40_Period_us = R40_CaptureValue[0];
      R40_PulseWidthPositive_us = R40_CaptureValue[1];
      R40_PulseWidthNegative_us = R40_Period_us - R40_PulseWidthPositive_us;
      R40_Frequency_Hz = 1000000 / R40_Period_us;
      R40_DutyCycle = (float)R40_PulseWidthPositive_us / R40_Period_us;
      }

      void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
      {
      if(htim->Instance == TIM3){ // R39
      __HAL_TIM_SET_COUNTER(&htim3, 0);
      R39_CaptureValue[0] = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1); // Period
      R39_CaptureValue[1] = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_2); // +Pulse
      compute_parameters_R39();
      }else if(htim->Instance == TIM2){ // R40
      __HAL_TIM_SET_COUNTER(&htim2, 0);
      R40_CaptureValue[0] = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); // Period
      R40_CaptureValue[1] = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2); // +Pulse
      compute_parameters_R40();
      }
      }

      // Init
      // Input Capture
      // 在上升沿和下降沿捕获值,在上升沿进入中断,清空计数值,并计算参数,通道一得到周期,通道二得到正脉宽
      HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1); // R39
      HAL_TIM_IC_Start(&htim3, TIM_CHANNEL_2);
      HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); // R40
      HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_2);
  • 输出比较

    CubeMX里TIM选择Internal Clock,设置PWM,分频,AutoReload等等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Init
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
    pwm_freq_duty(PA11_freq, PA11_duty);

    uint16_t PA11_freq = 1000;
    float PA11_duty = 0.5f;
    void pwm_freq_duty(uint16_t freq, float duty)
    {
    __HAL_TIM_SET_AUTORELOAD(&htim1, 1000000 / freq - 1);
    __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 1000000 / freq * duty);
    }

串口

  • 不定长指令处理

  • CubeMX配置:USART1,异步,波特率9600其他不改,DMA添加TX和RX,其他保持默认,打开global interrupt

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    // main.c
    // UART
    uint8_t rxBuffer[20] = {0};
    uint8_t txBuffer[20] = {0};

    void USER_UART_IRQHandler(UART_HandleTypeDef *huart);

    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
    HAL_UART_Receive_DMA(&huart1, rxBuffer, 20);

    void USER_UART_IRQHandler(UART_HandleTypeDef *huart)
    {
    if(huart->Instance == USART1)
    {
    if(__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
    {
    __HAL_UART_CLEAR_IDLEFLAG(huart);
    HAL_UART_DMAStop(huart);

    char ret, num;
    ret = sscanf((char *)rxBuffer, "R3%c", &num);
    if(ret == 1){
    if(num == '7' || num == '8' || num == 'a')
    {
    sprintf((char *)txBuffer, "%c", num);
    HAL_UART_Transmit_DMA(&huart1, txBuffer, strlen((char *)txBuffer));
    }
    }

    memset(rxBuffer, 0, sizeof(rxBuffer));
    HAL_UART_Receive_DMA(&huart1, rxBuffer, 20);
    }
    }
    }

    // stm32g4xx_it.c
    extern void USER_UART_IRQHandler(UART_HandleTypeDef *huart);

    void USART1_IRQHandler(void)
    {
    /* USER CODE BEGIN USART1_IRQn 0 */

    /* USER CODE END USART1_IRQn 0 */
    HAL_UART_IRQHandler(&huart1);
    /* USER CODE BEGIN USART1_IRQn 1 */
    USER_UART_IRQHandler(&huart1);
    /* USER CODE END USART1_IRQn 1 */
    }

DMA

串口

按键

  • 独立按键

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    GPIO_TypeDef *keyGPIOPort[4] = {GPIOB, GPIOB, GPIOB, GPIOA};
    uint16_t keyGPIOPin[4] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_0};
    uint8_t key_detect(void){
    static uint8_t keyPressedFlag = 0;

    for(uint8_t i = 0; i < 4; i++){
    if(!keyPressedFlag && HAL_GPIO_ReadPin(keyGPIOPort[i], keyGPIOPin[i]) == GPIO_PIN_RESET){
    HAL_Delay(10);
    if(HAL_GPIO_ReadPin(keyGPIOPort[i], keyGPIOPin[i]) == GPIO_PIN_RESET){
    keyPressedFlag = 1;
    return i + 1;
    }
    }
    }

    for(uint8_t i = 0; i < 4; i++){
    if(HAL_GPIO_ReadPin(keyGPIOPort[i], keyGPIOPin[i]) == GPIO_PIN_SET){
    continue;
    }else{
    return 0;
    }
    }
    keyPressedFlag = 0;
    return 0;
    }

    void key_process(void){
    uint8_t key = key_detect();

    if(key){
    LCD_Clear(Black);
    }
    switch(key)
    {
    case 1:
    LCD_DisplayStringLine(Line3, (uint8_t *)" B1 ");
    break;
    case 2:
    LCD_DisplayStringLine(Line3, (uint8_t *)" B2 ");
    break;
    case 3:
    LCD_DisplayStringLine(Line3, (uint8_t *)" B3 ");
    break;
    case 4:
    LCD_DisplayStringLine(Line3, (uint8_t *)" B4 ");
    break;
    default:
    break;
    }
    }

    // 主函数调用key_process();
  • 单双击 & 长短按处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    GPIO_TypeDef *keyPort[4] = {GPIOB, GPIOB, GPIOB, GPIOA};
    uint16_t keyPin[4] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_0};
    uint8_t Key_Scan(void){
    for(int i = 0; i < 4; i++){
    if(HAL_GPIO_ReadPin(keyPort[i], keyPin[i]) == GPIO_PIN_RESET){
    return i + 1;
    }
    }

    return 0;
    }


    uint32_t lastTick;
    uint8_t lastKey;
    uint32_t longShortTick;
    uint32_t singleDoubleTick;
    void Key_Proc(){
    if(HAL_GetTick() - lastTick < 50) return; // Period: 50ms
    lastTick = HAL_GetTick();

    uint8_t key = Key_Scan();
    uint8_t keyPressEdge = key & (lastKey ^ key);
    uint8_t keyReleaseEdge = ~key & (lastKey ^ key);
    lastKey = key;

    if(keyPressEdge){
    if(HAL_GetTick() - singleDoubleTick < 500){ // Double press
    // Do something
    LCD_Clear(Black);
    LCD_DisplayStringLine(Line4, (uint8_t *)" Double press");
    }else{ // Single press
    longShortTick = HAL_GetTick();
    // Do something
    LCD_Clear(Black);
    LCD_DisplayStringLine(Line4, (uint8_t *)" Single press");
    }
    }

    if(keyReleaseEdge){
    if(HAL_GetTick() - longShortTick > 1000){ // Long press
    // Do something
    LCD_Clear(Black);
    LCD_DisplayStringLine(Line4, (uint8_t *)" Long press");
    }else{ // Detect double press only when short press
    singleDoubleTick = HAL_GetTick();
    }
    }
    }


    void app_main(void){
    Key_Proc();
    }

TFT-LCD

  • GPIO配置,相关引脚全部设置为GPIO Output

  • 拷贝lcd.cSrc;拷贝lcd.hfonts.hInc

  • Keil双击主文件夹,添加lcd.c

  • main.c添加

    1
    2
    3
    4
    5
    6
    7
    #include "lcd.h"

    LCD_Init();

    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);

传感器

存储(E2PROM)

  • 导入软件i2c底层文件

  • 添加代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    // i2C_hal.h
    void eeprom_write_byte(uint8_t data_address, uint8_t data);
    uint8_t eeprom_read_byte(uint8_t data_address);

    // i2c_hal.c
    void eeprom_write_byte(uint8_t data_address, uint8_t data)
    {
    I2CStart();
    I2CSendByte(0xA0);
    I2CWaitAck();
    I2CSendByte(data_address);
    I2CWaitAck();
    I2CSendByte(data);
    I2CWaitAck();
    I2CStop();
    delay1(500);
    }


    uint8_t eeprom_read_byte(uint8_t data_address)
    {
    uint8_t read_data;

    I2CStart();
    I2CSendByte(0xA0);
    I2CWaitAck();
    I2CSendByte(data_address);
    I2CWaitAck();

    I2CStart();
    I2CSendByte(0xA1);
    I2CWaitAck();
    read_data = I2CReceiveByte();
    I2CSendNotAck();
    I2CStop();
    delay1(500);

    return read_data;
    }

    // main.c
    I2CInit();

    uint8_t count = eeprom_read_byte(0x01);
    count ++;

    eeprom_write_byte(0x01, count);

数据存储、统计与分析计算

嵌入式综合应用程序设计与调试

注意事项

  • 串口发送,没要求的字符不要发(比如”\r\n”),注意发送数量strlen(),去除最后的’\0’,用串口调试器验证一下
  • 注意别少单位
  • 编程完成后,从头到尾仔细验证一遍功能
  • ADC使用前要校准
  • 不能使用ARMCC V6编译器,否则会没分,只能用慢慢的V5
  • 比赛一般要求主频80MHz,使用HSI->PLL->SYSCLK=80MHz即可