Teensy 3.6, TPM1, channel 0 output pin?

teensy3056

Active member
I am working to better understand the Teensy 3.6 microcontroller's many hidden capabilities.

I would like to get the PWM output from TPM1, channel 0 to pin 16 of the Teensy 3.6.

In the code below, I have been successful in implementing TPM1 overflow and channel 0 interrupts starting with the 4 MHz clock and dividing it to 62.5 kHz.

I cannot seem to get the PWM signal on pin 16. I have tried the code for TPM2 also to no avail.

There are a lot of comments and printf() statements so I could see what is happening. I may be missing the setting or clearing of 1 bit someplace.

The pdf. page numbers refer to the MK66FX1M0 Manual.pdf.

Code:
/*
    Name:       Teensy_timers_14.ino
*/

#include <kinetis.h>
#include <core_pins.h>

//#define OVERFLOW_CONDITION
#define CHANNEL0_EVENT

//*****************************************************************************
// Prototypes
void ISR_TPM1(void);

//*****************************************************************************
// Global variables
volatile byte overflowOut = LOW;
volatile byte channel0Out = LOW;
volatile byte overflowFlag = 0;

volatile uint32_t resetValue = 0;

volatile uint32_t tempValue = 0;

//*****************************************************************************
void setup() {

    Serial.begin(115200);
    delay(1000);
    Serial.printf("\nSerial print ready.\n");

    // LED pin
    pinMode(13, OUTPUT);

    // TPM1, channel 0 pin
    pinMode(CORE_TPM1_CH0_PIN, OUTPUT);

    // Set MCG mode FEI, pdf. 646.
    //  Sets clock source, selects slow internal reference clock, pdf. 630.
    MCG_C1 = 0b0000'0100;

    //  Sets FLL, pdf. 636.
    MCG_C6 &= (~0b0100'0000);

    //*********************************

    // Choose oscillator for counter clock domain, pdf. 625.
    //  OSCCLK1/IRC48MCLK. Derived from internal 48 MHz oscillator.
    MCG_C7 |= 0b10;

    //Serial.printf("Here!!.\n");
    //delay(20000);

    // Setup timer, pdf. 630.
    // Disable MCGIRCLK clock
    MCG_C1 = 0b00000100;

    // MCG_C2, MCG_C3, MCG_C4, MCG_C5, MCG_S left unchanged, pdf. 631ff.

    // FCRDIV = 0b110 = divide by 64 -> 62.5 kHz.
    MCG_SC = 0b00001100;

    // MCG_C8, MCG_C9, (no MCG_C10), MCG_C11, MCG_C12 left in reset status, pdf. 641ff.

    // Enable MCGIRCLK clock
    MCG_C1 |= 0b10;
        
    //*********************************
        
    // Select MCGIRCLK clock as TPM counter clock domain, pdf. 238-9.
    SIM_SOPT2 |= SIM_SOPT2_TPMSRC(3);
    delay(1000);
    Serial.printf("SIM_SOPT2 = %u\n", SIM_SOPT2);

    // 50331648 has 0b11 in positions 25:24 and 0 elsewhere.
    //  The TPMSRC bit field is the two bits at positions 25:24.
    tempValue = ((SIM_SOPT2 & 50331648) >> 24);
    Serial.printf("SIM_SOPT2 bit field TPMSRC = %u\n", tempValue);

    // Gate control for TPM1 & TPM2, gate open, pdf. 256.
    SIM_SCGC2 |= (1 << 9) | (1 << 10);

    //*********************************

    // Settings for TPM1_SC, pdf. 1066ff
    // Spin-wait for disablement of the TPM counter clock while options are set.
    Serial.printf("Disable TPM counter clock for option setting.\n");
    TPM1_SC &= (~0b1'0111'1111);
    Serial.printf("01-TPM1_SC = %u\n", TPM1_SC);
    while (0 != (TPM1_SC & 0b0'0001'1000)) {
        TPM1_SC &= (~0b1'0111'1111);
        Serial.printf("02-TPM1_SC = %u\n", TPM1_SC);
    }
    Serial.printf("TPM counter clock disabled.\n");
    
    // Timer overflow interrupt, TOIE, enable.
    TPM1_SC |= (1 << 6);

    //TPM counter operates in up-counting mode, CPWMS.
    TPM1_SC |= (0 << 5);

    // Prescale factor, PS, initially set to 1.
    TPM1_SC |= (0 << 0);

    // Spin-wait for enablement of the TPM counter clock.
    //  Counter increments on every TPM counter clock, CMOD.
    //  I do not think the spin-wait is strictly necessary for enablement.
    TPM1_SC |= (1 << 3);
    Serial.printf("03-TPM1_SC = %u\n", TPM1_SC);
    while (8 != (TPM1_SC & 0b0'0000'1000)) {
        TPM1_SC |= (1 << 3);
    }
    Serial.printf("TPM counter clock is enabled.\n");

    //*********************************

    // Highest value and overflow value for the TPM counter.
    TPM1_MOD = 62'500;

    //*********************************
    
    // Set and enable edge-aligned PWM
    // Disable channel
    Serial.printf("Spin-wait for channel 0 to be disabled.\n");
    TPM1_C0SC &= ~0b0011'1100;
    while (0 != (TPM1_C0SC & 0b00111100)) {
        TPM1_C0SC &= ~0b0011'1100;
    }
    Serial.printf("Channel 0 disabled.\n");

    // Changes - set to edge-aligned PWM,
    //  High-true pulses (clear Output on match, set Output on reload)
    //  pdf. 1069.
    TPM1_C0SC |= 0b1110'1000;
    Serial.printf("Channel 0 edge-aligned PWM set.\n");

    // Channel 0 value to match, pdf. 1071.
    TPM1_C0V = (uint16_t)32'768;
    Serial.printf("Channel 0 match value set to: %u\n", TPM1_C0V);

    //*********************************
    
    attachInterruptVector(IRQ_TPM1, ISR_TPM1);

    NVIC_ENABLE_IRQ(IRQ_TPM1);

    Serial.printf("End of setup().\n");
    delay(5000);
}

//*****************************************************************************
void loop() {
}

//*****************************************************************************
void ISR_TPM1(void) {

#ifdef OVERFLOW_CONDITION
    // Overflow interrupt.
    if (0b1'0000'0000 == (TPM1_STATUS & 0b1'0000'0000)) {
        // Clears overflow interrupt flag.
        TPM1_SC |= (1 << 7);

        if (++resetValue > 0) {

            resetValue = 0;
            Serial.printf("%s\n", "Interrupt called.");
            overflowOut = !overflowOut;

            digitalWrite(13, overflowOut);
        }
    }
#endif

#ifdef CHANNEL0_EVENT
    // Channel 0 event.
    if (0b0'0000'0001 == (TPM1_STATUS & 0b0'0000'0001)) {

        // Clear channel 0 event flag.
        TPM1_STATUS |= (1 << 0);
        TPM1_C0SC |= (1 << 7);

        channel0Out = !channel0Out;

        Serial.printf("%s\n", "Channel 0 interrupt called.");

        digitalWrite(13, channel0Out);
    }
#endif
}
 
have you tried analogWrite(16,128) on T3.6? I believe that is using TPM1 channel 0. I have some test functions for T3.6 TPM, see
https://github.com/manitou48/teensy3/blob/master/k66TPM.ino
you have to configure the ALT function for the output pin. For TPM2 CH0, pin 29 is the output pin
CORE_PIN29_CONFIG = PORT_PCR_MUX(6) | PORT_PCR_DSE | PORT_PCR_SRE;

Take a look at teensy core code. For Teensy 3 PWM see hardware/teensy/avr/cores/teensy3/pins_teensy.c
 
manitou,

Thanks for your consideration.

On pdf. 186, TPM1_CH0 is associated with PTA8 for ALT6. My understanding is that PTA8 means Port A, PCR8.

On pdf. 221, I can set register PORTA_PCR8 to 0b110 via
Code:
PORTA_PCR8 |= (0b110 << 8);

Next I want to configure the Teensy 3.6 output pin 16 to be connected to PORTA_PCR8. However, in core_pins.h there is no PORTA_PCR8. Further, CORE_PIN16_CONFIG is defined as PORTB_PCR0.

Can you see what I am missing?
 
I found that a given channel may be on several pins. TPM1_CH0 supposedly will be on Teensy 3.6, pin 3 for port setting ALT7.

I tried the code
Code:
 CORE_PIN3_CONFIG |= PORT_PCR_MUX(7) | PORT_PCR_DSE | PORT_PCR_SRE | PORT_PCR_ODE;
to no avail.

I looked at pins_teensy.c. Honestly, I do not know what to make of it.

The requirements to set the ports for the MK66FX1M0 microcontroller and the associated pins on the Teensy 3.6 is stumping me.
 
The requirements to set the ports for the MK66FX1M0 microcontroller and the associated pins on the Teensy 3.6 is stumping me.

Repeating @manitou's question...

have you tried analogWrite(16,128) on T3.6? I believe that is using TPM1 channel 0.

Are you trying to change something, or just trying to understand how the Teensy core configures that pin?
 
joepasquariello,

I want the PWM signal from TPM1_CH0 at a pin.

Perhaps I am misreading the MK66FX1M0 microcontroller programming guide. My current understanding is that configuring the PWM for Edge-aligned PWM, High-true pulses (clear Output on match, set Output on reload), per Table 43-4, pdf. 1069, should put that signal on a pin assigned to TPM1_CH0, given that the correct alternate configuration (ALT#) per 11.3.1 K66 Signal Multiplexing and Pin Assignments, pdf. 184.

The manual, pdf. 1064 says, “Each TPM channel can be configured to operate either as input or output. The direction associated with each channel, input or output, is selected according to the mode assigned for that channel."

I had thought that by setting the mode I would get TPM1_CH0 on a pin.

Are you and manitou saying that I need to use analogWrite(16,128) or analogWrite(16,TPM1_CH0) to get that output?
 
The schematic at the bottom of the Teensy 3.6 Development Board shows that PTA12, ALT7 will be on pin 3 or PTB0, ALT6 will be on pin 16. Those two alternate configurations are for TPM1_CH0.

I have been relying on that technical information. If that is incorrect, or if I am interpreting it incorrectly, that will clearly be my fault.
 
I want the PWM signal from TPM1_CH0 at a pin. Perhaps I am misreading the MK66FX1M0 microcontroller programming guide. I had thought that by setting the mode I would get TPM1_CH0 on a pin.

Are you and manitou saying that I need to use analogWrite(16,128) or analogWrite(16,TPM1_CH0) to get that output?

This is very confusing. In your original post, you said,

I would like to get the PWM output from TPM1, channel 0 to pin 16 of the Teensy 3.6.

@manitou replied that you can use analogWrite( 16, 128). If you do that, you will get a 50% duty cycle PWM on pin 16. According to analog.c, for T3.6, macro TPM1_CH0_PIN is defined as 16. I take that to mean that the PWM you get on pin 16 will be generated via TPM1 channel 0.

The arguments to analogWrite() are pin number (16) and duty cycle (128). You could use analogWrite(TPM1_CH0_PIN,128), since TPM1_CH0_PIN = 16. Does that answer your question?
 
I am sorry for the confusion. It is my fault due in part to me trying to apply what I learned from working with the Arduino UNO.

That said, I was just able to get what I wanted. My post of 10:25 this morning specified my thinking a bit better, and that worked!

I added this code:
Code:
// Configure TPM1, channel 0 port, pdf. 187, 221, to drive Teensy 3.6, pin 16.
PORTB_PCR0 |= PORT_PCR_MUX(6) | PORT_PCR_DSE | PORT_PCR_SRE;

to the end of the original program and now pin 16 on the Teensy 3.6 is displaying the digital output of TPM1_CH0.

I did not have to use analogWrite(). The schematic for the Teensy 3.6 shows the hardwired connection between PTB0 and pin 16.

Gentlemen, I appreciate your engagement. You both made suggestions which were very helpful to me in improving my understanding.

Thank you.
 
So, you were simply trying to develop your own lower-level API for configuring PWM on T3.6? I’d be curious to see your entire program if you’re willing to share it.
 
joepasquariello,

It is not so much that I want my own lower-level API. Rather, I learned from a project I did with the Arduino UNO that there is an amazing capability in these microcontrollers which requires delving into the uC specifications.

The code in this post is a stepping stone for me to be able to accept an audio input via A/D, do some DSP on it, and then sent it out via a PWM D/A to a circuit which will drive a speaker. By getting TPM1_CH0 to a pin I can now move on.

This is the code. For this experiment you will see that the interrupt is driving the on-board LED. While I was working on the problem of this thread, I had a DMM connected to pin 16 which finally showed the digital variation at that output pin.

I welcome any comments, suggestions, etc. on the code. I have been using Visual Micro within Visual Studio 2019 for this work.

Code:
/*
    Name:       Teensy_timers_14.ino
*/

#include <kinetis.h>
#include <core_pins.h>

//#define OVERFLOW_CONDITION
#define CHANNEL0_EVENT

//*****************************************************************************
// Prototypes
void ISR_TPM1(void);

//*****************************************************************************
// Global variables
volatile byte overflowOut = LOW;
volatile byte channel0Out = LOW;
volatile byte overflowFlag = 0;

volatile uint32_t resetValue = 0;

volatile uint32_t tempValue = 0;

//*****************************************************************************
void setup() {

    Serial.begin(115200);
    delay(1000);
    Serial.printf("\nSerial print ready.\n");

    // LED pin
    pinMode(13, OUTPUT);

    // Set MCG mode FEI, pdf. 646.
    //  Sets clock source, selects slow internal reference clock, pdf. 630.
    MCG_C1 = 0b0000'0100;

    //  Sets FLL, pdf. 636.
    MCG_C6 &= (~0b0100'0000);

    //*********************************

    // Choose oscillator for counter clock domain, pdf. 625.
    //  OSCCLK1/IRC48MCLK. Derived from internal 48 MHz oscillator.
    MCG_C7 |= 0b10;

    //Serial.printf("Here!!.\n");
    //delay(20000);

    // Setup timer, pdf. 630.
    // Disable MCGIRCLK clock
    MCG_C1 = 0b00000100;

    // MCG_C2, MCG_C3, MCG_C4, MCG_C5, MCG_S left unchanged, pdf. 631ff.

    // FCRDIV = 0b110 = divide by 64 -> 62.5 kHz.
    MCG_SC = 0b00001100;

    // MCG_C8, MCG_C9, (no MCG_C10), MCG_C11, MCG_C12 left in reset status, pdf. 641ff.

    // Enable MCGIRCLK clock
    MCG_C1 |= 0b10;
        
    //*********************************
        
    // Select MCGIRCLK clock as TPM counter clock domain, pdf. 238-9.
    SIM_SOPT2 |= SIM_SOPT2_TPMSRC(3);
    delay(1000);
    Serial.printf("SIM_SOPT2 = %u\n", SIM_SOPT2);

    // 50331648 has 0b11 in positions 25:24 and 0 elsewhere.
    //  The TPMSRC bit field is the two bits at positions 25:24.
    tempValue = ((SIM_SOPT2 & 50331648) >> 24);
    Serial.printf("SIM_SOPT2 bit field TPMSRC = %u\n", tempValue);

    // Gate control for TPM1 & TPM2, gate open, pdf. 256.
    SIM_SCGC2 |= (1 << 9) | (1 << 10);

    //*********************************

    // Settings for TPM1_SC, pdf. 1066ff
    // Spin-wait for disablement of the TPM counter clock while options are set.
    Serial.printf("Disable TPM counter clock for option setting.\n");
    TPM1_SC &= (~0b1'0111'1111);
    Serial.printf("01-TPM1_SC = %u\n", TPM1_SC);
    while (0 != (TPM1_SC & 0b0'0001'1000)) {
        TPM1_SC &= (~0b1'0111'1111);
        Serial.printf("02-TPM1_SC = %u\n", TPM1_SC);
    }
    Serial.printf("TPM counter clock disabled.\n");
    
    // Timer overflow interrupt, TOIE, enable.
    TPM1_SC |= (1 << 6);

    //TPM counter operates in up-counting mode, CPWMS.
    TPM1_SC |= (0 << 5);

    // Prescale factor, PS, initially set to 1.
    TPM1_SC |= (0 << 0);

    // Spin-wait for enablement of the TPM counter clock.
    //  Counter increments on every TPM counter clock, CMOD.
    //  I do not think the spin-wait is strictly necessary for enablement.
    TPM1_SC |= (1 << 3);
    Serial.printf("03-TPM1_SC = %u\n", TPM1_SC);
    while (8 != (TPM1_SC & 0b0'0000'1000)) {
        TPM1_SC |= (1 << 3);
    }
    Serial.printf("TPM counter clock is enabled.\n");

    //*********************************

    // Highest value and overflow value for the TPM counter.
    TPM1_MOD = 62'500;

    //*********************************
    
    // Set and enable edge-aligned PWM
    // Disable channel
    Serial.printf("Spin-wait for channel 0 to be disabled.\n");
    TPM1_C0SC &= ~0b0011'1100;
    while (0 != (TPM1_C0SC & 0b00111100)) {
        TPM1_C0SC &= ~0b0011'1100;
    }
    Serial.printf("Channel 0 disabled.\n");

    // Changes - set to edge-aligned PWM,
    //  High-true pulses (clear Output on match, set Output on reload)
    //  pdf. 1069.
    TPM1_C0SC |= 0b1110'1000;
    Serial.printf("Channel 0 edge-aligned PWM set to %u\n", TPM1_C0SC);

    // Channel 0 value to match, pdf. 1071.
    TPM1_C0V = (uint16_t)32'768;
    Serial.printf("Channel 0 match value set to: %u\n", TPM1_C0V);

    //*********************************
    // Configure TPM1, channel 0 port, pdf. 187, 221, to drive Teensy 3.6, pin 16.
    PORTB_PCR0 |= PORT_PCR_MUX(6) | PORT_PCR_DSE | PORT_PCR_SRE;
    Serial.printf("Port B, PCR 0, MUX set to: %u\n", ((PORTB_PCR0 & 0b111'0000'0000) >> 8));

    //*********************************
    
    attachInterruptVector(IRQ_TPM1, ISR_TPM1);

    NVIC_ENABLE_IRQ(IRQ_TPM1);

    Serial.printf("End of setup().\n");
    delay(5000);

    
}

//*****************************************************************************
void loop() {
}

//*****************************************************************************
void ISR_TPM1(void) {

#ifdef OVERFLOW_CONDITION
    // Overflow interrupt.
    if (0b1'0000'0000 == (TPM1_STATUS & 0b1'0000'0000)) {
        // Clears overflow interrupt flag.
        TPM1_SC |= (1 << 7);

        if (++resetValue > 0) {

            resetValue = 0;
            Serial.printf("%s\n", "Interrupt called.");
            overflowOut = !overflowOut;

            digitalWrite(13, overflowOut);
        }
    }
#endif

#ifdef CHANNEL0_EVENT
    // Channel 0 event, pdf. 1072.
    if (0b0'0000'0001 == (TPM1_STATUS & 0b0'0000'0001)) {

        // Clear channel 0 event flag.
        TPM1_STATUS |= (1 << 0);
        TPM1_C0SC |= (1 << 7);

        channel0Out = !channel0Out;

        Serial.printf("%s\n", "Channel 0 interrupt called.");

        digitalWrite(13, channel0Out);
    }
#endif
}
 
I tried your program on a T3.6. It produces a very low-frequency PWM signal of about 0.5 Hz, with duty cycle 32768/62500. Do you need a very low-frequency signal like this in your project? I also tested the program below, and it does produce 1-kHz, 50% duty cycle PWM on pin 16 using TPM1 channel 0.

Code:
void setup() {
  analogWriteFrequency( 16, 1000 );
  analogWrite( 16, 128 );
}

void loop() {
}
 
joepasquariello,

Thanks for trying my code. It is always useful for another designer to test one's code so as to keep fatheadedness at bay!

No, I do not need very low frequency PWM square waves for my project. Those were only a means to an end. The 62.5 kHz is a part of what I intend to use, depending on what I learn about the A/D sampling frequency for an analog input.

Again, with the Arduino UNO experience behind me, I started delving into the 2237 pdf. pages of the MK66FX1M0 Manual and just found all kinds of juicy stuff I wanted to try. I also wanted to do it myself. I know there is very good Teensy timer code available for download. However, I like to understand the details which gives me a deeper understanding, and triggers ideas.

I decided to first replicate an Arduino UNO experiment I did a few weeks ago. This is a labeled view of that project:

Arduino project.png

The 32768 value was chosen so I could assess whether I had set the registers correctly to start with a 4 MHz square wave feeding TPM1.

The Teensy 3.6 has oodles of power which I can then use to do some audio DSP. Ultimately, I would like to do some RF DSP work with the Teensy, though that can only be successful once I have educated myself.
 
Back
Top