【BLE】CC2541之串口收发

来源:互联网 发布:限制员工上网软件 编辑:程序博客网 时间:2024/05/19 08:43

一、简介

本文介绍如何在SimpleBLEPeripheral工程中,使用串口。


二、实验平台

协议栈版本:BLE-CC254x-1.4.0

编译软件:IAR 8.20.2

硬件平台:Smart RF(主芯片CC2541)


版权声明

博主:si_zhou_qun_84342712

声明:喝水不忘挖井人,转载请注明出处。

原文地址:http://write.blog.csdn.net/postedit

联系方式:495527583@qq.com

四轴开源群:84342712

四轴开源(淘宝店):

四、基础知识

1、协议栈的串口默认使用的是查询方式,还是中断方式?

答:默认使用的是DMA的查询方式。


2、PC端为什么收不到2541的串口数据?

答:最常见原因有两个:

1)没有关闭流控。(关闭步骤见下文)

2)开了低功耗。快速解决办法如下



3、低功耗下是否能使用串口?

答:不能。进低功耗前要关闭串口,唤醒后要再使能串口。


4、如何关闭串口?(未实践,提供思路)

答:有群友指出一种方法:

使用dma方式、isr方式的串口,将串口引脚映射为普通IO口,即为关闭。


5、低功耗唤醒后串口没用了?(未实践,提供思路)

答:由于低功耗与正常模式的切换,会导致晶振的32k与32M的切换。而串口等外设使用的是32M的外部晶振,因此低功耗唤醒后需要做两件事:

1)适当延时等待32M晶振稳定。

2)再次初始化一次串口。


五、修改代码

1、IAR设置中添加宏定义

  1. HAL_UART=TRUE  

2、修改串口驱动

1)添加头文件(npi.c中)

  1. #include "OSAL.h"  

2)新增两个串口发送函数(npi.c中)

  1. //******************************************************************************  
  2. //name:     NPI_PrintString  
  3. //introduce:    打印字符串  
  4. //parameter:    str:字符串  
  5. //return:   none  
  6. //******************************************************************************  
  7. void NPI_PrintString(uint8 *str)  
  8. {  
  9.     NPI_WriteTransport(str, osal_strlen((char*)str));  
  10. }  
  11.   
  12. //******************************************************************************  
  13. //name:     NPI_PrintValue  
  14. //introduce:    打印指定的格式的数值  
  15. //parameter:    title:前缀字符串  
  16. //              value:需要显示的数值   
  17. //              format,需要显示的进制,10或16  
  18. //return:   none  
  19. //******************************************************************************  
  20. void NPI_PrintValue(char *title, uint16 value, uint8 format)  
  21. {  
  22.   uint8 tmpLen;  
  23.   uint8 buf[128];  
  24.   uint32 err;  
  25.   
  26.   tmpLen = (uint8)osal_strlen( (char*)title );  
  27.   osal_memcpy( buf, title, tmpLen );  
  28.   buf[tmpLen] = ' ';  
  29.   err = (uint32)(value);  
  30.   _ltoa( err, &buf[tmpLen+1], format );  
  31.   NPI_PrintString(buf);       
  32. }  


3)函数声明(npi.h中)

  1. extern void NPI_PrintString(uint8 *str);  
  2. extern void NPI_PrintValue(char *title, uint16 value, uint8 format);  

3、包含头文件(simpleBLEPeripheral.c中)
  1. #include "npi.h"   

4、新增初始化代码(simpleBLEPeripheral.c的SimpleBLEPeripheral_Init中)
  1. //初始化串口  
  2. NPI_InitTransport(NpiSerialCallback);     
  3.     
  4. // 按长度输出2    
  5. NPI_WriteTransport("SimpleBLETest_Init\r\n", 20);    
  6.     
  7. // 按字符串输出    
  8. NPI_PrintString("SimpleBLETest_Init2\r\n");      
  9.     
  10. // 可以输出一个值,用10进制表示    
  11. NPI_PrintValue("甜甜的大香瓜1 = ", 168, 10);    
  12. NPI_PrintString("\r\n");      
  13.     
  14. // 可以输出一个值,用16进制表示    
  15. NPI_PrintValue("甜甜的大香瓜2 = 0x", 0x88, 16);    
  16. NPI_PrintString("\r\n");   


5、定义并声明一个串口回调函数

1)定义串口回调函数(simpleBLEPeripheral.c中)

  1. static void NpiSerialCallback( uint8 port, uint8 events )  
  2. {  
  3.     (void)port;//加个 (void),是未了避免编译告警,明确告诉缓冲区不用理会这个变量  
  4.   
  5.     if (events & (HAL_UART_RX_TIMEOUT | HAL_UART_RX_FULL))   //串口有数据  
  6.     {  
  7.         uint8 numBytes = 0;  
  8.   
  9.         numBytes = NPI_RxBufLen();           //读出串口缓冲区有多少字节  
  10.           
  11.         if(numBytes == 0)  
  12.         {  
  13.             return;  
  14.         }  
  15.         else  
  16.         {  
  17.             //申请缓冲区buffer  
  18.             uint8 *buffer = osal_mem_alloc(numBytes);  
  19.             if(buffer)  
  20.             {  
  21.                 //读取读取串口缓冲区数据,释放串口数据     
  22.                 NPI_ReadTransport(buffer,numBytes);     
  23.   
  24.                 //把收到的数据发送到串口-实现回环   
  25.                 NPI_WriteTransport(buffer, numBytes);    
  26.   
  27.                 //释放申请的缓冲区  
  28.                 osal_mem_free(buffer);  
  29.             }  
  30.         }  
  31.     }  
  32. }  


2)声明串口回调函数(simpleBLEPeripheral.c中)

  1. static void NpiSerialCallback( uint8 port, uint8 events );   //串口回调  


6、关流控(npi.h中)

  1. #if !defined( NPI_UART_FC )  
  2. #define NPI_UART_FC                    FALSE //TRUE  
  3. #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中)

  1. #if !defined( DMA_PM )  
  2. #if defined POWER_SAVING  
  3. #define DMA_PM                     1  
  4. #else  
  5. #define DMA_PM                     0  
  6. #endif // POWER_SAVING  
  7. #endif // !DMA_PM  


2)一旦有了DMA_PM这个宏,_hal_uart_dma.c中就会包含P0的中断服务函数

  1. #if DMA_PM  
  2. /************************************************************************************************** 
  3.  * @fn      PortX Interrupt Handler 
  4.  * 
  5.  * @brief   This function is the PortX interrupt service routine. 
  6.  * 
  7.  * @param   None. 
  8.  * 
  9.  * @return  None. 
  10.  *************************************************************************************************/  
  11. #if (HAL_UART_DMA == 1)  
  12. HAL_ISR_FUNCTION(port0Isr, P0INT_VECTOR)  
  13. #else  
  14. HAL_ISR_FUNCTION(port1Isr, P1INT_VECTOR)  
  15. #endif  
  16. {  
  17.   HAL_ENTER_ISR();  
  18.   
  19.   PxIFG = 0;  
  20.   PxIF = 0;  
  21.   
  22.   dmaRdyIsr = 1;  
  23.   
  24.   CLEAR_SLEEP_MODE();  
  25.   HAL_EXIT_ISR();  
  26. }  
  27. #endif</span>  

3)IAR设置中包含HAL_KEY=TRUE时,Hal_key.c中就会包含P0的中断服务函数

/************************************************************************************************** 
  1.  * @fn      halKeyPort0Isr 
  2.  * 
  3.  * @brief   Port0 ISR 
  4.  * 
  5.  * @param 
  6.  * 
  7.  * @return 
  8.  **************************************************************************************************/  
  9. HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )  
  10. {  
  11.   HAL_ENTER_ISR();  
  12.   
  13. #if defined ( CC2540_MINIDK )  
  14.   if ((HAL_KEY_SW_1_PXIFG & HAL_KEY_SW_1_BIT) || (HAL_KEY_SW_2_PXIFG & HAL_KEY_SW_2_BIT))  
  15. #else  
  16.   if (HAL_KEY_SW_6_PXIFG & HAL_KEY_SW_6_BIT)  
  17. #endif  
  18.   {  
  19.     halProcessKeyInterrupt();  
  20.   }  
  21.   
  22.   /* 
  23.     Clear the CPU interrupt flag for Port_0 
  24.     PxIFG has to be cleared before PxIF 
  25.   */  
  26. #if defined ( CC2540_MINIDK )  
  27.   HAL_KEY_SW_1_PXIFG = 0;  
  28.   HAL_KEY_SW_2_PXIFG = 0;  
  29. #else  
  30.   HAL_KEY_SW_6_PXIFG = 0;  
  31. #endif  
  32.   HAL_KEY_CPU_PORT_0_IF = 0;  
  33.   
  34.   CLEAR_SLEEP_MODE();  
  35.   
  36.   HAL_EXIT_ISR();  
  37.   
  38.   return;  
  39. }  
所以,解决办法如下:

方法一(治标不治本):IAR设置中将HAL_UART、HAL_KEY其中一个设置为FALSE。

方法二(治标不治本):不使用低功耗。

方法三(治本但改动了协议栈代码):将两个P0的中断服务函数合二为一。

//(待测)方法四(非强迫症人群使用):任其冲突,反正只是警告。

5、串口接收多少个字节能进入回调函数?

1)测试代码如下:

  1. uint8 cb_time = 0;    
  2. uint8 numBytes_time1 = 0;    
  3. uint8 numBytes_time2 = 0;    
  4. uint8 numBytes_time3 = 0;    
  5. /*********************************************************************  
  6. *********************************************************************/    
  7. static void NpiSerialCallback( uint8 port, uint8 events )      
  8. {      
  9.     (void)port;//加个 (void),是未了避免编译告警,明确告诉缓冲区不用理会这个变量      
  10.     
  11.     switch(++cb_time)   //记录进回调函数的次数    
  12.     {    
  13.       case 1:    
  14.       numBytes_time1 = NPI_RxBufLen();//第一次进来时接收的数据长度    
  15.       break;    
  16.           
  17.       case 2:    
  18.       numBytes_time2 = NPI_RxBufLen();//第二次进来时接收的数据长度    
  19.       break;    
  20.     
  21.       case 3:    
  22.       numBytes_time3 = NPI_RxBufLen();//第三次进来时接收的数据长度    
  23.       break;         
  24.          
  25.       default:    
  26.       cb_time = 0;//无用语句,设置断点    
  27.       break;     
  28.     }    
  29. }   

2)测试结果



3)测试结论

2541串口接收端的数据是由DMA去接收的,2541会通过轮询的方式定期查询DMA的缓冲区是否有数据,一旦有数据即会进入回调函数(协议栈默认不开超时等待)。

上图说明了第一次进回调函数时DMA接收到了7个字节数据,它的长度与“主轮询时间”、“串口数据来的时机”有关系。

而第二次、第三次的接收数据之所以会比第一次多,是因为还加上了串口处理时接收到的数据。

0 0
原创粉丝点击