随着互联网连接技术的日益普及,以及大众环保意识增强,电子纸显示市场不断发展,墨水屏的应用场景也越来越多。墨水屏座位管理器方案具体功耗低,多节点管控,信息实时同步等特点,可应用于智慧办公,智慧零售,智慧商超等众多场景。
上篇给大家介绍硬件方案搭建墨水屏座位管理器-电路设计篇本篇将会从方案设计及功能实现等维度带大家了解墨水屏座位管理器嵌入式方案。
硬件部分搭建完成后可登录涂鸦IoT平台创建产品,下面是创建墨水屏demo的流程步骤。
登录涂鸦IoT平台,单击创建产品
在标准类目导航栏中,选择电工 >智能开关 >开关
在硬件开发页面中,开发者根据需求选择对应zigbee模组或蓝牙模组, 需注意的是要选择低功耗固件,否则会对设备功耗有很大影响。
本次方案采用涂鸦MCU SDK+Zigbee模组的设计来进行开发,下面是搭建开发环境的流程步骤。
ST开发环境安装
官方下载并安装keil,完成后开始搭建开发工程。
MCU SDK获取
在IoT平台创建完产品后就可以获取到MCU SDK软件包了,将MCU SDK添加到开发工程中,单击 Build 并根据软件提示修改相关错误或者警告信息。
移植了MCU-SDK后,再搭配一块烧录并授权了通用对接低功耗固件的zigbee或蓝牙模组,此时 MCU 就具备了连接涂鸦云和上报下发DP点的功能。
待程序编译通过之后,就可以下载到开发板中进行调试和测试。开发者可以先使用开发包中的串口调试助手分别模拟MCU和模拟模组来验证二者是否正常工作或通信。调试模组助手使用方法可以参考下面文档。
调试模组助手
完成上述步骤后正式开始墨水屏demo的应用功能开发。
功能描述 | 详细说明 |
---|---|
屏幕显示 | 1.座位号 2.座位状态 3.提醒信息 4.二维码 |
复位按键 | 长按3秒,设备重新配网 |
信息下发 | 正常:预定/无预定 异常:“暂不开放” |
预约信息记录 | 后台记录近30天座位占用信息 |
低电量报警 | 设备电池电量低于10%上报至管理端(云端) |
├── User
│ ├── main.c /* 主程序入口文件 */
│ └── MY_ST_config.h /* 硬件配置文件 */
├── system /* 系统文件目录 */
│ ├── delay.c /* 延时函数 */
│ ├── delay.h
│ ├── EPAPER.c /* 电子屏幕初始化 */
│ ├── EPAPER.h
│ ├── GT5SLAD3BFA_stm32l431_keil5.lib /* 字库芯片静态库文件 */
│ ├── GT5SLAD3B-GTA6401.h /* 字库芯片头文件 */
│ ├── IO.c /* GPIO口初始化 */
│ ├── IO.h
│ ├── key.c /* 按键初始化 */
│ ├── key.h
│ ├── picture.h /* 图片像素数据存储 */
│ ├── RCC.c /* 系统时钟配置 */
│ ├── RCC.h
│ ├── SPI.c /* SPI初始化 */
│ ├── SPI.h
│ ├── sys.c /* 系统任务文件 */
│ ├── sys.h
│ ├── qrcode_create.c /* 二维码组件 */
│ ├── qrcode_create.h
│ ├── tuya_qrcode_create.c
│ ├── tuya_qrcode_create.h
│ ├── USART.c /* 串口初始化 */
│ ├── USART.h
│ ├── utf8ToUnicode.c /* UTF8转UNICODE */
│ └── utf8ToUnicode.h
├── CJSON
│ ├── cJSON.c /* JSON配置文件 */
│ └── cJSON.h
├── mcu_sdk
│ ├── mcu_api.c /* dp功能数据文件 */
│ ├── mcu_api.h
│ ├── protocol.c /* 协议分析和接收模块发送消息时的响应 */
│ ├── protocol.h
│ ├── system.c /* 单片机与zigbee通信的框架分析 */
│ ├── system.h
│ └── zigbee.h /* SDK中使用的宏定义 */
首先是硬件部分的配置,本次硬件部分主要在于墨水屏幕的显示和字库芯片的读取,字库芯片与屏使用同一块SPI驱动,所以首先将SPI进行初始化配置,另外MCU与zigbee模组通过串口进行通信,所以也要将串口进行初始化;
//SPI
void SPI_Init(void)
{
SPI_SCK_OUT;
SPI_MOSI_OUT;
SPI_MISO_IN;
LIB_CS_OUT;
EPD_CS_OUT;
EPD_DC_OUT;
EPD_RST_OUT;
EPD_BUSY_IN;
}
//UART2 通信串口
void Configure_USART_ZIGBEE(uint32_t bound) //PA2 MTX , PA3 MRX
{
RCC->APB1RSTR1 &=~(1<<17);
RCC->AHB2ENR |= 1<<0;
GPIOA->MODER &=~(3<<4|3<<6);
GPIOA->MODER |=2<<4|2<<6;
GPIOA->AFR[0] &=~(0xf<<8|0xf<<12);
GPIOA->AFR[0] |=7<<8|7<<12;
RCC->APB1ENR1 |=1<<17;
USART_ZIGBEE->BRR = 16000000 / bound;
USART_ZIGBEE->CR1 |= 1<<0|1<<2|1<<3|1<<5;
NVIC_SetPriority(USART2_IRQn, 1);
NVIC_EnableIRQ(USART2_IRQn);
}
//USART3 log串口
void Configure_USART_LOG(uint32_t bound) //PB10 TXD PB11 RXD
{
RCC->APB1RSTR1 &=~(1<<18); //恢复串口3
RCC->AHB2ENR |= 1<<1; //使能GPIOB时钟
GPIOB->MODER &=~(3<<20|3<<22);
GPIOB->MODER |=2<<20|2<<22;
GPIOB->AFR[1] &=~(0xf<<8|0xf<<12);
GPIOB->AFR[1] |=7<<8|7<<12;
RCC->APB1ENR1 |=1<<18; //使能串口3时钟
USART_LOG->BRR = 16000000 / bound;
USART_LOG->CR1 |= 1<<0|1<<2|1<<3|1<<5;
while((USART_LOG->ISR & 1<<6) != 1<<6)
{
break;/* add time out here for a robust application */
}
NVIC_SetPriority(USART3_IRQn, 1);
NVIC_EnableIRQ(USART3_IRQn);
}
下面是电子屏的显示部分,屏幕初始化完成后可以直接在主程序中调用显示函数,另外如果SPI初始化有问题可以将模拟SPI改为硬件SPI再进行驱动 ;
屏幕显示也可以显示图片,采用取模软件将图片里的像素点数据放进picture.h文件中就可以显示图片了,要注意输出图片大小不能超过屏幕的尺寸大小,屏幕显示完成后调用局刷与全刷函数可以实现屏幕的局部刷新与全局刷新;需要注意屏幕每次刷新完后需要进入休眠模式,否则会对设备的整机功耗有很大影响;
屏幕刷新函数(全刷/局刷)
全屏刷新:整个页面全部刷新一次,整个屏幕要闪几次。 优势是没有残影,缺点是要多刷几下屏。
局部刷新:每一次刷新显示内容时,不会整个屏幕都刷新,仅刷新那些有画面和字的地方。优势是屏幕不会闪烁,但会有残影。(残影问题多刷几次白屏就能清除掉或者执行一次全刷也可以清掉)
在实现墨水屏的全局刷新与局部刷新功能时, 从局刷转到全刷时休眠后一定要先进入初始化再刷新。
// refresh_mode = Full 全屏刷新
// refresh_mode = Partial 局部刷新
void EPD_HW_Init(const unsigned char refresh_mode)
{
EPD_W21_Init();
Epaper_READBUSY();
Epaper_Write_Command(0x12);
Epaper_READBUSY();
Epaper_Write_Command(0x01);
Epaper_Write_Data(0x2B);
Epaper_Write_Data(0x01);
Epaper_Write_Data(0x00);
Epaper_Write_Command(0x11);
Epaper_Write_Data(0x03);
Epaper_Write_Command(0x44);
Epaper_Write_Data(0x00);
Epaper_Write_Data(0x31);
Epaper_Write_Command(0x45);
Epaper_Write_Data(0x00);
Epaper_Write_Data(0x00);
Epaper_Write_Data(0x2B);
Epaper_Write_Data(0x01);
Epaper_Write_Command(0x3C);
Epaper_Write_Data(0x01);
Epaper_Write_Command(0x18);
Epaper_Write_Data(0x80);
Epaper_Write_Command(0x22);
if(refresh_mode==Full)
Epaper_Write_Data(0xB1);
if(refresh_mode==Partial)
Epaper_Write_Data(0xB9);
Epaper_Write_Command(0x20);
Epaper_READBUSY();
Epaper_Write_Command(0x4E);
Epaper_Write_Data(0x00);
Epaper_Write_Command(0x4F);
Epaper_Write_Data(0x00);
Epaper_Write_Data(0x00);
Epaper_READBUSY();
}
//mode==POS , 正显
//mode==NEG , 负显
//mode==OFF , 清除
void EPD_Dis_Part(unsigned int xstart,unsigned int ystart,const unsigned char * datas,unsigned int PART_LINE,unsigned int PART_COLUMN,unsigned char mode)
{
unsigned int i;
int xend,ystart_H,ystart_L,yend,yend_H,yend_L;
xstart=xstart/8; //转换为字节
xend=xstart+PART_LINE/8-1;
ystart_H=ystart/256;
ystart_L=ystart%256;
yend=ystart+PART_COLUMN-1;
yend_H=yend/256;
yend_L=yend%256;
Epaper_Write_Command(0x44); // set RAM x address start/end
Epaper_Write_Data(xstart); // RAM x address start;
Epaper_Write_Data(xend); // RAM x address end
Epaper_Write_Command(0x45); // set RAM y address start/end
Epaper_Write_Data(ystart_L); // RAM y address start Low
Epaper_Write_Data(ystart_H); // RAM y address start High
Epaper_Write_Data(yend_L); // RAM y address end Low
Epaper_Write_Data(yend_H); // RAM y address end High
Epaper_Write_Command(0x4E); // set RAM x address count
Epaper_Write_Data(xstart);
Epaper_Write_Command(0x4F); // set RAM y address count
Epaper_Write_Data(ystart_L);
Epaper_Write_Data(ystart_H);
Epaper_Write_Command(0x24); //Write Black and White image to RAM
for(i=0;i<PART_COLUMN*PART_LINE/8;i++)
{
if (mode==POS)
{
Epaper_Write_Data(*datas);
datas++;
}
if (mode==NEG)
{
Epaper_Write_Data(~*datas);
datas++;
}
if (mode==OFF)
{
Epaper_Write_Data(0xFF);
}
}
}
void EPD_WhiteScreen_White(void)
{
unsigned int k;
Epaper_Write_Command(0x24); //write RAM for black(0)/white (1) 36
for(k=0;k<ALLSCREEN_GRAGHBYTES;k++)
{
Epaper_Write_Data(0xff);
}
Epaper_Write_Command(0x26); //write RAM for black(0)/white (1)
for(k=0;k<ALLSCREEN_GRAGHBYTES;k++)
{
Epaper_Write_Data(0xff);
}
EPD_Update();
}
当需要清掉局部信息并重新显示新的信息时就可以用硬件复位函数来实现;
void EPD_W21_Init(void)
{
EPD_W21_RST_0;
driver_delay_xms(100);
EPD_W21_RST_1; //hard reset
driver_delay_xms(100);
}
void EPD_DeepSleep(void)
{
Epaper_Write_Command(0x10); //enter deep sleep
Epaper_Write_Data(0x01);
driver_delay_xms(100);
}
注意屏幕刷新完后必须进入休眠模式。
在使用字库芯片时,用户只要知道字符的内码,就可以计算出该字符点阵在芯片中的地址,然后就可从该地址连续读出点阵信息用于显示。本次屏幕显示信息主要通过从字库芯片中读取数据从而显示在屏幕上,从字库中读到的数据一个字符就是一个数组数据,然后电子屏再将每一位数组数据显示在屏幕上。
void zk_init(void)
{
Rom_csH;
MOSIH;
Rom_sckH;
}
void GT5S_DeepSleep(void)
{
Rom_csL;
Send_Byte(0xB9);
Rom_csH;
delay_us(40);
}
字库芯片唤醒
以低电平作为起始位,高电平作为停止位;
void GT5S_Wakeup(void)
{
Rom_csL;
Send_Byte(0xAB);
Rom_csH;
delay_us(40);
}
矢量文字读取函数
调用方式:通过指定参数进行调用,获取点阵数据到pBits[]数组中
/*
*函数名 get_font()
*功能 矢量文字读取函数
*参数:pBits 数据存储
* sty 文字字体选择 @矢量公用部分
* fontCode 字符编码中文:GB18030, ASCII/外文: unicode
* width 文字宽度
* height 文字高度
* thick 文字粗细
*返回值:文字显示高度
**/
unsigned int get_font(unsigned char *pBits,unsigned char sty,unsigned long fontCode,unsigned char width,unsigned char height, unsigned char thick);
如果有需要固定显示的文字部分,可在程序中将固定文字信息写在数组中从而直接显示固定信息,一个ASCII码对应2位GBK内码占一个字节,一个中文字符对应4位GBK内码占两个字节;
unsigned char jtwb[128]="共享空间,预定优先";
unsigned char state[128]="暂不开放";
unsigned char pBits[512];
将固定信息通过文字读取函数get_font()与屏幕显示函数EPD_Dis_Part()进一步显示在墨水屏上;
void SEAT_SET(void)
{
EPD_HW_Init(Partial);
zk_init();
GT5S_Wakeup();
get_font(pBits,VEC_SONG_STY,(jtwb[0]<<8)+jtwb[1],24,24,24); //共
EPD_Dis_Part(196,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[2]<<8)+jtwb[3],24,24,24); //享
EPD_Dis_Part(220,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[4]<<8)+jtwb[5],24,24,24); //空
EPD_Dis_Part(244,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[6]<<8)+jtwb[7],24,24,24); //间
EPD_Dis_Part(268,55,pBits,24,24,NEG);
EPD_Part_Update();
EPD_DeepSleep();
GT5S_DeepSleep();
}
由于墨水屏需要连接到涂鸦智能平台,依靠平台来实现自动化、App端操控、以及设备之间的相互联动,所以需要有一个zigbee网关来帮助设备连接上智能平台。zigbee网关的作用就是负责连接智能平台,间接的把zigbee设备接入我们的智能平台,确保手机和zigbee网关处于同一个Wi-Fi网络,以保证手机与智能网关之间的有效连接。
将网关与电源连接,并通过网线与家庭2.4GHz频段路由器相连;
确认配网指示灯(绿灯)常亮(若指示灯处于其他状态,长按"复位键",至绿灯常亮);
确保手机连接家庭2.4GHz频段路由器,此时手机、网关处于同一个局域网;
打开涂鸦智能App首页,点击页面右上角添加按钮“+”;
选择网关中控/有线网关(zigbee),依照提示操作设备入网;
添加成功后,即可在列表中找到网关设备;
zigbee网关配网成功后,就可以在网关里添加子设备了,依据涂鸦智能App首页配网提示操作子设备配网,配网成功后就可以在涂鸦智能App上进行调试了;
除了在程序里写入配网指令实现配网还可以通过涂鸦调试助手发送配网指令来操作设备入网,详细说明可以从zigbee串口协议文档中深入了解;
zigbee串口协议
注意配网之前必须先在IoT平台将dp添加完成,否则配网会一直失败;
定义一个结构体数组来存放MCU接收到网关下发或上报的dp value,
TYPE_BUFFER_S FlashBuffer;
typedef struct {
uint8_t seat_set[255]; //座位信息
uint8_t st_qrcode[255]; //座位二维码链接地址
uint8_t st_qrcode1[255]; //第一条座位二维码链接地址
uint8_t st_qrcode2[255]; //第二条座位二维码链接地址
uint8_t st_add[255]; //增量预约信息起始时间
uint8_t et_add[255]; //增量预约信息终止时间
uint8_t n_add[255]; //增量预约者姓名
uint8_t st_all[255]; //全量预约信息起始时间
uint8_t et_all[255]; //全量预约信息终止时间
uint8_t n_all[255]; //全量预约者姓名
uint8_t low_power; //低电量数值
} TYPE_BUFFER_S;
调试过程可使用调试面板来下发dp,MCU收到指令后回复并执行对应的操作。
首先下发dp101 ,dp数据格式为{“n”:“seat number”,“st”:“state”}
dpid | 内容 | 备注 |
---|---|---|
101 | “query” | 设备发送,同步座位号信息 |
格式 | {“n”:“seat number”,“st”:“state”} | n:座位编码字段; seat number:编码内容 st:座位状态字段 ;state:座位真实状态 enable disable repairing |
下发dp后MCU接收并解析出座位编码、座位状态等字段信息,然后驱动屏幕将座位编号信息显示在墨水屏上;
定义一个结构体,用来表示墨水屏的三种工作状态:
typedef enum
{
enable, //使用中
disable, //未开放
reparing //维修中
}state;
dp解析及处理函数:
/*****************************************************************************
函数名称 : dp_download_seat_set_handle
功能描述 : 针对DPID_SEAT_SET的处理函数
输入参数 : value:数据源数据
: length:数据长度
返回参数 : 成功返回:SUCCESS/失败返回:ERROR
使用说明 : 可下发可上报类型,需要在处理完数据后上报处理结果至app
*****************************************************************************/
static unsigned char dp_download_seat_set_handle(const unsigned char value[], unsigned short length)
{
//示例:当前DP类型为STRING
unsigned char ret;
unsigned char dp_download_seat_set_handle = NULL;
cJSON *root = NULL, *item = NULL, *item1 = NULL;
char *number = NULL;
char *state = NULL;
char *pstr = NULL;
pstr = (char*)malloc(length+1);
if(NULL == pstr){
printf("malloc err\r\n");
return ERROR;
}
my_memset(pstr, 0x00, length+1);
my_memcpy(pstr, value, length);
printf("value: %s\r\n", value);
root = cJSON_Parse(pstr);
free(pstr), pstr = NULL;
if(NULL == root){
printf("root ERROR\r\n");
return ERROR;
}
item = cJSON_GetObjectItem(root, "n"); //获取座位编码字段
if(NULL == item){
printf("item ERROR\r\n");
}
number = (char*)malloc(sizeof(item));
if(NULL == number){
printf("malloc number err\r\n");
return ERROR;
}
free(number), number = NULL;
number = item->valuestring;
printf("number: %s\r\n", number);
memcpy(FlashBuffer.seat_set,number,strlen(number));
item1 = cJSON_GetObjectItem(root, "st"); //获取座位实际状态
if(NULL == item1){
printf("item1 ERROR\r\n");
return ERROR;
}
state = (char*)malloc(sizeof(item1));
if(NULL == state){
printf("malloc state err\r\n");
return ERROR;
}
free(state), state = NULL;
state = item1->valuestring;
printf("state: %s\r\n", state);
if(0 == strcmp(state, "enable")){ //使用中
SEAT_SET(); //显示座位详细信息
}
else if(0 == strcmp(state, "disable")){ //未开放
SEAT_CLOSE(); //显示“暂不开放”
}
else if(0 == strcmp(state, "repairing")){ //维修中
SEAT_CLOSE(); //显示“暂不开放”
}
cJSON_Delete(root);
return 0;
//处理完DP数据后应有反馈
ret = mcu_dp_string_update(DPID_SEAT_SET,value, length);
if(ret == SUCCESS)
{
Enter_stop_mode(); //处理完dp后设备需进入休眠模式
return SUCCESS;
}
else
return ERROR;
}
以座位S-1234为例,当座位状态为enable时才可以执行预约的动作,如果是disable 和 repairing 则显示该座位暂不开放(可根据自己需求修改);
座位状态 | dp格式 | 显示内容 |
---|---|---|
enable | {“n”:“S-1234”,“st”:"enable "} | 显示座位编号,若有预约信息则显示预约信息,无预约信息显示空白 |
disable | {“n”:“S-1234”,“st”:"disable "} | 显示座位编号以及“暂不开放” |
repairing | {“n”:“S-1234”,“st”:"repairing "} | 显示座位编号以及“暂不开放” |
MCU接收到value后将解析出的信息存放在 FlashBuffer.seat_set[]中并通过修改显示函数EPD_Dis_Part()中的横纵坐标来调整在屏幕中的位置,注意显示函数完成后屏幕和字库芯片都需要进入休眠;
座位编号显示函数:
void SEAT_SET(void)
{
EPD_HW_Init(Partial); //屏幕局刷初始化
zk_init(); //字库芯片初始化
GT5S_Wakeup(); //字库芯片唤醒
get_font(pBits,VEC_FT_ASCII_STY, FlashBuffer.seat_set[0],48,48,48); //下发信息
EPD_Dis_Part(8,45,pBits,48,48,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.seat_set[1],48,48,48);
EPD_Dis_Part(40,45,pBits,48,48,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.seat_set[2],48,48,48);
EPD_Dis_Part(64,45,pBits,48,48,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.seat_set[3],48,48,48);
EPD_Dis_Part(88,45,pBits,48,48,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.seat_set[4],48,48,48);
EPD_Dis_Part(114,45,pBits,48,48,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.seat_set[5],48,48,48);
EPD_Dis_Part(136,45,pBits,48,48,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.seat_set[6],48,48,48);
EPD_Dis_Part(160,45,pBits,48,48,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[0]<<8)+jtwb[1],24,24,24); //固定信息
EPD_Dis_Part(196,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[2]<<8)+jtwb[3],24,24,24);
EPD_Dis_Part(220,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[4]<<8)+jtwb[5],24,24,24);
EPD_Dis_Part(244,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[6]<<8)+jtwb[7],24,24,24);
EPD_Dis_Part(268,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[8]<<8)+jtwb[9],24,24,24);
EPD_Dis_Part(292,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[10]<<8)+jtwb[11],24,24,24);
EPD_Dis_Part(310,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[12]<<8)+jtwb[13],24,24,24);
EPD_Dis_Part(330,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[14]<<8)+jtwb[15],24,24,24);
EPD_Dis_Part(354,55,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,(jtwb[16]<<8)+jtwb[17],24,24,24);
EPD_Dis_Part(380,55,pBits,24,24,NEG);
EPD_Part_Update();
EPD_DeepSleep(); //屏幕休眠
GT5S_DeepSleep(); //字库芯片睡眠
}
可通过调试面板下发dp102生成对应座位二维码信息,dp存放字节长度最大为62字节,若url超过了最大限长,可将二维码链接分为两段dp来下发;这里以长度为80的url为例,单条dp可以不固定长度,但不能超过限长,开发者可以按照自身的长度自行分配分两段还是三段。
dpid | 内容 | 备注 |
---|---|---|
102 | “query” | 设备发送,同步座位链接信息 |
{“qr”:“url”,“d”:“th”,“a”:“all”} | qr:二维码链接字段;url:座位链接长度不超过255 设备通过url本地生成二维码 d:二维码条目字段 th:第几段二维码链接信息 a:二维码字段总量 all:共有几段二维码 |
示例:座位1111 url地址为https://seat-reservation-mobile.tuyacn.com/weapp?a=1&seat_id=1435857125099298816
故dp格式为 {“qr”:“https://seat-reservation-mobile.tuyacn.com”,“d”:“1”,“a”:“2”}
{“qr”:"/weapp?a=1&seat_id=1435857125099298816",“d”:“2”,“a”:“2”}
下发dp102后MCU接收并解析出url并存放在FlashBuffer.st_qrcode中,进一步调用二维码生成函数将座位二维码显示在墨水屏上;
dp解析及处理函数:
/*****************************************************************************
函数名称 : dp_download_st_qrcode_handle
功能描述 : 针对DPID_ST_QRCODE的处理函数
输入参数 : value:数据源数据
: length:数据长度
返回参数 : 成功返回:SUCCESS/失败返回:ERROR
使用说明 : 可下发可上报类型,需要在处理完数据后上报处理结果至app
*****************************************************************************/
static unsigned char dp_download_st_qrcode_handle(const unsigned char value[], unsigned short length)
{
//示例:当前DP类型为STRING
unsigned char ret;
unsigned char dp_download_st_qrcode_handle = NULL;
cJSON *root = NULL, *item_1 = NULL, *item_2 = NULL, *item_3 = NULL;
char *qrcode = NULL;
char *d = NULL;
char *a = NULL;
char *pstr = NULL;
pstr = (char*)malloc(length+1);
if(NULL == pstr){
printf("malloc error\r\n");
return ERROR;
}
my_memset(pstr, 0x00, length+1);
my_memcpy(pstr, value, length);
printf("value: %s\r\n", value);
root = cJSON_Parse(pstr);
free(pstr), pstr = NULL;
if(NULL == root){
printf("root ERROR!!!!!!!!!!!!!");
return ERROR;
}
//qr URL字段
item_1 = cJSON_GetObjectItem(root, "qr");
if(NULL == item_1){
printf("item_1 ERROR\r\n");
}
qrcode = (char*)malloc(sizeof(item_1));
if(NULL == qrcode){
printf("malloc qrcode err\r\n");
return ERROR;
}
free(qrcode), qrcode = NULL;
qrcode = item_1->valuestring;
printf("qrcode: %s\r\n", qrcode);
//d url次序
item_2 = cJSON_GetObjectItem(root, "d");
if(NULL == item_2){
return ERROR;
}
d = (char*)malloc(sizeof(item_2));
if(NULL == d){
printf("malloc d err\r\n");
return ERROR;
}
free(d), d = NULL;
d = item_2->valuestring;
printf("d: %s\r\n", d);
if(0 == strcmp(d, "1")){ //第一段url
memcpy(FlashBuffer.st_qrcode1,qrcode,strlen(qrcode));
printf("FlashBuffer.st_qrcode1: %s\r\n", FlashBuffer.st_qrcode1);
}
else if(0 == strcmp(d, "2")){ //第二段url
memcpy(FlashBuffer.st_qrcode2,qrcode,strlen(qrcode));
printf("FlashBuffer.st_qrcode2: %s\r\n", FlashBuffer.st_qrcode2);
strcat(FlashBuffer.st_qrcode1,FlashBuffer.st_qrcode2);
printf("FlashBuffer.st_qrcode1: %s\r\n", FlashBuffer.st_qrcode1);
memset(FlashBuffer.st_qrcode, 0x00, length+1);
memcpy(FlashBuffer.st_qrcode,FlashBuffer.st_qrcode1,strlen(FlashBuffer.st_qrcode1));
qrcod_test(); //二维码显示函数
}
//a
item_3 = cJSON_GetObjectItem(root, "a");
if(NULL == item_3) {
return ERROR;
}
a = item_3->valuestring;
cJSON_Delete(root);
root = NULL;
return 0;
//处理完DP数据后应有反馈
ret = mcu_dp_string_update(DPID_ST_QRCODE,value, length);
if(ret == SUCCESS)
{
Enter_stop_mode(); //处理完后设备进入低功耗模式
return SUCCESS;
}
else
return ERROR;
}
二维码显示函数:
TY_CREATE_IN_T Qrcod_CREATE_IN;
TY_CREATE_OUT_T *Qrcod_CREATE_OUT;
void qrcod_test(void)
{
Qrcod_CREATE_IN.ecc_level=QRCODE_ECC_MEDIUM;
Qrcod_CREATE_IN.version=9; //版本为9 53*53
Qrcod_CREATE_IN.magnifications=2; //放大倍数2
Qrcod_CREATE_IN.mode=STORAGE_MODE_BIT;
Qrcod_CREATE_IN.information=FlashBuffer.st_qrcode; //url存放位置
tuya_svc_image_generate_qrcode_create(&Qrcod_CREATE_IN,&Qrcod_CREATE_OUT);
EPD_HW_Init(Partial);
EPD_Dis_Part2(288,150,Qrcod_CREATE_OUT->dst_data,112,106); //显示坐标位置
EPD_Part_Update();
EPD_DeepSleep();
tuya_svc_image_generate_qrcode_create_free(Qrcod_CREATE_OUT);
}
开发者可以根据自己需求修改url信息,另外如果url前半部分固定不变的话可以将其写在程序中,通过dp只下发后半部分url,可以简化很多步骤;
dpid | 内容 | 备注 |
---|---|---|
103 | {“st”:time,“et”:time,“n”:“name”,“t”:“type”,“d”:“th”} | st:预约起始时间字段 et:预约结束时间字段 time:时间 n:预约者名字字段 name:名字内容 t:类型字段 type:add:增加 del:删除 d:第几条预约信息 |
dp103是预约信息的更新,更新类型包括新增与删除,更新的前提是设备已经从服务端同步过才有更新;
例如当前显示两条预约信息,当需要更新第一条预约信息时先下发更新dp,并且第一条更新完成的同时需将第二条信息删除,下面为更新与删除的举例;
类型 | 举例 |
---|---|
新增 | {“st”:“10:00”,“et”:“12:00”,“n”:“无施”,“t”:“add”,“d”:“1”} |
删除 | {“st”:“10:00”,“et”:“12:00”,“n”:“无施”,“t”:“del”,“d”:“2”} |
dp解析及处理函数:
/*****************************************************************************
函数名称 : dp_download_st_add_handle
功能描述 : 针对DPID_ST_ADD的处理函数
输入参数 : value:数据源数据
: length:数据长度
返回参数 : 成功返回:SUCCESS/失败返回:ERROR
使用说明 : 只下发类型,需要在处理完数据后上报处理结果至app
*****************************************************************************/
static unsigned char dp_download_st_add_handle(const unsigned char value[], unsigned short length)
{
//示例:当前DP类型为STRING
unsigned char ret;
unsigned char dp_download_st_add_handle = NULL;
my_memset(FlashBuffer.st_add, 0x00, length+1);
memcpy(FlashBuffer.st_add, value,length*sizeof(unsigned char));
cJSON *root = NULL, *item = NULL, *item1 = NULL, *item2 = NULL, *item3 = NULL,*item4 = NULL;
char* st = NULL; //预约起始时间
char* et = NULL; //预约终止时间
char* n = NULL; //预约者姓名
char* t = NULL; //预约类型 : add/del
char* d = NULL; //预约信息次序
char *pstr = NULL;
pstr = (char*)malloc(length+1);
printf("pstr: %s\r\n", pstr);
if(NULL == pstr){
printf("malloc err\r\n");
return ERROR;
}
my_memset(pstr, 0x00, length+1);
my_memcpy(pstr, value, length);
root = cJSON_Parse(pstr);
free(pstr), pstr = NULL;
if(NULL == root){
printf("ERROR!!!!!!!!!!!!!");
return ERROR;
}
//st 预约起始时间
item = cJSON_GetObjectItem(root, "st");
if(NULL == item){
printf("item ERROR\r\n");
}
st = item->valuestring;
printf("st: %s\r\n", st);
memcpy(FlashBuffer.st_add,st,strlen(st));
//et 预约终止时间
item1 = cJSON_GetObjectItem(root, "et");
if(NULL == item1){
printf("item1 ERROR\r\n");
}
et = item1->valuestring;
printf("et: %s\r\n", et);
memcpy(FlashBuffer.et_add,et,strlen(et));
//n 预约者姓名
item2 = cJSON_GetObjectItem(root, "n");
if(NULL == item2){
}
n = item2->valuestring;
printf("n: %s\r\n", n);
memcpy(FlashBuffer.n_add,n,strlen(n));
//t 预约类型 : add/del
item3 = cJSON_GetObjectItem(root, "t");
if(NULL == item3){
return ERROR;
}
t = item3->valuestring;
printf("t: %s\r\n", t);
//d 预约信息次序
item4 = cJSON_GetObjectItem(root, "d");
if(NULL == item4){
return ERROR;
}
d = item4->valuestring;
printf("d: %s\r\n", d);
if(0 == strcmp(t, "add")) //新增
{
if(0 == strcmp(d, "1")) //新增第一条信息
ST_ADD1();
else if(0 == strcmp(d, "2")) //新增第二条信息
ST_ADD2();
else
ST_ADD3(); //新增第三条信息
}
else
{
if(0 == strcmp(t, "del")) //删除
{
if(0 == strcmp(d, "1"))
ST_DEL1(); //删除第一条信息
else if(0 == strcmp(d, "2"))
ST_DEL2(); //删除第二条信息
else
ST_DEL3(); //删除第三条信息
}
}
cJSON_Delete(root);
return 0;
//处理完DP数据后应有反馈
ret = mcu_dp_string_update(DPID_ST_ADD,value, length);
if(ret == SUCCESS)
{
Enter_stop_mode(); //处理完后设备进入低功耗模式
return SUCCESS;
}
else
return ERROR;;
}
MCU接收到value后将解析出的起始时间存放在 FlashBuffer.st_add,将终止时间存放在 FlashBuffer.et_add,姓名字段存放在gbk_n中,并通过修改显示函数EPD_Dis_Part()中的横纵坐标来调整在屏幕中的位置。
void ST_ADD1(void)
{
EPD_SetRAMValue_BaseMap(pBits); //保留背景信息
EPD_HW_Init(Partial);
zk_init();
GT5S_Wakeup();
memset(pBits,0,sizeof(pBits));
memcpy(pBits, pBits, sizeof(pBits));
unsigned long gbk_n[30];
utf8ToGBK(FlashBuffer.n_add, gbk_n, 30); //GBK转换
//time
get_font(pBits,VEC_FT_ASCII_STY, FlashBuffer.st_add[0],24,24,24); //st
EPD_Dis_Part(0,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_add[1],24,24,24);
EPD_Dis_Part(16,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_add[2],24,24,24);
EPD_Dis_Part(32,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_add[3],24,24,24);
EPD_Dis_Part(44,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_add[4],24,24,24);
EPD_Dis_Part(60,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,0X2D,24,24,24);
EPD_Dis_Part(80,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[0],24,24,24); //et
EPD_Dis_Part(96,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[1],24,24,24);
EPD_Dis_Part(112,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[2],24,24,24);
EPD_Dis_Part(130,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[3],24,24,24);
EPD_Dis_Part(140,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[4],24,24,24);
EPD_Dis_Part(153,150,pBits,24,24,NEG);
//name
get_font(pBits,VEC_SONG_STY,gbk_n[0],24,24,24);
EPD_Dis_Part(176,150,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,gbk_n[1],24,24,24);
EPD_Dis_Part(200,150,pBits,24,24,NEG);
EPD_Part_Update();
EPD_DeepSleep();
GT5S_DeepSleep();
}
void ST_DEL1(void)
{
EPD_SetRAMValue_BaseMap(pBits);
EPD_HW_Init(Partial);
zk_init();
GT5S_Wakeup();
memcpy(pBits, pBits, sizeof(pBits));
unsigned long gbk_n[30];
utf8ToGBK(FlashBuffer.n_add, gbk_n, 30);
get_font(pBits,VEC_FT_ASCII_STY, FlashBuffer.st_add[0],24,24,24); //st
EPD_Dis_Part(0,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_add[1],24,24,24);
EPD_Dis_Part(16,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_add[2],24,24,24);
EPD_Dis_Part(32,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_add[3],24,24,24);
EPD_Dis_Part(44,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_add[4],24,24,24);
EPD_Dis_Part(60,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,0x2D,24,24,24); //-
EPD_Dis_Part(80,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[0],24,24,24); //et
EPD_Dis_Part(96,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[1],24,24,24);
EPD_Dis_Part(112,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[2],24,24,24);
EPD_Dis_Part(130,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[3],24,24,24);
EPD_Dis_Part(140,150,pBits,24,24,OFF);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_add[4],24,24,24);
EPD_Dis_Part(153,150,pBits,24,24,OFF);
get_font(pBits,VEC_SONG_STY,gbk_n[0],24,24,24);
EPD_Dis_Part(176,150,pBits,24,24,OFF);
get_font(pBits,VEC_SONG_STY,gbk_n[1],24,24,24);
EPD_Dis_Part(200,150,pBits,24,24,OFF);
EPD_Part_Update();
EPD_DeepSleep();
GT5S_DeepSleep();
}
dpid | 内容 | 备注 |
---|---|---|
104 | “query” | 设备发送,同步预约信息(全量) |
{“st”:time,“et”:time,“n”:“name”,“d”:“th”,“a”:“all”} | st:预约起始时间字段 et:预约结束时间字段 time:时间 n:预约者姓名字段 name:姓名 d:单条预约信息 a:预约信息总量 |
dp104是预约信息的同步,设备上电后同步所有数据,若查询到几条预约信息就对应显示几条预约信息,如查询到有三条信息则依次三条预约信息,下发格式
预约信息全量 | 举例 |
---|---|
第一条 | {“st”:“10:00”,“et”:“12:30”,“n”:“一一”,”d”:”1”,”a”:”3”} |
第二条 | {“st”:“14:10”,“et”:“16:30”,“n”:“一二”,”d”:”2”,”a”:”3”} |
第三条 | {“st”:“18:30”,“et”:“20:00”,“n”:“一三”,”d”:”3”,”a”:”3”} |
/*****************************************************************************
函数名称 : dp_download_st_all_handle
功能描述 : 针对DPID_ST_ALL的处理函数
输入参数 : value:数据源数据
: length:数据长度
返回参数 : 成功返回:SUCCESS/失败返回:ERROR
使用说明 : 可下发可上报类型,需要在处理完数据后上报处理结果至app
*****************************************************************************/
static unsigned char dp_download_st_all_handle(const unsigned char value[], unsigned short length)
{
//示例:当前DP类型为STRING
unsigned char ret;
unsigned char dp_download_st_all_handle = NULL;
memset(FlashBuffer.st_all, 0x00, length+1);
memcpy(FlashBuffer.st_all, value,length*sizeof(unsigned char));
cJSON *root = NULL, *item_1 = NULL, *item_2 = NULL, *item_3 = NULL, *item_4 = NULL, *item_5 = NULL;
char *st = NULL;
char *et = NULL;
char *n = NULL;
char *d = NULL;
char *a = NULL;
char *pstr = NULL;
pstr = (char*)malloc(length+1);
printf("pstr: %s\r\n", pstr);
if(NULL == pstr){
printf("malloc error\r\n");
return ERROR;
}
my_memset(pstr, 0x00, length+1);
my_memcpy(pstr, value, length);
printf("pstr: %s\r\n", pstr);
my_memset(FlashBuffer.st_all, 0x00, length+1);
my_memcpy(FlashBuffer.st_all, value, length);
root = cJSON_Parse(pstr);
free(pstr), pstr = NULL;
if(NULL == root){
printf("root ERROR!!!!!!!!!!!!!");
return ERROR;
}
//st
item_1 = cJSON_GetObjectItem(root, "st");
if(NULL == item_1){
printf("item_1 ERROR\r\n");
}
st = item_1->valuestring;
printf("st: %s\r\n", st);
memcpy(FlashBuffer.st_all,st,strlen(st));
//et
item_2 = cJSON_GetObjectItem(root, "et");
if(NULL == item_2){
printf("item_2 ERROR\r\n");
}
et = item_2->valuestring;
printf("et: %s\r\n", et);
memcpy(FlashBuffer.et_all,et,strlen(et));
//n
item_3 = cJSON_GetObjectItem(root, "n");
if(NULL == item_3){
return ERROR;
}
n = item_3->valuestring;
printf("n: %s\r\n", n);
memcpy(FlashBuffer.n_all,n,strlen(n));
//d
item_4 = cJSON_GetObjectItem(root, "d");
if(NULL == item_4){
return ERROR;
}
d = item_4->valuestring;
printf("d: %s\r\n", d);
if(0 == strcmp(d, "1")){ //第一条预约信息全量
ST_ALL1();
}
else if(0 == strcmp(d, "2")){ //第二条预约信息全量
ST_ALL2();
}
else if(0 == strcmp(d, "3")){ //第三条预约信息全量
ST_ALL3();
}
//a
item_5 = cJSON_GetObjectItem(root, "a");
if(NULL == item_5) {
return ERROR;
}
a = item_5->valuestring;
printf("a: %s\r\n", a);
cJSON_Delete(root), root = NULL;
return 0;
//处理完DP数据后应有反馈
ret = mcu_dp_string_update(DPID_ST_ALL,value, length);
if(ret == SUCCESS)
{
Enter_stop_mode(); //进入低功耗模式
return SUCCESS;
}
else
return ERROR;
}
MCU接收到value后将解析出的起始时间存放在 FlashBuffer.st_all,将终止时间存放在 FlashBuffer.et_all,姓名字段存放在gbk_1中,并通过修改显示函数EPD_Dis_Part()中的横纵坐标来调整在屏幕中的位置。
void ST_ALL1(void)
{
EPD_HW_Init(Partial);
zk_init();
GT5S_Wakeup();
unsigned long gbk_1[30];
utf8ToGBK(FlashBuffer.n_all, gbk_1, 30);
//time
get_font(pBits,VEC_FT_ASCII_STY, FlashBuffer.st_all[0],24,24,24);
EPD_Dis_Part(0,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_all[1],24,24,24);
EPD_Dis_Part(16,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_all[2],24,24,24);
EPD_Dis_Part(32,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_all[3],24,24,24);
EPD_Dis_Part(44,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.st_all[4],24,24,24);
EPD_Dis_Part(60,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,0X2D,24,24,24);
EPD_Dis_Part(80,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_all[0],24,24,24);
EPD_Dis_Part(96,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_all[1],24,24,24);
EPD_Dis_Part(112,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_all[2],24,24,24);
EPD_Dis_Part(130,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_all[3],24,24,24);
EPD_Dis_Part(140,150,pBits,24,24,NEG);
get_font(pBits,VEC_FT_ASCII_STY,FlashBuffer.et_all[4],24,24,24);
EPD_Dis_Part(153,150,pBits,24,24,NEG);
//name
get_font(pBits,VEC_SONG_STY,gbk_1[0],24,24,24);
EPD_Dis_Part(176,150,pBits,24,24,NEG);
get_font(pBits,VEC_SONG_STY,gbk_1[1],24,24,24);
EPD_Dis_Part(200,150,pBits,24,24,NEG);
EPD_Part_Update();
EPD_DeepSleep();
GT5S_DeepSleep();
}
当设备电池电量低于10%时将低电量状态上报至后台端,由于电池的电量和电压不是线性关系,若要通过电压来做电量指示的话需要多做几组充电放电的曲线测试,程序中也需要分三种情况处理:空载时、负载时、以及充电时将三种情况分别表示出来,用电压做电量指示才会更准确一些。
本次设计在计量电池电量时采用设备功耗来计算电池电量,如设备休眠状态(80ms)电流10uA,接收状态(600us)5mA,则可以算出平均电流,已知电池总电量,就可以计算出设备可工作时间。当电池电量低于10%,上报至后台端。
MCU与模组唤醒逻辑如下:
TX/RX:通讯串口
IO1:zigbee唤醒MCU引脚,拉低唤醒,电平持续时间10ms以上。
IO2:MCU唤醒Zigbee引脚,拉低唤醒,电平持续时间10ms以上。
MCU与模组唤醒的引脚为PB4 PB5,对应MCU的PB0,PC13
zigebee模组烧录的固件为低功耗固件,可通过配置zigbee网络策略参数来调节模组的功耗,以下为zigbee模组的常用参数
zigbee网络策略参数 | 默认值 |
---|---|
心跳时间 | 17000 |
配网时间 | 180 |
poll时间 | 5000 |
上电持续poll | 30 |
poll失败次数 | 4 |
应用触发rejion | 1 |
rejion间隔 | 180 |
rejion次数 | 1 |
发送功率 | 11 |
下面是对各个参数的详细描述,
心跳时间:是用来维护设备和网关之间的数据链路是否正常的手段,强电设备的心跳时间默认为150+random(30)秒,低功耗设备的心跳时间默认为 4 小时,设置范围为10~5*3600秒,且网关判定 12 小时内没有收到心跳则认为设备离线。
配网时间:当MCU发送配网指令之后,模组会执行一段时间的配网操作,并发送当前网络状态为配网状态。在一段时间内由于某些原因(例如附近没有开启配网的网络或者距离较远)导致模组没有加入到合适网络,则配网超时。配网超时之后,模组将处于未配网状态,同时也会将此状态发送给 MCU。配网超时时间默认为180秒,设置范围为30~600秒。
poll时间(唤醒周期):poll 时间是指已经加入到网络的低功耗模组会在周期内唤醒,唤醒之后低功耗模组会发送数据请求(Data request)至其父节点,用于告知父节点,其当前处于唤醒状态,父节点是否为其缓存数据。如果存在缓存数据,则父节点可以将数据发送给低功耗模组。
注:Poll 值主要是影响功耗。唤醒周期越短,功耗越大。Poll 最小值为 200ms,小于最小值按照最小值处理。建议取值小于等于 8s,这里poll时间默认为5000ms,设置范围为200~10000ms。
上电持续poll:通常上电之后,设置一段时间的快速 Poll,可以在这个时间窗内将网关的配置命令下发。上电之后的快速 Poll 的时间默认为 30 秒,设置范围为30~600s。
poll失败次数:低功耗模块发送Data request之后,父节点首先是需要回复 ack,该ack是对Data request的应答。如果有缓存数据则将数据发送给模块,如果没有数据发送,则仅需回复ack。如果模块发送了Data request,但由于环境、距离、父节点断电等因素导致模块没有收到ack,则模块的poll失败次数会加1,如果在累加的过程中重新收到父节点的ack,则累加清零,当累加到的一定的值时(Poll失败次数),认为模块丢失父节点,需要触发rejion。默认值为4次,设置范围为3~40次。
rejoin(重连):即重新加入到网络,但无须网关开启配网模式,是一种专门用于低功耗设备在父节点丢失时,重新加入网络的一种机制。
rejoin间隔: 周期触发rejion的间隔时间。低功耗模块触发rejion之后,发送beacon request的周期间隔。默认值为180s,即当设备丢失父节点时,会间隔180秒尝试rejion。
rejoin次数:低功耗模块触发rejion之后,发送beacon request的次数,默认值为1,可设置范围为1~10次。
发送功率:发送功率越大,功耗越高,默认值为11dB,可设置范围为3~19dB。
以上几个参数为模组较常用的几个参数,模组网络参数调节完之后测出模组休眠状态功耗约为2.4uA,以上参数满足低功耗要求故开发者无须再次修改调节。
MCU有多种低功耗模式:睡眠模式、低功耗运行模式、低功耗睡眠模式、停止模式、待机模式。
待机模式电流最低,但是待机模式时的MCU处于不受控制的状态,所有的IO口都工作在高阻抗的状态下,只有专门的几个引脚能将MCU唤醒,而每次唤醒后相当于系统复位,RAM中的数据全部丢失,在外部器件连接的情况下,器件的引脚可能会吸收大量的电流,反而达不到低功耗的要求。
停止模式的功耗仅次于待机模式。在STOP模式下,PLL,HSL,HSE都被停止,RAM和寄存器的值保留。
本次方案采用STOP2模式为功耗最低的睡眠模式,且STOP2模式只能从运行模式进入,开发者可根据自身应用场景选择相应的模式,下面是MCU在几种模式下的功耗大小,
低功耗模式 | 电流消耗 | 唤醒时间 |
---|---|---|
STOP0 | 100 µA | 0.7 µs |
STOP1 | 4.6 µA | 4 µs |
STOP2 | 2.7µA | 5 µs |
下面为MCU进入低功耗模式的流程步骤
1)把所有开启的外设先失能,再把引脚设为模拟模式,最后关闭外设时钟;
2)进入STOP2模式,调用低功耗函数;可配置为中断唤醒或事件唤醒,通过__WFI()或__**WFE()**选择唤醒模式;
void Enter_stop_mode(void)
{
RCC->APB1ENR1|=1<<28; //PWR电源接口使能
RCC->APB1RSTR1|=1<<17; //复位所以IO口
RCC->APB1RSTR1|=1<<18;
RCC->AHB2ENR|=0<<1; //禁止GPIO B
RCC->AHB2ENR|=0<<2; //禁止GPIO C
RCC->APB1ENR1|= 0<<14;
SPI_MISO_SLEEP;
EPD_BUSY_SLEEP;
//PB0作为外部中断
Exit_IO4();
//进入STOP2模式
__WFI(); //Wait For Interrupt
}
1)先恢复时钟配置
2)恢复外设状态,如GPIO、串口、SPI等
3)在需要进入STOP模式的地方直接调用函数
//退出STOP2后设置
void Exit_stop_mode(void)
{
SystemClock_Config();
IO_Init();
RCC->AHB2ENR|=1<<1;
RCC->AHB2ENR|=1<<2;
printf("exit stop\r\n");
}
void Exit_IO4(void)
{
PA4_IN; //PA4唤醒引脚
EXTI->IMR1|=1<<4; //中断请求未屏蔽
EXTI->RTSR1|=1<<4; //line4 上升沿
NVIC_SetPriority(EXTI4_IRQn, 1);
NVIC_EnableIRQ(EXTI4_IRQn);
EXTI->PR1 = 1<<4; //清除中断标志位
}
注意在调试低功耗时芯片有时会进入休眠自锁状态出现无法下载的情况,可按住开发板的复位键不放开再点下载程序,过1~2秒后放开按键即可烧写成功;
设备整机搭建如下图:
OP模式下,PLL,HSL,HSE都被停止,RAM和寄存器的值保留。
本次方案采用STOP2模式为功耗最低的睡眠模式,且STOP2模式只能从运行模式进入,开发者可根据自身应用场景选择相应的模式,下面是MCU在几种模式下的功耗大小,
低功耗模式 | 电流消耗 | 唤醒时间 |
---|---|---|
STOP0 | 100 µA | 0.7 µs |
STOP1 | 4.6 µA | 4 µs |
STOP2 | 2.7µA | 5 µs |
下面为MCU进入低功耗模式的流程步骤
1)把所有开启的外设先失能,再把引脚设为模拟模式,最后关闭外设时钟;
2)进入STOP2模式,调用低功耗函数;可配置为中断唤醒或事件唤醒,通过__WFI()或__**WFE()**选择唤醒模式;
void Enter_stop_mode(void)
{
RCC->APB1ENR1|=1<<28; //PWR电源接口使能
RCC->APB1RSTR1|=1<<17; //复位所以IO口
RCC->APB1RSTR1|=1<<18;
RCC->AHB2ENR|=0<<1; //禁止GPIO B
RCC->AHB2ENR|=0<<2; //禁止GPIO C
RCC->APB1ENR1|= 0<<14;
SPI_MISO_SLEEP;
EPD_BUSY_SLEEP;
//PB0作为外部中断
Exit_IO4();
//进入STOP2模式
__WFI(); //Wait For Interrupt
}
1)先恢复时钟配置
2)恢复外设状态,如GPIO、串口、SPI等
3)在需要进入STOP模式的地方直接调用函数
//退出STOP2后设置
void Exit_stop_mode(void)
{
SystemClock_Config();
IO_Init();
RCC->AHB2ENR|=1<<1;
RCC->AHB2ENR|=1<<2;
printf("exit stop\r\n");
}
void Exit_IO4(void)
{
PA4_IN; //PA4唤醒引脚
EXTI->IMR1|=1<<4; //中断请求未屏蔽
EXTI->RTSR1|=1<<4; //line4 上升沿
NVIC_SetPriority(EXTI4_IRQn, 1);
NVIC_EnableIRQ(EXTI4_IRQn);
EXTI->PR1 = 1<<4; //清除中断标志位
}
注意在调试低功耗时芯片有时会进入休眠自锁状态出现无法下载的情况,可按住开发板的复位键不放开再点下载程序,过1~2秒后放开按键即可烧写成功。
以上就是墨水屏座位管理器嵌入式功能实现的所有内容,如果你这对墨水屏相关应用场景实践感兴趣,或者在开发过程中遇到任何问题,欢迎留言交流。