基于msp430通过IO扩展板上的按键和旋转编码器控制并实现菜单功能
本任务通过MSP430核心板的ADC监测IO板模拟输出管脚的变化,判断哪一个按键或编码器的旋转发生了变化,进而控制1.44寸LCD屏幕的菜单显示,实现主菜单和二级菜单。
标签
嵌入式系统
2023寒假在家练
阿超的爱人
更新2023-03-28
南昌大学
486

基于msp430的电赛训练平台

本项目主要由msp430核心控制板以及硬禾学堂的拓展板组成。MSP-EXP430F5529LP是一款针对MSP430F5529 USB微控 制器的廉价而简单的开发套件。它为MSP430 MCU提供了一种简单的方法,具有用于编程和调试的板载仿真,以及用于简单用户界面的按钮和LED。扩展板包含如下功能:1.按键、旋转编码器输入 - 以模拟信号的方式。2.双电位计控制输入 - 以数字信号的方式。3.RGB三色LED显示。4.1.44寸128*128 LCD,SPI总线访问。5.MMA7660三轴姿态传感器.6.电阻加热。7.温度传感器。8.与MSP430 Launch Pad开发板的接口。

设计思路:首先需要驱动lcd显示屏,然后配置ADC,对Io口输入电压进行采样,根据检测的电压值可以得出哪个按键被按下。最后用代码设计出一个菜单界面,通过对判断按键的情况切换菜单界面。以下将会分步介绍实现过程

首先需要下载Code Composer Studio 12.2.0(以下简称ccs),直接下载安装即可,并导入库函数,这里网上有很多教程,就不再赘述了。

流程图如下:

FmxqgD-2M-frFkC7O6hc1_KTVAHd

lcd显示屏驱动部分:

在淘宝中找到该类型显示屏可以获得驱动代码,不过这个代码是基于stm32的,需要对代码进行移植

Fk-pWoOaTra4n9kVLATqXEFwfpWP

将这几个.c和.h文件导入ccs工程中,第一步查看原理图

Fi6UVrDpbRxRXY2tgmnzmKdKf0HT

修改这些文件对应的引脚配置,并且由于stm32和msp430中库函数不一致,需要把引脚初始化一起修改

在lcd_init.c中,修改如下

void LCD_GPIO_Init(void)
{
     GPIO_setAsOutputPin(GPIO_PORT_P3,GPIO_PIN2);//SCL
     GPIO_setAsOutputPin(GPIO_PORT_P3,GPIO_PIN0);//SDA
     GPIO_setAsOutputPin(GPIO_PORT_P3,GPIO_PIN7);//RES
     GPIO_setAsOutputPin(GPIO_PORT_P2,GPIO_PIN7);//DC
     GPIO_setAsOutputPin(GPIO_PORT_P2,GPIO_PIN6);//CS
    GPIO_setOutputHighOnPin(GPIO_PORT_P3,GPIO_PIN2);
    GPIO_setOutputHighOnPin(GPIO_PORT_P3,GPIO_PIN0);
    GPIO_setOutputHighOnPin(GPIO_PORT_P3,GPIO_PIN7);
    GPIO_setOutputHighOnPin(GPIO_PORT_P2,GPIO_PIN7);
    GPIO_setOutputHighOnPin(GPIO_PORT_P2,GPIO_PIN6);

}

在lcd_init.h中

#define LCD_SCLK_Clr() GPIO_setOutputLowOnPin(GPIO_PORT_P3,GPIO_PIN2)//SCL=SCLK
#define LCD_SCLK_Set() GPIO_setOutputHighOnPin(GPIO_PORT_P3,GPIO_PIN2)

#define LCD_MOSI_Clr() GPIO_setOutputLowOnPin(GPIO_PORT_P3,GPIO_PIN0)//SDA=MOSI
#define LCD_MOSI_Set() GPIO_setOutputHighOnPin(GPIO_PORT_P3,GPIO_PIN0)

#define LCD_RES_Clr()  GPIO_setOutputLowOnPin(GPIO_PORT_P3,GPIO_PIN7)//RES
#define LCD_RES_Set()  GPIO_setOutputHighOnPin(GPIO_PORT_P3,GPIO_PIN7)

#define LCD_DC_Clr()   GPIO_setOutputLowOnPin(GPIO_PORT_P2,GPIO_PIN7)//DC
#define LCD_DC_Set()   GPIO_setOutputHighOnPin(GPIO_PORT_P2,GPIO_PIN7)
 		     
#define LCD_CS_Clr()   GPIO_setOutputLowOnPin(GPIO_PORT_P2,GPIO_PIN6)//CS
#define LCD_CS_Set()   GPIO_setOutputHighOnPin(GPIO_PORT_P2,GPIO_PIN6)

#define LCD_BLK_Clr()  GPIO_setOutputLowOnPin(GPIO_PORT_P3,GPIO_PIN2)//BLK
#define LCD_BLK_Set()  GPIO_setOutputHighOnPin(GPIO_PORT_P3,GPIO_PIN2)

接下来做个图片取模的步骤就行,为了给后面的显示表情包铺垫,这个官方资料里也有教程。

后面来配置ADC,观察原理图后发现是通过P1.4引脚检测电压,配置ADC代码如下

void setupADC(void){

      #define ADCpin         GPIO_PORT_P6,GPIO_PIN0
       GPIO_setAsPeripheralModuleFunctionOutputPin(ADCpin);     // 复位P6.0

       ADC12_A_init(ADC12_A_BASE,ADC12_A_SAMPLEHOLDSOURCE_SC, ADC12_A_CLOCKSOURCE_ADC12OSC, ADC12_A_CLOCKDIVIDER_1);  //软件触发,内部振荡器MODCLK作为时钟

       ADC12_A_enable(ADC12_A_BASE);   //启用ADC12_A模块
       //设置并启用采样定时器脉冲,这里是使用的软件触发的形式,所以选择失能
       ADC12_A_setupSamplingTimer(ADC12_A_BASE,ADC12_A_CYCLEHOLD_16_CYCLES,ADC12_A_CYCLEHOLD_16_CYCLES,ADC12_A_MULTIPLESAMPLESDISABLE);

       ADC12_A_configureMemoryParam param = {0};
       param.memoryBufferControlIndex = ADC12_A_MEMORY_0;           //将内存缓冲配置为MEMORY_0
       param.inputSourceSelect = ADC12_A_INPUT_A0;                  //将输入A0映射到内存缓冲区0,因为P6.0引脚对应A0
       param.positiveRefVoltageSourceSelect = ADC12_A_VREFPOS_AVCC; //正电压为AVcc
       param.negativeRefVoltageSourceSelect = ADC12_A_VREFNEG_AVSS; //负电压为AVss
       param.endOfSequence = ADC12_A_NOTENDOFSEQUENCE;              //单通道转换

       ADC12_A_configureMemory(ADC12_A_BASE,&param);   //
      }
 int  readADC(void){

    //开始从MEMORY_0中进行单通道连续转换
    ADC12_A_startConversion(ADC12_A_BASE,ADC12_A_MEMORY_0,ADC12_A_SINGLECHANNEL);

    while(ADC12_A_isBusy(ADC12_A_BASE) == ADC12_A_BUSY){
        // 等待转换完成
    }
    //读取ADC转换之后寄存器的值
    int result = ADC12_A_getResults(ADC12_A_BASE, ADC12_A_MEMORY_0);
    //将其转化为单位为mv的电压值
    return  result ;                     // 3320是测量的Vss
}

然后我们可以通过显示屏将采样的电压显示出来,由于电压变化小,我没有将采样值转换成事迹电压值,这样方便比较大小。于是我们可以对旋转编码器和两个按键分别操作,观察电压的变化,并确定根据不同操作得到的电压范围。。为了方便判断,我们在写一个按键状态处理函数

void key_state()
{
    if((readADC()>3500)&&(readADC()<3700))
    {
        int num1=readADC();
        delay_ms(5);
        int num2=readADC();
        if((readADC()>3500)&&(readADC()<3700))  {key1=0;key2=0;state=0;gameflag=0;}//不按按键时
        if((readADC()<3500)&&(readADC()>3200)&&(page1flag==1))//旋转 旋转编码器时
        {
               picture_flag++;
               if(picture_flag>3)    picture_flag=1;
               if(picture_flag==1)   LCD_ShowPicture(20,20,100,100,gImage_pic);
               if(picture_flag==2)   LCD_ShowPicture(20,20,100,100,gImage_pic_2);
               if(picture_flag==3)   LCD_ShowPicture(20,20,100,100,gImage_pic_3);

        }

    }
    if((readADC()>2700)&&(readADC()<2900))//按下第一个按键
    {
        if(page0flag==1)
           {key1++;if(key1==4)  key1=1;}
        if(page3flag==1)
           {gameflag=1;}
    }
    if((readADC()>1800)&&(readADC()<2000))//按下第二个按键
        {
           key2=1;
        }
    if((readADC()>3100)&&(readADC()<3200)) //按下旋转编码器
        {

           key3=1;page1flag=0;page2flag=0;page3flag=0; page0();
        }
}

这里设置了很多标志位,是为了方便后面的菜单界面处理

在此之前,我们引入延时函数,并修改主频位25Mhz

#define CPU_F ((double)25000000)
#define delay_us(x) __delay_cycles((long)(CPU_F*(double)x/1000000.0))
#define delay_ms(x) __delay_cycles((long)(CPU_F*(double)x/1000.0))


     GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, GPIO_PIN2);
     GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5, GPIO_PIN3);

     UCS_turnOnXT2 (UCS_XT2_DRIVE_4MHZ_8MHZ);

     PMM_setVCore(PMM_CORE_LEVEL_3);

     UCS_initClockSignal(UCS_FLLREF, UCS_XT2CLK_SELECT, UCS_CLOCK_DIVIDER_8);
     UCS_initFLLSettle(25000, 50);

     UCS_initClockSignal(UCS_MCLK, UCS_DCOCLK_SELECT, UCS_CLOCK_DIVIDER_1);
     UCS_initClockSignal(UCS_SMCLK, UCS_DCOCLK_SELECT, UCS_CLOCK_DIVIDER_1);

     GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P2, GPIO_PIN2);   //SMCLK Output
     GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P7, GPIO_PIN7);
       /*修改主频为25MHZ*/

在while(1)前面初始化lcd和ADC就可以了

   LCD_Init();//LCD初始化
    LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
    setupADC();//ADC初始化
    page0();
    page0flag=1;

    GPIO_setAsOutputPin(GPIO_PORT_P1,GPIO_PIN4);

    GPIO_setOutputLowOnPin(GPIO_PORT_P1,GPIO_PIN4);
    //关闭电阻加热

这里关闭电阻加热,防止运行过程中发热严重,芯片被短路。

   while(1)
    {
        state_choice();
        page_choice();

    }

最后其实只要两个函数就可以实现,一个是光标移动函数和页面切换函数。

void state_choice()//设置光标移动函数
{
    if(page0flag==1)
    {
        key_state();
    if(key1)
    {
        num++;
        if(num>3)   num=1;
        while(key1) { key_state();}
        LCD_ShowString(5,40+20*obj(num-1),"-->",BLACK,WHITE,16,0);
        LCD_ShowString(5,40+20*obj(num),"   ",BLACK,WHITE,16,0);
        LCD_ShowString(5,40+20*obj(num+1),"   ",BLACK,WHITE,16,0);
    }
    }
}

 

下面是页面切换函数

void page_choice()//页面切换函数
{
    if(num==1)
    {
        key_state();
        if(key2==1&&(page0flag==1))
        {
            while(key2)  {key_state();};
            LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
            page1();
            page0flag=0;
        }

    }
    if(num==2)
     {
         key_state();
         if(key2==1&&(page0flag==1))
         {
             while(key2)  {key_state();};
             LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
             page2();
             page0flag=0;
         }

     }
    if(num==3)
        {
            key_state();
            if(key2==1&&(page0flag==1))
            {
                while(key2)  {key_state();};
                LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
                page3();
                page0flag=0;
            }

        }
}

其实还有4个页面函数,可以在源代码中看到。下面详细讲解菜单实现逻辑

首先定义一个主菜单界面,也就是上电后显示的界面page0,进入第一二三个选项对应page1,page2,page3,page4。并且在页面函数中设置标志位位pageflag,只要进入该页面,该标志位就会置1,否则是0.这个在页面函数中设计一下就好了。

首先上电后默认在页面0,这时候按下按键1就会触发光标移动函数,光标就会在三个选项中进行切换。当我们选中某个选项时,这个时候我们按下按键2,那么按键2的状态被接收到,这个时候我们只要再对光标移动函数中的标志位进行判断,判断选中了哪个选项,就可以根据选项的不同进入不同的界面。这个其实就是页面切换函数的功能,做了一个简易的菜单,后续的二级菜单,也不过是在页面函数page0,page1,page2,page3,中判断按键按下情况,再进行相应的处理,这样就可以实现页面切换。总的来说,流程是比较清晰的,先把项目分成几个模块,也就是几个界面显示的内容,最后再根据按键对内容进行刷新就可以。

遇到的主要难题:(1)有的图片进行取模之后显示在屏幕上比较模糊,调整了大小也不行。可能是由于这个屏幕的分辨率有限,所以选择一些对分辨率要求不高的表情包即可。

(2)对于msp430模块的ADC配置不太熟悉,可以到csdn上看别人写好的例程,进行改进,也通过这个方式学会了配置msp430的adc。

(3)程序很多次没有达到预期的效果,可以使用lcd屏幕将变量打印出来,这样相当于调试程序,可以清楚的发现是哪一步出现了问题,并做出调整。

心得:这其实是我第一次参加这种项目,主要是想学习到更多东西。从51单片机到stm32,但是那些都是在开发板上做好了的,所以能实现的功能比较有限,而且接触到的单片机也比较局限,通过这次寒假在家练,让我从0入手了一个新的单片机,学会怎么把程序进行移植,把没有学过的内容转化成已知的内容,提高了我的学习能力。同时也非常感谢硬禾学堂和电子森林,这个拿到的板卡设计很好,颜值挺高的,可以看得出他们的工作认真,并且在群里也会对一些问题做出解释,解决了我很多的问题。电子森林的电路仿真功能也十分强大,能大大缩小我的程序调试次数。在以后我会多多关注硬禾学堂和电子森林的活动,积极参加,学习更多知识,同时也希望他们越做越大,越做越好,谢谢硬禾学堂和电子森林。

 

 

 

 

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