首先感谢我姐送来的耳机(血脉压制,被逼的),再顺便给我姐捞个男朋友(诚心)。
一.实验简介
通过定时器的capture模式中的边沿计时模式,实现输入捕获PWM的频率和占空比。
二.板载定时器介绍(capture-边沿计时)
定时器模块可以捕捉上升沿,下降沿,在边沿到来时记录计数值,用来测定脉冲间的时间间隔。加计时的计时范围是从0到用户的预设值,减计时的范围为从预设值减到0,预设值可以通过TimerLoadSet函数进行设置。但是在此模式中不需要设置,如果在不设置preload的情况下也没有启用预分频器,那么预设值将默认为是2^16=0xffff,如果启用了预分频器,那么预设值将变为2^24。
对于加计时来说,加到preload后将会重置为0重复之前的工作,减计时也是,减到0后会重置到preload然后重复之前工作。
每当边沿到来,定时器模块会产生中断并记录当前计时值。可以通过计时值计算频率,占空比。
注意输入捕获只能在定时器拆分的情况下使用。
三.引脚图
?使用PF2作为PWM输出引脚,使用PB6作为输入捕获的引脚。
四.所需函数
有关串口,GPIO,PWM还有timer中已经用过的函数不在提起,有问题的可以去看我之前的博客
1.GPIOPinTypeTimer(uint32_t ui32Port, uint8_t ui8Pins)
参数:ui32Port为GPIO基地址,ui8Pins为外设引脚
作用:分配定时器信号
2.TimerControlEvent(uint32_t ui32Base, uint32_t ui32Timer,uint32_t ui32Event)
参数:ui32Base为定时器基地址,ui32Timer为定时器中的模块,ui32Event为捕捉边沿
作用:设置输入捕获的捕捉边沿
3.TimerValueGet(uint32_t ui32Base, uint32_t ui32Timer)
参数:ui32Base为定时器基地址,ui32Timer为定时器中的模块
作用:获取特定引脚的计时值。
4.TimerIntEnable(uint32_t ui32Base, uint32_t ui32IntFlags)
参数:ui32Base为定时器基地址,ui32IntFlags为中断模式
//! - \b TIMER_TIMB_DMA - Timer B uDMA complete
//! - \b TIMER_TIMA_DMA - Timer A uDMA complete
//! - \b TIMER_CAPB_EVENT - Capture B event interrupt
//! - \b TIMER_CAPB_MATCH - Capture B match interrupt
//! - \b TIMER_TIMB_TIMEOUT - Timer B timeout interrupt
//! - \b TIMER_RTC_MATCH - RTC interrupt mask
//! - \b TIMER_CAPA_EVENT - Capture A event interrupt
//! - \b TIMER_CAPA_MATCH - Capture A match interrupt
//! - \b TIMER_TIMA_TIMEOUT - Timer A timeout interrupt
?如果是普通定时器模式就设置为TIMOUT,如果是capture的边沿计数就是match,如果是边沿计时就是EVENT
作用:使能中断模式
五.代码及讲解
usart.c
#include "usart.h"
#include "uart.h"
#include "gpio.h"
#include "sysctl.h"
#include "pin_map.h"
#include "hw_memmap.h"
#include "uartstdio.h"
//配置串口,有问题的去看我之前博客
void USART_Config(void)
{
SysCtlPeripheralEnable( SYSCTL_PERIPH_GPIOA);
SysCtlPeripheralEnable( SYSCTL_PERIPH_UART0);
GPIOPinConfigure( GPIO_PA0_U0RX);
GPIOPinConfigure( GPIO_PA1_U0TX);
GPIOPinTypeUART( GPIO_PORTA_BASE, GPIO_PIN_0);
GPIOPinTypeUART( GPIO_PORTA_BASE, GPIO_PIN_1);
UARTClockSourceSet( UART0_BASE, UART_CLOCK_PIOSC);
UARTStdioConfig( 0, 115200,
16000000);
}
usart.h
#ifndef __USART_H
#define __USART_H
void USART_Config(void);
#endif
pwm_out.c
#include "gpio.h"
#include "pin_map.h"
#include "pwm_out.h"
#include "sysctl.h"
#include "pwm.h"
#include "hw_memmap.h"
//配置PWM输出,有问题的看我之前博客
void PWM_OUT_Config(void)
{
SysCtlPWMClockSet( SYSCTL_PWMDIV_4);
SysCtlPeripheralEnable( SYSCTL_PERIPH_GPIOF);
SysCtlPeripheralEnable( SYSCTL_PERIPH_PWM1);
GPIOPinTypePWM( GPIO_PORTF_BASE, GPIO_PIN_2);
GPIOPinConfigure( GPIO_PF2_M1PWM6);
PWMGenConfigure( PWM1_BASE, PWM_GEN_3,
PWM_GEN_MODE_DOWN|PWM_GEN_MODE_NO_SYNC);
PWMGenPeriodSet( PWM1_BASE, PWM_GEN_3,
2000);
PWMPulseWidthSet( PWM1_BASE, PWM_OUT_6,
PWMGenPeriodGet(PWM1_BASE, PWM_GEN_3)*0.5 - 1);
PWMOutputState( PWM1_BASE, PWM_OUT_6_BIT,
true);
PWMGenEnable( PWM1_BASE, PWM_GEN_3);
}
pwm_out.h
#ifndef __PWM_OUT_H
#define __PWM_OUT_H
void PWM_OUT_Config(void);
#endif
pwm_in.c
#include "pwm_in.h"
#include "gpio.h"
#include "sysctl.h"
#include "hw_memmap.h"
#include "pin_map.h"
#include "hw_ints.h"
#include "uartstdio.h"
#include "timer.h"
_Bool flag=0;
uint32_t zhouqi=0;
uint32_t freq=0;
_Bool uart_flag=0;
_Bool led_flag=0;
uint32_t capture_1=0,capture_2=0,capture_3=0;
uint32_t timer_flag=0;
uint32_t duty=0;
uint32_t up_count=0,down_count=0;
void PWM_IN_IRQHandler(void);
void TIMER_WID_IRQHandler(void);
//配置输入捕获引脚
void PWM_IN_Config(void)
{
//使能定时器与GPIO
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
//使能引脚复用
GPIOPinConfigure(GPIO_PB6_T0CCP0);
//分配引脚信号
GPIOPinTypeTimer(GPIO_PORTB_BASE, GPIO_PIN_6);
//设置引脚方向,注意虽然这是输入捕获,但是这是由外界的PWM信号控制,属于硬件控制,要设置为GPIO_DIR_MODE_HW
GPIODirModeSet( GPIO_PORTB_BASE, GPIO_PIN_6,
GPIO_DIR_MODE_HW);
//设置为推挽上拉输入
GPIOPadConfigSet( GPIO_PORTB_BASE, GPIO_PIN_6,
GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
//因为是输入捕获,只有在拆分模式下可以用输入捕获,所以将TIMER_CFG_SPLIT_PAIR与A周期计数进行或运算
TimerConfigure( TIMER0_BASE, TIMER_CFG_SPLIT_PAIR|TIMER_CFG_A_CAP_TIME_UP);
//设置为上升沿触发
TimerControlEvent( TIMER0_BASE, TIMER_A,
TIMER_EVENT_POS_EDGE);
//注册中断函数
TimerIntRegister( TIMER0_BASE, TIMER_A,
PWM_IN_IRQHandler);
//设置中断优先级
IntPrioritySet( INT_TIMER0A,
0);
//使能定时器中断的计时中断
TimerIntEnable( TIMER0_BASE, TIMER_CAPA_EVENT);
//使能中断
IntEnable( INT_TIMER0A);
IntMasterEnable();
TimerEnable( TIMER0_BASE, TIMER_A);
/*设置装载值,在边沿计时模式下可以省略,会自己填入默认值。
如果设置了预分频值,那么默认装载值就是2^24,如果没有预分频值,那么默认装载值就是2^16。
相当于STM32中使用了oc——toggle模式,默认预装载值填写65535*/
//TimerLoadSet(TIMER0_BASE, TIMER_A, Capture_LoadSet);
}
//输入捕获中断
void PWM_IN_IRQHandler(void)
{
//读取中断状态
uint32_t status=TimerIntStatus( TIMER0_BASE, true);
//清除中断标志位
TimerIntClear( TIMER0_BASE, status);
//第一次进中断是由于检测到了上升沿,然后将计时值读取,并将边沿检测变为下降沿
if(timer_flag==0)
{
TimerControlEvent( TIMER0_BASE, TIMER_A,
TIMER_EVENT_NEG_EDGE);
capture_1=TimerValueGet( TIMER0_BASE, TIMER_A);
timer_flag=1;
}
//第二次进中断是因为检测到了下降沿,然后将计时值读取,这时就已经获得了高电平数,
//可以计算出占空比,并将边沿检测变为上升沿
else if(timer_flag==1)
{
TimerControlEvent( TIMER0_BASE, TIMER_A,
TIMER_EVENT_POS_EDGE);
capture_2=TimerValueGet( TIMER0_BASE, TIMER_A);
timer_flag=2;
}
//第三次进中断时因为检测到了上升沿,至此,已经检测到了两个上升沿,也就可以得到周期值
else if(timer_flag==2)
{
timer_flag=0;
capture_3=TimerValueGet( TIMER0_BASE, TIMER_A);
/* ____ ___ capture_1相当于检测到第一个上升沿记的数,
| | | capture_2相当于检测到第一个下降沿记的数
_| |___| 所以capture_2与capture_1之间即为高电平数
capture_3相当于检测到第二个上升沿记的数,所以capture_3与capture_1之间为周期数*/
/* /| /| 现在要求占空比和频率,因为设置的是定时器A周期性加计数,
/ | / | 定时器A记的数先到最大然后再从0开始计数,现在通过求高电平和低电平时间来计算
/ | / | 如果capture_1与capture_2都在第一个计数周期的上升阶段,那1与2的差就是高电平
/ |/ | 如果1与2分别落在两个周期的上升阶段,那高电平就要通过0xffff-capture_1+capture_2获得。
如果capture_2与capture_3都在第一个计数周期的上升阶段,那2与3的差就是低电平,
如果分别落在两个周期上升阶段,低电平就要通过0xffff-capture_2+capture_3来获得*/
if(capture_2>capture_1)
{
up_count=capture_2-capture_1;
}
else
{
up_count=0xffff-capture_1+capture_2;
}
if(capture_3>capture_2)
{
down_count=capture_3-capture_2;
}
else
{
down_count=0xffff-capture_2+capture_3;
}
//频率用主频除周期即可得到
freq=SysCtlClockGet()/(up_count+down_count);
//占空比为高电平占周期的比值即可得到
duty=up_count*100/(up_count+down_count);
}
}
void Timer_Wid_Config(void)
{
//使能32/64bit定时器
SysCtlPeripheralEnable(SYSCTL_PERIPH_WTIMER0);
//使能复用
TimerConfigure(WTIMER0_BASE,TIMER_CFG_PERIODIC_UP);
//设置预装载值,使1s进一次中断
TimerLoadSet64( WTIMER0_BASE, SysCtlClockGet()/1000-1);
//使能定时器A超时中断
TimerIntEnable( WTIMER0_BASE, TIMER_TIMA_TIMEOUT);
//注册中断函数
TimerIntRegister( WTIMER0_BASE, TIMER_A,
TIMER_WID_IRQHandler);
//设置优先级
IntPrioritySet( INT_WTIMER0A, 1);
//设置中断
IntEnable( INT_WTIMER0A);
IntMasterEnable();
TimerEnable( WTIMER0_BASE, TIMER_A);
}
void TIMER_WID_IRQHandler(void)
{
static uint32_t time_count=0;
uint32_t status=TimerIntStatus( WTIMER0_BASE, true);
TimerIntClear( WTIMER0_BASE, status);
time_count++;
//1s打印一次,但是不知道为什么用UARTprintf打印一次就不打印了,我还在改进,但是输入捕获那块是没问题的
if(time_count==1000)
{
time_count=0;
UARTprintf("duty:%d", duty);
UARTprintf("freq:%d", freq);
}
}
pwm_in.h
#ifndef __PWM_IN_H
#define __PWM_IN_H
#include <stdint.h>
extern _Bool uart_flag;
extern uint32_t zhouqi;
extern uint32_t freq;
extern _Bool led_flag;
void PWM_IN_Config(void);
void PWM_IN_IRQHandler(void);
void Timer_Wid_Config(void);
void TIMER_WID_IRQHandler(void);
#endif
main.c
#include "tm4c123gh6pm.h"
#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_sysctl.h"
#include "inc/hw_gpio.h"
#include "inc/hw_memmap.h"
#include "driverlib/gpio.h"
#include "driverlib/sysctl.h"
#include "pwm_in.h"
#include "pwm_out.h"
#include "usart.h"
#include "uartstdio.h"
void main(void)
{
SysCtlClockSet( SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);
PWM_OUT_Config();
PWM_IN_Config();
USART_Config();
Timer_Wid_Config();
while(1)
{
}
}
|