一、实现串口
1.1、CC2541的串口简介
CC2541有21个数字输入/输出引脚, 可以配置为通用数字I/O或外设I/O信号, 配置为连接到ADC、 定时器或 USART外设。这些 I/O 口的用途可以通过一系列寄存器配置,由用户软件加以实现。
对于 USART 和定时器 I/O,在一个数字 I/O 引脚上选择外设 I/O 功能,需要设置对应的 PxSEL 位为 1。
外设 I/O 引脚映射如下图所示:
1.2、CC2541串口的寄存器实现方法
对于每个 USART,有 5 个如下的寄存器(x 是 USART 的编号,为 0 或者1):
● UxCSR: USARTx 控制和状态;
● UxUCR: USARTx UART 控制;
● UxGCR: USARTx 通用控制
● UxBUF: USART x 接收/发送数据缓冲
● UxBAUD: USART x 波特率控
CC2541 配置串口的一般步骤:
1、 配置 IO,使用外部设备功能。
2、 配置相应串口的控制和状态寄存器。
3、 配置串口工作的波特率。
具体代码如下:
#include <ioCC2540.h>
#include <string.h>
#include "uart.h"
/*
串口0,应用的IO口为:RX P0.2 TX P0.3
*/
#define Uart0_SIZE 40
int8 Rx_Flag0=0;
uint8 Rx_length0=0;
uint8 RxData0[Uart0_SIZE]; //存储接收字符串
/****************************************************************************
* 名 称: InitUart()
* 功 能: 串口初始化函数
* 入口参数: 无
* 出口参数: 无
****************************************************************************/
void Uart0_Init(void)
{
PERCFG &= ~0x01; //外设控制寄存器 USART 0的IO位置:0为P0口位置1
P0SEL = 0x0c; //P0_2,P0_3用作串口(外设功能)
P2DIR &= ~0xC0; //P0优先作为UART0
U0CSR |= 0x80; //设置为UART方式
U0GCR |= 11;
U0BAUD |= 216; //波特率设为115200
UTX0IF = 0; //UART0 TX中断标志初始置位0
U0CSR |= 0x40; //允许接收
IEN0 |= 0x84; //开总中断允许接收中断
}
/****************************************************************************
* 名 称: UartSendString()
* 功 能: 串口发送函数
* 入口参数: Data:发送缓冲区 len:发送长度
* 出口参数: 无
****************************************************************************/
void Uart0_Send(uint8 *Data, uint8 len)
{
uint8 i;
U0CSR &= ~0x40; //禁止接收
for(i=0; i<len; i++)
{
U0DBUF = *Data++;
while(UTX0IF == 0);
UTX0IF = 0;
}
U0CSR |= 0x40; //允许接收
}
/****************************************************************************
* 名 称: UART0_ISR(void) 串口中断处理函数
* 描 述: 当串口0产生接收中断,将收到的数据保存在RxBuf中
****************************************************************************/
uint8 ptrdata=0;
#pragma vector = URX0_VECTOR
__interrupt void UART0_ISR(void)
{
URX0IF = 0; // 清中断标志
ptrdata=U0DBUF;
if(ptrdata!=0x00)/* 因为工程特殊性,过滤掉0X00 */
{
if(Rx_length0<Uart0_SIZE)RxData0[Rx_length0] = ptrdata;
Rx_length0++;
}
}
uint8 Uart0_Read(uint8 *Data)
{
if(Rx_length0==0)//接收数据bit为零
{
return 0;
}else
{
if(Rx_Flag0==0)//接收数据长度为零,(此时正在接收数据)
{
Rx_Flag0=Rx_length0;
return 0;
}else
{
if(Rx_Flag0!=Rx_length0)//接收长度数据有变化,认为数据接收没有收完
{
Rx_Flag0=Rx_length0;
return 0;
}else//接收数据长度在一个隔离周期(10ms)后没有变化,认为接收完成
{
uint8 length=Rx_Flag0;
for(uint8 i=0;i<Rx_Flag0;i++)
{
Data[i]=RxData0[i];
}
Rx_length0=0;
Rx_Flag0=0;
return length;
}
}
}
}
1.3、串口的API实现方法
1.3.1、IAR 设置中添加宏定义
HAL_UART=TRUE
1.3.2初始化串口
在npi.c的库文件中有串口初始化函数如下:
/*******************************************************************************
* @fn NPI_InitTransport
*
* @brief This routine initializes the transport layer and opens the port
* of the device. Note that based on project defines, either the
* UART, USB (CDC), or SPI driver can be used.
*
* input parameters
*
* @param npiCback - User callback function when data is available.
*
* output parameters
*
* @param None.
*
* @return None.
*/
void NPI_InitTransport( npiCBack_t npiCBack )
{
halUARTCfg_t uartConfig;
// configure UART
uartConfig.configured = TRUE;
uartConfig.baudRate = NPI_UART_BR;
uartConfig.flowControl = NPI_UART_FC;
uartConfig.flowControlThreshold = NPI_UART_FC_THRESHOLD;
uartConfig.rx.maxBufSize = NPI_UART_RX_BUF_SIZE;
uartConfig.tx.maxBufSize = NPI_UART_TX_BUF_SIZE;
uartConfig.idleTimeout = NPI_UART_IDLE_TIMEOUT;
uartConfig.intEnable = NPI_UART_INT_ENABLE;
uartConfig.callBackFunc = (halUARTCBack_t)npiCBack;
// start UART
// Note: Assumes no issue opening UART port.
(void)HalUARTOpen( NPI_UART_PORT, &uartConfig );
return;
}
为了适应工程需求,可修改如下:
void My_NPI_InitTransport( npiCBack_t npiCBack, uint8 baudrate, uint8 parity, uint8 stopbit)
{
halUARTCfg_t uartConfig;
// configure UART
uartConfig.configured = TRUE;
uartConfig.baudRate = baudrate;//NPI_UART_BR;
uartConfig.parity = parity;
uartConfig.stopbit = stopbit;
uartConfig.flowControl = NPI_UART_FC;
uartConfig.flowControlThreshold = NPI_UART_FC_THRESHOLD;
uartConfig.rx.maxBufSize = NPI_UART_RX_BUF_SIZE;
uartConfig.tx.maxBufSize = NPI_UART_TX_BUF_SIZE;
uartConfig.idleTimeout = NPI_UART_IDLE_TIMEOUT;
uartConfig.intEnable = NPI_UART_INT_ENABLE;
uartConfig.callBackFunc = (halUARTCBack_t)npiCBack;
// start UART
// Note: Assumes no issue opening UART port.
(void)HalUARTOpen( NPI_UART_PORT, &uartConfig );
return;
}
1.3.3、定义串口回调函数
在串口回调函数中,可以接收串口数据
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
{
uint8 *buffer = osal_mem_alloc(numBytes);//申请缓冲区 buffer
if(buffer)
{
NPI_ReadTransport(buffer,numBytes);//读取读取串口缓冲区数据,释放串口数据
/*
......
// 数据处理
//蓝牙串口透传时,可以在此处调用BLE发送数据的方法
.......
*/
osal_mem_free(buffer);//释放申请的缓冲区
}
}
}
}
1.3.4、串口发送函数
串口发送函数在npi.c文件内定义:
/*******************************************************************************
* @fn NPI_WriteTransport
*
* @brief This routine writes data from the buffer to the transport layer.
*
* input parameters
*
* @param buf - Pointer to buffer to write data from.
* @param len - Number of bytes to write.
*
* output parameters
*
* @param None.
*
* @return Returns the number of bytes written to transport.
*/
uint16 NPI_WriteTransport( uint8 *buf, uint16 len )
{
return( HalUARTWrite( NPI_UART_PORT, buf, len ) );
}
二、增加特征值char6
本章节参考了【BLE】CC2541之添加特征值 要实现透传, 我们有个前提, 就是需要建立连接后,需要对某个特征值进行写和通知。
2.1、添加 char6 的宏定义
在simpleGATTprofile.h 文件中增加:
// Profile Parameters
#define SIMPLEPROFILE_CHAR1 0 // RW uint8 - Profile Characteristic 1 value
#define SIMPLEPROFILE_CHAR2 1 // RW uint8 - Profile Characteristic 2 value
#define SIMPLEPROFILE_CHAR3 2 // RW uint8 - Profile Characteristic 3 value
#define SIMPLEPROFILE_CHAR4 3 // RW uint8 - Profile Characteristic 4 value
#define SIMPLEPROFILE_CHAR5 4 // RW uint8 - Profile Characteristic 4 value
#define SIMPLEPROFILE_CHAR6 5 // RW uint8 - Profile Characteristic 5 value
// Simple Profile Service UUID
#define SIMPLEPROFILE_SERV_UUID 0xFFE0
// Key Pressed UUID
#define SIMPLEPROFILE_CHAR1_UUID 0xFFE6
#define SIMPLEPROFILE_CHAR2_UUID 0xFFE5
#define SIMPLEPROFILE_CHAR3_UUID 0xFFE4
#define SIMPLEPROFILE_CHAR4_UUID 0xFFE3
#define SIMPLEPROFILE_CHAR5_UUID 0xFFE2
#define SIMPLEPROFILE_CHAR6_UUID 0xFFE1
// Simple Keys Profile Services bit fields
#define SIMPLEPROFILE_SERVICE 0x00000001
// Length of Characteristic 5 in bytes
#define SIMPLEPROFILE_CHAR5_LEN 5
#define SIMPLEPROFILE_CHAR6_LEN 20
首先, 增加 CHAR6 的 profile 参数。
然后, 增加该特征值的 UUID。
最后, 增加该特征值的长度
2.2、添加 char6 的 UUID
在simpleGATTprofile.c 文件中增加:
// Characteristic 6 UUID: 0xFFE1
CONST uint8 simpleProfilechar6UUID[ATT_BT_UUID_SIZE] =
{
LO_UINT16(SIMPLEPROFILE_CHAR6_UUID), HI_UINT16(SIMPLEPROFILE_CHAR6_UUID)
};
将 16 位的 UUID 拆成 2 个字节放到数组里
2.3、添加 char6 的 的配置属性
在simpleGATTprofile.c 文件中增加:
// Simple Profile Characteristic 6 Properties
static uint8 simpleProfileChar6Props = GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP | GATT_PROP_NOTIFY;
// Characteristic 6 Value
static uint8 simpleProfileChar6[SIMPLEPROFILE_CHAR6_LEN] = { 0, 0, 0, 0, 0 };
static uint8 simpleProfileChar6Len = 0;
// Simple Profile Characteristic 6 Configuration Each client has its own
// instantiation of the Client Characteristic Configuration. Reads of the
// Client Characteristic Configuration only shows the configuration for
// that client and writes only affect the configuration of that client.
static gattCharCfg_t simpleProfileChar6Config[GATT_MAX_NUM_CONN];
// Simple Profile Characteristic 6 User Description
static uint8 simpleProfileChar6UserDesp[17] = "Characteristic 6\0";
2.4、修改属性表
2.4.1、修改属性表的大小
#define SERVAPP_NUM_ATTR_SUPPORTED 21
增加上面定义的 char6 的 4 个属性变量。
2.4.2、修改属性表
在simpleProfileAttrTbl数组中增加
// Characteristic 6 Declaration
{
{ ATT_BT_UUID_SIZE, characterUUID },
GATT_PERMIT_READ,
0,
&simpleProfileChar6Props
},
// Characteristic Value 6
{
{ ATT_BT_UUID_SIZE, simpleProfilechar6UUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
simpleProfileChar6
},
// Characteristic 6 configuration
{
{ ATT_BT_UUID_SIZE, clientCharCfgUUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
(uint8 *)simpleProfileChar6Config
},
// Characteristic 6 User Description
{
{ ATT_BT_UUID_SIZE, charUserDescUUID },
GATT_PERMIT_READ,
0,
simpleProfileChar6UserDesp
},
2.5、修改特征值的参数函数
2.5.1、增加 char6 的数值可设置的处理
在simpleGATTprofile.c 的 SimpleProfile_SetParameter函数中
case SIMPLEPROFILE_CHAR6:
if ( len <= SIMPLEPROFILE_CHAR6_LEN )
{
VOID osal_memcpy( simpleProfileChar6, value, len );
simpleProfileChar6Len = len;
// See if Notification has been enabled
GATTServApp_ProcessCharCfg( simpleProfileChar6Config, simpleProfileChar6, FALSE,
simpleProfileAttrTbl, GATT_NUM_ATTRS( simpleProfileAttrTbl ),
INVALID_TASK_ID );
}
else
{
ret = bleInvalidRange;
}
break;
2.5.2、增加 char6 的数值可获取的处理
在simpleGATTprofile.c 文件的 SimpleProfile_GetParameter函数中
case SIMPLEPROFILE_CHAR6:
VOID osal_memcpy( value, simpleProfileChar6, simpleProfileChar6Len );
*returnBytes = simpleProfileChar6Len;
break;
2.6、修改特征值的读写函数
2.6.1、增加 char6 的数值读取的处理
在simpleGATTprofile.c文件 的 simpleProfile_ReadAttrCB函数中
case SIMPLEPROFILE_CHAR6_UUID:
//*Len = SIMPLEPROFILE_CHAR6_LEN;
//VOID osal_memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR6_LEN );
LCD_WRITE_STRING_VALUE( "ReadAttrCB 6 len=", simpleProfileChar6Len, 10, HAL_LCD_LINE_1 );
*pLen = simpleProfileChar6Len;
VOID osal_memcpy( pValue, pAttr->pValue, simpleProfileChar6Len );
{
// 这个变量用于表明上一次写数据到从机已经成功, 可用于判断写数据时的判断, 以确保数据的完整性
extern bool simpleBLEChar6DoWrite2;
simpleBLEChar6DoWrite2 = TRUE;
}
break;
2.6.2、增加 char6 的数值写入的处理
在simpleGATTprofile.c文件 的 simpleProfile_WriteAttrCB函数中
case SIMPLEPROFILE_CHAR6_UUID:
//Validate the value
// Make sure it's not a blob oper
LCD_WRITE_STRING_VALUE( "WriteAttrCB 6 len=", len, 10, HAL_LCD_LINE_1 );
LCD_WRITE_STRING_VALUE( "WriteAttrCB 6 len2=", simpleProfileChar6Len, 10, HAL_LCD_LINE_1 );
if ( offset == 0 )
{
//if ( len != SIMPLEPROFILE_CHAR6_LEN )
if ( len > SIMPLEPROFILE_CHAR6_LEN )
{
status = ATT_ERR_INVALID_VALUE_SIZE;
}
}
else
{
status = ATT_ERR_ATTR_NOT_LONG;
}
//Write the value
if ( status == SUCCESS )
{
//VOID osal_memcpy( pAttr->pValue, pValue, SIMPLEPROFILE_CHAR6_LEN );
VOID osal_memcpy( pAttr->pValue, pValue, len );
simpleProfileChar6Len = len;
notifyApp = SIMPLEPROFILE_CHAR6;
}
break;
2.6.3、增加通知开关的处理
替换 simpleGATTprofile.c文件 的 simpleProfile_WriteAttrCB 函数中的 GATT_CLIENT_CHAR_CFG_UUID 部分
case GATT_CLIENT_CHAR_CFG_UUID:
status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,
offset, GATT_CLIENT_CFG_NOTIFY );
break;
2.7、增加 char6 的通知开关初始化
替换 simpleGATTprofile.c文件中 的 SimpleProfile_AddService函数
/*********************************************************************
* @fn SimpleProfile_AddService
*
* @brief Initializes the Simple Profile service by registering
* GATT attributes with the GATT server.
*
* @param services - services to add. This is a bit map and can
* contain more than one service.
*
* @return Success or Failure
*/
bStatus_t SimpleProfile_AddService( uint32 services )
{
uint8 status = SUCCESS;
// Initialize Client Characteristic Configuration attributes
GATTServApp_InitCharCfg( INVALID_CONNHANDLE, simpleProfileChar4Config );
GATTServApp_InitCharCfg( INVALID_CONNHANDLE, simpleProfileChar6Config );
//GATTServApp_InitCharCfg( INVALID_CONNHANDLE, simpleProfileChar7Config );
// Register with Link DB to receive link status change callback
VOID linkDB_Register( simpleProfile_HandleConnStatusCB );
if ( services & SIMPLEPROFILE_SERVICE )
{
// Register GATT attribute list and CBs with GATT Server App
status = GATTServApp_RegisterService( simpleProfileAttrTbl,
GATT_NUM_ATTRS( simpleProfileAttrTbl ),
&simpleProfileCBs );
}
return ( status );
}
2.8 、 增 加 char6 的 通 知 开 关 初 始 化 的 实 时 更 新
替 换 simpleGATTprofile.c文件中 的simpleProfile_HandleConnStatusCB 函数
/*********************************************************************
* @fn simpleProfile_HandleConnStatusCB
*
* @brief Simple Profile link status change handler function.
*
* @param connHandle - connection handle
* @param changeType - type of change
*
* @return none
*/
static void simpleProfile_HandleConnStatusCB( uint16 connHandle, uint8 changeType )
{
// Make sure this is not loopback connection
if ( connHandle != LOOPBACK_CONNHANDLE )
{
// Reset Client Char Config if connection has dropped
if ( ( changeType == LINKDB_STATUS_UPDATE_REMOVED ) ||
( ( changeType == LINKDB_STATUS_UPDATE_STATEFLAGS ) &&
( !linkDB_Up( connHandle ) ) ) )
{
GATTServApp_InitCharCfg( connHandle/*INVALID_CONNHANDLE*/, simpleProfileChar4Config );
GATTServApp_InitCharCfg( connHandle/*INVALID_CONNHANDLE*/, simpleProfileChar6Config );
//GATTServApp_InitCharCfg( connHandle/*INVALID_CONNHANDLE*/, simpleProfileChar7Config );
}
}
}
2.9、应用层修改
2.9.1、修改特征值初始化的数值
在 simpleBLEPeripheral.c文件 的 SimpleBLEPeripheral_Init 函数
中添加
// Setup the SimpleProfile Characteristic Values
{
uint8 charValue1 = 1;
uint8 charValue2 = 2;
uint8 charValue3 = 3;
uint8 charValue4 = 4;
uint8 charValue5[SIMPLEPROFILE_CHAR5_LEN] = { 1, 2, 3, 4, 5 };
uint8 charValue6[SIMPLEPROFILE_CHAR6_LEN] = { 1, 2, 3, 4, 5 };
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR1, sizeof ( uint8 ), &charValue1 );
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR2, sizeof ( uint8 ), &charValue2 );
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR3, sizeof ( uint8 ), &charValue3 );
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR4, sizeof ( uint8 ), &charValue4 );
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR5, SIMPLEPROFILE_CHAR5_LEN, charValue5 );
SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR6, SIMPLEPROFILE_CHAR6_LEN, charValue6 );
}
2.9.2、修改应用层的回调函数
在 simpleBLEPeripheral.c文件的 的 simpleProfileChangeCB 函数中
/*********************************************************************
* @fn simpleProfileChangeCB
*
* @brief Callback from SimpleBLEProfile indicating a value change
*
* @param paramID - parameter ID of the value that was changed.
*
* @return none
*/
static void simpleProfileChangeCB( uint8 paramID )
{
uint8 newValue;
uint8 newChar6Value[SIMPLEPROFILE_CHAR6_LEN];
uint8 returnBytes;
switch( paramID )
{
case SIMPLEPROFILE_CHAR1:
SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR1, &newValue, &returnBytes );
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
HalLcdWriteStringValue( "Char 1:", (uint16)(newValue), 10, HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
break;
case SIMPLEPROFILE_CHAR3:
SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR3, &newValue, &returnBytes );
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
HalLcdWriteStringValue( "Char 3:", (uint16)(newValue), 10, HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
break;
case SIMPLEPROFILE_CHAR6:
SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR6, newChar6Value, &returnBytes );
if(returnBytes > 0)
{
/*
//此处处理BLE接收的数据
*/
}
break;
default:
// should not reach here!
break;
}
}
三、实现串口0和BLE数据透传
3.1、串口接收数据并通过无线发送
在1.3.3章节定义的串口回调函数NpiSerialCallback()中可以接收串口数据,此时可以通过调用无线发送数据的函数,将数据发送出去。数据发送函数如下:
// 处理: 串口发送过来的数据, 通过无线发送出去
void simpleBLE_Send_Data(uint8 *buf, uint8 numBytes)
{
if(simpleBLEChar6DoWrite2) //写入成功后再写入
{
static attHandleValueNoti_t pReport;
pReport.len = numBytes;
pReport.handle = 0x0035;
osal_memcpy(pReport.value, buf, numBytes);
GATT_Notification( 0, &pReport, FALSE );
}
}
3.2、无线接收数据并通过串口发送
在2.9.2章节中修改的应用层的回调函数simpleProfileChangeCB()可以接收无线数据,在此处可以将无线数据通过串口发送出去。
四、实现串口1和BLE数据透传
4.1、在IAR 设置中添加宏定义
ZAPP_P2=TRUE
如图所示:
4.2、修改串口配置
在npi.h文件内修改串口配置如下:
/* UART port */
/*
#if !defined NPI_UART_PORT
#if ((defined HAL_UART_SPI) && (HAL_UART_SPI != 0))
#define NPI_UART_PORT HAL_UART_PORT_1
#else
#define NPI_UART_PORT HAL_UART_PORT_0
#endif
#endif
*/
#if !defined NPI_UART_PORT
#if ((defined HAL_UART_SPI) && (HAL_UART_SPI != 0))
#define NPI_UART_PORT HAL_UART_PORT_1
#else
#if (defined ZAPP_P1)
#define NPI_UART_PORT HAL_UART_PORT_0
#elif (defined ZAPP_P2)
#define NPI_UART_PORT HAL_UART_PORT_1
#else
#define NPI_UART_PORT HAL_UART_PORT_0
#endif
#endif
#endif
4.3、添加串口一的IO口初始化代码
在1.3.2章节的串口初始化函数调用前添加串口1的IO口初始化代码,如下:
// 配置io 口功能作为uart功能
{
//P1.6 as UART1_TXD,P1.7 as UART1_RXD
P1SEL |= 0xC0;
PERCFG |= 0x02;
P2SEL |= 0x20;
}