/* ============================================================
* 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 条灯线 ----------
* PA2 = 南北 红 PA3 = 南北 黄 PA4 = 南北 绿
* PA5 = 东西 红 PA6 = 东西 黄 PA7 = 东西 绿
* 6 个引脚 → 12 盏灯(同方向两个灯并联) */
#define LED_MASK 0xFCu /* 二进制 1111 1100:PA2~PA7 */
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 };
#define LED_MODE GPIO_MODE_OUTPUT_PP
#define LED_SPEED GPIO_SPEED_FREQ_LOW
static void leds_apply(uint8_t ns, uint8_t ew) {
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);
}
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;
}
// 【练习2:黄灯闪烁自检函数】
void warn_blink(uint8_t times)
{
for(uint8_t k = 0; k < times; k++)
{
leds_apply(C_YEL, C_YEL);
HAL_Delay(300);
leds_apply(C_OFF, C_OFF);
HAL_Delay(300);
}
leds_apply(C_RED, C_RED);
}
static void uart_init(void) {
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_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;
}
// 重定向printf
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)
{
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();
GPIO_InitTypeDef g = {0};
g.Pin = LED_MASK;
g.Mode = LED_MODE;
g.Pull = GPIO_NOPULL;
g.Speed = LED_SPEED;
HAL_GPIO_Init(GPIOA, &g);
leds_apply(C_OFF, C_OFF);
}
int main(void) {
HAL_Init();
SystemClock_Config();
gpio_init();
uart_init();
printf("\r\n=== L04 十字路口12灯,位掩码控制 ===\r\n");
warn_blink(3); //上电自检:全黄灯闪烁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 uint16_t DELAY[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(DELAY[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