2023寒假一起练平台(4)-通过IO扩展板上的按键和旋转编码器控制并实现菜单功能
通过这次2023寒假一起练活动,我首次接触MSP430系列的板卡,并进行了初步的学习,完成了基于MSP430电赛训练平台的项目1。
标签
嵌入式系统
显示
ADC
MSP430F5529
SKY
更新2023-03-27
南昌大学
419

1.项目需求

  • IO扩展板上的2个按键和旋转编码器的3个输入端口是通过R-2R电阻网络的方式连接在一起,生成一个模拟电压量。按下任何一个按键都会改变这个模拟电压量的值。
  • IO扩展板上的LCD屏幕为128*128分辨率的1.44寸彩色屏幕,通过SPI总线进行访问

要求:本任务需要通过MSP430核心板的ADC监测IO板模拟输出管脚的变化,判断哪一个按键按下或编码器是否旋转,进而控制1.44寸LCD屏幕的菜单显示,要求实现主菜单和至少二级菜单。

2.完成的功能及达到的性能

2.1  ADC采集

wW+MYRppSXCtAAAAABJRU5ErkJggg==

通过A_Out(P6.0)引脚采集电压值,判断按键是否按下以及按键是哪个按下。如上图所示,不同的按键按下后A_Out的电压值会发生不同的变化,理论上旋转编码器旋转使AC闭合,电压变化1/32*3.3V,使BC闭合,电压变化1/16*3.3V,旋转编码器上的按键按下,电压变化1/8*3.3V;K1按下,电压变化1/4*3.3V,K2按下,电压变化1/2*3.3V。用MSP430F5529内部的12位ADC进行电压采集,计算按键按下前后电压变化的大小,进行按键检测,从而实现按键检测功能。

2.2  LCD屏幕显示

GupaY6mJqSAH3s7beNH7961CjJqgQIIdRauHkaQgghpcN7NgghhJQOwwYhhJDSYdgghBBSOgwbhBBCSodhgxBCSOn+D7kNm49OtmonAAAAAElFTkSuQmCC

扩展板上用的是1.44寸128*128LCD,进行菜单显示,可以显示16位彩色的图片。我设计了一个简单的二级菜单,首先上电显示的是主菜单,通过按键操纵,进入下一级菜单。

3 实现思路

1.ADC对输入通道进行采样,采样由软件触发,采样后进入ADC中断服务函数,对采样值进行处理,计算出前后两次采样的差值的绝对值,通过电压的变化大小,判断按键到底是由哪个按键按下。

2.用LCD的显示函数设计一级菜单界面和二级菜单界面。

3.通过旋转编码器旋转,控制主菜单中的光标移动,按键K1按下,进入下一级菜单,按键K2按下,返回上一级菜单。

4 实现过程

4.1程序流程图

05ttHC4Jw3al89JYgdIrpKGDfefaN7uu869poX4GR66dz6cZegUJCsgAMBaEDAFzloz4GAIWB0AEArhA6AMAVQgcAuELoAABXCB0A4AqhAwBcIXQAgCuEDgBwhdABAK4QOgDAFUIHALhC6AAAVwgdAOAKoQMAXCF0AIArhA4AcIXQAQCuEDoAwBVCBwC4QugAAFcIHQDgCqEDAFwhdACAK4QOAHCF0AEArhA6AMAVQgcAuELoAABXCB0A4AqhAwBcIXQAgCuEDgBw9T8BO3vEAWCLcqIAAAAASUVORK5CYII=

4.2  ADC对数据进行采样

ADC初始化函数(定义位置:adc.c,调用位置:main.c):

void ADC_Init(void)

{
    //P6.0 ADC option select

    GPIO_setAsPeripheralModuleFunctionInputPin(
            GPIO_PORT_P6,
            GPIO_PIN0
            );
    //Initialize the ADC12_A Module

    /*
     * Base address of ADC12_A Module
     * Use internal ADC12_A bit as sample/hold signal to start conversion
     * USE MODOSC 5MHZ Digital Oscillator as clock source
     * Use default clock divider of 1
     */

    ADC12_A_init(ADC12_A_BASE,
            ADC12_A_SAMPLEHOLDSOURCE_SC,
            ADC12_A_CLOCKSOURCE_ADC12OSC,
            ADC12_A_CLOCKDIVIDER_1);

    ADC12_A_enable(ADC12_A_BASE);

    /*
     * Base address of ADC12_A Module
     * For memory buffers 0-7 sample/hold for 64 clock cycles
     * For memory buffers 8-15 sample/hold for 4 clock cycles (default)
     * Disable Multiple Sampling
     */

    ADC12_A_setupSamplingTimer(ADC12_A_BASE,
            ADC12_A_CYCLEHOLD_64_CYCLES,
            ADC12_A_CYCLEHOLD_4_CYCLES,
            ADC12_A_MULTIPLESAMPLESDISABLE);   //软件触发


    //Configure Memory Buffer

    /*
     * Base address of the ADC12_A Module
     * Configure memory buffer 0
     * Map input A5 to memory buffer 0
     * Vref+ = AVcc
     * Vr- = AVss
     * Memory buffer 0 is not the end of a sequence
     */

    ADC12_A_configureMemoryParam param = {0};
    param.memoryBufferControlIndex = ADC12_A_MEMORY_0;
    param.inputSourceSelect = ADC12_A_INPUT_A0;
    param.positiveRefVoltageSourceSelect = ADC12_A_VREFPOS_AVCC;
    param.negativeRefVoltageSourceSelect = ADC12_A_VREFNEG_AVSS;
    param.endOfSequence = ADC12_A_NOTENDOFSEQUENCE;             //单通道采集
    ADC12_A_configureMemory(ADC12_A_BASE ,&param);

    //Enable memory buffer 0 interrupt

    ADC12_A_clearInterrupt(ADC12_A_BASE,
            ADC12IFG0);

    ADC12_A_enableInterrupt(ADC12_A_BASE,
            ADC12IE0);

}

开启ADC采集函数(定义位置:adc12_a.c,调用位置:main.c):

baseAddress :   ADC12只有一个基地址

startingMemoryBufferIndex :   设置需要开始转换的信号。因为我们之前将ADC采集的信号存入MEMORY_0,所以这里选择ADC12_A_MEMORY_0,可选参数为0~15

conversionSequenceModeSelect :   这个是选择ADC开启的模式。有四种模式,如下。

说白了就是一个多通道和单通道,多通道可以多个通道一起进行ADC电压信号采集,比如P6.0和P6.1同时进行ADC电压采集,而单通道只有一个引脚进行ADC电压信号采集。

然后单次采样和多次采样的区别是,如果我们选择的单次采样,那么每次进行ADC采样都需要一次触发。但是如果是多次采样,那么我们只需要一次触发,那么它就会持续采样转换,如果此次电压转换之后的信号不马上读取,会被下一次转换之后的信号覆盖。因为我们这里选择的软件触发,我个人建议选择单次采样,这样我们的电压采样转换的信号就不会被覆盖,同时可以降低功耗。

void ADC12_A_startConversion (uint16_t baseAddress,
    uint16_t startingMemoryBufferIndex,
    uint8_t conversionSequenceModeSelect)
{
    //Reset the ENC bit to set the starting memory address and conversion mode

    //sequence

    HWREG8(baseAddress + OFS_ADC12CTL0_L) &= ~(ADC12ENC);
    //Reset the bits about to be set
    HWREG16(baseAddress + OFS_ADC12CTL1) &= ~(ADC12CSTARTADD_15 + ADC12CONSEQ_3);
    HWREG8(baseAddress + OFS_ADC12CTL1_H) |= (startingMemoryBufferIndex << 4);
    HWREG8(baseAddress + OFS_ADC12CTL1_L) |= conversionSequenceModeSelect;
    HWREG8(baseAddress + OFS_ADC12CTL0_L) |= ADC12ENC + ADC12SC;
}

获取当前ADC采样之后的值的函数(定义位置:adc12_a.c,调用位置:main.c):

uint16_t ADC12_A_getResults (uint16_t baseAddress, uint8_t memoryBufferIndex)

{
    //(0x20 + (memoryBufferIndex * 2)) == offset of ADC12MEMx
    return ( HWREG16(baseAddress + (0x20 + (memoryBufferIndex * 2))) );

}

ADC中断服务函数(定义位置:main.c,调用位置:main.c)

每采集到一次电压,进入中断服务函数中,计算ADC变化的绝对值,用于判断哪个按键按下

#pragma vector=ADC12_VECTOR
__interrupt
void ADC12_A_ISR (void)
{
    switch (__even_in_range(ADC12IV,34))
    {
        case  0: break;   //Vector  0:  No interrupt
        case  2: break;   //Vector  2:  ADC overflow
        case  4: break;   //Vector  4:  ADC timing overflow
        case  6:          //Vector  6:  ADC12IFG0
                 Measured_last=Measured;
                 Measured = ADC12_A_getResults(ADC12_A_BASE, ADC12_A_MEMORY_0);
                 change = fabs(Measured-Measured_last);//ADC变化的绝对值
                 botton(change);//获取按键位置
                 __bic_SR_register_on_exit(LPM0_bits);
        case  8: break;   //Vector  8:  ADC12IFG1
        case 10: break;   //Vector 10:  ADC12IFG2
        case 12: break;   //Vector 12:  ADC12IFG3
        case 14: break;   //Vector 14:  ADC12IFG4
        case 16: break;   //Vector 16:  ADC12IFG5
        case 18: break;   //Vector 18:  ADC12IFG6
        case 20: break;   //Vector 20:  ADC12IFG7
        case 22: break;   //Vector 22:  ADC12IFG8
        case 24: break;   //Vector 24:  ADC12IFG9
        case 26: break;   //Vector 26:  ADC12IFG10
        case 28: break;   //Vector 28:  ADC12IFG11
        case 30: break;   //Vector 30:  ADC12IFG12
        case 32: break;   //Vector 32:  ADC12IFG13
        case 34: break;   //Vector 34:  ADC12IFG14
        default: break;
    }
}

4.3  按键读取

用ADC读取到的电压变化的绝对值,判断哪个按键按下

获取键值函数:(定义位置:menu.c,调用位置:main.c):

extern uint16_t key;     //按键键值
extern uint16_t cursor;//光标位置
extern uint16_t page;  //用于标记不同界面,完成界面的切换
extern uint16_t flag_picture;  //用与“picture”中的图片切换

void botton(uint16_t change)
{
    if(change<2000&&change>1800)//k2      返回        1924
    {
        LCD_Fill(0,0,128,128,WHITE);     //用于清屏操作,防止界面刷新后出现残留
        key=2;
    }
    else if(change<1100&&change>900)//k1   确认       982
    {
        LCD_Fill(0,0,128,128,WHITE);
        key=1;
    }
    else if(change<600&&change>420)//k3           500
    {
        LCD_Fill(0,0,128,128,WHITE);
        key=3;
        flag_picture++;
        if(flag_picture==2)
        {
            flag_picture = 0;
        }
    }
    else if(change<420&&change>70)//旋转         370
    {
        key=4;
        cursor++;
        if(cursor==4)
        {
           cursor=0;
        }
    }
}

4.4  按键操作

读取到按键键值后,就可以对二级菜单进行相应操作

获取界面函数:(定义位置:menu.c,调用位置:main.c):

void get_page(void)
{
    if(cursor==0&&key==1)
    {
        page=21;  //0
    }
    else if(cursor==1&&key==1)
    {
        page=22;  //1
    }
    else if(cursor==2&&key==1)
    {
        page=23;  //2
    }
    else if(cursor==3&&key==1)
    {
        page=24;  //3
    }
    else if(key==2)
    {
        page=1;   //主界面
    }
    else if(key==0)//初始状态key=0
    {
        page=1;
    }
}

4.5  LCD菜单显示(定义位置:menu.c,调用位置:main.c):

extern const unsigned char gImage_1[];  //四张图片,本来想多存几张的,但是发现内存不够了
extern const unsigned char gImage_2[];
extern const unsigned char gImage_picture[];
extern const unsigned char gImage_app[];

//主菜单
void GUI_main(void)
{
    LCD_ShowString(0,0," Ranbe  ",BLACK,WHITE,32,0);
    LCD_ShowString(2,32,"    Picture    ",BLACK,WHITE,16,0);  //0
    LCD_ShowString(2,48,"  MSP430f5529   ",BLACK,WHITE,16,0);//1
    LCD_ShowString(2,64,"   Study plan   ",BLACK,WHITE,16,0); //2
    LCD_ShowString(2,80,"      App      ",BLACK,WHITE,16,0);  //3
    LCD_ShowString(2,96,"    .......     ",BLACK,WHITE,16,0);    //4
    switch(cursor)
    {
        case 0:
            LCD_ShowString(1,32,"->",RED,WHITE,16,0);
            break;
        case 1:
            LCD_ShowString(1,48,"->",RED,WHITE,16,0);
            break;
        case 2:
            LCD_ShowString(1,64,"->",RED,WHITE,16,0);
            break;
        case 3:
            LCD_ShowString(1,80,"->",RED,WHITE,16,0);
            break;
    }
}

//二级界面
void GUI_21(void)
{
    switch(flag_picture)
    {
        case 0:
            LCD_ShowPicture(0,0,128,128,gImage_1 );//显示图片
                    break;
        case 1:
            LCD_ShowPicture(0,0,128,128,gImage_2 );//显示图片
                    break;
    }

}

void GUI_22(void)
{
     LCD_ShowString(0,0,"  MSP430f5529  ",BLACK,WHITE,16,0);
     LCD_ShowString(2,18,"TI",BLACK,WHITE,16,0);               //0
     LCD_ShowString(2,36,"25MHz",BLACK,WHITE,16,0);               //1
     LCD_ShowString(2,54,"Flash 128KB",BLACK,WHITE,16,0);               //2
     LCD_ShowString(2,72,"RAM 8K",BLACK,WHITE,16,0);               //3
     LCD_ShowString(2,90,".......",BLACK,WHITE,16,0);           //4
}

void GUI_23(void)
{
    LCD_ShowString(0,0,"STUDY PLAN",BLACK,WHITE,16,0);
    LCD_ShowString(2,18,"Higher mathematics",BLACK,WHITE,16,0); //0
    LCD_ShowString(2,36,"7--9AM",BLACK,WHITE,16,0);             //1
    LCD_ShowString(2,54,"MSP430f5529",BLACK,WHITE,16,0);        //2
    LCD_ShowString(2,72,"9.20--11.20",BLACK,WHITE,16,0);        //3
    LCD_ShowString(2,90,".......",BLACK,WHITE,16,0);            //4
}

void GUI_24(void)
{
     LCD_ShowPicture(0,0,120,128,gImage_app);//显示图片 40*40
}

void GUI_display(void)
{
     switch(page)
     {
         case 1:
             GUI_main();
             break;
         case 21:
             GUI_21();
             break;
         case 22:
             GUI_22();
             break;
         case 23:
             GUI_23();
             break;
         case 24:
             GUI_24();
             break;
     }
}

4.6  菜单图片

由于某种未知原因,上传了N遍上传不了,大家可以去看我在哔哩哔哩发的视频,里面有展示菜单界面,实在抱歉。

4.7  主函数代码

//定义全局变量
uint16_t Measured;                         
uint16_t Measured_last;
uint16_t change;
uint16_t page=1;//界面等级
uint16_t page_1=1;
uint16_t key=0;
uint16_t cursor=0;//光标位置
uint16_t flag_picture=0;

int main( void )
{
    // Stop watchdog timer to prevent time out reset
   WDT_A_hold(WDT_A_BASE);
   clock_init(20);//20Mhz
   LCD_Init();     //LCD初始化
   ADC_Init();    //ADC初始化
   //interrupts enabled
   __bis_SR_register(GIE);
   Close_heat();  //关闭电阻加热模块
   LCD_Fill(0,0,128,128,WHITE);  //全屏填充白色
    while(1)
    {
        ADC12_A_startConversion(ADC12_A_BASE, ADC12_A_MEMORY_0, ADC12_A_SINGLECHANNEL);  
        __bis_SR_register(LPM0_bits + GIE);
        get_page();                                                           //获取要显示的界面
        GUI_display();                                                       //显示界面
    }
}

5 遇到的主要难题

5.1  ADC采集

项目中我没有对采集到的电压进行滤波,采集到的电压值不准确,不稳定。

具体优化方案:

1、限幅滤波法(又称程序判断滤波法)
A、方法:
根据经验判断,确定两次采样允许的最大偏差值(设为A)
每次检测到新值时判断:
如果本次值与上次值之差<=A,则本次值有效
如果本次值与上次值之差>A,则本次值无效,放弃本次值,用上次值代替本次值

B、优点:
能有效克服因偶然因素引起的脉冲干扰

C、缺点
无法抑制那种周期性的干扰
平滑度差

2. 中位值滤波算法

A、方法:

连续采样N次(N取奇数)
把N次采样值按大小排列
取中间值为本次有效值

B、优点

能有效克服因偶然因素引起的波动干扰
对温度、液位的变化缓慢的被测参数有良好的滤波效果

C、缺点

对流量、速度等快速变化的参数不宜

3、算术平均滤波法
A、方法:
连续取N个采样值进行算术平均运算
N值较大时:信号平滑度较高,但灵敏度较低
N值较小时:信号平滑度较低,但灵敏度较高
N值的选取:一般流量,N=12;压力:N=4

B、优点:
适用于对一般具有随机干扰的信号进行滤波
这样信号的特点是有一个平均值,信号在某一数值范围附近上下波动

C、缺点:
对于测量速度较慢或要求数据计算速度较快的实时控制不适用
比较浪费RAM

由于这个项目的主要目的不是采集ADC电压,而是从电压值判断哪个按键按下,所以只要

采集到的电压有波动,对整个系统的影响,应该不大

5.2 按键检测

从视频中,你们也可以发现,我每次按下的时间都较长,短按的话,不一定有反应,

说明代码还需优化。

而且我的代码不能识别旋转编码器旋转的方向,我只能判断出旋转编码器旋转半圈。

具体的优化步骤 :

我认为可以以一定的频率对ADC进行采样,绘出一定时间内的ADC变化曲线,通过曲线的变化

来判断按键是否按下,这样就可以判断正转和反转。这部分的代码应该稍微有些复杂,主要是

我的C语言学的较差,所以就没有尝试使用这种方法。

5.3 LCD显示

二级菜单进行切换时,它的刷新率还是比较慢的,可能是每次界面切换的时候都要清屏导致的。但是如果不清屏的话,有些界面之间的切换,会出现残留现象。

旋转编码器旋转时,光标的移动也很慢,需要较长的时间才能响应。提高刷新率也是代码继续优化的一个重要方向。

6 未来的计划建议

该项目已经成功实现了二级菜单的功能。但是有些地方还可以提高,可以把这个二级菜单做的更加精美。

1.提高刷新率;

2.缩短旋转编码器旋转后的响应时间;

3.提高显示的画质(不过图片显示受制于硬件,软件部分提高的空间不大,升级硬件应该可以

大幅提高);

4.旋转编码器的顺时针和逆时针旋转无法区分,可以采集更多的ADC值,得出ADC变化曲线,来判断旋转方向;

5.C语言还需要深入学习,硬件方面也要加强。

 

 

附件下载
msp430_adc.rar
团队介绍
南昌大学信息工程学院自动化专业
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号