基于涂鸦智能开发的墨水屏座位管理器——2.嵌入式功能实现篇
Posted 三明治开发社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于涂鸦智能开发的墨水屏座位管理器——2.嵌入式功能实现篇相关的知识,希望对你有一定的参考价值。
随着互联网连接技术的日益普及,以及大众环保意识增强,电子纸显示市场不断发展,墨水屏的应用场景也越来越多。墨水屏座位管理器方案具体功耗低,多节点管控,信息实时同步等特点,可应用于智慧办公,智慧零售,智慧商超等众多场景。
上篇给大家介绍硬件方案搭建墨水屏座位管理器-电路设计篇本篇将会从方案设计及功能实现等维度带大家了解墨水屏座位管理器嵌入式方案。
一、平台产品创建
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串口协议文档中深入了解;
注意配网之前必须先在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[<以上是关于基于涂鸦智能开发的墨水屏座位管理器——2.嵌入式功能实现篇的主要内容,如果未能解决你的问题,请参考以下文章
《安富莱嵌入式周报》第277期:业界首款Cortex-M55+Ethos-U55 NPU套件发布,20个墨水屏菊花链玩法,氙气灯镇流器设计