打印

stm32 uart 中断同时接收和发送时存在丢失数据的现象

[复制链接]
14396|24
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
prettyxp|  楼主 | 2013-11-20 18:35 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
如果使用DMA接收,在PC上通过串口发送100K的数据,一个数据都不会丢失,但是使用中断方式,就会造成接收数据少那么几个, 注:在PC上使用串口工具, 发送字节数为280个, 时间间隔为1 ms, 有没有那位高人可以帮我看一下.

static void UART_Interrupt( void *Param )
{
        OS_ERR err;
        unsigned int cr;
        struct UART_DEVICE *uart = (struct UART_DEVICE *)Param;
        unsigned int sr = uart->uart->SR;
        cr = uart->uart->CR1;
        if ( (cr && 0x20) && (sr & 0x20) ){
                //RXNE 读数据寄存器非空
                UART_Recv_Byte( uart, cr, sr );
                sr = uart->uart->SR;
        }
        if ( (cr & 0x80) && (sr & 0x80) ){
                //TXE:发送数据寄存器空
                uart->uart->DR = uart->Send.buffer[uart->Send.offset];
                uart->Send.offset++;
                if ( uart->Send.offset >= uart->Send.size ){
                        //发送缓冲区空中断使能
                        uart->uart->CR1 &= (~0x80);
                        BSP_OS_SemPost( &uart->Send_Sem );
                }
                uart->uart->SR &= (~0x80);
                sr = uart->uart->SR;
        }
        if ( (cr && 0x20) && (sr & 0x20) ){
                //RXNE 读数据寄存器非空
                UART_Recv_Byte( uart, cr, sr );
                sr = uart->uart->SR;
        }
        if ( (cr & 0x10) && (sr & 0x10) ){
                //IDEL 监测到总线空闲
                OSQPost( &uart->Recv_Queue, (void *)uart->current_buffer, 1, OS_OPT_POST_NO_SCHED | OS_OPT_POST_ALL | OS_OPT_POST_FIFO, &err );
                if ( uart->current_buffer == NULL ){
                        uart->current_buffer = &uart->Recv[0];
                }else if ( uart->current_buffer == &uart->Recv[0] ){
                        uart->current_buffer = &uart->Recv[1];
                }else if ( uart->current_buffer == &uart->Recv[1] ){
                        uart->current_buffer = &uart->Recv[2];
                }else if ( uart->current_buffer == &uart->Recv[2] ){
                        uart->current_buffer = &uart->Recv[3];
                }else{
                        uart->current_buffer = &uart->Recv[0];
                }
                uart->current_buffer->offset = 0;
                uart->current_buffer->Recv_Size = 0;
                uart->uart->CR1 &= (~0x10);
                uart->uart->SR &= (~0x10);
                sr = uart->uart->SR;
        }
        if ( (cr && 0x20) && (sr & 0x20) ){
                //RXNE 读数据寄存器非空
                UART_Recv_Byte( uart, cr, sr );
        }
}

static void Uart_SendTask( void *p_arg )
{
        struct UART_DEVICE *uart = (struct UART_DEVICE *)p_arg;
        while (DEF_TRUE) {                                          /* Task body, always written as an infinite loop.       */
                const PLIST_ENTRY Entry = &uart->Send_Entry;
                PLIST_ENTRY pListEntry = Entry->Flink;
                if ( pListEntry == Entry ){
                        BSP_OS_SemWait( &uart->Lock_Sem, 0 );
                        uart->ReadyStatus = 1;
                        BSP_OS_SemPost( &uart->Lock_Sem );
                        BSP_OS_SemWait( &uart->Ready_Sem, 0 );
                        continue;
                }
                while( pListEntry != Entry ){
                        struct UART_SEND_NODE *Node = CONTAINING_RECORD( pListEntry, struct UART_SEND_NODE, Link );
                        uart->Send.offset = 0;
                        uart->Send.size = Node->Size;
                        uart->Send.buffer = Node->Buffer;
                        //发送缓冲区空中断使能
                        uart->uart->CR1 |= 0x80;
                        BSP_OS_SemWait( &uart->Send_Sem, 0 );
                        BSP_OS_SemWait( &uart->Lock_Sem, 0 );
                        RemoveEntryList( &Node->Link );
                        BSP_OS_SemPost( &uart->Lock_Sem );
                        pListEntry = pListEntry->Flink;
                        if ( NULL == Node->Complete ){
                                BSP_OS_SemPost( &Node->SemComplete );
                        }else{
                                Node->Complete( Node->arg );
                                Node->Release( Node );
                        }
                }
        }       
}

static void Uart_RecvTask( void *p_arg )
{
        OS_ERR err;
        OS_MSG_SIZE i;
        OS_MSG_SIZE Size;
        struct UART_DEVICE *uart = (struct UART_DEVICE *)p_arg;     
        while (DEF_TRUE) {                                          /* Task body, always written as an infinite loop.       */
                struct UART_RECV* Recv = (struct UART_RECV *)OSQPend( &uart->Recv_Queue, 0, OS_OPT_PEND_BLOCKING, &Size, (CPU_TS *)0, &err );
                if ( err != OS_ERR_NONE ){
                        continue;
                }
                for ( i = 0; i < Size; i++ ){
                        m_Size += Recv->Recv_Size;
                        UART_Send( uart, Recv->buffer, Recv->Recv_Size, UART_Complete, NULL );
                        Recv++;
                }
        }
}
沙发
prettyxp|  楼主 | 2013-11-20 18:39 | 只看该作者
struct UART_RECV
{
        char buffer[256];
        unsigned short offset;
        unsigned short Recv_Size;
};
struct UART_DEVICE
{
        OS_Q Recv_Queue;
        char ReadyStatus;
        OS_TCB RecvTaskTCB;
        OS_TCB SendTaskTCB;
        USART_TypeDef* uart;
        BSP_OS_SEM Lock_Sem;
        BSP_OS_SEM Send_Sem;
        BSP_OS_SEM Ready_Sem;
        LIST_ENTRY Send_Entry;
        struct UART_SEND Send;
        struct UART_DMA SendDMA;
        struct UART_DMA RecvDMA;
        struct UART_RECV Recv[4];
        struct UART_RECV *current_buffer;
        CPU_STK RecvTaskStk[APP_TASK_START_STK_SIZE];
        CPU_STK SendTaskStk[APP_TASK_START_STK_SIZE];
};

串口的结构体和接收buffer结构体定义如上, 在收到256个字符后,Uart_RecvTask任务会回传. 发送和接收都是使用中断方式

使用特权

评论回复
板凳
prettyxp|  楼主 | 2013-11-20 18:41 | 只看该作者
static void UART_Recv_Byte( struct UART_DEVICE *uart, unsigned int cr, unsigned int sr )
{
        OS_ERR err;
        unsigned char value = uart->uart->DR;
        uart->uart->SR &= (~0x20);
        uart->current_buffer->buffer[uart->current_buffer->offset] = value;
        uart->current_buffer->offset++;
        uart->current_buffer->Recv_Size += 1;
        if ( uart->current_buffer->Recv_Size >= sizeof(uart->current_buffer->buffer) ){
                struct UART_RECV *current = uart->current_buffer;
                if ( uart->current_buffer == NULL ){
                        uart->current_buffer = &uart->Recv[0];
                }else if ( uart->current_buffer == &uart->Recv[0] ){
                        uart->current_buffer = &uart->Recv[1];
                }else if ( uart->current_buffer == &uart->Recv[1] ){
                        uart->current_buffer = &uart->Recv[2];
                }else if ( uart->current_buffer == &uart->Recv[2] ){
                        uart->current_buffer = &uart->Recv[3];
                }else{
                        uart->current_buffer = &uart->Recv[0];
                }
                uart->current_buffer->offset = 0;
                uart->current_buffer->Recv_Size = 0;
                OSQPost( &uart->Recv_Queue, (void *)current, 1, OS_OPT_POST_NO_SCHED | OS_OPT_POST_ALL | OS_OPT_POST_FIFO, &err );
        }
        uart->uart->CR1 |= (0x10);
        uart->uart->SR &= (~0x20);
}

贴上接收单个字符函数, 因为有的uart没有DMA, 所以需要调中断模式

使用特权

评论回复
地板
香水城主| | 2013-11-20 19:01 | 只看该作者
SW Too Slow.

使用特权

评论回复
5
airwill| | 2013-11-20 20:13 | 只看该作者
但是使用中断方式,就会造成接收数据少那么几个
其实数据丢失有几个可能.
1. 接收中断没有来得及响应
2.接收缓冲区数据没有来得及取走.
3. 软件 bug.

使用特权

评论回复
6
戈卫东| | 2013-11-20 20:52 | 只看该作者
好复杂.....

使用特权

评论回复
7
prettyxp|  楼主 | 2013-11-21 11:47 | 只看该作者
戈卫东 发表于 2013-11-20 20:52
好复杂.....

很复杂吗? 只是用了一点数据结构,在多个串口同时使用时,可以节约一点代码

使用特权

评论回复
8
prettyxp|  楼主 | 2013-11-21 11:48 | 只看该作者
香水城主 发表于 2013-11-20 19:01
SW Too Slow.

72Mhz的主频,做这点事都不行,那STM32能做什么呀!

使用特权

评论回复
9
prettyxp|  楼主 | 2013-11-21 11:54 | 只看该作者
airwill 发表于 2013-11-20 20:13
但是使用中断方式,就会造成接收数据少那么几个
其实数据丢失有几个可能.
1. 接收中断没有来得及响应

谢谢你的回答.
1. 我做过试验,只收,不发,一个都不会丢(100KB)以上的数据
1. 接收中断没有来得及响应
   我在中断里面做得代码很少,像while这样的代码不存在
2.接收缓冲区数据没有来得及取走
   这个我以前也猜想过,所以我在很多地方都有判断是否收到数据, 在中断里面有多次判断,特别是在TX发送完成后,也重新判断了
4. 软件 bug
   软件 bug我也想过,只要慢一点发送(10ms)每次发包(300 byte)个,程序也正常
   现论上来说,只是降底了串口的压力,程序流程应该是一样的
   同样的代码,如果换成dma接收,就不会有问题.

使用特权

评论回复
10
香水城主| | 2013-11-21 12:26 | 只看该作者
prettyxp 发表于 2013-11-21 11:48
72Mhz的主频,做这点事都不行,那STM32能做什么呀!

不会开车的人,再好的车也会开沟里去。

使用特权

评论回复
11
prettyxp|  楼主 | 2013-11-21 20:12 | 只看该作者
香水城主 发表于 2013-11-21 12:26
不会开车的人,再好的车也会开沟里去。

那请阁下教我开一下车, 请你贴一个效率非常高的函数.让我们这种小辈好好学习

使用特权

评论回复
12
prettyxp|  楼主 | 2013-11-21 20:28 | 只看该作者
72Mhz 主频, 115200婆特率, 115200/10 = 11520byte / S
Cortex-M3 1.25DMIPS/MHz = 1.25 * 72 * 1000000 = 90000000条指令每秒
第发一个byte的时音,CPU可以运行7812条指令
就当收发一起,每一个中断也有3906多条指令

使用特权

评论回复
13
kyzb001| | 2013-11-21 21:04 | 只看该作者
lz 数据量比较大的画, 不考虑下全部接受完毕在分析吗?   比如说 头+长度+data+crc   crc效验OK之后在进行数据分析。

连续数据快的时候还可以建立缓存。

也可能我就粗略浏览了下代码,所以有不当的地方。眼镜丢了,看着眼花!

使用特权

评论回复
14
kyzb001| | 2013-11-21 21:07 | 只看该作者
并且好像一般不建议在中断中做复杂处理。

使用特权

评论回复
15
huangxz| | 2013-11-21 21:39 | 只看该作者
stm32的串口没有fifo,实际应用还是有存在丢数的可能的,
建议楼主建一个队列,这样可以缓冲后处理的影响。

使用特权

评论回复
16
香水城主| | 2013-11-21 22:23 | 只看该作者
prettyxp 发表于 2013-11-21 20:28
72Mhz 主频, 115200婆特率, 115200/10 = 11520byte / S
Cortex-M3 1.25DMIPS/MHz = 1.25 * 72 * 1000000 = ...

1.25DMIPS/MHZ != 1.25M指令/MHz;Thumb2指令中有单周期指令,也有不少多周期指令,比如存储器访问指令。所以绝对不可能有每秒90,000,000条指令!

像你这种大量的指针运算+存储访问,是非常消耗时间的,你可以打开编译出来的汇编指令看看,比如这一句:
uart->current_buffer->buffer[uart->current_buffer->offset] = value;

既然有这么多的结构体操作,至少可以用一个指针直接访问,而不是每次计算指针。
比如可以用一个指针 struct UART_RECV *pbuf = uart->current_buffer;
然后上面那行改为:pbuf->buffer[pbuf->offset] = value;
pbuf还可以用修饰符register强制分配到寄存器中。

比较好的编译器可以帮你优化,如果运气不佳碰上一般的编译器,差距还是很大的。

使用特权

评论回复
17
prettyxp|  楼主 | 2013-11-22 09:33 | 只看该作者
香水城主 发表于 2013-11-21 22:23
1.25DMIPS/MHZ != 1.25M指令/MHz;Thumb2指令中有单周期指令,也有不少多周期指令,比如存储器访问指令。 ...

你说的没有错,的确存储访问会慢,像flash都要延时3个周期, 但你说的uart->current_buffer->buffer[uart->current_buffer->offset] = value; GCC是可以很好的优化, 因为gcc会把经常用的变量放在寄存器.其实和你用current_buffer指针一样

使用特权

评论回复
18
prettyxp|  楼主 | 2013-11-22 09:38 | 只看该作者
kyzb001 发表于 2013-11-21 21:04
lz 数据量比较大的画, 不考虑下全部接受完毕在分析吗?   比如说 头+长度+data+crc   crc效验OK之后在进行数 ...

我用STM32做的是串口分发器, 一个uart跟PC相连, 然后分出四个uart 接外设. 我现在还没有做数据分析,都处理不过来,丢包.只要解决数据分析不丢失数据,数据分析也不会有太大的问题, 因为采用的是中断机制

使用特权

评论回复
19
prettyxp|  楼主 | 2013-11-22 09:40 | 只看该作者
STM32 的设备,要么你就每个外设都可以使用DMA,要么就有fifo, 没有fifo, 又没有dma, 效率太底了.这个问题应该向原厂反映一下.

使用特权

评论回复
20
kyzb001| | 2013-11-22 11:18 | 只看该作者
prettyxp 发表于 2013-11-22 09:38
我用STM32做的是串口分发器, 一个uart跟PC相连, 然后分出四个uart 接外设. 我现在还没有做数据分析,都处 ...

中断机制处理不及时就会丢了呀!     先全部接下来  不管分发, 分析了之类的,全部先接下来  在做其他处理,比如UART_Recv_Byte这个函数。  也就相当于自己先建立一个fifo。   没FIFO  用STM32做串口大数据传输 有点虚。
LZ有兴趣可以阅读下 下面的代码,看看是否对你有所帮助
void MT_UartProcessZToolData ( uint8 port, uint8 event )
{
  uint8  ch;
  uint8  bytesInRxBuffer;
  
  (void)event;  // Intentionally unreferenced parameter

  while (Hal_UART_RxBufLen(port))
  {
    HalUARTRead (port, &ch, 1);

    switch (state)
    {
      case SOP_STATE:
        if (ch == MT_UART_SOF)
          state = LEN_STATE;
        break;

      case LEN_STATE:
        LEN_Token = ch;

        tempDataLen = 0;

        /* Allocate memory for the data */
        pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof ( mtOSALSerialData_t ) +
                                                        MT_RPC_FRAME_HDR_SZ + LEN_Token );

        if (pMsg)
        {
          /* Fill up what we can */
          pMsg->hdr.event = CMD_SERIAL_MSG;
          pMsg->msg = (uint8*)(pMsg+1);
          pMsg->msg[MT_RPC_POS_LEN] = LEN_Token;
          state = CMD_STATE1;
        }
        else
        {
          state = SOP_STATE;
          return;
        }
        break;

      case CMD_STATE1:
        pMsg->msg[MT_RPC_POS_CMD0] = ch;
        state = CMD_STATE2;
        break;

      case CMD_STATE2:
        pMsg->msg[MT_RPC_POS_CMD1] = ch;
        /* If there is no data, skip to FCS state */
        if (LEN_Token)
        {
          state = DATA_STATE;
        }
        else
        {
          state = FCS_STATE;
        }
        break;

      case DATA_STATE:

        /* Fill in the buffer the first byte of the data */
        pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen++] = ch;

        /* Check number of bytes left in the Rx buffer */
        bytesInRxBuffer = Hal_UART_RxBufLen(port);

        /* If the remain of the data is there, read them all, otherwise, just read enough */
        if (bytesInRxBuffer <= LEN_Token - tempDataLen)
        {
          HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], bytesInRxBuffer);
          tempDataLen += bytesInRxBuffer;
        }
        else
        {
          HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], LEN_Token - tempDataLen);
          tempDataLen += (LEN_Token - tempDataLen);
        }

        /* If number of bytes read is equal to data length, time to move on to FCS */
        if ( tempDataLen == LEN_Token )
            state = FCS_STATE;

        break;

      case FCS_STATE:

        FSC_Token = ch;

        /* Make sure it's correct */
        if ((MT_UartCalcFCS ((uint8*)&pMsg->msg[3], MT_RPC_FRAME_HDR_SZ + LEN_Token-3) == FSC_Token))
        {

        perWorkinfo = 0;
          osal_msg_send( App_TaskID, (byte *)pMsg );
                  
        }
        else
        {
          /* deallocate the msg */
          osal_msg_deallocate ( (uint8 *)pMsg );
        }

        /* Reset the state, send or discard the buffers at this point */
        state = SOP_STATE;

        break;

      default:
       break;
    }
  }
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2

主题

34

帖子

0

粉丝