在嵌入式电子产品的设计中,不可避免的要涉及到LCD的显示,显示设计没有多难,每一屏的内容设计出来开个定时器延时刷屏就好,难的是在未来业务变化时如何很便捷的添加一屏内容上去。
有人可能觉得添加一屏内容有多难,直接将这一屏设计出来然后往后添加就好,其实如果显示逻辑简单,这样做当然可以,但是稍微复杂一点的显示业务,会有各种逻辑下的显示,比如按下按键时向用户显示哪几屏,数据通信时向用户显示哪几屏,在这种稍微复杂一点的业务逻辑中,程序没有设计好要添加一屏内容或者添加一个显示逻辑时是很困难的。
显示业务变化时将会有2种可能,1,添加某一屏内容在某一时刻或者某一逻辑中显示,2,新添加一种显示逻辑,比如产品新增加了一个按键,当按键按下时要向用户显示哪些内容。
根据这2种需求,我们可以将所有显示逻辑抽象出来,每一个显示逻辑抽象出一个显示模式,然后以查表的设计模式来设计显示业务,这样子当要新添加一种显示逻辑时只需要新添加一种模式,要添加某一个页面时在相应的模式下新添加一个页面就好,然后程序会自动去显示这些内容,符合开闭设计原则(对修改关闭对扩展开放)。
虽然思路可能很复杂,但是程序实现却很简单,在普遍的过程式思维下往往思路简单但程序就很复杂。
水表LCD显示任务
/**
* 表的基本格式
*/
typedef struct
{
uint8_t dis_interval; /* 显示间隔 */
void (*dis_handler)(void); /* 页面句柄 */
}DisplayForm;
正常显示模式表
/**
* 正常模式下显示的5屏内容
*/
DisplayForm normal_mode[]=
{
{
2, dis_current_date},
{
2, dis_current_time},
{
2, dis_meter_addr_high},
{
2, dis_meter_addr_low},
{
2, dis_current_water_volume},
{
0, NULL}
};
红外通信模式表
/**
* 红外模式下显示的7屏内容
*/
DisplayForm infrared_mode[]=
{
{
2, dis_wf2401_status},
{
2, dis_wf2401_water_volume},
{
2, dis_current_date},
{
2, dis_current_time},
{
2, dis_meter_addr_high},
{
2, dis_meter_addr_low},
{
2, dis_current_water_volume},
{
0, NULL}
};
上报模式表
/**
* 上报模式下显示的N屏内容
*/
DisplayForm report_mode[]=
{
{
2, dis_activate},
{
2, dis_version},
{
2, dis_ip_high},
{
2, dis_ip_low},
{
2, dis_signal},
{
0, NULL}
};
表设计好了之后写一个结构体记录内部使用的所有参数
/**
* 内部使用参数
*/
typedef struct
{
DisMode dis_mode; /* 当前显示的模式 */
DisplayForm* current_form_addr; /* 当前表的地址 */
DisplayForm* form_start_addr; /* 记录开始地址 */
}InnerPara;
static InnerPara inner_para;
上面的代码均是为下面这个主函数服务,这个函数的要求是1秒运行一次,至于怎么1秒运行一次不在此讨论范围。
/**
* 翻屏显示任务
*/
void display_task_1s_loop(void)
{
static uint8_t delayCount = 2; /* 液晶显示循显间隔延时 */
if (inner_para.current_form_addr == NULL)
inner_para.current_form_addr = normal_mode; /* 防止指针跑飞 */
if (inner_para.current_form_addr->dis_handler) /* 防止意外无指针可用 */
inner_para.current_form_addr->dis_handler(); /* 显示内容句柄 */
if (delayCount)
{
delayCount--;
}
else
{
inner_para.current_form_addr++; /* 翻屏 */
if(inner_para.current_form_addr->dis_handler == NULL)
{
if (inner_para.form_start_addr == NULL)
inner_para.form_start_addr = normal_mode; /* 防止指针跑飞 */
inner_para.current_form_addr = inner_para.form_start_addr;/* 回卷 */
}
delayCount = inner_para.current_form_addr->dis_interval; /* 获取当前屏的显示时长 */
}
}
好了,复杂的逻辑显示在这里需要仅仅10几行代码就能完成了,而且有一半是为了防止指针出问题而写的。
回到最初的问题, 如果我要添加一屏内容或者添加一套逻辑怎么办?
若添加一屏内容,只需要确定好该屏内容是在哪一个模式下显示的,然后在这个模式的表内添加一个句柄就好,其他什么都不用做。例如我要在正常模式下添加一个版本显示:
/**
* 正常模式下显示的5屏内容
*/
DisplayForm normal_mode[]=
{
{
2, dis_current_date},
{
2, dis_current_time},
{
2, dis_meter_addr_high},
{
2, dis_meter_addr_low},
{
2, dis_current_water_volume},
{
2, dis_version}, /* 如果要添加一屏内容,在这里添加,然后将句柄内的内容设计好就行,其他什么都不用做 */
{
0, NULL}
};
如果我要添加一个模式,在做一张表就好了,也是其他什么都不用做,至于当前要运行什么模式,是由外部程序决定的,当外部程序改变模式时程序会自动切换过去,切换程序如下:
/**
* 显示模式选择
*/
typedef enum
{
NORMAL_DIS,
INFRARED_DIS,
REPORT_DIS
}DisMode;
/**
* 用户变更显示模式
*/
void display_mode_change(DisMode mode)
{
switch (mode)
{
case NORMAL_DIS:
inner_para.current_form_addr = normal_mode;
inner_para.form_start_addr = normal_mode;
break;
case INFRARED_DIS:
inner_para.current_form_addr = infrared_mode;
inner_para.form_start_addr = infrared_mode;
break;
case REPORT_DIS:
inner_para.current_form_addr = report_mode;
inner_para.form_start_addr = report_mode;
break;
default:
inner_para.current_form_addr = normal_mode;
inner_para.form_start_addr = normal_mode;
break;
}
}
设计中思维很重要,过程式思维写出来的过程式代码是不被人待见的,我们要努力锻炼自己的抽象思维,争取让复杂的逻辑在我们脑子中运行,简单的程序让计算机运行。
最后附上整个程序:
c文件:
/**
* display.c
* LCD显示任务
* 显示分为3个模式:
* (1):正常显示模式分为5屏,分别为 1 当前日期,2 当前时间, 3 表号高3字节, 4 表号低3字节, 5 当前用水量
* (2):红外通信模式分为7屏,分别为正常模式5屏加无磁信号强度显示和无磁模块内部计量值
* (3):NB上报模式分为5屏,分别为 1 激活界面, 2 IP地址高3字节, 3 IP地址低3字节, 4 版本信息,5 上报状态....
* 设计原则:容易的添加模式或者在某个模式中添加屏显
*/
#include
#include "hard_platform.h"
#include "display_task.h"
/**
* 内部使用的页面内容显示函数
*/
static void dis_current_date();
static void dis_current_time();
static void dis_meter_addr_high();
static void dis_meter_addr_low();
static void dis_current_water_volume();
static void dis_wf2401_status();
static void dis_wf2401_water_volume();
static void dis_activate();
static void dis_version();
static void dis_ip_high();
static void dis_ip_low();
static void dis_signal();
/**
* 表的基本格式
*/
typedef struct
{
uint8_t dis_interval; /* 显示间隔 */
void (*dis_handler)(void); /* 页面句柄 */
}DisplayForm;
/**
* 内部使用参数
*/
typedef struct
{
DisMode dis_mode; /* 当前显示的模式 */
DisplayForm* current_form_addr; /* 当前表的地址 */
DisplayForm* form_start_addr; /* 记录开始地址 */
}InnerPara;
static InnerPara inner_para;
/**
* 正常模式下显示的5屏内容
*/
DisplayForm normal_mode[]=
{
{
2, dis_current_date},
{
2, dis_current_time},
{
2, dis_meter_addr_high},
{
2, dis_meter_addr_low},
{
2, dis_current_water_volume},
{
0, NULL}
};
/**
* 红外模式下显示的7屏内容
*/
DisplayForm infrared_mode[]=
{
{
2, dis_wf2401_status},
{
2, dis_wf2401_water_volume},
{
2, dis_current_date},
{
2, dis_current_time},
{
2, dis_meter_addr_high},
{
2, dis_meter_addr_low},
{
2, dis_current_water_volume},
{
0, NULL}
};
/**
* 上报模式下显示的N屏内容
*/
DisplayForm report_mode[]=
{
{
2, dis_activate},
{
2, dis_version},
{
2, dis_ip_high},
{
2, dis_ip_low},
{
2, dis_signal},
{
0, NULL}
};
/**
* 显示任务初始化
*/
void display_task_init(void)
{
inner_para.dis_mode = NORMAL_DIS;
inner_para.current_form_addr = normal_mode;
inner_para.form_start_addr = normal_mode;
}
/**
* 用户变更显示模式
*/
void display_mode_change(DisMode mode)
{
switch (mode)
{
case NORMAL_DIS:
inner_para.current_form_addr = normal_mode;
inner_para.form_start_addr = normal_mode;
break;
case INFRARED_DIS:
inner_para.current_form_addr = infrared_mode;
inner_para.form_start_addr = infrared_mode;
break;
case REPORT_DIS:
inner_para.current_form_addr = report_mode;
inner_para.form_start_addr = report_mode;
break;
default:
inner_para.current_form_addr = normal_mode;
inner_para.form_start_addr = normal_mode;
break;
}
}
/**
* 翻屏显示任务
*/
void display_task_1s_loop(void)
{
static uint8_t delayCount = 2; /* 液晶显示循显间隔延时 */
if (inner_para.current_form_addr == NULL)
inner_para.current_form_addr = normal_mode; /* 防止指针跑飞 */
if (inner_para.current_form_addr->dis_handler) /* 防止意外无指针可用 */
inner_para.current_form_addr->dis_handler(); /* 显示内容句柄 */
if (delayCount)
{
delayCount--;
}
else
{
inner_para.current_form_addr++; /* 翻屏 */
if(inner_para.current_form_addr->dis_handler == NULL)
{
if (inner_para.form_start_addr == NULL)
inner_para.form_start_addr = normal_mode; /* 防止指针跑飞 */
inner_para.current_form_addr = inner_para.form_start_addr;/* 回卷 */
}
delayCount = inner_para.current_form_addr->dis_interval; /* 获取当前屏的显示时长 */
}
}
/**
* 该屏显示:
*/
static void dis_current_date()
{
}
/**
* 该屏显示:
*/
static void dis_current_time()
{
}
/**
* 该屏显示:
*/
static void dis_meter_addr_high()
{
}
/**
* 该屏显示:
*/
static void dis_meter_addr_low()
{
}
/**
* 该屏显示:
*/
static void dis_current_water_volume()
{
}
/**
* 该屏显示:
*/
static void dis_wf2401_status()
{
}
/**
* 该屏显示:
*/
static void dis_wf2401_water_volume()
{
}
/**
* 该屏显示:
*/
static void dis_activate()
{
}
/**
* 该屏显示:
*/
static void dis_version()
{
}
/**
* 该屏显示:
*/
static void dis_ip_high()
{
}
/**
* 该屏显示:
*/
static void dis_ip_low()
{
}
/**
* 该屏显示:
*/
static void dis_signal()
{
}
H文件:
/**
* display_task.h
*/
#ifndef __DISPLAY_TASK_H
#define __DISPLAY_TASK_H
/**
* 显示模式选择
*/
typedef enum
{
NORMAL_DIS,
INFRARED_DIS,
REPORT_DIS
}DisMode;
/**
* 显示任务初始化
*/
extern void display_task_init(void);
/**
* 用户变更显示模式
*/
extern void display_mode_change(DisMode mode);
/**
* 翻屏显示任务
*/
extern void display_task_1s_loop(void);
#endif /* DISPLAY_TASK_H */