使用Nordic的nrf52832控制指定从机(一主多从)

news/2025/2/12 19:37:20/

一主多从

  • 1. 想要实现的功能
  • 2. 从机
  • 3. 主机
    • 3.1 主从机连接个数设置
    • 3.2 扫描过滤
    • 3.3 连接和断开连接
    • 3.4 按键处理
    • 3.5 从机读写
      • 3.5.1 写
      • 3.5.1 读
  • 4运行效果

1. 想要实现的功能

1.主机能连接多个从机(主机作为控制器,从机作为节点)。
2.主机能使用不同的按键控制不同的节点(按键和节点一一对应,与从机的连接顺序无关)。
3.主机扫描过滤器使用设备全称,记录和从机的连接句柄,并进行控制。

2. 从机

从机使用例程:nRF5_SDK_17.1.0_ddde560\examples\ble_peripheral\ble_app_blinky\pca10040\s132\arm5_no_packs
以使用两个从机为例,从机的蓝牙名称分别改为Nordic_BedRoom和Nordic_DrawingRoom。

3. 主机

主机使用例程:nRF5_SDK_17.1.0_ddde560\examples\ble_central\ble_app_multilink_central\pca10040\s132\arm5_no_packs

如果不记录连接句柄的话从机连接顺序的不同会导致不能控制指定的从机,所以.h文件需要定义一些自己需要的内容:

#ifndef _MY_BLE_DEVIVE_
#define _MY_BLE_DEVIVE_typedef enum{INIT = 1,SCAN,CONN,READ,WRITE,DISCONN,
}PROC_TYPE;typedef enum{bedroom_device = 0,			//从机1,由主机的按键1控制drawingroom_device,			//从机2,由主机的按键2控制init_device,				//无效,初始化句柄用
}control_device;typedef struct{bool led_state;				//led状态uint8_t	my_scan_state :1;	//扫描到从机uint8_t	my_conn_state :1;	//连接到从机uint16_t my_conn_handle;	//连接句柄
}my_conn_record;				//从机记录结构体void my_device_manage(control_device dev,PROC_TYPE type,uint16_t handle);	//从机操作管理函数#endif

.c文件中定义:

BLE_LBS_C_ARRAY_DEF(m_lbs_c, NRF_SDH_BLE_CENTRAL_LINK_COUNT);#define BedRoom_BUTTON				BSP_BUTTON_0	//主机按键1控制从机1LED状态
#define DrawingRoom_BUTTON          BSP_BUTTON_1 	//主机按键2控制从机2LED状态
#define BedRoomRead_BUTTON			BSP_BUTTON_2	//主机按键3读取从机1LED状态
#define DrawingRoomRead_BUTTON      BSP_BUTTON_3	//主机按键4读取从机2LED状态
my_conn_record	conn_record[NRF_SDH_BLE_CENTRAL_LINK_COUNT];	//从机记录结构体数组//conn_record结构体数组的顺序同control_device中枚举的设备顺序一致void BedRoomProcessFunc(PROC_TYPE type)	//从机1处理函数
{ret_code_t err_code;switch(type){case READ:led_status_read_from_device(bedroom_device);if (err_code == NRF_SUCCESS){NRF_LOG_INFO("Read BedRoom LED state");}break;case WRITE:{conn_record[bedroom_device].led_state = !conn_record[bedroom_device].led_state;err_code = led_status_send_to_device(bedroom_device,conn_record[bedroom_device].led_state);if (err_code == NRF_SUCCESS){NRF_LOG_INFO("BedRoom write LED state %d", conn_record[bedroom_device].led_state);}}break;default:break;}	
}void DrawingRoomProcessFunc(PROC_TYPE type)	//从机2处理函数
{ret_code_t err_code;switch(type){case READ:err_code = led_status_read_from_device(drawingroom_device);if (err_code == NRF_SUCCESS){NRF_LOG_INFO("Read DrawingRoom LED state");}break;case WRITE:{conn_record[drawingroom_device].led_state = !conn_record[drawingroom_device].led_state;err_code = led_status_send_to_device(drawingroom_device,conn_record[drawingroom_device].led_state);if (err_code == NRF_SUCCESS){NRF_LOG_INFO("DrawingRoom write LED state %d", conn_record[drawingroom_device].led_state);}}break;default:break;}	
}void (*ProcessFunc[2])(PROC_TYPE type) =	//从机处理函数列表
{BedRoomProcessFunc,DrawingRoomProcessFunc,
};void my_device_manage(control_device dev,PROC_TYPE type,uint16_t handle)	//从机操作管理函数
{switch(type){case INIT:	//初始化,将conn_record结构体数组的句柄初始化为BLE_CONN_HANDLE_INVALID(0xFFFF)for (uint32_t i = 0; i< NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++){conn_record[i].my_conn_handle = handle;}break;case SCAN:	//记录哪个从机被主机扫描到conn_record[dev].my_scan_state = true;conn_record[dev].my_conn_state = false;conn_record[dev].my_conn_handle = handle;break;case CONN:	//记录哪个从机被主机连接conn_record[dev].my_scan_state = false;conn_record[dev].my_conn_state = true;conn_record[dev].my_conn_handle = handle;break;case READ:case WRITE:	//从机状态读写处理if((conn_record[dev].my_conn_state)&&(conn_record[dev].my_conn_handle != BLE_CONN_HANDLE_INVALID)){ProcessFunc[dev](type);}else{NRF_LOG_INFO("%s device error", m_target_periph_name[dev]);}break;case DISCONN:	//记录哪个从机断开,并将其连接句柄设置为BLE_CONN_HANDLE_INVALID(0xFFFF)conn_record[dev].my_scan_state = false;conn_record[dev].my_conn_state = false;conn_record[dev].my_conn_handle = handle;break;default:break;}
}

主函数中执行my_device_manage()函数把conn_record数组成员的连接句柄my_conn_handle 全部设置为BLE_CONN_HANDLE_INVALID(0xFFFF)。my_device_manage()函数中状态机除了CONN都会把连接句柄设置为0xFFFF,目的是未连接的从机不对其进行读写,第一个从机连接后连接句柄会是0,这样的话就会出现在读写从机时使用conn_record中记录的连接句柄匹配m_lbs_c中的句柄时错误的匹配到0。
在这里插入图片描述

3.1 主从机连接个数设置

宏定义:
在这里插入图片描述
使用一个主机,两个从机,所以主机要支持两路连接,NRF_SDH_BLE_CENTRAL_LINK_COUNT设置为2。最大支持20路连接,也就是可以连接20个从机,在ble_gap.h中有相关定义,支持的最大角色数目是20(主机连接个数+从机连接个数)。
在这里插入图片描述

3.2 扫描过滤

由于两个从机的设备名称不一样,例程中扫描过滤器使用的是设备全称过滤,需要将设备名称改为从机的名称:

static char const m_target_periph_name[NRF_SDH_BLE_CENTRAL_LINK_COUNT][NRF_BLE_SCAN_NAME_MAX_LEN] = 
{{"Nordic_BedRoom"},{"Nordic_DrawingRoom"}};

要通过设备名称过滤的从机数:
在这里插入图片描述
在scan_init()函数中设置扫描过滤器:
在这里插入图片描述
nrf_ble_scan_filter_set()函数中调用nrf_ble_scan_name_filter_add()函数,里面会判断设备名称是否添加过滤器,没有添加的话就添加。函数中scan_filters.name_filter.name_cnt为已经添加过滤器的数量,scan_filters.name_filter.target_name[index]为具体的要连接的从机设备名称,过滤器添加顺序与control_device中枚举的从机设备顺序一致,因此也与conn_record结构体数组的从机设备顺序一致。

static ret_code_t nrf_ble_scan_name_filter_add(nrf_ble_scan_t * const p_scan_ctx,char           const * p_name)
{uint8_t   index;uint8_t * counter  = &p_scan_ctx->scan_filters.name_filter.name_cnt;uint8_t   name_len = strlen(p_name);// Check the name length.if ((name_len == 0) || (name_len > NRF_BLE_SCAN_NAME_MAX_LEN)){return NRF_ERROR_DATA_SIZE;}// If no memory for filter.if (*counter >= NRF_BLE_SCAN_NAME_CNT){return NRF_ERROR_NO_MEM;}// Check for duplicated filter.for (index = 0; index < NRF_BLE_SCAN_NAME_CNT; index++){if (!strcmp(p_scan_ctx->scan_filters.name_filter.target_name[index], p_name)){return NRF_SUCCESS;}}// Add name to filter.memcpy(p_scan_ctx->scan_filters.name_filter.target_name[(*counter)++],p_name,strlen(p_name));NRF_LOG_DEBUG("Adding filter on %s name", p_name);return NRF_SUCCESS;
}

scan_start()函数简单做一下修改:
在这里插入图片描述

主机扫描到从机后生成事件调用函数nrf_ble_scan_on_ble_evt(),然后执行函数nrf_ble_scan_on_adv_report(),会根据扫描过滤器设置去判断是不是要连接的从机。
在这里插入图片描述
在adv_name_compare函数中会按顺序比对扫描到的从机是不是要连接的从机,那么也就可以使用自己写的my_device_manage函数来记录哪个要连接的从机被扫描到了。

static bool adv_name_compare(ble_gap_evt_adv_report_t const * p_adv_report,nrf_ble_scan_t     const * const p_scan_ctx)
{nrf_ble_scan_name_filter_t const * p_name_filter = &p_scan_ctx->scan_filters.name_filter;uint8_t                            counter       =p_scan_ctx->scan_filters.name_filter.name_cnt;uint8_t  index;uint16_t data_len;data_len = p_adv_report->data.len;// Compare the name found with the name filter.for (index = 0; index < counter; index++){if (ble_advdata_name_find(p_adv_report->data.p_data,data_len,p_name_filter->target_name[index])){my_device_manage((control_device)index,SCAN,BLE_CONN_HANDLE_INVALID);//记录哪个要连接的从机被扫描到return true;}}return false;
}

3.3 连接和断开连接

在主从机连接或者断开连接的时候会产生相应的事件,在ble_evt_handler()函数中也进行连接句柄的记录:

        case BLE_GAP_EVT_CONNECTED:{/*其他*/for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++){if((conn_record[i].my_scan_state == true)&&(conn_record[i].my_conn_state == false)){my_device_manage((control_device)i,CONN,p_gap_evt->conn_handle);}}/*其他*/} break; // BLE_GAP_EVT_CONNECTEDcase BLE_GAP_EVT_DISCONNECTED:{/*其他*/for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++){if((p_gap_evt->conn_handle == conn_record[i].my_conn_handle)&&(conn_record[i].my_conn_handle != BLE_CONN_HANDLE_INVALID)){my_device_manage((control_device)i,DISCONN,BLE_CONN_HANDLE_INVALID);}}/*其他*/} break;

3.4 按键处理

按键初始化:
在这里插入图片描述
按键事件处理函数,只在按键按下时动作:

static void button_event_handler(uint8_t pin_no, uint8_t button_action)
{switch (pin_no){case BedRoom_BUTTON://控制从机1LED状态if(button_action){my_device_manage(bedroom_device,WRITE,BLE_CONN_HANDLE_INVALID);}break;case DrawingRoom_BUTTON://控制从机2LED状态if(button_action){my_device_manage(drawingroom_device,WRITE,BLE_CONN_HANDLE_INVALID);}break;case BedRoomRead_BUTTON://读取从机1LED状态if(button_action){my_device_manage(bedroom_device,READ,BLE_CONN_HANDLE_INVALID);}break;case DrawingRoomRead_BUTTON://读取从机2LED状态if(button_action){my_device_manage(drawingroom_device,READ,BLE_CONN_HANDLE_INVALID);}break;default:APP_ERROR_HANDLER(pin_no);break;}
}

3.5 从机读写

在my_device_manage()函数中对从机读写之前会判断是不是处于连接状态,连接句柄是不是有效:
在这里插入图片描述
如果有效就执行相应的从机处理函数,以从机1的处理函数为例:
在这里插入图片描述

3.5.1 写

由于从机1模拟的是LED控制器,所以在写之前加了一行状态翻转,然后调用ble_lbs_led_status_send函数来对从机写。conn_record结构体数组的顺序是从机1(bedroom_device)、从机2(drawingroom_device),并且已经记录了相应的连接句柄,所以需要跟m_lbs_c数组的句柄比对,在m_lbs_c中找到要控制的从机1。

static ret_code_t led_status_send_to_device(control_device dev,uint8_t button_action)
{ret_code_t err_code;for (uint32_t i = 0; i< NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++){if(conn_record[dev].my_conn_handle == m_lbs_c[i].conn_handle){err_code = ble_lbs_led_status_send(&m_lbs_c[i], button_action);if (err_code != NRF_SUCCESS &&err_code != BLE_ERROR_INVALID_CONN_HANDLE &&err_code != NRF_ERROR_INVALID_STATE){return err_code;}}}return NRF_SUCCESS;
}

3.5.1 读

在ble_lbs_c.h文件中ble_lbs_c_evt_t结构体的params成员中增加LED状态:
在这里插入图片描述
ble_lbs_c_evt_type_t中增加LED事件:
在这里插入图片描述
在lbs的GATT事件回调函数状态机中增加读事件:
在这里插入图片描述
在on_hvx()函数中除了按键通知处理之外增加LED读处理:

static void on_hvx(ble_lbs_c_t * p_ble_lbs_c, ble_evt_t const * p_ble_evt)
{// Check if the event is on the link for this instance.if (p_ble_lbs_c->conn_handle != p_ble_evt->evt.gattc_evt.conn_handle){return;}// Check if this is a Button notification.if (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_lbs_c->peer_lbs_db.button_handle)//按键通知{if (p_ble_evt->evt.gattc_evt.params.hvx.len == 1){ble_lbs_c_evt_t ble_lbs_c_evt;ble_lbs_c_evt.evt_type                   = BLE_LBS_C_EVT_BUTTON_NOTIFICATION;ble_lbs_c_evt.conn_handle                = p_ble_lbs_c->conn_handle;ble_lbs_c_evt.params.button.button_state = p_ble_evt->evt.gattc_evt.params.hvx.data[0];p_ble_lbs_c->evt_handler(p_ble_lbs_c, &ble_lbs_c_evt);}}if (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_lbs_c->peer_lbs_db.led_handle)//led读{if (p_ble_evt->evt.gattc_evt.params.hvx.len == 1){ble_lbs_c_evt_t ble_lbs_c_evt;ble_lbs_c_evt.evt_type                   = BLE_LBS_C_EVT_LED_READRESP;ble_lbs_c_evt.conn_handle                = p_ble_lbs_c->conn_handle;ble_lbs_c_evt.params.led.led_state		 = p_ble_evt->evt.gattc_evt.params.hvx.data[0];p_ble_lbs_c->evt_handler(p_ble_lbs_c, &ble_lbs_c_evt);}}
}

在lbs的客户端回调函数状态机中增加读LED事件处理,并记录至conn_record中:

static void lbs_c_evt_handler(ble_lbs_c_t * p_lbs_c, ble_lbs_c_evt_t * p_lbs_c_evt)
{switch (p_lbs_c_evt->evt_type){case BLE_LBS_C_EVT_DISCOVERY_COMPLETE:{/*其他*/} break; // BLE_LBS_C_EVT_DISCOVERY_COMPLETEcase BLE_LBS_C_EVT_BUTTON_NOTIFICATION:{/*其他*/} break; // BLE_LBS_C_EVT_BUTTON_NOTIFICATIONcase BLE_LBS_C_EVT_LED_READRESP:{for (uint32_t i = 0; i< NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++){if(conn_record[i].my_conn_handle == p_lbs_c_evt->conn_handle){NRF_LOG_INFO("%s curr state:%d", m_target_periph_name[i],p_lbs_c_evt->params.led.led_state);conn_record[i].led_state = p_lbs_c_evt->params.led.led_state;}} } break; // BLE_LBS_C_EVT_LED_READRESPdefault:// No implementation needed.break;}
}

读LED状态函数为led_status_read_from_device(),在从机1处理函数BedRoomProcessFunc()中调用:

static ret_code_t led_status_read_from_device(control_device dev)
{ret_code_t err_code;for (uint32_t i = 0; i< NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++){if(conn_record[dev].my_conn_handle == m_lbs_c[i].conn_handle){err_code = ble_lbs_led_status_get(&m_lbs_c[i]);if (err_code != NRF_SUCCESS &&err_code != BLE_ERROR_INVALID_CONN_HANDLE &&err_code != NRF_ERROR_INVALID_STATE){return err_code;}}}return NRF_SUCCESS;
}

仿照例程中的ble_lbs_led_status_send()函数写一个读LED状态的函数:

uint32_t ble_lbs_led_status_get(ble_lbs_c_t * p_ble_lbs_c)
{VERIFY_PARAM_NOT_NULL(p_ble_lbs_c);if (p_ble_lbs_c->conn_handle == BLE_CONN_HANDLE_INVALID){return NRF_ERROR_INVALID_STATE;}NRF_LOG_DEBUG("Read LED status");nrf_ble_gq_req_t read_req;memset(&read_req, 0, sizeof(nrf_ble_gq_req_t));read_req.type							= NRF_BLE_GQ_REQ_GATTC_READ;read_req.error_handler.cb				= gatt_error_handler;read_req.error_handler.p_ctx         	= p_ble_lbs_c;read_req.params.gattc_read.handle		= p_ble_lbs_c->peer_lbs_db.led_handle;read_req.params.gattc_read.offset		= 0;return nrf_ble_gq_item_add(p_ble_lbs_c->p_gatt_queue, &read_req, p_ble_lbs_c->conn_handle);
}

4运行效果

按照control_device枚举的顺序,conn_record结构体数组成员顺序对应从机1(bedroom_device)和从机2(drawingroom_device)。从机1先连接,从机2后连接:
在这里插入图片描述
在这里插入图片描述

从机2先连接,从机1后连接:
在这里插入图片描述
在这里插入图片描述

仅连接从机1,正常读写从机1LED状态,读写从机2LED状态提示错误:
在这里插入图片描述
在这里插入图片描述


http://www.ppmy.cn/news/9496.html

相关文章

12个爆款 Java 开源项目

1JavaGuidehttps://github.com/Snailclimb/JavaGuide Star 10503【Java学习面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。2symphonyhttps://github.com/b3log/symphony Star 6664一款用 Java 实现的现代化社区&#xff08;论坛/BBS/社交网络/博客&#xff09;平台…

多线程之线程安全问题

1.线程安全示例 class Count{int a 0;public void add(){a;} } public class ThreadDemo8 {public static void main(String[] args) {Count count new Count();Thread t1 new Thread(()->{for (int i 0; i < 5_0000; i) {count.add();}});Thread t2 new Thread(()…

深度学习各子领域略览及术语列表

诸神缄默不语-个人CSDN博文目录 最近更新时间&#xff1a;2023.1.5 最早更新时间&#xff1a;2023.1.5 有监督supervised / 无监督unsupervised分类 多分类multi-class多标签multi-label极限多标签文本分类XMTC&#xff08;NLP课题入门 | 极限多标签文本分类 NLP课题入门 | 极…

【华为OD机试真题2023 JAVA】查找充电设备组合

华为OD机试真题,2023年度机试题库全覆盖,刷题指南点这里 查找充电设备组合 时间限制:5s 空间限制:256MB 限定语言:不限 题目描述: 某个充电站,可提供n个充电设备,每个充电设备均有对应的输出功率。任意个充电设备组合的输出功率总和,均构成功率集合P的1个元素。功率集…

【三】3D匹配Matching之曲面匹配Surface—Based——create_surface_model()算子

&#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; Halcon算子太多&#xff0c;学习查找都没有系统的学习查找路径&#xff0c;本专栏主要分享Halcon各类算子含义及用法&#xff0c;有…

uniapp 填坑之旅---udb微信小程序端显示异常

功能描述&#xff1a;A页面展示列表a&#xff0c;点击a&#xff0c;进入B页面&#xff0c;展示a对象关联的子对象b。在B页面中&#xff0c;通过unicloud-db组件manual模式加载&#xff0c;具体代码按照官网示例来写。问题描述&#xff1a;代码实现后&#xff0c;一直在H5调试&a…

Step10.选择静态库或共享库

Step10.选择静态库或共享库 在本节中&#xff0c;我们将展示如何使用BUILD_SHARED_LIBS变量来控制add_library()的默认行为&#xff0c;并允许控制如何构建没有显式类型&#xff08;STATIC、SHARED、MODULE或OBJECT&#xff09;的库。 为了实现这一点&#xff0c;我们需要将B…

【C语言】volatile 关键字

目录一、前言二、C语言中变量的访问1. 读变量2. 写变量三、代码优化1. 硬件层面&#xff1a;2. 软件层面&#xff1a;四、volatile的定义五、volatile的应用场合1. 中断2. 多线程3. 硬件寄存器一、前言 volatile 是 C语言 中规定的一个关键字&#xff0c;C语言课程中很少会提及…