This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CCS/EK-TM4C123GXL: Enabling Multiple Timers Is Causing Inaccuracy in Event Capture Timing

Part Number: EK-TM4C123GXL

Tool/software: Code Composer Studio

Hello,

I have been working on a program that needs to read the duty cycle of a PWM input signal and generate an appropriate proportional output signal. The program uses Timer 0 (split into two half-width timers) to capture the rising and falling edges from the PWM signal, and Timer 1 to periodically update the proportional signal. While debugging, I discovered that while Timer 1 is not enabled, the program reads the PWM signal perfectly. When both Timers 0 and 1 are enabled, the capture times for the rising/falling edges aren't lined up as they should for the given PWM signal.

I've pasted the code below. Is there anything I can change that would fix this problem?

#include <stdint.h>
#include <stdbool.h>
#include "inc/tm4c123gh6pm.h"
#include "inc/hw_gpio.h"
#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "driverlib/pin_map.h"
#include "driverlib/fpu.h"
#include "driverlib/rom.h"
#include "driverlib/gpio.h"
#include "driverlib/qei.h"
#include "driverlib/sysctl.h"
#include "driverlib/uart.h"
#include "utils/uartstdio.h"
#include "driverlib/timer.h"
#include "driverlib/interrupt.h"
#include "gaitdata/gaitdata.h"
#include "pwmwrite/pwmwrite.h"

volatile uint32_t currentpos = 0;
volatile uint32_t goalpos = 0; //as indicated by index
volatile int32_t error = 0;
volatile uint32_t index = 0; //keeps track of point in gait data
volatile int32_t PIDsignal = 0; //signal that alters goalpwmhigh
volatile uint32_t proportional = 50; //P from PID
volatile uint32_t goalpwmhigh = 2048; //duty value, out of 4096
volatile uint32_t goalduty = 50; //duty in %
volatile float rawgoalpos = 0;

//THIS BLOCK FOR PWMREAD
static volatile uint32_t periodStartTime = 0;
static volatile uint32_t periodStartTimePrevious = 0;
static volatile uint32_t pulseEndTime = 0;
static volatile uint32_t pulseWidth = 0;
static volatile uint32_t periodWidth = 1;
static volatile float dutyCycle = 0;
static volatile uint32_t encoderValue = 0;

void timer1ISR(void)
{
	TimerIntClear(TIMER1_BASE, TIMER_TIMA_TIMEOUT);
	rawgoalpos = getKneeData(index);
	goalpos = (float)250.0 + (float)2.88 * ((float)rawgoalpos - (float)28.5);
	currentpos = encoderValue;
	error = currentpos - goalpos;
	PIDsignal = proportional * error;
	goalpwmhigh = 2048 + PIDsignal;
	if (goalpwmhigh > 3696)
	{
		goalpwmhigh = 3696;
	}
	else if (goalpwmhigh < 410)
	{
		goalpwmhigh = 410;
	}
	goalduty = (float)100 * ((float)goalpwmhigh	/ (float)4096);

	setPulseWidth(50, goalduty);

	index++;
	if(index == 1000)
	{
		index = 0;
	}
}

void timerARisingISR(void)
{
	//
	// CALCULATE PERIOD WIDTH
	//

	// clear interrupt source
	TimerIntClear(TIMER0_BASE, TIMER_CAPA_EVENT);

	// retrieve the clock count at event trigger time
	periodStartTime = ((TimerValueGet(TIMER0_BASE, TIMER_A) << 16) >> 16);

	// if timer overflow occurred, calculate period width accordingly assuming 16 bit timer
	if (periodStartTime < periodStartTimePrevious) {
		periodWidth = periodStartTime + (65536 - periodStartTimePrevious);
	}
	// if no timer overflow since last event
	else {
		periodWidth = periodStartTime - periodStartTimePrevious;
	}

	// calculate duty cycle
	dutyCycle = (((float)pulseWidth/(float)periodWidth));

	encoderValue = (dutyCycle*1024);


	UARTprintf("rising\n");
	UARTprintf("periodStartTime: %u\n", periodStartTime);
	UARTprintf("periodStartTimePrevious: %u\n", periodStartTimePrevious);

	// mark current period start time as being old
	periodStartTimePrevious = periodStartTime;
}

void timerBFallingISR(void)
{
	// clear interrupt source
	TimerIntClear(TIMER0_BASE, TIMER_CAPB_EVENT);

	// retrieve the clock count at event trigger time
	pulseEndTime = ((TimerValueGet(TIMER0_BASE, TIMER_B) << 16) >> 16);

	// if 16 bit timer overflowed, calculate pulse width accordingly
	if (pulseEndTime < periodStartTime) {
		pulseWidth = pulseEndTime + (65536 - periodStartTime);
	}
	// if timer did not overflow
	else {
		pulseWidth = pulseEndTime - periodStartTime;
	}

	UARTprintf("falling\n");
	UARTprintf("pulseEndTime: %u\n", pulseEndTime);
}

int main(void)
{
	//set clock to 40 MHz
	SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);

	//enable UART0
	SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);

	//enable GPIOF
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

	// TIMER0 peripheral, and GPIO ports B and F
	SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);

	// Wait for the TIMER0 module to be ready
	while(!SysCtlPeripheralReady(SYSCTL_PERIPH_TIMER0))
	{
	}

	// configure PB6 for timer A capture
	GPIOPinTypeTimer(GPIO_PORTB_BASE, GPIO_PIN_6);
	GPIOPinConfigure(GPIO_PB6_T0CCP0);

	// configure PB7 for timer B capture
	GPIOPinTypeTimer(GPIO_PORTB_BASE, GPIO_PIN_7);
	GPIOPinConfigure(GPIO_PB7_T0CCP1);

	// Configure TimerA as a half-width one-shot timer, and TimerB as a
	// half-width edge capture counter.
	TimerConfigure(TIMER0_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP | TIMER_CFG_B_CAP_TIME_UP));

	// TIMER_CLOCK_SYSTEM: use 40MHz system clock
	// TIMER_CLOCK_PIOSC: use 16MHz precision internal oscillator for timing signals
	// It appears PIOSC might not be an option for the TM4C123GH6PM
	TimerClockSourceSet(TIMER0_BASE, TIMER_CLOCK_SYSTEM);

	// set a prescaler value for timerA and timerB to ensure that overflow occurs more slowly than PWM periods
	// timer frequency currently set to: 10MHz
	// for some reason this isn't working right now and timer is still running at 40MHz
	//TimerPrescaleSet(TIMER0_BASE, TIMER_BOTH, 4);

	// Configure TimerA to mark time of rising edge, configure TimerB to mark time of falling edge
	TimerControlEvent(TIMER0_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
	TimerControlEvent(TIMER0_BASE, TIMER_B, TIMER_EVENT_NEG_EDGE);

	// clear both interrupts
	TimerIntClear(TIMER0_BASE, TIMER_CAPA_EVENT | TIMER_CAPB_EVENT);

	// register timer interrupt service routines
	//TimerIntRegister(TIMER0_BASE, TIMER_A, *timerARisingISR);
	//TimerIntRegister(TIMER0_BASE, TIMER_B, *timerBFallingISR);

	// enable the interrupts for the two timers, A detects rising edge, B detects falling edge
	TimerIntEnable(TIMER0_BASE, TIMER_CAPA_EVENT | TIMER_CAPB_EVENT);

	// enable interrupts for timerA and timerB
	IntEnable(INT_TIMER0A);
	IntEnable(INT_TIMER0B);

	// enable timer module 1
	SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER1);

	// wait for timer module 1 to be ready
	while(!SysCtlPeripheralReady(SYSCTL_PERIPH_TIMER1))
	{
	}

	// configure one full width timer, counting down
	TimerConfigure(TIMER1_BASE, TIMER_CFG_PERIODIC);

	// set timer 1 to run off of system clock
	TimerClockSourceSet(TIMER1_BASE, TIMER_CLOCK_SYSTEM);

	// set timer to reset at 10Hz
	TimerLoadSet(TIMER1_BASE, TIMER_A, SysCtlClockGet() / 100);

	// register the timer interrupt service routine
	TimerIntRegister(TIMER1_BASE, TIMER_A, *timer1ISR);

	// clear rollover interrupt and then enable it
	TimerIntClear(TIMER1_BASE, TIMER_TIMA_TIMEOUT);
	TimerIntEnable(TIMER1_BASE, TIMER_TIMA_TIMEOUT);

	IntEnable(INT_TIMER1A);

	//configure GPIO Pins for UART
	GPIOPinConfigure(GPIO_PA0_U0RX);
	GPIOPinConfigure(GPIO_PA1_U0TX);
	GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);

	// Use the internal 16MHz oscillator as the UART clock source.
	UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC);

	// Initialize the UART for console I/O.
	UARTStdioConfig(0, 921600, 16000000);


	GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_3);
	GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, GPIO_PIN_3);

	IntMasterEnable();

	//synchronize timer A and timer B to start on the same clock cycle
	TimerSynchronize(TIMER0_BASE, TIMER_0A_SYNC | TIMER_0B_SYNC);

	//start the timers
	TimerEnable(TIMER0_BASE, TIMER_BOTH);
	TimerEnable(TIMER1_BASE, TIMER_A);

	while(1)
	{
		//nothing!
	}
}

  • Hello Thomas

    I have assigned the post to the concerned SME to help you out.
  • Hi,

    First step: remove all UARTPrintf statements from your interrupt routines. Use real debugging, breakpoints rather than UARTPrintf. Toggle a GPIO pin instead.

  • Hi Thomas,
    Can you please tell what is the expected input frequency? With timer1 disabled, what duty cycle did you capture using timer0? How much is off compared to when timer1 is enabled?
  • Hello.

    Removing the UART print statements fixed the problem, thank you. The signal we were reading had a frequency of roughly 1 kHz. We measured the length of the ISRs and found that the UART statements were causing them to overrun our PWM period.
  • Hi Thomas,
    Glad that you found the root cause. It is certainly a good tip to the forum community. You are trying to print via UART the below three strings inside the ISR at 921600. The three strings are roughly 50 characters or 400 bits. Each bit time is 1/921600 second. For 400 bits it can take more time than 1KHz.

    UARTprintf("rising\n");
    UARTprintf("periodStartTime: %u\n", periodStartTime);
    UARTprintf("periodStartTimePrevious: %u\n", periodStartTimePrevious);