前言
单片机的世界从点灯开始。
RTL8762是BLE蓝牙芯片,SOC,是性能比51单片机高出很多的存在,甚至不弱于一些ARM32位单片机,不用来点灯真是太可惜。
关于蓝牙开始很多人都有误解,认为蓝牙就是可以连接电脑,播放歌曲。那是传统蓝牙给人们的印象太深刻了。蓝牙3.0以下版本都是传统蓝牙,追求的是HIFI,可以连续播放歌曲,而不用考虑功率。
蓝牙4.0版本(BLE蓝牙低功耗)以上是用来传少量的数据控制指令,而不是传歌曲这种流量大户,4.0、5.0不是3.0传统蓝牙的替代,是共存的。并且4.0、5.0在低功耗领域为蓝牙技术开辟了新的战场,以适应物联网时代的到来。物联网时代要时时在线,低功耗是必须的选择。
BLE可以干什么?
点灯---蓝牙点灯,或者叫无线点灯
RTL8762的世界也是从点灯开始。
一、开始
本文所用源码基于BEE2-SDK-v1.2.0修改而来,BEE2-SDK-v1.2.0是RTL8762的SDK,官方管RTL8762系列叫小蜜蜂(BEE),非常巧合Zigbee也带一个BEE,看来物联网应该是蜜蜂建立的。BEE2-SDK-v1.2.0可从www.realmcu.com获得
原始工程路径:BEE2-SDK-v1.2.0\board\evb\ble_peripheral\peripheral.uvprojx
官方SDK有很多工程例子,本文用的是ble_peripheral
peripheral是外设意思,ble+peripheral
1、工程中的文件夹:
1、include(一些头文件,不需要修改)
2、lib(静态库,不需要修改)
3、cmsis(ARM的比较基础的东西,不需要修改)
4、peripheral(RTL8762的外设,sdk里有各种外设驱动源码,按需添加)
5、profile(一般不用修改,GATT的配置文件,可以建立自己的profile)
6、app(主要的应用实现,重点修改文件都在这里)
2、 peripheral下的文件
我加的这几个文件,源文件都在BEE2-SDK-v1.2.0\src\mcu\peripheral下,用什么加什么
rtl876x_io_dlps.c
rtl876x_uart.c
rtl876x_rcc.c
rtl876x_gpio.c
uart用来串口通讯,gipo用来点灯
3、app下的文件
ancs.c(原工程自带)
app_task.c(原工程自带,rtos任务创建)
main.c(原工程自带,主文件,初始化调度)
peripheral_app.c(原工程自带,消息处理)
overlay_mgr.c(原工程自带)
uart.c
只有UART.C是后来添加的,实现了UART基本封装,其他都是原有工程自带
BLE编程主要是处理各类消息,由peripheral_app.c负责。
二、主要代码解释
1.main.c
main.c是应用入口,实现了main函数如下:
int main(void)
{
extern uint32_t random_seed_value;
srand(random_seed_value);
board_init();
le_gap_init(APP_MAX_LINKS);
gap_lib_init();
app_le_gap_init();
app_le_profile_init();
pwr_mgr_init();
task_init();
os_sched_start();
return 0;
}
这里的main函数完全不用修改,所有的RTL8762的程序都是这么写的。
board_init()是硬件初始化
*gap*是GAP初始化,GAP是BLE的重要概念
app_le_profile_init()是GATT初始化,GATT是BLE另一个重要概念
task_init()是OS_IF(freertos)的任务初始化,实时操作系统的任务创建
常用的应该就是这么几个
2.关于GAP、GATT的粗浅认识
打个比方如果把蓝牙设备比作商店,那么GAP就是商店的基本信息,比如商店名,地理位置等。而GATT就是商店提供的服务,比如某一类商品,商品的价格,款式,尺寸。根据需要还可以上架新的GATT服务。
如果用BLE手机调试工具来看如下图所示:
其中,Generic Access就是GAP,Unknown Service 和Battery Service 是GATT
为什么BLE手机调试工具可以识别Battery Service,而不能识别Unknown Service。那是因为蓝牙标准组织给GATT服务提供了统一的编号,Battery Service有统一的编号,所以被识别出来,而Unknown Service本来就是自定义的服务,程序提供的编号不在统一编码内,BLE手机调试工具也就无法识别。
3.board.h文件
main.c 文件include了board.h文件 ,board.h文件是开发板定义文件。
其中以下定义根据需要开启,本文用到了UART和GPIO,所以USE_UART_DLPS和USE_GPIO_DLPS赋值为1,此处很关键,否则程序无法正常运行:
/* if use any peripherals below, #define it 1 */
#define USE_I2C0_DLPS 0
#define USE_I2C1_DLPS 0
#define USE_TIM_DLPS 0
#define USE_QDECODER_DLPS 0
#define USE_IR_DLPS 0
#define USE_RTC_DLPS 0
#define USE_UART_DLPS 1
#define USE_ADC_DLPS 0
#define USE_SPI0_DLPS 0
#define USE_SPI1_DLPS 0
#define USE_SPI2W_DLPS 0
#define USE_KEYSCAN_DLPS 0
#define USE_DMIC_DLPS 0
#define USE_GPIO_DLPS 1
#define USE_PWM0_DLPS 0
#define USE_PWM1_DLPS 0
#define USE_PWM2_DLPS 0
#define USE_PWM3_DLPS 0
4.GPIO初始化
点灯用P4_0和P4_1
#define GPIO_OUTPUT_PIN_0 P4_0
#define GPIO_OUTPUT_PIN_1 P4_1
#define GPIO_PIN_OUTPUT GPIO_GetPin(GPIO_OUTPUT_PIN_0)
#define GPIO_PIN_OUTPUT1 GPIO_GetPin(GPIO_OUTPUT_PIN_1)
UART用P3_0和P3_1
#define UART_TX_PIN P3_0
#define UART_RX_PIN P3_1
board初始化: PAD(管脚)、UART
void board_init(void)
{
/**
* @ led gpio init
*/
Pad_Config(GPIO_OUTPUT_PIN_0, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_ENABLE,
PAD_OUT_HIGH);
Pinmux_Config(GPIO_OUTPUT_PIN_0, DWGPIO);
Pad_Config(GPIO_OUTPUT_PIN_1, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_ENABLE,
PAD_OUT_HIGH);
Pinmux_Config(GPIO_OUTPUT_PIN_1, DWGPIO);
/**
* @ uart
*/
board_uart_init();
}
void board_uart_init(void)
void board_uart_init(void)
{
Pad_Config(UART_TX_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_HIGH);
Pad_Config(UART_RX_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_HIGH);
Pinmux_Config(UART_TX_PIN, UART0_TX);
Pinmux_Config(UART_RX_PIN, UART0_RX);
}
设备初始化:GPIO、UART
void driver_init(void)
{
/* Initialize GPIO */
RCC_PeriphClockCmd(APBPeriph_GPIO, APBPeriph_GPIO_CLOCK, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_PIN_OUTPUT|GPIO_PIN_OUTPUT1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_ITCmd = DISABLE;
GPIO_Init(&GPIO_InitStruct);
//GPIO_InitStruct.GPIO_Pin = GPIO_PIN_OUTPUT1;
//GPIO_Init(&GPIO_InitStruct);
//lightUpLed();
/* uart init*/
driver_uart_init();
}
void driver_uart_init(void)
void driver_uart_init(void)
{
UART_DeInit(UART);
RCC_PeriphClockCmd(APBPeriph_UART0, APBPeriph_UART0_CLOCK, ENABLE);
/* uart init */
UART_InitTypeDef UART_InitStruct;
UART_StructInit(&UART_InitStruct);
/* Config uart baudrate */
UART_InitStruct.div = BaudRate_Table[BAUD_RATE_115200].div;
UART_InitStruct.ovsr = BaudRate_Table[BAUD_RATE_115200].ovsr;
UART_InitStruct.ovsr_adj = BaudRate_Table[BAUD_RATE_115200].ovsr_adj;
UART_InitStruct.parity = UART_PARITY_NO_PARTY;
UART_InitStruct.stopBits = UART_STOP_BITS_1;
UART_InitStruct.wordLen = UART_WROD_LENGTH_8BIT;
UART_InitStruct.rxTriggerLevel = 16; //1~29
UART_InitStruct.idle_time = UART_RX_IDLE_2BYTE; //idle interrupt wait time
UART_Init(UART, &UART_InitStruct);
uart_sendString1("#### # ##### # # # #### ##### # ##### #### \r\n");
uart_sendString1(" # # # # # # # # # # # # # # # \r\n");
uart_sendString1(" # # # # # # # # # # # # # # # \r\n");
uart_sendString1(" ### # #### # # # # #### # # #### # # \r\n");
uart_sendString1(" # # # # # # ##### # # # # # # # \r\n");
uart_sendString1(" # # # # # # # # # # # # # # # \r\n");
uart_sendString1("#### ##### ##### ### # # # # # ##### ##### #### \r\n");
uart_sendString1(" ##### ##### \r\n");
uart_sendString1("Sarting......");
}
5.点灯函数
/*低电平点灯*/
void lightUpLed(void)
{
/* Light up LED0 */
GPIO_WriteBit(GPIO_PIN_OUTPUT, (BitAction)(0));
GPIO_WriteBit(GPIO_PIN_OUTPUT1,(BitAction)(0));
}
/*高电平灭灯*/
void lightDownLed(void)
{
/* Light down LED0 */
GPIO_WriteBit(GPIO_PIN_OUTPUT, (BitAction)(1));
GPIO_WriteBit(GPIO_PIN_OUTPUT1,(BitAction)(1));
}
/*闪灯*/
void flashLed(void)
{
for(uint32_t j=0;j<5;j++)
{
lightUpLed();
for (uint32_t i = 0; i < 100000; i++);
lightDownLed();
}
}
6、UART串口发送
其中uart_senddata_continuous是官方DEMO里的函数
void uart_sendString1(char* str)
{
uint16_t demo_str_len = 0;
demo_str_len = strlen(str);
memcpy(String_Buf1, str, demo_str_len);
/* Send demo tips */
uart_senddata_continuous(UART, String_Buf1, demo_str_len);
}
7、处理消息点灯,发送UART信息
在peripheral_app.c中,查找“BAS_READ_BATTERY_LEVEL”做如下修改
改成静态变量:static uint8_t battery_level = 90;
添加:battery_level++;
添加用于点灯:lightUpLed();
添加用于UART输出信息:uart_sendString1("Get BATTERY LEVEL\r\n");
case SERVICE_CALLBACK_TYPE_READ_CHAR_VALUE:
{
if (p_bas_cb_data->msg_data.read_value_index == BAS_READ_BATTERY_LEVEL)
{
static uint8_t battery_level = 90;
APP_PRINT_INFO1("BAS_READ_BATTERY_LEVEL: battery_level %d", battery_level);
uart_sendString1("Get BATTERY LEVEL\r\n");
bas_set_parameter(BAS_PARAM_BATTERY_LEVEL, 1, &battery_level);
battery_level++;
//trun_led(0);
lightUpLed();
}
}
8.蓝牙未连接时LED闪烁,连接时LED熄灭
app_task.c中增加timer的任务,回调函数是timer_callback;
另外用task方式建了一个uart任务,实验task方式处理UART。
void app_task_init()
{
// void *p_handle=NULL;
os_task_create(&app_task_handle, "app", app_main_task, 0, APP_TASK_STACK_SIZE,
APP_TASK_PRIORITY);
//timer task
if(os_timer_create(&p_handle,"timer",0,1000,true,timer_callback )== true)
{
os_timer_start(&p_handle);
}
else
{
//Timer failed to creat.
}
//uart task
os_task_create(&uart_task_handle, "uart", uart_task, 0, APP_TASK_STACK_SIZE,
APP_TASK_PRIORITY);
}
timer的任务的处理函数,回调函数负责闪烁LED(用了PAD方式,不是GPIO),timer_stop负责停止定时,闪烁停止;timer_restart负责重新定时,继续闪烁。
oid timer_callback(void *p_handle)
{
static uint8_t count =0;
if(count == 0)
{
Pad_Config(P4_0,PAD_SW_MODE,PAD_IS_PWRON,PAD_PULL_NONE,PAD_OUT_ENABLE,PAD_OUT_LOW);
count = 1;
}
else{
Pad_Config(P4_0,PAD_SW_MODE,PAD_IS_PWRON,PAD_PULL_NONE,PAD_OUT_ENABLE,PAD_OUT_HIGH);
count = 0;
}
}
void timer_stop(void){
os_timer_stop(&p_handle);
Pad_Config(P4_0,PAD_SW_MODE,PAD_IS_PWRON,PAD_PULL_NONE,PAD_OUT_ENABLE,PAD_OUT_HIGH);
}
void timer_restart(void){
os_timer_restart(&p_handle,100);
}
在peripheral_app.c中,查找“GAP_CONN_STATE_DISCONNECTED”,增加timer_restart(),实现在蓝牙断开后,LED继续闪烁。在“GAP_CONN_STATE_CONNECTED”后面增加timer_stop();,实现蓝牙连接后,LED停止闪烁。
case GAP_CONN_STATE_DISCONNECTED:
{
if ((disc_cause != (HCI_ERR | HCI_ERR_REMOTE_USER_TERMINATE))
&& (disc_cause != (HCI_ERR | HCI_ERR_LOCAL_HOST_TERMINATE)))
{
APP_PRINT_ERROR1("app_handle_conn_state_evt: connection lost cause 0x%x", disc_cause);
}
le_adv_start();
timer_restart();
}
break;
case GAP_CONN_STATE_CONNECTED:
{
uint16_t conn_interval;
uint16_t conn_latency;
uint16_t conn_supervision_timeout;
uint8_t remote_bd[6];
T_GAP_REMOTE_ADDR_TYPE remote_bd_type;
le_get_conn_param(GAP_PARAM_CONN_INTERVAL, &conn_interval, conn_id);
le_get_conn_param(GAP_PARAM_CONN_LATENCY, &conn_latency, conn_id);
le_get_conn_param(GAP_PARAM_CONN_TIMEOUT, &conn_supervision_timeout, conn_id);
le_get_conn_addr(conn_id, remote_bd, &remote_bd_type);
APP_PRINT_INFO5("GAP_CONN_STATE_CONNECTED:remote_bd %s, remote_addr_type %d, conn_interval 0x%x, conn_latency 0x%x, conn_supervision_timeout 0x%x",
TRACE_BDADDR(remote_bd), remote_bd_type,
conn_interval, conn_latency, conn_supervision_timeout);
timer_stop();
}
总结
1、源码这里下载https://download.csdn.net/download/weixin_44067125/87995309
2、主要实现以下功能:
BATTERY LEVEL 读取电量自增1、点亮LED
自定义characteristic 发送HEX数据:0A点亮led,发送14熄灭led,发送其他LED闪烁
蓝牙未连接时,LED闪烁,连接时LED熄灭
3、BLE基本概念需要了解
4、RTOS实时操作系统要了解一些,至少知道创建task
5、KEIL编译,MPTOOL烧写
6、手机上的BLE调试工具需要安一个,我用的是沁恒家的
7、板子:RTL8762C 开发板 - 嘉立创EDA开源硬件平台
RTL8762的手册和SDK前前后后看了一个月,终于可以点灯。小白自学没办法,都是新知识,卡在一个地方好久都出不来。
UART串口助手接收信息:
发送14(HEX)灭LED
接收电量信息,每次加1,LED亮
发送0A(HEX),LED亮