对ZStack串口的操作并不复杂,只要在需要的配置串口,打开串口,收发数据即可。
一、串口的配置和打开
要使用串口,首先需要对串口进行配置。在hal_uart.h中,我们可以找到halUARTCfg_t结构体用于配置串口:
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;
在这个结构体中,定义了串口正常工作需要的一些数据,如波特率等,同时也包含了一个重要的函数指针 halUARTCBack_t callBackFunc这个稍后再说。
举一个实例:
void UARTService_Init(void)
{
halUARTCfg_t uartConfig;
uartConfig.configured = TRUE; // 2x30 don't care - see uart driver.
uartConfig.baudRate = HAL_UART_BR_38400;
uartConfig.flowControl = FALSE;
uartConfig.flowControlThreshold = 256; // 2x30 don't care - see uart driver.
uartConfig.rx.maxBufSize = 70; // 2x30 don't care - see uart driver.
uartConfig.tx.maxBufSize = 256; // 2x30 don't care - see uart driver.
uartConfig.idleTimeout = 6; // 2x30 don't care - see uart driver.
uartConfig.intEnable = TRUE; // 2x30 don't care - see uart driver.
uartConfig.callBackFunc = UARTService_ReceiveCallBack;
HalUARTOpen(UART_CONTROLLER_PORT,&uartConfig);
HalUARTOpen(UART_WIFI_PORT,&uartConfig);
}
在配置好串口之后,调用uint8 HalUARTOpen(uint8 port, halUARTCfg_t *config)打开串口就可以正常使用了。HalUARTOpen的有两个参数,前一个为串口号,后一个为串口配置的结构体,之前已经叙述过了。CC2530配套的ZStack-CC2530-2.3.0-1.4.0,也就是目前我使用的版本,定义了2个串口,可以根据需要使用。
/* Ports */
#define HAL_UART_PORT_1 0x01
#define HAL_UART_PORT_MAX 0x02
二、串口的读写操作
简单来说,写串口调用uint16 HalUARTWrite(uint8 port, uint8 *buf, uint16 len),读串口调用uint16 HalUARTRead(uint8 port, uint8 *buf, uint16 len)函数即可(均在hal_uart.c)中定义。
写串口这样操作没有问题,但是读串口的时候就有问题了:我们怎么知道应该在什么时候读串口里的数据呢?
想一想之前在串口配置halUARTCfg_t中,是不是有什么东西我们还没有用到的?对的,有一个函数指针halUARTCBack_t ,先看一下具体定义:
typedef void (*halUARTCBack_t) (uint8 port, uint8 event);
就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来申明函数指针变量。
我先给你一个自定义数据类型的例子。
typedef int* PINT; //为int* 类型定义了一个PINT的别名
int main()
{
int x;
PINT px=&x; //与int * px=&x;是等价的。PINT类型其实就是int * 类型
*px=10; //px就是int*类型的变量
return 0;
}
根据注释,应该不难看懂吧!(虽然你可能很少这样定义使用,但以后学习Win32编程时会经常见到的。)
下面我们来看一下函数指针类型的定义及使用:(请与上对照!)
//自行包含头文件
void MyFun(int x); //此处的申明也可写成:void MyFun( int );
typedef void (*FunType)(int ); //这样只是定义一个函数指针类型
FunType FunP; //然后用FunType类型来申明全局FunP变量
{
(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;
}
#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;
}
#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;
}
{
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));
{
dmaCfg.uartCB(HAL_UART_DMA-1, evt);
}
(函数指针什么的我也不懂啊,恶补中……稍后继续……)
-----------------------------------------------学习归来的分割线-------------------------------------------------
首先解释一下上面的typedef,之前我理解这个语句是声明了一个函数指针,但是进一步了解之后发现不是这样,而是定义了一个函数指针的类型,解释如下:
由上可以看出halUARTCBack_t是一个函数指针类型,而在之前我给出的配置串口中,有这样一句:
halUARTCBack_t callBackFunc;
而在之后给出的串口配置实例中,又有这样一句:
halUARTCfg_t uartConfig;
uartConfig.callBackFunc = UARTService_ReceiveCallBack;
所以,到目前为止,我们是定义了一个halUARTCfg_t类型的函数指针,也就是UARTService_ReceiveCallBack。
(在这里我又纠结了,UARTService_ReceiveCallBack到底是函数还是指针?容我三思……)
问题出在函数指针的赋值上 uartConfig.callBackFunc = UARTService_ReceiveCallBack 。很明显等式的左侧是一个函数指针,也就是指向函数的指针,而等式右侧的又是什么呢?也是个指针吗?正确答案是,UARTService_ReceiveCallBack是一个函数(的地址),虽然省去了取地址符&,但它表明的是这个函数的地址,并且能通过大多数编译器的编译。
完整和推荐的写法是 uartConfig.callBackFunc = &UARTService_ReceiveCallBack;
到这里我们就能明白在配置串口之后,我们能得到一个可以自己定义的函数,用于处理串口的事件。那么下一个问题是,这个函数在什么时候被调用?
在对源代码进行各种查找之后,我得到的结论是:在打开串口(HalUARTOpen)的时候,这个自定义的函数就会被调用。
先看一下HalUARTOpen的定义(在hal_uart.c中):
uint8 HalUARTOpen(uint8 port, halUARTCfg_t *config)
嗯,和DMA、ISR、USB有关,不是很懂,没有找到什么能触发这个函数。
但是不能就这么放弃,继续深挖,找到HalUARTOpenDMA(config)的定义(在hal_uart_dma.c中):
static void HalUARTOpenDMA(halUARTCfg_t *config)
这下有了,注意标黑的一句,已经清楚的显示了在这里这个回调函数被调用了。按照同样的思路查找了一下,果然,在ISR中也找到了相应的调用。综上,就可以理解为一旦打开串口,就会调用自己定义的回调函数了O(∩_∩)O~
三、结论
搞清了以上的来龙去脉,我们可以看出,协议栈其实把什么东西都准备好了,你只要自己定义串口的回调函数即可。想实现什么功能,就在回调函数里面添加,剩下的,就是常规的配置串口打开串口和读写串口了,嗯嗯,就是这么简单~
自己动手,丰衣足食~
----------------------------------------------我是补充的分割线-------------------------------------------------
在hal_uart_dma.c中,有这样一个函数static void HalUARTPollDMA(void),在这个函数中,有这么一句话:
if (evt && (dmaCfg.uartCB != NULL))
可以看出,在有规定的事件发生并且定义了回调函数的情况下,会再次调用回调函数的。
(HalUARTPoll->HalUARTPollDMA 这个函数貌似会不停的驱动串口工作,类似于osal_start_system?太底层了,不深究……)