stm32采用队列方式接收和发送RS485串口数据

代码实现了一个用于RS-485通信的系统,包括数据接收、发送、静默时间管理、波特率控制和USART中断处理。关键功能如接收缓冲区、FIFO、回转时间计算和错误检测等都被详细描述。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/* 用于存储接收字节的缓冲区 - 大小必须是2的幂 /
static uint8_t Receive_Buffer_Data[512];
static FIFO_BUFFER Receive_Buffer;
/ 线上的静默时间 /
static struct mstimer Silence_Timer;
/ 波特率 */
static uint32_t Baud_Rate = 38400;

/* 在接收到的帧的最后一个八位的停止位结束后,节点可以启用其EIA-485驱动器之前的最短时间:40个位时间。/
/ 在9600波特率下,40个位时间约为4.166毫秒 /
/ 在19200波特率下,40个位时间约为2.083毫秒 /
/ 在38400波特率下,40个位时间约为1.041毫秒 /
/ 在57600波特率下,40个位时间约为0.694毫秒 /
/ 在76800波特率下,40个位时间约为0.520毫秒 /
/ 在115200波特率下,40个位时间约为0.347毫秒 /
/ 40位是包括每个八位的起始位和停止位的4个八位 */
#define Tturnaround (40UL)

/*************************************************************************

Description: 重置线上的静默时间计时器。
Returns: 无
Notes: 无
**************************************************************************/
void rs485_silence_reset(void)
{
mstimer_set(&Silence_Timer, 0);
}
/*************************************************************************

Description: 从计时器确定线上的静默时间。
Returns: 如果已经过了指定的时间间隔,则返回true
Notes: 无
**************************************************************************/
bool rs485_silence_elapsed(uint32_t interval)
{
return (mstimer_elapsed(&Silence_Timer) > interval);
}
/*************************************************************************

Description: 波特率决定了回转时间。
Returns: 毫秒数
Notes: 无
*************************************************************************/
static uint16_t rs485_turnaround_time(void)
{
/ 接收后传输之前的延迟 - 根据MS/TP规范 /
/ 等待至少40个位时间自接收以来 /
/ 至少2毫秒用于错误:四舍五入、时钟滴答 */
if (Baud_Rate) {
return (2 + ((Tturnaround * 1000UL) / Baud_Rate));
} else {
return 2;
}
}

/*************************************************************************

Description: 使用静默计时器确定回转时间。
Returns: 如果回转时间已过,则返回true。
Notes: 无
**************************************************************************/
bool rs485_turnaround_elapsed(void)
{
return (mstimer_elapsed(&Silence_Timer) > rs485_turnaround_time());
}
/*************************************************************************

Description: 确定接收时是否发生错误。
Returns: 如果发生错误,则返回true。
Notes: 无
**************************************************************************/
bool rs485_receive_error(void)
{
return false;
}
/*******************************************************************/
/

@brief
*USARTx
*中断
*处理程序
*子例程

@param[in]
*无

@return
***********************************************************************/
void USART2_IRQHandler(void)
{
uint8_t data_byte;

if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) {
/* 从接收数据寄存器中读取一个字节 */
data_byte = USART_ReceiveData(USART2);
(void)FIFO_Put(&Receive_Buffer, data_byte);
}
}

/*************************************************************************

DESCRIPTION: 返回是否有可用的字节。

RETURN: 如果有可用的字节,则返回true,并将字节存储在参数中。

NOTES: 无
**************************************************************************/
bool rs485_byte_available(uint8_t data_register)
{
bool data_available = false; / 返回值 */

if (!FIFO_Empty(&Receive_Buffer)) {
if (data_register) {
*data_register = FIFO_Get(&Receive_Buffer);
}
rs485_silence_reset();
data_available = true;
led_rx_on_interval(10);
}

return data_available;
}

/*************************************************************************

DESCRIPTION: 发送一个字节的数据。
RETURN: 无
NOTES: 无
**************************************************************************/
void rs485_byte_send(uint8_t tx_byte)
{
led_tx_on_interval(10);
USART_SendData(USART2, tx_byte);
rs485_silence_reset();
}
/*************************************************************************

Description: 确定USART中是否有一个字节从寄存器中移出。
Returns: 如果USART寄存器为空,则返回true。
Notes: 无
**************************************************************************/
bool rs485_byte_sent(void)
{
return USART_GetFlagStatus(USART2, USART_FLAG_TXE);
}
/*************************************************************************

Description: 确定整个帧是否从USART FIFO中发送完毕。
Returns: 如果USART FIFO为空,则返回true。
Notes: 无
**************************************************************************/
bool rs485_frame_sent(void)
{
return USART_GetFlagStatus(USART2, USART_FLAG_TC);
}

这段代码是一个用于实现RS-485通信的功能。它包含了一些函数和变量,用于接收和发送数据,并处理通信中的一些特殊情况。

首先,代码定义了一个用于存储接收到的字节的缓冲区(Receive_Buffer_Data)和一个FIFO缓冲区(Receive_Buffer)来管理接收缓冲区。

接下来,代码定义了一个用于计时静默时间的计时器(Silence_Timer)和一个用于存储波特率的变量(Baud_Rate)。

代码中的rs485_silence_reset函数用于重置静默时间计时器。

rs485_silence_elapsed函数用于判断静默时间是否已过去了指定的时间间隔。

rs485_turnaround_time函数根据波特率确定回转时间。

接下来,代码包含了一个USART2_IRQHandler函数,用于处理USART2的接收中断。当接收到数据时,它将数据存储到接收缓冲区中。

rs485_byte_available函数用于检查是否有可用的字节,并将字节存储在参数data_register中。

rs485_byte_send函数用于发送一个字节的数据。

rs485_byte_sent函数用于判断USART寄存器是否为空,即判断上一次发送的字节是否已经移出寄存器。

rs485_frame_sent函数用于判断整个帧是否已经发送完毕。

这些函数和变量的目的是实现RS-485通信的基本功能,包括接收和发送数据,并处理通信时的一些特殊情况,如静默时间和回转时间。

/*************************************************************************

DESCRIPTION: 发送一些数据并等待其发送完毕

RETURN: 如果发生冲突或超时,则返回true

NOTES: 无
**************************************************************************/
void rs485_bytes_send(uint8_t buffer, / 要发送的数据 /
uint16_t nbytes)
{ / 数据字节数 */
uint8_t tx_byte;

while (nbytes) {
/* 发送数据字节 */
tx_byte = buffer;
/ 发送一个字节 /
USART_SendData(USART2, tx_byte);
while (!rs485_byte_sent()) {
/ 什么也不做 - 等待直到Tx缓冲区为空 /
}
buffer++;
nbytes--;
}
/ 帧已发送? /
while (!rs485_frame_sent()) {
/ 什么也不做 - 等待直到整个帧在传输移位寄存器中移出 */
}
rs485_silence_reset();

return;
}

/*************************************************************************

Description: 配置USART的波特率

Returns: 无

Notes: 无
**************************************************************************/
static void rs485_baud_rate_configure(void)
{
USART_InitTypeDef USART_InitStructure;

USART_InitStructure.USART_BaudRate = Baud_Rate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

/* 配置USARTx */
USART_Init(USART2, &USART_InitStructure);
}

rs485_bytes_send函数的参数buffer是一个指向要发送数据的缓冲区的指针。该缓冲区存储了要发送的数据字节序列。

nbytes参数是要发送的数据字节数。它指示了要发送的数据的长度。

在调用rs485_bytes_send函数时,需要将要发送的数据存储在buffer指向的缓冲区中,并将要发送的数据字节数作为nbytes参数传递给函数。

需要注意的是,调用rs485_bytes_send函数时,确保提供的缓冲区大小足够存储要发送的数据,并且nbytes参数的值与要发送的数据字节数相匹配,以避免访问越界和发送不完整的数据。

/*************************************************************************

Description: 将波特率设置为非易失存储,并配置USART

Returns: 如果保存了波特率值,则返回true

Notes: 无
**************************************************************************/
bool rs485_baud_rate_set(uint32_t baud)
{
bool valid = true;

switch (baud) {
case 9600:
case 19200:
case 38400:
case 57600:
case 76800:
case 115200:
Baud_Rate = baud;
rs485_baud_rate_configure();
break;
default:
valid = false;
break;
}

return valid;
}

/*************************************************************************

Description: 确定波特率(以bps为单位)
Returns: 波特率(以bps为单位)
Notes: 无
**************************************************************************/
uint32_t rs485_baud_rate(void)
{
return Baud_Rate;
}
/*************************************************************************

Description: 启用请求发送(RTS)即发送使能引脚
Returns: 无
Notes: 无
**************************************************************************/
void rs485_rts_enable(bool enable)
{
if (enable) {
GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);
} else {
GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);
}
}

/*************************************************************************

Description: 初始化USART

Returns: 无

Notes: 无
**************************************************************************/
void rs485_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

GPIO_StructInit(&GPIO_InitStructure);
/* 配置USARTx的接收引脚为浮空输入 /
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/ 配置USARTx的发送引脚为复用推挽输出 /
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/ 配置请求发送(RTS)即发送使能引脚 /
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/ 使能USARTx时钟 /
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
/RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);/
/ 使能GPIO时钟 /
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/ 为这对引脚和外设功能启用USART引脚软件重映射:
USART3全重映射(TX/PD8,RX/PD9,CK/PD10,CTS/PD11,RTS/PD12)/
/GPIO_PinRemapConfig(GPIO_FullRemap_USART3, ENABLE);/
/ 配置NVIC的抢占优先级位数 /
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
/ 使能USARTx中断 /
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/ 使能USART生成中断 */
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

rs485_baud_rate_set(Baud_Rate);

USART_Cmd(USART2, ENABLE);

FIFO_Init(&Receive_Buffer, &Receive_Buffer_Data[0],
(unsigned)sizeof(Receive_Buffer_Data));
rs485_silence_reset();
}

首先,在调用rs485_init函数之前,确保已经正确配置了USART2的引脚和时钟。

调用rs485_init函数将初始化USART2和相关的GPIO引脚。

rs485_init函数中的rs485_baud_rate_set函数用于配置USART2的波特率。可以使用rs485_baud_rate_set函数来设置所需的波特率。默认情况下,波特率被设置为Baud_Rate变量的值。

USART_ITConfig函数用于使能USART2接收中断。这将允许在接收到数据时触发中断。

FIFO_Init函数用于初始化接收缓冲区。接收缓冲区是一个循环队列,用于存储接收到的数据。

rs485_silence_reset函数用于重置沉默定时器。

一旦完成了rs485_init函数的调用,USART2就已经准备好进行通信了。您可以使用USART_SendData函数发送数据,并使用USART_ReceiveData函数接收数据。

请注意,为了使USART2正常工作,您还需要根据具体的应用场景进行适当的配置,例如使能中断处理函数、处理接收到的数据等。

<think>好的,我现在需要帮助用户了解RS485的报文结构以及如何解析RS485通信。首先,用户的问题是关于RS485报文格式解析方法的,我需要根据提供的引用内容来组织回答。 首先,我应该回顾用户提供的引用内容。引用[1]介绍了RS485的基本设备TPIC485收发器,提到了驱动能力抗噪设计。引用[2]涉及Modbus-RTU协议,提到了寄存器地址,比如0x3F00用于母线电压。引用[3]讨论了串口通信的数据帧结构,比如帧头AA BB CC,以及校验方式。引用[4]展示了一个数据格式的例子,包含多个逗号分隔的数值,可能涉及ASCII编码的数据。 接下来,我需要整理RS485报文的基本结构。RS485本身是物理层标准,报文结构通常取决于应用层协议,如Modbus-RTU。因此,可能需要先说明RS485的物理层特性,然后转到常见的协议结构,比如Modbus-RTU。 根据引用[3],数据帧可能包含帧头、地址域、功能码、数据校验码。例如,AA BB CC作为帧头,然后是地址、功能码、数据长度、数据内容,最后是CRC校验。引用[2]中的例子提到了Modbus地址0x3F00,单位是V,功能是读取母线电压。这里的功能码可能是03(读取保持寄存器),地址0x3F00对应寄存器的地址。 解析步骤可能包括:确定通信协议(如Modbus-RTU)、识别帧结构、解析字段、校验验证。引用[3]提到了校验方法如CRC,需要计算校验码是否匹配。同时,引用[4]的例子可能展示了一种ASCII模式的数据,使用逗号分隔不同的数据点,这可能与Modbus的ASCII模式相关,但通常Modbus-RTU使用二进制格式。 处理数据时,需要注意半双工通信的特点,引用[2]提到使用两线半双工模式,所以收发切换需要正确处理。此外,数据解析需要参考设备手册,如引用[2]中查找寄存器地址单位。 可能的问题点:如何确定帧的起始结束?引用[3]提到帧头用于标识起始,而Modbus-RTU则依靠时间间隔(至少3.5个字符时间的静默)。校验码的计算方法(如CRC16)也是关键,需要正确的算法。 总结回答结构:先介绍RS485报文的一般结构,结合Modbus-RTU的例子,分步骤说明解析方法,引用相关示例,最后给出注意事项常见问题。</think>### RS485报文格式及解析方法 #### 一、RS485报文结构(以Modbus-RTU为例) RS485是物理层标准,实际报文格式由应用层协议定义。**Modbus-RTU**是工业领域常用的协议格式,其报文结构如下: $$ \text{帧结构} = [\text{地址域}][\text{功能码}][\text{数据域}][\text{CRC校验}] $$ 1. **地址域**:1字节,标识从机设备地址(例如`0x01`表示第一个设备)[^2]。 2. **功能码**:1字节,定义操作类型(如`0x03`表示读取保持寄存器)[^2]。 3. **数据域**:可变长度,包含寄存器地址、数据长度或具体数值(例如`0x3F00`表示母线电压寄存器地址)。 4. **CRC校验**:2字节,用于验证数据完整性(计算方式见下文)。 **示例报文**(读取母线电压): ``` 01 03 3F00 0001 CRC16 ``` - `01`: 设备地址 - `03`: 功能码(读取寄存器) - `3F00`: 寄存器地址 - `0001`: 读取1个寄存器 - `CRC16`: 校验码 #### 二、解析步骤 1. **确定协议类型** 确认设备使用的协议(如Modbus-RTU、自定义协议)。参考设备手册获取寄存器地址定义。 2. **识别帧边界** - **帧头标识**:部分协议使用固定帧头(如`AA BB CC`)标记数据包起始[^3]。 - **时间间隔**:Modbus-RTU通过3.5个字符时间的静默区分帧。 3. **解析字段内容** ```python # 示例代码:解析Modbus-RTU响应帧 data = b'\x01\x03\x02\x0A\x5B\xC1\x2F' address = data[0] # 0x01 func_code = data[1] # 0x03 byte_count = data[2] # 0x02 voltage = int.from_bytes(data[3:5], 'big') / 10.0 # 265.1V crc_received = int.from_bytes(data[5:7], 'big') crc_calculated = calculate_crc(data[:-2]) ``` 4. **校验验证** Modbus-RTU使用**CRC-16校验**,算法如下: ```c // CRC计算核心代码 uint16_t crc_update(uint16_t crc, uint8_t data) { crc ^= data; for (int i=0; i<8; i++) { if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } return crc; } ``` #### 三、注意事项 1. **半双工控制**:RS485需通过使能信号控制收发切换(引用TPIC485芯片的电流限幅功能可提高稳定性)[^1]。 2. **数据编码**:数值可能采用大端序、补码或浮点格式(如引用[4]中的`4494.99`可能为IEEE754浮点数)。 3. **错误处理**:需检测帧超时、校验失败、数据长度异常等情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

章鱼哥嵌入式开发

坚持不易,你们的鼓励是我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值