本帖最后由 正点原子运营 于 2023-9-18 16:50 编辑
第六十一章 T9拼音输入法实验 1)实验平台:正点原子探索者STM32F407开发板
2) 章节摘自【正点原子】STM32F407开发指南 V1.1
3)购买链接:https://detail.tmall.com/item.htm?id=609294673401
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)STM32技术交流QQ群:151941872
本章,我们将介绍如何在探索者STM32F407开发板上实现一个简单的T9中文拼音输入法。本章分为如下几个小节: 61.1 拼音输入法简介 61.2 硬件设计 61.3 程序设计 61.4 下载验证
61.1 拼音输入法简介在计算机上汉字的输入法有很多种,比如拼音输入法、五笔输入法、笔画输入法、区位输入法等。其中,又以拼音输入法用的最多。拼音输入法又可以分为很多类,比如全拼输入、双拼输入等。
而在手机上,用的最多的应该算是T9拼音输入法了,T9输入法全名为智能输入法,字库容量九千多字,支持十多种语言。T9输入法是由美国特捷通讯(Tegic Communications)软件公司开发的,该输入法解决了小型掌上设备的文字输入问题,已经成为全球手机文字输入的标准之一。
一般情况下,手机拼音输入键盘如图61.1.1所示: 在这个键盘上,我们对比下传统的输入法和 T9 输入法,输入“中国”两个字需要的按键次数。传统的方法,先按4次9,输入字母z,再按2次4,输入字母h,再按3次6,输入字母o,再按2次6,输入字母n,最后按1次4,输入字母g。这样,输入“中”字,要按键12 次,接着同样的方法,输入“国”字,需要按6次,总共就是18次按键。
如果是T9,我们输入“中”字,只需要输入:9、4、6、6、4,即可实现输入“中”字,在选择“中”字之后,T9会联想出一系列同“中”字组合的词,如:文、国、断、山等。这样输入“国”字,我们直接选择即可,所以输入“国”字按键0次,这样使用T9输入法总共只需要5次按键。
这就是T9智能输入法的优越之处。正因为T9输入法高效便捷的输入方式得到了众多手机厂商的采用,以至于T9成为了使用频率最高知名度最大的手机输入法。
在本实验中,我们实现的T9拼音输入法,没有真正的T9那么强大,我们这里仅实现输入部分,不支持词组联想。
61.2 硬件设计
1. 例程功能开机的时候先检测字库,然后显示提示信息和绘制拼音输入表,之后进入等待输入状态。此时用户可以通过屏幕上的拼音输入表输入拼音数字串(通过DEL可以实现退格),然后程序自动检测与之对应的拼音和汉字,并显示在屏幕上(同时输出到串口)。如果有多个匹配的拼音,则通过KEY_UP和KEY1进行选择。按键KEY0用于清除一次输入,按键KEY2用于触摸屏校准。LED0闪烁用于提示程序正在运行。
2. 硬件资源1)LED灯 LED0 – PF9 2)独立按键 KEY0 – PE4 KEY1 – PE3 WK_UP – PA0 3)串口1 4)正点原子 2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 5)NOR FLASH,通过SPI1驱动,我们需要里面的汉字库
61.3 程序设计
61.3.1 程序流程图我们通过LCD的绘制算法生成一个9宫格输入法,采集到对应的合法输入点后,我们把这些信息传给解码库进行解码,识别出对应的拼音,并调用汉字库显示对应的匹配汉字。
61.3.2 程序解析1.T9拼音输入法代码 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,T9拼音输入法的驱动源码包括pyinput.c、pyinput.h和pymb.h三个文件。
首先我们先介绍一下定义在pymb.h中的拼音索引。先介绍一下汉字排列表,该表将汉字拼音所有可能的组合都列出来了,如下所示: - /* 汉字排列表 */
- const uint8_t PY_mb_space []={""};
- const uint8_t PY_mb_a []={"啊阿腌吖锕厑嗄錒呵腌"};
- const uint8_t PY_mb_ai []={"爱埃挨哎唉哀皑癌蔼矮艾碍隘捱嗳嗌嫒瑷暧砹锿霭"};
- …………此处省略N多个组合…………
- const uint8_t PY_mb_zu []={"足组卒族租祖诅阻俎菹镞"};
- const uint8_t PY_mb_zuan []={"钻攥纂缵躜"};
- const uint8_t PY_mb_zui []={"最罪嘴醉蕞觜"};
- const uint8_t PY_mb_zun []={"尊遵樽鳟撙"};
- const uint8_t PY_mb_zuo []={"左佐做作坐座昨撮唑柞阼琢嘬怍胙祚砟酢"};
复制代码这里我们只列出了部分组合,我们将这些组合称之为码表,然后将这些码表和其对应的数字串对应起来,组成一个拼音索引表,如下所示: - /* 拼音索引表 */
- const py_index py_index3[]=
- {
- {"" ,"",(uint8_t*)PY_mb_space},
- {"2","a",(uint8_t*)PY_mb_a},
- {"3","e",(uint8_t*)PY_mb_e},
- {"6","o",(uint8_t*)PY_mb_o},
- {"24","ai",(uint8_t*)PY_mb_ai},
- …………此处省略N多个组合…………
- {"94664","zhong",(uint8_t*)PY_mb_zhong},
- {"94824","zhuai",(uint8_t*)PY_mb_zhuai},
- {"94826","zhuan",(uint8_t*)PY_mb_zhuan},
- {"248264","chuang",(uint8_t*)PY_mb_chuang},
- {"748264","shuang",(uint8_t*)PY_mb_shuang},
- {"948264","zhuang",(uint8_t*)PY_mb_zhuang},
- };
- 其中py_index是一个结构体,定义如下:
- /* 拼音码表与拼音的对应表 */
- typedef struct
- {
- uint8_t *py_input; /* 输入的字符串 */
- uint8_t *py; /* 对应的拼音 */
- uint8_t *pymb; /* 码表 */
- }py_index;
复制代码其中py_input,即与拼音对应的数字串,比如“94824”。py,即与py_input数字串对应的拼音,如果py_input = “94824”,那么py就是“zhuai”。最后pymb就是我们前面说到的码表。注意,一个数字串可以对应多个拼音,也可以对应多个码表。
有了这个拼音索引表(py_index3)之后,我们只需要将输入的数字串和py_index3索引表里面所有成员的py_input对比,将所有完全匹配的情况记录下来,用户要输入的汉字就被确定了,然后由用户选择可能的拼音组成(假设有多个匹配的项目),再选择对应的汉字,即完成一次汉字输入。
当然还可能是找遍了索引表,也没有发现一个完全符合要求的成员,那么我们会统计匹配数最多的情况,作为最佳结果,反馈给用户。比如,用户输入“323”,找不到完全匹配的情况,那么我们就将能和“32”匹配的结果返回给用户。这样,用户还是可以得到输入结果,同时还可以知道输入有问题,提示用户需要检查输入是否正确。
我们归纳一下一个完整的T9拼音输入步骤:
1,输入拼音数字串 我们用到的T9拼音输入法的核心思想就是对比用户输入的拼音数字串,所以必须先由用户输入拼音数字串。
2,在拼音索引表里面查找和输入字符串匹配的项,并记录 在得到用户输入的拼音数字串之后,在拼音索引表里面查找所有匹配的项目,如果有完全匹配的项目,就全部记录下来,如果没有完全匹配的项目,则记录匹配情况最好的一个项目。
3,显示匹配清单里面所有可能的文字,供用户选择 将匹配项目的拼音和对应的汉字显示出来,供用户选择。如果有多个匹配项(一个数字串对应多个拼音的情况),则用户还需要选择拼音。
4,用户选择匹配项,并选择对应的汉字 用户对匹配的拼音和汉字进行选择,选中其真正想输入的拼音和汉字,实现一次拼音输入。
下面介绍一下pyinput.c中比较核心的函数get_matched_pymb,代码如下: - /**
- * @brief 获取匹配的拼音码表
- * @param strin : 输入的字符串,形如:"726"
- * @param matchlist : 输出的匹配表
- * @retval 匹配状态
- * [7] , 0,表示完全匹配;1,表示部分匹配(仅在没有完全匹配的时候才会出现)
- * [6:0],完全匹配的时候,表示完全匹配的拼音个数
- * 部分匹配的时候,表示有效匹配的位数
- */
- uint8_t get_matched_pymb(uint8_t *strin, py_index **matchlist)
- {
- py_index *bestmatch = 0; /* 最佳匹配 */
- uint16_t pyindex_len = 0;
- uint16_t i = 0;
- uint8_t temp, mcnt = 0, bmcnt = 0;
-
- bestmatch = (py_index *)&py_index3[0]; /* 默认为a的匹配 */
- pyindex_len = sizeof(py_index3) / sizeof(py_index3[0]);/*得到py索引表的大小*/
- for (i = 0; i < pyindex_len; i++)
- {
- temp = str_match(strin, (uint8_t *)py_index3.py_input);
- if (temp)
- {
- if (temp == 0XFF)
- {
- matchlist[mcnt++] = (py_index *)&py_index3;
- }
- else if (temp > bmcnt) /* 找最佳匹配 */
- {
- bmcnt = temp;
- bestmatch = (py_index *)&py_index3; /* 最好的匹配 */
- }
- }
- }
- if (mcnt == 0 && bmcnt) /* 没有完全匹配的结果,但是有部分匹配的结果 */
- {
- matchlist[0] = bestmatch;
- mcnt = bmcnt | 0X80; /* 返回部分匹配的有效位数 */
- }
- return mcnt; /* 返回匹配的个数 */
- }
复制代码该函数实现的是将用户输入拼音数字串同拼音索引表里面的各个项对比,找出匹配结果,并将完全匹配的项目存放在matchlist里面,同时记录匹配数。对于那些没有完全匹配的输入串,则查找与其最佳匹配的项目,并将匹配的长度返回。
其中该文件还有一个函数test_py,提供给usmart调用,实现串口测试,在串口测试的时候才能用到,如果不使用的话,可以去掉。本实验也是加入usmart控制,大家可以通过该函数实现串口调试拼音输入法。
其他两个函数比较简单,这里就不细说了。
前面提及的matchlist,其定义在pyinput.h中,代码如下: - /* 拼音输入法 */
- typedef struct
- {
- uint8_t(*getpymb)(uint8_t *instr); /* 字符串到码表获取函数 */
- py_index *pymb[MAX_MATCH_PYMB]; /* 码表存放位置 */
- }pyinput;
复制代码该结构体提供了两个成员,一个成员就是字符串到码表获取函数,另外一个成员也就是码表的存放位置。另外一个结构体py_index,已经在前面讲解了,这里就不作展开。
2. main.c代码 在main.c文件下,除了main函数之外,还有py_load_ui、py_key_staset、py_get_keynum和py_show_result函数。其中,py_load_ui函数是用于加载输入键盘,在LCD上显示我们输入拼音数字串的虚拟键盘。py_key_staset函数用于与设置虚拟键盘某个按键的状态(按下/松开)。py_get_keynum函数用于得到触摸屏当前按下的按键键值,通过该函数实现拼音数字串的获取。py_show_result函数用于显示输入串的匹配结果,并将结果打印到串口。这部分代码就不列出来了,大家可以自行理解。
下面看一下main主函数的代码: - int main(void)
- {
- uint8_t i = 0;
- uint8_t result_num;
- uint8_t cur_index;
- uint8_t key;
- uint8_t temp;
- uint8_t inputstr[7]; /* 最大输入6个字符+结束符 */
- uint8_t inputlen; /* 输入长度 */
-
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- usmart_dev.init(84); /* 初始化USMART */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- key_init(); /* 初始化按键 */
- tp_dev.init(); /* 初始化触摸屏 */
- sram_init(); /* SRAM初始化 */
- norflash_init(); /* 初始化NORFLASH */
- my_mem_init(SRAMIN); /* 初始化内部SRAM内存池 */
- my_mem_init(SRAMEX); /* 初始化外部SRAM内存池 */
- my_mem_init(SRAMCCM); /* 初始化内部SRAMCCM内存池 */
- exfuns_init(); /* 为fatfs相关变量申请内存 */
- f_mount(fs[0], "0:", 1); /* 挂载SD卡 */
- f_mount(fs[1], "1:", 1); /* 挂载FLASH */
- RESTART:
- while (fonts_init()) /* 检查字库 */
- {
- lcd_show_string(60, 50, 200, 16, 16, "FontError!", RED);
- delay_ms(200);
- lcd_fill(60, 50, 240, 66, WHITE); /* 清除显示 */
- delay_ms(200);
- }
- text_show_string(30, 5, 200, 16, "正点原子STM32开发板", 16, 0, RED);
- text_show_string(30, 25, 200, 16, "拼音输入法实验", 16, 0, RED);
- text_show_string(30, 45, 200, 16, "正点原子@ALIENTEK", 16, 0, RED);
- text_show_string(30, 65, 200, 16, "KEY_UP:校准", 16, 0, RED);
- text_show_string(30, 85, 200, 16, "KEY0:翻页 KEY1:清除", 16, 0, RED);
- text_show_string(30, 105, 200, 16, "输入: 匹配: ", 16, 0, RED);
- text_show_string(30, 125, 200, 16, "拼音: 当前: ", 16, 0, RED);
- text_show_string(30, 145, 210, 32, "结果:", 16, 0, RED);
- /* 根据LCD分辨率设置按键大小 */
- if (lcddev.id == 0X5310)
- {
- kbdxsize = 86;
- kbdysize = 43;
- }
- else if (lcddev.id == 0X5510)
- {
- kbdxsize = 140;
- kbdysize = 70;
- }
- else
- {
- kbdxsize = 60;
- kbdysize = 40;
- }
- py_load_ui(30, 195);
- my_mem_set(inputstr, 0, 7); /* 全部清零 */
- inputlen = 0; /* 输入长度为0 */
- result_num = 0; /* 总匹配数清零 */
- cur_index = 0;
- while (1)
- {
- i++;
- delay_ms(10);
- key = py_get_keynum(30, 195);
- if (key)
- {
- if (key == 1) /* 删除 */
- {
- if (inputlen)inputlen--;
- inputstr[inputlen] = '\0'; /* 添加结束符 */
- }
- else
- {
- inputstr[inputlen] = key + '0'; /* 输入字符 */
- if (inputlen < 7)inputlen++;
- }
- if (inputstr[0] != NULL)
- {
- temp = t9.getpymb(inputstr); /* 得到匹配的结果数 */
- if (temp) /* 有部分匹配/完全匹配的结果 */
- {
- result_num = temp & 0X7F; /* 总匹配结果 */
- cur_index = 1; /* 当前为第一个索引 */
- if (temp & 0X80) /* 是部分匹配 */
- {
- inputlen = temp & 0X7F; /* 有效匹配位数 */
- inputstr[inputlen] = '\0'; /* 不匹配的位数去掉 */
- if (inputlen > 1)
- {
- temp = t9.getpymb(inputstr); /* 重新获取完全匹配字符数 */
- /* 如果还是部分匹配, 直接匹配数为0, 否则表示匹配数量 */
- result_num = (temp & 0X80)? 0 : (temp & 0X7F);
- }
- }
- }
- else /* 没有任何匹配 */
- {
- inputlen--;
- inputstr[inputlen] = '\0';
- }
- }
- else
- {
- cur_index = 0;
- result_num = 0;
- }
- lcd_fill(30 + 40, 105,30 + 40 + 48,110 + 16, WHITE);/* 清除之前的显示 */
- lcd_show_num(30 + 144, 105, result_num, 1, 16,BLUE);/*显示匹配的结果数*/
- /* 显示有效的数字串 */
- text_show_string(30 + 40, 105, 200, 16, (char *)inputstr, 16,0,BLUE);
- py_show_result(cur_index); /* 显示第cur_index的匹配结果 */
- }
- key = key_scan(0);
- if (key == WKUP_PRES && tp_dev.touchtype == 0) /* KEYUP按下,且是电阻屏 */
- {
- tp_dev.adjust();
- lcd_clear(WHITE);
- goto RESTART;
- }
- if (result_num)
- {
- switch (key)
- {
- case KEY0_PRES: /* 下翻 */
- if (result_num) /* 存在匹配的结果 */
- {
- if (cur_index < result_num)cur_index++;
- else cur_index = 1;
- /* 显示第cur_index的匹配结果 */
- py_show_result(cur_index);
- }
- break;
- case KEY1_PRES: /* 清除输入 */
- /* 清除之前的显示 */
- lcd_fill(30 + 40, 145, lcddev.width - 1, 145 + 48, WHITE);
- goto RESTART;
- }
- }
- if (i == 30)
- {
- i = 0;
- LED0_TOGGLE();
- }
- }
- }
复制代码在main函数里,实现了我们在58.2.1小节所说的功能,也是按照它表述的逻辑进行实现。在这里我们并没有实现汉字选择功能,但是由本例程作为基础,再实现汉字选择功能就比较简单了,大家自行实现即可。注意:kbdxsize和kbdysize代表虚拟键盘按键宽度和高度,程序根据LCD分辨率不同而自动设置这两个参数,以达到较好的输入效果。
61.4 下载验证将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图61.4.1所示: 此时,我们在虚拟键盘上输入拼音数字串,即可实现拼音输入,如图61.4.2所示: 如果发现输入错误了,可以通过屏幕上的DEL按键,来退格。如果有多个匹配的情况(匹配值大于1),则可以通过KEY_UP和KEY1来选择拼音。通过按下KEY0,可以KEY_UP和KEY1来选择拼音。通过按下KEY0,可以清除当前输入,通过按下KEY2,可以实现触摸屏校准(仅限电阻屏,电容屏无需校准)。
触摸输入的匹配项可以通过串口调试助手打印出来: 本章,我们还可以借助USMART调用test_py来实现输入法调试,如图61.4.4所示: |