基于MSP430完成游戏手柄控制LCD
本项目基于MSP430定时器输入捕获功能测量PWM信号的频率和占空比的方式来实现通过手柄控制显示屏的光标移动。
标签
嵌入式系统
显示
MSP430
摇杆
maskmoo
更新2023-03-28
800

项目需求

  • 实现基于MSP430定时器输入捕获功能测量扩展板PWM信号的频率和占空比,并判断出游戏手柄的方向变化;
  • 实现在扩展板显示屏在MSP430上的驱动移植;
  • 实现拨动摇杆能够触及LCD的全屏幕。

硬件介绍: 关于MSP430F5529LP:

MSP430F5529LP是一种低功耗微控制器,由德州仪器公司(Texas Instruments)开发。采用了MSP430核心,具有16位RISC架构,主频最高可达25MHz。该微控制器具有512KB闪存和8KB RAM,并配备了一系列外设,包括USB、UART、SPI、I2C、ADC和DAC等,可以满足各种应用需求。此外,它还支持多种低功耗模式,可帮助系统在不同场景下实现最佳的能耗管理。MSP430F5529LP通常被用于工业控制、物联网、医疗设备、家用电器等各种领域的应用。

关于扩展板:

扩展板包含按键、旋转编码器输入、双电位计控制输入、RGB三色LED显示1.44寸128*128 LCD,SPI总线访问、MMA7660三轴姿态传感器、电阻加热、温度传感器、与MSP430 Launch Pad开发板的接口等部分组成。

设计思路

1 基于MSP430定时器输入捕获功能测量扩展板PWM信号的频率和占空比,并判断出游戏手柄的方向变化:

要实现这个功能,需要用MSP430的定时器输入捕获模块来测量扩展板PWM信号的频率和占空比,具体实现方法如下:

  • 配置MSP430的定时器输入捕获模块,设置捕获通道和触发源。
  • 在定时器中断服务程序中读取捕获寄存器的值,计算PWM信号的频率和占空比。
  • 根据PWM信号的频率和占空比的变化判断手柄的方向变化。

2 实现在扩展板显示屏在MSP430上的驱动移植:

要实现这个功能,需要先了解扩展板显示屏的接口和通信协议,然后在MSP430上编写相应的驱动程序。具体实现方法如下:

  • 查找扩展板显示屏的数据手册,了解其接口和通信协议。
  • 在MSP430上编写相应的驱动程序,包括初始化、数据传输、刷新等功能。
  • 调试程序,确保扩展板显示屏能够正常工作。

3 实现拨动摇杆能够触及LCD的全屏幕:

在实现PWM信号的频率和占空比测量的基础上就可以实时监测摇杆的状态,根据摇杆的状态计算需要显示的位置,并在LCD上显示。因为理论上摇杆的轨迹是个圆形,所以要实现具体实现触及LCD的全屏幕还需要考虑到坐标变换和边界触碰等问题:

程序流程图

FvP-syDBgw6nVhe19oSZSKFBzOPJ

功能实现

1 定时器输入捕获测量PWM信号频率和占空比

需要配置定时器的计数模式、计数频率和触发源等参数,选择的是基于SMCLK的12分频,在系统时钟为25MHz时,分频设置的太低会有问题。

void Timer_A0_Capture_Init()
{
    Timer_A_initContinuousModeParam htim = {0};
    htim.clockSource = TIMER_A_CLOCKSOURCE_SMCLK;  
    htim.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_12;  
    htim.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_ENABLE;  //使能TAIE中断
    htim.timerClear = TIMER_A_DO_CLEAR;        //把定时器的定时计数器,分频计数器的计数值清零
    htim.startTimer = true;                 //初始化后立即启动定时器
    Timer_A_initContinuousMode(TIMER_A0_BASE, &htim);  //设置为连续计数模式

    GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1, GPIO_PIN2);   //复用P1.2
    Timer_A_initCaptureModeParam capture_htim = {0};
    capture_htim.captureRegister = TIMER_A_CAPTURECOMPARE_REGISTER_1;   //因为P1.2使用的是TA0.1,所以这里是REGISTER_1
    capture_htim.captureMode = TIMER_A_CAPTUREMODE_RISING_AND_FALLING_EDGE;  //选择双边沿触发
    capture_htim.captureInputSelect = TIMER_A_CAPTURE_INPUTSELECT_CCIxA;     //
    capture_htim.synchronizeCaptureSource = TIMER_A_CAPTURE_SYNCHRONOUS;     //捕获源与计时器时钟同步
    capture_htim.captureInterruptEnable = TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE;
    capture_htim.captureOutputMode = TIMER_A_OUTPUTMODE_OUTBITVALUE;
    Timer_A_initCaptureMode(TIMER_A0_BASE,&capture_htim);
}

在定时器中断服务程序中,可以读取捕获寄存器的值,根据捕获寄存器的值计算PWM信号的周期和占空比。

前面配置的是上升沿和下降沿都触发,所以这里用前两次触发来测量高电平持续时间,前3次触发来测量周期时间。

uint32_t enter_count = 0;
#pragma vector=TIMER0_A1_VECTOR
__interrupt
void TIMER0_A1_ISR (void)
{
    static uint16_t  Overflow_Times = 0;
    static uint16_t Sign_Begin = 0, Sign_End = 0,  ALL_Sign_End = 0;;

    switch(TA0IV)
    {
        case TA0IV_TACCR1:
            if(GPIO_getInputPinValue(GPIO_PORT_P1,GPIO_PIN2))  //获取P2.5引脚电平,如果为高电平,将当前寄存器值存入Sign_Begin
            {
                ALL_Sign_End = Timer_A_getCaptureCompareCount(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);
                if(enter_count != 0){
                    if(!Overflow_Times)    //计算高电平时间,如果高电平和低电平都在一个计数周期之内,进入
                        ALL_Sign_Counts = ALL_Sign_End - Sign_Begin;   //如果高低电平在同一个计数周期内,那么直接相减
                    else                   //计算高电平时间,如果高电平和低电平不在一个计数周期之内,进入
                    {
                        //注意,这里强制类型转换,是因为uint16_t 的最大值为65535,此处的Sign_Counts值会明显大于65535
                        ALL_Sign_Counts = (uint32_t)(65536 * Overflow_Times + ALL_Sign_End - Sign_Begin);  //如果高低电平不在同一个计数周期,需要先加上一个周期的计数值
                        Overflow_Times = 0;
                    }

                    Sign_Begin = ALL_Sign_End;
                }
                else{

                    Sign_Begin = ALL_Sign_End;
                    enter_count =2 ;
                }

            }
            else             //获取P2.5引脚电平,如果为低电平,将当前寄存器值存入Sign_End
            {
                Sign_End = Timer_A_getCaptureCompareCount(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);
                if(!Overflow_Times)    //计算高电平时间,如果高电平和低电平都在一个计数周期之内,进入
                    Sign_Counts = Sign_End - Sign_Begin;   //如果高低电平在同一个计数周期内,那么直接相减
                else                   //计算高电平时间,如果高电平和低电平不在一个计数周期之内,进入
                {
                    //注意,这里强制类型转换,是因为uint16_t 的最大值为65535,此处的Sign_Counts值会明显大于65535
                    Sign_Counts = (uint32_t)(65536 * Overflow_Times + Sign_End - Sign_Begin);  //如果高低电平不在同一个计数周期,需要先加上一个周期的计数值
//                    Overflow_Times = 0;
                }
            }
            Timer_A_clearCaptureCompareInterrupt(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);
            break;
        case TA0IV_TAIFG:
            if(GPIO_getInputPinValue(GPIO_PORT_P1,GPIO_PIN2))  //获取P2.5引脚电平。如果定时器都溢出中断了,现在还是高电平,那么表明高电平和低电平不在同一个定时周期内
            {
                ++Overflow_Times;
            }
            else  //获取P2.5引脚电平。如果定时器溢出中断了,现在不是高电平,那么表明高电平和低电平在同一个定时周期内
                Overflow_Times = 0;
            Timer_A_clearTimerInterrupt(TIMER_A0_BASE);
            break;
        default:
            break;
    }


}

频率和占空比计算,1048576*2.0是最终定时器的时钟,可以根据初始化的配置进行调整。

        frequency = 1/(ALL_Sign_Counts/(1048576*2.0));
        dutyCycle = (Sign_Counts/(1048576*2.0))/(ALL_Sign_Counts/(1048576*2.0))*100;

2 ST7735显示驱动移植

这部分是使用的乔楚大佬群里分享的工程,使用的软件SPI进行屏幕的驱动。

#ifndef ST7735_CONFIG_H_
#define ST7735_CONFIG_H_


// #define RED_BLUE_REVERSE

#define LCD_RESET_OUTPORT   P3OUT   // P3.7
#define LCD_RESET_DIRPORT   P3DIR
#define LCD_RESET_BIT_NUM   7
#define LCD_RESET_BIT_MASK  (1 << LCD_RESET_BIT_NUM)

#define LCD_CS_OUTPORT   P2OUT      // P2.6
#define LCD_CS_DIRPORT   P2DIR
#define LCD_CS_BIT_NUM   6
#define LCD_CS_BIT_MASK  (1 << LCD_CS_BIT_NUM)

#define LCD_A0_OUTPORT  P2OUT       // P2.7
#define LCD_A0_DIRPORT  P2DIR
#define LCD_A0_BIT_NUM  7
#define LCD_A0_BIT_MASK (1 << LCD_A0_BIT_NUM)

#define LCD_SCLK_OUTPORT  P3OUT     // P3.2
#define LCD_SCLK_DIRPORT  P3DIR
#define LCD_SCLK_BIT_NUM  2
#define LCD_SCLK_BIT_MASK (1 << LCD_SCLK_BIT_NUM)

#define LCD_SDA_OUTPORT   P3OUT     // P3.0
#define LCD_SDA_DIRPORT   P3DIR
#define LCD_SDA_BIT_NUM   0
#define LCD_SDA_BIT_MASK  (1 << LCD_SDA_BIT_NUM)

#define BACKLITE_OUTPORT   P1OUT
#define BACKLITE_DIRPORT   P1DIR
#define BACKLITE_BIT_NUM   0
#define BACKLITE_BIT_MASK  (1 << BACKLITE_BIT_NUM)

#endif /* ST7735_CONFIG_H_ */

3 坐标换算及显示部分

这部分是根据频率和占空比的变化来计算最终的显示坐标,并通过一个8*8像素的小方块在屏幕中进行显示。

        py = (int)((dutyCycle-32)/52*16);
        if(frequency>300&& frequency<500){
            px = (frequency-301)/145.0*8+8;
        }
        if(frequency>200&& frequency<300){
            px = (frequency-225)/75.0*8;
        }

        px = 181-(181/16*px);
        py = 181-(181/16*py);
        px = px - 40;
        if(px>=(120)){px=120;}
        else if(px<=26){px=0;}
        else{

        }
        py = py - 40;
        if(py>=(120)){py=120;}
        else if(py<=26){py=0;}
        else{

        }

        UART_printf(USCI_A1_BASE,"px:%d py:%d\r\n", px, py);
        if(px != old_px || py!=old_py){
            draw( old_px, old_py, 6, 6, demoPal[0]);
            draw( px, py, 6, 6, demoPal[5]);
        }
        old_px = px;
        old_py = py;

4 串口调试

串口P4.4和 P4.5引脚的串口是与调试器直接相连的,可以方便的进行调试信息的打印。

void Usart1_Init()
{
    //P4.4=UCA1TXD      P4.5=UCA1RXD
    GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P4, GPIO_PIN5+GPIO_PIN4);

    USCI_A_UART_initParam param1 = {0};
    param1.selectClockSource = USCI_A_UART_CLOCKSOURCE_SMCLK;
    param1.clockPrescalar = 13;
    param1.firstModReg = 9;
    param1.secondModReg = 0;
    param1.parity = USCI_A_UART_NO_PARITY;   //无校验位
    param1.msborLsbFirst = USCI_A_UART_LSB_FIRST;  //低位先行
    param1.numberofStopBits = USCI_A_UART_ONE_STOP_BIT;  //1停止位
    param1.uartMode = USCI_A_UART_MODE;
    param1.overSampling = USCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION;

    if (STATUS_FAIL == USCI_A_UART_init(USCI_A1_BASE, &param1)){
       return;
    }
    //Enable UART module for operation
    USCI_A_UART_enable(USCI_A1_BASE);

    //Enable Receive Interrupt
    USCI_A_UART_clearInterrupt(USCI_A1_BASE,USCI_A_UART_RECEIVE_INTERRUPT);
    USCI_A_UART_enableInterrupt(USCI_A1_BASE,USCI_A_UART_RECEIVE_INTERRUPT);
}


void UART_printf(uint16_t baseAddress, const char *format,...)
{
    uint32_t length;
    va_list args;
    uint32_t i;
    char TxBuffer[128] = {0};

    va_start(args, format);
    length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer), (char*)format, args);
    va_end(args);

    for(i = 0; i < length; i++)
        USCI_A_UART_transmitData(baseAddress, TxBuffer[i]);
}

实验现象

Ft198hr9xQpU8w9VgwW6_oZGsO1X      Ft__ac1xD0HFZRdoSZgRsDiAZTKR

遇到问题及解决方法:

1 浮点数打印问题

解决办法:需要更改工程配置的print等级调整成full。

2 主频调整后串口和定时器工作异常问题

根据MSP430 USCI/EUSCI UART Baudrate Calculator (ti.com)工具重新计算相关寄存器配置参数解决。

定时器部分是需要调整分频系数,不能讲定时器频率设置的太高。

参考链接:

1 MSP430F5529库函数定时器A——捕获实验

2MSP430F5529库函数学习——串口

3 Timer_A捕获模式

4 MSP430F5529 LaunchPad Development Kit

5 MSP-EXP430F5529LP

附件下载
StickControlProject.zip
团队介绍
向前进
团队成员
maskmoo
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号