项目描述
项目介绍
通过IO扩展板上的按键和旋转编码器控制并实现菜单功能
通过MSP430核心板的ADC监测IO板模拟输出管脚的变化,判断哪一个按键或编码器的旋转发生了变化,进而控制1.44寸LCD屏幕的菜单显示,实现主菜单和至少二级菜单。
设计思路
通过开发板的ADC模块,不断对拓展板的模拟输出管脚进行采样,通过对拓展板电路结构的分析以及实际测试,确定拓展板按钮和旋钮的不同行为对应模拟输出管脚ADC采样电平的具体变化。根据确定的拓展板行为和采样电平的映射关系,开发菜单操作逻辑,最终实现拓展板行为与菜单行为的映射。
菜单的行为需要显示在LCD屏幕上。借助开源的显示驱动,再根据自己的需求添加相应的修改,即可实现屏幕的显示。每当ADC模块检测到目标行为,LCD屏幕即做出对应的响应。
单纯实现菜单的显示还是显得工程过于羸弱,难以产生实际意义,所以我实现了通过按钮和旋钮控制,在LCD屏幕显示菜单文字,最终实现LED彩灯控制的功能。
硬件介绍
MSP430F5529是德州仪器推出的一款具有集成USB的超低功耗单片机,可以应用于能量收集、无线传感以及自动抄表等场合。它具有4个12位ADC通道,分别为P6.0~P6.3。此外,它还具有多种低功耗模式,是延长便携式测量应用电池寿命的最优选择。在本项目中,着重使用了其中的ADC采样模块。
- IO扩展板上的2个按键和旋转编码器的3个输入端口是通过R-2R电阻网络的方式连接在一起,生成一个模拟电压量。按下任何一个按键都会改变这个模拟电压量的值。
- IO扩展板上的LCD屏幕为128*128分辨率的1.44寸彩色屏幕,通过SPI总线进行访问
实现功能
首先借助ADC采样模块将拓展板行为映射为开发板的变量变化,从而构建出菜单逻辑。在实现菜单逻辑时,重点实现了如下几点功能:
- 工程上电后首先进入初始化欢迎界面,任意操作拓展板后进入第一级菜单。
- 拨动旋钮进行菜单选择,两个按钮分别对应确定键和返回键。
- 在一级菜单按下确定键,选择光标所指功能后可以进入二级菜单,光标回归初始状态。
- 在二级菜单按下确定键,可以实现光标所指的菜单功能。
- 在二级菜单按下返回键,可以返回上一级菜单,光标跳转到进入二级菜单前的最后位置。
菜单在LCD屏幕上的显示主要需要实现表征菜单可实现功能的表征,并且表征当前光标所指的菜单功能。可以细分为以下功能:
- 基础字母和字符的显示。
- 在每个菜单功能前均绘制一个小色块,通过色块颜色的改变指征光标的移动。
- 将所选菜单功能进行高亮强调。
通过菜单可以对拓展板上的LED进行一定的操作,主要实现了指定LED的点亮关闭和闪烁,闪烁后对应LED回归闪烁前的状态。
主要代码
1. 文字显示及高亮
屏幕的点亮工作借助了网络上的开源代码,这里不再负赘。但是我在驱动代码的基础上,实现了部分ASCII码的绘制,进而实现了字符串的绘制,同时实现了字符串的高亮。将所有函数进行封装,最终实现了屏幕菜单的显示与高亮。相关的文字说明都在代码注释里。
主要代码如下:
字符串显示
模块包括draw_ascii和draw_string两部分,draw_string调用draw_ascii,draw_ascii调用驱动底层的draw函数。
void draw_ascii(unsigned char y, unsigned char x,
unsigned char size,unsigned char char_index,unsigned long color)
{
//为了使得驱动适应真实屏幕方向,将x,y的位置进行了调换;
y = 128-y; //也是为了适应真实屏幕方向,所以做出调整
x = 128-x;
unsigned short start_index,pixel_flag,char_array;
unsigned short i,j; //循环参数
start_index = char_index*16;
for(i=0;i<16;i++)
{
char_array = char_pack[start_index+i];//根据传递给函数的信息,在单片机预先存储的词典中选择需要的信息,进行之后的显示操作
pixel_flag = 0x01;
for (j=0;j<8;j++)//逐行绘制文字像素
{
if (char_array&pixel_flag)
{
draw(y-j*size,x-i*size,size,size,color);
}
pixel_flag = (pixel_flag<<1);
}
}
}
void draw_string(unsigned char x, unsigned char y,
unsigned char size,unsigned long long string_index,unsigned short string_length,unsigned long color)
//由于draw_ascii模块已经将x,y的坐标与屏幕进行了兼容,所以这里的x,y是正常顺序。
{
unsigned short char_index,i;
for (i=0;i<string_length;i++)
{
char_index = ((string_index>>(i*8))& 0x000000ff); //从低到高,以两位为单位,依次截取传给函数的字符串变量,传递给draw_ascii函数进行单个字符的绘制
draw_ascii(x+size*8*i,y,size,char_index,color); //依次绘制单个字符
}
}
字符串高亮
该部分代码与字符串的显示模块非常相似,只需要调整显示逻辑,将draw_string函数点亮的地方熄灭,点亮draw_string函数未点亮的像素,所以不再赘述,如有需要,可以在我所附的源代码文件中查看。
菜单的整体显示及跳转
由于C语言语法中,函数只能返回一个变量,所以借用指针,使得函数返回sy,leader两个变量。Leader变量控制菜单光标的显示位置,sy为中间变量,在ADC模块控制leader变量的变化。在此函数中,leader变量似乎没有变化,其实在此函数执行过程中,ADC模块也在运行中,leader变量在该模块会变化。
int screen_turn(unsigned long long string1,unsigned long long string2,unsigned long long string3,unsigned long color,int sy,int unsigned* lead)
{
//控制字符串的高亮和光标的跳转
leader = *lead;
draw_string( 20, 8, 2, string1,5, color);
draw_string( 20, 48, 2, string2,5, color);
draw_string( 20, 88, 2, string3,5, color); //第一层界面
if (leader == 0x01) //第一层光标
{//光标选中字符串高亮
draw_string_complementary( 20, 8, 2, string1,5, 0xffffff);
draw_string_complementary( 20, 48, 2, string2,5, 0x000000);
draw_string_complementary( 20, 88, 2, string3,5, 0x000000);
draw( 115, 20, 5, 5, 0x00ffff);
draw( 115, 60, 5, 5, 0x00ffff);
draw( 115, 100, 5, 5, 0xffff00);
sy = 0x1;
}
else if(leader == 0x02)
{//光标选中字符串高亮
draw_string_complementary( 20, 8, 2, string1,5, 0x000000);
draw_string_complementary( 20, 48, 2,string2,5, 0xffffff);
draw_string_complementary( 20, 88, 2,string3,5, 0x000000);
draw( 115, 20, 5, 5, 0X00ffff);
draw( 115, 60, 5, 5, 0Xffff00);
draw( 115, 100, 5, 5, 0X00ffff);
sy = 0x2;
}
else if(leader == 0x03)
{//光标选中字符串高亮
draw_string_complementary( 20, 8, 2, string1,5, 0x000000);
draw_string_complementary( 20, 48, 2, string2,5, 0x000000);
draw_string_complementary( 20, 88, 2, string3,5, 0xffffff);
draw( 115, 20, 5, 5, 0Xffff00);
draw( 115, 60, 5, 5, 0X00ffff);
draw( 115, 100, 5, 5, 0X00ffff);
sy = 0x0;
}
*lead = leader;
return sy;
}
2. ADC采样模块及系统逻辑实现
部分系统操作逻辑内嵌在了菜单显示模块和LED点亮模块,大部分都在ADC模块里。不过由于该部分代码过于冗长,难以缩减,故不再在此贴出,我仅描述其实现的功能和实现功能的方法,如有需要,可以在所附的源代码中自行查找,其位置在main文件的最后。
ADC模块实时对管脚进行采样,将采样值赋值到ADC12MEM0变量,菜单操作逻辑的实现依赖于对ADC12MEM0变量变化的解读。
通过在板卡上实验,我确定了区分两个按键状态的ADC12MEM0变量经验值,即当0xB00<ADC12MEM0<0xD00时,判定为确定键使能,当ADC12MEM0<0xB00时,判定为返回键使能,当ADC12MEM0>0xD00时,判定为两键均不使能,开始判定旋钮状态。由于每次拨动旋钮,电平变化误差很大,故设置fg中间变量,fg = 模块上一次执行时的ADC12MEM0 + 25;若fg大于ADC12MEM0,此时认为旋钮不改变状态,否则认为旋钮改变状态,对应菜单中光标的移动。模块中诸如enter_flag,return_flag的变量,均为中间状态指示变量,增强系统的鲁棒性,减少系统的虚警概率,不过相应地,会适度增加系统的漏警概率。
3. 操作菜单实现LED功能
拓展板上的LED很容易点亮,结合菜单操作逻辑,很容易写出代码。不过由于代码复用较多,若全部贴出显得过于冗长,仅截取函数中case1部分,即菜单一举例。
int short led_ctrl(int unsigned panel_flag,int unsigned leader,int short enable)
{
if (enable == 2) //若选中功能并选择运行,ADC模块将enable赋值为2
{
switch(panel_flag) //panel_flag菜单一级界面所选功能,根据此变量决定操作的具体LED
{case 0: break;
case 1:
{//leader变量指示光标位置,根据此变量决定对LED的具体操作
if(leader == 1)
{
P1OUT &= ~BIT5; //点亮
}
if(leader == 2) //熄灭
{
P1OUT |= BIT5;
}
if(leader == 3)
{//闪烁两秒
//short k;
short previous=0;
previous = P1OUT;
for(i=10;i>0;i--)
{
P1OUT |= BIT5;
delay_1s();//延时函数
P1OUT &= ~BIT5;
delay_1s();
}
P1OUT |= previous;
}
break;}
enable = 1; //修改使能变量,函数结束
}
return enable;
}
遭遇问题
一路走来坎坎坷坷,磕磕绊绊,遇到了一系列的问题。
首先是开发板经验较少,之前仅接触过arduino,虽然arduino也是用C语言开发,与本次项目所用开发板类似,由于arduino封装较好,不够底层,在我首次在MSP430上操作寄存器时,还是给了我很大的挑战。
其次是开源思想的匮乏。起初我企图从头到尾,完全手撸屏幕驱动代码,虽然我对各种通信协议较为熟悉,并且之前在FPGA开发板上进行过实现,不过等到我真正将理论落地,借助SPI总线实现屏幕功能时,还是遇到了相当大的阻力。后来经过交流群大佬点拨,开始致力于网络上开源代码的搜寻与适配,后来在群文件中找到了适配的屏幕驱动。后来的文字显示工作,我也借助了中景园的取模教程和软件,最终实现了文字和图片的显示。
ADC采样模块和系统逻辑也遇到一定的阻力,由于每次ADC的采样值都有一定的漂移,所以简单地实现系统逻辑功能可能会因为器件物理特性的不理想造成一些bug,所以需要添加一些中间指示变量,增强系统的鲁棒性。
C语言已经好久没用,显得有些生疏,在switch语句忘加break;,妄图不借助指针实现函数return两个变量,造成了零零总总的很多bug,每一个都可能会让我debug半天。
未来计划
参加本次活动就是为即将到来的电赛做准备的,这个项目在短时间内应该不会吃灰,我会继续深入下去,将知识进行迁移,将成果进行移植,带着这段时间的所感所得,继续努力。