实验目的
本文利用STM32CubeMx初始化配置STM32WB55,使用自己生成的顾客服务与特性实现蓝牙之间的读写通知,通过写控制LED2亮灭,读取LED1的状态,通知LED3(300ms闪烁)来实现本实验的目的。
实验环境
- STM32CubeMx V6.1.0 , KeilMDK V5.33.0.0 ,ST BLE Sensor v4.6.1 (安卓版本) , WireShark V3.4.0
- NUCLE-WB55.Nucleo开发板
- STM32WB55参考手册 , NUCLE-WB55.Nucleo开发板原理图
- 注意 : 要实现程序的下载 PC端需要安装STMicroelectronics stlink-server这个驱动
硬件架构实现
- STM32WB55主要特性:2颗独立内核,CPU1主要是用户需求,通俗的讲,接收到的数据该如何去用。比如当客户端(ST BLE Sensor)写数据,服务器(NUCLE-WB55.Nucleo)将数据接收到,我们将接收到的数据进行利用。本文利用客户端写入0x01和0x00实现开发板上的LED2亮灭;CPU2的功能就是一个蓝牙芯片,例如Cannon的Bluenrg。
- 处理器间的通信:处理器间的通信是通过处理器间通信控制器 (IPCC)来实现的。
IPCC主要特性:
- 12 个通道传输状态信号
- 每个处理器有两条中断线
- 按通道屏蔽中断
- 两种通道工作模式
通过STM32WB55参考手册可知,CPU1和CPU2之间通过IPCC的RX和TX中断实现的。因此在编写代码过程中需使用到这两个中断。
软件架构的实现
本文通过STM32CubeMx生成,利用HAL库实现。
打开KeilMOK,在maim.c文件下可以看到一些初始化:
HAL_Init(); //重置所有外围设备,初始化Flash接口和Systick。 SystemClock_Config(); //配置系统时钟 MX_GPIO_Init(); //初始化IO口 MX_RF_Init(); //初始化RF,由于RF由于CPU2来控制的,因此在这里不配置,因此此函数为空 MX_RTC_Init(); //初始化RTC MX_TIM2_Init(); //初始化定时器 APPE_Init(); //STM32_WPAN的初始化代码
本文主要是实现蓝牙通信,因此APPE_Init这个函数就是用来对蓝牙进行相关的操作。进入APPE_Init可以看到;
SystemPower_Config(); //配置系统电源模式 HW_TS_Init(hw_ts_InitMode_Full, &hrtc); //初始化TimerServer BSP_LED_On(LED1); //顾客函数,相当于只要进入此函数,LED1点亮。 appe_Tl_Init(); //初始化所有传输层 return;
前面三个函数不必过于纠结,在本文本中还用不到理解,主要是appe_Tl_Init这个函数:
TL_MM_Config_t tl_mm_config; SHCI_TL_HciInitConf_t SHci_Tl_Init_Conf; /**< Reference table initialization */ TL_Init(); /**< System channel initialization */ UTIL_SEQ_RegTask( 1<< CFG_TASK_SYSTEM_HCI_ASYNCH_EVT_ID, UTIL_SEQ_RFU, shci_user_evt_proc ); SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&SystemCmdBuffer; SHci_Tl_Init_Conf.StatusNotCallBack = APPE_SysStatusNot; shci_init(APPE_SysUserEvtRx, (void*) &SHci_Tl_Init_Conf); /**< Memory Manager channel initialization */ tl_mm_config.p_BleSpareEvtBuffer = BleSpareEvtBuffer; tl_mm_config.p_SystemSpareEvtBuffer = SystemSpareEvtBuffer; tl_mm_config.p_AsynchEvtPool = EvtPool; tl_mm_config.AsynchEvtPoolSize = POOL_SIZE; TL_MM_Init( &tl_mm_config ); TL_Enable(); return;
这里不过多解释,后面还会讲到这里,这里主要是APPE_SysUserEvtRx这个函数:
static void APPE_SysUserEvtRx( void * pPayload ) { UNUSED(pPayload); APPD_EnableCPU2( ); //使能CPU2 APP_BLE_Init( ); //初始化BLE各层 UTIL_LPM_SetOffMode(1U << CFG_LPM_APP, UTIL_LPM_ENABLE); return; }
这个函数的作用就是实现CPU2使能,以及BLE各层初始化,这边也不过多解释,后面将会介绍。这样主函数初始化就结束了,程序从现在开始,就会等待就绪事件(VS_HCI_C2_Ready)。
while(1)循环函数。
在while(1)循环函数可以看到只用了一个函数 UTIL_SEQ_Run,看到这里估计会有些蒙。其实这里用到了调度器。
这个时候就开始用到之前没有讲到的地方了。UTIL_SEQ_RegTask和 UTIL_SEQ_SetTask。
UTIL_SEQ_RegTask这个函数类似于登记,UTIL_SEQ_SetTask类似于操作系统的信号量。
比如上文中看到UTIL_SEQ_RegTask( 1<< CFG_TASK_SYSTEM_HCI_ASYNCH_EVT_ID, UTIL_SEQ_RFU, shci_user_evt_proc );以及在app_entry.c文件末尾出可以找到shci_notify_asynch_evt这个函数,它包含了
UTIL_SEQ_SetEvt( 1<< CFG_IDLEEVT_SYSTEM_HCI_CMD_EVT_RSP_ID );这个函数。从这里可以看出这两个函数:UTIL_SEQ_SetEvt( 1<< CFG_IDLEEVT_SYSTEM_HCI_CMD_EVT_RSP_ID ); UTIL_SEQ_RegTask( 1<< CFG_TASK_SYSTEM_HCI_ASYNCH_EVT_ID, UTIL_SEQ_RFU, shci_user_evt_proc );
通俗的来讲,当程序进入到UTIL_SEQ_SetEvt( 1<< CFG_IDLEEVT_SYSTEM_HCI_CMD_EVT_RSP_ID );这个函数里,
UTIL_SEQ_Run这个函数就会找到UTIL_SEQ_RegTask( 1<< CFG_TASK_SYSTEM_HCI_ASYNCH_EVT_ID, UTIL_SEQ_RFU, shci_user_evt_proc );这个函数下的shci_user_evt_proc函数,最终程序执行shci_user_evt_proc函数里的东西。蓝牙连接所使用的函数
在custom_stm.c文件下,Custom_STM_Event_Handler函数就是对事件的处理,以下case语句就是Custom_STM_Event_Handler的部分代码,通过接收到的读写通知事件执行相关程序。case EVT_BLUE_GATT_ATTRIBUTE_MODIFIED: /* USER CODE BEGIN EVT_BLUE_GATT_ATTRIBUTE_MODIFIED */ attribute_modified = (aci_gatt_attribute_modified_event_rp0*)blue_evt->data; if(attribute_modified->Attr_Handle == (CustomContext.CustomShortledserviceHdle + 4)) { return_value = SVCCTL_EvtAckFlowEnable; if(attribute_modified->Attr_Data[0] & COMSVC_Notification) { Notification.Custom_Evt_Opcode = CUSTOM_STM_SHORTREADANDWRITEORNOTICECHAR_NOTIFY_ENABLED_EVT; Custom_STM_App_Notification(&Notification); } else { Notification.Custom_Evt_Opcode =CUSTOM_STM_SHORTREADANDWRITEORNOTICECHAR_NOTIFY_DISABLED_EVT; Custom_STM_App_Notification(&Notification); } } /* USER CODE END EVT_BLUE_GATT_ATTRIBUTE_MODIFIED */ break; case EVT_BLUE_GATT_READ_PERMIT_REQ : /* USER CODE BEGIN EVT_BLUE_GATT_READ_PERMIT_REQ */ read_perm_req = (aci_gatt_read_permit_req_event_rp0 *)blue_evt->data; if(read_perm_req->Attribute_Handle == (CustomContext.CustomShortledserviceHdle + 2)) { return_value = SVCCTL_EvtAckFlowEnable; Notification.Custom_Evt_Opcode = CUSTOM_STM_SHORTREADANDWRITEORNOTICECHAR_READ_EVT; Custom_STM_App_Notification(&Notification); aci_gatt_allow_read(read_perm_req->Connection_Handle); } /* USER CODE END EVT_BLUE_GATT_READ_PERMIT_REQ */ break; case EVT_BLUE_GATT_WRITE_PERMIT_REQ: /* USER CODE BEGIN EVT_BLUE_GATT_WRITE_PERMIT_REQ */ write_perm_req = (aci_gatt_write_permit_req_event_rp0*)blue_evt->data; if( write_perm_req->Attribute_Handle == (CustomContext.CustomShortledserviceHdle + 2)) { return_value = SVCCTL_EvtAckFlowEnable; Notification.Custom_Evt_Opcode = CUSTOM_STM_SHORTREADANDWRITEORNOTICECHAR_WRITE_NO_RESP_EVT; Notification.DataTransfered.Length=write_perm_req->Data_Length; Notification.DataTransfered.pPayload=write_perm_req->Data; aci_gatt_write_resp(write_perm_req->Connection_Handle,write_perm_req->Attribute_Handle,0,0,write_perm_req->Data_Length,write_perm_req->Data); Custom_STM_App_Notification(&Notification); } /* USER CODE END EVT_BLUE_GATT_WRITE_PERMIT_REQ */ break;
在通知过程中,本文利用TIM2定时器。每隔100ms发送通知,因此在定时器的回调函数中添加UTIL_SEQ_SetTask标志,在 APP_BLE_Init函数中添加UTIL_SEQ_SetTask函数。这里就不过多介绍。
实验步骤
STM32CubeMx配置
软件设计
软件设计视频中少添加了 attribute_modified = (aci_gatt_attribute_modified_event_rp0*)blue_evt->data,因此无法通知,可以参考下面例程。资料
百度网盘
链接:https://pan.baidu.com/s/1r8_FHHor-gnewPku_j2hmQ
提取码:9g0g