串口通信
1、实验内容:PC端串口调试助手向板子发送数据,板子接受到数据后,再把数据发送回给PC端串口调试助手
2、串口发送接受数据的基本步骤:初始化串口(设置波特率、中断等)、向缓冲区发送数据或者从接受缓冲区读取数据。
然而,上面的步骤都是以前不带操作系统单片机的步骤,而在OSAL中已经实现了串口的读取函数和写入函数。可以作为API一样使用。
与串口相关的三个API函数:
uint8 HalUARTOpen(uint8 port,halUARTCfg_t * config);
uint16 HalUARTRead(uint8 port,uint8 *buf,uint16 len);
uint16 HalUARTWrite(uint8 port,uint8* buf,uint16 len);
3、在实验2基础上改动以实现串口的收发
//Coordinator.c
#include "OSAL.h"
#include "AF.h"
#include "ZDApp.h"
#include "ZDObject.h"
#include "ZDProfile.h"
#include
#include "Coordinator.h"
#include "DebugTrace.h"
#if !defined(WIN32) //????
#include "OnBoard.h"
#endif
#include "hal_lcd.h"
#include "hal_led.h"
#include "hal_key.h"
#include "hal_uart.h"
const cId_t GenericApp_ClusterList[GENERICAPP_MAX_CLUSTERS]=
{
GENERICAPP_CLUSTERID
};
//简单设备描述符(描述一个ZigBee设备节点)
const SimpleDescriptionFormat_t GenericApp_SimpleDesc=
{
GENERICAPP_ENDPOINT,
GENERICAPP_PROFID,
GENERICAPP_DEVICEID,
GENERICAPP_DEVICE_VERSION,
GENERICAPP_FLAGS,
GENERICAPP_MAX_CLUSTERS,
(cId_t*)GenericApp_ClusterList, //?????
0,
(cId_t *)NULL
};
endPointDesc_t GenericApp_epDesc;//节点描述符
byte GenericApp_TaskID;//任务优先级
byte GenericApp_TransID;//数据发送序列号。
unsigned char uartbuf[128];//串口接收发送数据缓冲单元
void GenericApp_MessageMSGCB(afIncomingMSGPacket_t *pckt);//消息处理函数
void GenericApp_SendTheMessage(void);//数据发送函数
static void rxCB(uint8 port,uint8 envent);//???????????
void GenericApp_Init(byte task_id)//任务初始化函数
{
GenericApp_TaskID =task_id; //初始化任务优先级(任务优先级有协议栈的操作系统OSAL分配)
GenericApp_TransID =0; //发送数据包的序号初始化为0
//对节点描述符进行初始化
GenericApp_epDesc.endPoint =GENERICAPP_ENDPOINT;
GenericApp_epDesc.task_id =&GenericApp_TaskID;
GenericApp_epDesc.simpleDesc =(SimpleDescriptionFormat_t*)&GenericApp_SimpleDesc;
GenericApp_epDesc.latencyReq =noLatencyReqs;
afRegister(&GenericApp_epDesc);//afRegister()对节点的描述符进行注册。注册后,才能使用OSAL提供的系统服务。
halUARTCfg_t uartConfig;//该结构体变量是实现 串口的配置
//串口的初始化
uartConfig.configured =TRUE;
uartConfig.baudRate =HAL_UART_BR_115200;//波特率
uartConfig.flowControl =FALSE; //流控制
uartConfig.callBackFunc =rxCB; //填的是回调函数 ,数的指针(即函数的地址)作为参数传递给另一个函数,
//其实callBackFunc是一个函数指针,它的定义为halUARTCBack_t callBackFunc;
//而halUARTCBack_t的定义为 typed void (*halUARTCBack_t)(uint8 port,uint8 envent) 定义的是一个函数指针
HalUARTOpen(0,&uartConfig); //串口是否打开
}
//下面这个是回调函数,回电函数就是一个通过函数指针(函数地址)调用的函数,如果把函数的指针(即函数的地址)作为参数传递给另一
//个函数,当通过该指正调用它锁指向的函数时,称为函数的回调。
//回调函数不是有该函数的实现方直接调用的,而是在特定的事件或条件时,由另一方调用的额,用于对该事件或条件进行响应。
//回调函数机制提供了系统对异步事件的处理能力。
static void rxCB(uint8 port,uint8 envent)
{
HalLedBlink(HAL_LED_2,0,50,500); //LED1 闪烁
HalUARTRead(0,uartbuf,10); //从串口读取数据放在uartbuf缓冲区中
if(osal_memcmp(uartbuf,"abcdefghij",10))//判断接受到的数据是否是www.wlwmaker.com,如果是,函数返回TURE
{
HalLedBlink(HAL_LED_1,0,50,500); //LED2 闪烁
HalUARTWrite(0,uartbuf,10); //将接收到的数字输出到串口
}
}
//消息处理函数
UINT16 GenericApp_ProcessEvent(byte task_id,UINT16 events)
{
/* afIncomingMSGPacket_t* MSGpkt;//MSGpkt用于指向接收消息结构体的指针
if(events&SYS_EVENT_MSG)
{
MSGpkt=(afIncomingMSGPacket_t*)osal_msg_receive(GenericApp_TaskID);//osal_msg_receive()从消息队列上接收消息
while(MSGpkt)
{
switch(MSGpkt->hdr.event)
{
case AF_INCOMING_MSG_CMD: //接受到新数据的消息的ID是AF_INCOMING_MSG_CMD,这个宏是在协议栈中定义好的值为0x1A
//接受到的是无线数据包
GenericApp_MessageMSGCB(MSGpkt);//功能是完成对接受数据的处理
break;
default:
break;
}
osal_msg_deallocate((uint8 *)MSGpkt);//接收到的消息处理完后,释放消息所占的存储空间
MSGpkt=(afIncomingMSGPacket_t*)osal_msg_receive(GenericApp_TaskID);
//处理完一个消息后,再从消息队列里接受消息,然后对其进行相应处理,直到所有消息处理完
}
return (events ^ SYS_EVENT_MSG);
}*/
// return 0;
}
void GenericApp_MessageMSGCB(afIncomingMSGPacket_t *pkt)
{
unsigned char buffer[4]= " ";
switch(pkt->clusterId)
{
case GENERICAPP_CLUSTERID:
osal_memcpy(buffer,pkt->cmd.Data,3);
if((buffer[0]=='L')&&(buffer[1]=='E')&&(buffer[2]=='D'))
{
HalLedBlink(HAL_LED_1,0,50,500); //LED2 闪烁
HalLedBlink(HAL_LED_2,0,50,500); //LED2 闪烁
HalLedBlink(HAL_LED_3,0,50,500); //LED2 闪烁
HalLedBlink(HAL_LED_4,0,50,500); //LED2 闪烁
}
else
{
HalLedSet(HAL_LED_2,HAL_LED_MODE_ON);//其他内容 LED2 亮 这几个函数在哪里定义了呢????
}
break;
}
}
4、编译、下载、仿真调试
编译过程中别忘了设置下面一步(这种叫做条件编译,用来控制不同的模块是否参与编译,以达到最大程度地节省存储资源资源)
PC端调试助手设置如下:
结果:(发送的abcdefghij,接受到的是abcdefghij的ASCII码值(十六进制的):61 62 63 64 65 66 67 68 69 6A )
仿真调试真是个调试,第一次在一个板子上搞了一个下午都没整出来,换了块板子一下子就搞好了,估计那块板的硬件有点问题。当自己重新下次进行调试要不行啦。当然,要学会IAR的Debug仿真调试,单步、查看变量的等方式去测试问题出在哪了,这个过程得都知识点的熟悉且斗志斗勇。重要的是耐心。得好好提高。
5、代码分析:
重要的代码莫非刚才提到的三个API函数
uint8 HalUARTOpen(uint8 port,halUARTCfg_t * config);
uint16 HalUARTRead(uint8 port,uint8 *buf,uint16 len);
uint16 HalUARTWrite(uint8 port,uint8* buf,uint16 len);
还有一个重要的数据结构halUARTCfg_t ,它用于定义了一个halUARTCfg_t uartConfig;变量,该结构体变量是用于实现 串口的配置
//串口的初始化
uartConfig.configured =TRUE;
uartConfig.baudRate =HAL_UART_BR_115200;//波特率
uartConfig.flowControl =FALSE; //流控制
uartConfig.callBackFunc =rxCB; //这是一个回调函数。(函数体见下面)
static void rxCB(uint8 port,uint8 envent)
{
HalLedBlink(HAL_LED_2,0,50,500); //LED1 闪烁
HalUARTRead(0,uartbuf,3); //从串口读取数据放在uartbuf缓冲区中
if(osal_memcmp(uartbuf,"abc",3))//判断接受到的数据是否是abc,如果是,函数返回TURE
{
HalLedBlink(HAL_LED_1,0,50,500); //LED2 闪烁
HalUARTWrite(0,uartbuf,3); //将接收到的数字输出到串口
}
}
回调函数,回电函数就是一个通过函数指针(函数地址)调用的函数,如果把函数的指针(即函数的地址)作为参数传递给另一个函数,当通过该指正调用它锁指向的函数时,称为函数的回调。
回调函数不是有该函数的实现方直接调用的,而是在特定的事件或条件时,由另一方调用的额,用于对该事件或条件进行响应。回调函数机制提供了系统对异步事件的处理能力。
6、串口工作原理
串口的初始化:用到了HalUARTOpen( )
Filename: _hal_uart.c
/******************************************************************************
* @fn HalUARTOpen
*
* @brief Open a port according tp the configuration specified by parameter.
*
* @param port - UART port
* config - contains configuration information
*
* @return Status of the function call
*****************************************************************************/
uint8 HalUARTOpen(uint8 port, halUARTCfg_t *config)
{
(void)port;
(void)config;
#if (HAL_UART_DMA == 1)
if (port == HAL_UART_PORT_0) HalUARTOpenDMA(config);//本实验只调用个这个函数,因此只要看这个函数即可,见下面
#endif
#if (HAL_UART_DMA == 2)
if (port == HAL_UART_PORT_1) HalUARTOpenDMA(config);
#endif
#if (HAL_UART_ISR == 1)
if (port == HAL_UART_PORT_0) HalUARTOpenISR(config);
#endif
#if (HAL_UART_ISR == 2)
if (port == HAL_UART_PORT_1) HalUARTOpenISR(config);
#endif
#if (HAL_UART_USB)
HalUARTOpenUSB(config);
#endif
return HAL_UART_SUCCESS;
}
HalUARTOpenDMA(config);
Filename: _hal_uart_dma.c
/******************************************************************************
* @fn HalUARTOpenDMA
*
* @brief Open a port according tp the configuration specified by parameter.
*
* @param config - contains configuration information
*
* @return none
*****************************************************************************/
static void HalUARTOpenDMA(halUARTCfg_t *config)//这个函数是对串口的波特率(关注UxBAUD和UxGCR两个寄存器的值)进行初始化并对DMA接收缓冲区进行初始化。
{
dmaCfg.uartCB = config->callBackFunc;
// 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寄存器的值的设置
{
UxBAUD = 216;
}
else
{
UxBAUD = 59;
}
switch (config->baudRate) //UxGCR寄存器的值的设置
{
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;
}
//DMA 的初始化
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);
osal_memset(dmaCfg.rxBuf, (DMA_PAD ^ 0xFF), HAL_UART_DMA_RX_MAX*2);
UxCSR |= CSR_RE;
// Initialize that TX DMA is not pending
dmaCfg.txDMAPending = FALSE;
dmaCfg.txShdwValid = FALSE;
}
在ZigBee协议栈中,串口是和DMA结合起来使用的,这样能降低CPU的负担。
调用 HalUARTOpen(0,&uartConfig); 这个函数,重要的参数是uartConfig,他是halUARTCfg_t型的参数
halUARTCfg结构体如下:
Filename: hal_uart.h
typedef struct
{
bool configured;
uint8 baudRate;
bool flowControl;
uint16 flowControlThreshold;
uint8 idleTimeout;
halUARTBufControl_t rx;
halUARTBufControl_t tx;
bool intEnable;
uint32 rxChRvdTime;
halUARTCBack_t callBackFunc;//回调函数
}halUARTCfg_t;
Filename: _hal_uart.c
/*****************************************************************************
* @fn HalUARTRead
*
* @brief Read a buffer from the UART
*
* @param port - USART module designation
* buf - valid data buffer at least 'len' bytes in size
* len - max length number of bytes to copy to 'buf'
*
* @return length of buffer that was read
*****************************************************************************/
uint16 HalUARTRead(uint8 port, uint8 *buf, uint16 len)
{
(void)port;
(void)buf;
(void)len;
#if (HAL_UART_DMA == 1)
if (port == HAL_UART_PORT_0) return HalUARTReadDMA(buf, len);//本实验仅需要关注这个函数 见下面那个函数
#endif
#if (HAL_UART_DMA == 2)
if (port == HAL_UART_PORT_1) return HalUARTReadDMA(buf, len);
#endif
#if (HAL_UART_ISR == 1)
if (port == HAL_UART_PORT_0) return HalUARTReadISR(buf, len);
#endif
#if (HAL_UART_ISR == 2)
if (port == HAL_UART_PORT_1) return HalUARTReadISR(buf, len);
#endif
#if HAL_UART_USB
return HalUARTRx(buf, len);
#else
return 0;
#endif
}
Filename: _hal_uart_dma.c
/*****************************************************************************
* @fn HalUARTReadDMA
*
* @brief Read a buffer from the UART
*
* @param buf - valid data buffer at least 'len' bytes in size
* len - max length number of bytes to copy to 'buf'
*
* @return length of buffer that was read
*****************************************************************************/
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 HAL_UART_DMA_RX_MAX == 256
(dmaCfg.rxHead)++;
#else
if (++(dmaCfg.rxHead) >= HAL_UART_DMA_RX_MAX)
{
dmaCfg.rxHead = 0;
}
#endif
}
PxOUT &= ~HAL_UART_Px_RTS; // Re-enable the flow on any read.
return cnt;
}
// Filename: _hal_uart.c
/******************************************************************************
* @fn HalUARTWrite
*
* @brief Write a buffer to the UART.
*
* @param port - UART port
* buf - pointer to the buffer that will be written, not freed
* len - length of
*
* @return length of the buffer that was sent
*****************************************************************************/
uint16 HalUARTWrite(uint8 port, uint8 *buf, uint16 len)
{
(void)port;
(void)buf;
(void)len;
#if (HAL_UART_DMA == 1)
if (port == HAL_UART_PORT_0) return HalUARTWriteDMA(buf, len);//本实验仅需要关注这个函数 见下面那个函数
#endif
#if (HAL_UART_DMA == 2)
if (port == HAL_UART_PORT_1) return HalUARTWriteDMA(buf, len);
#endif
#if (HAL_UART_ISR == 1)
if (port == HAL_UART_PORT_0) return HalUARTWriteISR(buf, len);
#endif
#if (HAL_UART_ISR == 2)
if (port == HAL_UART_PORT_1) return HalUARTWriteISR(buf, len);
#endif
#if HAL_UART_USB
HalUARTTx(buf, len);
return len;
#else
return 0;
#endif
}
// Filename: _hal_uart_dma.c
/******************************************************************************
* @fn HalUARTWriteDMA
*
* @brief Write a buffer to the UART.
*
* @param buf - pointer to the buffer that will be written, not freed
* len - length of
*
* @return length of the buffer that was sent
*****************************************************************************/
static uint16 HalUARTWriteDMA(uint8 *buf, uint16 len)
{
uint16 cnt;
halIntState_t his;
uint8 txSel;
txIdx_t txIdx;
// 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;
}
得学会调试程序才行。还有我不清的是什么时候什么原因才能触发去调用下面的回调函数,然后进行读写串口???
static void rxCB(uint8 port,uint8 envent)
{
HalUARTRead(0,uartbuf,10); //从串口读取数据放在uartbuf缓冲区中
if(osal_memcmp(uartbuf,"abcdefghij",10))//判断接受到的数据是否是www.wlwmaker.com,如果是,函数返回TURE
{
HalLedBlink(HAL_LED_1,0,50,500); //LED2 闪烁
HalUARTWrite(0,uartbuf,10); //将接收到的数字输出到串口
}
}
本文参考自:《ZigBee无线传感器网络设计与实现》 王小强等人编著化学工业出版社