协议中UART的两种模式 【转】
转自:http://wjf88223.blog.163.com/blog/static/3516800120104179327286/
^^……
协议栈中UART有两种模式:
1、中断
2、DMA
对于这两种模式具体运用在哪一步,纠结了很久.通过UART配置结构:
typedef struct
{
uint8 *rxBuf;
uint8 rxHead;
uint8 rxTail;
uint8 rxMax;
uint8 rxCnt;
uint8 rxTick;
uint8 rxHigh;
uint8 *txBuf;
#if HAL_UART_BIG_TX_BUF
uint16 txHead;
uint16 txTail;
uint16 txMax;
uint16 txCnt;
#else
uint8 txHead;
uint8 txTail;
uint8 txMax;
uint8 txCnt;
#endif
uint8 txTick;
uint8 flag;
halUARTCBack_t rxCB;
} uartCfg_t;
可以看到协议栈为串口收发分别配置了一块内存空间rxBuf和txBuf,具体在HalUARTOpen()里配置.
而中断与DMA这两种模式具体就运用于 数据在串口缓存U0_1DBUF与rxBuf/txBuf之间传送 的过程.
串口接收DMA模式:(data) —> U0DBUF —(DMA)—> rxBuf —> HalUARTRead()读取rxBuf数据进行处理
串口接收中断模式:(data) —> U0DBUF —(中断)—> rxBuf —> HalUARTRead()读取rxBuf数据进行处理
串口发送DMA模式:(data) <— U0DBUF <—(DMA)— txBuf
串口发送中断模式:(data) <— U0DBUF <—(中断)— txBuf
我觉得这样理解好像还有问题,本身对DMA不了解,网上又几乎查不到关于协议栈有关UART_DMA模式具体
流程资料,卡在这里很久,所以打算建立个大概印象就跳出来,以后再看.
先记录下中断模式吧~
###########################################################################
###########################################################################
1、中断模式(UART接收)
当1写入UxCSR.RE位时,在UART上数据接收就开始了.然后UART会在输入引脚RXDx中寻找有效起始位,并且设置UxCSR.ACTIVE位为1.当检测出有效起始位时,收到的字节就传入接收寄存器,UxCSR.RX_BUTE位设置为1.该操作完成时,产生接收中断。通过寄存器UxBUF提供收到的数据字节。当UxBUF读出时,UxCSR.RX_BUTE位由硬件清零.当产生中断时,自然进入中断程序,看下UART0接收中断函数:
***************************************
#if HAL_UART_0_ENABLE
HAL_ISR_FUNCTION( halUart0RxIsr, URX0_VECTOR )
{
cfg0->rxBuf[cfg0->rxHead] = U0DBUF;
if ( cfg0->rxHead == cfg0->rxMax )
{
cfg0->rxHead = 0;
}
else
{
cfg0->rxHead++;
}
}
#endif
/**************************************
中断函数完成了把U0DBUF里一字节的数据传送到rxBuf[ ]存储空间去.这里rxHead是指向rxBuf[ ]的指针,看单词像是指在数组的头,其实应理解为rxBuf[ ]接收数据的个数(以字节为单位).rxMax是rxBuf[ ]可以存储最大字节数,为128.而后面当用HalUARTRead()来读取rxBuf[ ]时,rxTail应理解为rxBuf[]转移出去数据的个数(同样以字节为单位).那数据传送到rxBuf[ ]存储空间去后呢?先看下pollISR()
*****************************************************************************/
//大概每200ms调用pollISR()函数.当串口UxDBUF接收到一字节数据产生中断,在中断
//程序中把UxDBUF中数据传送到rxbuf[ ]中(这有个坎要跨过来,pollISR()200ms才被调用一次,而不是每次中断后都调用一次,如果串口接收的是大的数据包,则200ms内rxbuf[ ]已经接收了约48字节(这个后面分析),中断了48次??.当然如果串口没有接收到数据,也就是说没有发生串口接收中断,cfg应为是为空的,则cnt=0).此后pollISR()进行轮询,主要是重置超时时间和计算rxbuf[ ]还有多少数据没有读走(即cnt).然后再跳回到HalUARTPoll()函数进行下一步处理.
static void pollISR( uartCfg_t *cfg )
{
uint8 cnt = UART_RX_AVAIL( cfg ); //计算rxBuf[]中还有多少数据没有读出(以字节为单位)
if ( !(cfg->flag & UART_CFG_RXF) ) //UART_CFG_RXF:Rx flow is disabled.rx流控制未关闭
{
// If anything received, reset the Rx idle timer.
//如果又有新的数据接收到,则重置超时时间
if ( cfg->rxCnt != cnt )
{
cfg->rxTick = HAL_UART_RX_IDLE;
cfg->rxCnt = cnt;
}
/* It is necessary to stop Rx flow in advance of a full Rx buffer because
* bytes can keep coming while sending H/W fifo flushes.
*/
//当接收数据超过安全界限的时候,通过硬件流控制停止接收数据
if ( cfg->rxCnt >= (cfg->rxMax - SAFE_RX_MIN) )
{
RX_STOP_FLOW( cfg );
}
}
}
#endif
/******************************************************************************
pollISR()函数主要就是设置rxTick和rxCnt,发生接收中断并且接收中断函数处理完后,这些参数都会改变.
pollISR()执行完成后就跳回到HalUARTPoll()中.
看下 HalUARTPoll()
/**************************************
void HalUARTPoll( void )
{
#if ( HAL_UART_0_ENABLE | HAL_UART_1_ENABLE )
static uint8 tickShdw;
uartCfg_t *cfg;
uint8 tick;
//---------以下设置cfg
#if HAL_UART_0_ENABLE
if ( cfg0 ) //当串口接收到数据时,在中断函数或DMA程序中会改变cfg0值,如果没有接收到数据,cfg0
为空.
{
cfg = cfg0;
}
#endif
//---------
#if HAL_UART_1_ENABLE
if ( cfg1 )
{
cfg = cfg1;//同上
}
#endif
//---------
// Use the LSB of the sleep timer (ST0 must be read first anyway).
//睡眠定时器.ST0为睡眠定时器计数值的低8位
tick = ST0 - tickShdw;
tickShdw = ST0;
do
{
//------------------------发送超时时间
if ( cfg->txTick > tick )
{
cfg->txTick -= tick;
}
else
{
cfg->txTick = 0;
}
//------------------------接收超时时间
if ( cfg->rxTick > tick )
{
cfg->rxTick -= tick;
}
else
{
cfg->rxTick = 0;
}
//------------------------
#if HAL_UART_ISR
#if HAL_UART_DMA
if ( cfg->flag & UART_CFG_DMA )
{
pollDMA( cfg ); //pollDMA( cfg )
}
else
#endif
{
pollISR( cfg ); //pollISR( cfg )
}
#elif HAL_UART_DMA
pollDMA( cfg );
#endif
/* The following logic makes continuous callbacks on any eligible flag(合法标志)
* until the condition corresponding to the flag is rectified.
* So even if new data is not received, continuous callbacks are made.
*/
if ( cfg->rxHead != cfg->rxTail ) //初始化时rxHead=rxTail=0,不相等,rxbuf[ ]中有数据
{
uint8 evt;
if ( cfg->rxHead >= (cfg->rxMax - SAFE_RX_MIN) )
{
evt = HAL_UART_RX_FULL; //rxBuf接收[ ]满,则触发事件
}
else if ( cfg->rxHigh && (cfg->rxHead >= cfg->rxHigh) )
{
evt = HAL_UART_RX_ABOUT_FULL; //rxBuf[ ]接收到预设值(默认80字节),则触发事件
}
else if ( cfg->rxTick == 0 )
{
evt = HAL_UART_RX_TIMEOUT; //RX接收事件超时,则触发事件
}
else
{
evt = 0;
}
if ( evt && cfg->rxCB ) //有事件并且有回调函数
{
cfg->rxCB( ((cfg->flag & UART_CFG_U1F)!=0), evt ); //调用回调函数处理这些事件
}
}
………………
} while ( TRUE );
#else
return;
#endif
}
/**************************************
可以看到,初始化时rxHead=rxTail=0,如果发生接收中断,在中断服务函数中把UxDBUF中的数据传送到rxbuf[ ]中(这一步也可以在DMA服务程序中完成),则rxHead与rxTail值不等(rxHead是rxBuf[ ]接收数据的个数,rxTail是rxBuf[ ]转移出去数据个数),再根据两值的差值,判断具体事件evt,最后再调用回调函数:cfg->rxCB();回调函数在哪?回调函数 cfg->rxCB( ((cfg->flag & UART_CFG_U1F)!=0), evt ),在cfg中有halUARTCBack_t rxCB;则肯定有个地方配置cfg这个结构体,这个地方是HalUARTOpen( uint8 port, halUARTCfg_t *config );在这个函数中可以看到,结构体config的内容一项项赋给了结构体cfg,其中包括cfg->rxCB = config->callBackFunc;那肯定有个地方配置config这个结构体,这个地方是SPIMgr_Init (),这个函数有一句HalUARTOpen (SPI_MGR_DEFAULT_PORT, &uartConfig),*config与&uartConfig是同一halUARTCfg_t类型结构体,而且SPIMgr_Init ()函数中对uartConfig进行了定义,如下:
/* UART Configuration */
uartConfig.configured = TRUE;
uartConfig.baudRate = SPI_MGR_DEFAULT_BAUDRATE;
uartConfig.flowControl = 0;//SPI_MGR_DEFAULT_OVERFLOW;
uartConfig.flowControlThreshold = SPI_MGR_DEFAULT_THRESHOLD;
uartConfig.rx.maxBufSize = SPI_MGR_DEFAULT_MAX_RX_BUFF;
uartConfig.tx.maxBufSize = SPI_MGR_DEFAULT_MAX_TX_BUFF;
uartConfig.idleTimeout = SPI_MGR_DEFAULT_IDLE_TIMEOUT;
uartConfig.intEnable = TRUE;
#if defined (ZTOOL_P1) || defined (ZTOOL_P2)
uartConfig.callBackFunc = SPIMgr_ProcessZToolData; //回调函数
#elif defined (ZAPP_P1) || defined (ZAPP_P2)
uartConfig.callBackFunc = SPIMgr_ProcessZAppData; //回调函数
#else
uartConfig.callBackFunc = NULL;
#endif
来看下SPIMgr_ProcessZToolData ( uint8 port, uint8 event )
/*****************************************************
* @fn SPIMgr_ProcessZToolRxData
*
* @brief | SOP | CMD | Data Length | FSC | //帧格式
* | 1 | 2 | 1 | 1 |
*
* Parses the data and determine either is SPI or just simply serial data
* then send the data to correct place (MT or APP)
*
* @param pBuffer - pointer to the buffer that contains the data
* length - length of the buffer
*
*
* @return None
void SPIMgr_ProcessZToolData ( uint8 port, uint8 event )
{
uint8 ch;
/* Verify events */
if (event == HAL_UART_TX_FULL)
{
// Do something when TX if full
return;
}
if (event & (HAL_UART_RX_FULL | HAL_UART_RX_ABOUT_FULL | HAL_UART_RX_TIMEOUT))
{
while (Hal_UART_RxBufLen(SPI_MGR_DEFAULT_PORT)) //RxBuf[ ]中字节数,即有数据
{
HalUARTRead (SPI_MGR_DEFAULT_PORT, &ch, 1); //每次读取1字节到ch所指空间
//接下来对所读字节的state进行判断
switch (state) //state 参见上面宏定义/* State values for ZTool protocal */
{
case SOP_STATE:
if (ch == SOP_VALUE) //SOP_VALUE=0x02
state = CMD_STATE1;
break;
case CMD_STATE1:
CMD_Token[0] = ch;
state = CMD_STATE2;
break;
case CMD_STATE2:
CMD_Token[1] = ch;
state = LEN_STATE;
break;
case LEN_STATE:
LEN_Token = ch;
if (ch == 0)
state = FCS_STATE;
else
state = DATA_STATE; //state = DATA_STATE=0x04
tempDataLen = 0;
/* Allocate memory for the data */
SPI_Msg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof ( mtOSALSerialData_t ) +
2+1+LEN_Token );
if (SPI_Msg) //构造消息:把SOP字节和FCS字节去掉,剩下2字节的CMD和1字节的Data
{
/* Fill up what we can */
SPI_Msg->hdr.event = CMD_SERIAL_MSG; //CMD_SERIAL_MSG
SPI_Msg->msg = (uint8*)(SPI_Msg+1);
SPI_Msg->msg[0] = CMD_Token[0];
SPI_Msg->msg[1] = CMD_Token[1];
SPI_Msg->msg[2] = LEN_Token;
}
else
{
state = SOP_STATE;
return;
}
break;
case DATA_STATE:
SPI_Msg->msg[3 + tempDataLen++] = ch;
if ( tempDataLen == LEN_Token )
state = FCS_STATE;
break;
case FCS_STATE: //帧校验序列(FCS),则判断是接收数据是否正确
FSC_Token = ch;
/* Make sure it's correct */
if ((SPIMgr_CalcFCS ((uint8*)&SPI_Msg->msg[0], 2 + 1 + LEN_Token) == FSC_Token))
{
osal_msg_send( MT_TaskID, (byte *)SPI_Msg ); //接收数据正确则发送系统信息触发MT层任
务.事件为CMD_SERIAL_MSG
}
else
{
/* deallocate the msg */
osal_msg_deallocate ( (uint8 *)SPI_Msg);
}
/* Reset the state, send or discard the buffers at this point */
state = SOP_STATE;
break;
default:
break;
}
}
}
}
#endif*********************************************************
(这个函数只是总体上了解了下)
回调函数SPIMgr_ProcessZToolData()对rxbuf[]中的每一字节数据进行分析,并且构造一个发往系统的消息包(具体事件为SPI_Msg->hdr.event = CMD_SERIAL_MSG)然后调用osal_msg_send( MT_TaskID, (byte *)SPI_Msg )给系统发送消息,而下面肯定又会调用osal_set_event()来设置任务事件发生标志,最后调用MT层的事件处理函数,下面来看下MT层的事件处理函数:
/**********************************************************
* @fn MT_ProcessEvent
*
* @brief
*
* MonitorTest Task Event Processor. This task is put into the
* task table.
*
* @param byte task_id - task ID of the MT Task
* @param UINT16 events - event(s) for the MT Task
*
* @return void
*/
UINT16 MT_ProcessEvent( byte task_id, UINT16 events )
{
uint8 *msg_ptr;
………………
if ( events & SYS_EVENT_MSG )
{
while ( (msg_ptr = osal_msg_receive( MT_TaskID )) )
{
MT_ProcessCommand( (mtOSALSerialData_t *)msg_ptr );
}
………………
/*********************************************************************
可以看到它调用了MT_ProcessCommand( (mtOSALSerialData_t *)msg_ptr )来进行下一步的操作,来看下MT_ProcessCommand()这个函数:
/*********************************************************************
* @fn MT_ProcessCommand *
* @brief
*
* Process Event Messages.
*
* @param byte *msg - pointer to event message
*
* @return
*/
void MT_ProcessCommand( mtOSALSerialData_t *msg )
{
……………………
#if defined (ZTOOL_P1) || defined (ZTOOL_P2)
case CMD_SERIAL_MSG:
MT_ProcessSerialCommand( msg->msg );
break;
……………………
}
/*********************************************************************
通过上面的MT_ProcessCommand(),再根据先前SPI_Msg->hdr.event = CMD_SERIAL_MSG,可以得到它调用MT_ProcessSerialCommand( msg->msg )这个函数,而MT_ProcessSerialCommand()函数根据参数cmd来选择命令,包括读RAM,写RAM等等很多命令指令来对接收到的数据进行下一步操作(先不钻了……),对于这个cmd,cmd = BUILD_UINT16( msg[1], msg[0] ),SPIMgr_ProcessZToolData()对msg[1], msg[0]进行了配置:
SPI_Msg->msg[0] = CMD_Token[0];
SPI_Msg->msg[1] = CMD_Token[1];
而CMD_Token[0]与CMD_Token[1]的值又与SPIMgr_ProcessZToolData()中的参数ch密切相关,对于这个ch具体的定义,只是看到个uint8 ch;,没看到具体初始化为什么值.个人在想,是不是通过对参数ch的自由设定来选择串口命令cmd,通过cmd来实现对数据的不同处理……【现在发现这个应该是跟ZTOOL有关,晴天那天说ZTOOL上面可以发送不同命令的】
迷糊中!~~纠结中!~
上面分析了UART中断模式下,从接收到数据进入中断函数,把数据写入rxbuf[ ],200ms调用一次pollISR()轮询,跳回到HalUARTPoll()进行数据处理……………….那这个具体轮询时间rxTick约为200ms是怎么得出来的?借用《Z-STACK问题之串口结构uartCfg_t乱说》这篇文章的分析:
************
cfg->rxTick = HAL_UART_RX_IDLE
#define HAL_UART_RX_IDLE (6 * RX_MSECS_TO_TICKS)
#define RX_MSECS_TO_TICKS 33 (// The timeout tick is at 32-kHz, so multiply msecs by 33.)
(这个超时计数采用的是睡眠定时器,32KHZ时钟,一个微秒计数约33次)
因此cfg->rxTick=198,即大约200ms调用一次pollISR();
论证 #define SAFE_RX_MIN 48 // bytes - max expected per poll @ 115.2k
因为CC2430串口波特率为38400bps下,一个字节需要时间约为:4.16ms(这里……这?),那么198/4.16等于47.6(198ms是中断处理数据的间隔,这期间串口是不断接收数据,那在这期间能接收到多少数据呢?为47.6字节,因此要保证至少48字节的存储空间),约为48.所以这也就是上面定义这个48的原因。因为 cfg->rxTick定义了多久处理一次串口缓存区,而为了保证串口缓冲区不被新的数据覆盖,那么在这个间隔期间就必须保证大于48字节存储空间空闲。
*************
hal_uart.c有这段话:
/* Need to leave enough of the Rx buffer free to handle the incoming bytes
* after asserting flow control, but before the transmitter has obeyed it.
* At the max expected baud rate of 115.2k, 16 bytes will only take ~1.3 msecs,
* but at the min expected baud rate of 38.4k, they could take ~4.2 msecs.
* SAFE_RX_MIN and DMA_RX_DLY must both be consistent according to
* the min & max expected baud rate.
*/
我算了半天也算不出上面几个数来……悲剧!先这样吧……
对于串口里面的几个参数,后面直接转载《Z-STACK问题之串口结构uartCfg_t乱说》文章好了.
********************************************************
UART接收数据中断模式流程:(纯属个人理解,还有很多地方不清楚!)
(1)
ZMain.c调用HalDriverInit(),HalDriverInit调用HalUARTInit()初始化串口驱动程序任务初始化函数,MT_TaskInit()调用SPIMgr_Init()初始化串口配置
(2)
串口接收到数据产生中断,进入接收中断服务程序,把串口缓存UxDBUF中的数据传送到rxbuf[]中去.UxDBUF只能存放1字节,rxbuf[]能存放128字节,但要空出48字节作为安全区,因此接收数据达到80字节时作为一个临界值rxHigh,可以触发事件HAL_UART_RX_ABOUT_FUL.
(3)
系统主循环函数进入HalUARTPoll()轮询串口,大约每200ms调用一次pollISR(),然后跳回HalUARTPoll()对rxbuf[]中的数据进行处理——>调用回调函数SPIMgr_ProcessZToolData()分析rxbuf[]中每一字节,构造发往系统的消息——>调用osal_msg_send( MT_TaskID, (byte *)SPI_Msg )发送消息——>触发MT任务事件,调用MT任务事件处理函数 MT_ProcessEvent()——>调用MT_ProcessCommand(),根据msg->hdr.event——>调用MT_ProcessSerialCommand(),根据参数cmd来进行最终处理.
********************************************************
UART发送数据中断模式暂时不分析了,一头雾水……
############################################(下面小段于2010.5.25更新)
对于串口的回调函数,SPIMgr_Init()中配置了两个:
#if defined (ZTOOL_P1) || defined (ZTOOL_P2)
uartConfig.callBackFunc = SPIMgr_ProcessZToolData; //回调函数
#elif defined (ZAPP_P1) || defined (ZAPP_P2)
uartConfig.callBackFunc = SPIMgr_ProcessZAppData; //回调函数
#else
uartConfig.callBackFunc = NULL;
有什么区别呢?在《Zigbee技术规范与协议栈分析》这篇文章中看到过一段话:
作为协调器,如果程序使用了串口调试助手,则DMA将上位机的数据按照一个字节波特率加一个字节数据的形式组装到cfg->rxBuf中供其他函数调用,并且通过回调函数SPIMgr_ProcessZToolData ( uint8 port, uint8 event )将任务的ID和强制事件发送到任务列表中,供主循环处理函数扫描;作为终端节点和路由设备,无法使用串口调试助手,则通过回调函数 SPIMgr_ProcessZAppData ( uint8 port, uint8 event ) 将任务的ID和强制事件发送到任务列表中。当扫描至参数events=1,则进入相应层的处理程序进行任务ID和events的约定比对,完成相应的功能。
根据这段话个人在想:ZTOOL上可以发命令数据到ZC??,协调器调用SPIMgr_ProcessZToolData()进行处理,那ZR和ED的串口接收到数据(怎么接收?)调用SPIMgr_ProcessZAppData(),SPIMgr_ProcessZAppData()最终会调用osal_msg_send():
/**************************************
void SPIMgr_ProcessZAppData ( uint8 port, uint8 event )
{
…………
if ( msg_ptr )
{
msg_ptr->event = SPI_INCOMING_ZAPP_DATA;//SPI_INCOMING_ZAPP_DATA
msg_ptr->status = length;
/* Read the data of Rx buffer *///Rx buffer是读到上面为msg_ptr 分配的空间去的
HalUARTRead( SPI_MGR_DEFAULT_PORT, (uint8 *)(msg_ptr + 1), length );
/* Send the raw data to application...or where ever */
osal_msg_send( App_TaskID, (uint8 *)msg_ptr );
}
…………
}
/**************************************
至于把串口接收到的数据传送到哪里去,这就跟App_TaskID有关,而App_TaskID是通过下面这个函数来定义的:
/**************************************
* @fn MT_SerialRegisterTaskID
*
* @brief
*
* This function registers the taskID of the application so it knows
* where to send the messages whent they come in.
*
* @param void
*
* @return void
**************************************
void SPIMgr_RegisterTaskID( byte taskID )
{
App_TaskID = taskID;
}
/*************************************
可以看到,如果想把串口接收到的数据送到应用层任务(比如SampleApp)中,那这里应该注册SPIMgr_RegisterTaskID( SampleApp_TaskID ).这样就可以触发应用层任务(SampleApp)的事件(SPI_INCOMING_ZAPP_DATA),在事件处理函数中添加一个对事件SPI_INCOMING_ZAPP_DATA进行处理的程序就可以.
但是,我好像看到协调器设备的预编译里都是ZTOOL_P1,终端和路由功能的设备的预编译为XZTOOL_P1,没有编译ZAPP_P1和ZAPP_P2,也就是没有回调函数,是不是就不能进行串口传输了?
那如果我在终端和路由节点上面编译ZTOOL_P1是否可行??
那如果我在协调器上编译ZAPP_P1,再在应用任务事件处理函数中添加相应SPI_INCOMING_ZAPP_DATA事件的处理程序,串口接收的数据是否可以顺利传送到应用层??
——等有了实验套件,我再验证下!
个人在想,协调器串口接收的数据送到应用层任务中的方法有:
(1)通过默认的SPIMgr_ProcessZToolData()处理函数把数据发送到应用层供应用层使用(这点只是猜测,还没找着,毕竟MT_ProcessSerialCommand()函数这么多命令)
(2)通过SPIMgr_RegisterTaskID()注册相应TaskID,再通过SPIMgr_ProcessZAppData()处理函数把数据发送到相应任务中供其使用(也只是猜测,没有验证)
(3)通过串口读写函数HalUARTRead()和HalUARTWrite(),这两个函数可以在应用层中直接调用来读取串口接收到的数据以及通过串口发送数据。
HalUARTRead( uint8 port, uint8 *buf, uint16 len )//从rxbuf[ ]读取len长度的数据到*buf所指空间
HalUARTWrite( uint8 port, uint8 *buf, uint16 len )//把len长度的数据从*buf所指空间发送到txbuf[ ]
以上问题见《协议栈中串口的两种回调函数》记录。。。。。。。。
纯属个人想法,有待验证!
###########################################################################
###########################################################################
2、DMA模式(UART接收)
hal_board_cfg.h中有这段话:
/* The preferred method of implementation is by DMA for faster buad rate
* support. Customer may prefer to use the DMA channels for something else,
* in which case USART can be driven by ISR. Also, if the 2nd USART is to be
* used, this module does not currently support using 2 more DMA channels for
* it, so it must use ISR.
*/
对于USART操作,波特率高的情况下推荐使用DMA驱动模式.用户可能习惯把DMA应用在其它地方,这种情况下可以用中断模式.同样,如果第二个USART也被使用了,由于USART模块不能在同一时间支持两条以上DMA通道,因此这个时候必须使用中断来驱动.
********************
DMA:
允许不同速度的硬件装置来沟通,而不需要依于CPU 的大量中断负载。否则,CPU需要从来源把每一片段的资料复制到暂存器,然后把他们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,使CPU的效率大为提高。
********************
typedef struct {
uint8 srcAddrH;
uint8 srcAddrL;
uint8 dstAddrH;
uint8 dstAddrL;
uint8 xferLenV;
uint8 xferLenL;
uint8 ctrlA;
uint8 ctrlB;
} halDMADesc_t; //DMA描述符结构体
每一个通道都有相应的描述符.DMA传送首先要进行DMA参数配置,需要配置以下参数:
(1)源地址:DMA信道要读的数据的首地址
(2)目标地址:DMA信道从源地址读出的要写数据的首地址.目标地址要可写
(3)传送长度
(4)可变长度(VLEN)设置
(5)优先级别
(6)触发事件
(7)源地址和目标地址增量
(8)DMA传送模式
(9)字节传送或字传送
(10)中断屏蔽
(11)设置M8模式
CC2430中DMA有5条通道,UART默认的是哪个通道呢?
********************
HalUARTInit()中初始化为:
// Setup Tx by DMA.
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_TX );
// Setup Rx by DMA.
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_RX );
#define HAL_DMA_GET_DESC1234( a ) (dmaCh1234+((a)-1))
// Used by DMA macros to shift 1 to create a mask for DMA registers.
#define HAL_DMA_CH_TX 3 //协议栈默认的用于UART发送的DMA通道 映射后为DMA通道2
#define HAL_DMA_CH_RX 4 //协议栈默认的用于UART接收的DMA通道 映射后为DMA通道3
********************
对这两条分别用于发送与接收的DMA通道的配置是如何的?
********************
在HalUARTInit()中初始化为:
#if HAL_UART_DMA
//---------------------配置发送为DMA模式;数据从内存空间传送到DMA_UDBUF???
/*选择DMA通道*/
// Setup Tx by DMA.
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_TX ); //协议栈默认的用于UART发送的DMA通道映射后为DMA通道2
/*配置ch->dstAddrH/L*/
// The start address of the destination.目的地的起始地址
HAL_DMA_SET_DEST( ch, DMA_UDBUF ); //DMA_UDBUF为目的地,通过这个函数把DMA_UDBUF地址的高8位
//赋给ch->dstAddrH,低8位赋给ch->dstAddrL
/*配置ch->xferLenV*/
// Using the length field to determine how many bytes to transfer.
HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );//HAL_DMA_VLEN_USE_LEN=0x00 //配置VLEN域使用LEN字段
/*配置ch->ctrlA(通过多次配置ctrlA的每一位,同理包括ctrlB)*/
// One byte is transferred each time. //初始化为一次传输一字节
HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_BYTE );
/*配置ch->ctrlA*/
// The bytes are transferred 1-by-1 on Tx Complete trigger. //一次触发只传输一个字节,触发源为UART0 TX完成
HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE );
HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_TX );
/*配置ch->ctrlB*/
// The source address is decremented by 1 byte after each transfer.每次传送后源地址减1
HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_1 );
/*配置ch->ctrlB*/
// The destination address is constant - the Tx Data Buffer.目的地址不变,始终为Tx Data Buffer
HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_0 );
/*配置ch->ctrlB*/
// The DMA is to be polled and shall not issue an IRQ upon completion.传送完成后不传送中断请求
HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_DISABLE );
/*配置ch->ctrlB*/
// Xfer all 8 bits of a byte xfer. 8位一字节
HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );
/*配置ch->ctrlB*/
// DMA Tx has shared priority for memory access - every other one. 高优先级
HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );
//---------------------配置接收为DMA模式:数据从DMA_UDBUF传送到内存空间???
// Setup Rx by DMA.
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_RX );//
协议栈默认的用于UART接收的DMA通道 映射后为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 );
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 );
// 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 );
#endif
********************
对于发送,触发事件为HAL_DMA_TRIG_UTX1/0 :USART1/0 TX complete UART中数据发送完成就触发DMA从内存空间txbuf[ ]传送数据到串口??
对于接收,触发事件为HAL_DMA_TRIG_URX1/0:USART1/0 RX complete UART中数据接收完成就触发DMA把数据从串口缓存传送到内存空间rxbuf[ ]??
——我不敢确定!
事实上这一步所完成的任务和中断服务程序完成的任务是一样的,发送中断服务函数:把数据从txbuf[]传送到串口UDBUF;接收中断服务函数:把数据从UDBUF传送到rxbuf[ ];
UART接收数据DMA模式流程:(纯属个人理解,还有很多地方不清楚!)
(1)
ZMain.c调用HalDriverInit(),HalDriverInit调用HalDmaInit()初始化DMA通道01234的地址:
halDMADesc_t dmaCh0;
halDMADesc_t dmaCh1234[4];
HAL_DMA_SET_ADDR_DESC0( &dmaCh0 );
HAL_DMA_SET_ADDR_DESC1234( dmaCh1234 );
(2)
HalUARTInit()函数中对通道2和3进行配置.
(3)
系统主循环函数进入HalUARTPoll()轮询串口,大约每200ms调用一次 pollDMA()???,然后跳回HalUARTPoll()对rxbuf[]中的数据进行处理——>调用回调函数SPIMgr_ProcessZToolData()分析rxbuf[]中每一字节,构造发往系统的消息——>调用osal_msg_send( MT_TaskID, (byte *)SPI_Msg )发送消息——>触发MT任务事件,调用MT任务事件处理函数 MT_ProcessEvent()——>调用MT_ProcessCommand(),根据msg->hdr.event——>调用MT_ProcessSerialCommand(),根据参数cmd来进行最终处理.
DMA模式UART发送的暂不钻了.
###########################################################################
###########################################################################
对于UART的两种方式,实在有好多地方不搞不懂,以上个人记录也肯定有错误之处,希望哪位对这部分内容熟悉的高手能指点一下具体流程,或是在哪里看到相关资料给个链接提示,感激不尽!
###########################################################################
###########################################################################
说明:本文作者所记录,以上基本为个人见解,错误之处还请高手指点,本人随时更新,转载请注明出处,谢谢!
参考资料:《Z-STACK问题之串口结构uartCfg_t乱说》:http://hi.baidu.com/dapaopao/blog/item/ccf242900ef2e985a877a48a.html 已转载.
2010.5.17 ~XF
最近更新:2010.5.25 / 2010.6.05/2010.6.26