This is a lot of fun trying to figure out. I’m glad that MartinL put up several examples. And thanks to Robin2 for the serial input.
I don’t have a lot of experience, but I’m planning on using this to control some fans.
I’ve been following along the datasheet and commenting where I find things as best I can. Is there like a best practice cheat sheet or something? Like, for example is it a bad idea to NOT divide the DPLL0 clock source?
Does CC[0] == WO[0] && … CC[3] == WO[3] for a given TCC? Or do the channels correspond to the IOSET(s)?
// Adafruit feather m4 express: Set-up digital pin D12 and D13 to 25KHz variable duty cycle
// PWM frequency = (selected clock source / DIV) / (PRESCALER_DIV * (PERIOD + 1))
// ( GCLK_GENCTRL_SRC_DPLL0 / GCLK_GENCTRL_DIV(0) ) / (TC_CTRLA_PRESCALER_DIV2
#define PERIOD 2399
const byte numChars = 32;
char receivedChars[numChars]; // an array to store the received data
String inString = "";
float inputfloat = 0;
boolean newData = false;
void setup()
{
// Set up the generic clock (GCLK7) to clock timer TCC1
GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(0) | // Divide the clock source
GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW "square wave"
GCLK_GENCTRL_GENEN | // Enable GCLK7
//GCLK_GENCTRL_SRC_DFLL; // Select 48MHz DFLL clock source
//GCLK_GENCTRL_SRC_DPLL1; // Select 100MHz DPLL clock source
GCLK_GENCTRL_SRC_DPLL0; // Select 120MHz DPLL clock source
while (GCLK->SYNCBUSY.bit.GENCTRL7); // Wait for synchronization
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
// page 169
GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN | // Enable the TCC0/TCC1 perhipheral channel
GCLK_PCHCTRL_GEN_GCLK7; // Connect generic clock 7 to TCC0 and TCC1
// Enable the peripheral multiplexer on pin D12
PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
// Enable the peripheral multiplexer on pin D13
PORT->Group[g_APinDescription[13].ulPort].PINCFG[g_APinDescription[13].ulPin].bit.PMUXEN = 1;
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
// page 35
// Set the D12 (PORT_PA22 TCC1/WO[6]) peripheral multiplexer to peripheral (EVEN port number) F(0x5): TCC1, Channel 0
PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXE(5);
// Set the D13 (PORT_PA23 TCC1/WO[7]) peripheral multiplexer to peripheral (ODD port number) F(0x5): TCC1, Channel 1
PORT->Group[g_APinDescription[13].ulPort].PMUX[g_APinDescription[13].ulPin >> 1].reg |= PORT_PMUX_PMUXO(5);
TCC1->CTRLA.reg = TC_CTRLA_PRESCALER_DIV2 | // Set prescaler to 2, 120MHz/2 = 60MHz
TC_CTRLA_PRESCSYNC_PRESC; // Set the reset/reload to trigger on prescaler clock
TCC1->WAVE.reg = TC_WAVE_WAVEGEN_NPWM; // Set-up TCC1 timer for Normal (single slope) PWM mode (NPWM)
while (TCC1->SYNCBUSY.bit.WAVE) // Wait for synchronization
//pwm resolution
//log(PER.reg 2399) / log(2) = 2^11 steps (2048)
TCC1->PER.reg = PERIOD; // Set-up the PER (period) register for 25KHz
while (TCC1->SYNCBUSY.bit.PER); // Wait for synchronization
TCC1->CC[0].reg = PERIOD; // D12??? all the way on
while (TCC1->SYNCBUSY.bit.CC0); // Wait for synchronization
TCC1->CC[1].reg = PERIOD; // D13??? all the way on
while (TCC1->SYNCBUSY.bit.CC1); // Wait for synchronization
TCC1->CTRLA.bit.ENABLE = 1; // Enable timer TCC1
while (TCC1->SYNCBUSY.bit.ENABLE); // Wait for synchronization
Serial.begin(9600);
while (!Serial);
Serial.println("<Arduino is ready>");
}
void changeDutyCycle() {
if (newData == true) {
inString = receivedChars;
float rawinputfloat = inString.toFloat();
inString = "";
inputfloat = constrain(rawinputfloat, 0.00, 100.00);
float fperiod = inputfloat * (PERIOD / 100.00);
int period = constrain(fperiod, 0, PERIOD);
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
//page 43-44 table 6-29
// is ccbuf[0] the IOSET 1 pins?, ie if I want different duty cycles per pin do I have to pick another pin in a different IOSET?
TCC1->CCBUF[0].reg = period; //per can equal 0-2399 (+1) microseconds?
TCC1->CCBUF[1].reg = period; //does this need any input validation?
}
}
void loop()
{
recvWithEndMarker();
changeDutyCycle();
showNewData();
}
void recvWithEndMarker() {
static byte ndx = 0;
const char endMarker = '\n';
char rc;
// if (Serial.available() > 0) {
while (Serial.available() > 0 && newData == false) {
rc = Serial.read();
if (rc != '\n') {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
ndx = 0;
newData = true;
}
}
}
void showNewData() {
if (newData == true) {
Serial.print("This just in ... ");
Serial.println(inputfloat);
newData = false;
}
}
I’m using the arduino 1.8.9 IDE and adafruit’s latest board definition and this compiles.
14.6.2.7 Changing the Clock Frequency
The selected source for a Generator can be divided by writing a division value in the Division Factor bit
field of the Generator Control register (GENCTRLn.DIV). How the actual division factor is calculated is
depending on the Divide Selection bit (GENCTRLn.DIVSEL).
If GENCTRLn.DIVSEL=0 and GENCTRLn.DIV is either 0 or 1, the output clock will be undivided.
Note: The number of available DIV bits may vary from Generator to Generator.
Does CC[0] == WO[0] && ... CC[3] == WO[3] for a given TCC?
By default they do, (but this can be changed in the "Output Matrix Channel Pin Routing Configuration" described on page 1853 of the SAMD51 datasheet in the Waveform Extension section).
If there are more output channels than timer compare channels then the compare channels are repeated. For example timer TCC0 has outputs WO[0]...WO[7], but only 6 compare channels CC[0] to CC[5], therefore if WO[5] == CC[5] then WO[6] == CC[0] and WO[7] == CC[1].
Enabling dead-time insertion makes the corresponding compare and repeated compare channels, for example on timer TCC0 CC[1] on WO[1] and CC[1] on W[7], become the complementary low side and high side outputs.
By default they do, (but this can be changed in the “Output Matrix Channel Pin Routing Configuration” described on page 1853 of the SAMD51 datasheet in the Waveform Extension section).
If there are more output channels than timer compare channels then the compare channels are repeated. For example timer TCC0 has outputs WO[0]…WO[7], but only 6 compare channels CC[0] to CC[5], therefore if WO[5] == CC[5] then WO[6] == CC[0] and WO[7] == CC[1].
Enabling dead-time insertion makes the corresponding compare and repeated compare channels, for example on timer TCC0 CC[1] on WO[1] and CC[1] on W[6] become the complementary low side and high side outputs.
Thank you for your reply,
I believe that for TCC1: CC[2] == WO[6] && CC[3] == WO[7] because TCC1 has 4 CC.
// Adafruit feather m4 express: Set-up digital pin D12 and D13 to 25KHz variable duty cycle
// PWM frequency = (selected clock source / DIV) / (PRESCALER_DIV * (PERIOD + 1))
// ( GCLK_GENCTRL_SRC_DPLL0 / GCLK_GENCTRL_DIV(0) ) / (TC_CTRLA_PRESCALER_DIV2
#define PERIOD 2399
const byte numChars = 32;
char receivedChars[numChars]; // an array to store the received data
String inString = "";
float inputfloat = 0;
boolean newData = false;
void setup()
{
// Set up the generic clock (GCLK7) to clock timer TCC1
GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(0) | // Divide the clock source
GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW "square wave"
GCLK_GENCTRL_GENEN | // Enable GCLK7
//GCLK_GENCTRL_SRC_DFLL; // Select 48MHz DFLL clock source
//GCLK_GENCTRL_SRC_DPLL1; // Select 100MHz DPLL clock source
GCLK_GENCTRL_SRC_DPLL0; // Select 120MHz DPLL clock source
while (GCLK->SYNCBUSY.bit.GENCTRL7); // Wait for synchronization
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
// page 169
GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN | // Enable the TCC0/TCC1 perhipheral channel
GCLK_PCHCTRL_GEN_GCLK7; // Connect generic clock 7 to TCC0 and TCC1
// Enable the peripheral multiplexer on pin D12
PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
// Enable the peripheral multiplexer on pin D13
PORT->Group[g_APinDescription[13].ulPort].PINCFG[g_APinDescription[13].ulPin].bit.PMUXEN = 1;
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
// page 35
// Set the D12 (PORT_PA22 TCC1/WO[6]) peripheral multiplexer to peripheral (EVEN port number) F(0x5): TCC1, Channel 0
PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXE(5);
// Set the D13 (PORT_PA23 TCC1/WO[7]) peripheral multiplexer to peripheral (ODD port number) F(0x5): TCC1, Channel 1
PORT->Group[g_APinDescription[13].ulPort].PMUX[g_APinDescription[13].ulPin >> 1].reg |= PORT_PMUX_PMUXO(5);
TCC1->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV2 | // Set prescaler to 2, 120MHz/2 = 60MHz
TCC_CTRLA_PRESCSYNC_PRESC; // Set the reset/reload to trigger on prescaler clock
TCC1->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Set-up TCC1 timer for Normal (single slope) PWM mode (NPWM)
while (TCC1->SYNCBUSY.bit.WAVE) // Wait for synchronization
//pwm resolution
//log(PER.reg 2399) / log(2) = 2^11 steps (2048)
TCC1->PER.reg = PERIOD; // Set-up the PER (period) register for 25KHz
while (TCC1->SYNCBUSY.bit.PER); // Wait for synchronization
TCC1->CC[2].reg = PERIOD; // D12??? all the way on
while (TCC1->SYNCBUSY.bit.CC2); // Wait for synchronization
TCC1->CC[3].reg = PERIOD; // D13??? all the way on
while (TCC1->SYNCBUSY.bit.CC3); // Wait for synchronization
TCC1->CTRLA.bit.ENABLE = 1; // Enable timer TCC1
while (TCC1->SYNCBUSY.bit.ENABLE); // Wait for synchronization
Serial.begin(9600);
while (!Serial);
Serial.println("<Arduino is ready>");
}
void changeDutyCycle() {
if (newData == true) {
inString = receivedChars;
float rawinputfloat = inString.toFloat();
inString = "";
inputfloat = constrain(rawinputfloat, 0.00, 100.00);
float fperiod = inputfloat * (PERIOD / 100.00);
int period = constrain(fperiod, 0, PERIOD);
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
//page 43-44 table 6-29
TCC1->CCBUF[2].reg = period; //per can equal 0-2399 (+1) microseconds?
TCC1->CCBUF[3].reg = period; //does this need any input validation?
}
}
void loop()
{
recvWithEndMarker();
changeDutyCycle();
showNewData();
}
void recvWithEndMarker() {
static byte ndx = 0;
const char endMarker = '\n';
char rc;
// if (Serial.available() > 0) {
while (Serial.available() > 0 && newData == false) {
rc = Serial.read();
if (rc != '\n') {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
ndx = 0;
newData = true;
}
}
}
void showNewData() {
if (newData == true) {
Serial.print("This just in ... ");
Serial.println(inputfloat);
newData = false;
}
}
I changed TC_ to TCC_ for the prescaler and wavegen.
Time to break out the breadboard to see if it does anything!