任务一的目标是把开发板最基本的三样东西跑通,LED 能闪、按键有反应、串口能打印。工程用 STM32CubeIDE 建,下载器用板载 ST-Link,串口走 ST-Link 的虚拟串口,波特率 115200,八位数据一位停止无校验。这一步通了,说明编译、下载、串口三条链路都正常。

先配灯。板载 LD2 在 PA5 上,也就是 Arduino 排针的 D13,物理位置是 CN5 第 6 脚。配成推挽输出,初始输出低电平。这颗灯是高电平点亮。
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_5; /* PA5 = D13 = LD2 */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);再配按键。板载蓝色 B1 在 PC13 上。按照 UM3121 手册第 11.2 节,这颗按键接的是 VDD,按下时引脚为高电平,松开为低电平,所以引脚要开内部下拉,中断触发选上升沿。中断线 13 对应 EXTI13,中断号是 24。
GPIO_InitStruct.Pin = GPIO_PIN_13; /* PC13 = B1 */ GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; GPIO_InitStruct.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); HAL_NVIC_SetPriority(EXTI13_IRQn, 2, 0); HAL_NVIC_EnableIRQ(EXTI13_IRQn);
中断服务函数只清标志并转发,业务放在回调里,同时用一个 30 毫秒的时间窗做消抖,一次按压只记一次。这里用无符号相减而不是大小比较,是为了兼容毫秒计数回绕。
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
static uint32_t last_tick = 0;
if (GPIO_Pin != GPIO_PIN_13) return;
if ((g_ms_tick - last_tick) < 30u) return; /* 30ms 消抖窗口 */
last_tick = g_ms_tick;
g_key_event = 1u; /* 交给主循环处理 */
}蜂鸣器用的是 Grove 有源蜂鸣器模块,四线接口,按颜色分是黑色接地、红色接电源、黄色是信号,第四根是空脚。信号线接 Arduino 排针的 A3,对应的 MCU 引脚是 PB0,物理位置在 CN8 第 4 脚。电源接 3V3,也就是 CN6 第 4 脚,地接 CN6 第 6 或第 7 脚。控制方式是写高电平发声、写低电平停止,普通推挽输出即可。
GPIO_InitStruct.Pin = GPIO_PIN_0; /* PB0 = A3 */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
void Buzzer_Beep(uint16_t ms) /* 响 ms 毫秒,不阻塞 */
{
s_buzz_on = 1u;
s_buzz_ms = ms;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
}
void Buzzer_Service1ms(void) /* 由 1ms 时基调用 */
{
if (s_buzz_on == 0u) return;
if (--s_buzz_ms == 0u)
{
s_buzz_on = 0u;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
}
}闪灯和蜂鸣器的计时没有用延时函数,而是单独做了一个 1 毫秒的时基。芯片主频 250 兆,TIM6 预分频 249 得到 1 兆计数频率,再数 1000 次就是一个毫秒,中断优先级设为 3。
htim6.Instance = TIM6; htim6.Init.Prescaler = 250 - 1; /* 250MHz / 250 = 1MHz */ htim6.Init.Period = 1000 - 1; /* 1MHz / 1000 = 1kHz */ htim6.Init.CounterMode = TIM_COUNTERMODE_UP; htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; HAL_TIM_Base_Init(&htim6); HAL_TIM_Base_Start_IT(&htim6);
时基回调里做三件事:毫秒计数、蜂鸣器倒计时、LED 翻转。后面任务里的电机换相也会挂在这里,主循环即使被采集任务占住,这些动作也不会停。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance != TIM6) return;
g_ms_tick++;
Buzzer_Service1ms();
if (s_led_blink != 0u && ++s_led_acc >= 500u)
{
s_led_acc = 0u;
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); /* 500ms 翻转一次 */
}
}串口用 USART2,也就是板载虚拟串口,PA2 发送、PA3 接收,复用功能 AF7,PC 端打开对应 COM 口即可。打印用阻塞发送,接收用中断,每进一次中断收一个字节,攒进环形缓冲,主循环再按行解析。要让接收中断工作,除了配置引脚复用,还要在底层初始化里使能 USART2 的 NVIC 中断。
HAL_UART_Receive_IT(&huart2, (uint8_t *)&s_rx_byte, 1); /* 启动接收 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
uint8_t nh = (uint8_t)((s_rx_head + 1u) % sizeof(s_rx_q));
if (nh != s_rx_tail) /* 缓冲满了就丢新字节 */
{
s_rx_q[s_rx_head] = s_rx_byte;
s_rx_head = nh;
}
HAL_UART_Receive_IT(&huart2, (uint8_t *)&s_rx_byte, 1); /* 重新武装 */
}烧进去以后按一下 B1,绿灯开始每 500 毫秒翻转一次,蜂鸣器响 60 毫秒,串口同时打印一行日志:

任务一到这里就完成了。下一步接 LIS3DH,先用 I²C 把三轴数据读出来。
我要赚赏金
