MM32W

在前面一个章节中我们详细介绍了基于MM32W系列芯片的串口透传应用,该应用可以实现丰富的数据透传方案,在本章节我们将再给大家介绍一个简易而扩展性较强的应用方案——基于MM32W系列开发的数据采集仪器。


图1 方案应用图

在工业控制现场,常常需要实时采集大量的现场数据,如电压、电流、温度、湿度、气压等,往往这些小信号都是需要输入到专业的数据采集模块中进行处理,后者又将采集的数据传输到主机进行处理,由主机根据处理的结果,将控制信号传输给现场执行模块进行各种操作。目前数据的传输基本是基于有线的网络,如RS485,CAN等。这些有线的网络一般具有成本比较高、维护不方便等缺点。而无线传输相对具有一定的优势,如成本低、可靠性高、维护方便等。本文将介绍一个基于MM32W系列MCU的简易蓝牙数据采集系统实现过程。

硬件资源如下:

本方案基于MM32 BLE_Test Board进行测试验证,为了简易模拟实现传感器信号的采集与处理过程,在硬件原理上,本方案直接使用DEMO板上自带的ADC电位器RV1来调节不同的输入信号情况,该信号连接到引脚PB0上,该引脚可复用为AD采样通道CH8;使用PB1连接到绿色LED指示灯,可作为蓝牙连接状态的指示,且低功耗唤醒引脚选择PA0配置为下拉输入;蓝牙相关的功能引脚与前面介绍的方案一致,此处不做过多展开。


图2 硬件原理图

软件资源如下:

结合上述使用到的硬件资源,下面我们着重介绍软件实现流程以及相关配置代码,主要涉及ADC模拟通道以及相应引脚的配置,加上ADC采样的配置及使用。

以下为主函数初始化配置及相关全局变量定义内容,主要将所有的外设资源、蓝牙广播报文以及蓝牙协议栈初始化,并且以阻塞的的方式运行蓝牙,代码如下:

const unsigned char AdvDat_HRS[]=

{//定义广播报文

0x02,0x01,0x06,

0x03,0x19,0x41,0x03,

0x07,0x03,0x0D,0x18,0x0A,0x18,0x0F,0x18

};

 

int main(void)

{

BSP_Init();//初始化 SPI IO 以及ADC

radio_initBle(TXPWR_0DBM, &ble_mac_addr);//初始化蓝牙芯片及蓝牙协议栈,并且定义发射功率 

ble_set_adv_data((unsigned char *)AdvDat_HRS, sizeof(AdvDat_HRS));//设置BLE广播数据

SysTick_Count = 0;

while(SysTick_Count <= 1500){};//在初始化蓝牙协议栈后等待至少5ms才能正式运行蓝牙

Write_Iwdg_ON(IWDG_Prescaler_32, 0x4E2); //设置IWDG看门狗防止程序跑飞,1s不喂狗系统重启

ble_run(160*2); //采用堵塞方式运行蓝牙协议,广播间隔时间为 200ms

}

下面再介绍一下ADC操作相关的几个函数:

//在需要往APP上报实时ADC转换数值时调用下面获取ADC通道平均采样值的函数

/********************************************************************************************************

**函数信息 :Get_Adc_Average(uint8_t ADC_Channel_x,uint8_t times)

**功能描述 :获取几次ADC1采样值的平均值

**输入参数 :ADC_Channel_x , x为0~11

**输出参数 :puiADData为ADC读到的值

********************************************************************************************************/

u16 Get_Adc_Average(uint8_t ADC_Channel_x,uint8_t times)

{

u32 temp_val=0;

u8 t;

u8 delay;

   

for(t=0;t

/***************************************************************************************************

**函数信息 :ADC1_SingleChannel_Get()

**功能描述 :获取ADC1转换数据

**输入参数 :ADC_Channel_x , x为0~11

*puiADData ,ADC1实际转换数据

**输出参数 :ucStatus ,0 表示数据获取失败,1 表示成功

***************************************************************************************************/

u16 ADC1_SingleChannel_Get(uint8_t ADC_Channel_x)

{                 

u16 puiADData;

    

ADC_SoftwareStartConvCmd(ADC1, ENABLE); // ADCR寄存器的ADST位使能,软件启动转换

while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==0); //等待ADC转换完成标志位置位

ADC_ClearFlag(ADC1, ADC_FLAG_EOC);//清除ADC转换完成标志位置位

puiADData=ADC1->ADDATA&0xfff;//读取ADC转换值

return puiADData;

}

/********************************************************************************************************

**函数信息 :void ADC1_SingleChannel(uint8_t ADC_Channel_x)

**功能描述 :配置ADC1单次转换模式

**输入参数 :ADC_Channel_x , x为0~11

**输出参数 :无

********************************************************************************************************/

void ADC1_SingleChannel(uint8_t ADC_Channel_x)

{

ADC_InitTypeDef  ADC_InitStructure;   

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

   

ADC_InitStructure.ADC_PRESCARE = ADC_PCLK2_PRESCARE_16;//初始化ADC分频

ADC_InitStructure.ADC_Mode = ADC_Mode_Single;//初始化ADC工作模式

ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//禁止连续转换

ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //初始化ADC转换对齐方式

ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//初始化ADC采样精度

ADC_Init(ADC1, &ADC_InitStructure);//初始化ADC  

ADC_RegularChannelConfig(ADC1, DISABLE_ALL_CHANNEL , 0, 0); //屏蔽所有ADC通道

ADC_RegularChannelConfig(ADC1, ADC_Channel_x, 0, ADC_SampleTime_13_5Cycles); //使能选中通道

if(ADC_Channel_x == ADC_Channel_11)

{

ADC_VrefintCmd(ENABLE);//如果为通道11,则使能内部参考电压

}

ADC_Cmd(ADC1, ENABLE); //使能ADC1

}

我们在gatt_user_send_notify_data_callback函数中给手机发送数据,该函数属于回调函数,协议栈会在系统允许的时候(异步)回调本函数,该函数可用于蓝牙模块端主动发送数据之用,函数内部不得增加阻塞代码。在本应用中我们在此函数中实现将ADC采集并且转换后的数据传输给手机APP。详细实现代码如下:

//蓝牙连接成功后协议在空闲的时候会调用本回调函数

void gatt_user_send_notify_data_callback(void){ 

static unsigned char HRMData[3]={0x00,0x00,0x01};//定义装载ADC值的存储区

static unsigned char SimBatt=100;//定义装载ADC 百分数值的存储区

static u8 Cont=0;//回调次数计数器

u16 Val=0;

 

Cont++; //每进一次该函数回调次数计数器+1

if (Cont >= 20)

{//每进入该回调函数20次才发送一次ADC数据

Cont = 0;        

Val = Get_Adc_Average(ADC_Channel_3,5); //获取5次ADC转换的平均值

Val = Val>>3; // 0~511 for HRM data

cur_notifyhandle = 0x12;//ADC数据回复句柄值

if (Val<0x100)

{

HRMData[0] = 0; //1Byte

HRMData[1] = Val;

sconn_notifydata(HRMData,2);//换算处理好ADC数据后通过蓝牙发出

}

else

{

HRMData[0] = 1; //2Byte

HRMData[1] = Val;

HRMData[2] = Val>>8;

sconn_notifydata(HRMData,3);//换算处理好ADC数据后通过蓝牙发出

}   

}

else if (10 == Cont) {//每进入该回调函数10次才发送一次ADC数据

Val = Get_Adc_Average(ADC_Channel_3,5); //获取5次ADC转换的平均值

Val = Val>>3;

SimBatt = (Val*100)>>9; //0~100

cur_notifyhandle = 0x18;//ADC百分数形式数据回复句柄值

sconn_notifydata(&SimBatt,1);/换算处理好ADC百分数后通过蓝牙发出

}

}

除了上述关键的蓝牙数据发送函数外,下面再简单介绍一些与蓝牙相关的特征值定义:

在const BLE_CHAR AttCharList[] 中定义了本案中的两个特征值:

{TYPE_CHAR,0x11,ATT_CHAR_PROP_NTF, 0x12,0, 0x37,0x2A, UUID16_FORMAT},//ADC数值

{TYPE_CHAR,0x17,ATT_CHAR_PROP_RD|ATT_CHAR_PROP_NTF, 0x18,0,0x19,0x2A,UUID16_FORMAT},//百分数形式

在void att_server_rdByGrType( u8 pdu_type, u8 attOpcode, u16 st_hd, u16 end_hd, u16 att_type )中实现了自定义特征值服务声明。

手机操作流程如下:

打开手机蓝牙并打开 App,选择HRM进入,点击Connect按钮开始搜索温湿度蓝牙设备。

选择对应名称(MM32W0_ADC)的蓝牙设备并进行配对,等待连接成功。连接成功后会有相应提示,按钮Connect名字会变成Disconnect。

连接成功后,在App界面上电池图标会显示从DEMO板上的电位器分压得来的ADC数据信息(经过关系换算或以百分比形式,非实际转换值)。


图3 手机APP图

来源: 灵动MM32MCU

围观 65

在前面一个章节中我们详细介绍到了基于MM32W系列芯片开发的使用场景非常丰富的简易蓝牙智能锁方案,在本章节我们将继续给大家介绍一个蓝牙案例——基于MM32W系列开发的数据透传应用。


图1 透传应用连接传输图

说到低功耗蓝牙模块的工作方式,少不了要介绍低功耗蓝牙模块中最简单、最常见的通讯方式——数据透传。透传也叫串口透传,就是透明传输的意思,透传是一种工作方式,不是一种功能,一般出现在串口模块中。灵动MM32W系列MCU为了让用户更好的开发蓝牙无线传输产品,而不需要关心内部蓝牙协议栈如何实现,可以很快地实现串口模块的功能。串口模块具体的工作机制就是不对MCU要传输的数据做任何处理,也不需要自己增加什么协议,用户可以控制数据包的大小与APP之间进行双向传输。

硬件资源如下:

本方案基于MM32 BLE_Test Board进行测试验证,配套使用一条Micro usb数据线作为与上位机串口助手通讯方式的载体。在硬件原理上,本方案Demo板载了USB转串口芯片,通过跳线帽连接到MCU的UART2功能引脚,USB转串口芯片的RXD连接到了TX2(PA2)上,TXD连接到了RX2(PA3)上,且在通讯引脚上都分别并联了不同颜色的LED作为数据收发情况的指示;使用PB1连接到绿色LED指示灯,可作为蓝牙连接状态的指示;低功耗唤醒引脚选择PA0配置为上拉输入;蓝牙相关的功能引脚与前面介绍的方案一致,此处不做过多展开。以下为USB-TTL串口转换原理图:


图2 原理图

软件资源如下:

结合上述使用到的硬件资源,下面我们着重介绍软件实现流程以及相关配置代码。由于本应用方案BLE芯片与APP与之间为数据透传,为了避免其它数据干扰把应用层的广播报文取消,且不需要设置配对模式;由于本方案使用的是MCU的UART2,在使用printf调试打印功能时需将fputc和fgetc两个库函数内部使用的串口切换为UART2,且使用的是查询方式打印数据,与本案中透传发送至APP的方式不同(中断方式);另外再对连接状态指示灯的引脚进行配置,从而更为直观地对应用效果进行观测和验证,且使能看门狗复位功能来保证蓝牙服务的稳定性能;由于方案应用对低功耗的需要,本方案低功耗采用SLEEP和STOP双模式切换,根据不同的超时时间进入不同的低功耗模式。

以下为主函数初始化配置内容,主要将所有的外设资源和蓝牙协议栈初始化,并且以中断服务程序的方式运行蓝牙,主函数的循环中主要实现的功能为判断是否进入低功耗模式、根据串口接收和发送数据的超时时间进入何种低功耗模式,代码如下:

下面着重介绍一下串口2操作相关的几个函数:

/***串口2发送数据接口,当MCU接收到蓝牙透传数据后程序会调用该接口函数,该函数主要实现把要发送的数据装载到TX BUFF中,等待串口中断服务程序处理发送***/

void moduleOutData(u8*data, u8 len) 

{

unsigned char i;

if ((txLen+len)

/***当收到来自串口助手的数据时,中断服务程序处理接收BUFF装载数据,在蓝牙协议的周期性回调函数中调用该函数来把串口助手的数据发送至APP***/

void CheckComPortInData(void) //at cmd NOT supported

{

u16 send;

if(comrxbuf_wr_pos != comrxbuf_rd_pos)//接收BUFF不为空{

if(!GetConnectedStatus())//蓝牙未连接{

comrxbuf_rd_pos = comrxbuf_wr_pos; //清空接收BUFF

}

else //蓝牙正常连接{

if(comrxbuf_wr_pos > comrxbuf_rd_pos)//当前串口接收到的数据长度大于接收BUFF索引号{

//调用sconn_notifydata函数主动通过蓝牙发送数据

send = sconn_notifydata(rxBuf+comrxbuf_rd_pos,comrxbuf_wr_pos - comrxbuf_rd_pos);

comrxbuf_rd_pos += send;

}

else {//调用sconn_notifydata函数主动通过蓝牙发送数据

send = sconn_notifydata(rxBuf+comrxbuf_rd_pos,MAX_SIZE - comrxbuf_rd_pos);                         comrxbuf_rd_pos += send;

comrxbuf_rd_pos %= MAX_SIZE;//定义MAX_SIZE为接收BUFF最大长度,这里为200

}

}

}

}

在UsrProcCallback()处理函数中做了喂狗操作,且处理串口2透传数据的接收和发送操作,蓝牙协议会周期性回调本函数,无论是在广播状态还是连接状态;在串口2中断服务程序中主要实现了发送BUFF和接收BUFF的数据装载以及触发发送功能。以下为代码实现内容:


//蓝牙连接成功后协议在空闲的时候会调用本回调函数
 void gatt_user_send_notify_data_callback(void)
{
static u8 LockFlagBak = 1;
if (LockFlagBak != LockFlag) {
LockFlagBak = LockFlag;//LockFlag标志位会在MOTOR_Proc()中清零和置位      
sconn_notifydata(&LockFlagBak,1);
}
}

下面着重介绍一下蓝牙服务操作相关的几个函数和特征值定义:

//APP获取蓝牙设备名称信息
u8* getDeviceInfoData(u8* len) ;
//APP设置并更新蓝牙设备名称信息
void updateDeviceInfoData(u8* name, u8 len) ;
//APP获取蓝牙设备固件版本号
u8* getsoftwareversion(void) ;
//判断蓝牙连接状态,并且通过状态指示灯显示
void ConnectStausUpdate(unsigned char IsConnectedFlag) ;
//APP查询蓝牙及MCU串口波特率信息回复函数
void server_rd_rsp(u8 attOpcode, u16 attHandle, u8 pdu_type) ;
//APP发送数据到MCU及设置串口波特率响应函数,在此实现上位机到下位机数据的透传
void ser_write_rsp() ;

除了上述关键的蓝牙服务函数外,下面再简单介绍一些与蓝牙相关的特征值定义:

在const BLE_CHAR AttCharList[] 中定义了本案中的四个特征值:
{TYPE_CHAR,0x0011, {ATT_CHAR_PROP_NTF, 0x12,0, 0,0}, 1/*uuid128-idx1*/ },// RxNotify
{TYPE_CHAR,0x0014, {ATT_CHAR_PROP_W|ATT_CHAR_PROP_W_NORSP, 0x15,0, 0,0}, 2/*uuid128-idx2*/ },// Tx
{TYPE_CHAR,0x0017, {ATT_CHAR_PROP_W|ATT_CHAR_PROP_RD, 0x18,0, 0,0}, 3/*uuid128-idx3*/ },// BaudRate 设置
{TYPE_INFO,0x0019, {ATT_CHAR_PROP_RD}}// BaudRate 回复
在void att_server_rdByGrType( u8 pdu_type, u8 attOpcode, u16 st_hd, u16 end_hd, u16 att_type )中实现自定义特征值服务声明;

手机操作流程如下:

手机App使用nRF UART v2.0 App(安卓版本的App安装包nRFUART_Googlev2.apk,也可以从360手机助手查找和下载);

打开nRF UART v2.0 App,点击Connect开始搜索ble设备,选择对应名称(MM32_UART2)的蓝牙设备并进行配对,等待连接成功。连接成功后会有相应提示,按钮Connect名字会变成Disconnect;

连接成功后,在App内可以输入数据,然后点Send,手机发送的数据会通过蓝牙透传至测试demo板接收,并通过UART2发送到PC端的串口助手上显示:为了演示方便,可以短接串口2的TX2和RX2引脚,这样demo板接收到手机App发送的数据后会把同样的数据内容发给手机App上显示出来。



图3 手机APP图和PC端串口助手图

来源:灵动MM32MCU

围观 110

在前面一个章节中我们详细介绍到了基于MM32W系列芯片开发的温湿度监测仪方案,在本章节我们将继续给大家介绍一个使用场景非常丰富的蓝牙应用方案——基于MM32W系列开发的蓝牙智能锁方案。


图1方案场景

智能锁方案中使用到MM32W系列低功耗MCU,配合手机通过APP读取智能锁蓝牙信息,尝试配对,配对成功即可正常通讯,手机APP通过蓝牙把指令发送给智能锁进行解锁,整个流程简单可靠,该方案将彻底告别传统钥匙开锁。蓝牙智能锁安全便捷,符合用户的需求,可应用的日常场景广泛,例如:门锁(家门、仓库门、货车门等);车锁(电动车锁、自行车锁、摩托车锁等);私人箱柜锁(抽屉锁、更衣柜、旅行箱、工具箱等)。

硬件资源如下:

本方案基于MM32 BLE_Test Board进行测试验证,搭配上一颗微型的双向直流马达作为开锁方式的载体,这里只用马达驱动开锁,关锁默认为手动操作,用户可以通过手机APP来操作开锁。在硬件原理上,本方案使用到的DC马达是通过一颗专用的IC芯片来驱动,其控制引脚连接到MCU的PD2和PD3,通过控制两个IO的电平可以对马达进行不同运动状态的操作;使用PB1连接到绿色LED指示灯,可作为蓝牙连接状态的指示;模拟锁体状态功能引脚PA1配置为上拉输入,低功耗唤醒引脚选择PA0配置为上拉输入;蓝牙相关的功能引脚与前面介绍的方案一致,此处不做过多展开。以下为DC马达驱动原理图:


图2 方案应用图

软件资源如下:

结合上述使用到的硬件资源,下面我们着重介绍软件实现流程以及相关配置代码。由于本应用方案BLE芯片与APP建立连接过程中需要应用到配对的功能,利用蓝牙库的接口函数可以实现,这里将配对秘钥设置为“123456”,在连接时候需要设置正确才能配对成功并且连接上;控制DC马达的功能引脚全都配成推挽输出模式,直接连到驱动IC上去,通过改变高低电平组合来控制电机的正反转和启停;另外再对测试和模拟功能的引脚进行配置,从而更为直观地对应用效果的观测和验证,且使能看门狗复位功能来保证蓝牙服务的稳定性能;由于方案应用电池供电对低功耗的需要,本方案低功耗采用STOP模式。

以下为主函数初始化配置内容,主要将所有的外设资源和蓝牙协议栈初始化,并且以中断服务程序的方式运行蓝牙,主函数的循环中主要实现的功能为判断是否进入低功耗模式,并且唤醒后根据接收到的蓝牙数据对DC马达进行不同的操作,代码如下:

下面简单介绍一下蓝牙低功耗和DC马达操作相关的几个函数:

//初始化DC马达控制引脚PD2、PD3,并且全部置为低电平

void MOTOR_Init(void);//停止DC马达的转动
static void MOTOR_Stop(void);//根据不同方向参数去控制DC马达运转
static void MOTOR_Run(unsigned char direction);

在MOTOR_Proc()处理函数中针对蓝牙接收到的密码数据进行解析,如果与默认设置的4位0-9数字密码完全匹配上则操作解锁,将DC马达按照设定方向控制运转,为了简易验证,模拟一个信号表示解锁成功,这里是通过采集指定IO引脚PA1的输入来实现,采集到输入为高电平那么停止DC马达转动,且清零锁定标志位。以下为代码实现内容:

我们在gatt_user_send_notify_data_callback函数中给手机发送数据,该函数属于回调函数,协议栈会在系统允许的时候(异步)回调本函数,该函数被用于蓝牙模块端主动发送是否解锁状态信息给手机APP,函数内部不得增加阻塞代码。详细实现代码如下:

//蓝牙连接成功后协议在空闲的时候会调用本回调函数

 void gatt_user_send_notify_data_callback(void)

{

static u8 LockFlagBak = 1;

if (LockFlagBak != LockFlag)

{

LockFlagBak = LockFlag;//LockFlag标志位会在MOTOR_Proc()中清零和置位      

sconn_notifydata(&LockFlagBak,1);

}

}

蓝牙协议会周期性回调本UsrProcCallback()函数,无论是在广播状态还是连接状态,在该函数中可以做运行和低功耗模式切换的超时计数,给IrqMcuGotoSleepAndWakeup()来处理是否进入低功耗模式,并且针对获取到的是否连接信息用LED指示灯做不同的状态显示。详细实现代码如下:

//蓝牙协议会周期性回调本函数

void UsrProcCallback(void) 

{

static unsigned char led_flash = 0;    

IWDG_ReloadCounter();

StandbyTimeout ++; 

if(gConnectedFlag){  //连接成功

StandbyTimeout = 0;

LED_ONOFF(1);

}else{

led_flash ++;

LED_ONOFF(!(led_flash%10)); //蓝牙未连接,指示灯快闪

}

}

除了上述关键的蓝牙数据发送函数外,下面再简单介绍一些与蓝牙相关的特征值定义,在const BLE_CHAR AttCharList[] 中定义了本案中的两个特征值:

{TYPE_CHAR,0x0011,{ATT_CHAR_PROP_W_NORSP,0x12,0,0xf1,0xff}, UUID16_FORMAT},//解锁命令

{TYPE_CHAR,0x0013,{ATT_CHAR_PROP_RD|ATT_CHAR_PROP_NTF,0x14,0,0xf2,0xff}, UUID16_FORMAT},//锁体状态

在void att_server_rdByGrType( u8 pdu_type, u8 attOpcode, u16 st_hd, u16 end_hd, u16 att_type )中实现了自定义特征值服务声明;

在void ser_write_rsp()中实现对手机APP发送的蓝牙数据接收和存储:

if (StartEncryption)

{

    if (valueLen_w < 9)//对蓝牙数据进行解析和存储

    {

     Password_wr[0] = valueLen_w;

     memcpy(&Password_wr[1], attValue, valueLen_w);

     }

     }else{ //无效数据,不保存

     Password_wr[0] = 0;

}

在void server_rd_rsp(u8 attOpcode, u16 attHandle, u8 pdu_type)中实现把锁体状态回复给手机APP:

att_server_rd( pdu_type, attOpcode, attHandle, &LockFlag, 1); //将LockFlag 标志位返回给手机APP

手机操作流程如下:

使用手机原生蓝牙界面查找设备,找到MM32_Lock后点击进行配对。默认配对密码为123456,配对成功后MM32_LOCK设备自动保存到配对设备列表,以后不需要该步骤;

手机打开App,开始搜索BLE设备,选择对应名称(MM32_LOCK)的蓝牙设备并进行配对,等待连接成功。连接成功后会有相应提示,按钮Connect名字会变成Disconnect;

连接成功后,对UUID为fff1的特征值写0x31323334(模拟用户输入密码”1234”),测试板上连接的马达开始转动,执行开锁动作;

给PA1输入低电平模拟锁开启完成,UUID为fff2的特征值结果为0表示锁体开启,给PA1输入高电平模拟锁体锁上,UUID为fff2的特征值结果为1表示锁体锁上。这里只用马达驱动开锁,关锁默认为手动操作。


图3 手机APP图

来源: 灵动MM32MCU

围观 40

在前面两个章节中我们详细介绍过的基于MM32W系列芯片开发的智能灯控方案和蓝牙自拍杆方案,在本章节我们将继续给大家介绍一个使用场景较为丰富的蓝牙应用方案——基于MM32W系列开发的温湿度监测仪。


图1 方案应用图

温度、湿度与我们的生活息息相关,科研实验室、农业大棚、食品储存室、疫苗存储及配送、贮藏室等对环境的温度、湿度有着严格的控制标准,温度、湿度的异常变化都可能会给其造成严重的影响。传统的人工巡查和记录环境温湿度变化并非易事,随着时代的发展,可实现智能化监测环境温湿度的温湿度传感器出现了。如今,科研、农业、暖通、机房、航天航空、电力等工业部门都开始采用智能化的温湿度传感器监测环境的温湿度。利用温湿度传感器对环境的温湿度进行实时监测,不仅能够及时发现环境温湿度的异常,进而做出应对措施,避免或减少损失,还能够减少员工工作量,降低人力成本。

硬件资源如下:

本方案基于MM32 BLE_Test Board进行测试验证,搭配上温湿度传感器DHT11作为采集环境中温湿度数据,再加上一款小型的OLED屏幕作为本地式数据输出显示窗口,另外可以通过手机APP获取温湿度变化情况。在硬件原理上,本方案的DHT11模块的单线数据传输引脚连接到MCU的PA7,为了解析模块的数据时序,该引脚复用为TIM3_CH2输入捕获功能;使用硬件IIC接口连接到OLED屏上去,引脚为PB6(SCL)、PB7(SDA),可以将温湿度数据显示在OLED;蓝牙相关的功能引脚与前面介绍的方案一致,此处不做过多展开。

软件资源如下:

结合上述使用到的硬件资源,下面我们着重介绍软件实现流程以及相关配置代码。使用MCU的引脚复用为TIM3_CH2输入捕获功能DHT11模块,在开启捕获时将PA7配置为浮空输入模式,复用功能选择AF1配置为TIM3的CH2输入捕获通道,并且将TIM3开启;在停止捕获时将PA7配置为推挽输出模式,并且将TIM3关闭。由于DHT11模块限制,温湿度采样周期间隔必须大于1S,本方案采样和显示周期为2S。

在使用OLED屏幕需要用到硬件IIC外设接口,需要将对应的PB6 PB7配置为复用开漏输出模式,初始化时还需要根据不同的OLED模块在函数IIC_Init()中修改slave设备地址,使能IIC接口后即可以开始传输工作了。由于数据采集和定时显示需要,本方案的低功耗模式采用STOP模式。

以下为主函数初始化配置内容,主要将所有的外设资源和蓝牙协议栈初始化,并且以中断服务程序的方式运行蓝牙,代码如下:

主函数的循环中主要实现的功能为定时采集和显示当前环境的温湿度数据,而该数据也将在蓝牙服务中发送到APP端显示,代码如下:

下面简单介绍一下OLED操作相关的几个函数:

//初始化IIC

void IIC_Init(I2C_TypeDef* I2Cx);

//发送命令函数

static void Write_Command(unsigned char Command);

//发送数据显示在屏幕

static void Write_DataBuff(unsigned char *Data, unsigned char Len);

//OLED屏初始化

void OLED_Init(void);

//设置坐标

void OLED_SetPos(unsigned char x, unsigned char y);

//字符串显示

void OLED_DispStr(unsigned char x, unsigned char y, char *ch);

//显示logo

void OLED_DispLogo(void);

//清屏操作

void OLED_Clear(void);

 

下面简单介绍一下DHT11模块操作相关的几个函数:

// TIM3_CH2输入捕获初始化

void CaptureInit(void);   //用TIM3_CH2  PB5  AF3

//启动捕获

void CaptureStart(void);

//停止捕获

void CaptureStop(void);

在TIM3_IRQHandler()中针对捕获的数据进行解析和处理。

我们在gatt_user_send_notify_data_callback函数中给手机发送数据,该函数属于回调函数,协议栈会在系统允许的时候(异步)回调本函数,该函数可用于蓝牙模块端主动发送数据之用,函数内部不得增加阻塞代码。该应用中我们在此函数中实现将采集转换好的温湿度数据传输给手机APP。详细实现代码如下:

//蓝牙连接成功后协议在空闲的时候会调用本回调函数

void gatt_user_send_notify_data_callback(void){ 

static u8 notiCnt = 0;//回调次数计数器

u16 humiBat ,tempBpm = 0;

unsigned char DHTData[3]={0x00,0x00,0x01};

notiCnt++; //每进一次该函数回调次数计数器+1

if(CaptureDataMon(&humiBat, &tempBpm) == 0) return;//未成功采集到温湿度数据立即返回

tempBpm %= 512;//初步判断温度数据大小

humiBat /= 10;//初步判断湿度数据大小

if (notiCnt >= 20) {//每进入该回调函数20次才发送一次温度数据

notiCnt = 0;       

cur_notifyhandle = 0x12;//温度数据回复句柄值

if (tempBpm < 0x100){

DHTData[0] = 0; //1Byte

DHTData[1] = tempBpm;

sconn_notifydata(DHTData,2);//换算处理好温度数据后通过蓝牙发出

}

else {

DHTData[0] = 1; //2Byte

DHTData[1] = tempBpm;

DHTData[2] = tempBpm>>8;

sconn_notifydata(DHTData,3);//换算处理好温度数据后通过蓝牙发出

}

}

else if (10 == notiCnt) {//每进入该回调函数10次才发送一次湿度数据

SimBatt = humiBat; //0~100

cur_notifyhandle = 0x18;//湿度数据回复句柄值

sconn_notifydata(&SimBatt,1);//换算处理好湿度数据后通过蓝牙发出

}

}

除了上述关键的蓝牙数据发送函数外,下面再简单介绍一些与蓝牙相关的特征值定义:

手机操作流程如下:

打开手机蓝牙并打开App,选择HRM进入,点击Connect按钮开始搜索温湿度蓝牙设备。

选择对应名称(MM32W0_DHT)的蓝牙设备并进行配对,等待连接成功。连接成功后会有相应提示,按钮Connect名字会变成Disconnect。

连接成功后,在App界面上电池图标会显示从DHT11传感器获取的湿度信息(百分比)Finger和图表会显示从DHT11传感器获取的温度信息(原始数据,温度值x10)。


图2 手机APP图

来源: 灵动MM32MCU

围观 29

在前面一章中我们已经详细介绍了我们基于MM32W系列开发的智能灯控方案,在本章节我们将介绍一款大家生活中很常见的一个蓝牙产品的应用方案,基于MM32W系列开发的蓝牙自拍杆应用。

基于蓝牙技术的蓝牙自拍杆可以进行远距离拍照,不用担心因为线材等因素的约束,且本方案无需专门的手机APP,可直接使用系统内的蓝牙连接。该方案在开发、测试、使用操作阶段都比较简单,且操作迅速,能够适用大部分的场景、方便人们的生活。

硬件资源:

本方案基于MM32 BLE_Test Board进行测试验证,蓝牙自拍杆应用在硬件上只需要一个功能按键即可,蓝牙部分使用蓝牙控制的最小系统,我们将这个按键接到MCU的PA0引脚,既可以用做唤醒引脚使用,又可以用做自拍的功能引脚。


图1 测试开发板

软件资源:

将PA0配置为下拉输入模式,复用中断线到PA0并配置外部中断线中断,最后使能PWR时钟与WakeUp引脚。详细代码如下:

do {

GPIO_InitTypeDef GPIO_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);//使能GPIOA

GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0;  //PA.0

GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPD;//下拉输入

GPIO_Init(GPIOA, &GPIO_InitStructure);         //初始化IO

} while(0);

do {

EXTI_InitTypeDef EXTI_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能复用功能时钟

 

    //使用外部中断方式

SYSCFG_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);       //中断线0连接GPIOA.0

EXTI_InitStructure.EXTI_Line = EXTI_Line0;   //设置按键所有的外部线路

EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;          //设外外部中断模式:EXTI线路为中断请求

EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  //上升沿触发

EXTI_InitStructure.EXTI_LineCmd = ENABLE;

EXTI_Init(&EXTI_InitStructure);            // 初始化外部中断

} while(0);

do {

NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannel = EXTI0_1_IRQn; //使能按键所在的外部中断通道

NVIC_InitStructure.NVIC_IRQChannelPriority = 2; //从优先级2级

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道

NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

} while(0); 

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);        //使能PWR外设时钟

PWR_WakeUpPinCmd(ENABLE);  //使能唤醒管脚功能

2、我们在gatt_user_send_notify_data_callback函数中给手机发送数据,该函数属于回调函数,协议栈会在系统允许的时候(异步)回调本函数,该函数可用于蓝牙模块端主动发送数据之用,函数内部不得增加阻塞代码。在蓝牙自拍杆应用中我们在该函数中判断PA0的电平状态,若按键按下则给手机发送按键按下信息。详细实现代码如下:

void gatt_user_send_notify_data_callback(void)

{

if (GPIO_ReadInputData(GPIOA) & 0x01)//press

{

NotifyKey(0x28);

NotifyApplePhoto();

}

}

u8 NotifyApplePhoto(void)//apple photo hid photo capture, hard code

{

u8 Keyarray[5] = {2,0,8,0,0}; //VolUp,hard code

sconn_notifydata(Keyarray,5);

Keyarray[2] = 0;

sconn_notifydata(Keyarray,5);

return 1;

}

 

u8 NotifyKey(u8 KeyIdx)//hid standard keyboard key, hard code

{

u8 Keyarray[9] = {1,0,0,0,0,0,0,0,0};//0xa1

Keyarray[3] = KeyIdx;

sconn_notifydata(Keyarray,9);

Keyarray[3] = 0;

sconn_notifydata(Keyarray,9);

return 1;

}

我们直接使用手机自带的蓝牙功能进行测试,操作流程如下:

1. 打开手机蓝牙并进入蓝牙控制界面,搜索自拍杆蓝牙设备。

2. 选择对应名称(MindMotion-Shutter)的蓝牙设备并进行配对。

3. 配对成功后打开手机相机界面,这个时候点击按键K1就可以进行拍照了。


图2 手机界面

来源: 灵动MM32MCU

围观 46

在前面一章中我们已经详细介绍过MM32W系列MCU的自定义AT指令,在接下来的章节我们将着重介绍基于BLE开发的应用方案,在本章节我们将介绍智能灯控方案。

基于蓝牙技术的智能灯控方案是智能家居应用重要组成部分,通过连接手机APP可以控制灯的开关、亮度、设定开关时间、统计耗电量等功能。该方案具有控制方便,功能多样,操作迅速,设计开发简单等优势。

硬件资源:

LED的驱动分别使用PA9/10/11输出PWM波形控制三极管来驱动RGB灯。通过控制灰度来实现视觉上的亮度和颜色变化,红、绿、蓝三个颜色通道每种颜色各分为255阶亮度,在0时"灯"最弱--是关掉的,而在255时"灯"最亮。当三色数值相同时为无色彩的灰度色,而三色都为255时为最亮的白色,都为0时为黑色控制频率。可以使用几百赫兹到几十K赫兹来进行调节不同色彩的灯光。驱动控制原理部分详见下图:


图1 RGB灯驱动原理图

软件资源:

1、PA9/10/11为TIM1的CH2、3、4的捕获比较输出通道,需要将三个IO工作模式的配置为复用推挽输出,同时配置AF寄存器为TIM1的CH2、3、4功能,TIM1需要配置CH2、3、4为脉冲宽度调制模式。配置方式实现代码如下:

void PWM_Init(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

TIM_OCInitTypeDef  TIM_OCInitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE);

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA , ENABLE); 

GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource9,GPIO_AF_2);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource10,GPIO_AF_2);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource11,GPIO_AF_2);

TIM_TimeBaseStructure.TIM_Period = 255*100;

TIM_TimeBaseStructure.TIM_Prescaler = 0;

TIM_TimeBaseStructure.TIM_ClockDivision = 0;

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); 

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;

TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

TIM_OCInitStructure.TIM_Pulse = 0;

TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; 

TIM_OC2Init(TIM1, &TIM_OCInitStructure);

TIM_OC3Init(TIM1, &TIM_OCInitStructure);

TIM_OC4Init(TIM1, &TIM_OCInitStructure);

TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);

TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);

TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);

TIM_ARRPreloadConfig(TIM1, ENABLE);

TIM_Cmd(TIM1, ENABLE);

TIM1->BDTR |= 0x8000;

//上电亮白光

SetLEDLum(0,0,0,100);

SetLEDLum(0,0,100,101);

}

2、我们在UpdateLEDValueAll函数中调用Led_getInfo(data)来获取RGB的配置信息,data是一个数组指针,数组成员包含RGB三个LED的灰度值(0-255),而Led_getInfo(data)最终通过调用server_rd_rsp(u8 attOpcode, u16 attHandle, u8 pdu_type)函数来获取手机APP发送给我们的信息。根据data信息修改TIM1的CCR2、3、4的大小来调节CH2、3、4三路PWM输出的占空比,进而调节REB的三个LED的亮度,来实现我们对于不同色彩、亮度的需求。详细实现代码如下:

void UpdateLEDValueAll(void) //porting function

{

int t;

unsigned char data[7];

unsigned char EnableLED_Flag = 0;

unsigned int Led_Lum_percent = 100;

Led_getInfo(data);

EnableLED_Flag = data[0];

if(EnableLED_Flag == 0) {

TIM_SetCompare2(TIM1,0); //G

TIM_SetCompare3(TIM1,0); //B

TIM_SetCompare4(TIM1,0); //R

} else {

Led_Lum_percent = data[6];

t = data[3] * Led_Lum_percent; TIM_SetCompare2(TIM1,t); //Rx100

t = data[2] * Led_Lum_percent; TIM_SetCompare3(TIM1,t); //Gx100

t = data[1] * Led_Lum_percent; TIM_SetCompare4(TIM1,t); //Bx100

}

}

我们选用一款通用APP做为控制端,操作流程如下:

1. 手机打开App,会自动开始搜索蓝牙设备名(如MindMotionLED)并连接。

2. 连接成功以后app出现RGB控制界面,可以在APP界面中点选不同区域来改变LED灯的颜色。


图2 APP界面

来源: 灵动MM32MCU

围观 10

MM32W0/3提供模组和开发板方式供客户使用,支持UART\SPI\IIC接口的AT指令,用户通过发送相关固定格式的指令方式可以实现对应功能。开发板上电后,模块会自动进行广播,移动设备的APP 会对其进行扫描和连接,连接成功之后可以通过BLE 在模块和移动设备之间进行数据传输。用户MCU 可通过模块的串口和移动设备进行双向通信,移动设备也可以通过APP 对模块进行写操作,写入的数据将通过串口发送给用户的MCU,模块收到来自用户MCU 串口的数据,将自动转发给移动设备。

AT 指令主要用于配置模块参数,比如广播间隔、设备名、等,也用于发送透传数据和断开BLE 连接。而对于AT指令,客户可以很方便的进行修改,添加自己需要的功能。

通信流程


图1 通信流程

UART AT指令集


表1 UART AT指令集

在官方提供的程序中已经支持大部分的蓝牙设置等操作,可以实现透传、修改蓝牙参数等操作,如果用户需要单独开发AT指令集可以通过以下方式进行开发。

接收指令

首先是接收指令时的数据处理流程:


图2 接收指令流程图

在每次蓝牙服务调用UsrProcCallback()函数时,使用CheckAtCmdInfo()函数检查是否收到数据,如果有,在进入休眠之前加入一个可以接收20个字节的延时,在接收中断中接收剩下的数据,通过判断最后一位是否是0x0d或是0x0a来获得一条完整的指令,调用AtCmdPreParser()函数处理数据。检查数组开始的“AT+”和后面的指令名称,在at_func_list[]中查找并调用对应的函数对数据中后续的参数进行处理。

从流程中可以看到,如果只是简单的加减指令的话,只需要修改at_func_list数组就可以了,结构体AT_CMD_FUNC的两个成员变量分别是函数名称和对应的字符串。

注:处理时间不宜太长,更不能阻塞

typedef void (*ATCMDFUNC)(u8* cmd,u8 len);    

typedef struct _tagATCMD

{

ATCMDFUNC func;

u8 name[MAX_AT_CMD_NAME_SIZE]; //max len is 11 bytes

}AT_CMD_FUNC;

在例程中,收到AT指令要通过蓝牙发送数据时,使用的是sconn_notifydata()接口函数,这是一种不需要应答的蓝牙特征值,预设句柄为0x12,可以在发送前用set_notifyhandle()函数修改对应的句柄,或者直接修改变量u16 cur_notifyhandle。

发送数据

在例程中,通过UART发送数据都是通过moduleOutData()函数,往一个特定的缓存数组中写入数据。这个函数可以加在任何位置,可以加在AT指令处理函数中发送应答数据,也可以加到BLE服务中实现数据透传功能。

在每次蓝牙服务调用UsrProcCallback()函数时检查缓存数组,若不为空,在休眠之前加入一个延时,开启发送缓冲空中断,并在中断中发送剩下的数据。


图3 发送数据流程图
void moduleOutData(u8*data, u8 len) //api

{

unsigned char i;

if ((txLen+len)

以上例程使用的是UART接口的自定义AT指令实现方式,用户可以根据需要自行修改为其他接口,如SPI、IIC、CAN、USB等。

来源: 灵动MM32MCU

围观 36

MM32W0x2xxB 蓝牙功能协议栈目前以Lib 形式提供,用户通过调用相关接口的方式实现对应功能。例程中,用户如需调整BLE 数据交互的特征值、服务及数据的收发,可按照如下的几个步骤进行调整,大部分的配置都在..\SRC_LIB\app.c文件中。

蓝牙之间通信是以参数来进行数据传输,即服务端定好一个参数,客户端可以对这个参数进行读,写,通知等操作,这个东西我们称之为特征值(characteristic),但一个参数不够我们用,比如我们这个特征值是电量的值,另一个特征值是设备读取的温度值。那这时候会有多个特征值,并且我们还会对它们分类,分出来的类我们称之为服务(service)。一个设备可以有多个服务,每一个服务可以包含多个特征值,本章节将介绍如何在例程中调整服务及自定义特征值。
在收发数据的时候,对于协议的处理基本都在lib中完成了,我们只需要在对应的接口函数中进一步处理就好。

声明与定义

首先是服务及特特征值的定义,用户可以自己分配,参考结构定义如下所示:

typedef struct ble_character16{

u16 type16; //type2

u16 handle_rec; //handle

u8 characterInfo[5];//property1 - handle2 - uuid2

u8 uuid128_idx; //0xff means uuid16,other is idx of uuid128

}BLE_CHAR;

 

typedef struct ble_UUID128{

u8 uuid128[16];//uuid128 string: little endian

}BLE_UUID128;

分别修改const BLE_CHAR AttCharList[]和const BLE_UUID128 AttUuid128List[]中的数据,自行分配句柄(递增,不得重复)

1、type16 为database 每个记录的类型,具体取值根据蓝牙规范定义;

常用的三个宏定义:

#define TYPE_CHAR 0x2803 //特征值的声明

#define TYPE_CFG 0x2902 //客户端特征值配置描述符

#define TYPE_INFO 0x2901 //特征值用户描述符

2、handle_rec 为对应记录的句柄,用户可以自定义;

3、characterInfo 保存了对应特征值的属性(property1)、句柄(handle2)及uuid(uuid2),其中handle2 及uuid2 为16 bit 小端格式;

常用的属性的宏定义如下:

#define ATT_CHAR_PROP_RD   0x02   //可读

#define ATT_CHAR_PROP_W_NORSP   0x04   //可写,无需应答

#define ATT_CHAR_PROP_W  0x08    //可写

#define ATT_CHAR_PROP_NTF  0x10    //notify

#define ATT_CHAR_PROP_IND   0x20    //indicate

4、uuid128_idx 表示uuid2 的格式,如该值为UUID16_FORMAT(0xFF) 则表示uuid2 为16bit 格式,反之则表示uuid2 为128bit 的uuid 信息对应的索引值,该索引值对应于AttUuid128List 的内容索引。uuid128 为小端格式保存。UUID就是通用唯一识别码。在蓝牙协议栈中可能会有多个服务,每个服务会有多个特征值,而这些服务或者特征值都有一个唯一的ID,这样就可以区分了。这个UUID是其他设备设置蓝牙服务和特征值的唯一方法。

应答Primary Service 的查询

下一步要修改的是att_server_rdByGrType函数。

在函数中缺省情况下如果客户对Device Info 不做特别修改,可直接调用缺省函数att_server_rdByGrTypeRspDeviceInfo(pdu_type)即可。

而下面的att_server_rdByGrTypeRspPrimaryService()需要按照上面的定义填充对应的数据,其中start_hd 与end_hd 为对应Service handle 取值范围,uuid 为字符串,对应的长度由uuidlen给出。

写操作

当外界发来相关数据时,ser_write_rsp()函数将被调用。

void ser_write_rsp( u8 pdu_type/*reserved*/,

u8 attOpcode/*reserved*/,

u16 att_hd, //对应特征值句柄

u8* attValue, //数据内容指针

u8 valueLen_w) //数据长度

通过判断特征值句柄att_hd,就可以进一步处理收到的数据。

若特征值属性为ATT_CHAR_PROP_W,需要调用ser_write_rsp_pkt()函数对这次写操作进行应答,不应答会导致连接断开。

若特征值无效或未定义,则使用att_notFd()函数进行应答,参数直接引用回调函数对应参数即可。

其中att_hd 为从手机BLE 传(写)过来数据对应的特征值的句柄,数据内容保存在变量attValue 中,数据长度为valueLen_w。

读操作

类似写操作,收到读取特征值请求时,ser_write_rsp()函数将被调用。

void server_rd_rsp(u8 attOpcode, u16 attHandle, u8 pdu_type)

通过判断attHandle来执行对应操作,使用att_server_rd()函数进行应答。

void att_server_rd(unsigned char pdu_type,

unsigned char attOpcode

unsigned short att_hd, //对应特征值句柄

unsigned char* attValue, //应答数据指针

unsigned char datalen ); //数据长度

其中pdu_type和attOpcode直接引用回调函数中对应参数,每次调用发送的数据长度不得超过20字节。

同写操作,若特征值无效或未定义,则使用att_notFd()函数进行应答。

Notify 数据发送操作

在模块出厂时烧录的例程中,可以通过UART的AT指令,调用Notify数据透传,对应的接口函数是

u8 sconn_notifydata(u8* data, u8 len);

原则上数据长度可以超过20 字节,协议会自动拆包发送,每个分包最大20字节,推荐一次发送的数据尽量不超过3 个分包,该函数返回实际发送的数据长度。这一函数没有指定对应的句柄,如果用户定义了多个Notify特征值,需要在发送前使用set_notifyhandle()函数指定对应的句柄,或者直接修改变量u16 cur_notifyhandle。

下面我们以在例程中添加一个可读可写的特征值为例,最后通过手机app与BLE之间进行通信:

1、在const BLE_CHAR AttCharList[]数组最后添加

{TYPE_CHAR,0x1A,ATT_CHAR_PROP_RD|ATT_CHAR_PROP_W, 0x1B,0,0,0,4},

//User defined

即在原数组最后句柄0x19后添加新的特征值,对应特征值设置可读可写,句柄0x001B为用户自定义特征值,128位UUID,索引值为4;

2、在const BLE_UUID128 AttUuid128List[]数组最后添加对应的UUID

{0x9e,0xca,0x0dc,0x24,0x0e,0xe5,0xa9,0xe0,0x93,0xf3,0xa3,0xb5,5,0,0x40,0x6e}, //idx4,little endian, Test

3、修改att_server_rdByGrType()函数:

att_server_rdByGrTypeRspPrimaryService(pdu_type,0x10,0x1B,(u8*)(AttUuid128List[0].uuid128),16);

修改最后的句柄值。

4、修改ser_write_rsp()函数

在switch(att_hd)分支中加入

case 0x1B:

moduleOutData("Write_Server_1B\r\n",17);

ser_write_rsp_pkt(pdu_type);

break;

5、修改server_rd_rsp()函数

在switch(attHandle)分支中加入

case 0x1B:

moduleOutData("Read_Server_1B\r\n",17);

att_server_rd( pdu_type, attOpcode, attHandle, "RD_SERVE_1B", 11);

break;

如下图,程序下载运行后,我们用手机连接模块,可以看到在服务列表最后多出了一项Unknown Characteristic ,可读可写,点击读按钮,可以收到字符串” RD_SERVE_1B”,UART串口输出”Read_Server_1B”,点击写按钮发送任意值会UART串口输出”Write_Server_1B”。


图1 手机端截图


图2 UART输出

来源:灵动MM32MCU

围观 16

MM32W0x2xxB 的蓝牙协议栈目前以lib 形式提供用户使用,用户无需了解蓝牙底层协议,通过调用相关接口的方式即可实现蓝牙无线传输,其中BLE 协议栈lib 放置在SRC_LIB目录下,接口定义头文件在inc 目录下。应用入口文件为main.c,应用实现代码在app.c。本章节介绍了部分对应各接口,详细完整的函数定义及使用注意事项参见官网的编程指导手册。

蓝牙相关资料下载链接:http://www.mindmotion.com.cn/download.aspx?cid=2567


上表是lib中的接口函数,在编程指导手册中都有详细的说明,大部分函数的调用很简单,其中与服务(service)及特征值定义相关的函数将在后续章节详细介绍。

1、radio_initBle

函数原型: void radio_initBle(unsigned char txpwr, unsigned char**addr/*Output*/);

函数功能:用于初始化蓝牙芯片及蓝牙协议栈。需要在协议一开始调用。

输入参数:txpwr用于配置发射功率,可取的值有TXPWR_0DBM,TXPWR_3DBM 等。

输出参数: addr 该参数返回蓝牙 MAC 地址信息, 6 个字节长度。

2、ble_run

函数原型:void ble_run(unsigned short adv_interval);

函数功能:运行蓝牙协议

输入参数:adv_interval,参数的单位为0.625us,如果160 表示100ms 的广播间隔。

注:阻塞调用。中断方式运行时,在IRQ中断处理函数中调用,参数为0

3、sconn_notifydata

函数原型: unsigned char sconn_notifydata(unsigned char* data, unsigned char len);

函数功能:通过蓝牙发送数据输入。

参数: data 需要发送的数据指针 len 数据长度。

注意事项:本接口函数会根据系统缓存情况自动拆包发送数据,但不得在原地阻塞等待反复调用本接口。

4、radio_standby

函数原型:void radio_standby(void);

函数功能:在通过该函数可以使射频模块进入standby模式。

注意事项:射频模块进入standby后不能定时唤醒(射频模块进入STOP 模式可以定时唤醒自身以及控制模块),此时需要外界给IRQ 提供上升沿电平信号才能唤醒射频模块,给PA0 提供下降沿电平才能唤醒控制模块。

5、att_notFd

函数原型: void att_notFd(unsigned char pdu_type, unsigned char attOpcode, unsigned short attHd );

函数功能:对无效特征值(或没有定义的特征值)进行操作的应答函数

注意事项:凡是无效特征值的操作需要应答本函数,可将本函数作为缺省调用。

6、ser_write_rsp_pkt

函数原型: void ser_write_rsp_pkt(unsigned char pdu_type);

函数功能:对具有 Write With Response 属性特征值写操作后的应答函数。

注意事项:对需要写应答的特征值,如果不应答会导致连接的断开。

7、att_server_rdByGrTypeRspDeviceInfo

函数原型: void att_server_rdByGrTypeRspDeviceInfo(unsigned char pdu_type);

函数功能:对缺省 Device Info 内容的应答可调用本接口函数。

输入参数: pdu 类型参数,直接引用回调函数 att_server_rdByGrType 中对应参数。

注意事项:如果用户直接使用发布包代码,可直接调用本接口函数。

8、att_server_rdByGrTypeRspPrimaryService

函数原型: void att_server_rdByGrTypeRspPrimaryService(unsigned char pdu_type,

unsigned short start_hd,

unsigned short end_hd,

unsigned char*uuid,

unsigned char uuidlen);

函数功能:应答 Primary Service 的查询,用户需按特征值实际定义的句柄及 UUID 填充对应数据。

输入参数: pdu_type PDU 类型参数,直接引用回调函数 att_server_rdByGrType中对应参 start_hd, 某个Service 对应的起始句柄值 end_hd,某个 Service 对应的结束句柄值 uuid,某个 Service 对应的 UUID 字串(Hex 值),如 0x180A 表示为 0x0a, 0x18。 uuidlen,某个 Service 对应 UUID 字串的长度。

注意事项:需要严格按照特征值定义规划填充对应参数。

9、att_server_rd

函数原型: void att_server_rd(unsigned char pdu_type,

unsigned char attOpcode

unsigned short att_hd,

unsigned char* attValue,

unsigned char datalen );

函数功能:读取某特征值的值。

输入参数:pdu_type PDU 类型参数,直接引用回调函数server_rd_rsp 中对应参数 attOpcode操作对应的值,直接引用回调函数server_rd_rsp 中对应参数att_hd 特征值对应的句柄值,直接引用回调函数server_rd_rsp 中对应参数 attValue特征值对应的值字串指针 datalen特征值字串长度。

注意事项:需要按需将对应特征值内容作为应答内容,如果对应特征值内容无效可应答 att_notFd()。

回调函数

为便于蓝牙差异化功能的灵活实现,蓝牙协议内设置了若干接口并以回调函数的方式由应用层porting实现,所有回调函数不得阻塞调用,具体函数的实现可参考SDK 发布包中对应代码的实现示例。回调函数主要包括如下的一些:

1、void gatt_user_send_notify_data_callback(void);

蓝牙连接成功后协议在空闲的时侯会调用本回调函数;

2、void UsrProcCallback(void);

蓝牙协议会周期性回调本函数;

3、void ser_prepare_write(unsigned short handle,unsigned char* attValue, unsigned short attValueLen, unsigned short att_offset);

4、void ser_execute_write(void);

以上两个函数为队列写数据回调函数。

5、unsigned char* getDeviceInfoData(unsigned char* len);

本函数GATT 中设备名称获取的回调函数;

6、void att_server_rdByGrType( unsigned char pdu_type, unsigned char attOpcode,

unsigned short st_hd, unsigned short end_hd, unsigned short att_type );

蓝牙GATT 查询服务的回调函数;

7、void ser_write_rsp(unsigned char pdu_type/*reserved*/, unsigned char attOpcode/*reserved*/,unsigned short att_hd, unsigned char* attValue/*app data pointer*/,

unsigned char valueLen_w/*app data size*/);

蓝牙GATT 写操作回调函数;

8、void server_rd_rsp(unsigned char attOpcode, unsigned short attHandle, unsigned char pdu_type);

蓝牙GATT 读操作回调函数;

9、void ConnectStausUpdate(unsigned char IsConnectedFlag);

蓝牙连接状态更新回调函数;

在使用接口函数时需要注意事项:

1、所有接口函数不得阻塞调用。

2、函数att_server_rd(...)每次调用发送的数据长度不得超过20 字节。

3、函数sconn_notifydata(...)只能在协议主循环体内调用,函数不可重入,可以发送多于20 字节的数据,协议会自动分包发送,且每个分包长度最大为20 字节。推荐一次发送的数据尽量不超过3 个分包。

4、在参考例程提供了支持配对/加密的AES加密方式:unsigned char aes_encrypt_HW(unsigned char *painText128bitBE,unsigned char *key128bitBE); //是否支持硬件AES。如果不支持请返回0,蓝牙库支持软件AES。

5、UUID 支持16bit 和128bit 两种。

来源: 灵动MM32MCU

围观 58

本章我们来看一下低功耗模式下用到的休眠和时钟配置函数。

目前MM32W0系列有n4和q1两个版本,n4主要针对需要大容量的应用方案,q1针对超低功耗精简型的应用方案,两个型号的低功耗编程操作方式相同,不同的只是MCU的时钟系统控制方式,在参考程序已经提供不同的模式下功能,用户只要改变相对应的宏定义即可实现对应的低功耗功能。

休眠函数

对于低功耗应用来说,休眠是非常关键的一个功能。开启蓝牙广播时MM32W0的控制模块有三种工作模式:正常模式、睡眠模式和停机模式。在stop模式下,射频模块都会通过IRQ引脚定时触发一个外部中断,可以借此唤醒STOP模式中的控制模块。

在阻塞模式中,休眠是蓝牙服务通过调用void McuGotoSleepAndWakeup(void) 函数实现的,对于中断模式,则是用户在代码中主动调用IrqMcuGotoSleepAndWakeup()函数来实现。

void McuGotoSleepAndWakeup(void) // auto goto sleep AND wakeup, porting api

{

if ((SleepStop)&&                                            //开启休眠功能

 (TxTimeout < SysTick_Count)&&

 (RxTimeout < SysTick_Count))                   //UART无收发数据

{

if(SleepStop == 1){                      //SLEEP

SCB->SCR &= 0xfb;

__WFE();

}else{                                         //STOP

SysClk48to8();                          //HSI 6分频

SCB->SCR |= 0x4;

__WFI();                                 //进入STOP模式

                                         

RCC->CR|=RCC_CR_HSION;   //从STOP模式唤醒,使能时钟

SysClk8to48();                                      //PLL倍频至48MHz

}

}

}

            

void IrqMcuGotoSleepAndWakeup(void) // auto goto sleep AND wakeup, porting api

{

if(ble_run_interrupt_McuCanSleep() == 0) return;

#ifdef USE_UART

if ((SleepStop)&&

 (TxTimeout < SysTick_Count)&&

 (RxTimeout < SysTick_Count))

{

if(SleepStop == 1){                   //sleep

SleepStatus = 1;

SCB->SCR &= 0xfb;

__WFE();                                  //控制模块进入睡眠模式

}else{                                        //stop

SleepStatus = 2;

SysClk8M();

SCB->SCR |= 0x4; 

SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; 

__WFI();

}

}

#endif

}

在中断方式中,需要在中断处理函数中重新配置时钟:

void EXTI4_15_IRQHandler(void)

{

EXTI_ClearITPendingBit(EXTI_Line8); 

if(2 == SleepStatus){                //stop

RCC->CR|=RCC_CR_HSION;               //HSI使能

RCC->CR |= RCC_CR_PLLON;            //PLL使能

RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;

SysTick_Config(48000);

}

SleepStatus = 0; 

ble_run(0); 

}

时钟配置

进入低功耗前后需要配置时钟,从低功耗模式恢复时,时钟默认设置为HSI6分频,需要重新配置系统时钟。

注意:下面函数属于蓝牙库接口,没有用到也不要删除。

相关时钟配置:

void SysClk48to8(void)1

{

RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI);//selecting PLL clock as sys clock

    

while (RCC_GetSYSCLKSource() != 0x0)

{}

    

RCC->CR &=~(RCC_CR_PLLON);          //clear PLL

SysTick_Config(8000);

}

 

void SysClk8to48(void)         //从STOP模式中恢复

{

SetSysClock_HSI(4);//HSI:12*4=48M

 

RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
//selecting PLL clock as sys clock

while (RCC_GetSYSCLKSource() != 0x08)

{}

SysTick_Config(48000);

}

void SetSysClock_HSI(u8 PLL)      //重新配置HSI和PLL

{  

unsigned char temp=0;   

  

RCC->CR|=RCC_CR_HSION;  

while(!(RCC->CR&RCC_CR_HSIRDY));

RCC->CFGR=RCC_CFGR_PPRE1_2; //APB1=DIV2;APB2=DIV1;AHB=DIV1;

  

RCC->CFGR&=~RCC_CFGR_PLLSRC;              //PLLSRC ON 

  

RCC->CR &=~(RCC_CR_PLLON);                    

  

RCC->CR &=~(0x1f<<26);        //clear PLL

RCC->CR|=(PLL - 1) << 26;  //setting PLL value 2~16

  

FLASH->ACR=FLASH_ACR_LATENCY_1|FLASH_ACR_PRFTBE;       
//FLASH 2 delay clk cycles

  

RCC->CR|=RCC_CR_PLLON; //PLLON

while(!(RCC->CR&RCC_CR_PLLRDY));//waiting for PLL locked

RCC->CFGR&=~RCC_CFGR_SW;

RCC->CFGR|=RCC_CFGR_SW_PLL;//PLL to be the sys clock

while(temp!=0x02)    //waiting PLL become the sys clock

{    

temp=RCC->CFGR>>2;

temp&=0x03;

} 

}

以上时钟配置只是针对MM32W0系列的n4版本,在q1版不需要以上时钟操作。MM32W051PFB(q1)蓝牙功耗参数:

在SleepStop设置成0x02,MCU将会进入STOP模式,在保持 SRAM 和寄存器内容不丢失的情况下,停机模式可以达到最低的电能消耗。在停机模式下,HSI 的振荡器和 HSE 晶体振荡器被关闭。可以通过任一配置成 EXTI 的信号或者看门狗不复位方式把微控制器从停机模式中唤醒,EXTI 信号可以是 16 个外部 I/O 口之一、 PVD 的输出的唤醒信号。。STOP模式下无法下载调试程序。为了方便调试,可以在程序开始时加入一个延时,这样每次复位都有一段时间可以下载程序。

来源:灵动MM32MCU

围观 27

页面

订阅 RSS - MM32W