硬件环境:
(1)开发板:ST官方的stm32wb55-st-nucleo开发板
软件环境:
(1)开发环境:RT-Thread Studio 2.0.0
(2)RTOS:RT-Thread 4.0.x
(3)到ST官方下载stm32wb55开发包stm32cube_fw_wb_v1.10.0.zip,本次移植会用到官方开发包内的无线库(STM32_WPAN文件夹)和BLE简单外设示例(BLE_Peripheral_Lite)。
实现目标:将BLE_Peripheral_Lite示例BLE收发功能移植到RT-Thread系统。
开始移植:
1. 打开RT-Thread Studio,在SDK Manger内添加stm32wb55_st_nucleo资源包。
2. 基于开发板stm32wb55_st_nucleo建立rt-thread工程。
3. 编译工程无错误,下载到开发板,但无法运行。排查后发现是启动汇编文件默认跳转到main,应该更改为entry,所以将如下文件:
如下内容:
/* Call the application s entry point.*/
bl main
修改为:
/* Call the application s entry point.*/
bl entry
再次编译、下载、运行成功:
3. 在工程目录内新建modules目录(后期放自己的模块驱动),并新建SConscript文件:
import os
from building import *
objs = []
cwd = GetCurrentDir()
list = os.listdir(cwd)
for item in list:
if os.path.isfile(os.path.join(cwd, item, 'SConscript')):
objs = objs + SConscript(os.path.join(item, 'SConscript'))
Return('objs')
4. 在modules目录内新建ble文件夹,并新建SConscript文件:
from building import *
cwd = GetCurrentDir()
CPPPATH = [cwd]
src = Glob('*.c')
group = DefineGroup('modules', src, depend = [''], CPPPATH = CPPPATH)
# search in the file system implementation
list = os.listdir(cwd)
for item in list:
if os.path.isfile(os.path.join(cwd, item, 'SConscript')):
group = group + SConscript(os.path.join(item, 'SConscript'))
Return('group')
5. 将ST官方固件库stm32cube_fw_wb_v1.10.0内的无线库STM32_WPAN复制到工程的modules/ble目录内,并在STM32_WPAN目录内新建SConscript文件:
import rtconfig
from building import *
# get current directory
cwd = GetCurrentDir()
# The set of source files associated with this SConscript file.
src = Split('''
ble/core/auto/ble_events.c
ble/core/auto/ble_gap_aci.c
ble/core/auto/ble_gatt_aci.c
ble/core/auto/ble_hal_aci.c
ble/core/auto/ble_hci_le.c
ble/core/auto/ble_l2cap_aci.c
ble/svc/Src/svc_ctl.c
interface/patterns/ble_thread/tl/hci_tl.c
interface/patterns/ble_thread/tl/hci_tl_if.c
interface/patterns/ble_thread/tl/shci_tl.c
interface/patterns/ble_thread/tl/shci_tl_if.c
interface/patterns/ble_thread/tl/tl_mbox.c
interface/patterns/ble_thread/shci/shci.c
ble/core/template/osal.c
utilities/otp.c
utilities/stm_list.c
utilities/stm_queue.c
''')
path = [cwd + '',
cwd + '/interface/patterns/ble_thread',
cwd + '/interface/patterns/ble_thread/tl',
cwd + '/interface/patterns/ble_thread/shci',
cwd + '/utilities',
cwd + '/ble',
cwd + '/ble/core/template',
cwd + '/ble/core/']
group = DefineGroup('modules', src, depend = [''], CPPPATH = path)
Return('group')
6. link.lds连接文件增加RAM_SHARED区:
MEMORY
{
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 1024k /* 1024KB flash */
RAM (rw) : ORIGIN = 0x20000000, LENGTH = 192k /* 192KB sram */
RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K
}
7. board.c内的时钟配置SystemClock_Config()开启RF时钟:
以下:
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK4|RCC_CLOCKTYPE_HCLK2
|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK;
更改为:
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK4|RCC_CLOCKTYPE_HCLK2
|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
8.将ST官方固件库stm32cube_fw_wb_v1.10.0内的示例工程BLE_Peripheral_Lite内的inc和Src内的文件,复制到自己工程的modules/ble目录内。之后就是无聊而漫长的编译、找错误、修改、删除、增加等等的细节工作,最终可以正常运行,蓝牙助手可以连接成功,收发收据正常。另外,ST的示例BLE用户接口个人感觉不友好,写了一个ble_api.h的接口文件,测试ble_sample.c调用ble_api.h接口文件实现基本功能,详见测试工程代码。
最后初步移植完的目录文件(还可以进一步精简):
其中,ble_sample.c是测试程序:
void ble_sample(void)
{
static uint8_t send_buf[2];
ble_api.gap_notice_attach = user_gap_notification;
ble_api.gatt_notice_attach = user_gatt_notification;
ble_api.init();
while(1)
{
send_buf[0] = 0;
send_buf[1] ++;
ble_api.notify_send(send_buf,2); //发送2字节测试数据
rt_thread_mdelay(1000);
}
}
static void user_gap_notification(user_gap_event_t event)
{
switch (event)
{
case BLE_INIT_OK:
LOG_D("ble init ok!");
ble_api.advertising_start();
break;
case GAP_CONNECT:
LOG_D("ble connect!");
break;
case GAP_DISCONNECT:
LOG_D("ble disconnect!");
ble_api.advertising_start();
break;
default:
break;
}
}
static void user_gatt_notification(user_gatt_notice_t *p_notice)
{
switch (p_notice->event)
{
case GATT_NOTIFY_ENABLE:
LOG_D("GATT_NOTIFY_ENABLE!");
break;
case GATT_NOTIFY_DISABLE:
LOG_D("GATT_NOTIFY_DISABLE!");
break;
case GATT_WRITE_CHAR:
LOG_D("GATT_WRITE_CHAR! length=%d,payload[0]=0x%02X,[1]=0x%02X,",p_notice->length,p_notice->payload[0],p_notice->payload[1]);
break;
default:
break;
}
}
MSH_CMD_EXPORT(ble_sample, ble sample start.);
在控制台输入ble_sample即可测试功能:
9. ST的蓝牙BLE移植最主要的就是需要建立2个互斥量、2个信号量、1个事件集(2个事件),协议栈的运行特别是多线程时,ST可能是考虑到了一些互斥逻辑,ST的BLE协议栈会有对应的调用接口,分为SHCI、HCI两组接口,这两组格式基本一致。每组接口分别对应获取和释放互斥量、获取和释放信号量、事件通知MCU该调用特定的函数。再建立一个协议栈事件处理线程,完成这些后,再增加两个中断接口用于双核间通信,这样协议栈就可以运行了,调用协议栈的应用接口就可以进行蓝牙通信操作了,如广播、注册用户服务、接收发送数据、更改连接参数等等。移植主要内容汇总:
(1)协议栈事件通知调用接口
void hci_notify_asynch_evt(void* pdata)
{
(void)pdata;
rt_err_t result;
result = rt_event_send(async_evt_id,HCI_EVENT);
RT_ASSERT(result == RT_EOK);
}
void shci_notify_asynch_evt(void* pdata)
{
(void)pdata;
rt_err_t result;
result = rt_event_send(async_evt_id,SHCI_EVENT);
RT_ASSERT(result == RT_EOK);
}
(2)协议栈信号量获取和释放调用接口
void hci_cmd_resp_wait(uint32_t timeout)
{
(void)timeout;
rt_err_t result;
result = rt_sem_take(hci_sem_id, RT_WAITING_FOREVER);
RT_ASSERT(result == RT_EOK);
}
void hci_cmd_resp_release(uint32_t flag)
{
(void)flag;
rt_err_t result;
result = rt_sem_release(hci_sem_id);
RT_ASSERT(result == RT_EOK);
}
void shci_cmd_resp_wait(uint32_t timeout)
{
(void)timeout;
rt_err_t result;
result = rt_sem_take(shci_sem_id, RT_WAITING_FOREVER);
RT_ASSERT(result == RT_EOK);
}
void shci_cmd_resp_release(uint32_t flag)
{
(void)flag;
rt_err_t result;
result = rt_sem_release(shci_sem_id);
RT_ASSERT(result == RT_EOK);
}
(3)协议栈互斥量获取和释放调用接口
static void BLE_StatusNotificationCallback(HCI_TL_CmdStatus_t status)
{
rt_err_t result;
/* Callback not implemented - code flow under control of the developer */
switch (status)
{
case HCI_TL_CmdBusy:
result = rt_mutex_take(hci_mtx_id,RT_WAITING_FOREVER);
RT_ASSERT(result == RT_EOK);
break;
case HCI_TL_CmdAvailable:
rt_mutex_release(hci_mtx_id);
break;
default:
break;
}
}
static void SYS_StatusNotificationCallback( SHCI_TL_CmdStatus_t status )
{
rt_err_t result;
/* Callback not implemented - code flow under control of the developer */
switch (status)
{
case SHCI_TL_CmdBusy:
result = rt_mutex_take(shci_mtx_id,RT_WAITING_FOREVER);
RT_ASSERT(result == RT_EOK);
break;
case SHCI_TL_CmdAvailable:
rt_mutex_release(shci_mtx_id);
break;
default:
break;
}
}
(4)协议栈运行事件处理线程
static void ble_evt_proc_thread_entry(void *parameter)
{
(void)parameter;
rt_uint32_t e;
for(;;)
{
if (rt_event_recv(async_evt_id, (SHCI_EVENT | HCI_EVENT),
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &e) == RT_EOK)
{
if (e & SHCI_EVENT)
{
shci_user_evt_proc();
}
if (e & HCI_EVENT)
{
hci_user_evt_proc();
}
}
}
}
(5)增加M0与M4和通信用到的中断接口
void IPCC_C1_TX_IRQHandler(void)
{
HW_IPCC_Tx_Handler();
return;
}
void IPCC_C1_RX_IRQHandler(void)
{
HW_IPCC_Rx_Handler();
return;
}
总结
本次移植其实比较初级,是自己学习的一个记录,也不严谨,只是以实现功能和学习为目的。实际应用还得仔细研究协议栈ST是怎么实现的,精炼代码,提高可靠性,研究连接等参数。同时,本次移植是使协议栈运行起来,并没有进行添加服务UUID、各读写特征、更改广播信息等等,这些东西可参考官方例程进行添加和修改。
本次移植的代码下载地址:
链接:https://pan.baidu.com/s/1soNaPZRKFlceaUO4PyEVuw
提取码:9wwd