1.项目要求
本任务需要用MSP430板测量IO扩展板上的PWM信号,在LCD上以图形化的方式显示游戏摇杆的变化,通过游戏摇杆的拨动,能够触及LCD的全屏幕。
2.项目硬件
2.1简介:
MSP-EXP430F5529LP是一款针对MSP430F5529 USB微控 制器的廉价而简单的开发套件。它为MSP430 MCU提供了一种简单的方法,具有用于编程和调试的板载仿真,以及用于简单用户界面的按钮和LED。
2.2特性:
- 支持 USB 2.0 的 MSP430F5529 16 位微控制器
- 高达 25 MHz
- 128KB 闪存和 8KB 内存
- 12 位 SAR 模数转换器
- 提供各种 USB 设备类示例和嵌入式软件库(CDC、HID、MSC)
- eZ-FET lite:带应用程序 UART 的开源板载调试器
- 通过使用板载 USB 集线器,为仿真器和目标提供 USB 连接
- USB 作为电源:5V 和 3.3V 通过高效 DC/DC 转换器
- 40 针 LaunchPad 标准,利用 BoosterPack 生态系统
2.3输入、输出扩展板介绍:
- 按键、旋转编码器输入 - 以模拟信号的方式
- 双电位计控制输入 - 以数字信号的方式
- RGB三色LED显示
- 1.44寸128*128 LCD,SPI总线访问
- MMA7660三轴姿态传感器
- 电阻加热
- 温度传感器
- 与MSP430 Launch Pad开发板的接口
3.环境配置
- CCSTUDIO — Code Composer Studio™ 集成式开发环境 (IDE)
它是美国德州仪器公司(Texas Instrument,TI)出品的代码开发和调试套件。TI公司的产品线中有一大块业务是数字信号处理器(DSP)和微处理器(MCU),CCS便是供用户开发和调试DSP和MCU程序的集成开发软件。
- MSP430-GCC-OPENSOURCE — GCC - 用于 MSP430 微控制器的开源编译器
GCC(GNU Compiler Collection,GNU 编译器套装),是一套由 GNU 开发的编程语言编译器。GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C语言。GCC 快速演进,变得可处理 C++、Fortran、Pascal、Objective-C、Java 以及 Ada 等他语言。
4.完成的功能及达到的性能
上电后,便可以看见左上角显示频率,以及占空比,通过移动手柄,实现中间黄色圆圈的移动。
发现向右移动频率提升,向左频率下降,向上占空比下降,向下占空比上升。
5.实现思路
- 通过定时器的输入捕获捕获其高低电平,再通过计算得出占空比和频率;
- 通过提高msp430的时钟频率让其能正常使用spi;
- 通过查询地址,得出如何操控基于st7789的1.44寸LCD屏幕;
- 最后通过查看占空比和频率得到如何操控整个屏幕最后做出项目。
以下是设计流程图
6.程序实现
6.1提高定时器的时钟频率
为了让软件spi能正常的运行下去,首先我们得将默认低时钟频率的msp430提高一定的频率让它能正常运行。
提高电压
void SetVcoreUp(unsigned int level)
{
// Open PMM registers for write
PMMCTL0_H = PMMPW_H;
// Set SVS/SVM high side new level
SVSMHCTL = SVSHE + SVSHRVL0 * level + SVMHE + SVSMHRRL0 * level;
// Set SVM low side to new level
SVSMLCTL = SVSLE + SVMLE + SVSMLRRL0 * level;
// Wait till SVM is settled
while ((PMMIFG & SVSMLDLYIFG) == 0)
;
// Clear already set flags
PMMIFG &= ~(SVMLVLRIFG + SVMLIFG);
// Set VCore to new level
PMMCTL0_L = PMMCOREV0 * level;
// Wait till new level reached
if ((PMMIFG & SVMLIFG))
while ((PMMIFG & SVMLVLRIFG) == 0)
;
// Set SVS/SVM low side to new level
SVSMLCTL = SVSLE + SVSLRVL0 * level + SVMLE + SVSMLRRL0 * level;
// Lock PMM registers for write access
PMMCTL0_H = 0x00;
}
提高频率
void initClock(void)
{
// Increase Vcore setting to level3 to support fsystem=25MHz
// NOTE: Change core voltage one level at a time..
SetVcoreUp(0x01);
SetVcoreUp(0x02);
SetVcoreUp(0x03);
UCSCTL3 = SELREF_2; // Set DCO FLL reference = REFO
UCSCTL4 |= SELA_2; // Set ACLK = REFO
__bis_SR_register(SCG0); // Disable the FLL control loop
UCSCTL0 = 0x0000; // Set lowest possible DCOx, MODx
UCSCTL1 = DCORSEL_7; // Select DCO range 50MHz operation
UCSCTL2 = FLLD_0 + 762; // Set DCO Multiplier for 25MHz
// (N + 1) * FLLRef = Fdco
// (762 + 1) * 32768 = 25MHz
// Set FLL Div = fDCOCLK/2
__bic_SR_register(SCG0); // Enable the FLL control loop
// Worst-case settling time for the DCO when the DCO range bits have been
// changed is n x 32 x 32 x f_MCLK / f_FLL_reference. See UCS chapter in 5xx
// UG for optimization.
// 32 x 32 x 25 MHz / 32,768 Hz ~ 780k MCLK cycles for DCO to settle
__delay_cycles(782000);
// Loop until XT1,XT2 & DCO stabilizes - In this case only DCO has to stabilize
do
{
UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG);
// Clear XT2,XT1,DCO fault flags
SFRIFG1 &= ~OFIFG; // Clear fault flags
} while (SFRIFG1 & OFIFG); // Test oscillator fault flag
}
6.2定时器中断输入捕获
为了能获取到拓展板上的PWM波形,我们需要将定时器变换到输入捕获状态,定时器中断就是间隔一定的时间,执行一次中断服务函数。
定时器初始化
void Frequence_Duty_init()
{
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,GPIO_PIN2);
//选用P1.2 使用TA0.1 Timer Capture
Timer_A_initContinuousModeParam Counter = {0};
Counter.clockSource = TIMER_A_CLOCKSOURCE_SMCLK;
Counter.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1;
Counter.startTimer = true;
Counter.timerClear = TIMER_A_DO_CLEAR;
Counter.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_ENABLE;
Timer_A_initContinuousMode(TIMER_A0_BASE, &Counter);
//开启一个连续模式的计时
Timer_A_initCaptureModeParam CaptureParam = {0};
CaptureParam.captureRegister = TIMER_A_CAPTURECOMPARE_REGISTER_1;
CaptureParam.captureMode = TIMER_A_CAPTUREMODE_RISING_AND_FALLING_EDGE;
CaptureParam.captureInputSelect = TIMER_A_CAPTURE_INPUTSELECT_CCIxA;
CaptureParam.synchronizeCaptureSource = TIMER_A_CAPTURE_SYNCHRONOUS;
CaptureParam.captureInterruptEnable = TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE;
CaptureParam.captureOutputMode = TIMER_A_OUTPUTMODE_OUTBITVALUE;
Timer_A_initCaptureMode(TIMER_A0_BASE,&CaptureParam);
//开启捕获模式,并开启它的中断
}
定时器中断
#pragma vector=TIMER0_A1_VECTOR
__interrupt
void TIMER0_A1_ISR (void)
{
static uint16_t Overflow_Times = 0;
switch(TA0IV)
{
case TA0IV_TACCR1:
if(GPIO_getInputPinValue(GPIO_PORT_P1,GPIO_PIN2)==1 && index == 0) //获取引脚电平,如果为高电平,将当前寄存器值存入Sign_Begin
{
Sign_Begin = TA0R;
index=(index+1)%3;
Overflow_Times = 0;
}
else if(index==1 && GPIO_getInputPinValue(GPIO_PORT_P1,GPIO_PIN2)==0) //获取引脚电平,如果为低电平,将当前寄存器值存入Sign_End
{
Sign_End = Timer_A_getCaptureCompareCount(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);
index=(index+1)%3;
if(!Overflow_Times) //计算高电平时间,如果高电平和低电平都在一个计数周期之内,进入
{
Sign_Counts = Sign_End - Sign_Begin; //如果高低电平在同一个计数周期内,那么直接相减
}
else //计算高电平时间,如果高电平和低电平不在一个计数周期之内,进入
{
Sign_Counts = (uint32_t)(65536 * (Overflow_Times )+ Sign_End - Sign_Begin); //如果高低电平不在同一个计数周期,需要先加上一个周期的计数值
}
}
else if (index==2 && GPIO_getInputPinValue(GPIO_PORT_P1,GPIO_PIN2)==1)
{
Sign_All=Timer_A_getCaptureCompareCount(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);
if(!Overflow_Times) //计算高电平时间,如果高电平和低电平都在一个计数周期之内,进入
{
Sign_Counts1 = Sign_All - Sign_Begin; //如果高低电平在同一个计数周期内,那么直接相减
}
else //计算高电平时间,如果高电平和低电平不在一个计数周期之内,进入
{
//注意,这里强制类型转换,是因为uint16_t 的最大值为65535,此处的Sign_Counts值会明显大于65535
Sign_Counts1 = (uint32_t)(65536 * (Overflow_Times) + Sign_All - Sign_Begin); //如果高低电平不在同一个计数周期,需要先加上一个周期的计数值
}
index=(index+1)%3;
}
Timer_A_clearCaptureCompareInterrupt(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);
break;
case TA0IV_TAIFG:
Overflow_Times++;
Timer_A_clearTimerInterrupt(TIMER_A0_BASE);
break;
default:
break;
}
}
6.3 1.44寸的LCD屏幕初始化
为了完成任务中控制屏幕上的信息我们可以先上拓展版上的LCD屏幕初始化,再然后通过代码实现画一个小圆圈,从而使得我们可以通过遥杆控制圆圈。
LCD屏幕初始化
void ST7735_display_init( void )
{
ST7735_command( SW_RESET );
pause();
ST7735_command( SLEEP_OUT );
pause();
ST7735_command( DISPLAY_ON );
pause();
data[0] = 0xC0;
ST7735_command(0x36); // 屏幕显示方向设置
ST7735_data(data,1);
}
画圆函数
void Draw_Circle(unsigned short x0,unsigned short y0,unsigned short r,unsigned short color)
{
int a,b;
a=0;b=r;
if(x0<=0)
{
x0=0;
}
else if(x0>=130)
{
x0=130;
}
if(y0<=0)
{
y0=0;
}
else if(y0>=127)
{
y0=127;
}
while(a<=b)
{
LCD_DrawPoint(x0-b,y0-a,color); //3
LCD_DrawPoint(x0+b,y0-a,color); //0
LCD_DrawPoint(x0-a,y0+b,color); //1
LCD_DrawPoint(x0-a,y0-b,color); //2
LCD_DrawPoint(x0+a,y0-b,color); //5
LCD_DrawPoint(x0+a,y0+b,color); //6
LCD_DrawPoint(x0+b,y0+a,color); //4
LCD_DrawPoint(x0-b,y0+a,color); //7
a++;
if((a*a+b*b)>(r*r)) //判断要 画的点是否过远
{
b--;
}
}
}
6.4 完成主要的功能
为了让摇杆能控制圆圈并且达到整个屏幕的范围,下面为操控圆圈的函数
操控圆圈
void title_circle (void)
{
if(x_centre_circle>Frequency) //左半部分
{
x_circle =(Frequency-x_Fre_L)/0.8125;
if(Frequency<x_Fre_L) x_circle=0;
}
else if(x_centre_circle<Frequency) //右半部分
{
x_circle = 64+(Frequency-x_centre_circle)/1.3593;
}
if(y_centre_circle>Duty) //上半部分
{
y_circle = (Duty-y_Duty_U)*3;
if(Duty<y_Duty_U) y_circle=3;
}
else if(y_centre_circle<Duty)
{
y_circle = 64+(Duty-y_centre_circle)*3.5;
}
if(x_centre_circle == Frequency)
x_circle = 64;
if(y_centre_circle == Duty)
y_circle = 64;
LCD_ShowNum(0,0,Frequency,5,12);
LCD_ShowNum(20,0,Duty,5,12);
Draw_Circle(x_circle,y_circle,3,YELLOW);
}
7.主要的困难
7.1MSP430的学习
这次活动是第一次参加,对于一些MSP430的操作还是不太会,还有定时器的操作还是不会,只能上网上搜索并参考。
7.2 屏幕的操作
对于屏幕的操作,若不对其限幅的话会使得它跳到另一端,最初不知道还在调其他的东西。
7.3 圆圈的操作
通过测量发现若只对上下左右限制会导致四个角落无法接触,所以通过思考,可以先测量出四个角落的频率,再对测量出来的进行限幅。
8.未来的计划建议
该项目已经完成游戏手柄控制LCD上的信息,并达到了预期指标。然而可以通过更改可以将项目变得更好:
- 将软件spi更改成硬件spi可以让屏幕更快的刷新
- 将MSP430的时钟频率提升至45M,从而让屏幕更快的刷新以及更快的读取PWM波形。
关于这次项目设计,我花费了比较多的心思,既是对单片机理论内容的一次复习和巩固,还让我们丰富了更多与该专业相关的其他知识,比如软件应用等,在摸索中学习,在摸索中成长,在学习的过程中带着问题去学我发现效率很高,这是我做这次课程设计的又一收获,在真正设计之前我们做了相当丰富的准备,首先巩固一下课程理论,再一遍熟悉课程知识的构架,然后结合加以理论分析、总结,有了一个清晰的思路和一个完整的的软件流程图之后才着手设计。在设计程序时,我们不能妄想一次就将整个程序设计好,反复修改、不断改进是程序设计的必经之路;养成注释程序的好习惯是非常必要的,一个程序的完美与否不仅仅是实现功能,而应该让人一看就能明白你的思路,这样也能为资料的保存和交流提供了方便,我觉得在设计课程过程中遇到问题是很正常,但我们应该将每次遇到的问题记录下来,并分析清楚,以免下次再碰到同样的问题的课程设计又出错了。