z-stack协议栈串口驱动详解与同时使用两个串口的配置方法

更新时间:2023-09-13 11:21:01 阅读量: 教学研究 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Z-Stack串口操作:

CC2530共有两个串口,UART0和UART1,两个串口可以通过设置寄存器PERCFG的值映射到不同的引脚上,对应的映射关系如下:

端口 串口0 位置1 位置2 串口1 位置1 位置2 P0 5 RT RX P1 4 3 2 7 CT TX RX TX RT CT RX 6 TX 5 TX RT 4 RX CT 3 RT 2 CT

PERCFG寄存器相关位的描述如下: 位 名称 复位 1 U1CFG 0 0 U0CFG 0 读/写 读/写 读/写 描述 串口1端口映射。0:位置1;1:位置2 串口0端口映射。0:位置1;1:位置2 在CC2530中提供了两种方式来操作串口,即直接存取访问(DMA)和中断(ISR)方式,两种操作方式的区别在这里不再叙述,想了解的请自行查阅相关资料。

在z-stack中默认的使用DMA方式来操作串口,默认配置的串口为UART0,UART0默认使用位置1,即P02为UART的RX,P03为UART的TX。接下来将详细介一下Z-STACK串口默认的配置流程,并介绍如何配置同时使用两个串口。

一、 串口默认配置的流程

从main函数开始,串口的初始化化函数在调用HalDriverInit()函数时被调用,在HalDriverInit函数中调用了函数HalUARTInit()函数,函数原型如下:

void HalUARTInit(void) {

#if HAL_UART_DMA HalUARTInitDMA(); #endif

#if HAL_UART_ISR HalUARTInitISR(); #endif

#if HAL_UART_USB HalUARTInitUSB(); #endif }

可以看出,协议栈通过判断宏定义的方法来判断以何种方式来初始化串口,有关的宏可以在hal_board_cfg.h中找到,先将与串口有关的宏列出以便分析:

* Set to TRUE enable UART usage, FALSE disable it */

#ifndef HAL_UART //如果未定义HAL_UART

/*如果定义了defined ZAPP_P1、defined ZAPP_P2、defined ZTOOL_P1、defined ZTOOL_P2中的一个,就定义HAL_UART为1,协议栈默认地定义了ZTOOL_P1,可以点击project->options->c/c++complier->preprocess->defined symbols来查看预编译的宏*/

#if (defined ZAPP_P1) || (defined ZAPP_P2) || (defined ZTOOL_P1) || (defined ZTOOL_P2) #define HAL_UART TRUE

#else //否则的话在进行下面的

#define HAL_UART FALSE //将HAL_UART定义为0,即不使用串口 #endif #endif

#if HAL_UART //如果HAL_UART为1

// Always prefer to use DMA over ISR.总是优先地使用DMA方式 #if HAL_DMA

#ifndef HAL_UART_DMA

#if (defined ZAPP_P1) || (defined ZTOOL_P1) #define HAL_UART_DMA 1

#elif (defined ZAPP_P2) || (defined ZTOOL_P2) #define HAL_UART_DMA 2 #else

#define HAL_UART_DMA 1 #endif #endif

/*执行完上面的一部分代码后,HAL_UART_DMA的值就为1了,具体自行分析 #define HAL_UART_ISR 0 //默认不实用ISR方式来处理中断

/*在这里已经定义了HAL_UART_ISR,所以就不会进入下面的#ifndef HAL_UART_ISR 判断了*/ #else

#ifndef HAL_UART_ISR

#if (defined ZAPP_P1) || (defined ZTOOL_P1) #define HAL_UART_ISR 1

#elif (defined ZAPP_P2) || (defined ZTOOL_P2) #define HAL_UART_ISR 2 #else

#define HAL_UART_ISR 1 #endif #endif

#define HAL_UART_DMA 0 #endif

/*执行完上面的代码后,HAL_UART_ISR就被定义为0了*/

// Used to set P2 priority - USART0 over USART1 if both are defined. #if ((HAL_UART_DMA == 1) || (HAL_UART_ISR == 1)) #define HAL_UART_PRIPO 0x00 #else

#define HAL_UART_PRIPO 0x40 #endif

/*上述代码设置了两个串口的优先级,串口0大于串口1*/

/*这里的#else对应的是#if HAL_UART,意思就是如果串口使用,就将两种方式的宏都定义为0*/ #else

#define HAL_UART_DMA 0 #define HAL_UART_ISR 0 #endif

/* USB is not used for CC2530 configuration */ #define HAL_UART_USB 0

上述代码的if和else对应的关系可能很难看出,可以用notepad打开源文件来查看对应关系。

总结一下,上述代码完成的功能就是将HAL_UART_DMA宏设置为1,将HAL_UART_ISR宏设置为0。

然后我们再回到HalUARTInit(void)函数,可以看出在这里调用的串口初始化函数是HalUARTInitDMA()函数,函数原型如下(省略了部分不必要的代码): static void HalUARTInitDMA(void) {

halDMADesc_t *ch; //定义了一个DMA通道 /*设置串口优先级*/ P2DIR &= ~P2DIR_PRIPO; P2DIR |= HAL_UART_PRIPO;

#if (HAL_UART_DMA == 1) //默认配置会执行这里的语句 /*设置UART0在位置1,P0口*/

PERCFG &= ~HAL_UART_PERCFG_BIT; // Set UART0 I/O to Alt. 1 location on P0. #else

/*否则的话UART0在位置2,P1口*/

PERCFG |= HAL_UART_PERCFG_BIT; // Set UART1 I/O to Alt. 2 location on P1. #endif

/*设置P0口的P02、P03为外设功能,P02为RX、P03为TX*/ PxSEL |= HAL_UART_Px_RX_TX; // Enable Tx and Rx on P1.

/*禁用P02和P03的ADC功能,以防止对串口通信产生干扰。TI的工程师是挺细心的,其实我们在使用的时候也不会傻到把这两个端口硬件连接为ADC吧*/

ADCCFG &= ~HAL_UART_Px_RX_TX; // Make sure ADC doesnt use this. /*设置为UART模式,还可配置为SPI模式*/

UxCSR = CSR_MODE; // Mode is UART Mode. /**清除单元/

UxUCR = UCR_FLUSH; // Flush it. /*未列出DMA初始化的一些代码*/ ……………………. }

上面的函数执行完成以后就基本上完成了串口的初始化,包括串口处理方式、串口对应的引脚等,进一步的串口初始化设置在HalUARTOpenDMA(halUARTCfg_t *config)函数中被完成,该函数的原型如下,在看懂这个函数之前我们需要先了解一下结构体halUARTCfg_t这个结构

体的定义在hal_uart.h文件中,原型如下: typedef struct {

bool configured; //是否注册串口

uint8 baudRate; //串口通信波特率设置 bool flowControl; //硬件流控制

uint16 flowControlThreshold; //don’t care uint8 idleTimeout; //don’t care halUARTBufControl_t rx; //don’t care halUARTBufControl_t tx; //don’t care

bool intEnable; //don’t care uint32 rxChRvdTime; //don’t care halUARTCBack_t callBackFunc; //串口回调函数 }halUARTCfg_t;

可以看出上述结构体完成了串口初始化所需要的各项参数,包括在OSAL中注册串口,通信波特率的设置等,同时HalUARTOpenDMA函数的形参也是这个结构体,也就是说这些初始化将在这个函数中被完成,下面来通过注释来分析这个函数。 static void HalUARTOpenDMA(halUARTCfg_t *config) {

/*注册串口回调函数,串口收到数据后会调用回调函数*/ 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 = 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; }

/*上面的部分代码通过switch判断完成了对串口波特率的设置,具体的寄存器值可以参考下面的波特率寄存器设置表*/

// 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; //停止位为高电平 }

/*don’t care*/

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; } 波特率 2400 4800 9600 14400 19200 28800 38400 57600 76800 115200 230400 UxBAUD.BAUD_M[7:0] 59 59 59 216 59 216 59 216 59 216 216 UxGCR.BAUD_E 6 7 8 8 9 9 10 10 11 11 12 误差(%) 0.14 0.14 0.14 0.03 0.14 0.03 0.14 0.03 0.14 0.03 0.03 至此,串口的初始化已经全部完成了,我们在使用串口时只需要调用写串口函数HalUARTWrite(uint8 port, uint8 *buf, uint16 len)和串口读函数HalUARTRead(uint8 port, uint8 *buf, uint16 len)就行了,在这两个函数的内部同样是使用判断宏的方法来调用,DMA串口读写函数还是ISR串口读写函数,关于串口读写方式的具体实现方式在这里不再详细介绍,毕竟在使用时我们并不需要对它们进行改动。

上面结介绍了串口初始化的流程,接下来介绍如何在协议栈中配置使用串口。

二、 在协议栈中配置使用串口

首先确定你在options选项下定义了宏来使用串口功能,比如说宏ZTOOL_P1,如下图所示:

如果你的协议栈是默认的设置,就是装上以后没有修改过,那就按照下面的步骤来,如果是你修改使用过的,就只修改和你的不同的地方就行了。

首先新建两个C文件,GenericAppCoordinator.c和GenericAppEnddevice.c,然后将协议栈中原来的GenericApp中的内容分别复制进新建的两个文件中,这样做是为了能清除地区分不同设备类型的程序,然后将新建的两个文件添加到协议栈的APP层。

首先修改GenericAppCoordinator.c文件,修改GenericApp_Init函数如下(只写出并注释了添加的部分,省略部分和原代码相同): void GenericApp_Init( byte task_id ) {

halUARTCfg_t uartConfig; //定义一个串口配置有关的结构体类型 …………

uartConfig.configured = TRUE; //注册串口

uartConfig.baudRate = HAL_UART_BR_9600; //波特率设置为9600 uartConfig.flowControl = FALSE; //禁用硬件流控

uartConfig.flowControlThreshold = 1; // 2x30 don't care - see uart driver.

uartConfig.rx.maxBufSize = 255; // 2x30 don't care - see uart driver. uartConfig.tx.maxBufSize = 255; // 2x30 don't care - see uart driver.

uartConfig.idleTimeout = 1; // 2x30 don't care - see uart driver. uartConfig.intEnable = TRUE; // 2x30 don't care - see uart driver. uartConfig.callBackFunc =rxCB; //注册串口回调函数 HalUARTOpen (0,&uartConfig);//打开串口0 ………… }

将GenericApp_MessageMSGCB函数中的内容替换如下:

void GenericApp_MessageMSGCB( afIncomingMSGPacket_t *pkt ) {

uint8 sendBuff[11]; //定义一个发送的数据, switch ( pkt->clusterId ) {

case GENERICAPP_CLUSTERID:

osal_memcpy(&sendBuff,pkt->cmd.Data,sizeof(sendBuff));//将数据包中的数据拷贝到发送数组中

HalUARTWrite(0,(uint8*)&sendBuff,sizeof(sendBuff)); //将接收到的数据输出到串口 break; } }

在文件开头声明串口回调函数:

/********************************************************************* * LOCAL FUNCTIONS */

void GenericApp_ProcessZDOMsgs( zdoIncomingMsg_t *inMsg ); void GenericApp_HandleKeys( byte shift, byte keys );

void GenericApp_MessageMSGCB( afIncomingMSGPacket_t *pckt );

void GenericApp_SendTheMessage( void );

void rxCB(uint8 port,uint8 event); …………

在文件的最后添加串口回调函数: void rxCB(uint8 port,uint8 event) {

/*添加自定义的功能,用来读出串口发来的数据*/ }

以上是协调器端的代码修改,具体的功能就是当协调器接收到无线数据后,就通过串口0将收到的无线数据通过串口发送,这样我们就可以在电脑上用串口调试软件来观察接收到的无线数据了。

要实现上述的功能还需要另外一个设备来向协调器发送无线数据,这里我们用一个终端节点设备来向协调器发送无线数据。

接着来修改GenericApp_Enddevice文件中的代码,GenericApp_Init函数的修改和GenericApp_Coordinator.c中的修改一样。

然后在GenericApp_ProcessEvent( byte task_id, UINT16 events )函数中,如果终端节点设备加入网络(网络状态改变),我们就触发发送数据的事件,代码修改如下(省略部分和原代码相同):

UINT16 GenericApp_ProcessEvent( byte task_id, UINT16 events ) {

…………

case AF_INCOMING_MSG_CMD:

GenericApp_MessageMSGCB( MSGpkt ); break;

case ZDO_STATE_CHANGE:

GenericApp_NwkState = (devStates_t)(MSGpkt->hdr.status); if ( (GenericApp_NwkState == DEV_ZB_COORD) || (GenericApp_NwkState == DEV_ROUTER)

|| (GenericApp_NwkState == DEV_END_DEVICE) ) {

// Start sending \osal_set_event(GenericApp_TaskID,GENERICAPP_SEND_MSG_EVT); }

break; …………

if ( events & GENERICAPP_SEND_MSG_EVT ) {

// Send \

GenericApp_SendTheMessage();

// Setup to send message again

osal_start_timerEx( GenericApp_TaskID,

GENERICAPP_SEND_MSG_EVT, GENERICAPP_SEND_MSG_TIMEOUT );

// return unprocessed events return (event ………… }

上述代码的意思时,如果终端节点的网络状态发生改变(加入网络),就设置事件GENERICAPP_SEND_MSG_EVT,如果事件GENERICAPP_SEND_MSG_EVT发生(被设置),就调用函数GenericApp_SendTheMessage()来发送无线数据,同时使用osal_start_timerEx函数来定时触发GENERICAPP_SEND_MSG_EVT事件,也就是每GENERICAPP_SEND_MSG_TIMEOUT时间发送一次数据。

既然要发送数据,我们还需要在GenericApp_SendTheMessage()函数中做些修改,将函数修改成如下内容:

void GenericApp_SendTheMessage( void ) {

char theMessageData[] = \要发送的数据

GenericApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit; //使用16位的短地址发送数据 GenericApp_DstAddr.endPoint = GENERICAPP_ENDPOINT; //数据源为终端节点

GenericApp_DstAddr.addr.shortAddr = 0x0000; //数据发送的目标地址为0X0000,即协调器

if ( AF_DataRequest( &GenericApp_DstAddr, &GenericApp_epDesc, GENERICAPP_CLUSTERID,

(byte)osal_strlen( theMessageData ) + 1, (byte *)&theMessageData, &GenericApp_TransID,

AF_DISCV_ROUTE, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) {

// Successfully requested to be sent. } else {

// Error occurred in request to send. } }

剩下的就是串口回调函数了,处理方法和协调器那里的相同。 至此串口的配置就全部完成了,将程序非别烧到协调器和终端节点,然后用串口调试软件监测协调器的串口0,发现会以5秒每次的频率接收到Hello World数据。

前面讲过,协议栈默认地以DMA的方式来处理串口,而且默认的是使用串口0的,但是如果我们在项目中要用到两个串口时该怎么办呢?接下来就介绍如何在协议栈中配置以同时使用两个串口。

三、 在协议栈中配置以同时使用两个串口

要想同时使用两个串口,就需要以两种不同的方式来处理串口,既然串口0默认使用了DMA方式,那我们就看一下如何以ISR的方式来处理串口1。首先是初始化,在前面我们分析过,在串口的初始化函数HalUARTInit中是以判断宏的方式来决定以什么样的方式来对串口进行初始化的,同时我们也分析过在hal_board_cfg.h中宏HAL_UART_DMA被定义为了1,而宏HAL_UART_ISR被定义为了0,所以我们要在hal_board_cfg.hR文件中将宏HAL_UART_ISR定义为非0值,那么这个非0值是几呢?我们来看一下HalUARTOpen函数中的代码来确定: uint8 HalUARTOpen(uint8 port, halUARTCfg_t *config) {

(void)port; (void)config;

#if (HAL_UART_DMA == 1)//HAL_UART_DMA为1,所以会执行下面的语句 /*只有在串口号为0的时候才会以DMA的方式打开串口0*/ 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) //如果将HAL_UART_ISR 设置为1,就会执行下面的语句 /*只有在串口号为0的时候才会以ISR的方式打开串口0*/ if (port == HAL_UART_PORT_0) HalUARTOpenISR(config); #endif

#if (HAL_UART_ISR == 2)// 如果将HAL_UART_ISR 设置为2,就会执行下面的语句 /*只有在串口号为1的时候才会以ISR的方式打开串口0*/ if (port == HAL_UART_PORT_1) HalUARTOpenISR(config); #endif

#if (HAL_UART_USB)

HalUARTOpenUSB(config); #endif

return HAL_UART_SUCCESS; }

通过上面所加的注释我们大致可以看出,串口0被固定地只能用DMA的方式来处理,也就是说如果想使用串口1的话就必须使用ISR的方式来处理串口1,也就是说给HAL_UART_ISR定义的非0值只能是2,明白了这一点以后,我们就在hal_board_cfg.h修改HAL_UART_ISR的宏定义为2,修改如下:

/* Set to TRUE enable UART usage, FALSE disable it */ …………

#define HAL_UART_DMA 1 #endif #endif

#define HAL_UART_ISR 2 #else …………

这样的话,在初始化时就可以同时对两个串口以不同的方式初始化了,但是要想使用串口1的话还需要设置串口1的位置(位置1或者2),也就是串口以所在的端口,以及其他相关的寄存器,要修改的位置在_hal_uart_isr.c文件中,在这里我们将串口1配置到位置1,也就是P04作为UART1的TX,P05作为UART1的RX,首先要将文件中所有的预编译判断#if (HAL_UART_ISR == 1)修改为#if (HAL_UART_ISR == 2),这是因为我们将他的宏定义改成了2,然后修改串口1配置有关的寄存器宏定义如下(我将默认的注释掉了): #if (HAL_UART_ISR == 2) //#if (HAL_UART_ISR == 1) /*unenable the default**********/ /*

#define PxOUT P0 #define PxDIR P0DIR #define PxSEL P0SEL #define UxCSR U0CSR #define UxUCR U0UCR #define UxDBUF U0DBUF #define UxBAUD U0BAUD #define UxGCR U0GCR #define URXxIE URX0IE #define UTXxIE UTX0IE #define UTXxIF UTX0IF */

/****************END**************/

/***************use add*******************/

#define PxOUT P0 //UART1在P0口位置 #define PxDIR P0DIR //端口0方向控制寄存器 #define PxSEL P0SEL //端口0功能选择

#define UxCSR U1CSR //UART1控制和状态寄存器 #define UxUCR U1UCR //UART1控制寄存器

#define UxDBUF U1DBUF //UART1发送和接收数据缓冲区 #define UxBAUD U1BAUD //UART1波特率设置寄存器 #define UxGCR U1GCR //UART1通用控制 #define URXxIE URX1IE //UART1 RX中断使能 #define UTXxIE UTX1IE //UART1 TX中断使能 #define UTXxIF UTX1IF //UART1 TX中断标志 /****************END***************/

接着是串口1位置有关的寄存器宏定义:

#if (HAL_UART_ISR == 2) //#if (HAL_UART_ISR == 1) /***********unenable the default************/ /*

#define HAL_UART_PERCFG_BIT 0x01 // USART0 on P0, Alt-1; so clear this bit.

#define HAL_UART_Px_RX_TX 0x0C // Peripheral I/O Select for Rx/Tx. #define HAL_UART_Px_RTS 0x20 // Peripheral I/O Select for RTS. #define HAL_UART_Px_CTS 0x10 // Peripheral I/O Select for CTS.

*/

/***************end*******************/

/*******************use add*****************/

#define HAL_UART1_PERCFG_BIT 0x02 // USART1 on P0, Alt-1; so clear this bit.

#define HAL_UART1_Px_RX_TX 0x30 // Peripheral I/O Select for Rx/Tx. #define HAL_UART1_Px_RTS 0x20 // Peripheral I/O Select for RTS. #define HAL_UART1_Px_CTS 0x10 // Peripheral I/O Select for CTS. /***********************end********************/

具体的定义不再详细说,查一下数据手册一切都明白了,

然后为了不和DMA方式中的宏定义重复,我将_hal_uart_isr.c文件中文件中的HAL_UART_PERCFG_BIT都替换成了HAL_UART1_PERCFG_BIT、把HAL_UART_Px_RX_TX都替换成了HAL_UART1_Px_RX_TX、把HAL_UART_Px_RTS都替换成了HAL_UART1_Px_RTS、把HAL_UART_Px_CTS都替换成了HAL_UART1_Px_CTS。 然后:HalUARTInitISR函数修改如下: static void HalUARTInitISR (void) {

// Set P2 priority - USART0 over USART1 if both are defined. P2DIR &= ~P2DIR_PRIPO; P2DIR |= HAL_UART_PRIPO;

#if (HAL_UART_ISR == 2)

PERCFG &= ~HAL_UART1_PERCFG_BIT; // Set UART0 I/O location to P0. #else

PERCFG |= HAL_UART1_PERCFG_BIT; // Set UART1 I/O location to P1. #endif

PxSEL |= HAL_UART1_Px_RX_TX; // Enable Tx and Rx on P1.

ADCCFG &= ~HAL_UART1_Px_RX_TX; // Make sure ADC doesnt use this. UxCSR = CSR_MODE; // Mode is UART Mode. UxUCR = UCR_FLUSH; // Flush it. }

到这里底层的修改都全部完成了,然后我们回到APP层来调用串口1,只修改协调器那里就行了。

在上一节配置默认串口的基础上,在HalUARTOpen (0,&uartConfig);后面增加语句 HalUARTOpen (1,&uartConfig);//对串口1进行初始化 这样就打开串口0和串口1

在HalUARTWrite(0,(uint8*)&sendBuff,sizeof(sendBuff)); 后面增加语句

HalUARTWrite(1,(uint8*)&sendBuff,sizeof(sendBuff)); //将接收到的数据输出到串口 这样就通过函数调用将数据通过串口0和串口1各自发送了出去。

然后同样在电脑上用串口调试软件观察就会发现两个串口都会有数据收到:

本文来源:https://www.bwwdw.com/article/6khh.html

Top