本文参考以下几位大佬的文章,感谢前辈们的贡献【抱拳】【抱拳】!
MSP430F5529 DriverLib 库函数学习笔记(五)定时器A ——乙酸氧铍
msp430定时器A有三个:Timer0_A,Timer1_A,Timer2_A,对于每一个定时器,他们都有两个中断向量:
CCR0:常用于定时中断功能
TAIV:常用于输出比较,输入捕获
每个定时器都有这两个中断向量,共三个定时器A,定时器A的所有中断向量的宏定义如下所示:
TIMERx_A0_VECTOR 是 CCR0 的中断向量
TIMERx_A1_VECTOR 是 TAIV 的中断向量
#define TIMER2_A1_VECTOR (43 * 1u) /* 0xFFD6 Timer2_A5 CC1-4, TA */
#define TIMER2_A0_VECTOR (44 * 1u) /* 0xFFD8 Timer2_A5 CC0 */
#define TIMER1_A1_VECTOR (48 * 1u) /* 0xFFE0 Timer1_A3 CC1-2, TA1 */
#define TIMER1_A0_VECTOR (49 * 1u) /* 0xFFE2 Timer1_A3 CC0 */
#define TIMER0_A1_VECTOR (52 * 1u) /* 0xFFE8 Timer0_A5 CC1-4, TA */
#define TIMER0_A0_VECTOR (53 * 1u) /* 0xFFEA Timer0_A5 CC0 */
在编写中断服务函数时需要这几个中断向量,以定时器Timer0_A的CCR0为例子,应该这么编写中断服务函数:
#pragma vector=TIMER0_A0_VECTOR
//这一句把TIMER0_A0_VECTOR这个中断向量赋值给vector(译为向量的意思)
//表示下文将要编写中断向量TIMER0_A0_VECTOR的中断服务函数
__interrupt//指示下文为一个中断服务函数
void TIMER0_A0_ISR (void)//中断服务函数,它的名字可以自己定义,因为上面两行已经决定了它的中断向量
{
GPIO_toggleOutputOnPin(GPIO_PORT_P4, GPIO_PIN7); //翻转P4.7
}
以上就是一个中断函数基本格式,写它的关键就是要知道中断向量是什么,msp430的所有中断向量如下:
#define RTC_VECTOR (41 * 1u) /* 0xFFD2 RTC */
#define PORT2_VECTOR (42 * 1u) /* 0xFFD4 Port 2 */
#define TIMER2_A1_VECTOR (43 * 1u) /* 0xFFD6 Timer2_A5 CC1-4, TA */
#define TIMER2_A0_VECTOR (44 * 1u) /* 0xFFD8 Timer2_A5 CC0 */
#define USCI_B1_VECTOR (45 * 1u) /* 0xFFDA USCI B1 Receive/Transmit*/
#define USCI_A1_VECTOR (46 * 1u) /* 0xFFDC USCI A1 Receive/Transmit*/
#define PORT1_VECTOR (47 * 1u) /* 0xFFDE Port 1 */
#define TIMER1_A1_VECTOR (48 * 1u) /* 0xFFE0 Timer1_A3 CC1-2, TA1 */
#define TIMER1_A0_VECTOR (49 * 1u) /* 0xFFE2 Timer1_A3 CC0 */
#define DMA_VECTOR (50 * 1u) /* 0xFFE4 DMA */
#define USB_UBM_VECTOR (51 * 1u) /* 0xFFE6 USB Timer / cable event / USB reset */
#define TIMER0_A1_VECTOR (52 * 1u) /* 0xFFE8 Timer0_A5 CC1-4, TA */
#define TIMER0_A0_VECTOR (53 * 1u) /* 0xFFEA Timer0_A5 CC0 */
#define ADC12_VECTOR (54 * 1u) /* 0xFFEC ADC */
#define USCI_B0_VECTOR (55 * 1u) /* 0xFFEE USCI B0 Receive/Transmit */
#define USCI_A0_VECTOR (56 * 1u) /* 0xFFF0 USCI A0 Receive/Transmit */
#define WDT_VECTOR (57 * 1u) /* 0xFFF2 Watchdog Timer */
#define TIMER0_B1_VECTOR (58 * 1u) /* 0xFFF4 Timer0_B7 CC1-6, TB */
#define TIMER0_B0_VECTOR (59 * 1u) /* 0xFFF6 Timer0_B7 CC0 */
#define COMP_B_VECTOR (60 * 1u) /* 0xFFF8 Comparator B */
#define UNMI_VECTOR (61 * 1u) /* 0xFFFA User Non-maskable */
#define SYSNMI_VECTOR (62 * 1u) /* 0xFFFC System Non-maskable */
#define RESET_VECTOR (63 * 1u) /* 0xFFFE Reset [Highest Priority] */
(1)外部中断实现
了解中断函数的大致格式之后,GPIO的外部中断就可以实现了,msp430芯片的P1端口和P2端口的共16个引脚都具有外部中断的功能,分别对应两个中断向量:
#define PORT1_VECTOR (47 * 1u) /* 0xFFDE Port 1 */
#define PORT2_VECTOR (42 * 1u) /* 0xFFD4 Port 2 */
16个引脚只对应两个中断向量(也就是两个中断函数),我们怎么在中断函数中确定是哪个引脚导致的此次中断?这就需要标志位的判断了,可以在中断函数中首先通过库函数GPIO_getInterruptStatus来判断是否是某个引脚引发的本次中断。比如说,P2端口引脚1触发的中断可以这样写:
#pragma vector=PORT2_VECTOR // P2口中断源
__interrupt
void Port_2 (void) // 声明一个中断服务程序,名为Port_2()
{
if(GPIO_getInterruptStatus(GPIO_PORT_P2, GPIO_PIN1))
{
if(!GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN1))
{
//执行本次中断需要的操作
}
//P2.1 IFG cleared
GPIO_clearInterrupt(GPIO_PORT_P2, GPIO_PIN1);//最后要记得及时清除中断标志位
}
}
(2)GPIO输出输入:
请参考笔者的这篇文章:MSP430库函数——GPIO操作
下面为一个main.c代码的全部内容,直接下载即可实现功能,作用是使msp430f5529板子上一个由p4^7控制的LED等定时1s反转一次状态。
#include "driverlib.h"
#define MCLK_IN_HZ 25000000
#define delay_us(x) __delay_cycles((MCLK_IN_HZ/1000000*(x)))
#define delay_ms(x) __delay_cycles((MCLK_IN_HZ/1000*(x)))
void SystemClock_Init(void)
{
PMM_setVCore(PMM_CORE_LEVEL_3); //高主频工作需要较高的核心电压
//XT1引脚复用
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, GPIO_PIN4);
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5, GPIO_PIN5);
//起振XT1
UCS_turnOnLFXT1(UCS_XT1_DRIVE_3,UCS_XCAP_3);
//XT2引脚复用
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, GPIO_PIN2);
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5, GPIO_PIN3);
//起振XT2
UCS_turnOnXT2(UCS_XT2_DRIVE_4MHZ_8MHZ);
//XT2作为FLL参考时钟,先8分频,再50倍频 4MHz / 8 * 50 = 25MHz
UCS_initClockSignal(UCS_FLLREF, UCS_XT2CLK_SELECT, UCS_CLOCK_DIVIDER_8);
UCS_initFLLSettle(25000, 50);
//XT1作为ACLK时钟源 = 32768Hz
UCS_initClockSignal(UCS_ACLK, UCS_XT1CLK_SELECT, UCS_CLOCK_DIVIDER_1);
//DCOCLK作为MCLK时钟源 = 25MHz
UCS_initClockSignal(UCS_MCLK, UCS_DCOCLK_SELECT, UCS_CLOCK_DIVIDER_1);
//DCOCLK作为SMCLK时钟源 = 25MHz
UCS_initClockSignal(UCS_SMCLK, UCS_DCOCLK_SELECT, UCS_CLOCK_DIVIDER_1);
//设置外部时钟源的频率,使得在调用UCS_getMCLK, UCS_getSMCLK 或 UCS_getACLK时可得到正确值
UCS_setExternalClockSource(32768, 4000000);
}
void Timer_A_Init(void)
{
Timer_A_initUpModeParam htim = {0};
htim.clockSource = TIMER_A_CLOCKSOURCE_ACLK;//时钟源选为ACLK = 32768Hz
htim.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_64;//64分频
htim.timerPeriod = 512 - 1;
htim.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE;//禁止TAIE中断
htim.captureCompareInterruptEnable_CCR0_CCIE = TIMER_A_CCIE_CCR0_INTERRUPT_ENABLE;//使能CCIE中断
htim.timerClear = TIMER_A_DO_CLEAR;//清零计数器和定时器第二级分频器(定时器有两级分频器,第一级由参数二决定,本例中为64分频)
htim.startTimer = true;//调用初始化函数之后立刻启动定时器
Timer_A_initUpMode(TIMER_A0_BASE, &htim);
//配置定时器A为增计数模式
}
int main(void)
{
WDT_A_hold(WDT_A_BASE);
SystemClock_Init();
GPIO_setAsOutputPin(GPIO_PORT_P4, GPIO_PIN7);
Timer_A_Init();
//interrupts enabled
__bis_SR_register(GIE);
while(1)
{
}
}
#pragma vector=TIMER0_A0_VECTOR
__interrupt
void TIMER0_A0_ISR (void)
{
GPIO_toggleOutputOnPin(GPIO_PORT_P4, GPIO_PIN7);
}
我们一般会选择模式7,即计数值大于比较值,则输出低电平(复位),如果计数值小于比较值则输出高电平(置位)
对于msp430芯片的库函数来说,输出比较的初始化有两种方法,第一种是使用Timer_A_initCompareMode初始化为复位/置位比较模式;第二种是使用官方专用的一个pwm初始化函数Timer_A_outputPWM;第一种方法需要手动调用Timer_A_initUpMode初始化定时器为递增计数模式,而第二种方法直接在Timer_A_outputPWM内部实现这一个操作
虽然第一种方法繁琐一点,但是它相比于第二种方法也有优点,它可以灵活的改变比较值,进而改变占空比(占空比=比较值/总周期计数值);而第二种方法我找遍整个.h文件,也没有发现可以改变占空比的函数。
综合考量,在实际应用中,我们很可能需要改变占空比的,例如电机pwm调速,因此,这里使用第一种方法来实现输出比较
main.c
#include "driverlib.h"
#define MCLK_IN_HZ 25000000
#define delay_us(x) __delay_cycles((MCLK_IN_HZ/1000000*(x)))
#define delay_ms(x) __delay_cycles((MCLK_IN_HZ/1000*(x)))
void SystemClock_Init(void)
{
PMM_setVCore(PMM_CORE_LEVEL_3); //高主频工作需要较高的核心电压
//XT1引脚复用
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, GPIO_PIN4);
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5, GPIO_PIN5);
//起振XT1
UCS_turnOnLFXT1(UCS_XT1_DRIVE_3,UCS_XCAP_3);
//XT2引脚复用
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, GPIO_PIN2);
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5, GPIO_PIN3);
//起振XT2
UCS_turnOnXT2(UCS_XT2_DRIVE_4MHZ_8MHZ);
//XT2作为FLL参考时钟,先8分频,再50倍频 4MHz / 8 * 50 = 25MHz
UCS_initClockSignal(UCS_FLLREF, UCS_XT2CLK_SELECT, UCS_CLOCK_DIVIDER_8);
UCS_initFLLSettle(25000, 50);
//XT1作为ACLK时钟源 = 32768Hz
UCS_initClockSignal(UCS_ACLK, UCS_XT1CLK_SELECT, UCS_CLOCK_DIVIDER_1);
//DCOCLK作为MCLK时钟源 = 25MHz
UCS_initClockSignal(UCS_MCLK, UCS_DCOCLK_SELECT, UCS_CLOCK_DIVIDER_1);
//DCOCLK作为SMCLK时钟源 = 25MHz
UCS_initClockSignal(UCS_SMCLK, UCS_DCOCLK_SELECT, UCS_CLOCK_DIVIDER_1);
//设置外部时钟源的频率,使得在调用UCS_getMCLK, UCS_getSMCLK 或 UCS_getACLK时可得到正确值
UCS_setExternalClockSource(32768, 4000000);
}
//
void Timer_A_Compare_Init(void)
{
Timer_A_initUpModeParam htim = {0};
htim.clockSource = TIMER_A_CLOCKSOURCE_SMCLK;//时钟源选为SMCLK = 25 MHz
htim.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1;//不分频
htim.timerPeriod = 2500 - 1;//计数周期为2500
htim.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE;//禁止TAIE中断
htim.captureCompareInterruptEnable_CCR0_CCIE = TIMER_A_CCIE_CCR0_INTERRUPT_DISABLE;//禁止CCIE中断
htim.timerClear = TIMER_A_DO_CLEAR;//清零计数器和定时器第二级分频器
htim.startTimer = true;//调用初始化函数之后立刻启动定时器
Timer_A_initUpMode(TIMER_A0_BASE, &htim);
//配置定时器A为增计数模式
//P1.4复用输出
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1, GPIO_PIN4);
Timer_A_initCompareModeParam c0mpare_htim = {0};
c0mpare_htim.compareRegister = TIMER_A_CAPTURECOMPARE_REGISTER_3;//选择TA0的CCR3为比较寄存器(即引脚p1.4)
c0mpare_htim.compareInterruptEnable = TIMER_A_CAPTURECOMPARE_INTERRUPT_DISABLE;//禁止中断
c0mpare_htim.compareOutputMode = TIMER_A_OUTPUTMODE_RESET_SET;//选择模式为复位/置位模式
c0mpare_htim.compareValue = 625;//设置初始占空比:1250/2500=50%
Timer_A_initCompareMode(TIMER_A0_BASE, &c0mpare_htim);
}
//要设置占空比,需要改变比较寄存器CCR3的值,因此需要使用库函数Timer_A_setCompareValue
int main(void)
{
int i;
WDT_A_hold(WDT_A_BASE);
SystemClock_Init();
//interrupts enabled
__bis_SR_register(GIE);
Timer_A_Compare_Init();
while(1)
{
for(i = 0;i<2500;i+=25)
{
Timer_A_setCompareValue(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_3,i);
delay_ms(100);
}
for(i = 2500;i>0;i-=25)
{
Timer_A_setCompareValue(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_3,i);
delay_ms(100);
}
}
}
这个main.c文件实现的功能是:使msp430f5529上的p1.4引脚(即定时器A0比较通道3)输出pwm波,这个pwm波的占空比会持续改变,由小到大,再由大到小,变化周期为2s(由小到大耗时1s,由大到小耗时1s)
程序验证需要给p1.4外接一个LED灯,然后可以看到一个呼吸灯的效果!也可以用其他芯片如stm32的adc直接检测p1.4的电压
参考某位大佬的代码,做一些小改动。改成一个.h和.c的模块文件,利于工程整理
调用UART_Init初始化串口,msp430f5529板载上有两个串口,第一个串口直接使用usb线连接电脑即可使用,是串口1(实际上是p4.4,p4.5),还有一个串口2的引脚对应关系如下:
P3.3, P3.4 = USCI_A0 TXD/RXD
my_uart.h:
#ifndef __UART_H
#define __UART_H
#include "driverlib.h"
#include
#include
#include
bool UART_Init(uint16_t baseAddress, uint32_t Baudrate);
void UART_printf(uint16_t baseAddress, const char *format,...);
#endif
my_uart.c:
#include "my_uart.h"
bool UART_Init(uint16_t baseAddress, uint32_t Baudrate)
{
float UART_Temp = 0;
USCI_A_UART_initParam huart = {0};
if(baseAddress == USCI_A0_BASE) //P3.3, P3.4 = USCI_A0 TXD/RXD
{
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P3, GPIO_PIN3);
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P3, GPIO_PIN4);
}
else if(baseAddress == USCI_A1_BASE) //P4.4, P4.5 = USCI_A1 TXD/RXD
{
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P4, GPIO_PIN4);
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P4, GPIO_PIN5);
}
if(Baudrate <= 9600)
{
huart.selectClockSource = USCI_A_UART_CLOCKSOURCE_ACLK;
UART_Temp = (float)UCS_getACLK()/Baudrate;
}
else
{
huart.selectClockSource = USCI_A_UART_CLOCKSOURCE_SMCLK;
UART_Temp = (float)UCS_getSMCLK()/Baudrate;
}
if(UART_Temp < 16)
huart.overSampling = USCI_A_UART_LOW_FREQUENCY_BAUDRATE_GENERATION;
else
{
huart.overSampling = USCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION;
UART_Temp /= 16;
}
huart.clockPrescalar = (int)UART_Temp;
if(huart.overSampling == USCI_A_UART_LOW_FREQUENCY_BAUDRATE_GENERATION)
{
huart.secondModReg = (int)((UART_Temp - huart.clockPrescalar) * 8);
}
else
{
huart.firstModReg = (int)((UART_Temp - huart.clockPrescalar) * 16);
}
huart.parity = USCI_A_UART_NO_PARITY;
huart.msborLsbFirst = USCI_A_UART_LSB_FIRST;
huart.numberofStopBits = USCI_A_UART_ONE_STOP_BIT;
huart.uartMode = USCI_A_UART_MODE;
if (STATUS_FAIL == USCI_A_UART_init(baseAddress, &huart))
{
return STATUS_FAIL;
}
//Enable UART module for operation
USCI_A_UART_enable(baseAddress);
//Enable Receive Interrupt
USCI_A_UART_clearInterrupt(baseAddress, USCI_A_UART_RECEIVE_INTERRUPT);
USCI_A_UART_enableInterrupt(baseAddress, USCI_A_UART_RECEIVE_INTERRUPT);
return STATUS_SUCCESS;
}
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]);
}
//******************************************************************************
//
//This is the USCI_A0 interrupt vector service routine.
//
//******************************************************************************
#pragma vector=USCI_A0_VECTOR
__interrupt void USCI_A0_ISR (void)
{
uint8_t receivedData = 0;
switch (__even_in_range(UCA0IV,4))
{
//Vector 2 - RXIFG
case 2:
receivedData = USCI_A_UART_receiveData(USCI_A0_BASE);
USCI_A_UART_transmitData(USCI_A0_BASE,receivedData);
break;
default:
break;
}
}
//******************************************************************************
//
//This is the USCI_A1 interrupt vector service routine.
//
//******************************************************************************
#pragma vector=USCI_A1_VECTOR
__interrupt void USCI_A1_ISR (void)
{
uint8_t receivedData = 0;
switch (__even_in_range(UCA1IV,4))
{
//Vector 2 - RXIFG
case 2:
receivedData = USCI_A_UART_receiveData(USCI_A1_BASE);
USCI_A_UART_transmitData(USCI_A1_BASE,receivedData);
break;
default:
break;
}
}
串口发送使用示例:
int main(void)
{
WDT_A_hold(WDT_A_BASE);
SystemClock_Init();
//interrupts enabled
__bis_SR_register(GIE);
UART_Init(USCI_A1_BASE,115200);
while(1)
{
UART_printf(USCI_A1_BASE,"I love IU!%d\r\n",30);
delay_ms(500);
}
}
串口接收是通过中断函数来接收的,两个中断函数都在.c文件中,默认的接收逻辑是每接收到一个字符就通过相应串口发送出同样的字符