/* ============================================================
* L05 · 四相位状态机(阻塞版)
* 板型:STM32F103C8T6 Blue Pill @ Wokwi(64MHz)
* 接线:PA2~PA7 灯线(同 L04);PB0 应急键(上拉输入,按下=低);PA9 串口输出
* 配时:南北绿 30s / 东西绿 20s / 黄 3s;倒计时只用一个 r 推出两个方向
* ============================================================ */
#include "stm32f1xx_hal.h"
#include <stdio.h>
#define NS_GREEN_S 30u /* 南北绿灯:30 秒 */
#define EW_GREEN_S 20u /* 东西绿灯:20 秒 */
#define YEL_S 3u /* 黄灯:3 秒 */
#define SEC_MS 1000u /* 1 “秒”等于多少毫秒。课堂想快速看效果可临时改成 200(周期缩短到 1/5) */
#define LED_MASK 0xFCu
enum { C_RED = 0, C_YEL = 1, C_GRN = 2, C_OFF = 3 };
static const uint32_t LAMP[6] = { 1u<<2, 1u<<3, 1u<<4, 1u<<5, 1u<<6, 1u<<7 };
/* ---------- 状态表:4 个相位,每个相位由三项数据决定 ---------- */
/* 相位0 相位1 相位2 相位3 */
/* 南北绿 南北黄 东西绿 东西黄 */
static const uint8_t NS_STEP[4] = { C_GRN, C_YEL, C_RED, C_RED };
static const uint8_t EW_STEP[4] = { C_RED, C_RED, C_GRN, C_YEL };
static const uint8_t DUR_S[4] = { NS_GREEN_S, YEL_S, EW_GREEN_S, YEL_S };
static const char *NAME[4] = { "南北绿", "南北黄", "东西绿", "东西黄" };
static void leds_apply(uint8_t ns, uint8_t ew) { /* 与 L04 相同:BSRR 一次写入亮+灭 */
uint32_t set = 0;
if (ns <= C_GRN) set |= LAMP[ns];
if (ew <= C_GRN) set |= LAMP[3 + ew];
GPIOA->BSRR = set | ((LED_MASK & ~set) << 16);
}
/* 只用一个计数 r(当前相位剩余秒数)算出两个方向要显示的数字:
* 运行方向显示 r;
* 等待方向:当前是绿灯相位时,还要等“绿剩余 r + 黄 3 秒”才轮到它,所以显示 r + 3;
* 当前是黄灯相位时,下一秒就变绿,显示 r。
* 这样两个数字永远同步递减、不会各数各的,也不会有一个先到 0 停住。 */
static void counts(uint8_t phase, uint8_t r, uint8_t *ns_show, uint8_t *ew_show) {
uint8_t is_green = (phase == 0 || phase == 2);
uint8_t wait = r + (is_green ? YEL_S : 0);
if (phase < 2) { *ns_show = r; *ew_show = wait; } /* 南北在通行 */
else { *ew_show = r; *ns_show = wait; } /* 东西在通行 */
}
/* 阻塞方式运行一个相位:每秒打印一次,然后 HAL_Delay 等一秒(此期间 CPU 什么也不做) */
static void run_phase(uint8_t i) {
leds_apply(NS_STEP[i], EW_STEP[i]);
for (uint8_t r = DUR_S[i]; r > 0; r--) {
uint8_t ns_show, ew_show;
counts(i, r, &ns_show, ew_show);
printf("[%-6s] 南北=%2u 东西=%2u\r\n", NAME[i], ns_show, ew_show);
// 把1000ms拆成10×100ms,每次延时后扫描按键
for(uint8_t t = 0; t < 10; t++)
{
HAL_Delay(SEC_MS / 10);
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
printf(" >> 检测到 PB0 按下\r\n");
}
}
}
static void uart_init(void) { /* 串口黑盒,第 14 课讲透 */
__HAL_RCC_USART1_CLK_ENABLE();
GPIO_InitTypeDef g = {0};
g.Pin = GPIO_PIN_9; g.Mode = GPIO_MODE_AF_PP; g.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &g);
g.Pin = GPIO_PIN_10; g.Mode = GPIO_MODE_INPUT; g.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &g);
USART1->BRR = 556u;
USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
}
int _write(int fd, char *ptr, int len) {
(void)fd;
for (int i = 0; i < len; i++) { while (!(USART1->SR & USART_SR_TXE)) {} USART1->DR = (uint8_t)ptr[i]; }
return len;
}
static void SystemClock_Config(void) { /* HSI÷2×16 = 64MHz,逐字段说明见 L03 */
RCC_OscInitTypeDef o = {0}; RCC_ClkInitTypeDef c = {0};
o.OscillatorType = RCC_OSCILLATORTYPE_HSI; o.HSIState = RCC_HSI_ON; o.HSICalibrationValue = 16;
o.PLL.PLLState = RCC_PLL_ON; o.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2; o.PLL.PLLMUL = RCC_PLL_MUL16;
if (HAL_RCC_OscConfig(&o) != HAL_OK) for (;;) {}
c.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
c.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; c.AHBCLKDivider = RCC_SYSCLK_DIV1;
c.APB1CLKDivider = RCC_HCLK_DIV2; c.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&c, FLASH_LATENCY_2) != HAL_OK) for (;;) {}
}
static void gpio_init(void) {
__HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef g = {0};
g.Pin = LED_MASK; g.Mode = GPIO_MODE_OUTPUT_PP; g.Pull = GPIO_NOPULL; g.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &g); /* PA2~PA7:6 条灯线,推挽输出 */
g.Pin = GPIO_PIN_0; g.Mode = GPIO_MODE_INPUT; g.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &g); /* PB0:应急键,上拉输入(按下=低电平) */
leds_apply(C_OFF, C_OFF);
}
int main(void) {
HAL_Init();
SystemClock_Config();
gpio_init();
uart_init();
printf("\r\n=== L05 四相位状态机(阻塞版)===\r\n");
for (;;) {
for (uint8_t i = 0; i < 4; i++) run_phase(i); /* 0→1→2→3→回到 0,永远循环 */
}
}
// ------------------------------------------------------------
// 课堂作业(与课件第二小节一致;未完成的课下继续)
//
// 课堂练习1(基础):修改配时并计算周期
// 把南北绿灯改成 20 秒、东西绿灯改成 15 秒(只改宏定义),运行并核对串口倒计时。
// 计算并回答:①一个完整周期多少秒?②南北绿灯占整个周期的百分之几(保留整数)?
// 提示:周期 = 南北绿 + 南北黄 + 东西绿 + 东西黄。
//
// 课堂练习2(提升):绿灯最后 3 秒闪烁提醒
// 在“绿灯相位”最后 3 秒,让该方向绿灯每秒闪一次(亮 500ms / 灭 500ms),提醒驾驶员即将变黄。倒计时数字仍然每秒递减一次。
// 提示:最后 3 秒时(r <= 3)把原来的 HAL_Delay(1000) 拆成两个 500ms:前半秒亮、后半秒灭。
//
// 课堂练习3(挑战):阻塞版为什么会“漏按键”?
// 代码里每秒读一次 PB0(应急键)。请做实验:①快速点按 PB0(按下时间 < 0.2 秒)10 次,记录串口有几次“检测到”;②按住 PB0 超过 1 秒;
// 然后把 <code>HAL_Delay(SEC_MS)</code> 改成“10 次 HAL_Delay(SEC_MS/10) 并每次都读键”,再做①,记录结果。回答:这个改法的局限是什么?
// 提示:漏检概率 = 1 − 按下时长 ÷ 采样周期。
// ------------------------------------------------------------
Loading
stm32-bluepill
stm32-bluepill