1

I'm programming a SAMD21 and I need PWM.

When I chose pin with F function TCC0 output: PA22 - TCC0/WO[4] PA23 - TCC0/WO[5]

I successfully configured TCC0 base counter:

// enable clock for TCC0 - disable clock masking
PM->APBCMASK.reg |= PM_APBCMASK_TCC0;

// set GCLK1 as source to the TCC0 counter
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_GEN(1) | GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_ID(0x1A);
while(!SYSCTRL->PCLKSR.bit.DFLLRDY);

// set counter
TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV64; // setting prescaler
TCC0->WAVE.reg |=  TCC_WAVE_WAVEGEN_NPWM | TCC_WAVE_POL0;
while (TCC0->SYNCBUSY.bit.WAVE);

// set TOP (PER) value of counter - frequency
TCC0->CTRLA.bit.RESOLUTION = 0;
TCC0->PER.reg = 48'000'000 / (100 * 64) - 1; // Fpwm = Fglk / (PRESC(PER+1))  --> PER = Fglk / (Fpwm * PRESC) - 1
while (TCC0->SYNCBUSY.bit.PER);

But the problem was how to configure the compare channels - the SAMD21 has only 4 compare channels (CC) but I want output to WO[4] and WO[5].

How can I connect given compare channels to WO[x] pins?

EDIT:

I have also configured pins for mutiplexing (not sure if correctly):

PORT->Group[0].PINCFG->reg |= (1 << PIN_PA22) | (1 << PIN_PA23);
PORT->Group[0].PMUX->bit.PMUXE = (0x5 << (PIN_PA22/2));
PORT->Group[0].PMUX->bit.PMUXO = (0x5 << (PIN_PA23/2 + 1));
ocrdu
  • 2,172
  • 6
  • 15
  • 22
EFK
  • 23
  • 5
  • I haven't used PWM on these parts, but I'm almost certain that the problem is that you need to write to the PMUX register for the given pin. There's a magic number, that corresponds to a magic letter, that corresponds to the peripheral you want to route there. And yeah it is quite obscure. Check the part called I/O multiplexing in the manual and see which magic numbers and letters you need. – Lundin May 27 '20 at 09:11
  • If you are using ASF bloatware libs, then something along the lines of `PORT->Group[0].PMUX[PIN_PA22/2].reg = PORT_PMUX_PMUXE(x)`, where x is the magic number from the routing table in the manual. – Lundin May 27 '20 at 09:14
  • Forgot to mention it but I allready did it (not sure if correctly thou): ```PORT->Group[0].PINCFG->reg |= (1 << PIN_PA22) | (1 << PIN_PA23); PORT->Group[0].PMUX->bit.PMUXE = (0x5 << (PIN_PA22/2)); PORT->Group[0].PMUX->bit.PMUXO = (0x5 << (PIN_PA23/2 + 1)); ``` – EFK May 27 '20 at 09:49
  • Looks weird. `PORT->Group[0].PMUX->bit.PMUXE` is the upper nibble of 4 bits in a 8 bit register, `PMUXO` is the lower nibble. So why the strange shift by `PIN_PA22/2`, shouldn't it just be a shift by 4 in the `PMUXE` case and no shift at all in the `PMUXO` case? If the magic number 5 is the one you need to write. That shift is what the bloatware macros `PORT_PMUX_PMUXE` and `PORT_PMUX_PMUXO` do. Shift by 4 or 0 respectively. – Lundin May 27 '20 at 10:21
  • 1
    (And yeah, this register hardware layout deserves some manner of worst design award) – Lundin May 27 '20 at 10:25

2 Answers2

0

NOTE: PORT->Group[0].PMUX is an array! Which decays into a pointer to the first element if you do PMUX->bit, so it compiles, but it is wrong. You are setting the first element instead of the pin PIN_PA22/2 that you are interested in.

The routing PMUX register type in the ASF register map looks like this:

typedef union {
  struct {
    uint8_t  PMUXE:4;          /*!< bit:  0.. 3  Peripheral Multiplexing for Even-Numbered Pin */
    uint8_t  PMUXO:4;          /*!< bit:  4.. 7  Peripheral Multiplexing for Odd-Numbered Pin */
  } bit;                       /*!< Structure used for bit  access                  */
  uint8_t reg;                 /*!< Type      used for register access              */
} PORT_PMUX_Type;

Meaning you can either write to the 8 bit reg or the 4 bit bit nibbles. You seem to do the latter. If 0x05 is the "magic number" obtained by the manual, you should write this one both to the "even" and the "odd" nibble. That is:

PORT->Group[0].PMUX[PIN_PA22/2].bit.PMUXE = (0x5 << 4);
PORT->Group[0].PMUX[PIN_PA22/2].bit.PMUXO = (0x5 << 0);

Or if you will, you can alternatively use pointless ASF bloatware macros to hide away "scary" bitwise logic:

PORT->Group[0].PMUX[PIN_PA22/2].reg = PORT_PMUX_PMUXE(0x5) | PORT_PMUX_PMUXO(0x5);

If you don't get these right, there will be no activity on the pin at all.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Btw type punning through unions is not allowed in C++, so you can't use this register map reliably when compiling as C++. I'd advise switching to C for hardware-related programming, for this reason alone any many other reasons as well. – Lundin May 27 '20 at 10:43
  • What do you mean by type punning through unions? Sry for newbie question – EFK May 27 '20 at 14:19
  • @EFK It means that C allows you to write to one member of the union like `bit.PMUXE` and then later access another member of the same union like `reg`. This isn't allowed in C++, it is undefined behavior there. Lots of PC programmers carry weight in the C++ committee and so that language is turning increasingly useless for embedded systems with each revision of the standard... – Lundin May 27 '20 at 15:00
0

There is a PWM library you can re-use code from; it comes with a table (under "extras") with timers, output pins, output channels, pin multiplexers etc.

The library was written (by me) for SAMD21G-based Arduinos, but all the mappings and code you need are there, and it may help you in your efforts.

ocrdu
  • 2,172
  • 6
  • 15
  • 22