跳转至

菜单

总体目标与功能:

这套代码的目的是在一个嵌入式设备(很可能是单片机)的LCD屏幕上实现一个可操作的菜单系统。用户可以通过物理按键(上、下、左、右、确认等)来浏览菜单、选择项目、执行特定功能(如开关蜂鸣器、显示图像)或者调整参数。这些参数设置最终可以保存到非易失性存储器(代码中称为EEPROM,但实际可能是Flash)中,并在下次启动时加载。


由浅入深:核心概念与数据结构

我们先从 lcd_menu.h 头文件中定义的关键构建模块开始:

  1. MENU_PRMT (菜单参数结构体):

    typedef struct{
        uint8 ExitMark;     // 退出菜单标记 (0-不退出,1-退出)
        uint8 Cursor;       // 光标值 (当前光标在当前页的位置)
        uint8 PageNo;       // 菜单页 (当前显示的菜单是总菜单项中的第几页起始)
        uint8 Index;        // 菜单索引 (当前选择的菜单项在整个菜单中的绝对位置)
        uint8 DispNum;      // 显示项数 (当前菜单页实际显示的菜单项数量)
        uint8 MaxPage;      // 最大页数 (当前菜单总共需要多少页来显示)
    }MENU_PRMT;
    

    • 作用: 这个结构体保存了当前活动菜单(或子菜单)的**状态**。每个菜单(如主菜单、实验七菜单)都会有自己的 MENU_PRMT 实例来跟踪其显示和选择状态。
    • ExitMark: 标志当前子菜单是否应该退出(例如,用户按“返回”键时)。
    • Cursor: 高亮选择器在菜单*当前页*的位置(从0到 DispNum-1)。
    • PageNo: 如果一个菜单的条目数超过一屏能显示的数量 (PAGE_DISP_NUM),这个字段指示当前显示的是第几“页”的条目。屏幕上显示的第一个条目是完整菜单列表中的第 PageNo 个(以0为基准,但实际显示逻辑是 PageNo 加上页内偏移)。
    • Index: 当前选中的菜单项在*该菜单所有页面中*的绝对索引。通常计算为 Cursor + PageNo * PAGE_DISP_NUM(或者根据 Menu_Move 的逻辑是 Cursor + PageNo,这里 PageNo 更像是指当前页显示的第一个菜单项在整个菜单列表中的真实起始索引,具体看 Menu_MoveMenu_Display 的实现)。查看 Menu_Moveprmt->Index = prmt->Cursor + prmt->PageNo;,这表明 PageNo 可能是当前显示页的第一个菜单项在完整列表中的偏移量,而不是纯粹的页码。 更正:仔细看 Menu_DisplaymenuTable[pageNo + i],这里的 pageNo 确实是指当前页显示的第一个菜单项在整个菜单列表中的索引。 所以 Index 应该是 prmt->Cursor + prmt->PageNo
    • DispNum: *这个特定菜单*在单页上显示的菜单项数量。它可以小于或等于 PAGE_DISP_NUM
    • MaxPage: 这个特定菜单*显示所有项目所需的总页数。更准确地说,在 Menu_PrmtInit 中,prmt->MaxPage = page;,而 pageMenu_Process 中计算为 num - PAGE_DISP_NUM + 1(如果多于一页),这表示的是**可以滚动的起始行数*,或者说,如果一页显示 PAGE_DISP_NUM 个,总共有 N 个,那么可以有多少个不同的起始显示位置。例如,10个项,一页7个,则 page = 10 - 7 + 1 = 4。这意味着有4个可能的起始显示位置(第0项开始,第1项开始,第2项开始,第3项开始)。
  2. MENU_TABLE (菜单项定义结构体):

    typedef struct{
        uint8 *MenuName;        // 菜单项目名称 (字符串)
        void(*ItemHook)(void);  // 要运行的菜单函数 (函数指针)
        uint16 *DebugParam;     // 要调试的参数 (指向参数的指针)
    }MENU_TABLE;
    

    • 作用: 这个结构体定义了菜单中的一个单独条目。一个这种结构体的数组就构成了一个完整的菜单。
    • MenuName: 一个字符串(指向 uint8 的指针),表示此菜单项显示的文本(例如,"1.BEEP ON ")。
    • ItemHook: 一个**函数指针**。这是核心。它指向当此菜单项被选中时将执行的函数。
      • 如果它是 Menu_Null,则意味着此项可能是用于参数调整(如果 DebugParam 已设置)或它是一个占位符。
      • 如果它指向另一个菜单函数(例如,Menu_exp7),选择此项将导航到该子菜单。
      • 如果它指向一个动作函数(例如,BEEP_on),选择此项将执行该动作。
    • DebugParam: 一个指向 uint16 类型变量的指针。如果 ItemHookMenu_Null 并且此指针不是 NULL,则选择此菜单项将允许用户直接修改它所指向的变量的值。
  3. Site_t (屏幕坐标结构体):

    typedef struct{
        uint8 x;
        uint8 y;
    }Site_t;
    

    • 作用: 一个简单的结构体,用于保存X和Y坐标,很可能用于在LCD上定位文本或元素。
  4. 常量:

    • PAGE_DISP_NUM (7): 单个屏幕页面上最多可以显示的菜单项数量。
    • FLASH_SECTION_INDEX (0), FLASH_PAGE_INDEX (8): 似乎与Flash存储器组织有关,但 FLASH_PAGE_INDEX 在注释中被标记为“倒数第一个页码”,而在 Write_EEPROMRead_EEPROM 函数中,明确使用了扇区 0 的页 11。这可能表示一个旧的配置,或者是为数据缓冲页特意选择的。
  5. 外部变量 (Extern Variables):

    • extern uint16 beep_time_ms;: 蜂鸣器鸣叫的持续时间,在其他地方定义。
    • extern int16 mt9v03x_set_config_buffer[MT9V03X_CONFIG_FINISH][2];: 一个二维数组,可能存储MT9V03X摄像头传感器的配置参数。
    • extern keymsg;: (从 KeySan 的使用中可以推断,通常在 key.h 中定义) 一个全局结构体,用于保存按键的当前状态(例如,哪个键被按下,是新按下还是按住)。
  6. 全局变量 (在 lcd_menu.c 文件内):

    • volatile uint8 ExitMenu_flag;: 一个标志,用于指示退出*整个*菜单系统,而不仅仅是一个子菜单。volatile 关键字暗示它可能被中断服务程序修改。
    • uint32 *EEPROM_DATA[]: 一个指针数组。每个元素指向一个变量(如 beep_time_msmt9v03x_set_config_buffer 的元素),这些变量的值需要保存到Flash或从Flash加载。这是持久化设置的核心列表。

核心菜单逻辑与流程

系统通过将菜单定义为 MENU_TABLE 结构体数组,然后处理这些数组来工作。

  1. 定义菜单:

    • 全局声明了几个 MENU_PRMT 结构体(例如 MainMenu_Prmt, exp7_Prmt)。每个结构体将保存其对应菜单的运行时状态。
    • 定义了几个 MENU_TABLE 数组(例如 MainMenu_Table, exp7_MenuTable)。这些数组静态地定义了每个菜单的条目:
      • 示例:MainMenu_Table
        MENU_TABLE MainMenu_Table[] =
        {
            {(uint8 *)"1.NULL          ", Menu_Null, NULL},         // 占位符或直接调参
            {(uint8 *)"2.Exp7          ", Menu_exp7, NULL},         // 导航到Exp7菜单
            {(uint8 *)"3.Exp8          ", Menu_exp8, NULL},         // 导航到Exp8菜单
            // ... 等等
        };
        
      • 注意 Menu_exp7 (一个函数)是如何被用作 ItemHook 来转换到“实验七”子菜单的。
      • exp8_MenuTable 中:
        {(uint8 *)"1.beep time", Menu_Null, (uint16 *)&beep_time_ms},
        
        这里,ItemHookMenu_Null,但 DebugParam 指向 beep_time_ms。这意味着选择“beep time”将允许直接调整 beep_time_ms 变量。
  2. 启动菜单系统 (MainMenu_Set):

    • 这个函数似乎是设置菜单的主入口点。
    • 它首先检查开关(GET_SWITCH1(), GET_SWITCH2())来询问用户是否从“EEPROM”(Flash)读取数据。
      • 如果 GET_SWITCH1() 为真,则调用 Read_EEPROM()
    • 清除 ExitMenu_flag
    • 计算 MainMenu_Table 中的项目数。
    • 调用 Menu_Process() 来显示和管理主菜单。
    • 关键点:在 Menu_Process() 返回后(意味着用户已退出主菜单及其所有子菜单),调用 Write_EEPROM() 来保存任何已更改的设置。
    • 最后,屏幕填充红色,可能表示设置模式已完成。
  3. 系统的核心: Menu_Process():

    void Menu_Process(uint8 *menuName, MENU_PRMT *prmt, MENU_TABLE *table, uint8 num)
    

    • 这是一个通用函数,用于处理任何菜单。
    • 初始化:
      • 它根据 num (总项目数) 和 PAGE_DISP_NUM 来确定分页。
      • 它调用 Menu_PrmtInit() 来初始化 prmt 结构体(光标清零,页码清零等),为这个特定的菜单做准备。
    • 主循环 (do...while):
      1. tft180_show_string(): 显示 menuName (菜单标题)。
      2. Menu_Display(): 渲染菜单项的当前页,高亮显示 prmt->Cursor 指向的位置。
      3. KeySan(): 等待并获取按键按下的信息。
      4. Menu_Move(): 根据按下的键更新 prmt (光标、页码、退出标记)。
        • 如果 Menu_Move() 返回 0,意味着“确认/回车”键被按下。
      5. 执行动作/导航:
        • 如果按下了“确认”键:
          • 它会检查 table[prmt->Index].DebugParamtable[prmt->Index].ItemHook
          • 如果 DebugParam 不是 NULL 并且 ItemHookMenu_Null:
            • 这意味着此项用于参数调整。
            • 调用 adjustParam() 来允许用户更改 *(table[prmt->Index].DebugParam) 的值。
          • 否则 (即 ItemHook 不是 Menu_Null,或者它是 Menu_NullDebugParam 也是 NULL):
            • 执行 table[prmt->Index].ItemHook() 指向的函数。这可能是一个动作,或是一个本身会调用 Menu_Process 以进入子菜单的函数 (例如 Menu_exp7())。
      6. 循环继续,只要 prmt->ExitMark0 (用户未选择退出此子菜单) 并且 ExitMenu_flag0 (用户未选择退出整个菜单系统)。
    • 当循环结束时,tft180_clear() 清除屏幕。
  4. 显示菜单项 (Menu_Display()):

    • 0 迭代到 dispNum - 1 (当前页的项目数,dispNumprmt->DispNum)。
    • 对于每个项目:
      • 使用 menuTable[pageNo + i]menuTable 中确定实际项目 (pageNoprmt->PageNoi 是循环变量)。
      • 如果 cursor == i (当前项被选中),则以一组颜色显示 (例如,蓝底白字)。
      • 否则,以另一组颜色显示 (例如,白底蓝字)。
      • 如果 menuTable[pageNo + i].DebugParam 不是 NULL,则该参数的当前值也会显示在菜单项名称旁边。
  5. 处理按键:

    • KeySan():
      • 一个阻塞函数,它会一直等待,直到 keymsg.status不再是 KEY_UP (意味着一个键被按下) 或者 ExitMenu_flag 变为真。
      • 然后它重置 keymsg.status = KEY_UP (为下一次按键检测做准备,假设 keymsg 由中断或独立的轮询任务更新)。
      • 返回 keymsg.key 的值 (哪个键被按下了)。
    • Menu_Move():
      • 接收当前菜单的 MENU_PRMT 和按下的 key
      • 一个 switch 语句处理不同的按键:
        • KEY_U (上): 光标上移。如果在页面顶部,尝试转到上一页。如果在第一个项目,则循环到最后一个项目。
        • KEY_D (下): 光标下移。如果在页面底部,尝试转到下一页。如果在最后一个项目,则循环到第一个项目。
        • KEY_B (确认/回车): 将 prmt->Index 设置为当前选定项目的总索引,并返回 0 以示确认。
        • KEY_L (左/返回): 设置 prmt->ExitMark = 1,导致当前的 Menu_Process 循环终止,从而有效地“返回”上一级菜单。
        • KEY_R (右): 直接跳转到最后一页的最后一个项目。
      • 默认返回 1,仅当按下 KEY_B 时返回 0
  6. 参数调整 (adjustParam()):

    • 当选择了用于直接参数调整的菜单项时,调用此函数。
    • 它进入一个循环:
      1. KeySan(): 获取按键。
      2. 一个 switch 语句根据按键修改 *param 的值:
        • KEY_U: 增加1。
        • KEY_D: 减少1。
        • KEY_L: 减少10。
        • KEY_R: 增加10。
      3. tft180_show_uint(): 在LCD上更新显示的值。
      4. 循环继续,直到按下 KEY_B (确认),此时调整完成。
  7. 子菜单函数 (例如 Menu_exp7, Menu_Camera_Setting):

    • 这些是简单的包装函数。
    • 它们通常:
      1. 清除LCD (tft180_clear())。
      2. 计算其特定 MENU_TABLE (例如 exp7_MenuTable) 中的项目数 (menuNum)。
      3. 使用它们自己的标题、MENU_PRMT 实例、MENU_TABLE 数组和 menuNum 来调用 Menu_Process()。这有效地“启动”了子菜单。

深入细节:特定功能详解

  1. EEPROM / Flash 操作 (Write_EEPROM, Read_EEPROM):

    • EEPROM_DATA 数组: 这是关键。它列出了所有需要持久化存储的变量的内存地址。

      uint32 *EEPROM_DATA[] = {
          (uint32 *)&beep_time_ms, // beep_time_ms 是 uint16
          (uint32 *)&mt9v03x_set_config_buffer[1][1], // 这个元素是 int16
          // ... 更多摄像头参数
      };
      
      重要提示:关于类型转换: 变量 beep_time_msmt9v03x_set_config_buffer 的元素是 uint16int16。然而,它们在这个数组中被强制转换为 uint32*。这有点不寻常,但似乎是一种在 EEPROM_DATA 数组中统一处理这些指针的方式。实际读写的数据仍被视为 uint16

    • Write_EEPROM():

      1. 计算 EEPROM_DATA_NUM (有多少设置需要保存)。
      2. flash_erase_page(0, 11): 擦除扇区0的第11页。此页充当所有设置的专用缓冲区/存储区。
      3. 它遍历 EEPROM_DATA。对于每个设置:
        • data_ptr = (uint32 *)EEPROM_DATA[pageNum];
        • flash_union_buffer[pageNum].uint32_type = (uint16)*data_ptr;
          • 这一行很关键:它解引用 data_ptr (它指向一个 uint16int16 变量,尽管在数组中其类型是 uint32*),并将值**强制转换为 uint16**,然后存储在 flash_union_buffer 中。flash_union_buffer 很可能是一个RAM中的数组(或允许类似数组访问的联合体)。
      4. flash_write_page_from_buffer(0, 11): 将 flash_union_buffer 的全部内容(现在包含所有设置为 uint16 的值)写入先前擦除的扇区0的第11页。
      5. flash_buffer_clear(): 清除RAM缓冲区。
      6. 策略: 所有设置都被打包到一个RAM缓冲区中,然后一次性写入到Flash的单个页面。
    • Read_EEPROM():

      1. 计算 EEPROM_DATA_NUM
      2. flash_buffer_clear(): 清除RAM缓冲区。
      3. flash_read_page_to_buffer(0, 11): 将Flash第11页(扇区0)的全部内容读入 flash_union_buffer
      4. 它遍历 EEPROM_DATA。对于每个设置:
        • uint32 temp_vaule = flash_union_buffer[pageNum].uint32_type; (从缓冲区读取 uint16 值,尽管可能存储在联合体的 uint32 字段中)。
        • 一个简单的健全性检查:if((uint16)temp_vaule < 5000)。这是一个非常基础的验证。
        • data_ptr = (uint32 *)EEPROM_DATA[pageNum];
        • *data_ptr = (uint16)temp_vaule;*data_ptr = 0; (如果健全性检查失败)。
          • 从缓冲区读取的值(以 uint16 形式存储)被强制转换为 uint16,然后写回到 data_ptr 指向的实际变量中。
      5. 策略: 从Flash读取整个设置页面到RAM缓冲区,然后将各个设置从该缓冲区复制回其在RAM中的相应变量。
  2. 蜂鸣器函数 (BEEP_init, BEEP_on, BEEP_off):

    • 标准的GPIO(通用输入/输出)操作。
    • BEEP_init(): 将 BEEP_ENABLE 引脚配置为推挽输出,初始状态为关闭(电平0)。
    • BEEP_on(): 将 BEEP_ENABLE 引脚设置为高电平(电平1)。
    • BEEP_off(): 将 BEEP_ENABLE 引脚设置为低电平(电平0)。
  3. 图像显示函数 (image_show_rgb, image_show_gray):

    • 这些是简单的包装函数,调用底层的LCD函数 (tft180_show_rgb565_image, tft180_show_gray_image) 来显示可能从 mt9v03x 摄像头捕获的图像。它们传递图像数据 (mt9v03x_image[0]) 和尺寸信息。
  4. Menu_Null():

    • 一个占位函数,什么也不做。用作那些用于参数调整或没有立即动作的菜单项的 ItemHook

代码流程示例 (用户选择 "Exp7" -> "BEEP ON"):

  1. 调用 MainMenu_Set()
  2. 调用 Menu_Process((uint8 *)" -= Setting =- ", &MainMenu_Prmt, MainMenu_Table, num_main_items)
    • 主菜单显示。用户使用上/下键导航。光标移动。
  3. 用户高亮显示 "2.Exp7 " 并按确认键 (KEY_B)。
    • Menu_Move() 返回 0
    • Menu_Process 中,调用 MainMenu_Table[1].ItemHook (即 Menu_exp7)。
  4. 执行 Menu_exp7():
    • tft180_clear()
    • 调用 Menu_Process((uint8 *)" -= exp7 =- ", &exp7_Prmt, exp7_MenuTable, num_exp7_items)
    • Exp7 菜单显示。用户导航。
  5. 用户高亮显示 "1.BEEP ON " 并按确认键 (KEY_B)。
    • Menu_Move() 返回 0
    • 在(当前为 exp7_MenuTable 运行的)Menu_Process 中,调用 exp7_MenuTable[0].ItemHook (即 BEEP_on)。
  6. 执行 BEEP_on(): gpio_set_level(BEEP_ENABLE, 1)。蜂鸣器打开。
  7. BEEP_on() 返回。控制权回到 Exp7 菜单的 Menu_Process 循环。Exp7 菜单重新显示。
  8. 用户按左/返回键 (KEY_L)。
    • Menu_Move() 设置 exp7_Prmt.ExitMark = 1
    • Menu_Process 中(针对 Exp7 的)do...while 循环终止。
    • tft180_clear()
  9. Menu_exp7() 返回。控制权回到主菜单的 Menu_Process 循环。主菜单重新显示。
  10. 用户再次按左/返回键 (KEY_L)。
    • Menu_Move() 设置 MainMenu_Prmt.ExitMark = 1
    • Menu_Process 中(针对主菜单的)do...while 循环终止。
    • tft180_clear()
  11. MainMenu_Set()Menu_Process 返回后继续执行。
  12. 调用 Write_EEPROM() 保存当前设置(例如,如果在其他菜单中更改了 beep_time_ms)。
  13. 屏幕变红。

此设计的关键优点:

  • 模块化: 通过定义新的 MENU_TABLE 数组和相应的 MENU_PRMT 结构体以及钩子函数,可以相对容易地添加新的菜单和项目。
  • 可重用性: Menu_Process 是一个通用的引擎。
  • 灵活性: 支持直接执行动作、导航到子菜单以及屏幕上的参数调整。
  • 持久性: 将设置保存到非易失性存储器。

潜在的改进点或观察点:

  • EEPROM 命名: 代码在函数名和注释中使用了 "EEPROM",但底层的函数 (flash_...) 表明它实际上使用的是Flash存储器,这在嵌入式中很常见。
  • Flash写入策略: 退出主菜单后将所有参数写入单个页面,如果许多参数发生更改,这在Flash擦除/写入周期方面是高效的。如果只有一个参数更改,它仍然会重写所有参数。对于非常频繁的单个参数更改,可以考虑更细粒度的方法,但这通常是设置菜单的一个很好的权衡。
  • 错误处理: 错误处理较少(例如,如果 flash_write_page_from_buffer 失败会怎样?)。
  • adjustParam 中的 max_param_bit: 此参数已声明,但在 adjustParam 函数中未使用。
  • 全局 ExitMenu_flag: 虽然功能正常,但过度依赖全局标志有时会使代码更难跟踪。对于这种规模的系统,可能还可以接受。
  • EEPROM_DATA 中的类型转换:EEPROM_DATA 中对 uint16* 变量使用 (uint32 *) 强制转换,以及后续的 (uint16)*data_ptr 访问,有点不合常规,但代码能够工作是因为它始终将*值*视为 uint16。这种方式集中了需要保存的项目列表。

这是一个相当全面的嵌入式菜单系统。它使用函数指针和结构化数据等常用技术,为具有有限输入/输出的设备创建了一个可管理且可扩展的用户界面。

CODE

lcd_menu.h

/*
 * lcd_menu.h
 *
 *  Created on: 2025年6月4日
 *      Author: Tymen
 */

#ifndef CODE_LCD_MENU_H_
#define CODE_LCD_MENU_H_


#include "zf_common_headfile.h"
#include "key.h"
#include "switch.h"
//#include "camera_setting.h"
#include "zf_driver_flash.h"

/******************************* 菜单结构体定义 *******************************/
typedef struct{

    uint8 ExitMark;     // 退出菜单(0-不退出,1-退出)

    uint8 Cursor;       // 光标值(当前光标位置)

    uint8 PageNo;       // 菜单页(显示开始项)

    uint8 Index;        // 菜单索引(当前选择的菜单项)

    uint8 DispNum;      // 显示项数(每页可以现在菜单项)

    uint8 MaxPage;      // 最大页数(最大有多少种显示页)

}MENU_PRMT;      // 菜单参数


typedef struct{

    uint8 *MenuName;        // 菜单项目名称

    void(*ItemHook)(void);  // 要运行的菜单函数

    uint16 *DebugParam;     // 要调试的参数

}MENU_TABLE;     // 菜单执行
/******************************* 菜单结构体定义 *******************************/

/******************************* 按键端口的枚举 *******************************/
typedef struct{

    uint8 x;
    uint8 y;

}Site_t;
/******************************* 按键端口的枚举 *******************************/

void Menu_RunMode(void);
void Menu_exp7(void);
void Menu_exp8(void);
void Menu_Camera_Setting(void);
void Menu_show_Image(void);
void image_show_rgb(void);
void image_show_gray(void);

void MainMenu_Set(void);
void Menu_Process(uint8 *menuName, MENU_PRMT *prmt, MENU_TABLE *table, uint8 num);
void Menu_PrmtInit(MENU_PRMT *prmt, uint8 num, uint8 page);
void Menu_Display(MENU_TABLE *menuTable, uint8 pageNo, uint8 dispNum, uint8 cursor);
//uint8 Menu_Move(MENU_PRMT *prmt, KEY_e key);

//KEY_e KeySan(void);
void adjustParam(Site_t site, uint16 *param, uint8 max_param_bit, uint16 Color, uint16 bkColor);

void Menu_Null(void);
void Write_EEPROM(void);
void Read_EEPROM(void);

void BEEF_init();
void BEEF_on();
void BEEF_off();

#endif /* CODE_LCD_MENU_H_ */

lcd_menu.c

/*
 * lcd_menu.c
 *
 *  Created on: 2025年6月4日
 *      Author: Tymen
 */
#include    "lcd_menu.h"

#define     PAGE_DISP_NUM             (7)                   //一页最多显示的操作函数个数
#define     FLASH_SECTION_INDEX       (0)                   // 存储数据用的扇区
#define     FLASH_PAGE_INDEX          (8)                   // 存储数据用的页码 倒数第一个页码
extern uint16      beep_time_ms;
uint32      sectorNum = 0;                                  // 不可修改的数据默认0

volatile uint8      ExitMenu_flag;
extern   int16 mt9v03x_set_config_buffer[MT9V03X_CONFIG_FINISH][2];

//蜂鸣器初始化函数
void BEEP_init()
{
    gpio_init(BEEP_ENABLE, GPO, 0, GPO_PUSH_PULL);
}

//打开蜂鸣器
void BEEP_on()
{
    gpio_set_level(BEEP_ENABLE, 1);
}

//关闭蜂鸣器
void BEEP_off()
{
    gpio_set_level(BEEP_ENABLE, 0);
}

//----------------------------------   菜单定义   --------------------------------------

//定义eeprom中存储的数据
uint32 *EEPROM_DATA[] = {
        (uint32 *)&beep_time_ms,
        (uint32 *)&mt9v03x_set_config_buffer[1][1],
        (uint32 *)&mt9v03x_set_config_buffer[2][1],
        (uint32 *)&mt9v03x_set_config_buffer[3][1],
        (uint32 *)&mt9v03x_set_config_buffer[4][1],
        (uint32 *)&mt9v03x_set_config_buffer[5][1],
        (uint32 *)&mt9v03x_set_config_buffer[6][1],
        (uint32 *)&mt9v03x_set_config_buffer[7][1],
        (uint32 *)&mt9v03x_set_config_buffer[8][1],
};

MENU_PRMT   MainMenu_Prmt;                              //定义主菜单结构
MENU_PRMT   exp7_Prmt;                                  //定义实验七的菜单结构
MENU_PRMT   exp8_Prmt;                                  //定义实验八的菜单结构
MENU_PRMT   Camera_Setting_Prmt;                        //定义摄像头参数设置的菜单结构
MENU_PRMT   show_Image_Prmt;                            //定义图像显示的菜单结构

MENU_TABLE MainMenu_Table[] =                           //定义存储主菜单中的执行函数结构体
{
        {(uint8 *)"1.NULL          ", Menu_Null, NULL},
        {(uint8 *)"2.Exp7          ", Menu_exp7, NULL},
        {(uint8 *)"3.Exp8          ", Menu_exp8, NULL},
        {(uint8 *)"4.Camera Setting", Menu_Camera_Setting, NULL},
        {(uint8 *)"5.Image  Show   ", Menu_show_Image, NULL},
};

MENU_TABLE exp7_MenuTable[] =                           //定义存储实验七中的执行函数结构体
{
        {(uint8 *)"1.BEEP ON    ", BEEP_on  , NULL},
        {(uint8 *)"2.BEEP OFF   ", BEEP_off , NULL},
};

MENU_TABLE exp8_MenuTable[] =                           //定义存储实验八中的执行函数结构体
{
        {(uint8 *)"1.beep time", Menu_Null, (uint16 *)&beep_time_ms},
};

MENU_TABLE Camera_Setting_MenuTable[] =                 //定义存储摄像头调参中的执行函数结构体
{
        {(uint8*)"1.AUTO_EXP  ", Menu_Null,(uint16*)&mt9v03x_set_config_buffer[1][1]},
        {(uint8*)"2.EXP_TIME  ", Menu_Null,(uint16*)&mt9v03x_set_config_buffer[2][1]},
        {(uint8*)"3.SET_COL   ", Menu_Null,(uint16*)&mt9v03x_set_config_buffer[3][1]},
        {(uint8*)"4.SET_ROW   ", Menu_Null,(uint16*)&mt9v03x_set_config_buffer[4][1]},
        {(uint8*)"5.FPS       ", Menu_Null,(uint16*)&mt9v03x_set_config_buffer[5][1]},
        {(uint8*)"6.GAIN      ", Menu_Null,(uint16*)&mt9v03x_set_config_buffer[6][1]},
        {(uint8*)"7.LR_OFFSE  ", Menu_Null,(uint16*)&mt9v03x_set_config_buffer[7][1]},
        {(uint8*)"8.UD_OFFSET ", Menu_Null,(uint16*)&mt9v03x_set_config_buffer[8][1]},
};

MENU_TABLE show_Image_MenuTable[] =                     //定义存储图像显示中的执行函数结构体
{
        {(uint8 *)"1.RGB  Image", image_show_rgb, NULL},
        {(uint8 *)"2.Gray Image", image_show_gray, NULL},
};

void Menu_exp7(void)                                    //定义实验七要运行的菜单函数
{
    tft180_clear();
    uint8 menuNum;
    menuNum = sizeof(exp7_MenuTable) / sizeof(exp7_MenuTable[0]);
    Menu_Process((uint8 *)" -=    exp7    =- ", &exp7_Prmt, exp7_MenuTable, menuNum);
}

void Menu_exp8(void)                                    //定义实验八要运行的菜单函数
{
    tft180_clear();
    uint8 menuNum;
    menuNum = sizeof(exp8_MenuTable) / sizeof(exp8_MenuTable[0]);
    Menu_Process((uint8 *)"-=      exp8     =-", &exp8_Prmt, exp8_MenuTable, menuNum);
}

void Menu_Camera_Setting(void)                          //定义摄像头参数设置要运行的菜单函数
{
    tft180_clear();
    uint8 menuNum;
    menuNum = sizeof(Camera_Setting_MenuTable) / sizeof(Camera_Setting_MenuTable[0]);
    Menu_Process((uint8 *)"-= Camera Setting =-", &Camera_Setting_Prmt, Camera_Setting_MenuTable, menuNum);
}

void Menu_show_Image(void)                              //定义图像显示要运行的菜单函数
{
    tft180_clear();
    uint8 menuNum;
    menuNum = sizeof(show_Image_MenuTable) / sizeof(show_Image_MenuTable[0]);
    Menu_Process((uint8 *)" -= Image  Show =- ", &exp8_Prmt, show_Image_MenuTable, menuNum);
}
//----------------------------------   菜单定义   --------------------------------------

//----------------------------------   操作函数   --------------------------------------
void image_show_rgb(void)
{
    tft180_show_rgb565_image(0,0,(uint16*)mt9v03x_image[0],MT9V03X_W,MT9V03X_H,SCC8660_W,SCC8660_H,1);
}
void image_show_gray(void)
{
    tft180_show_gray_image(0,0,mt9v03x_image[0],MT9V03X_W,MT9V03X_H,SCC8660_W,SCC8660_H,0);
    //最后一个参数可改为二值化阈值
}
/******************************************************************************
 * FunctionName   : KeySan()
 * Description    : 按键获取
 * EntryParameter : None
 * ReturnValue    : 按键值
 *******************************************************************************/
KEY_e KeySan(void)
{
    //keymsg存储的是选择按键的状态
    //当按键按下 或 退出菜单时进入下一步
    while (keymsg.status == KEY_UP && !ExitMenu_flag)
    {
    }
    //赋给选择按键状态为:按键松开
    keymsg.status = KEY_UP;
    //返回触发的按键值
    return keymsg.key;
}

/******************************************************************************
 * FunctionName   : Menu_Move()
 * Description    : 光标移动
 * EntryParameter : prmt - 菜单参数
 * EntryParameter : key - 按键值
 * ReturnValue    : 有确认按键就返回0,否则返回1
 ******************************************************************************/
uint8 Menu_Move(MENU_PRMT *prmt, KEY_e key)
{
    uint8 rValue = 1;

    switch (key)
    {
        case KEY_U: // 向上
        {
            if (prmt->Cursor != 0) // 光标不在顶端
            {
                prmt->Cursor--; // 光标上移
            }
            else // 光标在顶端
            {
                if (prmt->PageNo != 0) // 页面没有到最小
                {
                    prmt->PageNo--; // 向上翻
                }
                else
                {
                    prmt->Cursor = prmt->DispNum - 1; // 光标到底
                    prmt->PageNo = prmt->MaxPage - 1; // 最后页
                }
            }
            keymsg.status = KEY_UP;

            break;
        }

        case KEY_D: // 向下
        {
            if (prmt->Cursor < prmt->DispNum - 1) // 光标没有到底,移动光标
            {
                prmt->Cursor++; // 光标向下移动
            }
            else // 光标到底
            {
                if (prmt->PageNo < prmt->MaxPage - 1) // 页面没有到底,页面移动
                {
                    prmt->PageNo++; // 下翻一页
                }
                else // 页面和光标都到底,返回开始页
                {
                    prmt->Cursor = 0;
                    prmt->PageNo = 0;
                }
            }
            keymsg.status = KEY_UP;

            break;
        }
        case KEY_B: // 确认
        {
            prmt->Index = prmt->Cursor + prmt->PageNo; //计算执行项的索引
            rValue = 0;
            keymsg.status = KEY_UP;

            break;
        }
        case KEY_L: // 左键返回上级菜单
        {

                prmt->Cursor = 0;
                prmt->PageNo = 0;
                prmt->ExitMark = 1;

            keymsg.status = KEY_UP;
            /*
            if(camera_flag == 1)
            {
                tft180_clear();
                camera_flag = 0;
//                if(mt9v03x_set_config(mt9v03x_set_config_buffer))
//                    tft180_show_string(0,0,"setting got!");
//                else
//                    tft180_show_string(0,0,"setting failed!");
            }
            */
            break;
        }
        case KEY_R: // 右键跳到底部
        {
            prmt->Cursor = prmt->DispNum - 1; // 光标到底
            prmt->PageNo = prmt->MaxPage - 1; // 最后页
            keymsg.status = KEY_UP;
            break;
        }

        default:
            break;
    }

    return rValue; // 返回执行索引
}

/******************************************************************************
 * FunctionName   : MainMenu_Set()
 * Description    : 常规设置
 * EntryParameter : None
 * ReturnValue    : None
 *******************************************************************************/
void MainMenu_Set(void)
{
    while (!GET_SWITCH1() && !GET_SWITCH2())
    {
        tft180_show_string(0, 0, "Whether Read DATA?");
    }
    if(GET_SWITCH1())
    {
        Read_EEPROM();
    }
    else
    {
        tft180_show_string(0, 0, "Skip to Read DATA!");
        system_delay_ms(500);
    }

    tft180_clear();
    ExitMenu_flag = 0;
    uint8 menuNum = sizeof(MainMenu_Table) / sizeof(MainMenu_Table[0]); // 菜单项数

    //显示菜单
    Menu_Process((uint8 *)" -=   Setting   =- ", &MainMenu_Prmt, MainMenu_Table, menuNum);

    Write_EEPROM(); //将数据写入EEPROM保存
//    tft180_clear();
    tft180_full(RGB565_RED);
}

/******************************************************************************
 * FunctionName   : Menu_Process()
 * Description    : 处理菜单项
 * EntryParameter : menuName - 菜单名称
 * EntryParameter : prmt - 菜单参数,
 * EntryParameter : table - 菜单表项
 * EntryParameter : num - 菜单项数
 * ReturnValue    : None
 * Example        : Menu_Process((uint8 *)" -=exp7=- ", &exp7_Prmt, exp7_MenuTable, menuNum);
 ******************************************************************************/
void Menu_Process(uint8 *menuName, MENU_PRMT *prmt, MENU_TABLE *table, uint8 num)
{
    KEY_e key;
    Site_t site;
    uint8 page;                                                                                    //显示菜单需要的页数

    if (num <= PAGE_DISP_NUM)                                                                      //当不足一页最多显示的操作函数个数时只创建一页菜单
        page = 1;
    else
    {
        page = num - PAGE_DISP_NUM + 1;
        num = PAGE_DISP_NUM;

    }
    Menu_PrmtInit(prmt, num, page);                                                                //设置显示项数和页数

    do                                                                                             //当ExitMark保持不退出状态(=0) 且 退出指针为FALSE时一直循环
    {
        tft180_show_string(0, 0, (const char *)menuName);                                          //显示菜单标题

        Menu_Display(table, prmt->PageNo, prmt->DispNum, prmt->Cursor);                            //显示菜单项

        key = KeySan();                                                                            //获取按键

        if (Menu_Move(prmt, key) == 0) //按下按键,菜单移动
        {
            // 判断此菜单项有无需要调节的参数,
            if (table[prmt->Index].DebugParam != NULL && table[prmt->Index].ItemHook == Menu_Null) //有则进入参数调节
            {
                site.x = 120;
                site.y = (1 + prmt->Cursor) * 16;

                tft180_show_uint(site.x, site.y, *(table[prmt->Index].DebugParam), 8);
                adjustParam(site, table[prmt->Index].DebugParam, 4, RGB565_WHITE, RGB565_RED);
            }
            else                                                                                   //无就执行菜单函数
            {
                table[prmt->Index].ItemHook();
            }
        }
    } while (prmt->ExitMark == 0 && ExitMenu_flag == 0);

    tft180_clear();
}

/******************************************************************************
 * FunctionName   : Menu_PrmtInit()
 * Description    : 初始化菜单参数
 * EntryParameter : prmt - 菜单参数, num - 每页显示项数, page - 最大显示页数
 * ReturnValue    : None
 *******************************************************************************/
void Menu_PrmtInit(MENU_PRMT *prmt, uint8 num, uint8 page)
{
    prmt->ExitMark = 0; //清除退出菜单标志

    prmt->Cursor = 0;    //光标清零
    prmt->PageNo = 0;    //页清零
    prmt->Index = 0;     //索引清零
    prmt->DispNum = num; //页最多显示项目数

    prmt->MaxPage = page; //最多页数
}

/******************************************************************************
 * FunctionName   : Menu_Display()
 * Description    : *menuTable  - 显示菜单项
 * EntryParameter : pageNo      - 当前页
 * EntryParameter : dispNum     - 每一页的显示项
 * EntryParameter : cursor      - 光标位置
 * ReturnValue    : None
 *******************************************************************************/
void Menu_Display(MENU_TABLE *menuTable, uint8 pageNo, uint8 dispNum, uint8 cursor)
{
    uint8 i;
    Site_t site;

    for (i = 0; i < dispNum; i ++)
    {
        if (cursor == i)
        {
            //显示当前光标选中菜单项
            site.x = 0;
            site.y = (i + 1) * 16;
            lcd_showstr_setColor((uint16)site.x, (uint16)site.y, (const char *)menuTable[pageNo + i].MenuName, RGB565_WHITE, RGB565_BLUE);
            // 若此菜单项有需要调的参数,则显示该参数
            if (menuTable[pageNo + i].DebugParam != NULL)
            {
                site.x = 120;

                uint16 num_t = (*(menuTable[pageNo + i].DebugParam));
                tft180_show_uint(site.x, site.y, num_t, 8);
            }
        }
        else
        {
            /* 正常显示其余菜单项 */
            site.x = 0;
            site.y = (i + 1) * 16;
            lcd_showstr_setColor((uint16)site.x, (uint16)site.y, (const char *)menuTable[pageNo + i].MenuName, RGB565_BLUE, RGB565_WHITE);
            /* 若此菜单项有需要调的参数,则显示该参数 */
            if (menuTable[pageNo + i].DebugParam != NULL)
            {
                site.x = 120;

                uint16 num_t = (*(menuTable[pageNo + i].DebugParam));
                tft180_show_uint(site.x, site.y, num_t, 8);
            }
        }
    }
}

/******************************************************************************
 * FunctionName   : adjustParam()
 * Description    : 在单片机上快速调参
 * EntryParameter : menuName - 菜单名称
 * EntryParameter : prmt - 菜单参数
 * EntryParameter : table - 菜单表项
 * EntryParameter : num - 菜单项数
 * ReturnValue    : None
 ******************************************************************************/
void adjustParam(Site_t site, uint16 *param, uint8 max_param_bit, uint16 Color, uint16 bkColor)
{
    do
    {
        KeySan();

        switch (keymsg.key)
        {
            case KEY_U:
                if (*param <= 65534)
                    (*param)++;
                break;

            case KEY_D:
                if (*param >= 1)
                    (*param)--;
                break;

            case KEY_L:
                if (*param >= 10)
                    (*param) -= 10;
                break;

            case KEY_R:
                if (*param <= 65525)
                    (*param) += 10;
                break;

            default:
                break;
        }
        tft180_show_uint(site.x, site.y, *param, 8);
    } while (keymsg.key != KEY_B);
}

void Menu_Null()
{
    //DELAY_MS(100);
}

//-------------------------------------------------------------------------------------------------------------------
//  函数简介      清除EEPROM扇区信息
//  参数说明      sector_num    仅可填写0  此处扇区编号并无实际作用,只是留出接口
//  参数说明      page_num      需要写入的页编号   参数范围0-11
//  备注信息      遍历所有数据页,找到有数据的并且擦除
//-------------------------------------------------------------------------------------------------------------------
/*
void eeprom_erase_sector(void)
{
    uint32 sectorNum = 0;
    for (uint16 pageNum = 0; pageNum < 11; ++pageNum)
    {
        if(flash_check(sectorNum, pageNum))                  // 判断是否有数据
        {
            flash_erase_page(sectorNum, pageNum);            // 擦除这一页
        }
    }
    tft180_show_string(0, 0, "Clear Finish!");
    system_delay_ms(1000);
}
*/
//-------------------------------------------------------------------------------------------------------------------
//  函数简介      向EEPROM写入数据(一页存一个数据)
//  参数说明      sector_num    仅可填写0  此处扇区编号并无实际作用,只是留出接口
//  参数说明      page_num      需要写入的页编号   参数范围0-11
/*  备注信息      一共有12个扇区
                 每个扇区有1024页
                 一共有96KB
                 一页只有8个字节,放两个32位,4个16位*/
//-------------------------------------------------------------------------------------------------------------------
void Write_EEPROM(void) //
{
    uint32 EEPROM_DATA_NUM = sizeof(EEPROM_DATA) / sizeof(EEPROM_DATA[0]); //需要写入的数据页数
    uint32 pageNum = 0;
    uint32 *data_ptr = NULL;      //创建指针,将eeprom中的数据作为指针传入写入函数

    //擦除扇区0的第11页,作为数据缓冲页
    flash_erase_page(0,11);

    tft180_show_string(0, 0, "START WRITE!");
    system_delay_ms(500);

    for(uint16 pageNum = 0; pageNum < EEPROM_DATA_NUM; ++pageNum)
    {
        data_ptr = (uint32 *)EEPROM_DATA[pageNum];
        if(data_ptr != NULL)//如果待写入数据非空,就写入缓冲页
        {
            flash_union_buffer[pageNum].uint32_type=(uint16)*data_ptr;
        }
    }

    //再向指定页写入缓冲区中的数据
    flash_write_page_from_buffer(0, 11);
    flash_buffer_clear();
    tft180_show_string(0, 0, "WRITE IS OK!");
    system_delay_ms(1000);
}

//-------------------------------------------------------------------------------------------------------------------
//  函数简介      读入EEPROM数据
//  参数说明      sector_num    仅可填写0  此处扇区编号并无实际作用,只是留出接口
//  参数说明      page_num      需要写入的页编号   参数范围0-11
//  参数说明      data_temp     存储读入的数据
//  备注信息
//-------------------------------------------------------------------------------------------------------------------
void Read_EEPROM(void)
{
    uint32 EEPROM_DATA_NUM = sizeof(EEPROM_DATA) / sizeof(EEPROM_DATA[0]); //需要写入的数据页数
    uint32 sectorNum = 0;
    uint32 *data_ptr = NULL;

    tft180_clear();                                     //清屏
    tft180_show_string(0, 0, "Start Reading!");         //显示读取字样
    system_delay_ms(1000);
    tft180_clear();

    flash_buffer_clear();
    flash_read_page_to_buffer(0, 11);

    //从第一页开始遍历每一页
    for (uint16 pageNum = 0; pageNum < EEPROM_DATA_NUM; pageNum ++ )
    {
        tft180_show_string(0, 0, "reading...");
        system_delay_ms(200);

        uint32 temp_vaule = flash_union_buffer[pageNum].uint32_type;

        if((uint16)temp_vaule<5000)
        {
            data_ptr=(uint32 *)EEPROM_DATA[pageNum];
            if (data_ptr != NULL)
            {
                *data_ptr = (uint16)temp_vaule;
            }
        }
        else
        {
            data_ptr=(uint32 *)EEPROM_DATA[pageNum];
            if (data_ptr != NULL)
            {
                *data_ptr = 0;
            }
        }
    }
}
//----------------------------------   操作函数   --------------------------------------