12下一页
返回列表 发新帖我要提问本帖赏金: 100.00元(功能说明)

[开发工具] 单片机串口静默模式全解析:从原理到通信例程实战

[复制链接]
 楼主| DKENNY 发表于 2025-5-15 16:21 | 显示全部楼层 |阅读模式
<
本帖最后由 DKENNY 于 2025-5-15 16:19 编辑

#技术资源# #申请原创#  @21小跑堂

前言

      哈喽!今天咱们来聊聊串口(UART/USART)里的一个“隐藏技能”——静默模式(Silent Mode 或 Mute Mode)。这个功能可能很多人没怎么用过,简单来说,它就像让串口“闭嘴听话”,特别适合多设备通信场景,能有效避免“抢话”导致的混乱。
      今天我就给大家简单介绍一下这个功能,别说让你们马上精通,至少让第一次接触的朋友有个基本认识,哈哈。
      那这里先过一下本文介绍的主要内容哈。
0f564ee2e5bbf090108997a2450750b4

1. 串口静默模式是啥?像啥?
       串口静默模式是单片机串口的一种特殊状态,让串口“只听不讲”:
        - 正常模式:串口能“说”(通过TX引脚发送数据,比如打印调试信息到电脑)和“听”(通过RX引脚接收数据,比如收电脑的命令)。
        - 静默模式:串口“闭嘴”,TX引脚不发送任何数据,但RX引脚还能正常接收数据,像个安静的“听众”。

      举一个简单的生活例子:
        - 我们在某微信群里时,正常可以发消息(TX)也能看消息(RX)。但群主说:“小明,你别发消息,只许看!” 这就是静默模式——你还能看到群消息(接收),但不能发(发送),就跟群禁言一样。
        - 串口静默模式就像给TX按了“静音键”,单片机只收数据,不往外“吱声”。
1515d087600338691bb07146f0a2a3c9

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 位。
8ac4ef448308d05453bcfebff98cac2b

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[3:0]`字段设置。
      - RS-485模式下,需控制驱动使能(DE)引脚,通常接外部RS-485收发器(如MAX485)。
      - 硬件支持“空闲帧检测”(IDLE),可自动进入静默模式。

4.2 注意事项
      - 缓冲区管理:静默模式下RX仍接收数据,确保接收缓冲区不溢出(用中断或DMA及时读取)。
      - 总线电平:RS-485等半双工总线需注意电平冲突,静默模式配合DE信号控制。
      - 协议兼容:确认协议是否需要静默模式(比如Modbus RTU需要,简单点对点通信不用)。
      - 调试:用示波器或逻辑分析仪检查TX引脚,确认静默模式是否生效(TX无信号)。

      其实,我们配置静默模式像给串口装个“开关”,关掉“麦克风”(TX),软件当“裁判”,决定啥时候开。

3be5af0d733454427efcf45be950f3ac

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位停止,无校验。

a839e5752936524762d176b0aaaab6e8

5.2 代码实现(APM32)
      以下是主机和从机的完整代码,基于APM32F407(Keil)。
  1. /* Includes */
  2. #include "main.h"
  3. #include "Board.h"
  4. #include "stdio.h"
  5. #include "apm32f4xx_gpio.h"
  6. #include "apm32f4xx_adc.h"
  7. #include "apm32f4xx_misc.h"
  8. #include "apm32f4xx_usart.h"
  9. #include "apm32f4xx_tmr.h"

  10. // 定义角色(取消注释其中一个)
  11. //#define Master
  12. #define Slave  // 当前为从机模式

  13. /* printf using USART1  */
  14. #define DEBUG_USART  USART1

  15. #define APM_COMInit  APM_TINY_COMInit

  16. /* 初始化USART1,用于调试输出(连接串口助手) */
  17. void USART1_Init()
  18. {
  19.     /* USART1初始化 */
  20.     USART_Config_T usartConfigStruct;

  21.     /* 配置USART1参数 */
  22.     USART_ConfigStructInit(&usartConfigStruct);  // 初始化默认配置
  23.     usartConfigStruct.baudRate = 115200;        // 波特率115200
  24.     usartConfigStruct.mode = USART_MODE_TX_RX;  // 收发模式
  25.     usartConfigStruct.parity = USART_PARITY_NONE;  // 无奇偶校验
  26.     usartConfigStruct.stopBits = USART_STOP_BIT_1; // 1停止位
  27.     usartConfigStruct.wordLength = USART_WORD_LEN_8B; // 8位数据
  28.     usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE; // 无硬件流控制

  29.     /* 初始化COM1(对应USART1) */
  30.     APM_COMInit(COM1, &usartConfigStruct);
  31. }

  32. #ifdef Master

  33. /* 主机模式:接收数据缓冲区和索引 */
  34. volatile uint8_t rx_data[10], rx_idx = 0;

  35. /* 初始化USART2,作为主机串口 */
  36. void USART2_Init(void)
  37. {
  38.     // 使能USART2和GPIOA时钟
  39.     RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USART2);  // 开启USART2时钟
  40.     RCM_EnableAHB2PeriphClock(RCM_AHB1_PERIPH_GPIOA);   // 开启GPIOA时钟

  41.     // 配置PA2(TX)和PA3(RX)
  42.     GPIO_Config_T gpioConfigStruct;
  43.     gpioConfigStruct.mode = GPIO_MODE_AF;       // 复用功能模式
  44.     gpioConfigStruct.otype = GPIO_OTYPE_PP;     // 推挽输出
  45.     gpioConfigStruct.pin = GPIO_PIN_2 | GPIO_PIN_3; // PA2和PA3
  46.     gpioConfigStruct.pupd = GPIO_PUPD_NOPULL;   // 无上下拉
  47.     gpioConfigStruct.speed = GPIO_SPEED_100MHz;  // 高速
  48.     GPIO_Config(GPIOA, &gpioConfigStruct);      // 应用GPIO配置

  49.     // 设置PA2和PA3的复用功能为USART2
  50.     GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_2, GPIO_AF_USART2); // PA2复用为USART2_TX
  51.     GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_3, GPIO_AF_USART2); // PA3复用为USART2_RX

  52.     // 配置USART2
  53.     USART_Config_T usartConfigStruct;
  54.     USART_ConfigStructInit(&usartConfigStruct);  // 初始化默认配置
  55.     usartConfigStruct.baudRate = 115200;        // 波特率115200
  56.     usartConfigStruct.mode = USART_MODE_TX_RX;  // 收发模式
  57.     usartConfigStruct.parity = USART_PARITY_NONE;  // 无奇偶校验
  58.     usartConfigStruct.stopBits = USART_STOP_BIT_1; // 1停止位
  59.     usartConfigStruct.wordLength = USART_WORD_LEN_8B; // 8位数据
  60.     usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE; // 无硬件流控制

  61.     USART_Config(USART2, &usartConfigStruct);   // 应用USART2配置
  62.     USART_Enable(USART2);                       // 使能USART2

  63.     // 配置接收中断
  64.     NVIC_EnableIRQRequest(USART2_IRQn, 1, 1);   // 使能USART2中断
  65.     USART_EnableInterrupt(USART2, USART_INT_RXBNE); // 使能接收非空中断

  66.     // 清除接收标志位,确保无残留状态
  67.     USART_ClearStatusFlag(USART2, USART_FLAG_RXBNE);
  68. }

  69. /* 发送单个字节通过USART2 */
  70. void USART2_SendByte(uint8_t byte)
  71. {
  72.     while (USART_ReadStatusFlag(USART2, USART_FLAG_TXBE) == RESET); // 等待发送缓冲区空
  73.     USART_TxData(USART2, byte); // 发送数据
  74. }

  75. /* 发送命令帧(地址+命令+校验) */
  76. void SendCommand(uint8_t addr, uint8_t cmd)
  77. {
  78.     uint8_t checksum = addr + cmd;  // 计算校验和
  79.     USART2_SendByte(addr);     // 发送地址
  80.     USART2_SendByte(cmd);      // 发送命令
  81.     USART2_SendByte(checksum); // 发送校验和
  82. }

  83. /* 主机主函数 */
  84. int main(void)
  85. {
  86.     USART1_Init();  // 初始化调试串口
  87.     USART2_Init();  // 初始化主机串口

  88.     while (1)
  89.     {
  90.         SendCommand(0x01, 0x10); // 向从机(地址0x01)发送查询命令(0x10)
  91.         for (volatile int i = 0; i < 1000000; i++); // 简单延时
  92.     }
  93. }

  94. /* USART2中断处理函数 */
  95. void USART2_IRQHandler(void)
  96. {
  97.     if (USART_ReadIntFlag(USART2, USART_INT_RXBNE)) // 检查接收非空中断
  98.     {
  99.         rx_data[rx_idx++] = USART_RxData(USART2); // 读取接收数据
  100.         if (rx_idx >= 3)   // 收到完整响应(地址+状态+校验)
  101.         {
  102.             printf("从机 0x%02X: 状态=0x%02X\n", rx_data[0], rx_data[1]); // 打印从机响应
  103.             rx_idx = 0; // 重置接收索引
  104.         }
  105.     }
  106. }

  107. #elif defined Slave

  108. /* 从机模式:接收数据缓冲区、索引和首次接收标志 */
  109. volatile uint8_t rx_data[10], rx_idx = 0;
  110. volatile uint8_t first_data_received = 0;

  111. /* 初始化USART2,作为从机串口 */
  112. void USART2_Init(void)
  113. {
  114.     // 使能USART2和GPIOA时钟
  115.     RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USART2); // 开启USART2时钟
  116.     RCM_EnableAHB2PeriphClock(RCM_AHB1_PERIPH_GPIOA);  // 开启GPIOA时钟

  117.     // 配置PA2(TX)和PA3(RX)
  118.     GPIO_Config_T gpioConfigStruct;
  119.     gpioConfigStruct.mode = GPIO_MODE_AF;       // 复用功能模式
  120.     gpioConfigStruct.otype = GPIO_OTYPE_PP;     // 推挽输出
  121.     gpioConfigStruct.pin = GPIO_PIN_2 | GPIO_PIN_3; // PA2和PA3
  122.     gpioConfigStruct.pupd = GPIO_PUPD_NOPULL;   // 无上下拉
  123.     gpioConfigStruct.speed = GPIO_SPEED_100MHz;  // 高速
  124.     GPIO_Config(GPIOA, &gpioConfigStruct);      // 应用GPIO配置

  125.     // 设置PA2和PA3的复用功能为USART2
  126.     GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_2, GPIO_AF_USART2); // PA2复用为USART2_TX
  127.     GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_3, GPIO_AF_USART2); // PA3复用为USART2_RX

  128.     // 配置USART2
  129.     USART_Config_T usartConfigStruct;
  130.     USART_ConfigStructInit(&usartConfigStruct);  // 初始化默认配置
  131.     usartConfigStruct.baudRate = 115200;        // 波特率115200
  132.     usartConfigStruct.mode = USART_MODE_TX_RX;  // 收发模式
  133.     usartConfigStruct.parity = USART_PARITY_NONE;  // 无奇偶校验
  134.     usartConfigStruct.stopBits = USART_STOP_BIT_1; // 1停止位
  135.     usartConfigStruct.wordLength = USART_WORD_LEN_8B; // 8位数据
  136.     usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE; // 无硬件流控制

  137.     USART_Config(USART2, &usartConfigStruct);   // 应用USART2配置
  138.     USART_Enable(USART2);                       // 使能USART2

  139.     // 配置接收中断
  140.     NVIC_EnableIRQRequest(USART2_IRQn, 1, 1);   // 使能USART2中断
  141.     USART_EnableInterrupt(USART2, USART_INT_RXBNE); // 使能接收非空中断

  142.     // 清除接收标志位,确保无残留状态
  143.     USART_ClearStatusFlag(USART2, USART_FLAG_RXBNE);
  144. }

  145. /* 发送单个字节通过USART2 */
  146. void USART2_SendByte(uint8_t byte)
  147. {
  148.     while (USART_ReadStatusFlag(USART2, USART_FLAG_TXBE) == RESET); // 等待发送缓冲区空
  149.     USART_TxData(USART2, byte); // 发送数据
  150. }

  151. /* 进入静默模式 */
  152. void Enter_Silent_Mode(void)
  153. {
  154.     if (!USART_ReadStatusFlag(USART2, USART_FLAG_RXBNE)) // 确保无未处理接收数据
  155.     {
  156.         USART_EnableMuteMode(USART2); // 启用静默模式
  157.         printf("从机:进入静默模式\n"); // 调试输出
  158.     }
  159. }

  160. /* 退出静默模式 */
  161. void Exit_Silent_Mode(void)
  162. {
  163.     USART_DisableMuteMode(USART2); // 禁用静默模式
  164.     USART2->CTRL1_B.TXEN = 1;      // 使能发送
  165.     printf("从机:退出静默模式\n"); // 调试输出
  166. }

  167. /* USART2中断处理函数 */
  168. void USART2_IRQHandler(void)
  169. {
  170.     if (USART_ReadIntFlag(USART2, USART_INT_RXBNE)) // 检查接收非空中断
  171.     {
  172.         rx_data[rx_idx++] = USART_RxData(USART2); // 读取接收数据
  173.         USART_ClearIntFlag(USART2, USART_INT_RXBNE); // 清除接收中断标志

  174.         // 首次接收数据后进入静默模式
  175.         if (!first_data_received)
  176.         {
  177.             first_data_received = 1; // 标记首次接收
  178.             Enter_Silent_Mode();     // 进入静默模式
  179.             rx_idx = 0;              // 重置接收索引
  180.             return;
  181.         }

  182.         if (rx_idx == 1 && rx_data[0] != 0x01)   // 检查地址(非本机地址0x01)
  183.         {
  184.             rx_idx = 0;              // 重置接收索引
  185.             Enter_Silent_Mode();     // 重新进入静默模式
  186.         }
  187.         else if (rx_idx >= 3)     // 收到完整命令(地址+命令+校验)
  188.         {
  189.             if (rx_data[0] == 0x01 && rx_data[1] == 0x10) // 验证地址和命令
  190.             {
  191.                 uint8_t checksum = rx_data[0] + rx_data[1]; // 计算校验和
  192.                 if (rx_data[2] == checksum) // 校验通过
  193.                 {
  194.                     Exit_Silent_Mode();     // 退出静默模式
  195.                     USART2_SendByte(0x01);  // 发送地址
  196.                     USART2_SendByte(0x11);  // 发送状态
  197.                     USART2_SendByte(0x01 + 0x11); // 发送校验和
  198.                     Enter_Silent_Mode();    // 重新进入静默模式
  199.                 }
  200.             }
  201.             rx_idx = 0; // 重置接收索引
  202.         }
  203.     }
  204. }

  205. /* 从机主函数 */
  206. int main(void)
  207. {
  208.     USART1_Init();  // 初始化调试串口
  209.     USART2_Init();  // 初始化从机串口

  210.     while (1)
  211.     {
  212.         // 空循环,中断处理通信
  213.     }
  214. }

  215. #endif


  216. #if defined (__CC_ARM) || defined (__ICCARM__) || (defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050))

  217. /*!
  218. * [url=home.php?mod=space&uid=247401]@brief[/url]       Redirect C Library function printf to serial port.
  219. *              After Redirection, you can use printf function.
  220. *
  221. * @param       ch:  The characters that need to be send.
  222. *
  223. * @param       *f:  pointer to a FILE that can recording all information
  224. *              needed to control a stream
  225. *
  226. * @retval      The characters that need to be send.
  227. *
  228. * @note
  229. */
  230. int fputc(int ch, FILE* f)
  231. {
  232.     /* send a byte of data to the serial port */
  233.     USART_TxData(DEBUG_USART, (uint8_t)ch);

  234.     /* wait for the data to be send */
  235.     while (USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);

  236.     return (ch);
  237. }

  238. #elif defined (__GNUC__)

  239. /*!
  240. * [url=home.php?mod=space&uid=247401]@brief[/url]       Redirect C Library function printf to serial port.
  241. *              After Redirection, you can use printf function.
  242. *
  243. * @param       ch:  The characters that need to be send.
  244. *
  245. * @retval      The characters that need to be send.
  246. *
  247. * @note
  248. */
  249. int __io_putchar(int ch)
  250. {
  251.     /* send a byte of data to the serial port */
  252.     USART_TxData(DEBUG_USART, ch);

  253.     /* wait for the data to be send */
  254.     while (USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);

  255.     return ch;
  256. }

  257. /*!
  258. * [url=home.php?mod=space&uid=247401]@brief[/url]       Redirect C Library function printf to serial port.
  259. *              After Redirection, you can use printf function.
  260. *
  261. * @param       file:  Meaningless in this function.
  262. *
  263. * @param       *ptr:  Buffer pointer for data to be sent.
  264. *
  265. * @param       len:  Length of data to be sent.
  266. *
  267. * @retval      The characters that need to be send.
  268. *
  269. * @note
  270. */
  271. int _write(int file, char* ptr, int len)
  272. {
  273.     int i;

  274.     for (i = 0; i < len; i++)
  275.     {
  276.         __io_putchar(*ptr++);
  277.     }

  278.     return len;
  279. }

  280. #else
  281. #warning Not supported compiler type
  282. #endif
      简单提一下,我前面提到了配置设备地址时,是需要配置USART_CTRL2的ADDR这一位的,我这里并没有配置这个地址,是因为手册提到ADDR[3:0]仅在多处理器通信的静默模式下生效。而我们的场景是单主机单从机,软件地址检查已足够,硬件匹配非必须。

5.3 运行效果
      1. 烧录代码:
         - 主机代码烧到一块APM32,连接RS-485模块和USB转串口(接电脑),逻辑分析仪。
         - 从机代码烧到另一块APM32,连接RS-485模块。
      2. 连接硬件:
         - RS-485模块的A、B线连接两板,GND共地。
         - 电脑打开串口助手(波特率115200),查看主机输出。
      3. 运行
         - 主机每秒发送命令 [0x01][0x10][0x11](地址0x01,命令0x10,校验0x11)。
         - 从机在静默模式下接收,检查地址0x01,匹配后退出静默模式,回复 [0x01][0x11][0x12](地址0x01,状态0x11,校验0x12)。
         - 从机发完响应后重新进入静默模式。
         - 串口助手显示:
From Slave 0x01: Status=0x11
From Slave 0x01: Status=0x11

     - 逻辑分析仪也可抓取对应波形,如下。
c5c811d6283b75526e5ccf10c22569a8

4c7c714a398e5f57d015f921bf39101c

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)、调试监听。
      - 例程:主机-从机通信展示了静默模式的实际效果,从机只在被点名时“开口”。
      总结而言,静默模式是串口通信中一项关键的智能控制手段,它帮助设备在复杂的通信环境中实现精准、高效且安全的数据交换。

本文例程: USART_MUTE_EXAMPLE.zip (779.17 KB, 下载次数: 4)



打赏榜单

21小跑堂 打赏了 100.00 元 2025-05-20
理由:恭喜通过原创审核!期待您更多的原创作品~~

评论

灵活使用串口的静默模式,提升代码的健壮性,作者细致的描述了串口静默模式的相关概念和相关应用,并加以实例演示,整体较佳。  发表于 2025-5-20 18:01
发光的梦 发表于 2025-5-15 18:16 | 显示全部楼层
真棒,真棒
和谐智者 发表于 2025-5-16 23:25 | 显示全部楼层
看下来 静默模式 更像是捂住耳朵
zjsx8192 发表于 2025-5-17 09:44 | 显示全部楼层
用dma更爽
dukedz 发表于 2025-5-17 11:07 | 显示全部楼层
这个硬件支持的静默模式,和其它 mcu 软件通过设置 tx gpio 为高阻有何区别?

建议极海 mcu 预置 cdbus 串口外设,和 can 一样支持仲裁,让 485 真正支持多主自由通讯
治愈糖果屋 发表于 2025-5-17 17:56 | 显示全部楼层
感谢分享!静默模式对于多设备通信确实非常有用,特别是在RS-485这样的总线上。能否详细解释一下如何在软件中配置和控制静默模式?
 楼主| DKENNY 发表于 2025-5-19 13:40 | 显示全部楼层
治愈糖果屋 发表于 2025-5-17 17:56
感谢分享!静默模式对于多设备通信确实非常有用,特别是在RS-485这样的总线上。能否详细解释一下如何在软件 ...

你可以看看手册上关于这部分的描述: 38720682ac422e9f3e.png

我这里针对于上面的例程,修改了一下从机的代码,用硬件匹配机制实现,并使用配置唤醒方式。
  1. #if defined Slave

  2. /* 从机模式:接收数据缓冲区和索引 */
  3. volatile uint8_t rx_data[10], rx_idx = 0;

  4. /* 初始化USART3,作为从机串口(硬件地址匹配) */
  5. void USART3_Init(void)
  6. {
  7.     /* 使能USART3和GPIOB时钟 */
  8.     RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USART3); // 开启USART3时钟
  9.     RCM_EnableAHB2PeriphClock(RCM_AHB1_PERIPH_GPIOB);  // 开启GPIOB时钟

  10.     /* 配置PB10(TX)和PB11(RX) */
  11.     GPIO_Config_T gpioConfig;
  12.     gpioConfig.mode = GPIO_MODE_AF;       // 复用功能模式
  13.     gpioConfig.otype = GPIO_OTYPE_PP;     // 推挽输出
  14.     gpioConfig.pin = GPIO_PIN_10 | GPIO_PIN_11; // PB10-TX,PB11-RX
  15.     gpioConfig.pupd = GPIO_PUPD_NOPULL;   // 无上下拉
  16.     gpioConfig.speed = GPIO_SPEED_50MHz;  // 中速
  17.     GPIO_Config(GPIOB, &gpioConfig);      // 应用GPIO配置

  18.     /* 设置PB10和PB11的复用功能为USART3 */
  19.     GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_10, GPIO_AF_USART3); // PB10复用为USART3_TX
  20.     GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_11, GPIO_AF_USART3); // PB11复用为USART3_RX

  21.     /* 配置USART3 */
  22.     USART_Config_T usartConfig;
  23.     USART_ConfigStructInit(&usartConfig);  // 初始化默认配置
  24.     usartConfig.baudRate = 115200;        // 波特率115200
  25.     usartConfig.mode = USART_MODE_RX;     // 初始仅接收模式
  26.     usartConfig.parity = USART_PARITY_NONE;  // 无奇偶校验
  27.     usartConfig.stopBits = USART_STOP_BIT_1; // 1停止位
  28.     usartConfig.wordLength = USART_WORD_LEN_8B; // 8位数据
  29.     usartConfig.hardwareFlow = USART_HARDWARE_FLOW_NONE; // 无硬件流控制
  30.     USART_Config(USART3, &usartConfig);   // 应用USART3配置

  31.     /* 配置硬件地址匹配 */
  32.     USART3->CTRL2 |= (0x01 << 0); // 设置ADDR[3:0]=0x01(从机地址)
  33.     USART3->CTRL1 |= USART_CTRL1_WUPMCFG; // 设置WUPMCFG=1(地址标记唤醒)

  34.     USART_Enable(USART3);  // 使能USART3

  35.     /* 配置接收中断 */
  36.     NVIC_EnableIRQRequest(USART3_IRQn, 1, 1);   // 使能USART3中断
  37.     USART_EnableInterrupt(USART3, USART_INT_RXBNE); // 使能接收非空中断
  38.     USART_ClearStatusFlag(USART3, USART_FLAG_RXBNE); // 清除接收标志
  39. }

  40. /* 发送单个字节通过USART3 */
  41. void USART3_SendByte(uint8_t byte)
  42. {
  43.     while (!USART_ReadStatusFlag(USART3, USART_FLAG_TXBE)); // 等待发送缓冲区空
  44.     USART_TxData(USART3, byte); // 发送数据
  45. }

  46. /* 进入静默模式 */
  47. void Enter_Silent_Mode(void)
  48. {
  49.     if (!USART_ReadStatusFlag(USART3, USART_FLAG_RXBNE)) // 确保无未处理接收数据
  50.     {
  51.         USART_EnableMuteMode(USART3); // 启用静默模式
  52.         printf("从机:进入静默模式\n"); // 调试输出
  53.     }
  54. }

  55. /* 从机主函数 */
  56. int main(void)
  57. {
  58.     USART1_Init();  // 初始化调试串口
  59.     USART3_Init();  // 初始化从机串口

  60.     /* 等待首次接收数据后进入静默模式 */
  61.     while (!USART_ReadStatusFlag(USART3, USART_FLAG_RXBNE)); // 轮询RXBNE
  62.     USART_RxData(USART3); // 读取并丢弃数据
  63.     USART_ClearStatusFlag(USART3, USART_FLAG_RXBNE); // 清除接收标志
  64.     Enter_Silent_Mode(); // 首次进入静默模式

  65.     while (1)
  66.     {
  67.         // 空循环,中断处理通信
  68.     }
  69. }

  70. /* USART3中断处理函数(硬件地址匹配) */
  71. void USART3_IRQHandler(void)
  72. {
  73.     if (USART_ReadIntFlag(USART3, USART_INT_RXBNE)) // 检查接收非空中断
  74.     {
  75.         rx_data[rx_idx++] = USART_RxData(USART3); // 读取接收数据
  76.         USART_ClearStatusFlag(USART3, USART_FLAG_RXBNE); // 清除接收标志

  77.         /* 硬件已匹配地址0x01,直接处理命令 */
  78.         if (rx_idx >= 3) // 收到完整命令(地址+命令+校验)
  79.         {
  80.             if (rx_data[1] == 0x10) // 验证命令
  81.             {
  82.                 uint8_t checksum = rx_data[0] + rx_data[1]; // 计算校验和
  83.                 if (rx_data[2] == checksum) // 校验通过
  84.                 {
  85.                     printf("从机:处理命令,地址=0x%02X\n", rx_data[0]); // 调试输出
  86.                     USART3->CTRL1_B.TXEN = 1; // 确保发送使能
  87.                     USART3_SendByte(0x01);  // 发送地址
  88.                     USART3_SendByte(0x11);  // 发送状态
  89.                     USART3_SendByte(0x01 + 0x11); // 发送校验和
  90.                     // 硬件将在下次非匹配地址时自动进入静默模式
  91.                 }
  92.             }
  93.             rx_idx = 0; // 重置接收索引
  94.         }
  95.     }
  96. }

  97. #endif



Unarty 发表于 2025-6-23 15:39 | 显示全部楼层
看了半天好像没啥意义,数据接收发送不都有软件控制吗?
uiint 发表于 2025-7-2 11:27 | 显示全部楼层
可以让串口只接收数据而不发送数据
zerorobert 发表于 2025-7-3 14:01 | 显示全部楼层
在RS-485或Modbus RTU网络中,主机查询从机时,仅被寻址的从机退出静默模式回复数据,其他从机保持静默以避免总线冲突。
janewood 发表于 2025-7-3 17:52 | 显示全部楼层
静默模式下RX仍接收数据,需通过中断或DMA及时读取接收缓冲区,防止溢出。
everyrobin 发表于 2025-7-3 20:45 | 显示全部楼层
正确配置UART控制寄存器以启用和禁用静默模式,确保不会影响其他UART功能。
chenci2013 发表于 2025-7-3 22:39 | 显示全部楼层
在静默模式下,单片机的串口模块可以接收数据并处理它,但不会将接收到的数据发送回TX线路上。
loutin 发表于 2025-7-4 11:42 | 显示全部楼层
在RS-485等半双工总线中,静默模式需配合驱动使能(DE)信号控制,防止TX输出干扰总线。
houjiakai 发表于 2025-7-4 13:35 | 显示全部楼层
并非所有单片机都支持串口静默模式,需要查阅特定单片机的文档来确认是否支持该功能。
phoenixwhite 发表于 2025-7-4 18:08 | 显示全部楼层
可以有效避免数据冲突和混乱。              
earlmax 发表于 2025-7-4 19:55 | 显示全部楼层
静默模式的实现依赖于UART硬件的支持,通过寄存器控制实现
天鹅绒之夜 发表于 2025-7-4 20:48 | 显示全部楼层
这个静默模式真好用。
印象中STM32没有这个功能啊
jkl21 发表于 2025-7-5 22:21 | 显示全部楼层
支持内部环回测试功能              
zerorobert 发表于 2025-7-6 09:33 | 显示全部楼层
DMX512、CANopen等协议要求设备按地址响应。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

59

主题

104

帖子

16

粉丝
快速回复 在线客服 返回列表 返回顶部

59

主题

104

帖子

16

粉丝
快速回复 在线客服 返回列表 返回顶部