zstack 串口解读,几家之言汇总。(2)

本文详细介绍了Z-Stack中对串口操作的封装,包括写操作、读操作及poll操作的核心实现,并解释了如何通过事件触发回调函数进行高效的数据处理。

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

最近在做一个智能家居的项目,用到了TI的CC2530芯片以及对应的zstack协议栈,其中串口通信部分使用的最多,下面就分享一下Z-Stack对串口封装的使用心得。


Z-Stack中对串口操作的封装主要在hal_uart.h,hal_uart.c中, 支持DMA和ISR两种处理方式, 真正的实现则都封装在_hal_uart_dma.c 和_hal_uart_isr.c中,  但系统只推荐使用DMA方式, 可以通过修改宏定义来改为ISR的方式,宏定义在hal_board_cfg.h中


Z-Stack对串口操作的封装使用了缓冲区的方式, 读写都是直接操作缓冲区, 不管是DMA方式还是ISR方式都是如此,下面以DMA为例介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef  struct
{
   uint16 rxBuf[HAL_UART_DMA_RX_MAX];
#if HAL_UART_DMA_RX_MAX < 256
   uint8 rxHead;
   uint8 rxTail;
#else
   uint16 rxHead;
   uint16 rxTail;
#endif
   uint8 rxTick;
   uint8 rxShdw;
   uint8 txBuf[2][HAL_UART_DMA_TX_MAX];
#if HAL_UART_DMA_TX_MAX < 256
   uint8 txIdx[2];
#else
   uint16 txIdx[2];
#endif
   volatile  uint8 txSel;
   uint8 txMT;
   uint8 txTick;            // 1-character time in 32kHz ticks according to baud rate,
                           // to be used in calculating time lapse since DMA ISR
                           // to allow delay margin before start firing DMA, so that
                           // DMA does not overwrite UART DBUF of previous packet
   
   volatile  uint8 txShdw;   // Sleep Timer LSB shadow.
   volatile  uint8 txShdwValid;  // TX shadow value is valid
   uint8 txDMAPending;      // UART TX DMA is pending
   halUARTCBack_t uartCB;
} uartDMACfg_t;

uartDMACfg_t结构体定力了相关的数据结构, 其中rxBuf和txBuf分别对应读写缓冲区


1、写操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static  uint16 HalUARTWriteDMA(uint8 *buf, uint16 len)
{
   uint16 cnt;
   halIntState_t his;
   uint8 txIdx, txSel;
   // Enforce all or none.
   if  ((len + dmaCfg.txIdx[dmaCfg.txSel]) > HAL_UART_DMA_TX_MAX)
   {
     return  0;
   }
   HAL_ENTER_CRITICAL_SECTION(his);
   txSel = dmaCfg.txSel;
   txIdx = dmaCfg.txIdx[txSel];
   HAL_EXIT_CRITICAL_SECTION(his);
   for  (cnt = 0; cnt < len; cnt++)
   {
     dmaCfg.txBuf[txSel][txIdx++] = buf[cnt];
   }
   HAL_ENTER_CRITICAL_SECTION(his);
   if  (txSel != dmaCfg.txSel)
   {
     HAL_EXIT_CRITICAL_SECTION(his);
     txSel = dmaCfg.txSel;
     txIdx = dmaCfg.txIdx[txSel];
     for  (cnt = 0; cnt < len; cnt++)
     {
       dmaCfg.txBuf[txSel][txIdx++] = buf[cnt];
     }
     HAL_ENTER_CRITICAL_SECTION(his);
   }
   dmaCfg.txIdx[txSel] = txIdx;
   if  (dmaCfg.txIdx[(txSel ^ 1)] == 0)
   {
     // TX DMA is expected to be fired
     dmaCfg.txDMAPending = TRUE;
   }
   HAL_EXIT_CRITICAL_SECTION(his);
   return  cnt;
}

从这段代码可以明显看出, Z-Stack对串口的写如果缓冲区剩余空间少于用户写入长度, 会直接返回0

也就是注释里的enforce all or none,  由于DMA方式使用的是双缓冲区,这个函数里也对缓冲区切换的情况做了保护。


2、读操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static  uint16 HalUARTReadDMA(uint8 *buf, uint16 len)
{
   uint16 cnt;
   for  (cnt = 0; cnt < len; cnt++)
   {
     if  (!HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))
     {
       break ;
     }
     *buf++ = HAL_UART_DMA_GET_RX_BYTE(dmaCfg.rxHead);
     HAL_UART_DMA_CLR_RX_BYTE(dmaCfg.rxHead);
     if  (++(dmaCfg.rxHead) >= HAL_UART_DMA_RX_MAX)
     {
       dmaCfg.rxHead = 0;
     }
   }
   PxOUT &= ~HAL_UART_Px_RTS;   // Re-enable the flow on any read.
   return  cnt;
}

这个函数很简单,就是直接从rxBuf里读取数据到用户缓冲区中, 需要注意的是,如果读到缓冲区的末尾,会自动调整游标到缓冲区头, 可能造成读到的数据并非真实接受到的数据, 所以在调用这个函数的时候,最好读取数据不要超过HAL_UART_DMA_RX_MAX


3、poll操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
static  void  HalUARTPollDMA( void )
{
   uint16 cnt = 0;
   uint8 evt = 0;
   if  (HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))
   {
     uint16 tail = findTail();
     // If the DMA has transferred in more Rx bytes, reset the Rx idle timer.
     if  (dmaCfg.rxTail != tail)
     {
       dmaCfg.rxTail = tail;
       // Re-sync the shadow on any 1st byte(s) received.
       if  (dmaCfg.rxTick == 0)
       {
         dmaCfg.rxShdw = ST0;
       }
       dmaCfg.rxTick = HAL_UART_DMA_IDLE;
     }
     else  if  (dmaCfg.rxTick)
     {
       // Use the LSB of the sleep timer (ST0 must be read first anyway).
       uint8 decr = ST0 - dmaCfg.rxShdw;
       if  (dmaCfg.rxTick > decr)
       {
         dmaCfg.rxTick -= decr;
         dmaCfg.rxShdw = ST0;
       }
       else
       {
         dmaCfg.rxTick = 0;
       }
     }
     cnt = HalUARTRxAvailDMA();
   }
   else
   {
     dmaCfg.rxTick = 0;
   }
   if  (cnt >= HAL_UART_DMA_FULL)
   {
     evt = HAL_UART_RX_FULL;
   }
   else  if  (cnt >= HAL_UART_DMA_HIGH)
   {
     evt = HAL_UART_RX_ABOUT_FULL;
     PxOUT |= HAL_UART_Px_RTS;
   }
   else  if  (cnt && !dmaCfg.rxTick)
   {
     evt = HAL_UART_RX_TIMEOUT;
   }
   if  (dmaCfg.txMT)
   {
     dmaCfg.txMT = FALSE;
     evt |= HAL_UART_TX_EMPTY;
   }
   if  (dmaCfg.txShdwValid)
   {
     uint8 decr = ST0;
     decr -= dmaCfg.txShdw;
     if  (decr > dmaCfg.txTick)
     {
       // No protection for txShdwValid is required
       // because while the shadow was valid, DMA ISR cannot be triggered
       // to cause concurrent access to this variable.
       dmaCfg.txShdwValid = FALSE;
     }
   }
   
   if  (dmaCfg.txDMAPending && !dmaCfg.txShdwValid)
   {
     // UART TX DMA is expected to be fired and enough time has lapsed since last DMA ISR
     // to know that DBUF can be overwritten
     halDMADesc_t *ch = HAL_DMA_GET_DESC1234(HAL_DMA_CH_TX);
     halIntState_t intState;
     // Clear the DMA pending flag
     dmaCfg.txDMAPending = FALSE;
     
     HAL_DMA_SET_SOURCE(ch, dmaCfg.txBuf[dmaCfg.txSel]);
     HAL_DMA_SET_LEN(ch, dmaCfg.txIdx[dmaCfg.txSel]);
     dmaCfg.txSel ^= 1;
     HAL_ENTER_CRITICAL_SECTION(intState);
     HAL_DMA_ARM_CH(HAL_DMA_CH_TX);
     do
     {
       asm( "NOP" );
     while  (!HAL_DMA_CH_ARMED(HAL_DMA_CH_TX));
     HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_TX);
     HAL_DMA_MAN_TRIGGER(HAL_DMA_CH_TX);
     HAL_EXIT_CRITICAL_SECTION(intState);
   }
   if  (evt && (dmaCfg.uartCB != NULL))
   {
     dmaCfg.uartCB(HAL_UART_DMA-1, evt);
   }
}

HalUARTPollDMA函数是整个串口操作的核心, 该函数会被系统大循环定时的调用,在这个函数里

会判断读写缓冲区的状态, 进而触发回调函数halUARTCfg_t.callBackFunc。在触发会调函数的时候,会传给回调函数几个事件,而这些事件涉及到以下4个值:

1
2
3
4
#define HAL_UART_DMA_FULL         (HAL_UART_DMA_RX_MAX - 16)
#define HAL_UART_DMA_HIGH         (HAL_UART_DMA_RX_MAX / 2 - 16)
#define HAL_UART_DMA_IDLE         (6 * HAL_UART_MSECS_TO_TICKS)
dmaCfg.txMT

当缓冲区数据长度大于等于HAL_UART_DMA_FULL 时, 触发HAL_UART_RX_FULL事件

当缓冲区数据长度大于等于HAL_UART_DMA_HIGH 时, 触发HAL_UART_RX_ABOUT_FULL事件

当缓冲区数据长度小于HAL_UART_DMA_FULL且等待时间达到HAL_UART_DMA_IDLE 时, 触发HAL_UART_TIMEOUT事件

当dmaCfg.txMT为真时,表明写缓冲区数据已经全部写入串口,触发HAL_UART_TX_EMPTY事件


所以用Z-Stack的hal_uart库对串口进行操作时, 推荐的做法是在回调函数里根据事件来判断是否需要读取数据,而写操作可以放到程序的任何位置,包括回调函数里, 写入数据的时候要判断一下返回值, 看数据是否真正写入到缓冲区中。


HalUARTPollDMA的调用频率大概是间隔200ms, 参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值