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.

EK-TM4C123GXL: PWM using PF0 - can't get it working

Part Number: EK-TM4C123GXL
Other Parts Discussed in Thread: LM4120, TM4C123GH6PM

I'm trying to get PWM working on an EK-TM4C123GXL Launchapd using port PF0. I'm aware that this is a locked pin for NMI purposes and have put the required code in to unlock the pin. However, I get stuck waiting for SysCtlPeripheralReady(SYSCTL_PERIPH_PWM1) to go true. I get stuck in the highlighted loop. I've tried everything, even copy and pasting it to another project and recompiling. I just don't know where the code is wrong. I've based this code on a TM4C1294 code using PF1 which works perfectly. I've changed processors and ports, but it should work!

For reference, the TM4C123G's PF0 PWM details are as follows: 

Here's the complete code:

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_gpio.h"
#include "inc/hw_types.h"
#include "driverlib/pwm.h"
#include "driverlib/interrupt.h"
#include "driverlib/gpio.h"
#include "driverlib/sysctl.h"
#include "driverlib/timer.h"
#include "utils/ustdlib.h"
#include "driverlib/pin_map.h"
#include "driverlib/systick.h"

int main(void)
{
// Set the system clock to run at 80Mhz off PLL with external crystal as reference.
SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN);

// Enable all the GPIO peripherals.
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF))
{
}
// Unlock PF0, which is a locked pin (NMI)
HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY;
HWREG(GPIO_PORTF_BASE + GPIO_O_CR) |= GPIO_PIN_0;
HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = 0; // re-lock it to prevent further changes
// Configure PWM clock
SysCtlPWMClockSet(SYSCTL_PWMDIV_1); // configure the PWM clock
SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM1); // enable PWM Module 1
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_PWM1))
{
}
GPIOPinTypePWM(GPIO_PORTF_BASE, GPIO_PIN_0);
GPIOPinConfigure(GPIO_PF0_M1PWM4);
PWMGenConfigure(PWM1_BASE, PWM_GEN_2, PWM_GEN_MODE_DOWN | PWM_GEN_MODE_NO_SYNC);
PWMGenPeriodSet(PWM1_BASE, PWM_GEN_2, SysCtlClockGet() / 10); // set default period to give 10 Hz
PWMPulseWidthSet(PWM1_BASE, PWM_OUT_4, SysCtlClockGet() / 20); // set default pulse width to give 50% duty cycle
PWMOutputState(PWM1_BASE, PWM_OUT_4_BIT, false); // enabled when required
PWMGenEnable(PWM1_BASE, PWM_GEN_2); // Start the timers in Generator 2

while(1)
{
}
}

  • Hi,

     I don't have any problem running beyond the line. I can see PWM generated on PF0 with the below code. Can yo try another board? Can you also try the below code?

    #include <stdint.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdbool.h>
    #include "inc/hw_ints.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_gpio.h"
    #include "inc/hw_types.h"
    #include "driverlib/pwm.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/gpio.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/timer.h"
    #include "utils/ustdlib.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/systick.h"
    
    int main(void)
    {
        // Set the system clock to run at 80Mhz off PLL with external crystal as reference.
        SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN);
    
        // Enable all the GPIO peripherals.
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
        while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF))
        {
        }
        // Unlock PF0, which is a locked pin (NMI)
    //    HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY;
    //    HWREG(GPIO_PORTF_BASE + GPIO_O_CR) |= GPIO_PIN_0;
    //    HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = 0; // re-lock it to prevent further changes
    
        GPIOUnlockPin(GPIO_PORTF_BASE, GPIO_PIN_0);
        // Configure PWM clock
        SysCtlPWMClockSet(SYSCTL_PWMDIV_1); // configure the PWM clock
        SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM1); // enable PWM Module 1
        while (!SysCtlPeripheralReady(SYSCTL_PERIPH_PWM1))
        {
        }
        GPIOPinTypePWM(GPIO_PORTF_BASE, GPIO_PIN_0);
        GPIOPinConfigure(GPIO_PF0_M1PWM4);
        PWMGenConfigure(PWM1_BASE, PWM_GEN_2, PWM_GEN_MODE_DOWN | PWM_GEN_MODE_NO_SYNC);
        PWMGenPeriodSet(PWM1_BASE, PWM_GEN_2, SysCtlClockGet() / 250); // set default period to give 10 Hz
        PWMPulseWidthSet(PWM1_BASE, PWM_OUT_4, PWMGenPeriodGet(PWM1_BASE, PWM_GEN_2) / 4); // set default pulse width to give 50% duty cycle
        PWMOutputState(PWM1_BASE, PWM_OUT_4_BIT, true); // enabled when required
        PWMGenEnable(PWM1_BASE, PWM_GEN_2); // Start the timers in Generator 2
    
        while(1)
        {
        }
    }

  • Thanks Charles,

    This is interesting. I tried another LaunchPad board and still had the problem. Both these boards were Stellaris LaunchPad boards purchased when they first came out. Literally the first day. I then tried a fresh purchase Tiva C Series LaunchPad board and it works fine. Both board types are at firmware revision 12630.

    The good news is I can progress my development effort. The bad news is that any older LaunchPad will lock up. We have a mixture and a user would not know what is going on.

    On a related matter, a few comments about the GPIOUnlockPin. First, when I try to use it, I get "unresolved symbol GPIOUnlockPin", which is strange since I can Ctrl-click the function and it takes me to the the .h file in gpio.h. However, looking at the gpio.c code for GPIOUnlockPin, I don't like it since it uses the first two HWREG commands I used, but does not re-lock it once it's done. I think I'll stick with my three lines, at least for now.

    In summary, the problem only appears on the Stellaris LaunchPad. Is there a workaround that makes both the old LaunchPad and new LaunchPad operate correctly. (I'm beginning to suspect the HWREG based unlocking of PF0 needs to be different for the older LaunchPads.)

  • Another piece of information. The chip on the Stellaris LaunchPad is LX4F120H5QRF1GA3.

  • Hi,

    Both these boards were Stellaris LaunchPad boards purchased when they first came out.

      In your original post, you never mentioned about Stellaris LaunchPad.

    I then tried a fresh purchase Tiva C Series LaunchPad board and it works fine. Both board types are at firmware revision 12630.

    The good news is I can progress my development effort. The bad news is that any older LaunchPad will lock up.

    I strongly suggest that you continue your software development with the Tiva LaunchPad board. I really don't know what is wrong with the Stellaris board. That board has the LM4120 MCU in it. LM4F MCU did not exist for long and got replaced by Tiva TM4C123 maybe a decade ago. I have not worked on LM4F120. The latest TM4C123 silicon revision is rev7. I think LM4F120 was an early engineering sample.  I'm not surprise that some errata/bugs got fixed along the way from LM4F120 to TM4C123. You can use LM flash programmer to unlock the device and see if you can recover the board but I still strongly suggest you move on to the Tiva LaunchPad instead of investing time on the old board. If you think it is worth the effort, you could do a AB swap by swapping a known good TM4C123 chip to the Stellaris board. If it resolves the issue, then you have an extra board to work with. The Tiva LaunchPad is only $16.99 which is not expensive to begin with. 

  • At the time of the original post, I didn't think it relevant that it was an original version LaunchPad. Everything else on the board works well. The problem is that we have a mixture of old and new LaunchPads and I can't ensure that there are none of the early versions running around.

    Given that it appears there is a fault in the original version LaunchPad it would be good to capture it in an errata.

    I will in due course I'll try to see if LM Flash Programmer can help me.

  • The chip on the Stellaris LaunchPad is LX4F120H5QRF1GA3.

    Looking at an archived copy of the LM4F120H5QR datasheet the  Register 31: Pulse Width Modulator Peripheral Present (PPPWM) shows a reset value of zero which means the PWM0 and PWM1 modules are *not* present in the LM4F120H5QR. That would explain why SysCtlPeripheralReady(SYSCTL_PERIPH_PWM1) gets stuck on the obsolete Stellaris LaunchPad.

    Whereas the TM4C123GH6PM datasheet shows the PWM0 and PWM1 are present, so if you get a EK-TM4C123GXL it should work.

  • Hi Chester, we now have the answer. The LM4F120H5QR does not have the PWM modules. Great to know. I can still get the PWM functionality by using the Capture Compare PWM (CCP) functionality. Fortunately PF0 has the T0CCP0 function available.

    In the interests of moving forward, I'll complete the project with the EK-TM4C123GXL. I've just purchased two more  EK-TM4C123GXL to get enough for project developement and tester use.|

    Thanks Chester for identifying the root cause of the problem.

  • Ultimately, I decided to use the CCP functionality to get the PWM signals. This method works on both the Stellaris LaunchPad (LM4F120H5QR) and Tiva LaunchPad (TM4C123GH6PM). The code is as follows:

    uint32_t PWMPeriod = 20000;   // PWMPeriod of 20000 ticks, gives PWM rate of 4 kHz Hz (for 80 MHz CPU clock)
    uint32_t dutyCycle = 50;      // Initial duty cycle, in percentage, is 50%

    // Unlock PF0, which is a locked pin (NMI)
    HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY;  // Unlock the port by using the device LOCK key
    HWREG(GPIO_PORTF_BASE + GPIO_O_CR) |= GPIO_PIN_0;      // Commit the pin to keep it in GPO mode
    HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = 0;              // re-lock it to prevent further changes
    // Confiure PF1 as T0CCP0
    SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);          // Enable Timer 0
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_TIMER0)) {} // Wait foir Timer0 module to be ready
    GPIOPinConfigure(GPIO_PF0_T0CCP0); // Configure PF0 as output of Timer 0A
    GPIOPinTypeTimer(GPIO_PORTF_BASE, GPIO_PIN_0);         // Enable PF0 as output of timer assigned to it
    TimerConfigure(TIMER0_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_PWM));  // Timer 0 as 1 x 16 bit PWM
    TimerControlLevel(TIMER0_BASE, TIMER_A, 0);
    TimerLoadSet(TIMER0_BASE, TIMER_A, PWMPeriod - 1); // Set period
    TimerMatchSet(TIMER0_BASE, TIMER_A, (PWMPeriod - 1)*dutyCycle/100);
    TimerEnable(TIMER0_BASE, TIMER_A);                     // Start Timer 0 A

    To change the frequency and duty cycle, just call the following two lines:

    TimerLoadSet(TIMER0_BASE, TIMER_A, PWMPeriod - 1); // Set period
    TimerMatchSet(TIMER0_BASE, TIMER_A, (PWMPeriod - 1)*dutyCycle/100);

    Only problem is that the timers are 16-bit which means that at a CPU clock of 80 MHz, the lowest frequency is 1221 Hz. Tried to use the wide counters, but there does not appear to be one for GPIO PF0. The above code serves my purposes.

  • Hi Vito,

      With the prescaler, you can extend the timerA to 24bit. By combining both timerA and timerB you can extend to 48bit. 

    11.3.2.5 PWM Mode
    The GPTM supports a simple PWM generation mode. In PWM mode, the timer is configured as a
    24-bit or 48-bit down-counter with a start value (and thus period) defined by the GPTMTnILR and
    GPTMTnPR registers.

  • Thanks for your response Charles. The plot thickens. I previously tried TimerPrescaleSet(TIMER0_BASE, TIMER_A, X), just before the TimerConfigure but could not get things to behave as I expected. For instance, with PWMPeriod = 40,000 (CPU clock 80 MHz), I get the following periods:

    TimerPresscaleSet    Resultant Period    Expected Period   Deviation

            0                  500 us            500 us             0%

            1                 1350 us           1000 us           +35%

            2                 2150 us           2000 us            +8%

            3                 3000 us           4000 us           -25%

            4                 3750 us           8000 us           -47%

    Also, the time spend in the low state, presumably set by TimerMatchSet(TIMER0_BASE, TIMER_A, (PWMPeriod - 1)*0.5) remains constant regardless of the TimerPrescaleSet value.

    I would have thought that TimerPrescaleSet would proportionally affect the resultant period (which it roughly does) and TimerMatchSet (which it doesn't affect). The TivaWare Peripheral Driver Library doesn't really explain TimerLoadSet and TimerMatchSet well enough for my understanding. Very light on explanation. 

  • Hi Vito,

      Please refer the below example for extending the prescaler. 

    TimerControlLevel(TIMER1_BASE, TIMER_B, true);

    //
    TimerConfigure(TIMER1_BASE, TIMER_CFG_SPLIT_PAIR | TIMER_CFG_B_PWM);

    // Set the Timer1B preload 
    TimerPrescaleSet(TIMER1_BASE, TIMER_B, 123);
    //
    // Set the Timer1B load value
    //
    TimerLoadSet(TIMER1_BASE, TIMER_B, 45678);

    period = TimerLoadGet(TIMER1_BASE, TIMER_B) + TimerPrescaleGet(TIMER1_BASE, TIMER_B) * 65536 ;
    duty = period / 2;

    // //
    // // Set the Timer1B match value to load value / 2.
    //

    TimerPrescaleMatchSet(TIMER1_BASE, TIMER_B,
    duty / 65536);
    //
    TimerMatchSet(TIMER1_BASE, TIMER_B,
    duty % 65536);

  • Thanks for your prompt response Charles. I think we have arrived at a great solution. Your mentioning TimerPrescaleSet allows me to now have a solution where the PWM can operate down to 5 Hz, which is more than enough for audio applications. In the end, the solution was simple. I've included two new functions TimerLoadSet24 and TimerMatchSet24, which are used instead of TimerLoadSet and TimerMatchSet.

    //***************************************************************
    // 24 bit version of TimerLoadSet
    //***************************************************************
    void TimerLoadSet24(uint32_t base, uint32_t timer, uint32_t value)
    {
    TimerPrescaleSet(base, timer, value >> 16);
    TimerLoadSet(base, timer, value & 0xFFFF);
    }

    //*****************************************************************
    // 24 bit version of TimerMatchSet
    //*****************************************************************
    void TimerMatchSet24(uint32_t base, uint32_t timer, uint32_t value)
    {
    TimerPrescaleMatchSet(base, timer, value >> 16);
    TimerMatchSet(base, timer, value & 0xFFFF);
    }

    Everything works well (although there was a small bug fix to get the duty cycle right (highlighted in red):

        TimerMatchSet24(TIMER0_BASE, TIMER_A, (PWMPeriod - 1)*(100-dutyCycle)/100);