调试平台:逐飞TC387库 4路按键 3路拨码开关 IPS200屏 2电位器
按键用于页面选择/保存参数 拨码开关用于页的切换(比如说一个页面放pid 一个页面放陀螺仪)
双adc一个用于切换选择项,另一个用于参数的调整。
这个程序的核心思想是简洁的接口,仅修改几个数组就可以很轻松的添加/修改参数

比如说这是一个显示列表的页面 第一个参数是显示在名字前面的字符串 第二个是类型,第三个是地址,之后的文章中会讲到为什么要这样定义
- 程序的整体思路
整个程序主要分三个模块,显示列表,调试列表,FLASH列表
显示列表是最基本的列表,也就是仅仅显示数据 ,没有调参
调试列表在显示列表的基础上,加入了ADC调参,一个用于选择调整的项,另一个用于选择调整多少
FLASH列表是调试列表的拓展 如果你的参数同时在FLASH列表和调试列表中,那么你的参数在调整并且按下按键后会被写入FALSH
显示列表和调试列表都有一个控制器,这个控制器除了存放用于存放各项目的数组之外,还要存放和换页相关的参数
每个控制器代表用拨码开关选择的一块,比如说2路拨码开关就可以出现4种状态,也就是有4块,每一个块对应着一个调试列表/显示列表的控制器
FLASH列表则复杂一些,因为前面说到的那些调试参数可能会很长,动辄几十多条,但是FLASH里面只存了参数,然后程序上电的时候依次赋值。如果这几十条参数放在同一个列表中,那么就无法改动列表项的顺序,增加/减少一个都会导致数据乱套。这是很危险的。
你可能会有疑问:既然嵌入式是32位的 那么地址也是32位 为什么不能相邻的两个格子,一个存放参数,另一个存放地址呢?因为变量的地址在编译的时候可能会改变,这是无法人为控制的。
综上所述,我的程序中采用了FLASH控制器+FLASH中存放控制页面的方式,这样可以尽可能小的减小因为参数顺序改变造成的问题,这个后面会细讲。
- 显示参数的基本原理
先说说显示的基本原理,因为笔者用到的单片机是32位的英飞凌TC387,32位意味着地址总线是32位的,也就是说,我们可以用32个bit存放片上任意一个数据的地址。这就是上面这张图片为什么要取地址后转换成uint32类型的原因。
知道数据的地址之后,我们怎么把数据解析成原数据呢?比如说变量uint32 a= &b,把b的地址赋给了a。那么如果用 *a解析出b的原数据可行吗?显然是不行的,因为无法知道b的元数据类型,是无符号还是有符号?是浮点还是定点数?指针所指向的位置存放的是一串二进制,我们无法通过一串二进制把他转回需要的数据,所以我们在保存参数的时候还需要增加一个数据的类型,在后续解析的时候可以强转回这个类型的指针,再转回原数据。这样我们就可以定义出本程序中最基础的数据结构
cpp
typedef enum data_type{
FLOAT,
INT
}data_type;
typedef struct debug_item{
char name[15];
data_type type;
uintptr_t data;
//以上的需要显式定义
FI current_data;
int is_eeprom;
FI _default;//_default val form program
float step; // adc per circle step
int is_changed; //
int is_saved; //
}debug_item;
其中
data_type type;
uintptr_t data;这两个参数是知道一个数据类型所必须的,其他作为一些附加的参数,在之后的程序中会用到
上文说完了如何存放数据,接下来说明如何把数据指针转回原始的值

如图所示 根据类型 转换对应类型的指针 再转换成对于类型的数据,拿int来举例
*((int*) show_list1[i].data)这句话的意思是 show_list1[i].data是一个uint32类型的数据,我们先通(int*) show_list1[i].data把他强制转为了一个int指针,再取这个指针里的值。
- 显示/调试参数控制器
之所以把显示/调试参数控制器放在一起讲,是因为显示和调试控制器这两个的区别仅在对于ADC的响应,也就是说 显示部分不加入ADC调整数据,调试控制器加入了一个ADC调数据。
cpp
typedef struct debug_page{
char page_name[15];
debug_item* items;
int item_num;
int total_page;
int current_page;
}debug_headle;
typedef struct disp_page{
char page_name[15];
debug_item* items;
int item_num;
int total_page;
int current_page;
}disp_headle;
page_name是显示在屏幕上的块的名字,比如说陀螺仪就显示GYRO_INFO这种
debug_item* items是这个块所要显示数据的数据数组的指针

INIT_DISP_PAGE是一个宏定义,因为数组退化成指针的时候会丢失长度,所以如果在运行的时候再进行长度的计算是不可行的,必须使用宏定义:
cpp
#define INIT_DISP_PAGE(name, arr) { \
name, /* page_name */ \
arr, /* item */ \
ARRAY_SIZE(arr),/* item_num */ \
(ARRAY_SIZE(arr) + (PAGE_LENGTH) - 1) / (PAGE_LENGTH), /* total_page */ \
0 /* current_page */ \
}
然后就可以显示参数了:
cpp
void show_main(disp_headle* d){
DISP_STRING(0, 0, d->page_name);
int item_begin_cur_page = PAGE_LENGTH * d->current_page;
int item_end_cur_page = func_limit_ab(PAGE_LENGTH * (d->current_page + 1) - 1, 0, d->item_num);
for(int i = 0; i < item_end_cur_page - item_begin_cur_page; i++){
uint16 data_y = ITEM_BEGIN + i * DATA_Y_STEP;
DISP_STRING(0, data_y, d->items[i].name);
if(d->items[i].type == INT) DISP_INT (NUM_BEGIN, data_y, *((int*) d->items[i].data));
if(d->items[i].type == FLOAT) DISP_FLOAT(NUM_BEGIN, data_y, *((float*)d->items[i].data));
}
char page_info[20];
sprintf(page_info,"Page-%d-of-%d-", d->current_page + 1, d->total_page);
ips200_show_string(0, 300, page_info);
int cur_key = get_key();
if(cur_key == KEY2){
ips200_clear();
d->current_page = (d->current_page + 1) % d->total_page;
}
if(cur_key == KEY1){
ips200_clear();
d->current_page = (d->current_page - 1 + d->total_page) % d->total_page;
}
}
这里还有一个函数是
cpp
#define func_limit_ab(x, a, b) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x)))
这个函数的意思是把x限定在a~b中 逐飞的函数还是很好用的
- 参数保存进FLASH
前文提到,本程序采用单独的FLASH管理页面和FALSH数据存储页面,同时 代码中存放了FLASH