基于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),直接下载安装即可,并导入库函数,这里网上有很多教程,就不再赘述了。
流程图如下:
lcd显示屏驱动部分:
在淘宝中找到该类型显示屏可以获得驱动代码,不过这个代码是基于stm32的,需要对代码进行移植
将这几个.c和.h文件导入ccs工程中,第一步查看原理图
修改这些文件对应的引脚配置,并且由于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,¶m); //
}
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入手了一个新的单片机,学会怎么把程序进行移植,把没有学过的内容转化成已知的内容,提高了我的学习能力。同时也非常感谢硬禾学堂和电子森林,这个拿到的板卡设计很好,颜值挺高的,可以看得出他们的工作认真,并且在群里也会对一些问题做出解释,解决了我很多的问题。电子森林的电路仿真功能也十分强大,能大大缩小我的程序调试次数。在以后我会多多关注硬禾学堂和电子森林的活动,积极参加,学习更多知识,同时也希望他们越做越大,越做越好,谢谢硬禾学堂和电子森林。