Melis4.0[D1s]:1.启动流程(与adc按键初始化相关部分)跟踪笔记
本文是自己为了厘清Melis4.0[D1s]启动时加载输入按键驱动流程而做的笔记。
开发板使用了mangopi-MQ-R(F133),melis只支持spinor flash,不支持spinand和sd卡。而mangopi-MQ-R(F133)没有焊接spinor flash,必须自己购买补焊。
我在立创商城买的华邦的 W25Q128JVSIQ:
参考文章:
- 作者:waxly-,文章 :全志 Melis-4.0(rt-thread内核) 环境搭建与初步编译介绍
1.启动流程
Melis4.0的RTOS内核有2种选择,我们选的是RT-Thread:
关于RT-Thread启动流程的详细资料可以参考官方文档:RT-Thread Nano 移植原理。
但是Melis的启动流程似乎与RT-Thread关系不大,参考全志官方文档《Melis4.0 RTOS系统开发指南》。
下面做部分摘录,稍作整理:
1.1 最先进入的文件:head_s.S
该文件路径为 《D1s-Melis\ekernel\arch\riscv\rv64gc\head_s.S》,完成以下功能:
• bss 段的清零;
• sp 栈指针的初始化;
• mmu 初始化和页表基地址赋值;
• 异常统一入口赋值;
• 跳转至 start_kernel 函数;
1.2 start_kernel()函数所在的文件:init.c
该文件路径为 《D1s-Melis\ekernel\arch\riscv\sunxi\init.c》,start_kernel()函数间接调用input_init()函数完成输入设备初始化函数的调用。
这里的awos_init_thread()函数还调用do_initcalls(),而do_initcalls()调用了sunxi_keyboard_init()。下面摘录官方文章介绍do_initcalls()的内容:
do_initcalls()函数通过层层调用,调用了initcall_levels数组定义的函数地址标识,这些地址标识在riscv/lds/kernel.lds中定义,表示代码段名称为initcallxx.init类的代码,c 文件通过__attribute__((section(“text”))),指定函数或变量在链接时存放的代码段段名称。这个由source/include/melis/init.h文 件 中 的 宏 定 义___define_initcall(fn, id, .initcall##id)来 声 明 实现。
例 如:subsys_initcall(drv_dma_init);将 函 数 指 定 到 代 码 段.initcall4.init, 将 会 在 调用__initcall4_start时,把__initcall4_start到__initcall5_start代码段地址区间的函数接口执行一遍。
1.3 input_init()函数所在文件:sys_input.c
该文件路径为 《D1s-Melis\ekernel\legacy\input\sys_input.c》,按键初始化是默认有的,触摸则根据配置参数决定是否初始化,鼠标则默认没有。按键设备的初始化函数为INPUT_LKeyDevInit(),继续跟踪。
int32_t input_init(void)
{
__inf("input system initialize....");
if (INPUT_CoreInit() != EPDK_OK)
{
__wrn("INPUT_CoreInit failed");
return EPDK_FAIL;
}
if (INPUT_LKeyDevInit() != EPDK_OK)
{
__wrn("INPUT_LkeyDevInit failed");
INPUT_CoreExit();
return EPDK_FAIL;
}
#if 0
if (INPUT_LMouseDevInit() != EPDK_OK)
{
__wrn("INPUT_LMouseDevInit failed");
INPUT_LKeyDevExit();
INPUT_CoreExit();
return EPDK_FAIL;
}
#endif
#if CONFIG_SUPPORT_TOUCHPANEL
if (INPUT_LTSDevInit() != EPDK_OK)
{
__wrn("INPUT_LTPDevInit failed");
//INPUT_LMouseDevExit();
INPUT_LKeyDevExit();
INPUT_CoreExit();
return EPDK_FAIL;
}
#endif
return EPDK_OK;
}
1.4 INPUT_LKeyDevInit()所在文件:keyboarddev.c
该文件路径为 《D1s-Melis\ekernel\legacy\input\keyboard\keyboarddev.c》,调用了esINPUT_RegLdev()。
1.5 esINPUT_RegLdev()所在文件:input.c
该文件路径为 《D1s-Melis\ekernel\legacy\input\input\input.c》
1.5.1 来到这里,无法与后面的硬件初始化搭上关系。
上面是从开始往后面跟踪调用子函数,下面是从子函数跟踪被哪个上级函数调用,来到这里失联了。
2.从底层操作硬件的函数往前面跟踪
根据本节的分析,adc按键的功能是默认的,不用使用 make menuconfig 进行配置。不过为了保险起见,还是做足下面的配置:
2.1 实现对adc按键硬件初始化的函数 sunxi_keyboard_init()
该文件路径为 《D1s-Melis\ekernel\drivers\drv\source\input\keyboard\sunxi_keyboard.c》。
2.1.1 本函数是被do_initcalls()调用的
在文件sunxi_keyboard.c有下面的语句:
late_initcall(sunxi_keyboard_init);
对应的宏定义在文件 《D1s-Melis\include\melis\init.h》:
#define late_initcall(fn) __define_initcall(fn, 7)
于是变成了指针数组 initcall_levels 的一员:
static initcall_entry_t *initcall_levels[] =
{
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
最终在do_initcalls()被调用,do_initcalls()所在的文件为 《D1s-Melis\ekernel\arch\common\initcall.c》。
2.1.2 本函数注册了中断函数keyboard_irq_callback(),直接产生按键消息
int sunxi_keyboard_init(void)
{
.......
hal_gpadc_init();
hal_gpadc_channel_init(GP_CH_0);
hal_gpadc_register_callback(GP_CH_0, keyboard_irq_callback);
return 0;
}
keyboard_irq_callback()可以直接发送系统消息:
int keyboard_irq_callback(uint32_t data_type, uint32_t data)
{
......
if (key_data->key_code < key_config.key_num)
{
if (key_flag == 0)
{
console_LKeyDevEvent(NULL, EV_KEY, key_data->scankeycodes[key_data->key_code], 1);
console_LKeyDevEvent(NULL, EV_SYN, 0, 0);
key_flag = 1;
}
......
}
3.根据分压电阻的实际阻值修改代码
官方的adc按键部分电路:
我用万能板焊接的电路板,使用了手头已有的电阻4.7k,10k,20k,30k:
3.1 关键代码分析
计算键值的关键代码:
struct sunxikbd_config key_config =
{
.measure = 1800,
.key_num = 5,
.key_vol = {210, 410, 590, 750, 880},
.scankeycodes = {KPAD_UP, KPAD_DOWN, KPAD_ENTER, KPAD_MENU, KPAD_RETURN},
.name = "sunxi-keyboard"
};
static unsigned char keypad_mapindex[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, /* key 1, 0-8 */
1, 1, 1, 1, 1, /* key 2, 9-13 */
2, 2, 2, 2, 2, 2, /* key 3, 14-19 */
3, 3, 3, 3, 3, 3, /* key 4, 20-25 */
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* key 5, 26-36 */
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* key 6, 37-39 */
6, 6, 6, 6, 6, 6, 6, 6, 6, /* key 7, 40-49 */
7, 7, 7, 7, 7, 7, 7 /* key 8, 50-63 */
};
大概思路是将AD值处理后(大约是除以32),在keypad_mapindex[]里面查表,得到一个在0-4(共5个按键)范围内的值,再从key_config. scankeycodes[]里面查表得到最终的键值。以key1为例,key1按下时的AD值(理论值) 经过处理,应该是4,这样可以使允许误差最大化。
3.1 根据硬件不同,重新计算参数
K0对应的AD理论值为183,除以32得 5.7,约等于6,那么可以取:
static unsigned char keypad_mapindex[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* key 1, 0-10 */
K1对应的AD理论值为524,除以32得 16.3,约等于16,那么可以取:
static unsigned char keypad_mapindex[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* key 1, 0-10 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* key 2, 11-20 */
K2对应的AD理论值为1055,除以32得 33,那么可以取:
static unsigned char keypad_mapindex[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* key 1, 0-10 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* key 2, 11-20 */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, /* key 3, 21-36 */
K3对应的AD理论值为1448,除以32得 45.2,约等于45,那么可以取:
static unsigned char keypad_mapindex[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* key 1, 0-10 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* key 2, 11-20 */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, /* key 3, 21-39 */
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* key 4, 40-50 */
K3对应的AD理论值为1878,除以32得 58.6,约等于58,那么可以取:
static unsigned char keypad_mapindex[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* key 1, 0-10 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* key 2, 11-20 */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, /* key 3, 21-39 */
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* key 4, 40-50 */
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, /* key 5, 51-69 */
其实,这个表是由下面的代码重新计算,并覆盖掉的:
static int sunxikbd_data_init(struct sunxikbd_drv_data *key_data, struct sunxikbd_config *sunxikbd_config)
{
int i, j = 0;
int key_num = 0;
unsigned int resol;
unsigned int key_vol[KEY_MAX_CNT];
key_num = sunxikbd_config->key_num;
if (key_num < 1 || key_num > KEY_MAX_CNT)
{
return -1;
}
resol = sunxikbd_config->measure / MAXIMUM_SCALE;
for (i = 0; i < key_num; i++)
{
key_data->scankeycodes[i] = sunxikbd_config->scankeycodes[i];
}
for (i = 0; i < key_num; i++)
{
key_vol[i] = sunxikbd_config->key_vol[i];
}
for (i = 0; i < (key_num - 1); i++)
{
key_vol[i] += (key_vol[i + 1] - key_vol[i]) / 2;
}
/*
for (i = 0; i < MAXIMUM_SCALE; i++)
{
if (i * resol > key_vol[j])
{
j++;
}
keypad_mapindex[i] = j;
}
*/
key_data->last_key_code = INITIAL_VALUE;
return 0;
}
我因为还没看懂这个函数的逻辑,把其中覆盖keypad_mapindex[128]的代码屏蔽了。
3.2 ADC按键的另一种算法
我在使用上面的代码时,不知道什么原因(估计时硬件的可能性更大),经常识别错误。我改成另一种算法:
uint8_t filter_cnt = 20;
uint32_t keyADCthreshhold[10] =
{
78 -8,78 +8,
226 -8,226+8,
460 -8,460+8,
630 -8,630+8,
820 -8,820+8,
};
int analyseKeyADC(uint32_t data)
{
int i ;
for(i=0;i<5;i++){
if((data > keyADCthreshhold[i*2]) && (data < keyADCthreshhold[i*2+1])) break;
}
return i;
}
int keyboard_irq_callback(uint32_t data_type, uint32_t data)
{
uint32_t vol_data;
uint8_t ch_num;
if (data_type == GPADC_UP && key_flag == 1)
{
key_data->compare_later = 5;
key_data->key_cnt = 0;
key_flag = 0;
__inf("GPADC_UP");
console_LKeyDevEvent(NULL, EV_KEY, key_data->scankeycodes[key_data->key_code], 0);
console_LKeyDevEvent(NULL, EV_SYN, 0, 0);
// input_report_key(sunxikbd_dev, key_data->scankeycodes[key_data->key_code], 0);
// input_sync(sunxikbd_dev);
}
data = ((VOL_RANGE / 4096) * data); /* 12bits sample rate */
vol_data = data / 1000;
if (vol_data < SUNXIKEY_DOWN)
{
key_data->compare_before = analyseKeyADC(vol_data);
if(key_data->compare_before < 5){ // 有效
if(key_data->compare_later == key_data->compare_before) {
key_data->key_cnt++;
if (key_data->key_cnt >= filter_cnt)
{
key_data->key_code = key_data->compare_before;//keypad_mapindex[key_data->compare_before];
key_data->compare_later = 0;
key_data->key_cnt = 0;
if (key_data->key_code < key_config.key_num)
{
if (key_flag == 0)
{
__inf("input_report_key %x", key_data->scankeycodes[key_data->key_code]);
console_LKeyDevEvent(NULL, EV_KEY, key_data->scankeycodes[key_data->key_code], 1);
console_LKeyDevEvent(NULL, EV_SYN, 0, 0);
key_flag = 1;
// debug key ,add by hwd 2023-02-18
__log("key: %d",key_data->compare_before);
}
}
}
}else{
key_data->compare_later = key_data->compare_before;
key_data->key_cnt = 0;
}
}
else
{
key_data->key_cnt = 0;
key_data->compare_later = 5;
}
}
return 0;
}