MCU

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

浏览 1 次

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

浏览 1 次

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

围观 5

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

目前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 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 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 &=~(0x1fCR|=(PLL - 1) 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

围观 2

本章我们将对软件架构进行简单的讲解。

阻塞式例程介绍

对于大部分的低功耗设备来说,CPU都是处在休眠模式中,只在接收到特定数据的时候被唤醒处理少量数据,这种应用场景比较适合阻塞方式运行,这种方式配置简单,CPU大部分时间都被蓝牙服务占用,在收到来自射频模块的IRQ信号时需要及时处理,因此,用户的代码不允许出现阻塞。

我们先看一下例程中的main函数:

int main(void)

{

unsigned long temp=0x800000;

SystemClk_HSEInit();               //系统时钟配置为48MHz

            

#ifdef USE_UART                    //目前只支持UART接收与发送AT指令

#ifdef USE_AT_CMD    //开启AT指令功能

SleepStop = 0x01;       //空闲时低功耗,0x00不休眠,0x01睡眠,0x02停机模式

#endif

#endif

            

#ifdef USE_UART                    //开启UART功能,修改全局宏定义改变UART和对应引脚

uart_initwBaudRate();               //波特率默认9600,可以修改uart.c中的BaudRate变量

#endif

            

#ifdef USE_I2C                        //开启I2C功能

IIC_Init(I2C1);               // I2C1,标准模式,SCL PB6 ,SDA PB7 ,SendDataFlag PA10

#endif

 

#ifdef USE_USB                      //开启USB功能

usb_test();                    //使用PA11、PA12,枚举为USB HID设备

#endif

 

//SysTick_Count每1ms加一,系统时钟改变时应调用SysTick_Config()函数

SysTick_Configuration();           

            

//启用SPI2,在芯片内部与射频模块通信,速度应不低于6Mhz

SPIM_Init(SPI2,/*0x06*/0x06); 

            

IRQ_RF();         //配置PB8的IRQ 功能处理射频模块信号,用于低功耗唤醒

            

while(temp--);                //延时,方便烧录程序

radio_initBle(0x48, &ble_mac_addr);   //初始化射频模块(3dBm),并获取MAC地址

printf("\r\nMAC:%02x-%02x-%02x-%02x-%02x-%02x\r\n", ble_mac_addr[5],
ble_mac_addr[4],ble_mac_addr[3],
ble_mac_addr[2],ble_mac_addr[1],
ble_mac_addr[0]);

            

ble_run(160*2); //广播间隔320*0.625=200 ms,

}

对于蓝牙,必要的系统资源有:用于计时的Systick和与射频模块通信的SPI2,Systick使用SysTick_Count变量计时,也可以使用这个变量主动避开与IRQ处理任务的冲突;

目前程序支持RUN MODE,SLEEP MODE,STOP MODE模式,其中,在stop模式下MCU可以通过任意一个外部中断线唤醒,比如MM32W073NTB封装,在芯片设计上将IRQ与PB8共用一个GPIO口,在MM32W073PFB封装上,IRQ是独立的GPIO,需要用户在设计时将IRQ连接到任意一个GPIO口修改外部中断唤醒源即可实现stop唤醒模式;

例程中通过修改全局宏定义,可以启用UART、IIC和USB,目前都支持AT指令方式;

蓝牙的广播间隔由ble_run()中的参数决定,单位为0.625ms;

与中断式例程不同的地方:

① 蓝牙服务会定时调用接口函数McuGotoSleepAndWakeup();

② ble_run()是一个阻塞函数,后续程序将不会执行,中断模式中ble_run参数为0,且不是阻塞函数;

③ 中断式例程中蓝牙广播间隔由ble_run_interrupt_start()函数的参数决定,阻塞式例程中蓝牙广播间隔由ble_run()的参数决定;


图2 阻塞式程序流程

阻塞式程序流程如上图。实际使用中,蓝牙服务将定时调用UsrProcCallback()函数,在连接后允许时调用gatt_user_send_notify_data_callback()。两个函数的处理时间应尽可能短,不能影响IRQ信号的处理,否则可能出现蓝牙连接断开、无蓝牙广播等问题。

我们可以将用户程序放在callback.c的UsrProcCallback()函数中定时执行,如例程中的AT指令的处理CheckAtCmdInfo()函数,注意不要阻塞。可以将发送自身状态的程序放在gatt_user_send_notify_data_callback()函数中,同样不得阻塞。

来源:灵动MM32MCU

围观 2

本章我们将对软件架构进行简单的讲解。


图1 蓝牙通信框图

MM32W0控制模块通过SPI通信对射频模块进行控制,MM32W0的蓝牙程序提供以库的形式提供给大家使用,用户无需了解蓝牙协议栈,只需要对MCU进行控制即可实现蓝牙控制。在协议栈中为方便用户使用预留接口函数,用户通过调用相关接口的方式实现对应功能。

以下几点需要注意:

1)控制模块SPI2 仅且只能用于与射频模块的通信。

2)IRQ 信号引脚用于射频模块与控制模块的唤醒,且PB8 引脚只能用于控制模块唤醒。

3)AVDD 供电电压为2.2V ~ 3.6V

目前蓝牙控制程序有两种类型:中断式和阻塞式,中断方式是是以中断服务的方式运行,适合于实现用户某功能需要占用较长CPU 时间但可以被任意打断的应用场景;阻塞方式是蓝牙协议运行的入口函数为ble_run(),该函数不会返回,两种方式调用的接口函数都相同。

中断式例程介绍

中断服务程序方式运行的软件架构如下图所示。


图2 中断方式软件构架
main()函数:

int main(void)

{

unsigned long temp=0x800000;

unsigned long i=0; 

while(temp--);

SystemClk_HSEInit();

PWM_Init();



#ifdef USE_UART

#ifdef USE_AT_CMD

SleepStop = 0x02;

#endif

#endif



#ifdef USE_UART

uart_initwBaudRate();

#endif



#ifdef USE_I2C

IIC_Init(I2C1);

#endif    

SysTick_Configuration();



SPIM_Init(SPI2,/*0x06*/0x06); //6Mhz

 

IRQ_RF();

 

SetBleIntRunningMode(); 

radio_initBle(TXPWR_0DBM, &ble_mac_addr); 

SysTick_Count = 0;

while(SysTick_Count CR|=RCC_CR_HSION;

RCC->CR |= RCC_CR_PLLON;

RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;

SysTick_Config(48000);

}

SleepStatus = 0;         //设置当前状态为唤醒

ble_run(0); 

}

中断式例程需要用到两个中断服务程序,一个是蓝牙IRQ 中断PB8对应的外部中断线,一个是实现SysTick 对应的中断。IRQ 对应的中断服务程序用以运行蓝牙协议,需要有较高的中断优先级(针对所有系统中断来说)。

UART,SPI,IRQ,USB等控制模块上的配置同阻塞方式。

SPIM_Init(SPI2,0x06)是控制模块和射频模块间通信的初始化,SPI2只能用于与射频模块的通信。

IRQ_RF将PB8设置为外部中断,用于实现IRQ外部唤醒功能,通过一个下降沿唤醒MCU。PB8 引脚只能用于控制模块唤醒。

uart_initwBaudRate()是UART的初始化,对于两种封装对应的UART和GPIO接口不同。

不同点:

① 初始化蓝牙配置函数radio_initBle()之前,需要先调用SetBleIntRunningMode()函数。

②启动蓝牙调用ble_run_interrupt_start()而不是ble_run(),后面需要一个while(1)循环,可以将用户程序放在这里。

③ 进入休眠模式的函数需要主动调用IrqMcuGotoSleepAndWakeup()函数,函数McuGotoSleepAndWakeup()不再被调用。根据启动蓝牙时的参数,射频模块将定时触发IRQ的外部中断唤醒MCU。

来源: 灵动MM32MCU

围观 4

在文章中使用到了MM32 BLE_TestBoard和蓝牙模组,该蓝牙开发板是为了用户快速上手、了解、学习及评估MM32无线系列MCU性能的一块入门级开发板,本章节将会针对开发板及两款模组差异做详细介绍。

MM32W0系列主要有QFN32和LQFP48两种封装,支持32k\64k\128k flash容量,支持UART、I2C、SPI、USB device、CAN等通信接口。目前都是提供模组和开发板的方式供大家评估、测试。

MM32W0系列蓝牙模块是灵动微电子专为智能低功耗蓝牙数据传输而打造,遵循BLEV4.1蓝牙规范。支持蓝牙SPP 协议,可与所有版本安卓手机收发数据,可与支持BLE 的IOS 设备配对连接,无需额外授权费用,支持后台程序常驻运行;支持AT 指令,用户可根据需要更改串口波特率、设备名称、配对密码等参数,使用灵活。

本模块支持UART 接口、I2C 接口和USB 接口,具有成本低、体积小、功耗低、收发灵敏性高等优点,只需配备少许的外围元件就能实现其强大无线数据传输功能。

模组资源介绍


图1 QFN32封装模组资源


图2 LQFP48封装模组资源


图3 模组尺寸封装

开发板介绍

对于两种模块,大小规格是一样的,所以官方的开发板兼容这两种模块,只需要配置好对应的引脚即可正常使用。


图4 BLE开发板

如上图,这是一个官方的开发板,模块上的芯片是一颗LQFP48封装的MM32W0芯片。开发板的左边画框的部分需要连接跳帽,从上到下分别是UART,USB,和供电。

① UART部分连接有LED指示灯。

配置时需要注意的是,

LQFP48:UART2、PA2、PA3;

QFN32:UART1、PA9、PA10。

②USB部分使用的是PA11和PA12,可以向上连接使用USB功能,也可以向下连接使用UART的硬件流控功能。

③供电部分工作电压为2.3V ~ 3.6V

④在开发板下方是标准的SWD烧录接口

⑤在模块的周围,可以看到用丝印将对应的引脚名称或常用功能标注出来了。其中右上角标注了两种模块的不同,从上到下依次是:

LQFP48:PB9(Pin34)、PB0、PB1、PA0、PA1、IRQ

QFN32:PA8(Pin34)、PB0、PB1、PB2、PA0、IRQ

⑥在开发板最右边(以LQFP48为例)

LED2:PB9,低电平点亮

LED2:PB1,低电平点亮

变阻器:PB0,10K

Key2:Reset,按下为低电平

Key1:PA0,按下为高电平

来源:灵动MM32MCU

浏览 1 次

在物联网的大趋势下,智慧城市和智能家居也随之兴起。而物联网的发展离不开无线技术,众所周知蓝牙是目前物联网产业中使用最广泛的无线通讯技术,特别是像蓝牙这种低功耗技术,更是众所瞩目的焦点。

BLE的优点主要包括:高可靠性、高安全性、低成本、低功耗。

灵动微电子有基于ARM® CortexTM-M3 和ARM® CortexTM-M0为内核的无线连接功能的MM32W系列(集成MCU+RF射频芯片的单芯片),MM32W0xxB 产品提供LQFP48 和QFN32 封装形式;根据不同的封装形式,器件中的外设配置不尽相同。这些丰富的外设配置,使得MM32W0xxB 产品微控制器适合于多种应用场合。

下面是MM32W0系列的选型表,可以根据所需的外设来选择合适的型号。


图1 MM32W0系列选型表


图2 MM32W3系列选型表

为了方便客户的使用,灵动微电子设计了几种蓝牙模块及配套的蓝牙开发板,只需配备少许的外围元件就能实现其强大无线数据传输功能,,搭载了MM32W0xxB是超低功耗的单模蓝牙芯片,工作电压为2.3V ~ 3.6V,多种省电工作模式保证低功耗应用的要求。支持多种数据通信接口(UART\USB\IIC\SPI等),支持AT 指令,用户可根据需要更改串口波特率、设备名称、配对密码等参数,使用灵活。该模块主要用于短距离的数据无线传输领域,可以实现与手机间一对一互连。


图3 两种常用的模块


图4 模组物理特性

模组主要应用领域:

1)蓝牙无线数据采集、传输;
2)工业遥控、遥测;
3)无线键盘、鼠标;
4)汽车检测设备、交通、井下定位、报警;
5)智能家居、楼宇自动化、安防、机房设备无线监控、门禁系统;
6)蓝牙操纵杆、蓝牙游戏手柄;
7)蓝牙遥控玩具;
8)防丢器、LED 灯控、iBeacon

使用AT指令控制模块

下面我们使用蓝牙开发板来演示AT指令控制蓝牙模块,通过Micro-USB线连接电脑,打开串口助手,手机端使用蓝牙APP软件与模块连接。


图5 蓝牙开发板


图6 部分AT指令列表

如图4在程序运行之后,首先在串口输出的是模块的MAC地址,手机连接之后输出已连接信息,在第三行,收到手机端发来的Hello字符串。接着,我们通过串口向模块发送AT指令,AT+BLESEND=10,0x4D696E644D6F74696F6E,在手机端收到发来的字符串”Mindmotion”如图5,最后断开连接,串口输出状态。


图7 模块串口输出


图8 手机端收发数据

对于简单的应用,连接上VDD、GND、TX和RX这四根线之后就可以为项目增加蓝牙通信功能。


图9 QFN32典型应用电路

用户如果有需要重新设计PCB,可以参照上面的原理图,自行设计合适的通信模块,更好地应用在不同的项目中。

来源: 灵动MM32MCU

围观 2

本章节将与大家一起使用CRC模块进行数据校验。

在数据传输过程中,无论传输系统的设计再怎么完美,差错总会存在,这种差错可能会导致在链路上传输的一个或者多个帧被破坏(出现比特差错,0变为1,或者1变为0),从而接受方接收到错误的数据。为尽量提高接受方收到数据的正确率,在接受方接收数据之前需要对数据进行差错检测,当且仅当检测的结果为正确时接收方才真正收下数据。检测的方式有多种,常见的有奇偶校验、因特网校验和循环冗余校验等。

其中循环冗余校验(CRC)原理实际上就是在一个p位二进制数据序列之后附加一个r位二进制检验码(序列),从而构成一个总长为n=p+r位的二进制序列;附加在数据序列之后的这个检验码与数据序列的内容之间存在着某种特定的关系。如果因干扰等原因使数据序列中的某一位或某些位发生错误,这种特定关系就会被破坏。因此,通过检查这一关系,就可以实现对数据正确性的检验。只要经过严格的挑选,并使用位数足够多的除数 P,那么出现检测不到的差错的概率就很小很小。CRC是一种常用的检错码,无法检测出错误在哪里,因此并不能用于自动纠错,一般的做法是丢弃接收的数据。

从上面的程序框图,我们可以发现,多项式的幂次越高,校验效果越好,但所花费的时间也越长。因此常用查表法或专用的硬件CRC模块来提高效率。

其中查表法,是事先根据特定的校验多项式,算出1字节数据范围所对应的256个余数,将其作为表格,编程写到程序存储器中查询而避免在线运算,已是非常通用的做法。但如果是CRC16校验,存储表格要占512字节(CRC32则需要1 KB),对于有限的单片机ROM资源来说所占比例不小,往往只因为多装了此表,就不得不升级单片机的型号。

在SPIN2x系列MCU中,加入了一个CRC计算单元,使用1个32位数据寄存器作为输入和输出,在执行写操作时输入CRC计算的新数据,在执行读操作时返回上一次CRC计算的结果。每一次写入数据寄存器,都会对整个32位字进行CRC计算,而不是逐字节地计算,从而节省大量的时间。

• 使用CRC-32(以太网)多项式: 0x4C11DB7
• 每次CRC计算需要4个AHB时钟周期
• 在 CRC 计算期间会暂停写操作,因此可以对寄存器 CRC_DR 进行背靠背写入或者连续地写-读操作。
• 可以通过设置寄存器CRC_CTRL的RESET位来重置寄存器 CRC_DR 为 0xFFFFFFFF,该操作不影响8位独立数据寄存器CRC_IDR内的数据。


图2 CRC计算单元框图

下面我们来看一下在程序中软件CRC与硬件CRC的配置。

CRC模块的配置步骤如下:

• CRC模块时钟使能
• CRC_CR的第一位RESET位复位(可选)
• 将数据写入CRC_DR寄存器
• 从CRC_DR寄存器中读出计算结果

程序中配置如下:

uint32_t Hardware_CRC(u32*addr, int num) 

{

CRC->CR|=1; //复位

for (; num > 0; num--)             

CRC->DR = (*addr++);

return CRC->DR;

}

我们可以使用软件算法来检验计算结果,对比两种方式花费的时间。

软件算法如下:

u32 Software_CRC (u32 *ptr,u32 len) 

{ 

u32 xbit; 

u32 data; 

u32 CRC32 = 0xFFFFFFFF; 

u32 bits; 

const u32 dwPolynomial =0x04C11DB7 ; 

u32 i; 

             

for(i = 0;i >= 1; 

} 

} 

return CRC32; 

}

下面我们用两个简单的数组,一个数组从0x00递增到0x7F,另一个数组从0x7F递减到0x00,分别使用硬件CRC和软件CRC计算,同时使用TIM1进行计时,最后通过UART输出得到的校验码和花费的时间。

计算和输出程序:

void CRCTest()

{

unsigned int i;      

u32 CRCtime,CRCresault;

u32 crc1[128];

for(i=0;iCR|=1; //复位

printf("\r\nCRC_DR=%x\t\r\n",CRC->DR);//输出复位值

      

TIM1->CNT &= 0;

CRCresault=Hardware_CRC(crc1,128);

CRCtime =TIM1->CNT;

printf("Hardware_CRC1:resault=%08x\ttime=%d\r\n",CRCresault,CRCtime);

             

TIM1->CNT &= 0;

CRCresault=Software_CRC(crc1,128);

CRCtime =TIM1->CNT;

printf("Software_CRC1:resault=%08x\ttime=%d\r\n",CRCresault,CRCtime);

             

for(i=0;iCNT &= 0;

CRCresault=Hardware_CRC(crc1,128);

CRCtime =TIM1->CNT;

printf("Hardware_CRC2:resault=%08x\ttime=%d\r\n",CRCresault,CRCtime);

             

TIM1->CNT &= 0;

CRCresault=Software_CRC(crc1,128);

CRCtime =TIM1->CNT;

printf("Software_CRC2:resault=%08x\ttime=%d\r\n",CRCresault,CRCtime);

}

定时器配置程序:

void Tim1_UPCount_test(u16Prescaler,u16 Period)

{

TIM_TimeBaseInitTypeDefTIM_StructInit;

 

/*使能TIM1时钟,默认时钟源为PCLK2(PCLK2未分频时不倍频,否则由PCLK2倍频输出),可选其它时钟源*/

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);

 

TIM_StructInit.TIM_Period=Period;                                         //ARR寄存器值

TIM_StructInit.TIM_Prescaler=Prescaler;                                 //预分频值

/*数字滤波器采样频率,不影响定时器时钟*/

TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;                 //采样分频值

TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;       //计数模式

TIM_StructInit.TIM_RepetitionCounter=0;

TIM_TimeBaseInit(TIM1,&TIM_StructInit);

 

TIM_Cmd(TIM1, ENABLE);  

/*更新定时器时会产生更新时间,清除标志位*/

TIM_ClearFlag(TIM1,TIM_FLAG_Update);

}

Main函数:

int main(void)

{ 

Tim1_UPCount_test(48-1,0xffff); //APB2时钟为48M,48分频后TIM1时钟为1MHz

delay_init();

uart_initwBaudRate(9600);    //初始化UART

CRCTest();

while(1) { }

}


图3 软件CRC与硬件CRC计算结果

从结果中,我们可以看到,对于这两个数组,硬件CRC与软件CRC所得到的结果一致,但是硬件CRC每个数组只花费了52us,远小于软件CRC的2.5ms,由此可见,在进行大量数据处理的时候,使用硬件CRC模块可以节省大量的时间,同时保证了计算结果的正确。

来源:灵动MM32MCU

围观 3

本章节将通过使用采集红外测距模块测量阻挡物的距离与大家一起学习配置ADC和DMA模块。

ADC介绍与配置

ADC,Analog-to-Digital Converter的缩写,指模数转换器,是一种将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。ADC可以实现这个功能,不同的应用对ADC的精度有着不同的要求。

在MM32SPIN27系列芯片中,内嵌 2 个 12 位的模拟/数字转换器 (ADC),每个 ADC 可用多达 16 个外部通道,可以满足大部分产品需求。


图1 ADC框图

MM32SPIN27中的12 位 ADC 是逐次逼近式的模拟-数字转换器 (SAR A/D 转换器),拥有高达 1Msps 转换速率,可以软件配置通道采样时间和分辨率。每个通道拥有独立的数据寄存器和一个共用的数据寄存器。

MM32SPIN27的ADC支持多种工作模式,包括单次转换模式、单周期扫描模式和连续扫描模式。扫描模式中可以选择通道扫描的顺序,选择采样顺序从高到低或从低到高。

A/D转换的启动方式有软件设定、外部引脚触发以及各个定时器启动。在触发信号产生后,ADC最多可以延时512个PLCK2时钟周期再开始采样。

在比较模式下提供了上限和下限两个比较寄存器。可通过软件设定 CMPCH 位选择监控通道。

ADC 的输入时钟由 PCLK2 经分频产生,通过正确配置ADC_ADCFG寄存器的ADCPRE位和ADCPRE位,可以自由地配置2分频到17分频。注意,输入时钟不得超过15MHz。

下面是一个简单的ADC配置函数:

void ADCInit(void)

{

ADC_InitTypeDef ADC_InitStructure;      

ADC_StructInit(&ADC_InitStructure);

GPIO_InitTypeDef GPIO_InitStructure;

 

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);                         

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);         

 

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_2;                                

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;                          

GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;                              

GPIO_Init(GPIOA, &GPIO_InitStructure);

                                 

ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;   //12位分辨率

ADC_InitStructure.ADC_PRESCARE =ADC_PCLK2_PRESCARE_8;//96M时钟8分频

ADC_InitStructure.ADC_Mode =ADC_Mode_Continuous_Scan;//连续扫描模式

ADC_InitStructure.ADC_DataAlign =ADC_DataAlign_Right;   //数据对齐方式,右对齐     

ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_T1_CC1;

//选择触发源,需要ADC_ExternalTrigConvCmd函数开启外部触发模式        

ADC_Init(ADC1, &ADC_InitStructure);

   

ADC_RegularChannelConfig(ADC1,ADC_Channel_DisableAll, 0, ADC_SampleTime_13_5Cycles);

ADC_RegularChannelConfig(ADC1, ADC_Channel_2,0, ADC_SampleTime_239_5Cycles);

//仅开启ADC1的通道2,采样时间为239.5个周期

ADC_DMACmd(ADC1,ENABLE);   //启用DMA传输功能                                                  

ADC_Cmd(ADC1, ENABLE);                                                        

}

这样就把PA2配置成了ADC1通道2的输入,每次转换完成后,数据将会保存在独立寄存器ADC1->ADDR2和共用寄存器ADC1->ADDATA中,同时ADC将会发出DMA传输请求。

下面我们来配置DMA传输。

DMA介绍与配置

DMA(DirectMemory Access,直接内存存取)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须 CPU 任何干预,通过 DMA 数据可以快速地移动。这就节省了 CPU 的资源来做其他操作。

MM32SPIN27的DMA 控制器有 5个通道,每个通道都直接连接专用的硬件 DMA 请求(具体可以在用户手册中有描述),每个通道都同样支持软件触发。每个通道都有 3个事件标志:DMA 半传输, DMA 传输完成和 DMA 传输出错。

在5个通道的请求之间的优先权可以通过软件编程设置 (共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定 (请求 0 优先于请求 1,依此类推)。


图2 DMA功能框图

下面我们来配置DMA将ADC采集的数据传输到数组中:

void DMAInit(void)

{

DMA_InitTypeDef DMA_InitStructure;

NVIC_InitTypeDef NVIC_InitStruct;

   

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

DMA_DeInit(DMA1_Channel1);

      

DMA_InitStructure.DMA_PeripheralBaseAddr =(u32)&(ADC1->ADDATA);//外设地址

DMA_InitStructure.DMA_MemoryBaseAddr =(u32)&ADCValue;     //内存地址

DMA_InitStructure.DMA_DIR =DMA_DIR_PeripheralSRC;             //方向

DMA_InitStructure.DMA_BufferSize = 20;                                     //传输数量

DMA_InitStructure.DMA_PeripheralInc =DMA_PeripheralInc_Disable;//外设地址固定

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址自增

DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_HalfWord;

DMA_InitStructure.DMA_MemoryDataSize =DMA_MemoryDataSize_HalfWord;

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                 //循环传输

DMA_InitStructure.DMA_Priority =DMA_Priority_High;                 //传输优先级

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                   //外设与内存交互

DMA_Init(DMA1_Channel1,&DMA_InitStructure);

DMA_Cmd(DMA1_Channel1, ENABLE);

   

//NVIC中断设定

DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);                              

NVIC_InitStruct.NVIC_IRQChannel =DMA1_Channel1_IRQn;

NVIC_InitStruct.NVIC_IRQChannelPriority = 0;

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStruct);

}

void DMA1_Channel1_IRQHandler(void)

{

ADC_SoftwareStartConvCmd(ADC1, DISABLE);

//DMA传输20个数据完成,标志置1

DMA_ClearITPendingBit(DMA1_IT_TC1);                                        

ADCflag = 1;                                                                

}

配置完成后,DMA控制器收到ADC产生的请求后,将会传输一次数据到ADCValue数组中,并执行一次 DMA_CNDTRx 寄存器的递减操作和内存地址自动增量操作,直到传输完成20个数据,关闭ADC转换,并将标志ADCflag置1。

接下来就需要根据不同的应用来处理数据了。

简单应用:2Y0A21YK0F红外测距模块

2Y0A21YK0F是一个有效距离为10cm~80cm的红外测距模块,由PSD(位置敏感探测器)、IRED(红外发射二极管)和信号处理电路组成。采用三角测距法,目标反射率、环境温度和工作时间的变化对距离检测影响较小,在V0输出与检测距离相对应的电压。


图3 2Y0A21YK0F红外测距模块


图4 输出电压与距离倒数的关系

根据图4中给出的关系,将ADC中的数据进行滤波和计算,就可以得到所需的距离信息了。

程序配置:

void ADCFilter(void)                                                           

{

static u16 cntFilter;

static u32 lADCFilterValue = 0;

for(cntFilter = 0; cntFilter 

程序运行结果:


图5 程序运行结果

连接实验板与红外测距模块,编译下载运行程序,按照模块说明,使用一本白色封面的书本,接近再远离模块,通过不断变化距离,我们可以从UART串口看到程序将计算得到的结果(图5)。在这个过程中,ADC将模拟信号转换成数字信号,通过DMA传输到内存中,经过MCU的处理,最终得到了一个相对准确的距离数据。

来源: 灵动MM32MCU

围观 2

页面

订阅 RSS - MCU