ZStack 串口应用笔记(一)

软件平台:ZStack-CC2530-2.5.1a\SerialApp

硬件平台:FB2530EB V1.0 2010-09-20

1.初始化串口DMA参数函数 HalUARTInitDMA(_hal_uart_dma.c)

串口外设端口选择P0.2-->RX,P0.3-->TX。

文档《CC253x_4x User's Guide.pdf》中关于DMA参数配置的说明。

DMA Configuration Setup

The DMA channel parameters such as address mode, transfer mode, and priority, described in the previous section, must be configured before a DMA channel can be armed and activated. The parameters are not configured directly through SFR registers, but instead they are written in a special DMA configuration-data structure in memory. Each DMA channel in use requires its own DMA configurationdata structure. The DMA configuration-data structure consists of eight bytes and is described in Section 8.6. A DMA configuration-data structure may reside at any location decided on by the user software, and the address location is passed to the DMA controller through a set of SFRs, DMAxCFGH:DMAxCFGL. Once a channel has been armed, the DMA controller reads the configuration data structure for that channel, given by the address in DMAxCFGH:DMAxCFGL. It is important to note that the method for specifying the start address for the DMA configuration data structure differs between DMA channel 0 and DMA channels 1 –4 as follows:
DMA0CFGH:DMA0CFGL gives the start address for the DMA channel 0 configuration data structure.
DMA1CFGH:DMA1CFGL gives the start address for the DMA channel 1 configuration data structure, followed by the channel 2–4 configuration-data structures.

 

DMA通道的地址模式、传输模式、优先级等参数并非直接改写SFR寄存器,而是以DMA配置数据结构的形式保存在RAM中。DMA0CFGH:DMA0CFGL组成的16位寄存器指定DMA通道0的配置数据结构的起始地址,DMA1CFGH:DMA1CFGL则指定DMA1通道1的配置数据结构的起始地址,通道2-4则紧随其后。可以从“hal_dma.c”中存储对应DMA通道配置数据结构的参数定义可以看出:
halDMADesc_t dmaCh0;//对应DMA通道0
halDMADesc_t dmaCh1234[4];//数组对应DMA通道1-4


TX使用DMA通道4,8位单字节传输,使能DMA中断。

 

RX使用DMA通道3,16位双字节传输,没有使用中断。其中RX DMA传输数据使用了一个技巧来判断是否收到新的数据。首先看一下文档《CC253x_4x User's Guide.pdf》中的Table 2-1. SFR Overview (continued)中U0DBUF地址为0xC1,U0BAUD地址为0xC2

ZStack 串口应用分析(一) - jinhang314 - 田遥的个人主页

当RX的DMA源地址设置为U0DBUF且以16位双字节传输时,小端模式下读回来的数据为((uint16)U0BAUD寄存器值<<8)+U0DBUF寄存器值,而寄存器U0BAUD的值在设定好串口的波特率后就是一个确定值,当把接收缓存区初始化为一个非U0BAUD值时,只要串口有接收到数据,接收缓存区对应的高字节就会更新为U0BAUD的值,以此来判断否收到新的数据。接收缓存区数据被读取后,设定对应的高字节为一个非U0BAUD值,zstack设定为(U0BAUD^0xFF)。

串口接收数据缓存区为dmaCfg.rxBuf。

【源码剖析】

 

static void HalUARTInitDMA(void)
{
  halDMADesc_t *ch;


  P2DIR &= ~P2DIR_PRIPO;
  P2DIR |= HAL_UART_PRIPO;           //P0外设优先级设置
                                    //  00:
                                    //1st priority: USART 0
                                    //2nd priority: USART 1
                                    //3rd priority: Timer 1
#if (HAL_UART_DMA == 1)
  PERCFG &= ~HAL_UART_PERCFG_BIT;    // Set UART0 I/O to Alt. 1 location on P0.
#else
  PERCFG |= HAL_UART_PERCFG_BIT;     // Set UART1 I/O to Alt. 2 location on P1.
#endif
  PxSEL  |= HAL_UART_Px_RX_TX;       // Enable Tx and Rx on P1.
  ADCCFG &= ~HAL_UART_Px_RX_TX;      // Make sure ADC doesnt use this.
  UxCSR = CSR_MODE;                  // Mode is UART Mode.
  UxUCR = UCR_FLUSH;                 // Flush it.


  // Setup Tx by DMA. TX DMA通道参数配置
  ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_TX );// TX使用DMA通道4


  // The start address of the destination.
  HAL_DMA_SET_DEST( ch, DMA_UDBUF );


  // Using the length field to determine how many bytes to transfer.
  HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );


  // One byte is transferred each time.
  HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_BYTE );


  // The bytes are transferred 1-by-1 on Tx Complete trigger.
  HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE );
  HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_TX );


  // The source address is incremented by 1 byte after each transfer.
  HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_1 );


  // The destination address is constant - the Tx Data Buffer.
  HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_0 );


  // The DMA Tx done is serviced by ISR in order to maintain full thruput.
  HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_ENABLE );


  // Xfer all 8 bits of a byte xfer.
  HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );


  // DMA has highest priority for memory access.
  HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );


  // Setup Rx by DMA. RX DMA通道参数配置
  ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_RX );// TX使用DMA通道3


  // The start address of the source.
  HAL_DMA_SET_SOURCE( ch, DMA_UDBUF );


  // Using the length field to determine how many bytes to transfer.
  HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );


  /* The trick is to cfg DMA to xfer 2 bytes for every 1 byte of Rx.
   * The byte after the Rx Data Buffer is the Baud Cfg Register,
   * which always has a known value. So init Rx buffer to inverse of that
   * known value. DMA word xfer will flip the bytes, so every valid Rx byte
   * in the Rx buffer will be preceded by a DMA_PAD char equal to the
   * Baud Cfg Register value.
   */
  HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_WORD );


  // The bytes are transferred 1-by-1 on Rx Complete trigger.
  HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE_REPEATED );
  HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_RX );


  // The source address is constant - the Rx Data Buffer.
  HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_0 );


  // The destination address is incremented by 1 word after each transfer.
  HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_1 );
  HAL_DMA_SET_DEST( ch, dmaCfg.rxBuf );
  HAL_DMA_SET_LEN( ch, HAL_UART_DMA_RX_MAX );


  // The DMA is to be polled and shall not issue an IRQ upon completion.
  HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_DISABLE );


  // Xfer all 8 bits of a byte xfer.
  HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );


  // DMA has highest priority for memory access.
  HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );

}

2.DMA通道配参数地址设定  HalDmaInit(hal_dma.c)

【源码剖析】

void HalDmaInit( void )
{
  HAL_DMA_SET_ADDR_DESC0( &dmaCh0 );//DMA通道0参数配置数据结构起始地址
  HAL_DMA_SET_ADDR_DESC1234( dmaCh1234 );//DMA通道1234参数配置数据结构起始地址
#if (HAL_UART_DMA || \
   ((defined HAL_SPI) && (HAL_SPI == TRUE))  || \
   ((defined HAL_IRGEN) && (HAL_IRGEN == TRUE)))
  DMAIE = 1;
#endif
}

3.打开串口  HalUARTOpenDMA(_hal_uart_dma.c)

设置串口回调函数、波特率、初始化接收数据缓存区、启动串口接收DMA传输

【源码剖析】

static void HalUARTOpenDMA(halUARTCfg_t *config)
{
  dmaCfg.uartCB = config->callBackFunc;//串口回调函数处理HalUARTPollDMA中触发事件
  // Only supporting subset of baudrate for code size - other is possible.
  HAL_UART_ASSERT((config->baudRate == HAL_UART_BR_9600) ||
                  (config->baudRate == HAL_UART_BR_19200) ||
                  (config->baudRate == HAL_UART_BR_38400) ||
                  (config->baudRate == HAL_UART_BR_57600) ||
                  (config->baudRate == HAL_UART_BR_115200));
  //串口波特率配置
  if (config->baudRate == HAL_UART_BR_57600 ||
      config->baudRate == HAL_UART_BR_115200)
  {
    UxBAUD = 216;
  }
  else
  {
    UxBAUD = 59;
  }
  
  switch (config->baudRate)
  {
    case HAL_UART_BR_9600:
      UxGCR = 8;
      dmaCfg.txTick = 35; // (32768Hz / (9600bps / 10 bits))
                          // 10 bits include start and stop bits.
                          //发送超时计时器,保证发送数据需要的时间间隔
      break;
    case HAL_UART_BR_19200:
      UxGCR = 9;
      dmaCfg.txTick = 18;
      break;
    case HAL_UART_BR_38400:
      UxGCR = 10;
      dmaCfg.txTick = 9;
      break;
    case HAL_UART_BR_57600:
      UxGCR = 10;
      dmaCfg.txTick = 6;
      break;
    default:
      // HAL_UART_BR_115200
      UxGCR = 11;
      dmaCfg.txTick = 3;
      break;
  }


  // 8 bits/char; no parity; 1 stop bit; stop bit hi.
  if (config->flowControl)
  {
    UxUCR = UCR_FLOW | UCR_STOP;
    PxSEL |= HAL_UART_Px_CTS;
    // DMA Rx is always on (self-resetting). So flow must be controlled by the S/W polling the Rx
    // buffer level. Start by allowing flow.
    PxOUT &= ~HAL_UART_Px_RTS;
    PxDIR |=  HAL_UART_Px_RTS;
  }
  else
  {
    UxUCR = UCR_STOP;
  }


  dmaCfg.rxBuf[0] = *(volatile uint8 *)DMA_UDBUF;  // Clear the DMA Rx trigger.
  HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_RX);
  HAL_DMA_ARM_CH(HAL_DMA_CH_RX);                   //启动RX DMA传输          
  osal_memset(dmaCfg.rxBuf, (DMA_PAD ^ 0xFF), HAL_UART_DMA_RX_MAX*2);//初始化接收缓存区为(U0BAUD ^ 0xFF)


  UxCSR |= CSR_RE;  //串口接收使能
  
  // Initialize that TX DMA is not pending
  dmaCfg.txDMAPending = FALSE;
  dmaCfg.txShdwValid = FALSE;
}

4. uartDMACfg_t结构解析(_hal_uart_dma.c)

【源码剖析】

typedef struct
{
  uint16 rxBuf[HAL_UART_DMA_RX_MAX];//接收数据缓存区
  rxIdx_t rxHead;  //接收数据缓存区头索引值                         
  rxIdx_t rxTail;    //接收数据缓存区尾索引值   
  uint8 rxTick;     //接收数据超时计时器,用来判断串口接收数据流的结束
  uint8 rxShdw;  //影子计时器,保存接收到新数据时计时器Sleep Timer 的值


  uint8 txBuf[2][HAL_UART_DMA_TX_MAX];//发送数据缓存区
  txIdx_t txIdx[2]; //发送数据缓存区索引值
  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;

5.主循环串口DMA查询   HalUARTPollDMA(_hal_uart_dma.c)

主循环调用HalUARTPollDMA函数,处理串口接收数据缓存区和发送数据缓存区的状态更新,触发响应的事件供串口回调函数在应用层处理。

uartDMACfg_t结构中的rxHead和rxTail是两个独立的索引,rxHead用来供HalUARTReadDMA函数从数据缓存区读取数据;而rxTail是用来处理结束数据流的结束超时标识。

uartDMACfg_t结构中定义了2个发送数据缓存区是因为在DMA发送数据时,已经使用的发送缓存区在DMA数据传送完成前不能再往里面写数据,在启动DMA触发启动时,发送缓存区的数据长度等参数已经确定,DMA不需要MCU再进行干预会按规则自动传输完成。如果在个过程中改写使用的发送缓存区数据会造成数据的紊乱。于是zstack提供了1个备用的发送缓存区,以供DMA传输时应用程序往发送缓存区写数据,2个缓存区交替使用。

【源码剖析】

 

static void HalUARTPollDMA(void)
{
  uint16 cnt = 0;
  uint8 evt = 0;
  //串口接收数据查询
  if (HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))//接收数据缓存区Head索引处收到新数据
  {
    rxIdx_t tail = findTail(); //找出接收数据缓存区Tail索引   


    // If the DMA has transferred in more Rx bytes, reset the Rx idle timer.
    if (dmaCfg.rxTail != tail)//DMA RX通道收到新的数据
    {
      dmaCfg.rxTail = tail;   //更新接收数据缓存区Tail索引  


      // Re-sync the shadow on any 1st byte(s) received.
      if (dmaCfg.rxTick == 0)
      {
        dmaCfg.rxShdw = ST0;  //接收到第一个字节时同步更新影子计时器值
      }
      dmaCfg.rxTick = HAL_UART_DMA_IDLE;//接收数据流结束超时计时(about 1ms)
    }
    else if (dmaCfg.rxTick)
    {
      // Use the LSB of the sleep timer (ST0 must be read first anyway).
      uint8 decr = ST0 - dmaCfg.rxShdw;//Sleep Timer计时器时间间隔


      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; //触发接收缓存区FULL事件
  }
  else if (cnt >= HAL_UART_DMA_HIGH)
  {
    evt = HAL_UART_RX_ABOUT_FULL; //触发接收缓存区ABOUT_FULL事件
    PxOUT |= HAL_UART_Px_RTS;  // Disable Rx flow.
  }
  else if (cnt && !dmaCfg.rxTick)
  {
    evt = HAL_UART_RX_TIMEOUT;//触发接收数据流结束超时事件
  }
  //串口发送数据查询
  if (dmaCfg.txMT)
  {
    dmaCfg.txMT = FALSE;
    evt |= HAL_UART_TX_EMPTY;//发送数据完成时触发TX缓存区EMPTY事件
  }


  if (dmaCfg.txShdwValid)   //串口发送影子计时器有效
  {
    uint8 decr = ST0;
    decr -= dmaCfg.txShdw;  //Sleep Timer时间间隔
    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; //超时,串口发送影子计时器无效,保证DMA数据发送完成,
                                  //防止发送未完成时,应用层再次触发DMA数据发送导致丢数据
    }
  }
  
  if (dmaCfg.txDMAPending && !dmaCfg.txShdwValid)//DMA发送数据完成处于空闲状态,缓存区有数据待发送
  {
    // 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;  //切换发送数据缓存区,方便应用程序调用HalUARTWriteDMA往串口发送缓存区写数据
    HAL_ENTER_CRITICAL_SECTION(intState);
    HAL_DMA_ARM_CH(HAL_DMA_CH_TX);//启动串口发送DMA数据传输
    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);
  }
  else
  {
    halIntState_t his;


    HAL_ENTER_CRITICAL_SECTION(his);
    if ((dmaCfg.txIdx[dmaCfg.txSel] != 0) && !HAL_DMA_CH_ARMED(HAL_DMA_CH_TX)
                                          && !HAL_DMA_CHECK_IRQ(HAL_DMA_CH_TX))
    {
      HAL_EXIT_CRITICAL_SECTION(his);
      HalUARTIsrDMA();//中断响应延迟时且发送缓存区有数据待发送时强制响应中断
    }
    else
    {
      HAL_EXIT_CRITICAL_SECTION(his);
    }
  }


  if (evt && (dmaCfg.uartCB != NULL))
  {
    dmaCfg.uartCB(HAL_UART_DMA-1, evt);//串口回调函数处理触发事件
  }
}

6.DMA中断响应函数   HAL_ISR_FUNCTION( halDmaIsr, DMA_VECTOR )(hal_dma.c)

调用HalUARTIsrDMA函数

【源码剖析】

HAL_ISR_FUNCTION( halDmaIsr, DMA_VECTOR )
{
  extern void HalUARTIsrDMA(void);


  HAL_ENTER_ISR();


  DMAIF = 0;


#if HAL_UART_DMA
  if (HAL_DMA_CHECK_IRQ(HAL_DMA_CH_TX))
  {
    HalUARTIsrDMA();
  }
#endif // HAL_UART_DMA


#if (defined HAL_SPI) && (HAL_SPI == TRUE)
  if ( HAL_DMA_CHECK_IRQ( HAL_DMA_CH_RX ) )
  {
    HAL_DMA_CLEAR_IRQ( HAL_DMA_CH_RX );
    HalSpiRxIsr();
  }


  if ( HAL_DMA_CHECK_IRQ( HAL_DMA_CH_TX ) )
  {
    HAL_DMA_CLEAR_IRQ( HAL_DMA_CH_TX );
    HalSpiTxIsr();
  }
#endif // (defined HAL_SPI) && (HAL_SPI == TRUE)


#if (defined HAL_IRGEN) && (HAL_IRGEN == TRUE)
  if ( HAL_IRGEN == TRUE && HAL_DMA_CHECK_IRQ( HAL_IRGEN_DMA_CH ) )
  {
    HAL_DMA_CLEAR_IRQ( HAL_IRGEN_DMA_CH );
    HalIrGenDmaIsr();
  }
#endif // (defined HAL_IRGEN) && (HAL_IRGEN == TRUE)

 


  CLEAR_SLEEP_MODE();
  HAL_EXIT_ISR();
}

7.串口DMA响应处理函数   HalUARTIsrDMA(_hal_uart_dma.c)

【源码剖析】

void HalUARTIsrDMA(void)
{
  HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_TX);//清除中断标识


  // Indicate that the other buffer is free now.
  dmaCfg.txIdx[(dmaCfg.txSel ^ 1)] = 0;//发送缓存区索引清零,在HalUARTPollDMA中已做切换,所以这里仍需切换回来
  dmaCfg.txMT = TRUE;
  
  // Set TX shadow
  dmaCfg.txShdw = ST0;
  dmaCfg.txShdwValid = TRUE;


  // If there is more Tx data ready to go, re-start the DMA immediately on it.
  if (dmaCfg.txIdx[dmaCfg.txSel])//准备发送备用的缓存区数据
  {
    // UART TX DMA is expected to be fired
    dmaCfg.txDMAPending = TRUE;
  }
}

posted @ 2015-02-06 08:47  Irving314  阅读(1278)  评论(0)    收藏  举报