隨著互聯網連接技術的日益普及,以及大眾環保意識增強,電子紙顯示市場不斷發展,墨水屏的應用場景也越來越多,墨水屏座位管理器方案具體功耗低,多節點管控,資訊實時同步等特點,可應用于智慧辦公,智慧零售,智慧商超等眾多場景,
上篇給大家介紹硬體方案搭建墨水屏座位管理器-電路設計篇本篇將會從方案設計及功能實作等維度帶大家了解墨水屏座位管理器嵌入式方案,
一、平臺產品創建
1.創建產品
硬體部分搭建完成后可登錄涂鴉IoT平臺創建產品,下面是創建墨水屏demo的流程步驟,
-
登錄涂鴉IoT平臺,單擊創建產品
-
在標準類目導航欄中,選擇電工 >智能開關 >開關
- 選擇自定義方案,輸入產品名稱如墨水屏demo,可選擇zigbee或藍牙通訊協議,本方案以zigbee方案,單擊創建產品

-
(可選)在功能定義選項中,可以先選擇一項標準功能開關,方便后續除錯,

-
(必選)在自定義功能區域單擊添加功能,
例如本次方案需要添加五個 DP 點,按照如下類別依次新建5個自定義功能點,

-
在設備面板頁面中選擇想要的面板,開始除錯時可選擇開發除錯面板,后面可根據自身需要自由配置面板,

-
在硬體開發頁面中,開發者根據需求選擇對應zigbee模組或藍牙模組, 需注意的是要選擇低功耗韌體,否則會對設備功耗有很大影響,
- 上述操作完成之后,單擊下載全部下載所有開發需要用到的資料,如MCU SDK、模組除錯助手和和功能點除錯檔案等,接下來就可以開始嵌入式程式的開發,

2.環境搭建
本次方案采用涂鴉MCU SDK+Zigbee模組的設計來進行開發,下面是搭建開發環境的流程步驟,
-
ST開發環境安裝
官方下載并安裝keil,完成后開始搭建開發工程,
-
MCU SDK獲取
在IoT平臺創建完產品后就可以獲取到MCU SDK軟體包了,將MCU SDK添加到開發工程中,單擊 Build 并根據軟體提示修改相關錯誤或者警告資訊,
移植了MCU-SDK后,再搭配一塊燒錄并授權了通用對接低功耗韌體的zigbee或藍牙模組,此時 MCU 就具備了連接涂鴉云和上報下發DP點的功能,
待程式編譯通過之后,就可以下載到開發板中進行除錯和測驗,開發者可以先使用開發包中的串口除錯助手分別模擬MCU和模擬模組來驗證二者是否正常作業或通信,除錯模組助手使用方法可以參考下面檔案,
除錯模組助手
STM32 支持 ST-Link ,J-Link 等工具下載,這里推薦 ST-Link,引腳連接方式如下:

二、韌體開發
完成上述步驟后正式開始墨水屏demo的應用功能開發,
1.方案設計
-
功能需求
| 功能描述 | 詳細說明 |
|---|---|
| 螢屏顯示 | 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中使用的宏定義 */
2.硬體外設
首先是硬體部分的配置,本次硬體部分主要在于墨水螢屏的顯示和字庫芯片的讀取,字庫芯片與屏使用同一塊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);
}
3.墨水屏部分
下面是電子屏的顯示部分,螢屏初始化完成后可以直接在主程式中呼叫顯示函式,另外如果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);
}
注意螢屏重繪完后必須進入休眠模式,
4.字庫芯片
在使用字庫芯片時,用戶只要知道字符的內碼,就可以計算出該字符點陣在芯片中的地址,然后就可從該地址連續讀出點陣資訊用于顯示,本次螢屏顯示資訊主要通過從字庫芯片中讀取資料從而顯示在螢屏上,從字庫中讀到的資料一個字符就是一個陣列資料,然后電子屏再將每一位陣列資料顯示在螢屏上,
- 字庫芯片初始化
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();
}
5.設備配網
1)網關配網
由于墨水屏需要連接到涂鴉智能平臺,依靠平臺來實作自動化、App端操控、以及設備之間的相互聯動,所以需要有一個zigbee網關來幫助設備連接上智能平臺,zigbee網關的作用就是負責連接智能平臺,間接的把zigbee設備接入我們的智能平臺,確保手機和zigbee網關處于同一個Wi-Fi網路,以保證手機與智能網關之間的有效連接,
-
將網關與電源連接,并通過網線與家庭2.4GHz頻段路由器相連;
-
確認配網指示燈(綠燈)常亮(若指示燈處于其他狀態,長按"復位鍵",至綠燈常亮);
-
確保手機連接家庭2.4GHz頻段路由器,此時手機、網關處于同一個局域網;
-
打開涂鴉智能App首頁,點擊頁面右上角添加按鈕“+”;
-
選擇網關中控/有線網關(zigbee),依照提示操作設備入網;
-
添加成功后,即可在串列中找到網關設備;
2)zigbee模組配網
zigbee網關配網成功后,就可以在網關里添加子設備了,依據涂鴉智能App首頁配網提示操作子設備配網,配網成功后就可以在涂鴉智能App上進行除錯了;
除了在程式里寫入配網指令實作配網還可以通過涂鴉除錯助手發送配網指令來操作設備入網,詳細說明可以從zigbee串口協議檔案中深入了解;
zigbee串口協議
注意配網之前必須先在IoT平臺將dp添加完成,否則配網會一直失敗;
6.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;
1)座位資訊更新
除錯程序可使用除錯面板來下發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,可以簡化很多步驟;
2)預約資訊更新
| 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();
}
3)預約資訊同步
| 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決議及處理函式:
/*****************************************************************************
函式名稱 : 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();
}
4)電量報警
當設備電池電量低于10%時將低電量狀態上報至后臺端,由于電池的電量和電壓不是線性關系,若要通過電壓來做電量指示的話需要多做幾組充電放電的曲線測驗,程式中也需要分三種情況處理:空載時、負載時、以及充電時將三種情況分別表示出來,用電壓做電量指示才會更準確一些,
本次設計在計量電池電量時采用設備功耗來計算電池電量,如設備休眠狀態(80ms)電流10uA,接收狀態(600us)5mA,則可以算出平均電流,已知電池總電量,就可以計算出設備可作業時間,當電池電量低于10%,上報至后臺端,
7.功耗除錯
MCU與模組喚醒邏輯如下:

TX/RX:通訊串口
IO1:zigbee喚醒MCU引腳,拉低喚醒,電平持續時間10ms以上,
IO2:MCU喚醒Zigbee引腳,拉低喚醒,電平持續時間10ms以上,
MCU與模組喚醒的引腳為PB4 PB5,對應MCU的PB0,PC13

1)模組功耗除錯
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,以上引數滿足低功耗要求故開發者無須再次修改調節,
2)MCU功耗除錯
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秒后放開按鍵即可燒寫成功,
以上就是墨水屏座位管理器嵌入式功能實作的所有內容,如果你這對墨水屏相關應用場景實踐感興趣,或者在開發程序中遇到任何問題,歡迎留言交流,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/356830.html
標籤:其他
上一篇:推薦一款基于nodejs+koa+vue開發的開源智慧物業系統
下一篇:物聯網產品中服務器的重要性
