任务要求:
- 初始运行图显示当前日期(年、月、日)。
- 按压KEY4键,显示设置图,黑色三角表示当前设置项。KEY2键上移黑色三角设置项,KEY3键下移黑色三角设置项。
- 在设置图页面按压KEY4,在当前设置项开启*符号,板上的LED2灯点亮,表示当前设置项可修改,KEY2键设置项加1,KEY3键设置项减1。
- 修改设置项后,按压KEY4,关闭*符号,板上的LED2灯熄灭,保存当前值,返回设置图。
- 通过USB数据线,将NB-IOT智慧盒连接到开发机串口上,从开发机串口上发送以下16进制命令帧,NB-IOT接收后自动修改年、月、日参数,并统一返回成功:0xFB 0x00 0xFE或失败:0xFB 0x01 0xFE。
- 注:2021年拆分为20(年1),21(年2)两部分。
- 在设置图状态下,按压KEY1复位键返回初始运行图,此时显示新设置的日期。
任务分析:
不难看出,这道题比起上一道ZigBee的题难度翻了N倍,所以隐约可以看出以后比赛中任务量与难度是NB、Lora>ZigBee的,所以平常可以针对性训练和了解一些相关知识。
在这道题中,我们要使用一块Nb-iot版,要开发屏幕,按键,串口三个部分,在比赛中,竞赛资料中已经有了很充足的代码工程给我们使用,所以在接下来的stm32开发任务中,我将完全使用比赛时的竞赛资料中的库文件完成开发。
这道题其实很简单,我们只要初始化相应模块,做好串口接收逻辑就可以了。
代码实现:
※这里使用library版通用工程LoRa_新库_发布。
首先,我们使用竞赛资料自带取模工具取出“当前日期年月日”的子模。工具设置如下图:
//汉字,顺序为当前日期年月日0123456
char zi[][32]={/*-- ??: ? --*/
/*-- ??12; ??????????:?x?=16x16 --*/
0x00,0x40,0x42,0x44,0x58,0x40,0x40,0x7F,0x40,0x40,0x50,0x48,0xC6,0x00,0x00,0x00,
0x00,0x40,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0xFF,0x00,0x00,0x00,
/*-- ??: ? --*/
/*-- ??12; ??????????:?x?=16x16 --*/
0x08,0x08,0xE8,0x29,0x2E,0x28,0xE8,0x08,0x08,0xC8,0x0C,0x0B,0xE8,0x08,0x08,0x00,
0x00,0x00,0xFF,0x09,0x49,0x89,0x7F,0x00,0x00,0x0F,0x40,0x80,0x7F,0x00,0x00,0x00,
/*-- ??: ? --*/
/*-- ??12; ??????????:?x?=16x16 --*/
0x00,0x00,0x00,0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0x82,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xFF,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xFF,0x00,0x00,0x00,0x00,
/*-- ??: ? --*/
/*-- ??12; ??????????:?x?=16x16 --*/
0x00,0x04,0xFF,0x24,0x24,0x24,0xFF,0x04,0x00,0xFE,0x22,0x22,0x22,0xFE,0x00,0x00,
0x88,0x48,0x2F,0x09,0x09,0x19,0xAF,0x48,0x30,0x0F,0x02,0x42,0x82,0x7F,0x00,0x00,
/*-- ??: ? --*/
/*-- ??12; ??????????:?x?=16x16 --*/
0x00,0x20,0x18,0xC7,0x44,0x44,0x44,0x44,0xFC,0x44,0x44,0x44,0x44,0x04,0x00,0x00,
0x04,0x04,0x04,0x07,0x04,0x04,0x04,0x04,0xFF,0x04,0x04,0x04,0x04,0x04,0x04,0x00,
/*-- ??: ? --*/
/*-- ??12; ??????????:?x?=16x16 --*/
0x00,0x00,0x00,0xFE,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0xFE,0x00,0x00,0x00,
0x80,0x40,0x30,0x0F,0x02,0x02,0x02,0x02,0x02,0x02,0x42,0x82,0x7F,0x00,0x00,0x00,
/*-- ??: ? --*/
/*-- ??12; ??????????:?x?=16x16 --*/
0x00,0x00,0x00,0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0x82,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xFF,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xFF,0x00,0x00,0x00,0x00,
}
汉字取模完毕后,按题目所说,我们要有年月日三个变量,其中年分为两部分,所以我们初步定义变量为
#define index 0 //主页
#define opt1 //设置页
unsigned int mode = 0;//显示页面
unsigned int year= 2021,mon = 10, day = 12;//年月日
然后对屏幕初始化,先做出基础默认界面。注意,在比赛设备中OLED 屏幕有以下应记:
//屏幕x为横轴,y为纵轴,y有8行,每个正常显示占2行,8px模式占1行
//每个汉字占用横轴15个点,为了清洗显示应加入多余的点占空分隔,
//x=18为第一个汉字,后面每个字的起始点都在上一个字的基础上+18
//调用Screen_ShowChineseChar函数显示汉字,Screen_ShowString函数显示英文字符
我们使用mode来确定当前是哪个页面,以下为主页代码
//年月日:
Screen_ShowChineseChar(0,0,zi[0]);
Screen_ShowChineseChar(0+18,0,zi[1]);
Screen_ShowChineseChar(0+18*2,0,zi[2]);
Screen_ShowChineseChar(0+18*3,0,zi[3]);
Screen_ShowString(18*4,0,": ");
//在当前版本的竞赛库中,并没有直接显示数字的函数,所以要创建一个临时temp,
//使用sprintf函数将type改变
char temp[16];
//xxx年
sprintf(temp,"%d",year);
Screen_ShowString(0,3,temp);
Screen_ShowChineseChar(18*2,3,zi[4]);
//xx月xx日
sprintf(temp,"%d",mon);
Screen_ShowString(0,6,temp);
Screen_ShowChineseChar(18,6,zi[5]);
sprintf(temp,"%d",day);
Screen_ShowString(18*2,6,temp);
Screen_ShowChineseChar(18*3,6,zi[6]);
目前为止,我们已经完成了第一步:初始运行图显示当前日期(年、月、日)。
接下来我们将进行安检功能控制的开发,根据题目分析得知,我们任然要进行一个界面的设计,
由于汉字我们已经取模过,所以要取模‘◀’,打不出这个符号的可以直接复制:
/*-- 文字: ▲ --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x00,0x00,0x00,0x80,0x80,0xC0,0xC0,0xE0,0xE0,0xF0,0xF0,0xF8,0xF8,0xFC,0x00,0x00,
0x00,0x01,0x01,0x03,0x03,0x07,0x07,0x0F,0x0F,0x1F,0x1F,0x3F,0x3F,0x7F,0x00,0x00,
符号取模完后,我们来编写界面布局代码
//年
Screen_ShowChineseChar(0,0,zi[4]);
Screen_ShowString(18,0,": ");
char temp[16];
sprintf(temp,"%d",year);
Screen_ShowString(18*2,0,temp);
//月
Screen_ShowChineseChar(0,3,zi[5]);
Screen_ShowString(18,3,": ");
sprintf(temp,"%d",mon);
Screen_ShowString(18*2,3,temp);
//日
Screen_ShowChineseChar(0,6,zi[6]);
Screen_ShowString(18,6,": ");
sprintf(temp,"%d",day);
Screen_ShowString(18*2,6,temp);
界面布局完成后,我们要做出相应的按键功能,这里我们使用两个变量,opt_cursor与is_ok,opt_cursor表示opt页面下三角符号对应哪一行,is_ok表示‘*’所在的行数,
//年
if(opt_cursor==0)
Screen_ShowChineseChar(18*4,0,zi[7]);
if(is_ok==0)
Screen_ShowString(18*6,0,"*");
//月
if(opt_cursor==1)
Screen_ShowChineseChar(18*4,3,zi[7]);
if(is_ok==1)
Screen_ShowString(18*6,3,"*");
//日
if(opt_cursor==1)
Screen_ShowChineseChar(18*4,3,zi[7]);
if(is_ok==1)
Screen_ShowString(18*6,6,"*");
在按键中,key4用来确定页面的切换及选择,但是因为我们各设了变量,所以具体代码逻辑非常简单
void key4_E()
{
if(mode == index) //如果是在主页就切换到设置页
mode = opt;
if(mode == opt)//如果是在设置页就确定选择了哪一项
{
is_ok = !is_ok;
if(is_ok)
Led_On(2);
else
Led_Off(2);
}
//更新屏幕
Screen_Clear();
LED_Disp();
}
在key2与key3中分别处理上移下移增加减少的选择
void key2_E()
{
//设置项选择
if(is_ok==0 && opt_cursor!=0)
opt_cursor-=1;
if(is_ok==1 && opt_cursor==0) //当前设置,年+1
year+=1;
if(is_ok==1 && opt_cursor==1) //当前设置,月+1
mon+=1;
if(is_ok==1 && opt_cursor==2) //当前设置,日+1
day+=1;
Screen_Clear();
LED_Disp();
}
void key3_E()
{
//设置项选择
if(is_ok==0 && opt_cursor!=2)
opt_cursor+=1;
if(is_ok==1 && opt_cursor==0) //当前设置,年-1
year-=1;
if(is_ok==1 && opt_cursor==1) //当前设置,月-1
mon-=1;
if(is_ok==1 && opt_cursor==2) //当前设置,日-1
day-=1;
Screen_Clear();
LED_Disp();
}
至此,我们已经完成了以下任务点,恭喜你,已经拿到了一半的任务分数。
- 初始运行图显示当前日期(年、月、日)。
- 按压KEY4键,显示设置图,黑色三角表示当前设置项。KEY2键上移黑色三角设置项,KEY3键下移黑色三角设置项。
- 在设置图页面按压KEY4,在当前设置项开启*符号,板上的LED2灯点亮,表示当前设置项可修改,KEY2键设置项加1,KEY3键设置项减1。
- 修改设置项后,按压KEY4,关闭*符号,板上的LED2灯熄灭,保存当前值,返回设置图。
按照题目要求,我们下一步编写串口收发相关代码,我们初始化串口并定义成功与失败的返回值,波特率按照题目要求来,没有就随意,建议使用115200。
Uart_Init(115200);//串口初始化,波特率115200
unsigned char fail = {0xfb,0x01,0xfe};//失败
unsigned char success = {0xfb,0x00,0xfe}; //成功
在初始化完成后,我们编写相应的逻辑代码,代码逻辑比较简单,就不一一写注释了,看不懂去补C基础去
1
然后到了相对来说比较重要的一点,flash闪存,因此,我们也需要对它进行相应的初始化,复位键不可编程,我这里使用数组的方式写入,但是在复位读取后数据任然是空,希望路过的各位大佬给予指点。
#include "lib_flash.h" //flash库
#define flash_add 0x0800f100//flash闪存地址
unsigned char flash_arr[3]={0x00,0x00,0x00};
Flash_WriteBuffer(flash_add,flash_arr,3);//储存数据
DelayMs(20);//5ms写入时间
Flash_ReadBuffer(flash_add,flash_arr,3);
year = flash_arr[0];
mon = flash_arr[1];
day = flash_arr[2];
“在设置图状态下,按压KEY1复位键返回初始运行图,此时显示新设置的日期。”
至此,完成了所有的任务代码,最后一个功能不能用,由于是新换的资料我第一次见所以有些问题,希望路过的各位大佬指指点点。
以下main.c源文件
/**
******************************************************************************
* File Name : main.c
* Description : Main program body
******************************************************************************
*/
#include <string.h>
#include "board.h"
//#include "hal_oled.h"
#include "lib_screen.h"
#include "lib_screen_font.h"
#include "lib_key.h"
#include "lib_uart.h"
#include "lib_timertask.h"
#include "lib_timer.h"
#include "lib_led.h"
#include "lib_flash.h"
#include "lib_relay.h"
#define flash_add 0x0800f100
#define index 0
#define opt 1
//当前日期年月日
char zi[][32]={
0x00,0x40,0x42,0x44,0x58,0x40,0x40,0x7F,0x40,0x40,0x50,0x48,0xC6,0x00,0x00,0x00,
0x00,0x40,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0xFF,0x00,0x00,0x00,
0x08,0x08,0xE8,0x29,0x2E,0x28,0xE8,0x08,0x08,0xC8,0x0C,0x0B,0xE8,0x08,0x08,0x00,
0x00,0x00,0xFF,0x09,0x49,0x89,0x7F,0x00,0x00,0x0F,0x40,0x80,0x7F,0x00,0x00,0x00,
0x00,0x00,0x00,0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0x82,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xFF,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xFF,0x00,0x00,0x00,0x00,
0x00,0x04,0xFF,0x24,0x24,0x24,0xFF,0x04,0x00,0xFE,0x22,0x22,0x22,0xFE,0x00,0x00,
0x88,0x48,0x2F,0x09,0x09,0x19,0xAF,0x48,0x30,0x0F,0x02,0x42,0x82,0x7F,0x00,0x00,
0x00,0x20,0x18,0xC7,0x44,0x44,0x44,0x44,0xFC,0x44,0x44,0x44,0x44,0x04,0x00,0x00,
0x04,0x04,0x04,0x07,0x04,0x04,0x04,0x04,0xFF,0x04,0x04,0x04,0x04,0x04,0x04,0x00,
0x00,0x00,0x00,0xFE,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0xFE,0x00,0x00,0x00,
0x80,0x40,0x30,0x0F,0x02,0x02,0x02,0x02,0x02,0x02,0x42,0x82,0x7F,0x00,0x00,0x00,
0x00,0x00,0x00,0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0x82,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xFF,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xFF,0x00,0x00,0x00,0x00,
/*-- 文字: ▲ --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x00,0x00,0x00,0x80,0x80,0xC0,0xC0,0xE0,0xE0,0xF0,0xF0,0xF8,0xF8,0xFC,0x00,0x00,
0x00,0x01,0x01,0x03,0x03,0x07,0x07,0x0F,0x0F,0x1F,0x1F,0x3F,0x3F,0x7F,0x00,0x00,
};
unsigned int mode = index,opt_cursor = 0,is_ok = 0;//显示页面,设置页光标
unsigned char fail[] = {0xfb,0x01,0xfe};//失败
unsigned char success[] = {0xfb,0x00,0xfe}; //成功
unsigned int year=2021,mon = 10, day = 12,uart_rx_len = 0;//年月日串口接收数
unsigned char uRxBuf[255],flash_arr[3]={0x00,0x00,0x00};
void LED_Disp()
{
if(mode==index)//默认显示
{
//屏幕x为横轴,y为纵轴,y有8行,每个正常显示占2行,8px模式占1行
//每个汉字占用横轴15个点,为了清洗显示应加入多余的点占空分隔,
//x=18为第一个汉字,后面每个字的起始点都在上一个字的基础上+18
//调用Screen_ShowChineseChar函数显示汉字,Screen_ShowString函数显示英文字符
Screen_ShowChineseChar(0,0,zi[0]);
Screen_ShowChineseChar(0+18,0,zi[1]);
Screen_ShowChineseChar(0+18*2,0,zi[2]);
Screen_ShowChineseChar(0+18*3,0,zi[3]);
Screen_ShowString(18*4,0,": ");
//在当前版本的竞赛库中,并没有直接显示数字的函数,所以要创建一个临时temp,
//使用sprintf函数将type改变
char temp[16];
sprintf(temp,"%d",year);
Screen_ShowString(0,3,temp);
Screen_ShowChineseChar(18*2,3,zi[4]);
sprintf(temp,"%d",mon);
Screen_ShowString(0,6,temp);
Screen_ShowChineseChar(18,6,zi[5]);
sprintf(temp,"%d",day);
Screen_ShowString(18*2,6,temp);
Screen_ShowChineseChar(18*3,6,zi[6]);
}
if(mode==opt)
{
Screen_ShowChineseChar(0,0,zi[4]);
Screen_ShowString(18,0,": ");
char temp[16];
sprintf(temp,"%d",year);
Screen_ShowString(18*2,0,temp);
if(opt_cursor==0)
Screen_ShowChineseChar(18*4,0,zi[7]);
if(is_ok==1 && opt_cursor==0)
Screen_ShowString(18*6,0,"*");
Screen_ShowChineseChar(0,3,zi[5]);
Screen_ShowString(18,3,": ");
sprintf(temp,"%d",mon);
Screen_ShowString(18*2,3,temp);
if(opt_cursor==1)
Screen_ShowChineseChar(18*4,3,zi[7]);
if(is_ok==1 && opt_cursor==1)
Screen_ShowString(18*6,3,"*");
Screen_ShowChineseChar(0,6,zi[6]);
Screen_ShowString(18,6,": ");
sprintf(temp,"%d",day);
Screen_ShowString(18*2,6,temp);
if(opt_cursor==2)
Screen_ShowChineseChar(18*4,6,zi[7]);
if(is_ok==1 && opt_cursor==2)
Screen_ShowString(18*6,6,"*");
}
}
void key2_E()
{
//设置项选择
if(is_ok==0 && opt_cursor!=0)
opt_cursor-=1;
if(is_ok==1 && opt_cursor==0) //当前设置,年+1
year+=1;
if(is_ok==1 && opt_cursor==1) //当前设置,月+1
mon+=1;
if(is_ok==1 && opt_cursor==2) //当前设置,日+1
day+=1;
Screen_Clear();
LED_Disp();
}
void key3_E()
{
//设置项选择
if(is_ok==0 && opt_cursor!=2)
opt_cursor+=1;
if(is_ok==1 && opt_cursor==0) //当前设置,年-1
year-=1;
if(is_ok==1 && opt_cursor==1) //当前设置,月-1
mon-=1;
if(is_ok==1 && opt_cursor==2) //当前设置,日-1
day-=1;
Screen_Clear();
LED_Disp();
}
void key4_E()
{
if(mode == opt)//如果是在设置页就确定选择了哪一项
{
is_ok = !is_ok;
if(is_ok)
Led_On(2);
else
Led_Off(2);
}
if(mode == index) //如果是在主页就切换到设置页
mode = opt;
//更新屏幕
Screen_Clear();
LED_Disp();
}
void Init()
{
BoardInitMcu();//初始化班板子
Screen_Init();//led屏初始化
Screen_Display_On();//开启显示
Key_Init();//按键初始化
Timer2_Init(1,Key_EventScanner);//定时器时间与回调
Timer2_Start();//开始
Timer3_Init(1,Uart_DataReceiveScanner);//定时器时间与回调
Timer3_Start();//开始
Led_Init();//led初始化
Uart_Init(115200);//串口初始化,波特率115200
//获取串口发来的数据
Flash_ReadBuffer(flash_add,flash_arr,3);
year = flash_arr[0];
mon = flash_arr[1];
day = flash_arr[2];
LED_Disp();//自定义屏幕函数
}
/**
* Main application entry point.
*/
int main( void )
{
Init();
while(1)
{
//使用Uart_DataRead函数接收串口数据并存储在uRxBuf中,返回接收到的数据长度
uart_rx_len = Uart_DataRead(uRxBuf,255);
if(uart_rx_len > 0)
{
//进行帧头帧尾判断,使用else返回报错
if(uRxBuf[0] == 0xfb && uRxBuf[uart_rx_len-1] == 0xfe)
{
//数据处理,数据长度不对同样返回报错
if(uart_rx_len==4 || uart_rx_len==5)
{
if(uRxBuf[1] == 0x01)
{
int a ,b;//局部变量,用于年的高低位
a = uRxBuf[2];
b = uRxBuf[3];
year = a*100+b;
}
else if(uRxBuf[1] == 0x02)
mon = uRxBuf[2];
else if(uRxBuf[1] == 0x03)
day = uRxBuf[2];
else
{
Uart_Write(fail,3);
continue;
}
flash_arr[0] = year;
flash_arr[1] = mon;
flash_arr[2] = day;
Flash_WriteBuffer(flash_add,flash_arr,3);
DelayMs(20);//5ms写入时间
Uart_Write(success,3);
Screen_Clear();
LED_Disp();
}
else
{
Uart_Write(fail,3);
}
}
else
{
Uart_Write(fail,3);
}
}
Key2_OnClickHandler(key2_E);
Key3_OnClickHandler(key3_E);
Key4_OnClickHandler(key4_E);
}
}