[应用相关] stm32采用队列方式接收和发送RS485串口数据

[复制链接]
4646|51
 楼主| 漫天星yl 发表于 2024-3-31 22:48 | 显示全部楼层 |阅读模式
stm32采用队列方式接收和发送RS485串口数据
  1. /* 用于存储接收字节的缓冲区 - 大小必须是2的幂 /
  2. static uint8_t Receive_Buffer_Data[512];
  3. static FIFO_BUFFER Receive_Buffer;
  4. / 线上的静默时间 /
  5. static struct mstimer Silence_Timer;
  6. / 波特率 */
  7. static uint32_t Baud_Rate = 38400;

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

  17. /*************************************************************************

  18. Description: 重置线上的静默时间计时器。
  19. Returns: 无
  20. Notes: 无
  21. **************************************************************************/
  22. void rs485_silence_reset(void)
  23. {
  24. mstimer_set(&Silence_Timer, 0);
  25. }
  26. /*************************************************************************

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

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

  51. /*************************************************************************

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

  61. Description: 确定接收时是否发生错误。
  62. Returns: 如果发生错误,则返回true。
  63. Notes: 无
  64. **************************************************************************/
  65. bool rs485_receive_error(void)
  66. {
  67. return false;
  68. }
  69. /*******************************************************************/
  70. /

  71. @brief
  72. *USARTx
  73. *中断
  74. *处理程序
  75. *子例程

  76. @param[in]
  77. *无

  78. @return
  79. *无
  80. **********************************************************************/
  81. void USART2_IRQHandler(void)
  82. {
  83. uint8_t data_byte;

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

  90. /*************************************************************************

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

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

  93. NOTES: 无
  94. **************************************************************************/
  95. bool rs485_byte_available(uint8_t data_register)
  96. {
  97. bool data_available = false; / 返回值 */

  98. if (!FIFO_Empty(&Receive_Buffer)) {
  99. if (data_register) {
  100. *data_register = FIFO_Get(&Receive_Buffer);
  101. }
  102. rs485_silence_reset();
  103. data_available = true;
  104. led_rx_on_interval(10);
  105. }

  106. return data_available;
  107. }

  108. /*************************************************************************

  109. DESCRIPTION: 发送一个字节的数据。
  110. RETURN: 无
  111. NOTES: 无
  112. **************************************************************************/
  113. void rs485_byte_send(uint8_t tx_byte)
  114. {
  115. led_tx_on_interval(10);
  116. USART_SendData(USART2, tx_byte);
  117. rs485_silence_reset();
  118. }
  119. /*************************************************************************

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

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


 楼主| 漫天星yl 发表于 2024-3-31 22:48 | 显示全部楼层
这段代码是一个用于实现RS-485通信的功能。它包含了一些函数和变量,用于接收和发送数据,并处理通信中的一些特殊情况。

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

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

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

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

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

接下来,代码包含了一个USART2_IRQHandler函数,用于处理USART2的接收中断。当接收到数据时,它将数据存储到接收缓冲区中。
 楼主| 漫天星yl 发表于 2024-3-31 22:49 | 显示全部楼层
rs485_byte_available函数用于检查是否有可用的字节,并将字节存储在参数data_register中。

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

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

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

这些函数和变量的目的是实现RS-485通信的基本功能,包括接收和发送数据,并处理通信时的一些特殊情况,如静默时间和回转时间。
 楼主| 漫天星yl 发表于 2024-3-31 22:49 | 显示全部楼层
/*************************************************************************

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);
}
 楼主| 漫天星yl 发表于 2024-3-31 22:49 | 显示全部楼层
rs485_bytes_send函数的参数buffer是一个指向要发送数据的缓冲区的指针。该缓冲区存储了要发送的数据字节序列。

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

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

需要注意的是,调用rs485_bytes_send函数时,确保提供的缓冲区大小足够存储要发送的数据,并且nbytes参数的值与要发送的数据字节数相匹配,以避免访问越界和发送不完整的数据。
 楼主| 漫天星yl 发表于 2024-3-31 22:49 | 显示全部楼层
/*************************************************************************

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();
}
 楼主| 漫天星yl 发表于 2024-3-31 22:49 | 显示全部楼层
首先,在调用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正常工作,您还需要根据具体的应用场景进行适当的配置,例如使能中断处理函数、处理接收到的数据等。
jkl21 发表于 2024-4-5 21:41 | 显示全部楼层
在处理接收和发送队列中的数据时,需要根据具体应用需求进行相应的数据处理。例如,可以对接收到的数据进行解析和校验,对发送的数据进行封装和加密等。
bestwell 发表于 2024-4-6 16:42 | 显示全部楼层
为队列分配适当的内存空间,并设置正确的队列参数,如最大容量、头部和尾部指针等。
sesefadou 发表于 2024-4-7 12:31 | 显示全部楼层
如果同时使用多个中断,需要合理设置中断优先级,以确保RS485接收中断能够及时得到处理,不会因为其他低优先级中断而被延迟。
sdlls 发表于 2024-4-7 15:21 | 显示全部楼层
在队列方式下,需要设置适当的中断处理函数。
LEDyyds 发表于 2024-4-7 16:58 | 显示全部楼层
用队列有啥好处吗
macpherson 发表于 2024-4-7 18:25 | 显示全部楼层
RS485通信通常在中断模式下进行,因此需要妥善管理串口中断,确保在中断服务程序中正确处理接收到的数据,并将数据推入队列。
robincotton 发表于 2024-4-7 20:44 | 显示全部楼层
在软件设计方面,需要实现一个队列系统来管理接收和发送的数据。这涉及到队列的数据结构设计,确保数据的先进先出(FIFO)顺序,以及处理可能的队列溢出或下溢情况。
eefas 发表于 2024-4-7 22:57 | 显示全部楼层
RS485通信缓冲区分配足够大的内存,以容纳最大可能的传输数据量。队列的大小应该根据预期的数据流量和传输速率来确定。
EmmaTT 发表于 2024-4-8 15:25 来自手机 | 显示全部楼层
队列有什么用啊
sdlls 发表于 2024-4-9 10:02 | 显示全部楼层
可以设置串口数据接收完成的中断处理函数,以便于将接收到的数据放入队列中。
mollylawrence 发表于 2024-4-9 14:52 | 显示全部楼层
在向队列中添加数据前,需要检查队列是否已满。同样,在从队列中取出数据时,也需要检查队列是否为空。
louliana 发表于 2024-4-9 17:03 | 显示全部楼层
设计合理的通信协议,包括帧头、地址码、命令码、数据段和校验码等
robincotton 发表于 2024-4-9 20:09 | 显示全部楼层
在RS485串口通信中,需要设计合适的数据包格式,以便于区分有效数据和无效数据。数据包格式通常包括起始位、数据位、校验位和停止位。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

34

主题

350

帖子

0

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