3、MCU选型与架构:常见光模块MCU(STM32/EFM8/MSP430)、内部资源(Flash/RAM/定时器)、外设接口(I2C/SPI/UART/GPIO)
做光模块固件开发,选MCU是第一道坎。
说实话,市面上能用的MCU太多了,但真正适合光模块的,翻来覆去就那么几款。我这些年经手过的项目,从10G到400G,主控芯片基本没跳出过STM32、EFM8和MSP430这三个家族。今天咱们就掰开揉碎聊聊,这三款芯片到底该怎么选,内部资源怎么用,外设接口怎么搭。
3.1 三大主流MCU:谁是你的菜?
先看一张对比表,心里有个底:
| 特性 | STM32(F0/F1/L0系列) | EFM8(Silicon Labs) | MSP430(TI) |
|---|---|---|---|
| 内核 | Cortex-M0/M3 | 8051(增强型) | 16位RISC |
| 主频 | 48-72MHz | 24-50MHz | 16-25MHz |
| Flash | 16-256KB | 8-64KB | 1-128KB |
| RAM | 4-32KB | 0.5-4KB | 0.5-8KB |
| 功耗 | 中等(μA级待机) | 低(nA级待机) | 极低(nA级待机) |
| 开发难度 | 中等(HAL库) | 简单(寄存器操作) | 中等(DriverLib) |
| 典型光模块 | 100G/400G DSP方案 | SFP+/QSFP+ 低速模块 | 低功耗/工业级模块 |
STM32——我最常用的主力。Cortex-M0内核,性能够用,外设丰富。做100G以上模块时,DSP芯片需要配合一个主控MCU做管理,STM32F0系列是我的首选。为什么?因为它的I2C接口支持400kHz快速模式,处理DDM数据时不会卡顿。
EFM8——老朋友了。8051内核的现代版,Silicon Labs专门为光模块优化的。我记得2018年做SFP28项目时,EFM8BB10F8G这颗芯片,8KB Flash、0.5KB RAM,硬是跑完了完整的SFF-8472协议栈。它的优势是:外设集成度高,内部有精密振荡器,省掉外部晶振,BOM成本能降0.3美金。
MSP430——低功耗之王。TI的16位架构,待机电流低到0.1μA。我有个客户做工业级光模块,要求-40°C到85°C全温范围,功耗不能超过50mW。MSP430G2553配合LDO,整板功耗控制在35mW,稳得很。
选型口诀:
- 要性能、要生态 → STM32
- 要成本、要简单 → EFM8
- 要低功耗、要稳定 → MSP430
3.2 内部资源:Flash、RAM、定时器怎么分配?
光模块固件对资源的要求,其实挺苛刻的。你想想看,一个模块里要跑I2C从机协议、DDM监控、激光器控制、告警处理,还得留空间给升级功能。资源分配不好,后面全是坑。
3.2.1 Flash:代码和数据的家
Flash主要放三样东西:程序代码、常量表、校准参数。
- 程序代码:一般占60%-70%。我习惯把协议栈(SFF-8472/8636)放在固定区域,方便后续升级。
- 常量表:比如温度补偿表、APC曲线表。这些表用const声明,放在Flash里,不占RAM。
- 校准参数:每个模块出厂时都要写校准值。注意!校准参数必须放在独立的Flash页里,方便擦写。我曾经遇到过,把校准参数和代码混在一起,结果升级固件时把校准值也擦掉了,整批模块报废……
避坑指南:
我曾经在STM32F030上做批量升级,发现Flash擦写次数超过10万次后,某些页会失效。后来我改用双备份机制——主区和备份区各存一份校准参数,写之前先校验,写之后回读确认。从此再没出过问题。
3.2.2 RAM:寸土寸金
光模块的RAM通常很小,EFM8才0.5KB,MSP430也就2KB。怎么省着用?
- 全局变量:能省则省。我习惯把所有状态标志位用bit field打包,一个字节存8个标志。
- 堆栈:这是最容易爆的地方。中断嵌套深了,栈溢出直接死机。我建议中断服务函数里只设标志位,具体处理放到主循环。
- DMA缓冲区:I2C和SPI的收发缓冲区,尽量复用。比如发送缓冲区和接收缓冲区共用一块内存,用乒乓操作。
// 示例:EFM8上RAM极度紧张时的变量打包技巧
// 一个字节存8个状态标志
typedef union {
uint8_t all;
struct {
uint8_t tx_ready : 1;
uint8_t rx_pending : 1;
uint8_t alarm_los : 1;
uint8_t alarm_tx_fault : 1;
uint8_t ddm_updated : 1;
uint8_t fw_updating : 1;
uint8_t reserved1 : 1;
uint8_t reserved2 : 1;
} bits;
} StatusFlags_t;
// 全局只用一个字节
StatusFlags_t g_status = {0};
3.2.3 定时器:光模块的节拍器
定时器在光模块里太重要了。我一般至少用三个:
- 定时器0:1ms系统滴答,用于DDM数据采集。每1ms读一次ADC,更新温度、电压、偏置电流、发射功率。
- 定时器1:100ms轮询,用于告警检测。比如LOS(信号丢失)状态,100ms扫一次就够了。
- 定时器2:PWM输出,控制激光器的偏置电流。这个频率要高,一般用20kHz以上,避免人眼可见的闪烁。
我的习惯:
定时器中断里不要做复杂运算。我见过有人把温度补偿算法放在定时器中断里,结果中断执行时间超过定时周期,系统直接卡死。正确的做法是:中断里只设标志,主循环里处理。
3.3 外设接口:I2C、SPI、UART、GPIO
光模块和外界通信,全靠这几个接口。每个接口都有它的脾气。
3.3.1 I2C:光模块的命脉
I2C是光模块最重要的接口。主机(交换机)通过I2C读取模块的DDM信息,写入配置参数。标准速率100kHz,快速模式400kHz。
我踩过最大的坑是I2C时钟拉伸。有些从机芯片(比如DSP)会拉低SCL线,要求主机等待。如果MCU的I2C外设不支持时钟拉伸,通信就会超时。STM32的I2C外设默认支持,但EFM8的某些型号需要软件模拟。
// 示例:STM32 I2C初始化(快速模式400kHz)
void I2C_Init(void) {
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
// 配置GPIO:PB6-SCL, PB7-SDA
GPIOB->AFR[0] |= (4 << 24) | (4 << 28); // AF4
GPIOB->MODER |= (2 << 12) | (2 << 14); // Alternate function
// I2C时序配置:PCLK=48MHz,目标400kHz
I2C1->TIMINGR = 0x00201D2B; // 这个值要根据时钟算
I2C1->CR1 |= I2C_CR1_PE; // 使能I2C
}
3.3.2 SPI:高速数据通道
SPI在光模块里主要用于和DSP芯片通信。速率可以跑到10MHz以上,比I2C快得多。但SPI的缺点是线多——至少4根(SCK、MOSI、MISO、CS)。
我个人习惯用SPI的DMA模式。比如从DSP读取眼图数据,一次要传几千个字节,用CPU轮询太浪费。DMA传输时CPU可以干别的事,比如处理告警。
SPI vs I2C 怎么选?
- 需要高速批量数据 → SPI(比如读DSP寄存器、下载固件)
- 需要多从机、线少 → I2C(比如读温度传感器、EEPROM)
- 引脚不够用 → I2C(只用2根线)
3.3.3 UART:调试利器
UART在量产模块里一般不启用,但开发阶段必不可少。我每个项目都会预留一个UART口,用115200波特率,输出调试信息。
这里有个技巧:用UART实现命令行交互。我写过一个简单的shell,支持set/get命令,调试时直接敲命令读写寄存器,比用烧录器方便多了。
// 伪代码:UART命令行解析
void UART_ProcessCmd(char *cmd) {
if (strncmp(cmd, "get", 3) == 0) {
uint8_t addr = (uint8_t)atoi(cmd + 4);
uint8_t val = ReadRegister(addr);
printf("Reg[0x%02X] = 0x%02X\r\n", addr, val);
} else if (strncmp(cmd, "set", 3) == 0) {
uint8_t addr = (uint8_t)atoi(cmd + 4);
uint8_t val = (uint8_t)atoi(cmd + 7);
WriteRegister(addr, val);
printf("Set OK\r\n");
}
}
3.3.4 GPIO:最灵活的外设
GPIO看似简单,但用好了能解决大问题。光模块里GPIO的典型用途:
- TX_DISABLE:输出,控制激光器关断。上电时必须拉高,防止模块未初始化时激光器误亮。
- TX_FAULT:输入,检测激光器故障。我习惯用外部中断触发,一旦拉低立即关断激光器。
- LOS:输入,检测信号丢失。这个信号需要去抖,一般用定时器延时10ms再确认。
- MOD_DEF0:I2C地址选择。通过GPIO电平决定模块的I2C地址是0xA0还是0xA2。
注意:
GPIO的上下拉电阻一定要配好。我曾经在EFM8上遇到TX_FAULT引脚悬空,结果上电时电平不确定,导致激光器误关断。后来我在外部加了10kΩ上拉电阻,问题解决。
3.4 知识体系总览
下面这张图,把MCU选型、内部资源、外设接口的关系串起来了。你看完应该能对光模块的MCU架构有个整体认识。
嗯,MCU选型这块,说到底就是平衡的艺术。性能高了浪费,低了不够用。我建议新手先从EFM8入手,寄存器少,上手快。等把光模块协议跑通了,再换STM32做复杂项目。至于MSP430,如果你做的是工业级或者电池供电的模块,它会是你的好搭档。
下一节咱们聊固件架构设计,我会分享一个我自己用了五年的分层架构模板,保证代码好维护、好移植。