//-------------------------------------------堆 栈数据结构-------------------------------
typedef struct TStackSequenceCtrl_t{
BYTE *const Data;
BYTE Tail; //StackSequence: 指向 下一个将要存储的索引
const WORD Align;
const BYTE Size;
} TStackSequenceCtrl;
#define DEFINE_STACK_SEQUENCE(name, size, align) \
BYTE name##Buf[align * size]; \
TStackSequenceCtrl name = \
{ \
.Data = name##Buf, \
.Tail = 0, \
.Align = align, \
.Size = size, \
};
BYTE Lib_StackSequencePush(TStackSequenceCtrl *StackSequence, void *Data)
{
BYTE Ret = FALSE;
if(StackSequence->Tail < StackSequence->Size)
{
Ret = TRUE;
memcpy(&StackSequence->Data[StackSequence->Tail * StackSequence->Align], Data, StackSequence->Align);
StackSequence->Tail++;
}
return Ret;
}
BYTE Lib_StackSequenceTailData(TStackSequenceCtrl *StackSequence, void *Data)
{
BYTE Ret = FALSE;
if(StackSequence->Tail != 0)
{
Ret = TRUE;
memcpy(Data, &StackSequence->Data[(StackSequence->Tail - 1) * StackSequence->Align], StackSequence->Align);
}
return Ret;
}
void Lib_StackSequencePop(TStackSequenceCtrl *StackSequence)
{
if(StackSequence->Tail != 0)
{
StackSequence->Tail--;
}
}
typedef struct TSequenceCtrl_t{
BYTE *const Data;
BYTE Head; //StackSequence: 指向 第一个存储的索引
BYTE Lenth; //有效长度
const WORD Align;
const BYTE Size;
} TSequenceCtrl;
#define DEFINE_SEQUENCE(name, size, align) \
BYTE name##Buf[align * size]; \
TSequenceCtrl name = \
{ \
.Data = name##Buf, \
.Head = 0, \
.Lenth = 0, \
.Align = align, \
.Size = size, \
};
BYTE Lib_SequencePush( TSequenceCtrl *Sequence, void *Data)
{
BYTE Ret = FALSE;
if(Sequence->Lenth < Sequence->Size)
{
Ret = TRUE;
BYTE Next = (Sequence->Head + Sequence->Lenth) % Sequence->Size;
memcpy(&Sequence->Data[Next * Sequence->Align], Data, Sequence->Align);
Sequence->Lenth++;
}
return Ret;
}
BYTE Lib_SequenceHeadData(TSequenceCtrl *Sequence, void *Data)
{
BYTE Ret = FALSE;
if(Sequence->Lenth != 0)
{
Ret = TRUE;
memcpy(Data, &Sequence->Data[(Sequence->Head * Sequence->Align)], Sequence->Align);
}
return Ret;
}
void Lib_SequencePop(TSequenceCtrl *Sequence)
{
if(Sequence->Lenth != 0)
{
Sequence->Head++;
Sequence->Head %= Sequence->Size;
Sequence->Lenth--;
}
}
//--------------------------------定时器---------------------------
BYTE Lib_TimerDecrease(void *Timer, BYTE Align, DWORD Init)
{
BYTE Ret = FALSE;
DWORD TimerBak;
TimerBak = 0;
Align = (Align > sizeof(TimerBak)) ? sizeof(TimerBak) : Align;
memcpy(&TimerBak, Timer, Align);
if(TimerBak != 0)
{
TimerBak--;
}
if(TimerBak == 0)
{
TimerBak = Init;
Ret = TRUE;
}
memcpy(Timer, &TimerBak, Align);
return Ret;
}
//---------------------------------------------按键扫描-------------------------
static BYTE s_KeyStatus;
void api_KeyScanf(void)
{
static WORD s_DelayTimer[8];
s_KeyStatus = 0;
if(!Up_KeyEnter)
{
if(s_DelayTimer[0]>2)
s_KeyStatus |= Up_Key;
s_DelayTimer[0] = 0;
}
else
{
s_DelayTimer[0]++;
}
if(!Down_KeyEnter)
{
if(s_DelayTimer[1]>2)
s_KeyStatus |= Down_Key;
s_DelayTimer[1] = 0;
}
else
{
s_DelayTimer[1]++;
}
if(!Enter_KeyEnter)
{
if(s_DelayTimer[2]>2)
s_KeyStatus |= Enter_Key;
s_DelayTimer[2] = 0;
}
else
{
s_DelayTimer[2]++;
}
if(!Return_KeyEnter)
{
if(s_DelayTimer[3]>2)
s_KeyStatus |= Return_Key;
s_DelayTimer[3] = 0;
}
else
{
s_DelayTimer[3]++;
}
if(!Right_KeyEnter)
{
if(s_DelayTimer[4]>2)
s_KeyStatus |= Right_Key;
s_DelayTimer[4] = 0;
}
else
{
s_DelayTimer[4]++;
}
if(!Left_KeyEnter)
{
if(s_DelayTimer[5]>2)
s_KeyStatus |= Left_Key;
s_DelayTimer[5] = 0;
}
else
{
s_DelayTimer[5]++;
}
}
// 0 - 9; Range = 10
void MakeUpDigitKey(BYTE KeyStatus, BYTE* Data, BYTE Range)
{
BYTE Digit = Data[0];
if( (Range == 0 ) || (Data[0] >= Range))
{
Data[0] = 0;
return;
}
if((KeyStatus == Right_Key) || (KeyStatus == Up_Key))
{
Data[0]++;
Digit = (Data[0] >= Range) ? 0 : Data[0];
}
if((KeyStatus == Left_Key) || (KeyStatus == Down_Key))
{
Digit = (Data[0] == 0) ? Range - 1 : Data[0]-1;
}
Data[0] = Digit;
}
void MakeUpDigitASSIC(const BYTE *Src, BYTE *Dst, BYTE Len)
{
for (BYTE i = 0; i < Len; i++)
{
if(Src[i] <= 0x09)
Dst[i] = Src[i] + '0';
else
Dst[i] = (Src[i] - 0x0A) + 'A';
}
}
//---------------------------------显示逻辑 --------------------------------------
typedef BYTE (*TLCDEventFilter)(WORD CurrKey, WORD CurrSubMenu); //功能实现
struct TLCDMenuTable_t
{
const BYTE *HintInfo; //当前菜单功能信息提示
BYTE SubMenuNum; //当前菜单下 菜单个数
const struct TLCDMenuTable_t *SubMenuTable; //当前菜单下 子菜单
TLCDEventFilter LCDEventFilter; //当前菜单 功能实现
BYTE BtnCmd; //bitx 有效置位 具体含义: bit0 :确定键:进入下一级目录 bit1:返回键:返回上层目录
};
typedef struct TLCDMenuTable_t TLCDMeunTable; //菜单目录
typedef struct TLCDStackTrace_t //用于菜单的翻页,以及回退
{
const TLCDMeunTable *CurrMenu; //当前菜单
WORD SubMenuNum; //当前菜单下 子菜单的个数
WORD FirstSubMenu; //当前界面第一行 表示的子菜单
WORD CurrSubMenu; //当前选中的第一子菜单
}TLCDStackTrace;
//-----------------------------------LCD 控制框架------------------------------------
#define LCD_MENU_NULL ((TLCDMeunTable*)NULL) //空菜单
#define LCD_CTRL_FUN_NULL ((TLCDEventFilter)NULL) //空显示函数
#define SUB_MENU_NUM( type ) (sizeof(type)/sizeof(TLCDMeunTable))
//------------------------------------LCD 子菜单---------------------------------------
BYTE ReadVolRateEvtFilter(WORD CurrKey, WORD CurrSubMenu)
{
#define ADDR_LEN 12
static BYTE s_Addr[ADDR_LEN], s_Cursor, s_IsStart = FALSE;
static WORD s_Delay = 2;
BYTE Addr[ADDR_LEN + 1];
//没有开始发送,返回键直接 返回到上一屏
if( (CurrKey == Return_Key) && (s_IsStart == FALSE))
{
return Return_Key;
}
//开始发送,返回键,直接暂停
if( (CurrKey == Return_Key) && (s_IsStart == TRUE))
{
s_IsStart = FALSE;
}
//1、确定按键状态
if((CurrKey == Right_Key) || (CurrKey == Left_Key))
{
MakeUpDigitKey(CurrKey, &s_Cursor, ADDR_LEN);
}
if((CurrKey == Up_Key) || (CurrKey == Down_Key))
{
MakeUpDigitKey(CurrKey, &s_Addr[s_Cursor], 0x10);
}
if(CurrKey == Enter_Key)
{
s_IsStart = TRUE;
}
//2、开始发送报文
if((Lib_TimerDecrease(&s_Delay, sizeof(s_Delay), 2) == TRUE) && (s_IsStart == TRUE))
{
extern BYTE CommuAddr[6];
memcpy(CommuAddr, s_Addr, ADDR_LEN);
MassageSend645();
}
//3、主循环调用,非按键翻屏,不要清屏
if(CurrKey != Refulsh_Key)
{
OLED_Clear(OLED_WRBUF);
}
//4、显示地址 与 是否发送
OLED_ShowGBK(0, 0, "通信地址", OLED_WRBUF);
Addr[ADDR_LEN] = 0x00;//结束字符
MakeUpDigitASSIC(s_Addr, Addr, ADDR_LEN);
OLED_ShowGBK(0, 16, (char const *)Addr, OLED_WRBUF);
OLED_ShowGBK(8*s_Cursor, 16, " ", OLED_WRBUF);
Addr[0] = Addr[s_Cursor]; Addr[1] = 0x00;
OLED_ShowGBK(8*s_Cursor, 16, (char const *)Addr, OLED_OPWRBUF);
if(s_IsStart == TRUE)
{
OLED_ShowGBK(32, 32, "正在发送", OLED_WRBUF);
}
else
{
OLED_ShowGBK(32, 32, " ", OLED_WRBUF);
}
return Invaild_Key;
}
static const TLCDMeunTable c_LCDAVolRateMenuTable[1] =
{
"子菜单1", 0, LCD_MENU_NULL, ReadVolRateEvtFilter, BIT1,
};
BYTE AddrMenuEvtFilter(WORD CurrKey, WORD CurrSubMenu)
{
static BYTE IsLock = FALSE;
static BYTE s_Addr[ADDR_LEN], s_Cursor;
BYTE Addr[ADDR_LEN + 1], Ret = CurrKey;
//当前没有锁定子菜单,返回键直接返回上一级目录
if((CurrKey == Return_Key) && (IsLock == FALSE))
{
return Return_Key;
}
//当前锁定子菜单,返回键直接解除锁定
if((CurrKey == Return_Key) && (IsLock == TRUE))
{
IsLock = FALSE;
Ret = Invaild_Key;
}
//1、确定按键状态
if(CurrKey == Enter_Key)
{
IsLock = TRUE;
}
//2、锁定当前子菜单,所有按键操作仅局限于当前 菜单,
if((CurrKey != Refulsh_Key) && (IsLock == TRUE))
{
Ret = Invaild_Key;
if((CurrKey == Right_Key) || (CurrKey == Left_Key))
{
MakeUpDigitKey(CurrKey, &s_Cursor, ADDR_LEN);
}
if((CurrKey == Up_Key) || (CurrKey == Down_Key))
{
MakeUpDigitKey(CurrKey, &s_Addr[s_Cursor], 0x10);
}
}
#define START_ADDR_COLU 2*16
//4、无论当前是否处理锁定,子菜单选项落到该项时,都应该被刷新显示
OLED_ShowGBK(START_ADDR_COLU, 0, " ", OLED_WRBUF);
Addr[ADDR_LEN] = 0x00;//结束字符
MakeUpDigitASSIC(s_Addr, Addr, ADDR_LEN);
OLED_ShowGBK(START_ADDR_COLU, 0, (char const *)Addr, OLED_WRBUF);
//5、锁定状态下,显示光标位置
if(IsLock == TRUE)
{
OLED_ShowGBK(START_ADDR_COLU + 8*s_Cursor, 0, " ", OLED_WRBUF);
Addr[0] = Addr[s_Cursor]; Addr[1] = 0x00;
OLED_ShowGBK(START_ADDR_COLU + 8*s_Cursor, 0, (char const *)Addr, OLED_OPWRBUF);
}
return Ret;
}
static const TLCDMeunTable c_LCDVolRateMenuTable[] =
{
{"子菜单1", 0, LCD_MENU_NULL,
AddrMenuEvtFilter, BIT1 },
{"子菜单2", 0, c_LCDAVolRateMenuTable,
LCD_CTRL_FUN_NULL, BIT0|BIT1 },
{"子菜单3", 0, LCD_MENU_NULL,
LCD_CTRL_FUN_NULL, BIT1 },
{"子菜单4", 0, LCD_MENU_NULL,
LCD_CTRL_FUN_NULL, BIT1 },
};
//-----------------------------------主 菜单目录------------------------------------
static const TLCDMeunTable c_LCDMainMenuTable[] =
{
{"菜单1", SUB_MENU_NUM(c_LCDVolRateMenuTable), c_LCDVolRateMenuTable, LCD_CTRL_FUN_NULL, BIT0},
{"菜单2", SUB_MENU_NUM(c_LCDVolRateMenuTable), c_LCDVolRateMenuTable, LCD_CTRL_FUN_NULL, BIT0},
};
//---------------------------------LCD 控制框架--建议不做更改------------------------------------
DEFINE_SEQUENCE(g_KeySequence, 30, sizeof(BYTE))
DEFINE_STACK_SEQUENCE(g_LCDStackTrace, 30, sizeof(TLCDStackTrace))
#define LCD_MENU_NUM 3 //一屏 做多显示3行菜单
#define LCD_MENU_ROW_SPACE 16 //行间距
static void ProcInitLCD(void)
{
TLCDStackTrace MainMenu;
MainMenu.CurrMenu = &c_LCDMainMenuTable[0];
MainMenu.SubMenuNum = SUB_MENU_NUM(c_LCDMainMenuTable);
MainMenu.FirstSubMenu = 0;
MainMenu.CurrSubMenu = 0;
Lib_StackSequencePush(&g_LCDStackTrace, &MainMenu);
}
static void DoLCDBulidSceen(const TLCDMeunTable *Menu, WORD SubMenuNum, WORD FirstSubMenu, WORD CurrSubMenu)
{
const TLCDMeunTable *MainMenu = Menu;
WORD MenuMax = FirstSubMenu + LCD_MENU_NUM;
//|---------|
//| .... | 第一步的作用就是 将子菜单的信息显示
//|---------|
//| .... | 第二步的作用就是 执行每一个菜单中 特殊操作
//|---------|
//| .... |
//|---------|
//1、一屏最多显示 LCD_MENU_NUM屏 子菜单
MenuMax = (MenuMax > SubMenuNum) ? SubMenuNum : MenuMax;
for (WORD i = 0; i < (MenuMax - FirstSubMenu); i++)
{
Menu = &MainMenu[FirstSubMenu + i];
if( (FirstSubMenu + i) == CurrSubMenu)
{
OLED_ShowGBK( 0, LCD_MENU_ROW_SPACE * i, (char const *)Menu->HintInfo, OLED_OPWRBUF );
}
else
{
OLED_ShowGBK( 0, LCD_MENU_ROW_SPACE * i, (char const *)Menu->HintInfo, OLED_WRBUF );
}
TLCDEventFilter LCDEventFilter = Menu->LCDEventFilter;
if(LCDEventFilter != LCD_CTRL_FUN_NULL)
{
LCDEventFilter(Refulsh_Key, FirstSubMenu + i);
}
}
//2、之所以放到最后,以用户的逻辑为主,
if(SubMenuNum == 0)
{
TLCDEventFilter LCDEventFilter = MainMenu[0].LCDEventFilter;
if(LCDEventFilter != LCD_CTRL_FUN_NULL)
{
LCDEventFilter(Refulsh_Key, 0);
}
}
OLED_ShowGBK( 0, 48, "确定", OLED_WRBUF );
OLED_ShowGBK( 96, 48, "返回", OLED_WRBUF );
}
static void ProcLCDBulidSceen(void)
{
BYTE KeyStatus = Invaild_Key;
TLCDStackTrace CurrMenuTrace;
const TLCDMeunTable *CurrMenu; //当前菜单--数组起始地址
const TLCDMeunTable *SubMenu; //当前菜单下 选中的子菜--非数组
//1、获取当前菜单,以及当前菜单下的子菜单选择
CurrMenu = &c_LCDMainMenuTable[0]; //意外情况
CurrMenuTrace.CurrMenu = CurrMenu;
CurrMenuTrace.SubMenuNum = SUB_MENU_NUM(c_LCDMainMenuTable);
CurrMenuTrace.FirstSubMenu = 0;
CurrMenuTrace.CurrSubMenu = 0;
if(Lib_StackSequenceTailData(&g_LCDStackTrace, &CurrMenuTrace) == TRUE)
{
CurrMenu = CurrMenuTrace.CurrMenu;
}
SubMenu = &CurrMenu[CurrMenuTrace.CurrSubMenu];
Lib_StackSequencePop(&g_LCDStackTrace);
OLED_Clear(OLED_WRBUF);
//2、按键动作
while(Lib_SequenceHeadData(&g_KeySequence, &KeyStatus) == TRUE)
{
Lib_SequencePop(&g_KeySequence);
//事件滤波,根据当前子菜单下的 滤波器优先执行 用户自定义动作,并根据后续反馈,确定是否执行下一步动作
TLCDEventFilter LCDEvnetFilterFun = SubMenu->LCDEventFilter;
if(LCDEvnetFilterFun != LCD_CTRL_FUN_NULL)
{
KeyStatus = LCDEvnetFilterFun(KeyStatus, CurrMenuTrace.CurrSubMenu);
}
//根据按键做出相应的动作
switch(KeyStatus)
{
case Invaild_Key:
break;
case Enter_Key: //进入子菜单,重新确定当前菜单也 以及子菜单
if(SubMenu->BtnCmd & BIT0)
{
Lib_StackSequencePush(&g_LCDStackTrace, &CurrMenuTrace);
if(CurrMenu[CurrMenuTrace.CurrSubMenu].SubMenuTable != LCD_MENU_NULL)
{
CurrMenu = SubMenu->SubMenuTable;
CurrMenuTrace.CurrMenu = CurrMenu;
CurrMenuTrace.SubMenuNum = SubMenu->SubMenuNum;
CurrMenuTrace.FirstSubMenu = 0;
CurrMenuTrace.CurrSubMenu = 0;
SubMenu = &CurrMenu[CurrMenuTrace.CurrSubMenu];
}
}
break;
case Return_Key: //返回上一级菜单,恢复原 选中子菜单
if(SubMenu->BtnCmd & BIT1)
{
if(Lib_StackSequenceTailData(&g_LCDStackTrace, &CurrMenuTrace) == TRUE)
{
Lib_StackSequencePop(&g_LCDStackTrace);
CurrMenu = CurrMenuTrace.CurrMenu;
SubMenu = &CurrMenu[CurrMenuTrace.CurrSubMenu];
}
}
break;
default: //菜单栏暂时不支持 左右按键
if(KeyStatus == Up_Key) //子菜单上下 移动选中
{
MakeUpDigitKey(Down_Key, (BYTE *)&CurrMenuTrace.CurrSubMenu, CurrMenuTrace.SubMenuNum);
}
if(KeyStatus == Down_Key)
{
MakeUpDigitKey(Up_Key, (BYTE *)&CurrMenuTrace.CurrSubMenu, CurrMenuTrace.SubMenuNum);
}
CurrMenuTrace.FirstSubMenu = (CurrMenuTrace.CurrSubMenu / LCD_MENU_NUM) * LCD_MENU_NUM;
SubMenu = &CurrMenu[CurrMenuTrace.CurrSubMenu];
break;
}
}
/*KeyStatus = Refulsh_Key;
TLCDEventFilter LCDEventFilter = SubMenu->LCDEventFilter;
if(LCDEventFilter != LCD_CTRL_FUN_NULL)
{
KeyStatus = LCDEventFilter(Refulsh_Key, CurrMenuTrace.CurrSubMenu);
}
if(KeyStatus == Invaild_Key)
{
DoLCDBulidSceen(LCD_MENU_NULL, 0, 0, 0);
}
else //if(KeyCmd == 0xFE)*/
{
DoLCDBulidSceen(CurrMenuTrace.CurrMenu, CurrMenuTrace.SubMenuNum, CurrMenuTrace.FirstSubMenu, CurrMenuTrace.CurrSubMenu);
}
Lib_StackSequencePush(&g_LCDStackTrace, &CurrMenuTrace);
OLED_Refresh();
}
inline static void LCD100MsTask(void)
{
ProcLCDBulidSceen();
}
void api_LCDTask_1(void)
{
static BYTE s_100msTime;
if(api_GetTaskFlag(eTASK_EVENTS_ID, eFLAG_10_MS) == TRUE)
{
api_ClrTaskFlag(eTASK_EVENTS_ID, eFLAG_10_MS);
//按键队列,按键扫描快,刷新屏幕慢,暂存按键状态
api_KeyScanf();
for (BYTE i = 0; i < 8;i++)
{
BYTE KeyStatus = (1U << i);
if(s_KeyStatus & KeyStatus)
{
Lib_SequencePush(&g_KeySequence, &KeyStatus);
}
}
//100ms 刷屏任务
if (Lib_TimerDecrease(&s_100msTime, sizeof(s_100msTime), 20) == TRUE)
{
LCD100MsTask();
}
}
}
1、ProcLCDBulidSceen()利用栈结构,存储当前菜单信息。
确定键按下,当前菜单信息压栈,进入下一级目录菜单;
返回键按下,弹出菜单信息作为当前菜单信息,实现按键返回;
上下翻屏键,当前菜单下,选中子菜单,高亮显示;
2、点阵液晶驱动显示,OLED_ShowGBK()自行实现