- /* 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, 下载次数: 4)