本帖最后由 冒汗的心情 于 2016-4-15 16:32 编辑
CC2541串口的基本知识和使用
一、导读 主要介绍了了CC2541协议栈中串口的使用。 协议栈版本:BLE-CC254x-1.4.0 编译软件:IAR 8.20.2 硬件平台:Smart RF(主芯片CC2541)
二、基础知识 1、协议栈的串口默认 默认使用的是DMA的查询方式。
2、PC端收不到2541的串口数据的原因
答:最常见原因有两个: 1)没有关闭流控。(关闭步骤见下文) 2)开了低功耗。快速解决办法如下
3、低功耗下是否能使用串口 答:不能。进低功耗前要关闭串口,唤醒后要再使能串口。
三、修改代码 1、IAR设置中添加宏定义
2、修改串口驱动 1)添加头文件(npi.c中)
2)新增两个串口发送函数(npi.c中) //******************************************************************************
//name: NPI_PrintString
//introduce: 打印字符串
//parameter: str:字符串
//return: none
//******************************************************************************
void NPI_PrintString(uint8 *str)
{
NPI_WriteTransport(str, osal_strlen((char*)str));
}
//******************************************************************************
//name: NPI_PrintValue
//introduce: 打印指定的格式的数值
//parameter: title:前缀字符串
// value:需要显示的数值
// format,需要显示的进制,10或16
//return: none
//******************************************************************************
void NPI_PrintValue(char *title, uint16 value, uint8 format)
{
uint8 tmpLen;
uint8 buf[128];
uint32 err;
tmpLen = (uint8)osal_strlen( (char*)title );
osal_memcpy( buf, title, tmpLen );
buf[tmpLen] = ' ';
err = (uint32)(value);
_ltoa( err, &buf[tmpLen+1], format );
NPI_PrintString(buf);
}
3)函数声明(npi.h中) extern void NPI_PrintString(uint8 *str);
extern void NPI_PrintValue(char *title, uint16 value, uint8 format);
3、包含头文件(simpleBLEPeripheral.c中)#include "npi.h"
4、新增初始化代码(simpleBLEPeripheral.c的SimpleBLEPeripheral_Init中)
//初始化串口
NPI_InitTransport(NpiSerialCallback);
// 按长度输出2
NPI_WriteTransport("SimpleBLETest_Initrn", 20);
// 按字符串输出
NPI_PrintString("SimpleBLETest_Init2rn");
// 可以输出一个值,用10进制表示
NPI_PrintValue("甜甜的大香瓜1 = ", 168, 10);
NPI_PrintString("rn");
// 可以输出一个值,用16进制表示
NPI_PrintValue("甜甜的大香瓜2 = 0x", 0x88, 16);
NPI_PrintString("rn");
5、定义并声明一个串口回调函数 1)定义串口回调函数(simpleBLEPeripheral.c中) static void NpiSerialCallback( uint8 port, uint8 events )
{
(void)port;//加个 (void),是未了避免编译告警,明确告诉缓冲区不用理会这个变量
if (events & (HAL_UART_RX_TIMEOUT | HAL_UART_RX_FULL)) //串口有数据
{
uint8 numBytes = 0;
numBytes = NPI_RxBufLen(); //读出串口缓冲区有多少字节
if(numBytes == 0)
{
return;
}
else
{
//申请缓冲区buffer
uint8 *buffer = osal_mem_alloc(numBytes);
if(buffer)
{
//读取读取串口缓冲区数据,释放串口数据
NPI_ReadTransport(buffer,numBytes);
//把收到的数据发送到串口-实现回环
NPI_WriteTransport(buffer, numBytes);
//释放申请的缓冲区
osal_mem_free(buffer);
}
}
}
}
2)声明串口回调函数(simpleBLEPeripheral.c中) static void NpiSerialCallback( uint8 port, uint8 events ); //串口回调
6、关流控(npi.h中) #if !defined( NPI_UART_FC )
#define NPI_UART_FC FALSE //TRUE
#endif // !NPI_UART_FC
流控是用于防止串口阻塞的,需要多两根线用于流控。
通常使用中都是关闭的,因此我们这里关闭之。 注意!!如果这里不关闭,会导致串口通信不了。
四、实验结果
初始化时打印了4条语句,最后一条是通过PC端的串口工具发给2541、2541再原样返回的值。
五、串口相关问题 1、上电时串口接收缓冲区会有一个字节,暂不明该数据哪里来的。暂时用接收处理将该无用字节过滤掉。 答:经检查,是P0_2(RX)使用内部上拉电阻,但电压仅为0.6V。上电时电压不稳定导致接收缓冲区会有一个字节的数据“0x00”。 因此解决方法是:外接1个10K上拉电阻到3.3V,将P0_2(RX)的电压拉高至3.3V。 注:TX引脚最好也外接上拉电阻。
2、串口什么时候进回调函数? 答: 1)正常串口端无发送、无接收时,是不会进回调函数的。如果这种情况会进回调函数,TX、RX端外接上拉电阻稳定电平。 2)如果接收端有数据,立马就会进回调。事件是“接收超时事件”。 3)2541发送端发送完数据,会进回调函数。事件是“发送缓冲区空事件”。
3、没开广播时串口正常使用,开了广播后串口出现乱码、丢包? 答: 1)在SimpleBLEPeripheral的应用层初始化中注释掉:
HCI_EXT_ClkDivOnHaltCmd( HCI_EXT_ENABLE_CLK_DIVIDE_ON_HALT );
PS:这条语句会让空闲的CPU自动进入低频以此降低功耗,注释掉代表不自动切换频率。
2)在SimpleBLEPeripheral的应用层初始化中添加:
HCI_EXT_HaltDuringRfCmd(HCI_EXT_HALT_DURING_RF_DISABLE);
PS:默认是ENABLE的,ENABLE会让RF期间停止MCU。因此需要添加本条语句,关闭它。
4、编译时出现警告“Warning[w52]: More than one definition for the byte at address 0x6b in common segment INTVEC. It is defined in module "hal_uart" as well as in module "hal_key"”? 答:原因是IAR中包含了POWER_SAVING的宏之后,串口唤醒就需要用到IO中断,而协议栈中串口唤醒的默认IO口是P04脚。因此在_hal_uart_dma.c中包含了的P0中断服务函数就与Hal_key.c中包含的P0中断服务函数相冲突。
详情代码如下: 1)当IAR中包含了POWER_SAVING的宏之后,会定义一个DMA_PM的宏(_hal_uart_dma.c中) #if !defined( DMA_PM )
#if defined POWER_SAVING
#define DMA_PM 1
#else
#define DMA_PM 0
#endif // POWER_SAVING
#endif // !DMA_PM
2)一旦有了DMA_PM这个宏,_hal_uart_dma.c中就会包含P0的中断服务函数
#if DMA_PM
/**************************************************************************************************
* @fn PortX Interrupt Handler
*
* @brief This function is the PortX interrupt service routine.
*
* @param None.
*
* @Return None.
*************************************************************************************************/
#if (HAL_UART_DMA == 1)
HAL_ISR_FUNCTION(port0Isr, P0INT_VECTOR)
#else
HAL_ISR_FUNCTION(port1Isr, P1INT_VECTOR)
#endif
{
HAL_ENTER_ISR();
PxIFG = 0;
PxIF = 0;
dmaRdyIsr = 1;
CLEAR_SLEEP_MODE();
HAL_EXIT_ISR();
}
#endif
3)IAR设置中包含HAL_KEY=TRUE时,Hal_key.c中就会包含P0的中断服务函数
/**************************************************************************************************
* @fn halKeyPort0Isr
*
* @brief Port0 ISR
*
* @param
*
* @return
**************************************************************************************************/
HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )
{
HAL_ENTER_ISR();
#if defined ( CC2540_MINIDK )
if ((HAL_KEY_SW_1_PXIFG & HAL_KEY_SW_1_BIT) || (HAL_KEY_SW_2_PXIFG & HAL_KEY_SW_2_BIT))
#else
if (HAL_KEY_SW_6_PXIFG & HAL_KEY_SW_6_BIT)
#endif
{
halProcessKeyInterrupt();
}
/*
Clear the CPU interrupt flag for Port_0
PxIFG has to be cleared before PxIF
*/
#if defined ( CC2540_MINIDK )
HAL_KEY_SW_1_PXIFG = 0;
HAL_KEY_SW_2_PXIFG = 0;
#else
HAL_KEY_SW_6_PXIFG = 0;
#endif
HAL_KEY_CPU_PORT_0_IF = 0;
CLEAR_SLEEP_MODE();
HAL_EXIT_ISR();
return;
}
所以,解决办法如下:
方法一(治标不治本):IAR设置中将HAL_UART、HAL_KEY其中一个设置为FALSE。 方法二(治标不治本):不使用低功耗。 方法三(治本但改动了协议栈代码):将两个P0的中断服务函数合二为一。 //(待测)方法四(非强迫症人群使用):任其冲突,反正只是警告。
5、串口接收多少个字节能进入回调函数? 1)测试代码如下: uint8 cb_time = 0;
uint8 numBytes_time1 = 0;
uint8 numBytes_time2 = 0;
uint8 numBytes_time3 = 0;
/*********************************************************************
*********************************************************************/
static void NpiSerialCallback( uint8 port, uint8 events )
{
(void)port;//加个 (void),是未了避免编译告警,明确告诉缓冲区不用理会这个变量
switch(++cb_time) //记录进回调函数的次数
{
case 1:
numBytes_time1 = NPI_RxBufLen();//第一次进来时接收的数据长度
break;
case 2:
numBytes_time2 = NPI_RxBufLen();//第二次进来时接收的数据长度
break;
case 3:
numBytes_time3 = NPI_RxBufLen();//第三次进来时接收的数据长度
break;
default:
cb_time = 0;//无用语句,设置断点
break;
}
}
2)测试结果
3)测试结论 2541串口接收端的数据是由DMA去接收的,2541会通过轮询的方式定期查询DMA的缓冲区是否有数据,一旦有数据即会进入回调函数(协议栈默认不开超时等待)。 上图说明了第一次进回调函数时DMA接收到了7个字节数据,它的长度与“主轮询时间”、“串口数据来的时机”有关系。 而第二次、第三次的接收数据之所以会比第一次多,是因为还加上了串口处理时接收到的数据
|