智能骑行头盔技术传承备课教案
·
智能骑行头盔技术传承备课教案
第一课时:项目总体架构与双平台对比
1.1 STM32F407 毕业设计架构(C语言 + FreeRTOS)
硬件拓扑框图:
┌─────────────────────────────────────────────────────────────────┐
│ STM32F407VET6 最小系统 │
│ ARM Cortex-M4 @ 168MHz │
│ Flash: 512KB RAM: 192KB │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ I2C1 │ │ I2C2 │ │ I2C_模拟 │ │ USART1 │ │
│ │ PB6(SCL) │ │ PB10(SCL)│ │ PC10(SCL) │ │ PA9(TX) │ │
│ │ PB7(SDA) │ │ PB11(SDA)│ │ PC11(SDA) │ │ PA10(RX) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │ │
│ ┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐ ┌─────▼─────────┐ │
│ │ MPU6050 │ │ TOF400C │ │ TOF400C │ │ M800AK 4G模组 │ │
│ │ 六轴姿态 │ │ 后方测距 │ │ 侧方测距 │ │ 定位+MQTT上云 │ │
│ └──────────┘ └──────────┘ └──────────┘ └───────────────┘ │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐ │
│ │ USART2 │ │ TIM1_CH1/CH2 │ │ GPIO PA4 │ │ GPIO │ │
│ │ PA2(TX) │ │ PE9, PE11 │ │ 推挽+下拉 │ │ │ │
│ │ PA3(RX) │ │ PWM+DMA │ │ │ │ │ │
│ └────┬─────┘ └──────┬───────┘ └────┬─────┘ └──────────┘ │
│ │ │ │ │
│ ┌────▼─────┐ ┌──────▼───────┐ ┌────▼─────┐ │
│ │ ASR01 │ │ WS2812B×2 │ │ 震动马达 │ │
│ │ 离线语音 │ │ 左/右灯带 │ │ 触觉反馈 │ │
│ └──────────┘ └──────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
为什么要用 I2C 而不是其他协议?
讲到这里要深挖 I2C 的物理层:
- I2C 只需要 2 根线:SCL(时钟线)+ SDA(数据线)
- 总线拓扑:一条总线上可以挂多个设备,每个设备有唯一的 7 位地址
- 主从架构:MCU 永远是 Master,传感器是 Slave
- 开漏输出 + 上拉电阻:这是 I2C 最关键的电特性
I2C 总线的物理连接:
VCC(3.3V)
│
┌───┴───┐
│ 4.7kΩ │ 上拉电阻(必须!)
│ │
└───┬───┘
│
┌────────┬───────────────┼───────────────┬────────┐
│ │ │ │ │
SCL SDA SCL SDA ...
│ │ │ │ │
Master (MCU) Slave1 Slave2
STM32 MPU6050(0x68) TOF400C(0x29)
为什么需要上拉电阻?
- I2C的SCL和SDA都是开漏(Open-Drain)输出
- 开漏只能拉低,不能拉高
- 所以需要外部上拉电阻把总线拉到高电平
- 阻值通常4.7kΩ,太小会增大功耗,太大会使上升沿变慢
为什么 TOF400C 的两路测距用了不同的 I2C?
同一I2C总线上的设备地址必须唯一:
MPU6050 默认地址: 0x68 (AD0接GND)
VL53L1X 默认地址: 0x29 (TOF400C的核心芯片)
问题:两颗TOF400C都是0x29,挂在同一条I2C总线上会冲突!
解决方案:
后方TOF400C → I2C2硬件外设 (PB10/PB11)
侧方TOF400C → 软件模拟I2C (PC10/PC11)
两条物理独立的I2C总线,地址互不干扰
FreeRTOS 的四任务架构:
优先级从高到低:
Task1: 环境感知任务 (最高优先级)
├─ 采集周期: MPU6050 @100Hz, TOF400C @25Hz
├─ 时间同步: 零阶保持插值法,25Hz→100Hz对齐
├─ 姿态解算: DMP引擎输出四元数→欧拉角
└─ 判定逻辑: 跌倒检测(倾角>45° && 加速度>8g && 持续200ms)
盲区预警(距离<1.5m && 持续3帧)
Task2: 无线通信任务 (次高优先级)
├─ M800AK 4G模组: UART1 @115200bps
├─ 网络心跳: 定期AT指令检测驻网状态
├─ MQTT发布: 传感器数据打包JSON上传
└─ 紧急上报: 跌倒时获取GPS坐标,发送求救报文
Task3: 语音识别任务 (普通优先级)
├─ ASR01: UART2 @9600bps
├─ 中断接收: 串口空闲中断 + DMA
└─ 指令解析: 0x01=左转, 0x02=右转, 0x05=取消
Task4: 声光控制任务 (普通优先级)
├─ WS2812B: TIM1_CH1(PE9) + TIM1_CH2(PE11)
├─ PWM+DMA: ARR=209, 800KHz, 自动搬运色彩数据
└─ 震动马达: PA4 GPIO推挽输出
进程间通信机制:
为什么用互斥锁 + 全局结构体?
全局状态结构体:
typedef struct {
uint8_t fall_flag; // 跌倒标志
uint8_t rear_distance_flag; // 后方来车标志
uint8_t side_distance_flag; // 侧方来车标志
uint8_t turn_cmd; // 转向指令 (0/1/2)
float rear_distance; // 后方距离(mm)
float side_distance; // 侧方距离(mm)
float roll, pitch, yaw; // 欧拉角
} SystemState;
SystemState g_state; // 全局共享内存
SemaphoreHandle_t xMutex; // 互斥锁
写入流程(环境感知任务):
xSemaphoreTake(xMutex, portMAX_DELAY);
g_state.rear_distance = tof_data;
g_state.fall_flag = check_fall();
xSemaphoreGive(xMutex);
读取流程(声光控制任务):
xSemaphoreTake(xMutex, portMAX_DELAY);
uint8_t local_fall = g_state.fall_flag;
xSemaphoreGive(xMutex);
if(local_fall) { 执行声光告警 }
为什么不用消息队列?
- 状态数据是持续更新的(距离值每秒几十次刷新)
- 消费方只需要"最新值",不需要"历史值"
- 如果消息队列堆积未处理的历史数据,会浪费内存
- 但缺点是互斥锁持有期间所有其他任务阻塞 → 未来可优化
1.2 STM32F413 大赛架构(Keil5 C语言 + UniKnect 扩展板)
硬件拓扑框图(与 407 架构的关键差异):
┌─────────────────────────────────────────────────────────────────┐
│ NUCLEO-F413ZH 开发板 │
│ STM32F413ZHT6 │
│ ARM Cortex-M4 @ 100MHz ⬅ 注意主频变化 │
│ Flash: 1.5MB RAM: 320KB │
│ │
│ ┌──────────────────────────┐ │
│ │ ST-LINK/V2-1 调试器 │ │
│ │ ├─ SWD 烧录/调试 │ │
│ │ └─ 虚拟串口(REPL用) │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Arduino UNO 接口 (与 UniKnect 垂直堆叠) │ │
│ │ │ │
│ │ UART1_TX (PA9) ←────→ EC200U 主串口 RX │ │
│ │ UART1_RX (PA10) ←────→ EC200U 主串口 TX │ │
│ │ I2C1_SCL (PB6) ←────→ 板载传感器 (S502→ARDU) │ │
│ │ I2C1_SDA (PB7) ←────→ 板载传感器 │ │
│ │ ADC_IN (PA0) ←────→ 光敏电阻 GL5528 │ │
│ │ GPIO PB0 ─────→ EC200U PWRKEY 控制 │ │
│ │ GPIO PB1 ─────→ EC200U RESET 控制 │ │
│ │ 5V / 3.3V / GND ←────→ 供电 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 外部扩展 (通过 NUCLEO 引出的其他 144 引脚) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ MPU6050 │ │ TOF400C×2│ │ WS2812B×2 │ │ ASR-PRO │ │
│ │ I2C2 │ │ I2C3+模拟│ │ TIM1 PWM │ │ UART2 │ │
│ └──────────┘ └──────────┘ └──────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ UniKnect Gen1-PRO 扩展板 (叠在下方) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ EC200U 4G Cat.1 模组 │ │
│ │ ├─ LTE 通信 (上行5Mbps/下行10Mbps) │ │
│ │ ├─ GNSS 定位 (GPS+北斗双模) │ │
│ │ ├─ TTS 语音合成 (板载扬声器J402输出) │ │
│ │ ├─ 录音功能 (板载咪头MIC1输入) │ │
│ │ └─ USB虚拟串口 (J401, 调试/AT指令) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 板载传感器 + 切换开关 │ │
│ │ ├─ LIS2DH12TR 三轴加速度计 │ │
│ │ ├─ AH20-F 温湿度传感器 │ │
│ │ ├─ GL5528 光敏电阻 (通过ADC) │ │
│ │ └─ S502 开关: ARDU↔MCU / QUEC↔EC200U直连 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ SIM卡 + 天线 │ │
│ │ ├─ eSIM (内置贴片物联网卡) + NanoSIM卡座 │ │
│ │ ├─ S501 开关: eSIM ↔ 插拔SIM卡 │ │
│ │ ├─ J101 主天线接口 (SMA外螺内孔) │ │
│ │ ├─ J102 GNSS天线接口 │ │
│ │ └─ J103 WiFi Scan天线接口 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 调试接口 │ │
│ │ ├─ J403 USB-C: CP2105双串口(Enhanced+Standard) │ │
│ │ │ ├─ Enhanced COM: EC200U DEBUG日志输出 │ │
│ │ │ └─ Standard COM: MCU↔EC200U 串口数据监测 │ │
│ │ ├─ J401 USB-C: 模组USB口(AT命令/固件升级) │ │
│ │ └─ DC5V电源输入 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
两代架构的核心差异对照表:
| 维度 | STM32F407 毕业设计 | STM32F413 大赛方案 | 迁移策略 |
|---|---|---|---|
| MCU | F407VET6, 168MHz, 512KB Flash | F413ZHT6, 100MHz, 1.5MB Flash | 引脚部分兼容,HAL库API一致 |
| 4G模组 | 银尔达 M800AK (UART AT指令) | 移远 EC200U (UART AT指令) | 协议兼容,AT指令集略有差异 |
| IMU | MPU6050 (I2C, 0x68) | 板载LIS2DH12TR + 可外接MPU6050 | I2C驱动逻辑不变 |
| 测距 | TOF400C×2 (硬件I2C+模拟I2C) | TOF400C×2 (同上) | 完全复用 |
| 语音 | ASR01 (UART, 十六进制码) | ASR-PRO (UART, 十六进制码) | 协议不变,天问Block配置复用 |
| 灯带 | WS2812B×2 (TIM1 PWM+DMA) | WS2812B×2 (TIM1 PWM+DMA) | 需重新计算ARR值(主频变了) |
| 云平台 | OneNET MQTT (M800AK) | OneNET MQTT (EC200U) | 物模型、三元组、Topic完全复用 |
| 音频输出 | ASR01自带喇叭 | EC200U TTS → 板载扬声器 | ASR-PRO识别,EC200U发声 |
| 定位 | M800AK内置GPS | EC200U内置GNSS | AT指令不同,逻辑相同 |
第二课时:STM32CubeMX 配置 STM32F413ZH(100MHz)
2.1 创建工程
打开 STM32CubeMX
→ File → New Project
→ 搜索 STM32F413ZH
→ 双击选择 LQFP144 封装 (这是NUCLEO-F413ZH的封装)
→ Start Project
2.2 时钟树配置:如何从外部晶振到 100MHz 系统时钟
NUCLEO-F413ZH 的外部晶振说明:
板载晶振:
HSE (高速外部时钟): 8MHz 无源晶振 (连接 PH0/PH1)
LSE (低速外部时钟): 32.768KHz 无源晶振 (连接 PC14/PC15)
F413 和 F407 的时钟树差异:
F407: 8MHz → PLL × 336 / 2 / 2 = 168MHz (最大主频)
F413: 8MHz → PLL × 100 / 2 / 2 = 100MHz (最大主频!)
注意:F413 最高主频就是 100MHz,不是 168MHz!
这和 F407 不一样,CubeMX 不会让你配置超过 100MHz
CubeMX 时钟树配置步骤(详细操作):
第一步:Pinout & Configuration 标签页
→ System Core → RCC
→ High Speed Clock (HSE): Crystal/Ceramic Resonator
→ Low Speed Clock (LSE): Crystal/Ceramic Resonator
第二步:Clock Configuration 标签页
时钟树配置值(从左到右填写):
┌─────────────────────────────────────────────────────┐
│ HSE Input Frequency: 8 MHz │
│ │
│ PLL Source Mux: HSE │
│ PLLM: /8 (8MHz÷8=1MHz 进PLL) │
│ PLLN: ×100 (1MHz×100=100MHz VCO) │
│ PLLP: /2 (100MHz÷2=50MHz) │
│ PLLQ: 不用管 │
│ PLLR: 不用管 │
│ │
│ System Clock Mux: PLLCLK │
│ AHB Prescaler: /1 (HCLK=100MHz) │
│ │
│ ┌──────┐ /8 ┌────┐ ×100 ┌────┐ /2 ┌─────┐│
│ │8MHz │───→───│PLLM│───→───│PLLN│──→───│PLLP ││
│ │ HSE │ └────┘ └────┘ └──┬──┘│
│ └──────┘ │ │
│ ┌───────────────────────────────┘ │
│ ▼ │
│ PLLCLK = 50MHz │
│ │ │
│ ┌─────▼─────┐ │
│ │ System │ → SYSCLK = 50MHz? ❌ │
│ │ Clock Mux │ │
│ └───────────┘ │
└─────────────────────────────────────────────────┘
等等!50MHz 不是 100MHz?
注意区分:
- PLLP 输出的是 PLLCLK,这个 = 50MHz
- 但 STM32F413 的 System Clock 可以选择不同的 PLL 输出
- 实际上 F413 的 PLL 可以输出 100MHz 直接给 SYSCLK
正确的 F413 时钟配置应该是:
PLLM = 8
PLLN = 200 ← 注意!是 200 不是 100
PLLP = 2 → VCO=8÷8×200=200MHz, PLLCLK=200÷2=100MHz
或者另一种常用配置:
PLLM = 4
PLLN = 100 → VCO=8÷4×100=200MHz
PLLP = 2 → PLLCLK=100MHz
F413 时钟树 CubeMX 实际截图对应的值:
在 Clock Configuration 页面的输入框依次填写:
1. HSE: 8 MHz
2. PLL Source Mux: HSE
3. PLLM: 4 (8MHz ÷ 4 = 2MHz)
4. PLLN: 100 (2MHz × 100 = 200MHz VCO)
5. PLLP: 2 (200MHz ÷ 2 = 100MHz SYSCLK)
6. System Clock Mux: PLLCLK
7. AHB Prescaler: /1 (HCLK = 100MHz)
8. APB1 Prescaler: /2 (APB1 = 50MHz, 因为APB1最大50MHz)
9. APB2 Prescaler: /1 (APB2 = 100MHz, APB2可跑满100MHz)
10. TIM Prescaler: APB1×2=100MHz, APB2×1=100MHz
(当APB分频≠1时,定时器时钟=APB×2,这是STM32的时钟倍增机制)
2.3 FreeRTOS 配置
Pinout & Configuration → Middleware → FREERTOS
Interface: CMSIS_V2
Configuration:
→ Tasks and Queues 标签:
创建四个任务:
1. Task_Environment (环境感知)
Priority: osPriorityHigh
Stack Size: 512 Words
2. Task_Communication (无线通信)
Priority: osPriorityAboveNormal
Stack Size: 1024 Words
3. Task_Voice (语音识别)
Priority: osPriorityNormal
Stack Size: 512 Words
4. Task_LightSound (声光控制)
Priority: osPriorityNormal
Stack Size: 256 Words
→ Timers and Semaphores 标签:
创建互斥锁:
- Mutex_SystemState: 保护全局状态结构体
创建二值信号量:
- Sem_UART2_Rx: 语音模块串口接收完成信号
2.4 I2C 配置
Pinout & Configuration → Connectivity → I2C1
I2C1:
Mode: I2C
I2C Speed Mode: Fast Mode (400KHz)
Clock Speed: 400000 Hz
引脚映射:
I2C1_SCL → PB6
I2C1_SDA → PB7
I2C2 (如果接MPU6050或第二路TOF):
Mode: I2C
I2C Speed Mode: Fast Mode
引脚映射:
I2C2_SCL → PB10
I2C2_SDA → PB11
I2C 通信原理深挖(讲给学弟学妹的):
I2C 通信的完整时序:
1. START 条件:
SCL 高电平时,SDA 从高→低的跳变
SDA ─┐
SCL ─────┘────── 这是主机发出的"我要开始说话了"
2. 7位地址 + R/W位:
主机发送 8 位:7位地址 + 1位方向
例如 MPU6050 (地址 0x68):
写操作: 0x68 << 1 | 0 = 0xD0
读操作: 0x68 << 1 | 1 = 0xD1
每个从机在第9个SCL时钟拉低SDA,表示"ACK,我在!"
3. 数据传输:
每次8位,第9位是ACK
主机发: 寄存器地址 (0x3B = ACCEL_XOUT_H)
从机回: ACK
主机发: 重复START
主机发: 读地址 (0xD1)
从机回: 数据字节1, 主机ACK
从机回: 数据字节2, 主机NACK ← 主机说"够了"
4. STOP 条件:
SCL 高电平时,SDA 从低→高的跳变
SDA ─┐
SCL ─────┘────── 总线释放
为什么I2C最多挂127个设备?
- 地址只有7位: 2^7 = 128 个地址
- 减去 0x00(广播)和一些保留地址,实际可用约112个
- 但受限于总线电容(最大400pF),实际挂不了那么多
2.5 USART 配置
Pinout & Configuration → Connectivity → USART1
Mode: Asynchronous
Baud Rate: 115200 (与EC200U通信)
Word Length: 8 Bits
Parity: None
Stop Bits: 1
引脚: TX=PA9, RX=PA10
USART2 (接ASR-PRO语音模块):
Mode: Asynchronous
Baud Rate: 9600 (ASR01/ASR-PRO 默认)
Word Length: 8 Bits
Parity: None
Stop Bits: 1
引脚: TX=PA2, RX=PA3
USART3 (备用调试串口):
Mode: Asynchronous
Baud Rate: 115200
USART 名称含义深挖:
USART vs UART 的区别?
UART: Universal Asynchronous Receiver/Transmitter
通用异步收发器
- 只能异步通信(没有时钟线)
- 双方约定相同的波特率
USART: Universal Synchronous/Asynchronous Receiver/Transmitter
通用同步/异步收发器
- 既可以异步(和UART一样)
- 也可以同步(有时钟线CK输出)
- STM32的串口都叫USART,但通常我们只用异步模式
为什么 STM32F413 的手册上写的是 USART?
- STM32 的所有串口硬件都支持同步模式
- 同步模式需要额外的 CK 引脚(时钟线)
- 但我们项目中不需要,所以只配置 TX 和 RX
- CubeMX 中选择 "Asynchronous" 模式即可
异步通信的关键参数:
波特率(Baud Rate): 每秒传输的码元数
9600bps = 每秒9600个bit = 约960字节/秒(含起始位和停止位)
115200bps = 每秒115200个bit = 约11.5KB/秒
帧格式:
[起始位(1bit)] [数据位(8bit)] [校验位(0/1bit)] [停止位(1bit)]
总共10位传输1字节,所以实际有效速率 = 波特率 ÷ 10
例如115200bps → 实际约11.5KB/s
全双工 vs 半双工:
全双工(Full Duplex):
TX和RX是两条独立的物理线
MCU可以同时发送和接收
例: USART的PA9(TX)和PA10(RX)同时工作
┌─────┐ TX ────────────→ RX ┌─────┐
│ MCU │ │模组 │
│ │ RX ←──────────── TX │ │
└─────┘ └─────┘
半双工(Half Duplex):
只有一条数据线,发送和接收不能同时进行
例: RS485、I2C的SDA(虽然是I2C不是UART)
┌─────┐ 数据线 ┌─────┐
│ MCU │ ←────────────────→ │模组 │
│ │ 同一时刻只能单向 │ │
└─────┘ └─────┘
UART 通信没有 CLK 线,如何保证时序正确?
同步通信(如SPI):
CLK线由主机发出,双方按照时钟边沿采样
┌─┐ ┌─┐ ┌─┐ ┌─┐
│1│0│1│1│0│0│1│0│ ← 用CLK来"同步节拍"
异步通信(如UART):
没有CLK线,双方"约定"波特率
- 接收方用自己内部的16倍或8倍过采样时钟
- 检测到起始位的下降沿后,开始按自己的时钟采样
- 如果双方波特率误差太大(>3%),就会出错
过采样原理:
接收方以 16×波特率 的频率采样
每个bit持续期间采样第8/9/10次,取多数值
这样即使有微小误差也能正确接收
2.6 PWM 配置(WS2812B 的关键)
为什么 WS2812B 对时序要求极其严格?
WS2812B 的通信协议是单线归零码(NRZ),800KHz:
逻辑 "0": ┌──┐┌──────────────────────┐
│ ││ │
└──┘└──────────────────────┘
←0.4μs→←──────0.85μs───────→
高电平 低电平
逻辑 "1": ┌──────┐┌───────┐
│ ││ │
└──────┘└───────┘
←0.8μs→←0.45μs→
高电平 低电平
关键参数:
周期 T = 1/800KHz = 1.25μs
逻辑0高电平: 0.4μs → 占空比 = 0.4/1.25 = 32%
逻辑1高电平: 0.8μs → 占空比 = 0.8/1.25 = 64%
为什么不用普通GPIO翻转?
- STM32在100MHz主频下,一条指令约10ns
- 0.4μs = 400ns = 40条指令
- 用延迟函数很难精确控制,而且中断会打断时序
- LED灯会随机闪烁、颜色错乱
所以必须用硬件PWM+DMA!
CubeMX 配置 PWM(STM32F413 @ 100MHz):
Pinout & Configuration → Timers → TIM1
TIM1 配置:
Clock Source: Internal Clock
Channel1: PWM Generation CH1 (接左侧WS2812B → PE9)
Channel2: PWM Generation CH2 (接右侧WS2812B → PE11)
Configuration → Parameter Settings:
Prescaler: 0 (不分频,输入100MHz)
Counter Mode: Up
Counter Period: 124 (ARR = 124)
计算过程:
PWM频率 = 定时器时钟 ÷ (Prescaler+1) ÷ (ARR+1)
800KHz = 100MHz ÷ 1 ÷ (ARR+1)
ARR+1 = 100MHz ÷ 800KHz = 125
ARR = 124
逻辑0比较值: 124 × 32% ≈ 40
逻辑1比较值: 124 × 64% ≈ 80
DMA Settings:
点击 Add → 选择 TIM1_CH1
Direction: Memory to Peripheral
Mode: Circular
Data Width: Half Word (16位,一次传输一个颜色分量)
TIM1_CH2 同样配置 DMA
WS2812B 的色彩数据格式:
每颗灯珠需要24位数据(G-R-B顺序):
G7 G6 G5 G4 G3 G2 G1 G0 | R7 R6 R5 R4 R3 R2 R1 R0 | B7 B6 B5 B4 B3 B2 B1 B0
例如,黄色(R=255, G=255, B=0)的数据:
G: 0xFF = 11111111
R: 0xFF = 11111111
B: 0x00 = 00000000
组合: 11111111 11111111 00000000 = 0xFF, 0xFF, 0x00
复位码:
发送完所有灯珠数据后,数据线保持低电平 > 50μs
灯珠内部锁存数据,新颜色生效
第三课时:编译原理与烧录技术
3.1 从源码到固件:编译的四个阶段
main.c → .o → .elf/.axf → .hex/.bin
阶段1: 预处理 (Preprocessing)
编译器: armcc -E
输入: .c / .h 文件
输出: .i 文件 (预处理后的源码)
做了什么:
- #include 头文件展开(把头文件内容复制过来)
- #define 宏替换
- #ifdef / #ifndef 条件编译
- 删除注释
示例:
原始代码:
#define LED_ON() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET)
LED_ON();
预处理后:
HAL_GPIO_WritePin(GPIOB, ((uint16_t)0x0000), ((uint32_t)0x0001));
阶段2: 编译 (Compilation)
编译器: armcc
输入: .i 文件
输出: .s 文件 (汇编代码)
做了什么:
- 词法分析、语法分析、语义分析
- 生成中间代码
- 优化
- 生成目标架构的汇编代码
示例: C → ARM汇编
int a = b + 5;
↓
LDR R0, [SP, #4] ; 从栈加载b
ADD R0, R0, #5 ; R0 = b + 5
STR R0, [SP, #0] ; 存到a的位置
阶段3: 汇编 (Assembly)
汇编器: armasm
输入: .s 文件
输出: .o 文件 (目标文件,机器码)
做了什么:
- 把汇编指令翻译成二进制机器码
- 生成符号表(记录函数名、变量名的地址占位符)
.o 文件内容:
包含: 机器码 + 重定位信息 + 符号表
不能直接运行!因为地址还没最终确定
阶段4: 链接 (Linking)
链接器: armlink
输入: 多个 .o 文件 + 库文件(.lib) + 链接脚本(.sct)
输出: .elf / .axf 文件 (可执行文件)
做了什么:
- 把所有 .o 文件合并
- 解决符号引用(把函数调用指向实际地址)
- 按照链接脚本分配内存地址
- 添加启动代码、中断向量表
.elf/.axf 文件:
ELF = Executable and Linkable Format
AXF = ARM eXecutable Format
包含: 代码 + 数据 + 调试信息 + 符号表
最终转换:
fromelf.exe:
.axf → .hex (Intel HEX格式,含地址信息)
.axf → .bin (纯二进制镜像,无地址信息)
各文件格式的区别:
| 文件格式 | 内容 | 大小 | 用途 |
|---|---|---|---|
.o |
单个源文件的机器码+符号表 | 小 | 编译中间产物 |
.s |
ARM汇编源码 | 文本 | 调试、优化时查看 |
.axf / .elf |
完整可执行文件(含调试信息) | 最大 | Keil仿真调试用 |
.hex |
Intel HEX格式(ASCII文本,含地址) | 中等 | 烧录(通用性最好) |
.bin |
纯二进制镜像(无地址) | 最小 | 烧录(需指定起始地址) |
c语言和lua、python的区别
3.2 烧录是什么?为什么叫"烧录"?
"烧录"的由来:
- 早期ROM(只读存储器)用熔丝技术
- 用高压电流"烧断"特定熔丝,写入数据
- 这个过程不可逆,像"烧"进去一样
现代Flash的写入原理:
- 浮栅晶体管(Floating Gate MOSFET)
- 用较高的电压(10-12V)给浮栅注入/擦除电子
- 电子被"困"在浮栅中,改变阈值电压
- 0和1通过浮栅电荷量来区分
- 可以反复擦写,但还是习惯叫"烧录"
3.3 三种烧录/调试接口
SWD (Serial Wire Debug) ← ST-Link 使用
┌──────────────────────────────────────┐
│ 只需要2根信号线! │
│ │
│ SWDIO ←→ 双向数据线 │
│ SWCLK → 时钟线(Master发出) │
│ GND ── 共地 │
│ VCC → 可选(目标板自供电就不接) │
│ │
│ 优点: 引脚少,抗干扰强,速率快 │
│ 缺点: 需要专用调试器 │
│ │
│ NUCLEO-F413ZH 板载 ST-LINK/V2-1 │
│ 自动通过 SWD 连接片上 STM32F413 │
└──────────────────────────────────────┘
JTAG (Joint Test Action Group) ← J-Link 使用
┌──────────────────────────────────────┐
│ 需要4-5根信号线: │
│ │
│ TDI → 测试数据输入 │
│ TDO ← 测试数据输出 │
│ TCK → 测试时钟 │
│ TMS → 测试模式选择 │
│ TRST → 测试复位(可选) │
│ │
│ 优点: 标准接口,功能强大 │
│ 缺点: 引脚多,占用PCB面积 │
└──────────────────────────────────────┘
ISP (In-System Programming) ← 串口烧录
┌──────────────────────────────────────┐
│ 通过芯片内置的Bootloader烧录: │
│ │
│ 需要: UART1 (TX/RX) + BOOT0引脚 │
│ │
│ 步骤: │
│ 1. BOOT0 接高电平(拉高到3.3V) │
│ 2. 复位芯片 │
│ 3. 芯片进入System Memory的Bootloader │
│ 4. 通过UART1下载程序到Flash │
│ 5. BOOT0 接低电平,复位,运行新程序 │
│ │
│ 优点: 不需要专用调试器,串口即可 │
│ 缺点: 不能在线调试,速度慢 │
└──────────────────────────────────────┘
为什么 NUCLEO 板上有两个芯片?(F413 和 F103)
NUCLEO-F413ZH 板上的两个芯片:
1. STM32F413ZHT6 (目标芯片,144引脚大芯片)
- 你的程序运行在这里
- 通过 SWD 连接到 ST-LINK
2. STM32F103CBT6 (ST-LINK/V2-1 调试器芯片)
- 出厂预烧了 ST-LINK 固件
- 负责: USB↔SWD 协议转换
- 提供虚拟串口 (VCP) 功能
- 用户LED (LD1) 由它控制
工作流程:
PC USB ──→ F103 (ST-LINK) ──SWD──→ F413 (你的程序)
│
└── 虚拟串口 ──UART──→ F413 (printf输出)
第四课时:串口调试与数据显示
4.1 SSCOM/XCOM 使用技巧
为什么 0x01 显示为方框?
ASCII 码表的关键区间:
可打印字符: 0x20(空格) ~ 0x7E(~)
控制字符: 0x00 ~ 0x1F
0x01 是 SOH (Start of Heading) 控制字符
在 ASCII 表中是不可打印的!
SSCOM 中显示:
HEX模式: 显示 "01"
字符串模式: 显示 "□"(方框) 或者什么都不显示
所以调试时:
ASR-PRO 返回 0x01 → SSCOM需要切换到 "HEX显示" 模式
或者在你的代码里把接收到的 0x01 转成人类可读的字符串
SSCOM 同时监测多个串口的技巧:
方法1: 复制多份 SSCOM.exe 到不同文件夹
C:\Tools\SSCOM_COM3\SSCOM.exe → 打开 COM3 (ASR-PRO)
C:\Tools\SSCOM_COM5\SSCOM.exe → 打开 COM5 (EC200U监测)
方法2: 用 XCOM 替代
XCOM 支持多实例,直接打开两次即可
方法3: 用 MobaXterm
多个标签页,每个打开一个串口
第五课时:工程思维培养——混合音频架构设计
5.1 问题分析:为什么不能简单"借用"EC200U 的麦克风?
EC200U 的 MIC 输入电路(简化):
VCC
│
┌────┴────┐
│ 偏置电阻 │
│ 2.2kΩ │
└────┬────┘
│
┌───────────┼───────────┐
│ │ │
┌────▼────┐ ├──→ EC200U_MIC_P
│ 咪头 │ │
│ (MIC1) │ ├──→ EC200U_MIC_N (或GND)
└─────────┘ │
│ │
GND ┌────▼────┐
│ 隔直电容 │
│ 100nF │
└────┬────┘
│
EC200U 内部ADC
关键问题:
1. 咪头是模拟信号,不能直接"分线"给两个芯片
2. EC200U 内部有偏置电压,会干扰 ASR-PRO 的 MIC 输入
3. ASR-PRO 如果也加偏置,两个偏置电压打架,咪头没法正常工作
4. 即使断开连接,ASR-PRO 核心板自己的 MIC 引脚也没有偏置电路,
需要自己搭运放电路,比买一个开发板还贵
5.2 最终方案:一盔两 MIC,一喇叭
┌─────────────────────────────────────────────────────────────┐
│ 智能骑行头盔 音频架构 │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ ASR-PRO 开发板 │ │ UniKnect 扩展板 │ │
│ │ (带板载MIC咪头) │ │ │ │
│ │ │ │ MIC1 ──→ EC200U │ │
│ │ MIC ──→ ASR-PRO芯片 │ │ 板载咪头 (TTS+录音)│ │
│ │ 语音识别输入 │ │ │ │
│ │ │ │ J402 ←── EC200U │ │
│ │ (SPK不接喇叭!) │ │ 扬声器接口 (唯一喇叭)│ │
│ │ │ │ │ │ │
│ │ UART ──→ STM32 │ │ UART ←──→ STM32 │ │
│ └──────────────────────┘ └──────────┬───────────┘ │
│ │ │
│ ┌────▼────┐ │
│ │ J402接口 │ │
│ │ 接8Ω喇叭 │ │
│ │ (唯一喇叭)│ │
│ └─────────┘ │
└─────────────────────────────────────────────────────────────┘
两个MIC的职责:
ASR-PRO的MIC → 离线语音识别 (骑行者说话→识别指令)
EC200U的MIC → 录音功能 (紧急情况时双向通话用)
一个喇叭的职责:
EC200U J402 → TTS播报 + 通话音频
(ASR-PRO不接喇叭,识别结果通过UART告诉STM32,
STM32通过EC200U的TTS驱动统一喇叭发声)
5.3 软件联动流程
完整交互序列:骑行者说"左转左转"
时间轴 →
t0: 骑行者说出"左转左转"
│
▼
t1: ASR-PRO 麦克风拾音
│
▼
t2: ASR-PRO 离线识别引擎匹配"左转左转"
│
▼ (约100ms)
t3: ASR-PRO 通过 UART TX 发出 0x01
│
▼
t4: STM32 USART2 中断接收 0x01
│
├─────────────────────────────┐
│ │
▼ ▼
t5: TIM1 PWM+DMA USART1 发送AT指令
左侧灯带黄色闪烁 AT+QTTS=2,"正在左转,请注意安全"
(500ms单次,持续5秒)
│ │
│ ▼
│ t6: EC200U 收到AT指令
│ 调用内部TTS引擎
│ │
│ ▼
│ t7: EC200U DAC → 功放 → J402
│ 喇叭播放"正在左转请注意安全"
│ │
▼ ▼
t8: 5秒后灯带熄灭 TTS播放完毕
总延迟:t1→t7 ≈ 200ms,骑行者几乎感觉不到
5.4 为什么这个方案是"工程思维"的体现?
工程思维的核心:不重复造轮子,利用现有资源的最优组合
资源盘点:
✅ EC200U 自带 TTS 引擎 (移远已经开发好,稳定可靠)
✅ EC200U 自带音频功放 (无需外接)
✅ EC200U 有 Audio API (支持TTS/录音/播放)
✅ ASR-PRO 有最好的离线语音识别能力
✅ UniKnect 有 J402 扬声器接口
✅ UniKnect 有 MIC1 咪头
最优组合决策:
1. 语音输入: ASR-PRO 的 MIC (专做识别,精度高)
2. 语音输出: EC200U TTS → J402喇叭 (统一出声,音质好)
3. ASR-PRO 不接喇叭 (避免两个声源互相干扰)
4. EC200U MIC 预留 (未来紧急通话用)
成本分析:
ASR-PRO 开发板: 约35元
ASR-PRO 核心板: 约25元
差异: 10元 = 一套完整的MIC+偏置电路+USB下载电路
如果买核心板再自己搭音频电路:
- MIC咪头: 2元
- 运放芯片: 3元
- 电阻电容: 2元
- 洞洞板+焊接时间: 无价
总计: 远超过10元 + 至少半天时间
教训:
硬件设计中,"省10元"往往会导致"多花两天"
工程思维就是要算"总拥有成本"而不是只看BOM价格
第六课时:嘉立创EDA实操要点
6.1 核心操作流程
1. 创建工程 → 选择"专业版"
2. 原理图:
- 与立创商城联动: 搜索框输入型号 → 自动找到元器件
- 网络编号: 同名网络自动连通 (VCC_3.3V, GND, I2C1_SCL等)
- Shift分隔: 画线时按住 Shift → 可以转角分段走线
- 自定义元件: 库 → 新建元件 → 画封装 → 关联符号
3. 转PCB:
- 设计 → 转PCB
- 布局: 先放主控,再放模块,最后电源
- 布线: 信号线 10mil, 电源线 20-30mil
- 铺铜: 顶层和底层都铺 GND 铜皮
第七课时:云平台配置
7.1 MQTT 协议简介
MQTT = Message Queuing Telemetry Transport
消息队列遥测传输协议
核心概念:发布/订阅模式 (Pub/Sub)
┌─────────┐ 发布 Topic: "helmet/fall" ┌─────────┐
│ 头盔端 │ ─────────────────────────────────→ │ Broker │
│(Publisher)│ Payload: {"fall":1,"lat":34.3, │ (OneNET) │
└─────────┘ "lon":108.9} └────┬────┘
│
订阅 Topic: "helmet/fall"
│
▼
┌──────────┐
│ 微信小程序│
│(Subscriber)│
└──────────┘
MQTT 的 QoS (服务质量):
QoS 0: 至多一次 (发完不管)
QoS 1: 至少一次 (确认收到,可能重复)
QoS 2: 恰好一次 (四次握手,保证不重不漏)
本项目用 QoS 1:跌倒报警必须确保送达,重复了前端可去重
7.2 OneNET 物模型配置
物模型 JSON 定义 (示例):
{
"properties": [
{
"identifier": "rear_distance",
"name": "后方距离",
"dataType": "float",
"unit": "mm",
"accessMode": "r" // 只读,设备上报
},
{
"identifier": "fall_alarm",
"name": "跌倒警报",
"dataType": "bool",
"accessMode": "r" // 只读,设备上报
},
{
"identifier": "turn_cmd",
"name": "转向命令",
"dataType": "int",
"accessMode": "rw" // 读写,小程序可下发
}
]
}
三元组:设备的唯一身份
ProductKey: 产品标识 (同一类设备共享)
DeviceName: 设备名称 (每台设备唯一)
DeviceSecret: 设备密钥 (鉴权用)
设备连接时需要:
MQTT ClientID = ProductKey + DeviceName
MQTT Username = ProductKey
MQTT Password = HMAC_SHA1(DeviceSecret, 时间戳等)
第八课时:Git 仓库协作
8.1 仓库平台选择
根据你"不用梯子"的要求,推荐 Gitee(码云):
| 特性 | Gitee | GitHub(无梯子) | GitCode |
|---|---|---|---|
| 国内访问速度 | ⭐⭐⭐⭐⭐ 快 | ❌ 极慢/不可用 | ⭐⭐⭐⭐ 快 |
| 免费私有仓库 | ✅ 5人内免费 | ✅ 但无法访问 | ✅ |
| 上传速度 | 快 | 不可用 | 快 |
| 团队协作 | ✅ Issues/PR/Wiki | ❌ 无法用 | ✅ |
8.2 仓库结构建议
smart-helmet/
├── docs/ # 文档
│ ├── 论文_王浩丞.pdf
│ ├── 硬件设计/
│ │ ├── 电路图.pdf
│ │ └── 引脚分配表.xlsx
│ └── 会议纪要/
├── firmware_f407/ # 毕业设计代码(FreeRTOS C)
│ ├── Core/
│ ├── Drivers/
│ └── README.md
├── firmware_f413/ # 大赛移植代码(C)
│ ├── Core/
│ ├── Drivers/
│ └── README.md
├── hardware/ # 嘉立创EDA工程
│ └── smart_helmet.epro
├── tools/ # 工具配置
│ └── asr_project.hd # 天问Block工程
└── README.md # 项目总览和快速开始
更多推荐


所有评论(0)