# Raspberry Pi Pico 学习
**Repository Path**: lceda/TEST_PICO
## Basic Information
- **Project Name**: Raspberry Pi Pico 学习
- **Description**: 嵌入式智能编程综合实验——Arduino菜单
- **Primary Language**: C
- **License**: LGPL-3.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-06-02
- **Last Updated**: 2024-06-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Arduino
## README
嵌入式智能编程综合实验——Arduino菜单
吕亮 2021210893
## 需求分析及功能描述
### 需求分析
- 多个程序无法在一次烧录中同时实现
- 需要一个菜单,实现程序之间的切换
### 主程序功能
- 实现了一个图形界面菜单,做到了多个子程序的选择运行(见附件 菜单.mp4)。

### 子程序功能
- 图片显示
实现了图片显示的功能,同时在菜单页面显示缩略图。
本工程中,图片显示子程序位于第0、1、3、4个页面。
- 示波器:改变纵轴分辨率和偏置
在例程`tftplot_QMI8658_gfx.cpp`的基础上,添加了使用按键改变纵轴分辨率和偏置的功能(见附件 示波器.mp4)。

本工程中,示波器子程序位于第2个页面。
- 球:根据光线改变颜色
在例程`ball_st7789_rp2040_wire1.cpp`的基础上,添加了光敏电阻外设,可以根据光线改变球的颜色。
本工程中,球子程序位于第5个页面。
## 技术实现要点
- 设计页面切换逻辑
- 设计菜单页面服务程序,方便用户在不同程序之间来回切换。
- 设计子程序页面服务程序,定义代码规范,保证能实现任意程序的移植。
## 主要程序流程图
### setup()
在setup()中,初始化按键、TFT、子程序所需外设。

### loop()
在loop()中,首先轮询按键,然后执行菜单/子程序页面服务函数。

### 菜单页面服务程序
菜单页面用到了5个按键(上、下、左、右、中)来控制子页面的选中。
- 如果按下了上、下、左、右按键,那么改变行数或列数,并更新方框。
- 如果按下了中键,那么根据行数和列数切换到子程序页面。

### 子程序页面服务程序
子页面只需要1个按键(key6)作为返回菜单的按键。
- 如果按下了key6,那么切换到菜单页面。
- 具体实现的功能由子程序定义。
![main-ver0.1-handle_pages[page]](assets/main-ver0.1-handle_pages[page].png)
## 主要代码
### loop()中页面切换的逻辑
```cpp
// 使用列表和函数指针,简化代码
typedef int8_t (*func_ptr)(bool init);
func_ptr handle_pages[9] = {
handle_page_0,
handle_page_1,
handle_page_2,
handle_page_3,
handle_page_4,
handle_page_5,
handle_page_6,
handle_page_7,
handle_page_8};
void loop()
{
static int8_t page_prev = 0x7f;
static int8_t page = -1;
static bool page_init_flag = 1;
// 扫描按键
checkButton();
// 页面切换逻辑
page_init_flag = (page != page_prev);
page_prev = page;
if (0 <= page && page <= 8)
{
page = handle_pages[page](page_init_flag);
}
else if (page == PAGE_MAIN)
{
page = handle_page_main(page_init_flag);
}
else
{
page = PAGE_MAIN;
}
}
```
### 将任意程序移植到子程序页面
通过一定的代码规范,就可以实现在子程序页面运行任意程序(不能使用 key6)。
例如,要将一段Arduino程序移植到第6个页面:
```cpp
int8_t handle_page_6(bool init) // PIC_STM32
{
static int8_t page_nxt = 6;
// Global variables
// ...
if (init)
{
// setup
// ...
page_nxt = 6;
}
// loop
// ...
if (buttonUprise.key6)
{
page_nxt = PAGE_MAIN;
}
return page_nxt;
}
```
将原程序的全局变量复制到// Global variables,setup()函数复制到// setup,loop()函数复制到// loop,就可以实现原程序在子程序页面中的运行。
根据主程序loop()函数的执行逻辑,// setup中的代码在每次切换到该页面时被执行一次,// loop中的代码在停留在该页面时被重复执行,从而和原程序的运行顺序保持一致。
### 用轮询定时器代替delay()
在例程`tftplot_QMI8658_gfx.cpp`和`ball_st7789_rp2040_wire1.cpp`中,都使用了软件延时delay()来控制采样间隔或刷新频率。本项目提出了一种改进思路,使用轮询定时器代替软件延时,增加了定时间隔确定性,减少了软件阻塞。
代码如下:
```cpp
// loop
timerval = millis(); // 轮询定时器,伪定时器中断
if (timerval - timerval_prev >= 10)
{
timerval_prev = timerval;
// 10ms执行一次的程序
}
```
代码分析:
通过 millis() 函数读取内部定时器的毫秒值(timerval),如果读取到的毫秒值(timerval) - 上一次执行程序的毫秒值(timerval_prev) > 10,则执行需要定时的程序,否则就不执行。
由于没有找到适用于 Pico 的 Arduino 定时器中断库,因此只能用这种方法代替。
## 结果及心得
### 实验结果
实现了菜单的主要功能,并基于例程实现了功能拓展。
### 可以改进之处
1. 当有多个子程序争用同一资源(如QMI8658或其他外设)时,移植的过程可能还需要优化以达到最佳效果。可以使用前后台分离的思想来实现。
2. 程序移植的通用性还有待验证。目前只移植了两个例程,其他更复杂的程序在移植过程中还可能产生新的问题。
3. 如果要改为多级菜单,可能需要进一步规划数据结构。
## 所有代码
[https://gitee.com/lceda/TEST_PICO/tree/master](https://gitee.com/lceda/TEST_PICO/tree/master)