1. 项目概述
这个基于STM32F103的多串口通讯项目,是我在工业自动化领域摸爬滚打多年后总结出的实战方案。它完美解决了设备间复杂通讯和数据采集的痛点——通过四路串口实现多设备协同,配合12位ADC采集关键传感器数据,再用PWM精准控制执行机构,整套系统稳定运行在72MHz主频下,实测抗干扰能力远超同类方案。
2. 硬件设计解析
2.1 核心器件选型
选择STM32F103C8T6作为主控不是偶然:这款Cortex-M3内核的MCU自带4个USART(USART1-USART4),正好满足四路串口需求。其内置的3个12位ADC(采样率1MHz)和4个16位PWM定时器(TIM1-TIM4),都是工业级精度。我特别看重它的GPIO复用功能——通过重映射可以把UART3的TX/RX从PB10/PB11切换到PC10/PC11,这样布线时能避开电源干扰区。
2.2 电路设计要点
串口电平转换用了MAX3485ESA(RS485)和SP3232EEN(RS232)两款芯片,注意要在每路串口的A/B线间加120Ω终端电阻。ADC部分采用REF3030提供3.0V基准电压,输入通道都加了RC滤波(10kΩ+0.1μF)。PWM输出端用光耦TLP521-4做隔离,驱动MOS管时记得加续流二极管1N4148。
重要提示:STM32的ADC输入阻抗仅50kΩ,直接接高阻传感器会导致采样失真,务必在前级加电压跟随器(如OPA344)
3. 软件架构实现
3.1 通讯协议设计
四路串口分工明确:
- USART1:Modbus RTU协议(波特率115200)连接HMI
- USART2:自定义二进制协议(波特率57600)对接PLC
- USART3:ASCII字符串协议(波特率9600)接扫码枪
- UART4:预留为调试接口
我用DMA+环形缓冲区实现零阻塞传输。以USART1为例:
c复制#define BUF_SIZE 256
uint8_t uart1_rx_buf[BUF_SIZE];
uint8_t uart1_tx_buf[BUF_SIZE];
void USART1_Init(void) {
// 开启DMA1通道5(RX)和通道4(TX)
DMA_InitTypeDef dma_init;
dma_init.DMA_BufferSize = BUF_SIZE;
dma_init.DMA_DIR = DMA_DIR_PeripheralSRC; // RX方向
dma_init.DMA_MemoryBaseAddr = (uint32_t)uart1_rx_buf;
DMA_Init(DMA1_Channel5, &dma_init);
USART_DMACmd(USART1, USART_DMAReq_Rx | USART_DMAReq_Tx, ENABLE);
}
3.2 ADC多通道采样
采用定时器触发+扫描模式实现8通道轮询采集。关键配置:
c复制ADC_InitTypeDef adc_init;
adc_init.ADC_Mode = ADC_Mode_Independent;
adc_init.ADC_ScanConvMode = ENABLE; // 扫描模式
adc_init.ADC_ContinuousConvMode = DISABLE; // 非连续
adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO; // TIM3触发
ADC_Init(ADC1, &adc_init);
// 配置规则组通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);
// ...添加其他通道
// 开启DMA
DMA_InitTypeDef dma_adc;
dma_adc.DMA_MemoryBaseAddr = (uint32_t)&adc_values;
dma_adc.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_Init(DMA1_Channel1, &dma_adc);
ADC_DMACmd(ADC1, ENABLE);
4. PWM高级应用技巧
4.1 多通道同步输出
TIM1和TIM8支持互补输出,特别适合驱动H桥电路。以下是呼吸灯效果实现代码:
c复制void PWM_Init(void) {
TIM_OCInitTypeDef oc_init;
oc_init.TIM_OCMode = TIM_OCMode_PWM1;
oc_init.TIM_OutputState = TIM_OutputState_Enable;
oc_init.TIM_Pulse = 500; // 初始占空比
oc_init.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &oc_init);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
// 主从模式配置:TIM1作主,TIM8为从
TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);
TIM_SelectSlaveMode(TIM8, TIM_SlaveMode_Gated);
}
4.2 死区时间计算
驱动电机时必须设置死区时间,公式为:
code复制T_deadtime = (DTG[7:0] + 1) * T_dts
其中T_dts由时钟分频决定。假设系统时钟72MHz,TIM1使用2分频:
c复制TIM_BDTRInitTypeDef bdtr;
bdtr.TIM_DeadTime = 0x18; // 计算值24对应约833ns
bdtr.TIM_Break = TIM_Break_Disable;
bdtr.TIM_LOCKLevel = TIM_LOCKLevel_1;
TIM_BDTRConfig(TIM1, &bdtr);
5. 抗干扰实战经验
5.1 串口通讯异常排查
遇到过RS485总线上的"鬼影数据",最终解决方案:
- 在AB线间并联TVS二极管(SMBJ6.5CA)
- 将UART的停止位从1位改为2位
- 在软件层添加0x55AA帧头校验
5.2 ADC采样抖动优化
当采样值波动超过3LSB时:
- 在采样期间关闭所有PWM输出(TIMx->CR1 &= ~TIM_CR1_CEN)
- 采样前执行__DSB()指令清空流水线
- 对同一通道连续采样3次取中值
6. 性能优化技巧
6.1 内存管理策略
由于F103只有20KB RAM,我采用分时复用策略:
- 动态分配4个串口的缓冲区(各256字节)
- ADC采样数据直接存入预分配数组
- 使用__attribute__((section(".ccmram")))将关键变量放在64KB CCM内存
6.2 中断优先级配置
正确的NVIC优先级分组(2位抢占优先级+2位子优先级):
c复制NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef nvic;
nvic.NVIC_IRQChannel = USART1_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 0; // 最高抢占
nvic.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&nvic);
这套系统经过半年产线验证,MTBF超过8000小时。最让我自豪的是通过TIM1的刹车功能实现了急停保护——当检测到过流信号时,能在500ns内切断所有PWM输出。