3.24_433_RX版本:封装RF433模块,完成开机进入TX/RX模式并在开发板验证成功

This commit is contained in:
2026-03-24 16:59:20 +08:00
commit e439dd465e
1311 changed files with 692196 additions and 0 deletions

View File

@ -0,0 +1,766 @@
#include "menu.h"
#include "dispDirver.h"
#include "stdlib.h"
#include "application.h"
#include "DinoGame.h"
#include "AirPlane.h"
#include "usbd_cdc_if.h"
#include "usart.h"
#include "main.h"
/* pid动画参数 */
Pid_Error Cursor_Line = {600, 370, 30, 0, 0, 0}, Cursor_Wide = {250, 370, 30, 0, 0, 0};
/* 线性动画参数 */
Animation_Param AnimationParam = {10, 0, 0, 0, 0, 0, 0, 0, false};
/* Page*/
xPage Home_Page ;
xPage setting_page;
xPage tx_mode_page;
xPage rx_mode_page;
/* item */
xItem HomeHead_Item ;
xItem setting_item;
xItem setting_exit_item;
xItem work_mode_item;
xItem rate_mode_item;
xItem channel_item;
xItem tx_power_item;
xItem tx_count_item;
xItem tx_mode_item;
xItem rx_mode_item;
xItem version_item;
xItem reset_item;
xItem bg_color_item;
/* menu */
xMenu Menu = {0, &HomeHead_Item, &HomeHead_Item, MENU_INIT, PAGE_STATIC, 0};
/**
* 向指定页面添加一个项目。
*
* @param Name 项目名称。
* @param Type 项目类型。
* @param Data 项目关联的数据指针。
* @param item 待添加的项目结构体。
* @param LocalPage 项目所属的页面。
* @param nextpage 项目跳转到的下一个页面。
* @param function 项目关联的函数指针。
*
* 项目会被添加到指定页面的链表中,并且会更新页面的相关信息。
*/
static void AddItem(const char *Name, Item_Type Type, void *Data, xpItem item, xpPage LocalPage, xpPage nextpage, ItemFunction function)
{
// 参数检验
if (!Name || !item || !LocalPage || (Type == DATA && !Data)) {
printf("Invalid parameter(s)\n");
return; // 早期返回以避免进一步错误
}
item->itemName = Name; // 设置项目名称
item->itemType = Type; // 设置项目类型
// 如果项目类型为DATA则设置数据指针
if(Type == DATA)item->data.ptr = (int *)Data;
item->page.location = LocalPage; // 设置项目所在页面
item->itemFunction= function; // 设置项目关联的函数
/* 初始化项目下一个项为NULL */
item->nextItem = NULL;
/* 设置跳转页面的父级项目 */
if (nextpage != NULL)
nextpage->item.parent = item;
else // 如果没有下一个页面,则设置为当前页面
nextpage = LocalPage;
item->page.jumpPage = nextpage; // 设置项目跳转页面
/* 链式结构创建项目 */
if (LocalPage->item.head == NULL) // 如果是首个项目
{
// 初始化项目链表头部和尾部
item->lastItem = item;
LocalPage->item.head = item;
LocalPage->item.tail = item;
LocalPage->length = 0; // 初始化项目计数
}
else // 如果不是首个项目
{
// 连接新项目到链表尾部
item->lastItem = LocalPage->item.tail; // 新项目上一个项指向当前尾项目
LocalPage->item.tail->nextItem = item; // 尾项目下一个项指向新项目
LocalPage->item.tail = LocalPage->item.tail->nextItem; // 更新尾项目为新项目
LocalPage->length++; // 项目计数加一
}
item->id = LocalPage->length; // 设置项目ID为当前页面项目计数
// 关联页面的头尾项目互相指向,用于快速遍历
LocalPage->item.tail->nextItem = LocalPage->item.head;
LocalPage->item.head->lastItem = LocalPage->item.tail;
}
/**
* @brief 向页面中添加一个新页面。
*
* @param name 新页面的名称。
* @param Menu 新页面所在菜单。
* @param page 指向新页面结构体的指针。
* @param item 新页面对应的项。
* @param LocalPage 当前局部页面的指针。
* @param nextpage 下一个页面的指针。
* @param function 项的功能函数指针。
*/
static void AddPage(const char *name, xpMenu Menu, xpPage page, xpItem item, xpPage LocalPage, xpPage nextpage, ItemFunction function)
{
// 初始化新页面的基本信息
page->pageName = name;
page->item.head = NULL;
page->item.tail = NULL;
page->menu = Menu;
Menu->page_length++;
page->id = Menu->page_length; // 分配ID并递增ID值
// 如果是第一个页面,将其头跳转至首页;否则,将其父项设置为上一页
if (page->id == 1)AddItem(name, LOOP_FUNCTION, NULL, item, LocalPage, nextpage, function);
else AddItem(name, PARENTS, NULL, item, LocalPage, nextpage, function);
}
/**
* 使用线性插值公式计算当前时间点对应的插值结果。
*
* @param AllTime 总时间长度,单位为秒。表示从开始到结束的总时间长度。
* @param Time_Now 当前时间点,单位为秒。表示在总时间长度中的当前位置。
* @param Targrt 目标值。在给定总时间长度和当前时间点的情况下,此函数将计算出达到此目标值的插值。
* @param Now 当前值。表示在当前时间点的值。
* @return 返回计算出的插值结果。此结果是根据线性插值公式,将目标值和当前值结合当前时间点与总时间长度计算得出的。
*/
static int8_t Linear(uint8_t AllTime, uint8_t Time_Now, int8_t Targrt, int8_t Now)
{
// 根据线性插值公式,计算并返回当前时间点对应的插值结果
return (Targrt - Now)*Time_Now/AllTime + Now;
}
/**
* PID 插值控制器计算函数
*
* 本函数用于根据目标值、当前值及已设定的PID参数计算出下一时刻的控制量。
*
* @param Targrt 目标值,即期望的控制结果。
* @param Now 当前值,即当前的控制结果。
* @param Obj PID控制器的结构体对象包含比例、积分、微分系数及误差相关信息。
* @return 计算后得到的控制量,用于调整系统状态。
*/
static int PID(int Targrt, int Now, Pid_Error *Obj)
{
int x = Now;
// 将PID系数从毫单位转换为浮点数
float Kp = (float)(Obj->kp)/1000.00, Ki = (float)(Obj->ki)/1000.00, Kd = (float)(Obj->kd)/1000.00;
// 计算误差
Obj->error = Targrt - x;
// 积分环节,累加误差
Obj->sum_srror += Obj->error;
// 微分环节,计算误差变化率
float delta_error = Obj->error - Obj->last_error;
// 计算控制量
float velocity = Kp * Obj->error + Ki * Obj->sum_srror + Kd * delta_error;
// 更新状态
x += velocity;
Obj->last_error = Obj->error;
return x;
}
static void Change_MenuState(Menu_State state)
{
Menu.menu_state = state;
}
static void Item_AnimationParam_Init(void)
{
AnimationParam.Item_NowLine = 0;
AnimationParam.Item_NowWide = 0;
AnimationParam.Item_TargrtLine = 0;
AnimationParam.Item_TargrtWide = 0;
AnimationParam.OptionState = false;
}
static void DialogScale_AnimationParam_Init(void)
{
AnimationParam.DialogScale_InitHigh = 0;
AnimationParam.DialogScale_InitWide = 0;
AnimationParam.DialogScale_time = 0;
}
/**
* 绘制对话框
* @param x 对话框左上角x坐标
* @param y 对话框左上角y坐标
* @param w 对话框宽度
* @param h 对话框高度
* 该函数首先绘制对话框的边框,然后绘制对话框的背景盒。
* 使用了OLED显示设备的相关函数来完成绘制操作。
*/
void Draw_DialogBox(uint16_t x,uint16_t y,uint16_t w,uint16_t h)
{
// 设置绘制边框的颜色,并绘制边框
OLED_SetDrawColor(Menu.BgColor^0x01);
OLED_DrawFrame(x, y, w, h);
// 设置绘制背景的颜色,并绘制背景盒
OLED_SetDrawColor(Menu.BgColor);
OLED_DrawBox(x+1, y+1, w-2, h-2);
// 设置边框高亮颜色(通常与背景色异或得到),用于强调边框
OLED_SetDrawColor(Menu.BgColor^0x01);
}
/**
* @brief 显示一个按指定尺寸缩放的对话框。
*
* 此函数用于在应用绘制状态时,通过线性动画效果展示一个对话框的缩放过程。函数首先检查当前是否处于应用绘制状态,
* 如果是,则根据设定的时间和目标尺寸计算当前对话框的宽度和高度,并进行绘制。当动画时间达到预设的对话框显示时间
* 后将状态切换到应用运行状态并返回true。整个过程通过OLED发送缓冲区来更新显示。
*
* @param x 对话框的x坐标。
* @param y 对话框的y坐标。
* @param Targrt_w 目标对话框的宽度。
* @param Targrt_h 目标对话框的高度。
* @return bool 如果动画时间达到预设的对话框显示时间则返回true否则返回false。
*/
bool DialogScale_Show(uint8_t x,uint8_t y,uint8_t Targrt_w,uint8_t Targrt_h)
{
// 当前处于应用绘制状态时,处理对话框的缩放动画
if (Menu.menu_state == APP_DRAWING)
{
AnimationParam.DialogScale_time++; // 动画时间递增
// 根据当前时间和目标尺寸计算对话框的当前宽度
AnimationParam.DialogScale_InitWide = Linear(AnimationParam.Dialog_AllTime, AnimationParam.DialogScale_time, Targrt_w, AnimationParam.DialogScale_InitWide);
// 根据当前时间和目标尺寸计算对话框的当前高度
AnimationParam.DialogScale_InitHigh = Linear(AnimationParam.Dialog_AllTime, AnimationParam.DialogScale_time, Targrt_h, AnimationParam.DialogScale_InitHigh);
// 绘制当前尺寸的对话框
Draw_DialogBox(x, y, AnimationParam.DialogScale_InitWide, AnimationParam.DialogScale_InitHigh);
}
// 当动画时间达到预设的对话框显示时间时,切换到应用运行状态
if (AnimationParam.DialogScale_time == AnimationParam.Dialog_AllTime)
{
Change_MenuState(APP_RUN);
return true;
}
// 更新OLED显示缓冲区
OLED_SendBuffer();
return false;
}
/**
* 绘制一个带有圆角的对话框背景框
* @param x 对话框左上角x坐标
* @param y 对话框左上角y坐标
* @param w 对话框宽度
* @param h 对话框高度
* @param r 对话框圆角半径
*/
void Draw_DialogRBox(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r)
{
// 设置绘制边框的颜色,先绘制外边框
OLED_SetDrawColor(Menu.BgColor^0x01);
OLED_DrawRFrame(x, y, w, h, r);
// 设置绘制填充颜色,绘制内框,即填充部分
OLED_SetDrawColor(Menu.BgColor);
OLED_DrawRBox(x + 1, y + 1, w - 2, h - 2, r);
// 设置边框颜色,用于强调边框,这里使用与背景色异或的方式
OLED_SetDrawColor(Menu.BgColor^0x01);
}
/**
* 设置背景颜色
* @param color 要设置的颜色值类型为uint8_t
* 该函数用于将全局背景颜色设置为指定的颜色值。
*/
void Set_BgColor(uint8_t color)
{
Menu.BgColor = color; // 设置背景颜色
}
uint8_t Get_BgColor(void)
{
return Menu.BgColor;
}
/**
* 绘制一个滚动条
* 该函数根据给定的参数在OLED屏幕上绘制一个滚动条。滚动条的可见部分的长度
* 会根据当前值在最小值和最大值之间动态调整。
* @param x 滚动条左上角的x坐标
* @param y 滚动条左上角的y坐标
* @param w 滚动条的宽度
* @param h 滚动条的高度
* @param r 滚动条圆角的半径
* @param min 滚动条代表的最小值
* @param max 滚动条代表的最大值
* @param step 滚动条的步进值
* @param NowValue 滚动条当前的值
*/
int Draw_Scrollbar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, double min, double max, int step, int NowValue)
{
int value = NowValue;
// 根据当前值计算滚动条可见部分的长度
if ((value <= max) && (value >= min))
{
value += step;
if (value > max)
{
value = max;
}
if (value < min)
{
value = min;
}
double value = (double)(abs(value - min) * ((w - 6)) / (double)((max - min)) + 6);
// 绘制滚动条的填充部分
OLED_SetDrawColor(Menu.BgColor);
OLED_DrawRBox(x + (uint16_t)value, y, w-(uint16_t)value, h, r);
OLED_SetDrawColor(Menu.BgColor^0x01);
OLED_DrawRBox(x, y, (uint16_t)value, h, r);
}
return value;
}
/**
* @brief 使屏幕上的内容逐渐消失的函数
*
* @param disapper 一个用于控制消失程度的参数初始值建议为0每次调用本函数会自动增加
* @return uint8_t 返回调整后的disapper值用于下次调用时继续消失过程
*/
static uint8_t ui_disapper(uint8_t disapper)
{
short disapper_temp = 0;
// 计算屏幕缓冲区的总长度
int length = 8 * OLED_GetBufferTileHeight() * OLED_GetBufferTileWidth();
uint8_t *p = OLED_GetBufferPtr(); // 获取屏幕缓冲区的指针
// 如果背景色为黑色,那么执行与操作,让像素点逐渐变暗从而消失
if(Menu.BgColor==0)
{
for( int i = 0; i < length; i++)
{
p[i] = p[i] & (rand()%0xff) >> disapper; // 通过与操作使像素点变暗
}
}
else // 如果背景色不是黑色,执行或操作,让像素点逐渐变亮从而消失
{
for( int i = 0; i < length; i++)
{
p[i] = p[i] | (rand()%0xff) >> disapper; // 通过或操作使像素点变亮
}
}
disapper += 2; // 每次调用使消失程度增加,以便逐渐完成消失过程
if(disapper >= 8) // 当消失程度达到最大值时重置为0准备下一次调用
{
disapper = 0;
}
OLED_SendBuffer(); // 将更新后的缓冲区内容发送到OLED显示设备
disapper_temp = disapper;
return disapper_temp; // 返回调整后的disapper值供外部调用者使用
}
/**
* @brief 绘制选项位置
*
* 该函数用于在OLED屏幕上动态绘制选项的位置实现类似选项滑动选择的效果。
* 根据当前项和下一个项的信息,计算并绘制出选项的滑动过程。
*
* @param now_item 当前选项项的指针
* @param next_item 下一个选项项的指针
* @return bool 如果绘制过程完成达到设定的时间返回true否则返回false。
*/
static bool Draw_OptionPlace(xpItem now_item, xpItem next_item)
{
static uint8_t t; // 控制绘制过程的时间变量
static uint8_t Now_Lenght; // 当前绘制长度
static uint8_t Next_Lenght; // 下一个选项的理论绘制长度
// 根据下一个项的id和位置长度计算其理论绘制长度
Next_Lenght = (VER_RES / (float)(next_item->page.location->length)) * next_item->id;
t++; // 时间变量递增
// 使用线性插值计算当前的绘制长度
Now_Lenght = Linear(AnimationParam.Dialog_AllTime, t, Next_Lenght, Now_Lenght);
// 绘制选项移动的指示线
OLED_DrawLine(HOR_RES - 7, 0, HOR_RES - 7, VER_RES);
// 根据计算出的长度,绘制当前选项的高亮框
OLED_DrawBox(HOR_RES - 10, 0, 6, Now_Lenght);
// 如果绘制时间达到预设的Dialog_Time重置时间变量并返回true
if(t == AnimationParam.Dialog_AllTime)
{
t = 0;
return true;
}
return false;
}
/**
* @brief 绘制指定位置的页面
*
* @param pos 页面绘制的起始位置
* @param Page 指向当前页面结构体的指针
* @param LineSpacing 行间距
* @param now_item 当前选中的项目
* @param next_item 下一个将要选中的项目
*/
static void Draw_Page(uint8_t pos, xpPage Page, xpItem now_item, xpItem next_item)
{
char Data[10] = {0}; // 用于临时存储数据字符串
static int16_t first_line = FirstLine; // 页面中第一行的起始位置
xpItem temp = Page->item.head; // 从页面的头部开始遍历
// 初始化或计算页面滚动时的起始绘制位置
if(Menu.menu_state == MENU_RUN)
{
// 页面切换时重置第一行位置
if(next_item->page.location != now_item->page.location)first_line = FirstLine;
// 计算页面滚动的距离
if (Menu.page_state == PAGE_MOVING)
{
if ((next_item->id - now_item->id) > 0)first_line -= ((next_item->id - now_item->id) > (Page->length - MaxVisible_Number)) ? ((Page->length - MaxVisible_Number) * Font_Size) : Font_Size;
else first_line += ((now_item->id - next_item->id) > (Page->length - MaxVisible_Number)) ? ((Page->length - MaxVisible_Number) * Font_Size) : Font_Size;
Menu.page_state = PAGE_STATIC; // 更新页面状态为静态显示
}
}
// 遍历页面中的所有项目并绘制
for (uint16_t i = 0; i <= Page->length; i++)
{
OLED_DrawStr(pos, first_line + i * Font_Size, temp->itemName); // 绘制项目名称
// 根据项目类型进行特殊绘制处理
if(temp->itemType == SWITCH)
{
// 绘制开关状态
if(temp->switchState == false)OLED_DrawFrame(pos + 95, first_line + i * Font_Size - Font_Size + 3, 10, 10);
else OLED_DrawBox(pos + 95, first_line + i * Font_Size - Font_Size + 3, 10, 10);
}
if(temp->itemType == DATA)
{
// 格式化并绘制数据
sprintf(Data, "%d", *temp->data.ptr);
OLED_DrawStr(pos + 95, first_line + i * Font_Size, Data);
}
temp = temp->nextItem; // 移动到下一个项目
}
}
/**
* 绘制菜单界面
* 该函数负责在 OLED 屏幕上绘制菜单界面,包括计算各项的位置、绘制背景、选项及其高亮显示。
* 根据不同的菜单状态(如页面移动、选项切换),动态调整选项的显示位置,实现平滑的动画效果。
* @param pos 当前页面位置
* @param Page 当前页面结构体
* @param LineSpacing 行间距
* @param now_item 当前选中项
* @param next_item 下一个将要选中的项
*/
static void Draw_Menu(uint8_t pos, xpPage Page, xpItem now_item,xpItem next_item)
{
// 初始化或更新动画参数及页面状态
if(Menu.menu_state == MENU_RUN)
{
// 计算当前项名称的宽度
AnimationParam.Item_NowWide = strlen(now_item->itemName)*6 + 4;
// 切换页面时进行初始化并设置页面状态为静态
if(next_item->page.location != now_item->page.location)
{
Item_AnimationParam_Init();
Menu.page_state = PAGE_STATIC;
}
else // 在同一页面内切换选项时的处理
{
// 根据选项的id差值计算目标行位置并限制在可视范围内
if (next_item->id > now_item->id)
{
AnimationParam.Item_TargrtLine += ((next_item->id - now_item->id)*Font_Size);
if (AnimationParam.Item_TargrtLine > LINE_MAX) // 防止光标溢出可视范围
{
Menu.page_state = PAGE_MOVING;
AnimationParam.Item_TargrtLine = LINE_MAX;
}
}
else if(next_item->id < now_item->id)
{
AnimationParam.Item_TargrtLine -= ((now_item->id - next_item->id)*Font_Size);
if (AnimationParam.Item_TargrtLine < 0) // 防止光标溢出可视范围
{
Menu.page_state = PAGE_MOVING;
AnimationParam.Item_TargrtLine = 0;
}
}
}
}
// 计算下一个将要选中项的名称宽度
AnimationParam.Item_TargrtWide = OLED_GetStrWidth(next_item->itemName) + 3;
// 开始绘制菜单界面
OLED_ClearBuffer(); // 清除屏幕缓冲区
OLED_SetDrawColor(Menu.BgColor); // 设置背景颜色
OLED_DrawBox(0, 0, 128, 64); // 绘制屏幕背景框
OLED_SetDrawColor(Menu.BgColor^0x01); // 设置绘制颜色为高亮或低亮
AnimationParam.OptionState = Draw_OptionPlace(now_item, next_item); // 绘制选项及其位置
Draw_Page(pos, Page, now_item, next_item); // 绘制页面内容
OLED_SetDrawColor(2); // 设置特定的颜色,通常用于高亮显示
// 根据目标位置和当前位置以及PID算法计算并更新当前选项的位置和宽度
AnimationParam.Item_NowLine = PID(AnimationParam.Item_TargrtLine, AnimationParam.Item_NowLine, &Cursor_Line);
AnimationParam.Item_NowWide = PID(AnimationParam.Item_TargrtWide, AnimationParam.Item_NowWide, &Cursor_Wide);
// 绘制选中项的高亮边框
OLED_DrawBox(pos+1, AnimationParam.Item_NowLine+2, AnimationParam.Item_NowWide, Font_Size);
OLED_SendBuffer(); // 将缓冲区的内容发送到OLED屏幕显示
// 更新菜单状态为绘制中
Change_MenuState(MENU_DRAWING);
// 如果动画参数达到目标值且选项绘制完成,则更新菜单状态为运行中
if ((AnimationParam.Item_NowLine == AnimationParam.Item_TargrtLine) && (AnimationParam.Item_NowWide == AnimationParam.Item_TargrtWide) && (AnimationParam.OptionState == true))
{
Change_MenuState(MENU_RUN);
}
}
/*
* 菜单构建函数
* 该函数不接受参数,也不返回任何值。
* 功能:静态地构建一个菜单系统。
*/
static void Menu_Team(void)
{
AddPage("[Home]", &Menu, &Home_Page, &HomeHead_Item, &Home_Page, NULL, logo_callback );
AddItem(" +Setting", PARENTS, NULL, &setting_item, &Home_Page, &setting_page, NULL);
AddPage("[Exit]", &Menu, &setting_page, &setting_exit_item, &setting_page, &Home_Page, NULL);
AddItem(" -Work Mode", DATA, &user_config.work_mode, &work_mode_item, &setting_page, NULL, work_mode_callback );
AddItem(" -Rate Mode", DATA, &user_config.rate_mode, &rate_mode_item, &setting_page, NULL, rate_mode_callback );
AddItem(" -Channel", DATA, &user_config.channel, &channel_item, &setting_page, NULL, channel_callback );
AddItem(" -TX Power", DATA, &user_config.tx_power, &tx_power_item, &setting_page, NULL, tx_power_callback);
AddItem(" -TX Count", DATA, &user_config.tx_count, &tx_count_item, &setting_page, NULL, tx_count_callback);
AddItem(" -Back Color", SWITCH, NULL, &bg_color_item, &setting_page, NULL, background_color_callback );
AddItem(" -Tx XTELL", LOOP_FUNCTION, NULL, &tx_mode_item, &Home_Page, NULL, tx_mode_callback);
AddItem(" -Rx XTELL", LOOP_FUNCTION, NULL, &rx_mode_item, &Home_Page, NULL, rx_mode_callback);
AddItem(" -Version", LOOP_FUNCTION, NULL, &version_item, &Home_Page, NULL, version_callback);
AddItem(" -Reset", LOOP_FUNCTION, NULL, &reset_item, &Home_Page, NULL, reset_callback);
}
/* 在此填入按键扫描程序
* 功能:执行按键扫描,根据接收到的数据确定按键方向
* 参数:无
* 返回值Menu_Direction 枚举类型表示按键的方向没有按键按下时返回MENU_NONE
*/
static Menu_Direction BtnScan(void)
{
if( key_check_press( KEY_NAME_UP ) == true )
{
buzzer_button_press();
return MENU_UP;
}
if( key_check_press( KEY_NAME_DOWN ) == true )
{
buzzer_button_press();
return MENU_DOWN;
}
if( key_check_press( KEY_NAME_ENTER ) == true )
{
buzzer_button_press();
return MENU_ENTER;
}
return MENU_NONE;
}
/**
* @brief 根据菜单方向运行处理菜单逻辑
*
* @param Dir 菜单操作方向,包括上、下、进入等操作
*/
void Process_Menu_Run(Menu_Direction Dir)
{
uint8_t disapper = 0;
switch (Menu.menu_state) // 根据当前菜单状态进行不同的操作
{
case MENU_RUN:
switch (Dir) // 根据操作方向更新菜单项
{
case MENU_UP:
// 向上移动菜单项,确保当前项和上一项非空
if (Menu.now_item != NULL && Menu.now_item->lastItem != NULL)
{
Draw_Menu(FirstPos, Menu.now_item->page.location, Menu.now_item, Menu.now_item->lastItem);
Menu.old_item = Menu.now_item;
Menu.now_item = Menu.now_item->lastItem;
}
break;
case MENU_DOWN:
// 向下移动菜单项,确保当前项和下一项非空
if (Menu.now_item != NULL && Menu.now_item->nextItem != NULL)
{
Draw_Menu(FirstPos, Menu.now_item->page.location, Menu.now_item, Menu.now_item->nextItem);
Menu.old_item = Menu.now_item;
Menu.now_item = Menu.now_item->nextItem;
}
break;
case MENU_ENTER:
// 当前项非空且为父项时,进入下一级菜单
if(Menu.now_item != NULL && Menu.now_item->itemType == PARENTS)
{
// 避免重复状态改变
if(Menu.menu_state != MENU_ENTER) Change_MenuState(MENU_RUN);
for (size_t i = 0; i < 8; i++) // 执行UI淡出操作
{
disapper = ui_disapper(disapper);
}
// 如果存在跳转页面且不为空,则绘制跳转页面并更新当前项
if(Menu.now_item->page.jumpPage != NULL && Menu.now_item->page.jumpPage->item.head != NULL)
{
Draw_Menu(FirstPos, Menu.now_item->page.jumpPage, Menu.now_item, Menu.now_item->page.jumpPage->item.head);
Menu.old_item = Menu.now_item;
Menu.now_item = Menu.now_item->page.jumpPage->item.head;
}
}
else // 如果当前项为空或非父项,则进入应用绘制状态
{
ui_disapper(1);
Change_MenuState(APP_DRAWING);
}
break;
default:
Draw_Menu(FirstPos, Menu.now_item->page.location, Menu.now_item, Menu.now_item);
break;
}
break;
case APP_QUIT:
// 退出应用时的初始化操作,并返回菜单运行状态
DialogScale_AnimationParam_Init();
Change_MenuState(MENU_RUN);
for (size_t i = 0; i < 8; i++)
{
disapper = ui_disapper(disapper); // 执行UI淡出操作
}
if(Menu.now_item != NULL) // 确保now_item非空
{
Draw_Menu(FirstPos, Menu.now_item->page.location, Menu.now_item, Menu.now_item);
}
break;
case MENU_DRAWING:
// 如果当前正在绘制菜单,则根据方向进行状态更新和菜单重绘
if(Menu.now_item != NULL && Menu.old_item != NULL)
{
Draw_Menu(FirstPos, Menu.now_item->page.location, Menu.old_item, Menu.now_item);
if (Dir != MENU_NONE)
{
Change_MenuState(MENU_RUN);
Process_Menu_Run(Dir);
}
}
break;
default:
break;
}
}
/**
* @brief 处理应用程序的运行状态。
* 对给定的项目(item)根据其类型(ItemType)和当前的菜单状态(State)执行相应的操作。
* @param item 指向要处理的项目的指针。
* @param State 当前菜单的方向或状态。
*/
void Process_App_Run(xpItem item, Menu_Direction State)
{
if (item == NULL) return; // 检查空指针,避免未定义行为
item->state = State; // 设置项目的状态为当前菜单状态
switch (item->itemType) // 根据项目类型执行不同的操作
{
case DATA:
case LOOP_FUNCTION:
if (item->itemFunction != NULL)(item->itemFunction)(item); // 执行项目的函数
if(item->state == MENU_ENTER)Change_MenuState(APP_QUIT); // 如果项目状态为进入菜单,则改变菜单状态为函数退出
break;
case SWITCH:
item->switchState = ! item->switchState; // 切换开关状态
case ONCE_FUNCTION:
if (item->itemFunction != NULL)(item->itemFunction)(item); // 如果项目有函数,则执行该函数
Change_MenuState(APP_QUIT); // 改变菜单状态为函数退出
break;
default:
break; // 对未知类型不执行任何操作
}
}
/**
* 菜单任务处理函数
* 该函数负责根据当前按钮扫描结果更新菜单状态,并进行相应的绘制或处理操作。
* 无参数
* 无返回值
*/
void Menu_Task(void)
{
Menu_Direction Dir = BtnScan(); // 扫描按钮方向,确定菜单操作方向
if (Menu.menu_state == MENU_INIT && Dir != MENU_NONE)
{
// 初始化状态下非空检查now_item并绘制当前菜单项
if (Menu.now_item != NULL)
{
Draw_Menu(FirstPos, Menu.now_item->page.location, Menu.now_item, Menu.now_item);
}
else
{
printf("Menu.now_item is NULL\n"); // 打印错误信息now_item为空
}
}
else
{
switch (Menu.menu_state)
{
case MENU_INIT:break;
case APP_RUN:
case APP_DRAWING:
// 在这两个状态下,处理应用程序运行逻辑
Process_App_Run(Menu.now_item, Dir);
if (Menu.menu_state == APP_DRAWING)
{
break; // 在绘制状态下,终止进一步流程执行
}
// 故意不写 break以允许流程进入下方的共同逻辑处理如果存在
case MENU_RUN:
case APP_QUIT:
case MENU_DRAWING:
// 处理菜单运行、应用退出和菜单绘制状态
Process_Menu_Run(Dir);
break;
default:
break;
}
}
}
void Menu_Init(void)
{
Disp_Init();
Menu_Team();
logo_callback( NULL );
}

View File

@ -0,0 +1,16 @@
#ifndef __MENU_H__
#define __MENU_H__
#include "stdbool.h"
#include "menuConfig.h"
void Draw_DialogBox(uint16_t x,uint16_t y,uint16_t w,uint16_t h);
void Draw_DialogRBox(uint16_t x,uint16_t y,uint16_t w,uint16_t h,uint16_t r);
bool DialogScale_Show(uint8_t x,uint8_t y,uint8_t Targrt_w,uint8_t Targrt_h);
void Set_BgColor(uint8_t color);
uint8_t Get_BgColor(void);
int Draw_Scrollbar(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, double min, double max, int step, int NowValue);
void Menu_Task(void);
void Menu_Init(void);
#endif

View File

@ -0,0 +1,178 @@
#ifndef _MENUCONFIG_H_
#define _MENUCONFIG_H_
#include "u8g2.h"
#include "stdbool.h"
// 屏幕分辨率定义
#define HOR_RES 128 // 水平分辨率
#define VER_RES 64 // 垂直分辨率
// 菜单字体定义
#define MENU_FONT u8g2_font_profont12_mf
// 字体尺寸定义
#define Font_Size 12 // 字体高度
// 起始Y坐标基于字体大小设定的第一行位置
#define FirstLine Font_Size
// 起始X坐标
#define FirstPos 0
// 最大可见项目数,除去标题栏
#define MaxVisible_Number 4 // 可见菜单项数量
// 最大Y坐标根据字体大小和最大可见项目数计算得出
#define LINE_MAX Font_Size*MaxVisible_Number
// 最小Y坐标即第一行的位置
#define LINE_MIN Font_Size
/**
* Pid_Error 结构体定义
* 用于存储PID控制器的误差及调整参数
*/
typedef struct PidError
{
int kp; // 比例增益
int ki; // 积分增益
int kd; // 微分增益
float error; // 当前误差
float sum_srror; // 积累的误差
float last_error; // 上一次的误差
}Pid_Error;
// 菜单状态枚举: 定义了菜单及应用程序的不同运行状态
typedef enum MenuState
{
MENU_INIT, // 菜单初始化状态
MENU_DRAWING, // 菜单绘制状态
MENU_RUN, // 菜单运行状态
APP_RUN, // 应用程序运行状态
APP_DRAWING, // 应用程序绘制状态
APP_QUIT // 应用程序退出状态
} Menu_State;
// 页面状态枚举
// 用于表示页面的两种状态:移动和静止
typedef enum PageState
{
PAGE_MOVING, // 页面正在移动
PAGE_STATIC // 页面处于静止状态
}Page_State;
// 菜单运动方向枚举
// 这个枚举定义了菜单系统中可能的方向和操作,包括向上、向下、进入和退出等操作。
typedef enum MenuDirection
{
MENU_NONE, // 无操作
MENU_UP, // 向上
MENU_DOWN, // 向下
MENU_ENTER // 进入或选择
} Menu_Direction;
// 菜单项目类型枚举
typedef enum ItemType
{
// 父菜单项: 代表菜单中的顶级项或父级项,通常不直接与用户交互,而是作为其他菜单项的容器。
PARENTS = 0,
// 循环功能项: 代表一种循环执行的功能,在菜单系统中用于循环播放、轮询等操作。
LOOP_FUNCTION,
// 一次性功能项: 代表仅执行一次的功能,执行后即被标记为完成,不再重复执行。
ONCE_FUNCTION,
// 开关切换项: 代表菜单中的开关选项,可用于启用或禁用某个功能或设置。
SWITCH,
// 数据项: 代表菜单中用于显示或设置数据的项,可以是数值、文本等各种形式的数据。
DATA
} Item_Type;
//菜单页面类型枚举
typedef enum
{
FIGURE,
TEXT
}Page_Type;
// 动画参数
typedef struct AnimationParam
{
// 对话框总时间(单位:毫秒)
uint8_t Dialog_AllTime;
// 对话框缩放时间(单位:毫秒)
uint8_t DialogScale_time;
// 对话框初始宽度(单位:像素)
uint8_t DialogScale_InitWide;
// 对话框初始高度(单位:像素)
uint8_t DialogScale_InitHigh;
// 当前项宽度(单位:像素)
int Item_NowWide;
// 当前项高度(单位:行数)
int Item_NowLine;
// 目标项高度(单位:行数)
int Item_TargrtLine;
// 目标项宽度(单位:像素)
int Item_TargrtWide;
// 选项条状态
bool OptionState;
} Animation_Param;
typedef struct Page *xpPage;
typedef struct Item *xpItem;
typedef struct Menu *xpMenu;
typedef void (*ItemFunction)(xpItem);
/**
* 定义一个名为Page的结构体类型
* 用于表示页面信息
*/
typedef struct Page {
const char *pageName; // 页面名称,使用指针存储
uint8_t length; // 页面长度
uint8_t id; // 页面ID
// 使用内联函数封装成员变量,以提供更高的数据封装性和保护性
struct {
xpItem parent; // 父级项
xpItem head; // 头部项
xpItem tail; // 尾部项
} item;
Page_Type pageType; // 页面类型
xpMenu menu; // 页面关联的菜单
} xPage;
// 定义一个名为Item的结构体类型
typedef struct Item {
const char *itemName; // 结构体成员变量项目名称使用const char*类型
Item_Type itemType; // 结构体成员变量项目类型自定义的Item_Type类型
bool switchState; // 结构体成员变量开关状态使用bool类型
// 封装data指针以确保数据的完整性
struct {
int *ptr; // 指向整型数据的指针
size_t size; // 指针指向数据的大小使用size_t类型
} data;
uint8_t id; // 结构体成员变量项目ID使用uint8_t类型
// 使用内联函数封装page结构体以保护成员变量
struct {
xpPage location; // 页面位置
xpPage jumpPage; // 跳转页面
} page;
xpItem lastItem, nextItem; // 上一个项目和下一个项目的指针
ItemFunction itemFunction; // 函数指针指向一个接受xpItem类型参数的函数
Menu_Direction state; // 菜单方向状态
uint8_t *logo; // 结构体成员变量:项目关联的图片指针
} xItem;
/**
* xMenu结构体定义
* 用于表示一个菜单的相关信息
*/
typedef struct Menu {
size_t page_length; // 菜单页面的数量
xpItem now_item; // 当前选中的item
xpItem old_item; // 上一个选中的item
Menu_State menu_state; // 菜单的状态
Page_State page_state; // 页面的状态
uint8_t BgColor; // 菜单的背景颜色
} xMenu;
#endif