嵌入式技术论坛
直播中

尚文清

7年用户 1624经验值
私信 关注
[问答]

通过RTT实现一个下位机并接收其它串口模块的数据是否合理呢

是这样的,我想通过RTT实现一个下位机,它可以接收来自RS232、RS485和其它串口模块的数据,并给出响应。我的想法在串口中断中接收数据,然后通过消息队列发送到数据接收线程,然后将接收到的数据通过消息队列,发送到统一的数据处理线程对数据进行处理。大概的示意图如下:

1.jpg

用RS232的数据来举个例子,RS232数据来了以后触发串口中断,然后通过消息队列发送到RS232的数据接收线程,该线程按照我的交互协议格式对接收的数据进行接收,当一帧数据接收完成以后,就发送一个不定长度的消息给数据处理线程(使用动态内存分配),通知数据处理线程对RS232的数据进行处理,数据处理线程接收到不定长度的消息以后,将消息取出,然后释放消息的内存,最后对取出的消息进行处理给出响应结果。

不知道我这种思路是否合理呢?刚从裸机切换到RTOS,老感觉不放心,请大佬们指教。我将部分关键代码贴出来,如下:

typedef struct
{
rt_uint8_t from; /* 消息来源:FROM_RS232、485... */
rt_uint8_t data_ptr; / 数据块首地址 /
rt_uint32_t data_size; /
数据块大小 */
}S_MSG, P_MSG;
/
RS232串口接收中断 /
void USART2_IRQHandler(void)
{
uint8_t data;
rt_err_t res;
rt_interrupt_enter();
if(USART_GetITStatus(USART2, USART_INT_RDNE) != RESET)
{
data = USART_ReceiveData(USART2);
res = rt_mq_send (rs232_recv_mq, &data, 1);/
发送一个字节的消息给RS232数据接收线程,同时该消息队列也可以作为数据接收缓冲区防止丢包 /
if( res != RT_EOK )
{
while(1);
}
}
rt_interrupt_leave();
}
/
RS232数据接收线程 */
void rs232_thread_entry(void p)
{
rt_err_t res;
uint8_t data;
S_MSG msg_ptr;
while(1)
{
res = rt_mq_recv(rs232_recv_mq, &data, 1, RT_WAITING_FOREVER);
if( res == RT_EOK )
{
....这里有一些协议的判断被省略了,这不重要,总之就是判断一帧数据接收完成...
if( rs232_port.recv_len == length + 3 )/
到这里表示一帧数据接收完成了 /
{
msg_ptr.data_size = rs232_port.recv_len;/
记录数据长度 /
msg_ptr.from = FROM_RS232; /
记录数据源 /
msg_ptr.data_ptr = rt_malloc(msg_ptr.data_size);/
为不定长度的消息数据区分配内存 /
if( msg_ptr.data_ptr == RT_NULL )
{
while(1);
}
rt_memcpy(msg_ptr.data_ptr, rs232_port.recv_buff, msg_ptr.data_size);/
数据拷贝 /
res = rt_mq_send(host_msg_mq, &msg_ptr, sizeof(msg_ptr));/
发送消息给数据处理线程 /
if( res != RT_EOK )
{
rt_free(msg_ptr.data_ptr);
while(1);
}
}
}
}
}
/
数据处理线程 */
void host_if_thread_entry(void p)
{
rt_err_t res;
P_HOST p_host = &host_handle;
S_MSG msg_ptr;
while(1)
{
res = rt_mq_recv(host_msg_mq, &msg_ptr, sizeof(msg_ptr), RT_WAITING_FOREVER);/
接收不定长消息 /
if( res == RT_EOK )
{
rt_memcpy(p_host->recv_buff, msg_ptr.data_ptr, msg_ptr.data_size);/
将消息取出,释放消息内存 /
p_host->recv_len = msg_ptr.data_size;
p_host->msg.from = msg_ptr.from;
rt_free(msg_ptr.data_ptr);
host_if_handle(p_host);/
数据处理 /
if( p_host->msg.from == FROM_RS232 )/
返回应答 */
{
rs232_send_data(p_host->send_buff, p_host->send_len);
}
}
}
}

回帖(8)

王飞云

2022-9-1 14:36:06
因为消息体内传送的是内存地址,所以,发送者和接收者不能同时访问这块内存。
由以上可以得出,发送消息时不能使用重复的内存。所以它必须申请新内存。
但是,如果发送比接收快,某些消息被丢弃,这个申请的内存也得释放掉。
举报

尚文清

2022-9-1 14:36:14
谢谢解答,我就是不大确定这个共享内存的问题。但是我感觉消息的那一片内存应该不存在同时访问的情况,因为每次接收到一帧数据以后都是新分配的内存(不是上一次分配的),当数据接收线程发送完成以后,数据处理线程才会对这片内存进行访问,所以应该是不会同时访问的情况的。

另外关于发送比接收快的情况下,那个内存是有释放的,请看下面的代码,我在发送不定长消息的时候,如果发送失败了,就会释放这片申请的内存,应该是这么处理吧。

res = rt_mq_send(host_msg_mq, &msg_ptr, sizeof(msg_ptr)); /* 发送消息给数据处理线程 */
if (res != RT_EOK)
{
    rt_free(msg_ptr.data_ptr);
    while (1)
        ;
}
举报

贾永世

2022-9-1 14:36:28
看到你使用消息队列发送一个字节数据。这个有点儿压力。
加上 DMA ,中断里先检查一下多少字节,把字节数发出去,或者发消息队列,但是发消息队列的次数能明显降低。比如 1+11 这种,你的实现方式必须12次中断,发12次消息。但是可能只发两次消息就够了。
举报

尚文清

2022-9-1 14:36:35
确实消息按照字节发送太频繁了,这个因为我们的协议是按照字节的方式进行设计的,有每个字节的超时要求,所以用DMA加IDLE好像不大合适,后面我再想下能否实现。不过对于消息队列发送一个字节的问题,我后面想到一个更好的方法来避免,那就是把数据帧的接收放到中断进行,帧的判断也在中断进行,其实也就是一些判断和接收逻辑,应该不会耗时很长时间,这样就不用每个硬件接口都开辟一个线程来接收了,示意图如下:
1.jpg
中断服务程序改成和线程接收差不多的方式:
  • void USART2_IRQHandler(void)
  • {
  •     uint8_t data;
  •     rt_err_t res;
  •     S_MSG msg_ptr;
  •     rt_interrupt_enter();
  •     if(USART_GetITStatus(USART2, USART_INT_RDNE) != RESET)
  •     {
  •         data = USART_ReceiveData(USART2);
  •             //...这里省略了帧的接收判断逻辑...执行到了下面的语句就表示一帧接收完成了,帧接收完成就申请内存,然后通过消息队列发送消息到数据处理线程。
  •                 if( rs232_port.recv_count == rs232_port.length + 3 )
  •                 {
  •                     if (rs232_recv_timer != RT_NULL)
  •                     {
  •                         rt_timer_stop(rs232_recv_timer);
  •                     }
  •                     msg_ptr.data_size = rs232_port.recv_count;
  •                     msg_ptr.from = FROM_RS232;
  •                     msg_ptr.data_ptr = rt_malloc(msg_ptr.data_size);
  •                     if( msg_ptr.data_ptr == RT_NULL )
  •                     {
  •                         while(1);
  •                     }
  •                     rt_memcpy(msg_ptr.data_ptr, rs232_port.recv_buff, msg_ptr.data_size);
  •                     res = rt_mq_send(host_msg_mq, &msg_ptr, sizeof(msg_ptr));
  •                     if( res != RT_EOK )
  •                     {
  •                         rt_free(msg_ptr.data_ptr);
  •                         while(1);
  •                     }
  •                     rs232_port.recv_state = HEADER;
  •                 }
  •     }
  •     rt_interrupt_leave();
  • }



举报

贾永世

2022-9-1 14:36:59
也可以,减少 mq send 调用。尽量别调用复杂函数。
你为啥用 while在中断里
举报

尚文清

2022-9-1 14:37:32
while(1);那个语句吗?这个是我暂时用来检查我的代码的,因为还没有移植finsh,暂时还不能打印。
我在看官方文档的时候,无意中看到这么一段话,说是不能在中断中申请或者释放内存,请看下图:
1.jpg
那么,我这里的串口中断服务程序是不是有问题呢,因为我在中断中做了内存申请
msg_ptr.data_ptr = rt_malloc(msg_ptr.data_size);
举报

贾永世

2022-9-1 14:37:40
是,忽略这个问题了。
换 mempool 吧,这个申请内存时没有使用信号量
举报

贾飞小

2022-9-1 14:37:51
自己实现一个环形缓冲区,中断中入队。然后开一个线程定时解析缓冲区里面的数据后再用系统自带的消息队列转发到其它线程是否更好一点。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分