February 7, 2024

蓝桥杯比赛

蓝桥杯比赛

竞赛提供文件

  1. USB驱动程序(一般不会用到)

  2. 底层驱动代码参考

    • DS18B20温度传感器,一般省赛不会用,该配件在扩展版上

    • I2C驱动程序(重要)

  3. 芯片资料:板子上几乎所有芯片的手册,大多数用不到,重要的在于STM32的编程手册和参考手册。客观题中相关问题可以直接查阅手册。

  4. 库文件(STM32CubeMX配置完成之后该文件也一般不会用到)

  5. 液晶驱动参考程序(重点)

    开始比赛后直接将HAL_06_LCD直接复制一份作为工程模板使用,不用再写额外的LCD调试程序(其CubeMX工程版本可能有问题,需要事先注意版本移植的问题)。

  6. CT117E_M4_SCH.pdf:开发板原理图

  7. CT117E_M4产品手册:开发板开发手册,包含原理图

客观题部分

客观题包含:STM32相关、数电模电部分知识。主要看平时的积累,STM32相关不会的要会查阅手册。

程序设计部分

1 LED模块

  1. 修改CubeMX配置:IO引脚,IO端口
  2. 注意事项:使用LED时必须将LE引脚拉高,不用时拉低
  3. 使用说明:高电平LED熄灭,低电平LED点亮

2 按键KEY模块

  1. 修改CubeMX配置:IO引脚,IO端口

  2. 注意事项:未按下为高电平,按下为低电平

  3. 按键需要消抖,但是不能阻塞程序,可以参考以下的扫描方式

    Key Scan Code
    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
    uint8_t KeyScanCount = 0;
    uint8_t KeyScanFlag = 0;
    uint8_t KeyPressedFlag = 0;
    uint8_t KeyFlag = 0;

    /* 按键扫描函数,20ms扫描一次实现消抖 */
    void Key_Scan(void)
    {
    for (uint8_t i = 0; i < 4; i ++)
    {
    if (HAL_GPIO_ReadPin(KeyPort[i], KeyPin[i]) == GPIO_PIN_SET) // lift up
    {
    KeyPressedFlag &= ~(1 << i);
    }
    else if ((KeyPressedFlag & (1 << i)) == 0) // first push
    {
    if (HAL_GPIO_ReadPin(KeyPort[i], KeyPin[i]) == GPIO_PIN_SET)
    {
    return;
    }
    KeyFlag |= (1 << i);
    KeyPressedFlag |= (1 << i);
    }
    }
    }

    void Key_Process(void)
    {
    printf("keyFlag: %X\n", KeyFlag);
    }

3 串口收发模块

  1. CubeMX配置USART1初始化;

  2. CubeMX使能USART中断;

  3. 串口发送:重写fputc()函数(赛前多写几次),即可直接使用printf()函数进行串口发送;

    1
    2
    3
    4
    5
    int fputc(int ch, FILE* f)
    {
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFF);
    return ch;
    }
  4. 串口接收:使能中断接收,之后重写中断服务函数HAL_UART_RxCpltCallback()函数即可:

    1
    HAL_UART_Receive_IT(&huart1, uartRxBuffer, 1);
    1
    2
    3
    4
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    /* 接收处理代码 */
    }

4 ADC模块

  1. 初始化:直接在CubeMX中配置即可,推荐使用DMA循环模式触发ADC采样

  2. 开始采样:在程序初始化中直接开启DMA模式ADC采样。之后CPU就不用处理ADC的内容了,需要数据直接读取即可。

    1
    2
    3
    uint16_t AdcBuffer[2] = {0};

    HAL_ADC_Start_DMA(&hadc2, (uint32_t*)Adcbuffer, 2);
  3. 读取数据:直接读取即可,DMA会自动更新其中的内容。

    1
    2
    AdcDisplay[0] = Adcbuffer[0] * 3.3 / 4095.0;
    AdcDisplay[1] = Adcbuffer[1] * 3.3 / 4095.0;
  4. 数据不稳定:延长采样时间

5 PWM输出

  1. 初始化:直接在CubeMX中配置好TIM与对应通道的PWM输出PWM Config

  2. 开始/停止:直接调用库函数即可

    1
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
  3. 调节频率设置占空比:直接设置PSC(预分频系数,一般不用设置这个)、ARR(自动重装载值)、CCRx(捕获比较寄存器)的值即可:

    1
    2
    htim3.Instance->ARR = 49;
    htim3.Instance->CCR2 = 99;

6 测量PWM频率和占空比

使用定时器的捕获中断服务回调函数处理。

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
uint8_t pwmMode = 0;
uint32_t pwmPosTime = 0;
double pwmPredTime = 0;
double pwmDuty = 0;
double pwmFreq = 0;

void HAL_TIM_IC_CaptureCallBack(TIM_HandleTypeDef *htim)
{
if (pwmMode == 0)
{
htim2.Instance->CCER |= 0x0002; // set rising detection to falling detection
htim2.Instance->CNT = 0;
pwmMode = 1;
}
else if (pwmMode == 1)
{
htim2.Instance->CCER &= ~0x0002; // set falling detection to rising detection
pwmPosTime = htim2.Instance->CCR1;
pwmMode = 2;
}
else if (pwmMode == 2)
{
htim2.Instance->CCER |= 0x0002; // set rising detection to falling detection
pwmPredTime = htim2.Instance->CCR1;
htim2.Instance->CNT = 0;
pwmMode = 1;
pwmDuty = (100.0 * pwmPosTime) / (pwmPredTime);
pwmFreq = 10000000.0 / (pwmPredTime); // 1000000.0 = 主频/PSC
}
}
  1. 第一次先捕获上升沿,清空计数器,并将两个时间设置为0;
  2. 第二次捕获下降沿,记录高电平时间;
  3. 第三次再捕获上升沿,记录整个周期的时间;
  4. 主函数中判断是否已经捕获完成一个周期,将pwmMode设置为0开始下一次捕获;

整体代码编写思路

题目的主要特点:

  1. 任务要求多
  2. 实时处理和显示
  3. 同一任务的处理时间间隔有一定要求

HAL_06_LCD工程开始逐步添加功能。最重要的编写思想是==前后台系统==。

前后台系统

前后台系统即将每一个模块及其相应称为一个任务。使用SysTick计时,每隔一段时间执行一次某个任务,同时该任务不能占据太长时间。以下面的代码为例:

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
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 (KeyScanCount == 49) // 任务 1:按键扫描,50ms一次
{
KeyScanCount = 0;
KeyScanFlag = 1;
}
else
{
KeyScanCount ++;
}

if (LEDRefreshCount == 99) // 任务 2:LED刷新,100ms一次
{
LEDRefreshCount = 0;
LEDRefreshFlag = 1;
}
else
{
LEDRefreshCount ++;
}

if (LCDRefreshCount == 99) // 任务 3:LCD刷新,100y
{
LCDRefreshCount = 0;
LCDRefreshFlag = 1;
}
else
{
LCDRefreshCount ++;
}

/* USER CODE END SysTick_IRQn 1 */
}

main函数中:

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
void main(void)
{
/* Init code */

while (1)
{
if (LCDRefreshFlag == 1) { // LCD刷新
LCD_Refresh();
LCDRefreshFlag = 0;
}
if (LEDRefreshFlag == 1) { // LED刷新
LED_Refresh();
LEDRefreshFlag = 0;
}
if (KeyScanFlag == 1) { // 按键扫描
Key_Scan();
KeyScanFlag = 0;
}
if (KeyFlag || KeyLongFlag) { // 长按检测
Key_Process();
KeyFlag = 0;
KeyLongFlag = 0;
}
}
}

About this Post

This post is written by Yun Zhang, licensed under CC BY-NC 4.0.

#嵌入式开发#STM32#蓝桥杯