引脚手册获取
详情看MSP430F5529库函数定时器A——硬件PWM获取引脚手册部分。
实验目的
捕获波形高电平持续时间
代码
MSP430F5529可以进行信号捕获。先提供代码
#include "driverlib.h" #include <string.h> #include <stdarg.h> #include <stdio.h> #define CPU_F ((double)1000000) #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)) 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]); } //9600 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 = 6; param1.firstModReg = 13; 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); } uint32_t Sign_Counts = 0; void Timer_A2_Capture_Init() { Timer_A_initContinuousModeParam htim = {0}; htim.clockSource = TIMER_A_CLOCKSOURCE_SMCLK; //1048576Hz htim.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1; //一分频,1048576Hz htim.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_ENABLE; //使能TAIE中断 htim.timerClear = TIMER_A_DO_CLEAR; //把定时器的定时计数器,分频计数器的计数值清零 htim.startTimer = true; //初始化后立即启动定时器 Timer_A_initContinuousMode(TIMER_A2_BASE, &htim); //设置为连续计数模式 GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P2, GPIO_PIN5); //复用P2.5 Timer_A_initCaptureModeParam capture_htim = {0}; capture_htim.captureRegister = TIMER_A_CAPTURECOMPARE_REGISTER_2; //因为P2.5使用的是TA2.2,所以这里是REGISTER_2 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_A2_BASE,&capture_htim); } int main(void) { //关闭看门狗 WDT_A_hold(WDT_A_BASE); //打开输入捕获 Timer_A2_Capture_Init(); Usart1_Init(); //interrupts enabled __bis_SR_register(GIE); while(1) { delay_ms(1000); //UART_printf(USCI_A1_BASE,"高电平持续时间:%f ms",1000.*Sign_Counts/1048576); //注意,这里1000后面必须+'.'表示是浮点运算!!!否则结果为0!!! UART_printf(USCI_A1_BASE,"高电平持续时间:%f ms",1000.*Sign_Counts/UCS_getSMCLK());//UCS_getSMCLK是获取当前SMCLK时钟频率,当前SMCLK为1048576HZ } } #pragma vector=TIMER2_A1_VECTOR __interrupt void TIMER2_A1_ISR (void) { static uint16_t Overflow_Times = 0; static uint16_t Sign_Begin = 0, Sign_End = 0; switch(TA2IV) { case TA2IV_TACCR2: if(GPIO_getInputPinValue(GPIO_PORT_P2,GPIO_PIN5)) //获取P2.5引脚电平,如果为高电平,将当前寄存器值存入Sign_Begin { Sign_Begin = Timer_A_getCaptureCompareCount(TIMER_A2_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_2); } else //获取P2.5引脚电平,如果为低电平,将当前寄存器值存入Sign_End { Sign_End = Timer_A_getCaptureCompareCount(TIMER_A2_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_2); 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_A2_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_2); break; case TA2IV_TAIFG: if(GPIO_getInputPinValue(GPIO_PORT_P2,GPIO_PIN5)) //获取P2.5引脚电平。如果定时器都溢出中断了,现在还是高电平,那么表明高电平和低电平不在同一个定时周期内 { ++Overflow_Times; } else //获取P2.5引脚电平。如果定时器溢出中断了,现在不是高电平,那么表明高电平和低电平在同一个定时周期内 Overflow_Times = 0; Timer_A_clearTimerInterrupt(TIMER_A2_BASE); break; default: break; } }
代码解析
串口数据发送部分
注意,这部分我就提醒一下。我们是需要进行浮点数据打印的,所以我们看MSP430F5529库函数学习——串口的时候,需要按照浮点数据打印这一部分配置!
数据捕获部分
定时器部分可选参数
(1)clockSource:选择时钟源 TIMER_A_CLOCKSOURCE_EXTERNAL_TXCLK [Default] TIMER_A_CLOCKSOURCE_ACLK TIMER_A_CLOCKSOURCE_SMCLK TIMER_A_CLOCKSOURCE_INVERTED_EXTERNAL_TXCLK (2)clockSourceDivider:选择时钟分频次数 TIMER_A_CLOCKSOURCE_DIVIDER_1 [Default] TIMER_A_CLOCKSOURCE_DIVIDER_2 TIMER_A_CLOCKSOURCE_DIVIDER_3 TIMER_A_CLOCKSOURCE_DIVIDER_4 TIMER_A_CLOCKSOURCE_DIVIDER_5 TIMER_A_CLOCKSOURCE_DIVIDER_6 TIMER_A_CLOCKSOURCE_DIVIDER_7 TIMER_A_CLOCKSOURCE_DIVIDER_8 TIMER_A_CLOCKSOURCE_DIVIDER_10 TIMER_A_CLOCKSOURCE_DIVIDER_12 TIMER_A_CLOCKSOURCE_DIVIDER_14 TIMER_A_CLOCKSOURCE_DIVIDER_16 TIMER_A_CLOCKSOURCE_DIVIDER_20 TIMER_A_CLOCKSOURCE_DIVIDER_24 TIMER_A_CLOCKSOURCE_DIVIDER_28 TIMER_A_CLOCKSOURCE_DIVIDER_32 TIMER_A_CLOCKSOURCE_DIVIDER_40 TIMER_A_CLOCKSOURCE_DIVIDER_64 TIMER_A_CLOCKSOURCE_DIVIDER_48 TIMER_A_CLOCKSOURCE_DIVIDER_56 (3)timerInterruptEnable_TAIE:使能还是失能定时器中断 TIMER_A_TAIE_INTERRUPT_ENABLE //使能定时器中断 TIMER_A_TAIE_INTERRUPT_DISABLE //失能定时器中断 (4)timerClear:选择是否把定时器的定时计数器,分频计数器的计数值清零 TIMER_A_DO_CLEAR //清除 TIMER_A_SKIP_CLEAR //不清除 (5)startTimer:选择初始化之后是否立即启动定时器 true //初始化后立即启动定时器 false //初始化后不启动定时器
设置定时器部分
(1)MSP430F5529定时器时钟 TACLK 可以选择 ACLK,SMCLK 或者来自外部的 TAxCLK。SMCLK系统默认 1048576Hz,ACLK系统默认为32768Hz。这里我们选择SMCLK作为时钟源
(2)不进行分频,因为如果进行输入捕获,频率越高,精确度越大。
(3)因为数据捕获实验是采用的连续计数模式,所以只能使用TAIE中断。详情请看MSP430F5529库函数定时器A——定时中断的Timer_A_initContinuousMode()函数介绍。
(4)把定时器的定时计数器,分频计数器的计数值清零。也就是计数器从0开始计数
(5)初始化之后立即启动定时器,否则需要之后再去使用Timer_A_startCounter()函数设置为连续计数模式启动定时器
(6)我们开启定时器2
Timer_A_initContinuousModeParam htim = {0}; htim.clockSource = TIMER_A_CLOCKSOURCE_SMCLK; //1048576Hz htim.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1; //一分频,1048576Hz htim.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_ENABLE; //使能TAIE中断 htim.timerClear = TIMER_A_DO_CLEAR; //把定时器的定时计数器,分频计数器的计数值清零 htim.startTimer = true; //初始化后立即启动定时器 Timer_A_initContinuousMode(TIMER_A2_BASE, &htim); //设置为连续计数模式
捕获部分可选参数
captureRegister:选择所捕获引脚的CCRx,如果是CCR2就选择REGISTER_2 TIMER_A_CAPTURECOMPARE_REGISTER_0 TIMER_A_CAPTURECOMPARE_REGISTER_1 TIMER_A_CAPTURECOMPARE_REGISTER_2 TIMER_A_CAPTURECOMPARE_REGISTER_3 TIMER_A_CAPTURECOMPARE_REGISTER_4 TIMER_A_CAPTURECOMPARE_REGISTER_5 TIMER_A_CAPTURECOMPARE_REGISTER_6 captureMode:选择捕获模式 TIMER_A_CAPTUREMODE_NO_CAPTURE //不进行捕获 TIMER_A_CAPTUREMODE_RISING_EDGE //上升沿捕获 TIMER_A_CAPTUREMODE_FALLING_EDGE //下降沿捕获 TIMER_A_CAPTUREMODE_RISING_AND_FALLING_EDGE //双边沿捕获 captureInputSelect:选择捕获的CCIxA还是CCIxB TIMER_A_CAPTURE_INPUTSELECT_CCIxA //如果是CCI2A,就选择这个。好像基本都是选择这个 TIMER_A_CAPTURE_INPUTSELECT_CCIxB //如果是CCI2B,就选择这个。这个好像是跟时钟有关的 TIMER_A_CAPTURE_INPUTSELECT_GND TIMER_A_CAPTURE_INPUTSELECT_Vcc synchronizeCaptureSource:捕获源是否与计时器同步,一般设置为同步 TIMER_A_CAPTURE_ASYNCHRONOUS //不同步 TIMER_A_CAPTURE_SYNCHRONOUS //同步 captureInterruptEnable:是启用或禁用定时器capturecompare中断 TIMER_A_CAPTURECOMPARE_INTERRUPT_DISABLE //不进行定时器中断 TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE //开启定时器中断 captureOutputMode:指定输出模式 TIMER_A_OUTPUTMODE_OUTBITVALUE //因为是捕获模式,所以只能选择这个 TIMER_A_OUTPUTMODE_SET TIMER_A_OUTPUTMODE_TOGGLE_RESET TIMER_A_OUTPUTMODE_SET_RESET TIMER_A_OUTPUTMODE_TOGGLE TIMER_A_OUTPUTMODE_RESET TIMER_A_OUTPUTMODE_TOGGLE_SET TIMER_A_OUTPUTMODE_RESET_SET
设置捕获引脚部分
(1)因为我们上面设置的是定时器2,所以我们需要查看引脚图。选择具有捕获功能的,又是定时器2的引脚。最后选择了P2.5脚。首先我们需要将他复用为输入,因为是要进行波形捕获。
(2)我们看引脚手册可知,P2.5对应的是定时器A的定时器2的CCR2,所以选择TIMER_A_CAPTURECOMPARE_REGISTER_2
(3)因为我们需要捕获高电平持续时间,所以需要捕获上升沿和下降沿
(4)因为是CCI2A所以选择INPUTSELECT_CCIxA,我查了一下手册,发现CCIxB好像都是跟时钟有关的东西。
(5)这个captureOutputMode是用于设置输出的。这个是PWM输出的时候才需要用到的,所以此处设置为TIMER_A_OUTPUTMODE_OUTBITVALUE,定时器输出电平由OUT位控制。详情看MSP430F5529库函数定时器A——硬件PWM比较输出模式部分。
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P2, GPIO_PIN5); //复用P2.5 Timer_A_initCaptureModeParam capture_htim = {0}; capture_htim.captureRegister = TIMER_A_CAPTURECOMPARE_REGISTER_2; //因为P2.5使用的是TA2.2,所以这里是REGISTER_2 capture_htim.captureMode = TIMER_A_CAPTUREMODE_RISING_AND_FALLING_EDGE; //选择双边沿触发 capture_htim.captureInputSelect = TIMER_A_CAPTURE_INPUTSELECT_CCIxA; //因为是CCI2A所以选择INPUTSELECT_CCIxA capture_htim.synchronizeCaptureSource = TIMER_A_CAPTURE_SYNCHRONOUS; //捕获源与计时器时钟同步 capture_htim.captureInterruptEnable = TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE; //打开定时器捕获中断 capture_htim.captureOutputMode = TIMER_A_OUTPUTMODE_OUTBITVALUE; //定时器输出电平由OUT位控制 Timer_A_initCaptureMode(TIMER_A2_BASE,&capture_htim);
中断处理
因为我们采用的是定时器2的TALE中断,所以中断向量选择TIMER2_A1_VECTOR。
(1)首先我们需要知道,TAVE中断向量是具有多个中断源的。我们此刻是TA2.2,使用的是CCR2的中断源,所以需要一个TA2IV_TACCR2中断判断(如果上升沿或者下降沿就触发中断)。
(2)因为我们捕获的波形不可能是同一个定时周期里面,所以还需要一个TAVE中断,这样就不会因为高电平持续时间太长,导致无法计算。
#pragma vector=TIMER2_A1_VECTOR __interrupt void TIMER2_A1_ISR (void) { static uint16_t Overflow_Times = 0; static uint16_t Sign_Begin = 0, Sign_End = 0; switch(TA2IV) { case TA2IV_TACCR2: if(GPIO_getInputPinValue(GPIO_PORT_P2,GPIO_PIN5)) //获取P2.5引脚电平,如果为高电平,将当前寄存器值存入Sign_Begin { Sign_Begin = Timer_A_getCaptureCompareCount(TIMER_A2_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_2); } else //获取P2.5引脚电平,如果为低电平,将当前寄存器值存入Sign_End { Sign_End = Timer_A_getCaptureCompareCount(TIMER_A2_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_2); 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_A2_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_2); break; case TA2IV_TAIFG: if(GPIO_getInputPinValue(GPIO_PORT_P2,GPIO_PIN5)) //获取P2.5引脚电平。如果定时器都溢出中断了,现在还是高电平,那么表明高电平和低电平不在同一个定时周期内 { ++Overflow_Times; } else //获取P2.5引脚电平。如果定时器溢出中断了,现在不是高电平,那么表明高电平和低电平在同一个定时周期内 Overflow_Times = 0; Timer_A_clearTimerInterrupt(TIMER_A2_BASE); break; default: break; } }
如下为TAVE中断向量里面所含有的中断方式。
#pragma vector=TIMER0_A1_VECTOR __interrupt void TIMER0_A1_ISR (void) { switch(TA0IV) { case TA0IV_NONE: break; case TA0IV_TACCR1: break; case TA0IV_TACCR2: break; case TA0IV_TACCR3: break; case TA0IV_TACCR4: break; case TA0IV_5: break; case TA0IV_6: break; case TA0IV_TAIFG: GPIO_toggleOutputOnPin(GPIO_PORT_P4, GPIO_PIN1); break; default: break; } }
TA2IV_TACCR2解析
(1)首先是TA2IV_TACCR2中断,如果P2.5脚捕获到上升沿或者下降沿将会进入这里。我们先判断是上升沿还是下降沿,如果是上升沿,那么此时P2.5将会是高电平。我们将定时器的值存入Sign_Begin 中,程序中认为,高电平为信号起始。
(2)如果现在P2.5为低电平,进入else语句,表示捕获已经结束了。将当前捕获的数据储存到,开始进行判断高电平和低电平是否在同一个定时周期内。
(3)如果Overflow_Times为0,那么表示高电平和低电平在同一定时周期,只需要把记录到的低电平时间减去高电平时间即可
(4)如果Overflow_Times不为0,那么高低电平不在同一定时周期。需要先让记录的低电平时间加上Overflow_Times(定时了几个周期)*65536 (定时器A为16位的定时器,16bit最大为65535,计数65536次,因为是从0开始计数),然后再减去记录的高电平时间即可。
case TA2IV_TACCR2: if(GPIO_getInputPinValue(GPIO_PORT_P2,GPIO_PIN5)) //获取P2.5引脚电平,如果为高电平,将当前寄存器值存入Sign_Begin { Sign_Begin = Timer_A_getCaptureCompareCount(TIMER_A2_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_2); } else //获取P2.5引脚电平,如果为低电平,将当前寄存器值存入Sign_End { Sign_End = Timer_A_getCaptureCompareCount(TIMER_A2_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_2); 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_A2_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_2); break;
TA2IV_TAIFG解析
(1)因为捕获的波形高电平持续时间不一定会完整的被一共定时周期捕获,所以需要建立一个 Overflow_Times 来统计高电平到底跨越了几个定时周期。
(2)我们先判断P2.5引脚电平,如果P2.5为高电平,表示高电平还在持续,这个跨度超过了一共定时周期。
(3)如果现在P2.5为低电平,就说明高电平和低电平在同一个定时周期内。将Overflow_Times置0。
case TA2IV_TAIFG: if(GPIO_getInputPinValue(GPIO_PORT_P2,GPIO_PIN5)) //获取P2.5引脚电平。如果定时器都溢出中断了,现在还是高电平,那么表明高电平和低电平不在同一个定时周期内 { ++Overflow_Times; } else //获取P2.5引脚电平。如果定时器溢出中断了,现在不是高电平,那么表明高电平和低电平在同一个定时周期内 Overflow_Times = 0; Timer_A_clearTimerInterrupt(TIMER_A2_BASE); break;
主函数解析
(1)我们需要关闭看门狗,否则你不即使喂狗,会导致程序一致复位——>执行一点点程序——>执行一点点程序。最后你调试的时候,会有一种程序卡死的感觉。
(2)打开捕获和串口,使能总中断。
(3)我们每过1S给电脑发送一次高电平持续时间。我们需要知道HZ,1S所变化的次数,如果时钟为1048576HZ,那么定时器+1,就是1/1048576秒。所以这里需要/1048576。因为这里是以ms为单位,所以需要乘以1000。如果我们不知道当前时钟的频率,可以使用UCS_getSMCLK()函数,获取当前SMCLK的频率。为什么是获取SMCLK的频率,是因为我们设置的定时器时钟源是SMCLK。
注意:1000后面一定一定要加一个小数点'.',因为/1048576之后,如果一个波形周期内,高电平持续时间<1S,那么算出来的值一定会小于1。如果是整型运算,那么结果一定会只是0!!!加上小数点'.'后,变成浮点运算。
//关闭看门狗 WDT_A_hold(WDT_A_BASE); //打开输入捕获 Timer_A2_Capture_Init(); Usart1_Init(); //interrupts enabled __bis_SR_register(GIE); while(1) { delay_ms(1000); UART_printf(USCI_A1_BASE,"高电平持续时间:%f ms",1000.*Sign_Counts/1048576); //注意,这里1000后面必须+'.'表示是浮点运算!!!否则结果为0!!! }
实验现象
我用自己的手持式示波器产生一共100HZ的,占空比为50%的方波。给单片机测量,结果如下
如果没有信号发生器,可以自己利用定时器产生一共PWM。不会的,可以看这位大佬的MSP430F5529 DriverLib 库函数学习笔记(五)定时器A,只需要看输入捕获模式测量脉宽长度部分即可。看我写的MSP430F5529库函数定时器A——硬件PWM,也可以。