单片机串口静默模式全解析:从原理到通信例程实战
本帖最后由 DKENNY 于 2025-5-15 16:19 编辑#技术资源# #申请原创#@21小跑堂
前言
哈喽!今天咱们来聊聊串口(UART/USART)里的一个“隐藏技能”——静默模式(Silent Mode 或 Mute Mode)。这个功能可能很多人没怎么用过,简单来说,它就像让串口“闭嘴听话”,特别适合多设备通信场景,能有效避免“抢话”导致的混乱。
今天我就给大家简单介绍一下这个功能,别说让你们马上精通,至少让第一次接触的朋友有个基本认识,哈哈。
那这里先过一下本文介绍的主要内容哈。
1. 串口静默模式是啥?像啥?
串口静默模式是单片机串口的一种特殊状态,让串口“只听不讲”:
- 正常模式:串口能“说”(通过TX引脚发送数据,比如打印调试信息到电脑)和“听”(通过RX引脚接收数据,比如收电脑的命令)。
- 静默模式:串口“闭嘴”,TX引脚不发送任何数据,但RX引脚还能正常接收数据,像个安静的“听众”。
举一个简单的生活例子:
- 我们在某微信群里时,正常可以发消息(TX)也能看消息(RX)。但群主说:“小明,你别发消息,只许看!” 这就是静默模式——你还能看到群消息(接收),但不能发(发送),就跟群禁言一样。
- 串口静默模式就像给TX按了“静音键”,单片机只收数据,不往外“吱声”。
2. 静默模式的工作原理:咋“闭嘴”的?
静默模式是串口硬件(UART模块)的一个功能,通过寄存器控制实现。
1. 正常串口通信
- UART有TX(发送)和RX(接收)两条线。
- 发送:程序把数据写入发送缓冲区,UART通过TX引脚输出电信号(高低电平)。
- 接收:外部数据通过RX引脚进入接收缓冲区,触发中断或由程序读取。
2. 进入静默模式
- 通过设置UART的控制寄存器,禁用TX功能。例如,APM32 的 USART_CTRL1 寄存器有个 RXMUTEEN 位(或类似标志),置1后:
- TX引脚停止输出信号(进入高阻态或固定电平)。
- 发送缓冲区的数据不会发送出去。
- RX功能不受影响,接收缓冲区照常工作,收到数据可触发中断或DMA。
3. 退出静默模式
- 清除 MUTE 位,恢复TX功能,串口又能“说”了。
- 某些场景下,硬件自动退出静默模式,比如收到特定数据帧(如地址匹配)。
- 在APM32中,静默模式常用于RS-485或多点通信协议(如Modbus RTU、DMX512)。MUTE 位控制驱动使能(DE)信号,防止TX输出干扰总线。
- 硬件可检测“空闲帧”(一段时间无数据)或“地址帧”来进入/退出静默模式。例如,收到匹配的地址字节后,UART自动清 MUTE 位。
3. 静默模式有啥用?啥时候需要?
静默模式主要解决多设备串口通信中的“乱说话”问题,还能省电或支持特定协议。
3.1 避免“抢话”:多设备通信
- 场景:多个单片机通过串口连到一条总线(像RS-485),就像一群人在一根电话线聊天。
- 问题:大家同时说话(发送数据),数据会撞车,乱成一团。
- 静默模式作用:
- 让从设备(从机)保持静默,只听主机命令。
- 主机发命令(比如“设备1,报状态!”),从机收到后检查是不是叫自己,叫到才退出静默模式回复。
- 例子:RS-485网络中,主机查询从机温度,只有被点名的从机发送数据,其他从机“闭嘴”。
3.2 省电:低功耗场景
- 场景:电池供电的设备(像物联网传感器)用串口通信,大部分时间不需要发送。
- 问题:发送数据激活TX电路,费电。
- 静默模式作用:
- 禁用TX,降低功耗,只接收外部命令。
- 收到特定命令(如唤醒)再恢复发送。
- 例子:一个无线传感器节点,通过串口接收主机查询,平时静默省电。
这种情况可以理解为我们的手机待机,只收消息不发,省电到飞起。
3.3 协议支持:地址匹配
- 场景:一些协议(如Modbus RTU、DMX512)要求设备只响应特定地址的命令。
- 问题:总线上数据很多,单片机得先判断“是不是叫我”,再决定回不回话。
- 静默模式作用:
- 单片机在静默模式下接收数据,检查数据帧的地址字段。
- 如果地址匹配(“是我!”),退出静默模式,发送响应;否则继续“装死”。
- 例子:在Modbus RTU网络中,从机收到主机命令,检查地址字节,只有匹配的从机回应。
像我们收快递时,快递员喊:“张伟,拿包裹!” 只有叫张伟的人才去签收,其他人不动。
3.4 调试:当“隐形人”
- 场景:开发时想让单片机“偷听”串口通信,但不干扰现有网络。
- 问题:单片机随便发送数据,可能会打乱其他设备的通信。
- 静默模式作用:
- 让单片机只接收数据,记录或分析总线上的通信,不发送任何数据。
- 适合调试多设备系统,观察协议是否正常。
- 例子:调试Modbus网络,临时加个单片机监听主机和从机的对话,分析数据包格式。
就像卧底一样,悄悄听别人聊天,自己一句话不说。
4. 静默模式的实现:硬件和软件咋搞?
静默模式的实现依赖UART硬件支持,软件配置寄存器控制。
4.1 APM32的静默模式配置
APM32的USART模块支持静默模式,通常用于RS-485或多点通信。配置步骤如下:
1. 初始化UART:
- 配置波特率、数据位、停止位等基本参数。
- 使能接收(RX),可选使能中断或DMA。
2. 进入静默模式:
- 设置`USART_CTRL1`寄存器的`RXMUTEEN`位(或硬件特定的地址匹配位)。
- TX功能禁用,RX继续工作。
3. 处理接收数据:
- 在中断或轮询中读取接收缓冲区,检查数据(比如地址字节)。
- 如果需要响应,清除`RXMUTEEN`位,启用TX发送数据。
4. 退出静默模式:
- 手动清`RXMUTEEN`位,或硬件自动退出(比如地址匹配)。
- 恢复正常通信。
技术细节:
- APM32的`RXMUTEEN`位在`USART_CTRL1`寄存器,地址匹配功能通过USART_CTRL2的`ADDR`字段设置。
- RS-485模式下,需控制驱动使能(DE)引脚,通常接外部RS-485收发器(如MAX485)。
- 硬件支持“空闲帧检测”(IDLE),可自动进入静默模式。
4.2 注意事项
- 缓冲区管理:静默模式下RX仍接收数据,确保接收缓冲区不溢出(用中断或DMA及时读取)。
- 总线电平:RS-485等半双工总线需注意电平冲突,静默模式配合DE信号控制。
- 协议兼容:确认协议是否需要静默模式(比如Modbus RTU需要,简单点对点通信不用)。
- 调试:用示波器或逻辑分析仪检查TX引脚,确认静默模式是否生效(TX无信号)。
其实,我们配置静默模式像给串口装个“开关”,关掉“麦克风”(TX),软件当“裁判”,决定啥时候开。
5. 串口通信例程:主机-从机带静默模式
我这里设计了一个串口通信例程,模拟RS-485总线的主机-从机通信,展示静默模式如何避免“抢话”。
5.1 实验场景
- 硬件:
- 两块APM32开发板(比如APM32F103或F407),一块作主机,一块作从机。
- RS-485模块(淘宝几十块,如MAX485),连接两板的USART2(PA2-TX,PA3-RX)。
- RS-485总线:A、B线连接两模块,GND共地。
- 电脑+逻辑分析仪,查看实际波形。
- 通信协议:
- 主机发送命令帧:[地址][命令][数据][校验](简单示例:1字节地址+1字节命令)。
- 从机在静默模式下接收,检查地址,匹配则退出静默模式,回复 [地址][状态]。
- 地址:主机0x00,从机0x01。
- 命令:0x10(查询状态)。
- 校验:简单累加和。
- 波特率:115200,8位数据,1位停止,无校验。
5.2 代码实现(APM32)
以下是主机和从机的完整代码,基于APM32F407(Keil)。
/* Includes */
#include "main.h"
#include "Board.h"
#include "stdio.h"
#include "apm32f4xx_gpio.h"
#include "apm32f4xx_adc.h"
#include "apm32f4xx_misc.h"
#include "apm32f4xx_usart.h"
#include "apm32f4xx_tmr.h"
// 定义角色(取消注释其中一个)
//#define Master
#define Slave// 当前为从机模式
/* printf using USART1*/
#define DEBUG_USARTUSART1
#define APM_COMInitAPM_TINY_COMInit
/* 初始化USART1,用于调试输出(连接串口助手) */
void USART1_Init()
{
/* USART1初始化 */
USART_Config_T usartConfigStruct;
/* 配置USART1参数 */
USART_ConfigStructInit(&usartConfigStruct);// 初始化默认配置
usartConfigStruct.baudRate = 115200; // 波特率115200
usartConfigStruct.mode = USART_MODE_TX_RX;// 收发模式
usartConfigStruct.parity = USART_PARITY_NONE;// 无奇偶校验
usartConfigStruct.stopBits = USART_STOP_BIT_1; // 1停止位
usartConfigStruct.wordLength = USART_WORD_LEN_8B; // 8位数据
usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE; // 无硬件流控制
/* 初始化COM1(对应USART1) */
APM_COMInit(COM1, &usartConfigStruct);
}
#ifdef Master
/* 主机模式:接收数据缓冲区和索引 */
volatile uint8_t rx_data, rx_idx = 0;
/* 初始化USART2,作为主机串口 */
void USART2_Init(void)
{
// 使能USART2和GPIOA时钟
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USART2);// 开启USART2时钟
RCM_EnableAHB2PeriphClock(RCM_AHB1_PERIPH_GPIOA); // 开启GPIOA时钟
// 配置PA2(TX)和PA3(RX)
GPIO_Config_T gpioConfigStruct;
gpioConfigStruct.mode = GPIO_MODE_AF; // 复用功能模式
gpioConfigStruct.otype = GPIO_OTYPE_PP; // 推挽输出
gpioConfigStruct.pin = GPIO_PIN_2 | GPIO_PIN_3; // PA2和PA3
gpioConfigStruct.pupd = GPIO_PUPD_NOPULL; // 无上下拉
gpioConfigStruct.speed = GPIO_SPEED_100MHz;// 高速
GPIO_Config(GPIOA, &gpioConfigStruct); // 应用GPIO配置
// 设置PA2和PA3的复用功能为USART2
GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_2, GPIO_AF_USART2); // PA2复用为USART2_TX
GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_3, GPIO_AF_USART2); // PA3复用为USART2_RX
// 配置USART2
USART_Config_T usartConfigStruct;
USART_ConfigStructInit(&usartConfigStruct);// 初始化默认配置
usartConfigStruct.baudRate = 115200; // 波特率115200
usartConfigStruct.mode = USART_MODE_TX_RX;// 收发模式
usartConfigStruct.parity = USART_PARITY_NONE;// 无奇偶校验
usartConfigStruct.stopBits = USART_STOP_BIT_1; // 1停止位
usartConfigStruct.wordLength = USART_WORD_LEN_8B; // 8位数据
usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE; // 无硬件流控制
USART_Config(USART2, &usartConfigStruct); // 应用USART2配置
USART_Enable(USART2); // 使能USART2
// 配置接收中断
NVIC_EnableIRQRequest(USART2_IRQn, 1, 1); // 使能USART2中断
USART_EnableInterrupt(USART2, USART_INT_RXBNE); // 使能接收非空中断
// 清除接收标志位,确保无残留状态
USART_ClearStatusFlag(USART2, USART_FLAG_RXBNE);
}
/* 发送单个字节通过USART2 */
void USART2_SendByte(uint8_t byte)
{
while (USART_ReadStatusFlag(USART2, USART_FLAG_TXBE) == RESET); // 等待发送缓冲区空
USART_TxData(USART2, byte); // 发送数据
}
/* 发送命令帧(地址+命令+校验) */
void SendCommand(uint8_t addr, uint8_t cmd)
{
uint8_t checksum = addr + cmd;// 计算校验和
USART2_SendByte(addr); // 发送地址
USART2_SendByte(cmd); // 发送命令
USART2_SendByte(checksum); // 发送校验和
}
/* 主机主函数 */
int main(void)
{
USART1_Init();// 初始化调试串口
USART2_Init();// 初始化主机串口
while (1)
{
SendCommand(0x01, 0x10); // 向从机(地址0x01)发送查询命令(0x10)
for (volatile int i = 0; i < 1000000; i++); // 简单延时
}
}
/* USART2中断处理函数 */
void USART2_IRQHandler(void)
{
if (USART_ReadIntFlag(USART2, USART_INT_RXBNE)) // 检查接收非空中断
{
rx_data = USART_RxData(USART2); // 读取接收数据
if (rx_idx >= 3) // 收到完整响应(地址+状态+校验)
{
printf("从机 0x%02X: 状态=0x%02X\n", rx_data, rx_data); // 打印从机响应
rx_idx = 0; // 重置接收索引
}
}
}
#elif defined Slave
/* 从机模式:接收数据缓冲区、索引和首次接收标志 */
volatile uint8_t rx_data, rx_idx = 0;
volatile uint8_t first_data_received = 0;
/* 初始化USART2,作为从机串口 */
void USART2_Init(void)
{
// 使能USART2和GPIOA时钟
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USART2); // 开启USART2时钟
RCM_EnableAHB2PeriphClock(RCM_AHB1_PERIPH_GPIOA);// 开启GPIOA时钟
// 配置PA2(TX)和PA3(RX)
GPIO_Config_T gpioConfigStruct;
gpioConfigStruct.mode = GPIO_MODE_AF; // 复用功能模式
gpioConfigStruct.otype = GPIO_OTYPE_PP; // 推挽输出
gpioConfigStruct.pin = GPIO_PIN_2 | GPIO_PIN_3; // PA2和PA3
gpioConfigStruct.pupd = GPIO_PUPD_NOPULL; // 无上下拉
gpioConfigStruct.speed = GPIO_SPEED_100MHz;// 高速
GPIO_Config(GPIOA, &gpioConfigStruct); // 应用GPIO配置
// 设置PA2和PA3的复用功能为USART2
GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_2, GPIO_AF_USART2); // PA2复用为USART2_TX
GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_3, GPIO_AF_USART2); // PA3复用为USART2_RX
// 配置USART2
USART_Config_T usartConfigStruct;
USART_ConfigStructInit(&usartConfigStruct);// 初始化默认配置
usartConfigStruct.baudRate = 115200; // 波特率115200
usartConfigStruct.mode = USART_MODE_TX_RX;// 收发模式
usartConfigStruct.parity = USART_PARITY_NONE;// 无奇偶校验
usartConfigStruct.stopBits = USART_STOP_BIT_1; // 1停止位
usartConfigStruct.wordLength = USART_WORD_LEN_8B; // 8位数据
usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE; // 无硬件流控制
USART_Config(USART2, &usartConfigStruct); // 应用USART2配置
USART_Enable(USART2); // 使能USART2
// 配置接收中断
NVIC_EnableIRQRequest(USART2_IRQn, 1, 1); // 使能USART2中断
USART_EnableInterrupt(USART2, USART_INT_RXBNE); // 使能接收非空中断
// 清除接收标志位,确保无残留状态
USART_ClearStatusFlag(USART2, USART_FLAG_RXBNE);
}
/* 发送单个字节通过USART2 */
void USART2_SendByte(uint8_t byte)
{
while (USART_ReadStatusFlag(USART2, USART_FLAG_TXBE) == RESET); // 等待发送缓冲区空
USART_TxData(USART2, byte); // 发送数据
}
/* 进入静默模式 */
void Enter_Silent_Mode(void)
{
if (!USART_ReadStatusFlag(USART2, USART_FLAG_RXBNE)) // 确保无未处理接收数据
{
USART_EnableMuteMode(USART2); // 启用静默模式
printf("从机:进入静默模式\n"); // 调试输出
}
}
/* 退出静默模式 */
void Exit_Silent_Mode(void)
{
USART_DisableMuteMode(USART2); // 禁用静默模式
USART2->CTRL1_B.TXEN = 1; // 使能发送
printf("从机:退出静默模式\n"); // 调试输出
}
/* USART2中断处理函数 */
void USART2_IRQHandler(void)
{
if (USART_ReadIntFlag(USART2, USART_INT_RXBNE)) // 检查接收非空中断
{
rx_data = USART_RxData(USART2); // 读取接收数据
USART_ClearIntFlag(USART2, USART_INT_RXBNE); // 清除接收中断标志
// 首次接收数据后进入静默模式
if (!first_data_received)
{
first_data_received = 1; // 标记首次接收
Enter_Silent_Mode(); // 进入静默模式
rx_idx = 0; // 重置接收索引
return;
}
if (rx_idx == 1 && rx_data != 0x01) // 检查地址(非本机地址0x01)
{
rx_idx = 0; // 重置接收索引
Enter_Silent_Mode(); // 重新进入静默模式
}
else if (rx_idx >= 3) // 收到完整命令(地址+命令+校验)
{
if (rx_data == 0x01 && rx_data == 0x10) // 验证地址和命令
{
uint8_t checksum = rx_data + rx_data; // 计算校验和
if (rx_data == checksum) // 校验通过
{
Exit_Silent_Mode(); // 退出静默模式
USART2_SendByte(0x01);// 发送地址
USART2_SendByte(0x11);// 发送状态
USART2_SendByte(0x01 + 0x11); // 发送校验和
Enter_Silent_Mode(); // 重新进入静默模式
}
}
rx_idx = 0; // 重置接收索引
}
}
}
/* 从机主函数 */
int main(void)
{
USART1_Init();// 初始化调试串口
USART2_Init();// 初始化从机串口
while (1)
{
// 空循环,中断处理通信
}
}
#endif
#if defined (__CC_ARM) || defined (__ICCARM__) || (defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050))
/*!
* @brief Redirect C Library function printf to serial port.
* After Redirection, you can use printf function.
*
* @param ch:The characters that need to be send.
*
* @param *f:pointer to a FILE that can recording all information
* needed to control a stream
*
* @retval The characters that need to be send.
*
* @note
*/
int fputc(int ch, FILE* f)
{
/* send a byte of data to the serial port */
USART_TxData(DEBUG_USART, (uint8_t)ch);
/* wait for the data to be send */
while (USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);
return (ch);
}
#elif defined (__GNUC__)
/*!
* @brief Redirect C Library function printf to serial port.
* After Redirection, you can use printf function.
*
* @param ch:The characters that need to be send.
*
* @retval The characters that need to be send.
*
* @note
*/
int __io_putchar(int ch)
{
/* send a byte of data to the serial port */
USART_TxData(DEBUG_USART, ch);
/* wait for the data to be send */
while (USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);
return ch;
}
/*!
* @brief Redirect C Library function printf to serial port.
* After Redirection, you can use printf function.
*
* @param file:Meaningless in this function.
*
* @param *ptr:Buffer pointer for data to be sent.
*
* @param len:Length of data to be sent.
*
* @retval The characters that need to be send.
*
* @note
*/
int _write(int file, char* ptr, int len)
{
int i;
for (i = 0; i < len; i++)
{
__io_putchar(*ptr++);
}
return len;
}
#else
#warning Not supported compiler type
#endif 简单提一下,我前面提到了配置设备地址时,是需要配置USART_CTRL2的ADDR这一位的,我这里并没有配置这个地址,是因为手册提到ADDR仅在多处理器通信的静默模式下生效。而我们的场景是单主机单从机,软件地址检查已足够,硬件匹配非必须。
5.3 运行效果
1. 烧录代码:
- 主机代码烧到一块APM32,连接RS-485模块和USB转串口(接电脑),逻辑分析仪。
- 从机代码烧到另一块APM32,连接RS-485模块。
2. 连接硬件:
- RS-485模块的A、B线连接两板,GND共地。
- 电脑打开串口助手(波特率115200),查看主机输出。
3. 运行:
- 主机每秒发送命令 (地址0x01,命令0x10,校验0x11)。
- 从机在静默模式下接收,检查地址0x01,匹配后退出静默模式,回复 (地址0x01,状态0x11,校验0x12)。
- 从机发完响应后重新进入静默模式。
- 串口助手显示:
From Slave 0x01: Status=0x11
From Slave 0x01: Status=0x11
- 逻辑分析仪也可抓取对应波形,如下。
6. 常见问题
Q1:为啥要用静默模式?不用行不行?
不用静默模式也可以,但多设备通信容易乱(数据冲突)。静默模式像“纪律委员”,让从机轮流说话,避免抢话。点对点通信(单片机连电脑)一般不用。
Q2:静默模式会不会漏收数据?
不会!静默模式只禁用TX,RX正常。只要缓冲区够大(或用中断及时读),数据不会丢。
Q3:怎么确认静默模式生效?
用示波器看TX引脚(静默时无信号,或固定高/低电平)。或加调试LED,进入静默时点亮。
Q4:从机为啥不一直静默?
从机需要响应主机命令,所以匹配地址后退出静默,发送数据。发完再静默,保持总线安静。
Q5:RS-485模块有啥注意事项?
确保A、B线接对(主机A连从机A),GND共地。RS-485是半双工,静默模式配合DE信号(驱动使能)防止冲突。
Q6:静默和休眠的区别
特性
静默模式
休眠模式
目标
禁用串口TX,保持RX,防冲突或支持协议
全局省电,CPU和外设停工
串口状态
TX禁用,RX正常
串口通常全关,可配置唤醒
CPU状态
正常运行
停止运行,等待唤醒
功耗
仅TX略省电,整体不变
极低(uA级)
典型场景
多设备通信(RS-485、Modbus)
低功耗待机(传感器)
7. 总结
- 静默模式是串口的“闭嘴”功能,禁用TX,只接收RX,适合多设备通信。
- 原理:通过寄存器(如APM32的`MUTE`位)控制TX禁用,RX正常,配合地址匹配。
- 用途:避免数据冲突、省电、支持协议(如Modbus)、调试监听。
- 例程:主机-从机通信展示了静默模式的实际效果,从机只在被点名时“开口”。
总结而言,静默模式是串口通信中一项关键的智能控制手段,它帮助设备在复杂的通信环境中实现精准、高效且安全的数据交换。
本文例程:
真棒,真棒 看下来 静默模式 更像是捂住耳朵 用dma更爽 这个硬件支持的静默模式,和其它 mcu 软件通过设置 tx gpio 为高阻有何区别?
建议极海 mcu 预置 cdbus 串口外设,和 can 一样支持仲裁,让 485 真正支持多主自由通讯 感谢分享!静默模式对于多设备通信确实非常有用,特别是在RS-485这样的总线上。能否详细解释一下如何在软件中配置和控制静默模式?
治愈糖果屋 发表于 2025-5-17 17:56
感谢分享!静默模式对于多设备通信确实非常有用,特别是在RS-485这样的总线上。能否详细解释一下如何在软件 ...
你可以看看手册上关于这部分的描述:
我这里针对于上面的例程,修改了一下从机的代码,用硬件匹配机制实现,并使用配置唤醒方式。#if defined Slave
/* 从机模式:接收数据缓冲区和索引 */
volatile uint8_t rx_data, rx_idx = 0;
/* 初始化USART3,作为从机串口(硬件地址匹配) */
void USART3_Init(void)
{
/* 使能USART3和GPIOB时钟 */
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USART3); // 开启USART3时钟
RCM_EnableAHB2PeriphClock(RCM_AHB1_PERIPH_GPIOB);// 开启GPIOB时钟
/* 配置PB10(TX)和PB11(RX) */
GPIO_Config_T gpioConfig;
gpioConfig.mode = GPIO_MODE_AF; // 复用功能模式
gpioConfig.otype = GPIO_OTYPE_PP; // 推挽输出
gpioConfig.pin = GPIO_PIN_10 | GPIO_PIN_11; // PB10-TX,PB11-RX
gpioConfig.pupd = GPIO_PUPD_NOPULL; // 无上下拉
gpioConfig.speed = GPIO_SPEED_50MHz;// 中速
GPIO_Config(GPIOB, &gpioConfig); // 应用GPIO配置
/* 设置PB10和PB11的复用功能为USART3 */
GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_10, GPIO_AF_USART3); // PB10复用为USART3_TX
GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_11, GPIO_AF_USART3); // PB11复用为USART3_RX
/* 配置USART3 */
USART_Config_T usartConfig;
USART_ConfigStructInit(&usartConfig);// 初始化默认配置
usartConfig.baudRate = 115200; // 波特率115200
usartConfig.mode = USART_MODE_RX; // 初始仅接收模式
usartConfig.parity = USART_PARITY_NONE;// 无奇偶校验
usartConfig.stopBits = USART_STOP_BIT_1; // 1停止位
usartConfig.wordLength = USART_WORD_LEN_8B; // 8位数据
usartConfig.hardwareFlow = USART_HARDWARE_FLOW_NONE; // 无硬件流控制
USART_Config(USART3, &usartConfig); // 应用USART3配置
/* 配置硬件地址匹配 */
USART3->CTRL2 |= (0x01 << 0); // 设置ADDR=0x01(从机地址)
USART3->CTRL1 |= USART_CTRL1_WUPMCFG; // 设置WUPMCFG=1(地址标记唤醒)
USART_Enable(USART3);// 使能USART3
/* 配置接收中断 */
NVIC_EnableIRQRequest(USART3_IRQn, 1, 1); // 使能USART3中断
USART_EnableInterrupt(USART3, USART_INT_RXBNE); // 使能接收非空中断
USART_ClearStatusFlag(USART3, USART_FLAG_RXBNE); // 清除接收标志
}
/* 发送单个字节通过USART3 */
void USART3_SendByte(uint8_t byte)
{
while (!USART_ReadStatusFlag(USART3, USART_FLAG_TXBE)); // 等待发送缓冲区空
USART_TxData(USART3, byte); // 发送数据
}
/* 进入静默模式 */
void Enter_Silent_Mode(void)
{
if (!USART_ReadStatusFlag(USART3, USART_FLAG_RXBNE)) // 确保无未处理接收数据
{
USART_EnableMuteMode(USART3); // 启用静默模式
printf("从机:进入静默模式\n"); // 调试输出
}
}
/* 从机主函数 */
int main(void)
{
USART1_Init();// 初始化调试串口
USART3_Init();// 初始化从机串口
/* 等待首次接收数据后进入静默模式 */
while (!USART_ReadStatusFlag(USART3, USART_FLAG_RXBNE)); // 轮询RXBNE
USART_RxData(USART3); // 读取并丢弃数据
USART_ClearStatusFlag(USART3, USART_FLAG_RXBNE); // 清除接收标志
Enter_Silent_Mode(); // 首次进入静默模式
while (1)
{
// 空循环,中断处理通信
}
}
/* USART3中断处理函数(硬件地址匹配) */
void USART3_IRQHandler(void)
{
if (USART_ReadIntFlag(USART3, USART_INT_RXBNE)) // 检查接收非空中断
{
rx_data = USART_RxData(USART3); // 读取接收数据
USART_ClearStatusFlag(USART3, USART_FLAG_RXBNE); // 清除接收标志
/* 硬件已匹配地址0x01,直接处理命令 */
if (rx_idx >= 3) // 收到完整命令(地址+命令+校验)
{
if (rx_data == 0x10) // 验证命令
{
uint8_t checksum = rx_data + rx_data; // 计算校验和
if (rx_data == checksum) // 校验通过
{
printf("从机:处理命令,地址=0x%02X\n", rx_data); // 调试输出
USART3->CTRL1_B.TXEN = 1; // 确保发送使能
USART3_SendByte(0x01);// 发送地址
USART3_SendByte(0x11);// 发送状态
USART3_SendByte(0x01 + 0x11); // 发送校验和
// 硬件将在下次非匹配地址时自动进入静默模式
}
}
rx_idx = 0; // 重置接收索引
}
}
}
#endif
看了半天好像没啥意义,数据接收发送不都有软件控制吗? 可以让串口只接收数据而不发送数据
页:
[1]