安信可 PB-03 模组基于奉加微 PHY-6252 SoC 平台,支持 BLE、BLE Mesh,基于 Cortex-M0 内核,外挂 512 KB Flash。本篇文章介绍如何使用安信可 PB-03M-Kit 开发板搭配 SHT30 温湿度传感器实现一个带温湿度测量功能的 BLE Mesh 彩灯。
安装好上述三个软件,SDK 解压到任意不带中文路径的文件夹,用 Keil 打开release_bbb_sdk-PHY62XX_SDK_3.1.1\example\ble_mesh\mesh_light\mesh_light.uvprojx
先编译一遍,验证环境是否搭建成功,随后使用 Visual Studio Code 打开release_bbb_sdk-PHY62XX_SDK_3.1.1
文件夹,安装 C/C++ 相关扩展以启用自动补全功能,至此环境搭建完毕。
官方的mesh_light
项目其实已经实现了彩灯控制功能,因此我们只需要在mesh_light
的基础上增加温湿度上报功能就可以了。不过 PB-03M Kit 的引脚与官方开发板有所不同,需要对原来的代码进行一些修改才能点亮彩灯。
定位到example\ble_mesh\mesh_gateway\source\bleMesh\bleMesh.c
,修改GPIO_RED
、GPIO_GREEN
和GPIO_BLUE
为 PB-03M Kit 对应的 RGB 彩灯引脚号(见 PB-03M-Kit 开发板规格书):
#define GPIO_RED P7
#define GPIO_GREEN P11
#define GPIO_BLUE P18
定位到components\driver\led_light\led_light.c
,根据实际电路中彩灯是高电平点亮还是低电平点亮修改 PWM 信号的极性。
void light_pwm_init(void)
{
hal_pwm_module_init();
for(int i = 0; i < sizeof(pwm_ch)/sizeof(pwm_ch[0]); i++)
{
pwm_ch[i].pwmN = (PWMN_e)(PWM_CH0 + (PWMN_e)i);
pwm_ch[i].pwmPin = GPIO_DUMMY;
pwm_ch[i].pwmDiv = PWM_CLK_NO_DIV;
pwm_ch[i].pwmMode = PWM_CNT_UP;
pwm_ch[i].pwmPolarity = PWM_POLARITY_FALLING; // 此处修改为 PWM_POLARITY_RISING
pwm_ch[i].cmpVal = 0;
pwm_ch[i].cntTopVal = LIGHT_TOP_VALUE;
}
pwm_ch[0].pwmPin = *(led_pin_ptr + 0);
pwm_ch[1].pwmPin = *(led_pin_ptr + 1);
pwm_ch[2].pwmPin = *(led_pin_ptr + 2);
}
SHT30 传感器通过 I²C 总线与模组进行通信,整体读取流程如下:
我们每隔 500ms 通过 I²C 读取一次传感器数据,如果我们读取成功,则从传感器的原始数据计算真实的温湿度;如果读取失败,说明传感器可能死机,我们就要通过 GPIO 操作传感器复位脚模拟复位动作来复位传感器,等待 3s 后再次尝试读取传感器。
因为该流程涉及到状态的转换以及使用了定时器,我们使用一个 OSAL 任务来承载读取传感器的工作,以下是任务的主要代码:
uint16 sht30_ProcessEvent(uint8 task_id, uint16 events)
{
if (events & SHT30_RST_PULL_DOWN_EVT)
{
// 按下重置按键
hal_gpio_fast_write(P20, 0);
// 300ms 后释放重置按键
osal_start_timerEx(sht30_TaskID, SHT30_RST_PULL_UP_EVT, 300);
}
if (events & SHT30_RST_PULL_UP_EVT)
{
// 释放重置按键
hal_gpio_fast_write(P20, 1);
// 300ms 后发送读取指令
osal_start_timerEx(sht30_TaskID, SHT30_SEND_READ_EVT, 300);
}
if (events & SHT30_SEND_READ_EVT)
{
// 初始化 I²C
hal_i2c_pin_init(I2C_0, P24, P23);
pi2cdev = hal_i2c_init(I2C_0, I2C_CLOCK_100K);
// 发送读取指令
write_command(SHT31_MEAS_HIGHREP);
// 3s 后读取数据
osal_start_timerEx(sht30_TaskID, SHT30_DATA_RECEIVE_EVT, 3000);
}
if (events & SHT30_DATA_RECEIVE_EVT)
{
uint8 buffer[6];
osal_memset(buffer, 0, sizeof buffer);
// 尝试从 I²C 总线读取数据
int ret = hal_i2c_read(pi2cdev, SHT31_DEFAULT_ADDR, 0, buffer, sizeof buffer);
if (ret == PPlus_ERR_TIMEOUT)
{
// 数据读取超时,开始发送重置指令
LOG("i2c read timeout, reset sensor\r\n");
osal_set_event(sht30_TaskID, SHT30_RST_PULL_DOWN_EVT);
}
else
{
// 将读取到的数据打印出来
LOG("received data:");
LOG_DUMP_BYTE(buffer, sizeof buffer);
/// 处理数据,获得真正的温湿度并上报的代码省略
// 500ms 后再次发送读取指令,进入下一轮循环
osal_start_timerEx(sht30_TaskID, SHT30_SEND_READ_EVT, 500);
}
}
// 丢弃未处理的事件
return 0;
}
void sht30_Init(uint8 task_id)
{
sht30_TaskID = task_id;
// 初始化传感器重置引脚
hal_gpio_pull_set(P20, GPIO_PULL_DOWN);
hal_gpio_write(P20, 1);
// 发送读取命令,开始进入工作循环
osal_set_event(sht30_TaskID, SHT30_SEND_READ_EVT);
}
定位到example\ble_mesh\mesh_light\source\bleMesh\appl_sample_example_phylight.c
中的UI_set_brr_scan_rsp_data()
函数,修改广播的设备名称:
API_RESULT UI_set_brr_scan_rsp_data (void)
{
/**
Currently setting MT-MESH-SAMPLE-8 as Complete Device Name!
This can be updated to each individual devices as per requirement.
*/
UCHAR UI_brr_scanrsp_data[] =
{
0x0D, 0x09, 'P', 'H', 'Y', '-', 'M', 'S', 'H', 'L', 'I', 'G', 'H', 'T'
};
CONSOLE_OUT("\n Setting PHY MSH LIGHT as Complete Device Name!\n");
/* Set the Scan Response Data at the Bearer Layer */
blebrr_set_adv_scanrsp_data_pl
(
UI_brr_scanrsp_data,
sizeof(UI_brr_scanrsp_data)
);
return API_SUCCESS;
}
此处的UI_br_scanrsp_data[]
中的第二个字节0x09
代表设备名称长度(不包含\0
),修改设备名称时不要忘记更改这个地方。
定位到example\ble_mesh\mesh_light\source\bleMesh\appl_sample_example_phylight.c
,修改UI_lprov_device.uuid
即可修改广播的 UUID。
/** Unprovisioned device identifier */
//DECL_STATIC PROV_DEVICE_S UI_lprov_device =
PROV_DEVICE_S UI_lprov_device =
{
/** UUID */
{0x88, 0x88, 0x62, 0x12, 0x00, 0x01, 0x00, 0x01, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00},
//0x05,0x04
/** OOB Flag */
0x00,
/**
Encoded URI Information
For example, to give a web address, "https://www.abc.com"
the URI encoded data would be -
0x17 0x2F 0x2F 0x77 0x77 0x77 0x2E 0x61 0x62 0x63 0x2E 0x63 0x6F 0x6D
where 0x17 is the URI encoding for https:
*/
NULL
};
printf()
函数SDK 中的myprintf.h
头文件提供了一个精简版的printf()
函数,这个函数相比于 Keil 提供的完整版的printf()
函数少了浮点数打印功能,但是最后生成的代码体积相应也更小。如果项目业务部分代码量过大,使用完整版printf()
函数可能会因为目标文件体积过大而链接失败。在不需要打印浮点数的项目中,我们应尽量使用精简版printf()
函数缩小生成固件的体积。
SDK 项目的 Keil 编译选项中提供了DEBUG_INFO
宏来定义日志输出级别。在log.h
中可以看见,将DEBUG_INFO
宏设置为0
可以关掉日志输出,设置为3
可以打开全部的日志输出。
#if(DEBUG_INFO == 1)
#define AT_LOG(...)
#define LOG_DEBUG(...)
#define LOG(...) dbg_printf(__VA_ARGS__)
#define LOG_INIT() dbg_printf_init()
#define LOG_DUMP_BYTE(a,b) my_dump_byte(a,b)
#elif(DEBUG_INFO == 2)
#define AT_LOG(...) dbg_printf(__VA_ARGS__)
#define LOG_DEBUG(...) dbg_printf(__VA_ARGS__)
#define LOG(...)
#define LOG_INIT() dbg_printf_init()
#define LOG_DUMP_BYTE(a,b) my_dump_byte(a,b)
#elif(DEBUG_INFO == 3)
#define LOG(...) dbg_printf(__VA_ARGS__)
#define AT_LOG(...) dbg_printf(__VA_ARGS__)
#define LOG_DEBUG(...) dbg_printf(__VA_ARGS__)
#define LOG_INIT() dbg_printf_init()
#define LOG_DUMP_BYTE(a,b) my_dump_byte(a,b)
#else
#define AT_LOG(...)
#define LOG_DEBUG(...)
#define LOG(...)
#define LOG_INIT() //{clk_gate_enable(MOD_UART);clk_reset(MOD_UART);clk_gate_disable(MOD_UART);}
#define LOG_DUMP_BYTE(a,b)
#endif
SDK 的项目中自带一个简单的命令行演示程序,用来做显示 Mesh 配网信息和重置模组等操作。这个命令行占用的代码体积较大,业务逻辑过多的情况下很容易超体积,我们可以删除掉引入的命令行组件来减小代码体积。
我们使用 nRF Mesh 这款 App 测试 Mesh 群控功能。打开 App ,添加几个设备进行测试。然后创建群组并选择设备加入,测试开关灯功能是否正常。
https://github.com/zxf1023818103/release_bbb_sdk-PHY62XX_SDK_3.1.1.git