/* ============================================================
* L04 · 12 盏灯的十字路口与位掩码
* 板型:STM32F103C8T6 Blue Pill @ Wokwi(64MHz)
* 接线(与 L00 相同):PA2 南北红 / PA3 南北黄 / PA4 南北绿 / PA5 东西红 / PA6 东西黄 / PA7 东西绿
* 每条线并联 2 盏同色灯(北+南、东+西),每盏灯各串 220Ω 到 GND,高电平亮
* ============================================================ */
#include "stm32f1xx_hal.h"
#include <stdio.h>
/* ---------- 十字路口的 6 条灯线(与 L00 同构,每条线并联路口两端同色的 2 盏灯) ----------
* PA2 = 南北 红 PA3 = 南北 黄 PA4 = 南北 绿
* PA5 = 东西 红 PA6 = 东西 黄 PA7 = 东西 绿
* 即 6 个引脚 → 12 盏灯:北、南的红灯并联在 PA2,东、西的红灯并联在 PA5,其余同理。 */
#define LED_MASK 0xFCu /* 二进制 1111 1100:PA2~PA7 这 6 位 */
enum { C_RED = 0, C_YEL = 1, C_GRN = 2, C_OFF = 3 }; /* 灯色编号:用它当数组下标 */
/* LAMP[i] 是“只有第 i 条灯线对应那一位为 1”的掩码 */
static const uint32_t LAMP[6] = { 1u<<2, 1u<<3, 1u<<4, 1u<<5, 1u<<6, 1u<<7 };
/* ---------- 模式体验区(课堂验证 V3 要用):一次只改一行 ----------
* 可选 GPIO_MODE_OUTPUT_PP(默认)/ GPIO_MODE_OUTPUT_OD / GPIO_MODE_AF_PP / GPIO_MODE_AF_OD */
#define LED_MODE GPIO_MODE_OUTPUT_PP
#define LED_SPEED GPIO_SPEED_FREQ_LOW
/* 同时设置南北、东西两个方向的灯色。ns / ew 取值 C_RED / C_YEL / C_GRN / C_OFF
* 做法:先算出“要亮的那几位”set,再用 BSRR 一次写入:
* BSRR 低 16 位写 1 → 置高(亮);高 16 位写 1 → 清零(灭)
* set 是要亮的位;(LED_MASK & ~set) 是其余要灭的位,左移 16 位放到高半部分
* 一次写入完成“亮该亮的 + 灭该灭的”,不会出现中间的错误状态(原子操作) */
static void leds_apply(uint8_t ns, uint8_t ew) {
uint32_t set = 0;
if (ns <= C_GRN) set |= LAMP[ns]; /* 南北方向:下标 0/1/2 → PA2/PA3/PA4 */
if (ew <= C_GRN) set |= LAMP[3 + ew]; /* 东西方向:下标 3/4/5 → PA5/PA6/PA7 */
GPIOA->BSRR = set | ((LED_MASK & ~set) << 16);
}
/* 互斥检查:南北是“绿或黄”时,东西必须是“红或灭”,反之亦然。返回 1=安全 */
static uint8_t is_safe(uint8_t ns, uint8_t ew) {
uint8_t ns_go = (ns == C_GRN || ns == C_YEL);
uint8_t ew_go = (ew == C_GRN || ew == C_YEL);
return !(ns_go && ew_go);
}
/* 经过检查再点灯:不安全就拒绝,并保持原状态 */
static uint8_t leds_set_safe(uint8_t ns, uint8_t ew) {
if (!is_safe(ns, ew)) { printf("!! 拒绝:南北=%u 东西=%u 同时放行\r\n", ns, ew); return 0; }
leds_apply(ns, ew);
return 1;
}
/* ---------- 串口(黑盒,第 14 课讲透) ---------- */
static void uart_init(void) {
__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;
}
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(); /* ① 开 GPIOA 时钟 */
GPIO_InitTypeDef g = {0}; /* ② 填配置:6 条灯线一次配好 */
g.Pin = LED_MASK; g.Mode = LED_MODE; g.Pull = GPIO_NOPULL; g.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(GPIOA, &g); /* ③ 应用 */
leds_apply(C_OFF, C_OFF); /* ④ 初值:12 盏全灭 */
}
int main(void) {
HAL_Init();
SystemClock_Config();
gpio_init();
uart_init();
printf("\r\n=== L04 十字路口 12 灯 · 位掩码 ===\r\n");
/* 四个相位的“灯色表”:第 i 步 南北=NS_STEP[i],东西=EW_STEP[i],持续 MS_STEP[i] 毫秒 */
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 uint16_t MS_STEP[4] = { 3000, 1500, 3000, 1500 };
static const char *NAME[4] = { "南北绿", "南北黄", "东西绿", "东西黄" };
for (;;) {
for (uint8_t i = 0; i < 4; i++) {
if (leds_set_safe(NS_STEP[i], EW_STEP[i])) printf("相位:%s\r\n", NAME[i]);
HAL_Delay(MS_STEP[i]);
}
/* 故意发出一个危险请求:南北绿 + 东西绿。互斥检查应当拒绝,灯保持不变 */
leds_set_safe(C_GRN, C_GRN);
HAL_Delay(1000);
}
}
// ------------------------------------------------------------
// 课堂作业(与课件第二小节一致;未完成的课下继续)
//
// 课堂练习1(基础):在每次换相之间加入 1 秒“全红清场”
// 现在的循环是 南北绿→南北黄→东西绿→东西黄。请在“南北黄之后、东西绿之前”和“东西黄之后、南北绿之前”各加入 1 秒<b>全红</b>(四个方向都亮红灯),再进入下一相位。每个状态都用 printf 打印状态名。
// 提示:全红 = leds_apply(C_RED, C_RED)。
//
// 课堂练习2(提升):函数 warn_blink():全黄闪 3 次
// 写函数 <code>void warn_blink(uint8_t times)</code>:四个方向同时亮黄、灭,各 300ms,共闪 <code>times</code> 次,结束后恢复全红。上电时先调用 warn_blink(3) 做“自检”。
// 提示:亮黄 = leds_apply(C_YEL, C_YEL);灭 = leds_apply(C_OFF, C_OFF)。
//
// 课堂练习3(挑战):穷举 16 种组合,数出合法状态
// 南北、东西各有 4 种灯态(红、黄、绿、灭),共 16 种组合。写一个双重 for 循环,对每种组合调用 <code>is_safe()</code>,用串口打印“合法/非法”,并统计合法个数。
// 回答:①合法的有几种?②非法组合有什么共同点?
// 提示:C_RED=0, C_YEL=1, C_GRN=2, C_OFF=3;循环变量取 0~3。
// ------------------------------------------------------------
Loading
stm32-bluepill
stm32-bluepill