发新帖本帖赏金 100.00元(功能说明)我要提问
返回列表
打印
[开发工具]

单片机串口静默模式全解析:从原理到通信例程实战

[复制链接]
1754|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
DKENNY|  楼主 | 2025-5-15 16:21 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 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[3:0]`字段设置。
      - 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_USART  USART1

#define APM_COMInit  APM_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[10], 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[rx_idx++] = USART_RxData(USART2); // 读取接收数据
        if (rx_idx >= 3)   // 收到完整响应(地址+状态+校验)
        {
            printf("从机 0x%02X: 状态=0x%02X\n", rx_data[0], rx_data[1]); // 打印从机响应
            rx_idx = 0; // 重置接收索引
        }
    }
}

#elif defined Slave

/* 从机模式:接收数据缓冲区、索引和首次接收标志 */
volatile uint8_t rx_data[10], 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[rx_idx++] = 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[0] != 0x01)   // 检查地址(非本机地址0x01)
        {
            rx_idx = 0;              // 重置接收索引
            Enter_Silent_Mode();     // 重新进入静默模式
        }
        else if (rx_idx >= 3)     // 收到完整命令(地址+命令+校验)
        {
            if (rx_data[0] == 0x01 && rx_data[1] == 0x10) // 验证地址和命令
            {
                uint8_t checksum = rx_data[0] + rx_data[1]; // 计算校验和
                if (rx_data[2] == 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))

/*!
* [url=home.php?mod=space&uid=247401]@brief[/url]       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__)

/*!
* [url=home.php?mod=space&uid=247401]@brief[/url]       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;
}

/*!
* [url=home.php?mod=space&uid=247401]@brief[/url]       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[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

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




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)



使用特权

评论回复

打赏榜单

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

评论
21小跑堂 2025-5-20 18:01 回复TA
灵活使用串口的静默模式,提升代码的健壮性,作者细致的描述了串口静默模式的相关概念和相关应用,并加以实例演示,整体较佳。 
沙发
发光的梦| | 2025-5-15 18:16 | 只看该作者
真棒,真棒

使用特权

评论回复
板凳
和谐智者| | 2025-5-16 23:25 | 只看该作者
看下来 静默模式 更像是捂住耳朵

使用特权

评论回复
地板
zjsx8192| | 2025-5-17 09:44 | 只看该作者
用dma更爽

使用特权

评论回复
5
dukedz| | 2025-5-17 11:07 | 只看该作者
这个硬件支持的静默模式,和其它 mcu 软件通过设置 tx gpio 为高阻有何区别?

建议极海 mcu 预置 cdbus 串口外设,和 can 一样支持仲裁,让 485 真正支持多主自由通讯

使用特权

评论回复
6
治愈糖果屋| | 2025-5-17 17:56 | 只看该作者
感谢分享!静默模式对于多设备通信确实非常有用,特别是在RS-485这样的总线上。能否详细解释一下如何在软件中配置和控制静默模式?

使用特权

评论回复
7
DKENNY|  楼主 | 2025-5-19 13:40 | 只看该作者
治愈糖果屋 发表于 2025-5-17 17:56
感谢分享!静默模式对于多设备通信确实非常有用,特别是在RS-485这样的总线上。能否详细解释一下如何在软件 ...

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

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

/* 从机模式:接收数据缓冲区和索引 */
volatile uint8_t rx_data[10], 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[3:0]=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[rx_idx++] = USART_RxData(USART3); // 读取接收数据
        USART_ClearStatusFlag(USART3, USART_FLAG_RXBNE); // 清除接收标志

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

#endif



使用特权

评论回复
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

52

主题

94

帖子

10

粉丝