Teensy 3.6, unexpected low frequencies at pins

teensy3056

Active member
I am trying to take in an signal via the ADC0 and send it out via TPM1. The ADC works, the TPM1, specifically the selected clock, OSCERCLK, does not.

In the following code, 3 files, the configuration is in Definitions.cpp. The rest is included for completeness. The pdf. page numbers refer to the MK66FX1M0 Manual

The problems:

1. I want the OSCERCLK to be connected to Teensy 3.6 pin 9, and expect to see a frequency of 2 MHz. Instead I am measuring 30 Hz (not kHz, not MHz, Hz) with an oscilloscope.

2. The TPM1 output is connected to Teensy pin 16 and has the correct duty cycle for the analog voltage input. However, the frequency of the PWM signal is 1 Hz (not kHz, not MHz, Hz).

Teensy_Sound_Processing_B.ino:

Code:
/*
	Name:       Teensy_Sound_Processing_B_09.ino
*/


//*****************************************************************************
#include "Definitions.h"

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

	Serial.begin(115200);

	Serial.printf("\n1. Serial print ready.\n");
	Serial.printf("2. Serial print ready.\n");

	// LED pin
	pinMode(13, OUTPUT);

	Configure_ADC();

	Configure_TPM1();

	//Serial.printf("ADC0_SC2 = 0x%X\n", ADC0_SC2);

	attachInterruptVector(IRQ_ADC0, ISR_ADC0);
	NVIC_ENABLE_IRQ(IRQ_ADC0);

	//*********************************
	Serial.printf("End of setup().\n");

	startTime = micros();
}

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

	currentTime = micros();
	deltaTime = currentTime - lastTime;

	if (((ADC0_SC2 & (1 << 7)) == 0) && startConversion) {

		startConversion = false;

		currentTime = lastTime = micros();

		// ADC Status and Control Registers 1 (ADC0_SC1A), pdf. 959
		//  Writing to this register initiates a conversion.
		//  SC1A is used for both software and hardware trigger modes of operation.
		//  Disable interrupt
		//  Single-ended conversions
		//  Input channel AD18.
		ADC0_SC1A = 0x12;
	}

	if (((ADC0_SC2 & (1 << 7)) == 0) && !startConversion) {

		ISR_ADC0();

		if (++printCount > 99) {

			printCount = 0;

			//Serial.printf("Input voltage = %6.3f\n", digitizedScaledInput);
		}

		startConversion = true;
	}

	if (digitizedScaledInput > 1.655) {

		digitalWrite(13, HIGH);
	}
	else {

		digitalWrite(13, LOW);
	}

	if (/*(digitizedScaledInput < 1.600) &&*/ (conversionCount > 50'000)) {

		conversionCount = 0;
		
		endTime = micros();

		//Serial.printf("Conversion count = %d in %d (us) \n", conversionCount, (endTime - startTime));

		Serial.printf("Input ADC value = %d\n", digitizedInput);

		Serial.printf("Input voltage = %6.3f\n", digitizedScaledInput);

		Serial.printf("TPM1_C0V = %d\n", TPM1_C0V);
	}
}

Definitions.h

Code:
// Definitions_09.h

#ifndef _DEFINITIONS_h
#define _DEFINITIONS_h

#if defined(ARDUINO) && ARDUINO >= 100
	#include "arduino.h"
#else
	#include "WProgram.h"
#endif

//*****************************************************************************
// Global variables
extern volatile bool startConversion;
extern volatile uint16_t digitizedInput;
extern volatile float digitizedScaledInput;
extern volatile uint32_t deltaTime;
extern volatile uint32_t currentTime;
extern volatile uint32_t lastTime;

extern volatile uint32_t printCount;

extern volatile uint32_t startTime;
extern volatile uint32_t endTime;
extern volatile uint32_t conversionCount;

//*****************************************************************************
// ADC function declarations

void Configure_ADC();

void ISR_ADC0();

//*****************************************************************************
// TPM1 function declarations

void Configure_TPM1();


#endif

Definitions.cpp

Code:
// Definitions_09.cpp

#include "Definitions.h"

//*****************************************************************************
volatile bool startConversion = true;
volatile uint16_t digitizedInput = 0;
volatile float digitizedScaledInput = 0.0f;
volatile uint32_t deltaTime = 0;
volatile uint32_t currentTime = 0;
volatile uint32_t lastTime = 0;

volatile uint32_t printCount = 0;

volatile uint32_t startTime = 0;
volatile uint32_t endTime = 0;
volatile uint32_t conversionCount = 0;

//*****************************************************************************
// ADC configuration

void Configure_ADC() {

	// pdf. 960.
	// Bit 7 is the conversion complete flag.
	// ADC interrupt disabled.
	// Single-ended conversions.
	// Input channel, AD18, default pin 34.
	// Writing to ADC0_SC1A starts conversion.
	//ADC0_SC1A |= 0x12;

	// pdf. 962
	// Clock divide by 8
	// Long sample time
	// 10-bit conversion
	// ADC clock is 30 / 2 MHz.
	// Default, ADC0_CFG1 = 0x39
	ADC0_CFG1 |= 0x12;

	// pdf. 963
	// ADxxa channels
	// Asynchronous clock and clock output is enabled regardless of the state of the ADC.
	// Normal conversion sequence selected.
	// 2 extra ADCK cycles; 6 ADCK cycles total sample time.
	ADC0_CFG2 |= 0x03;

	// pdf. 964
	// ADC0_RA & 0x3FF is the 10-bit conversion result.

	// pdf. 966
	// Compare value

	// pdf. 967
	// Will want to enable compare value function.
	ADC0_SC2 |= 0x00;
}

//*********************************
void ISR_ADC0() {

	currentTime = micros();
	deltaTime = currentTime - lastTime;

	digitizedInput = ADC0_RA;

	// pdf.1066
	// Check TPM1 overflow flag.
	//  Write new value if an overflow has occurred.
	if ((TPM1_SC & 0x80) != 0) {
		// Clear TPM1 overflow flag, w1c.
		TPM1_SC |= 0x80;
		TPM1_C0V = (digitizedInput << 6);
	}

	//TPM1_C0V = (digitizedInput << 5);

	digitizedScaledInput = ((float)digitizedInput / (float)1023) * 3.3f;

	conversionCount++;
}

//*****************************************************************************
// TPM1 function declarations

void Configure_TPM1() {

	// pdf.1066
	// Disable TPM1 for subsequent setting.
	TPM1_SC &= 0x80;
	while (TPM1_SC != 0x0) {
		TPM1_SC &= 0x80;
	}
	Serial.printf("TPM1 is disable.  Should equal 0x0, = 0x%X\n", TPM1_SC);
	
	// pdf.1066
	// Overflow flag cleared.
	// Set for upcounting.
	// TPM counter increments on every TPM counter clock, CMOD = 0b01.
	// Prescaler = 32, 2 MHz / 32 = 62.5 kHz.
	TPM1_SC |= 0x08D;
	while ((TPM1_SC & 0xD) != 0xD) {
		TPM1_SC |= 0x08D;
	}
	Serial.printf("TPM1_SC should equal 0xD, = 0x%X\n", TPM1_SC);

	// pdf. 1067
	// Initialize the TPM1 counting register
	TPM1_CNT = 0x0000;
	Serial.printf("TPM1_CNT reset, should equal 0x0, = 0x%X\n", TPM1_CNT);

	// pdf. 1068
	// Overflow value, set to max.
	TPM1_MOD |= 0xFFFF;
	while ((TPM1_MOD & 0xFFFF) != 0xFFFF) {
		TPM1_MOD |= 0xFFFF;
	}
	Serial.printf("Check value should equal 0xFFFF, = 0x%X\n", (TPM1_MOD & 0xFFFF));

	// pdf. 1069
	// MS0B:MS0A = 0b10, ELS0B:ELS0A = 0b10 - Edge-aligned PWM, High-true pulses
	//  (clear Output on match, set Output on reload).

	// Disable the channel.
	while ((TPM1_C0SC & 0x3C) != 0) {
		TPM1_C0SC &= 80;
	}
	Serial.printf("(TPM1_C0SC & 0x3C) should be disabled and = 0x0, = 0x%X\n", (TPM1_C0SC & 0x3C));
	TPM1_C0SC |= 0xA8;
	Serial.printf("TPM1_C0SC should be = 0xA8, = 0x%X\n", TPM1_C0SC);

	// pdf. 1071
	// Will be updated as received from the ADC input.
	//TPM1_C0V = digitizedInput, set in ISR_ADC0().  This is a 50% duty cycle starting setting.
	TPM1_C0V |= 32'768;

	//************************************
	// TPM1 clock

	// pdf. 631
	// Very high frequency range selected for the crystal oscillator.
	// Oscillator requested.
	// Fast internal reference clock selected.
	MCG_C2 |= 0x1;
	Serial.printf("MCG_C2 should be 0x25 = 0x%X\n", MCG_C2);

	// pdf. 239
	// Check that TPM1 clock source is OSCERCLK.
	Serial.printf("((SIM_SOPT2 & (0b10 << 24)) >> 24) should be 0x2 = 0x%X\n", ((SIM_SOPT2 & (0b10 << 24)) >> 24));

	// pdf. 667
	// Disable OSCERCLOCK.
	// OSC_CR = 0x8A
	OSC0_CR &= ~0x80;
	while ((OSC0_CR & 0x80) != 0) {
		OSC0_CR &= ~0x80;
	}
	Serial.printf("((OSC0_CR & 0x80) >> 7) is disabled, should be = 0x0, = 0x%X\n", ((OSC0_CR & 0x80) >> 7));

	// pdf. 669
	// Divsor is 8.  Output frequency should be 16 MHz / 8 = 2 MHz.
	OSC0_OSC_DIV |= (0b11 << 6);
	Serial.printf("OSC0_OSC_DIV should be = 0xC0, = 0x%X\n", OSC0_OSC_DIV);

	// pdf. 667
	// Set by default to the OSCERCLOCK.
	// Enable OSCERCLOCK.
	// External reference clock stays enabled in Stop mode
	// OSC_CR = 0x8A
	OSC0_CR |= 0xAA;
	Serial.printf("OSC0_CR should be = 0xAA, = 0x%X\n", OSC0_CR);

	// pdf. 255
	// Enable the TPM1 clock.
	SIM_SCGC2 |= (1 << 9);
	Serial.printf("(SIM_SCGC2 & (1 << 9)) should be = 0x200, = 0x%X\n", (SIM_SCGC2 & (1 << 9)));

	//************************************
	// Check that clock for ports B and C are enabled.
	// pdf. 261
	Serial.printf("((SIM_SCGC5 & (3 << 10)) >> 10) should be 0x3, = 0x%X\n", ((SIM_SCGC5 & (3 << 10)) >> 10));
	 
	// pdf. 220
	// Divided OSCERCLOCK output connected to pin 9.
	// pdf. 188
	// PTC3, ALT5, CLKOUT
	PORTC_PCR3 |= (5 << 8);
	Serial.printf("(PORTC_PCR3 & (7 << 8)) should be = 0x500, = 0x%X\n", (PORTC_PCR3 & (7 << 8)));

	// pdf. 220
	// TPM1 output connected to pin 16.
	// pdf. 188
	// PTB0, ALT6
	PORTB_PCR0 |= (6 << 8);
	Serial.printf("(PORTB_PCR0 & (7 << 8)) should be = 0x600, = 0x%X\n", (PORTB_PCR0 & (7 << 8)));
}

The output from this project is:

Code:
1. Serial print ready.
2. Serial print ready.
TPM1 is disable.  Should equal 0x0, = 0x0
TPM1_SC should equal 0xD, = 0xD
TPM1_CNT reset, should equal 0x0, = 0x3E84
Check value should equal 0xFFFF, = 0xFFFF
(TPM1_C0SC & 0x3C) should be disabled and = 0x0, = 0x0
TPM1_C0SC should be = 0xA8, = 0xA8
MCG_C2 should be 0x25 = 0x25
((SIM_SOPT2 & (0b10 << 24)) >> 24) should be 0x2 = 0x2
((OSC0_CR & 0x80) >> 7) is disabled, should be = 0x0, = 0x0
OSC0_OSC_DIV should be = 0xC0, = 0xC0
OSC0_CR should be = 0xAA, = 0xAA
(SIM_SCGC2 & (1 << 9)) should be = 0x200, = 0x200
((SIM_SCGC5 & (3 << 10)) >> 10) should be 0x3, = 0x3
(PORTC_PCR3 & (7 << 8)) should be = 0x500, = 0x500
(PORTB_PCR0 & (7 << 8)) should be = 0x600, = 0x600
End of setup().
Input ADC value = 608
Input voltage =  1.961
TPM1_C0V = 38912

I am using a potentiometer wired as a voltage divider to divide the 3.3 VDC Teensy voltage as an input to the ADC. This is working.

TPM1 is supposed to be using OSCERCLK divided by 8 from 16 MHz to 2 MHz.

Again, the problems:

1. I want the OSCERCLK to be connected to Teensy 3.6 pin 9, and expect to see a frequency of 2 MHz. Instead I am measuring 30 Hz (not kHz, not MHz, Hz) with an oscilloscope.

2. The TPM1 output is connected to Teensy pin 16 and has the correct duty cycle for the analog voltage input. However, the frequency of the PWM signal is 1 Hz (not kHz, not MHz, Hz).

My relevant configuration code is in Definitions.cpp and begins after the "// TPM1 function declarations" comment.

Any help would be appreciated.

Thanks.
 
How about trying
Code:
  analogWriteFrequency(16, 8000000);   // 16 and 17 max 8mhz uses TPM T3.6 only
  analogWrite(16, 128);

after starting the analogWrite() you could print out the various TPM1 registers to see how they are configured, or look at the Teensy core
hardware/teensy/avr/cores/teensy3/pins_teensy.c

Code:
     SIM_SCGC2 0x200   TPM1 enabled
     SIM_SOPT2 0x20710C0
     TPM1_C0SC 0xA8   mode,edge,level   set to 0x28
     TPM1_SC 0x88
     TPM1_MOD 0x1   16mhz/2
     TPM1_C0V 0x1

I also have some TPM test snippets in https://github.com/manitou48/teensy3/blob/master/k66TPM.ino
sketch includes exercising TPM2 on pin 29
 
Sheesh! I made a dumb mistake by overlooking the overflow value to load into the TPM1 counter. I was running the full 16-bits which of course effectively divided the clock frequency by 65536.

I fixed that and am now getting a 15.6 kHz PWM output the duty cycle of which changes with the ADC input.

I know I am doing this the "hard way". I want to understand the nuts and bolts basics. That said, I am examining the register values as the code enters my setup(). That has been helpful, and I appreciate those who pointed that out.
 
Back
Top