项目需求
- 实现基于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的全屏幕还需要考虑到坐标变换和边界触碰等问题:
程序流程图
功能实现
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, ¶m1)){
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]);
}
实验现象
遇到问题及解决方法:
1 浮点数打印问题
解决办法:需要更改工程配置的print等级调整成full。
2 主频调整后串口和定时器工作异常问题
根据MSP430 USCI/EUSCI UART Baudrate Calculator (ti.com)工具重新计算相关寄存器配置参数解决。
定时器部分是需要调整分频系数,不能讲定时器频率设置的太高。
参考链接: