So far i've been unsuccesful trying to get PWM to work the way i want it to.
The problem is my lack of coding skill
Now my code consists of a mix of ASF4 and bare metal.
I've also gained the power to summon hard faults on demand with code that checks out with 0 errors or warnings.
ASF4 isn't horribly bad it's just that there's soooo many layers to this onion.
Anyways i'll keep trying to get something, the goal is to set TC2 PWM output on PA10 with 1µs period.
Edit: through DMA of course.
After digging through i found the way to write to TC2 CC registers: TC2->COUNT32.CC[0].reg. It may not the the correct way but it's something.
I got it from this line here in hri_tc_d10.h:
static inline void hri_tccount32_write_CC_reg(const void *const hw, uint8_t index, hri_tccount32_cc_reg_t data)
{
TC_CRITICAL_SECTION_ENTER();
((Tc *)hw)->COUNT32.CC[index].reg = data;
TC_CRITICAL_SECTION_LEAVE();
}
Writing to CC[0] changes the period, but weirdly enough, writing to CC[1] does nothing, when it's supposed to change the PWM ( at least according to ASF4).
Also i just noticed that this is not my first time setting PWM like this. I've previously done something similar with STM32:
#define Set_PWM htim22.Instance->CCMR1
Edit: i'm still working on the ASF4 generated template because i can't set up TC2 on bare metal yet. Could it be that my initial settings in Start.Atmel are incorrect?
Also TC2->COUNT32.CCB[0].reg does not work and gives an error.
Also TC2->COUNT32.CCB[0].reg does not work and gives an error.
TCs don't have buffer registers, only TCCs do. And only TTCs can be used for this application.
How CC[ x] and PER registers affect the output depends on the mode the timer is configured for.
You need to read the datasheet to understand how all that works, you won't get far by poking random values into random registers.
I've also gained the power to summon hard faults on demand with code that checks out with 0 errors or warnings.
HFs are run-time errors, they can't be detected at compile-time, so compiler warnings are irrelevant here.
I've looked through the datasheet but i'm still struggling with TCC0, i guess i'm missing some details still.
As far as i can understand this piece of code right here:
void TCC_setup(void){
//HAL_GPIO_PWM_pmuxen(PORT_PMUX_PMUXE_F_Val);
// function F = WO[x]
// LED_data out on PA10
// PA10 = WO[2]
PM->APBCMASK.reg |= PM_APBCMASK_TCC0;
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(TCC0_GCLK_ID) | GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(0);
TCC0->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV1 | TCC_CTRLA_PRESCSYNC_PRESC;
TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;
TCC0->PER.reg = 7; // at 8mhz makes period 1mhz
TCC0->CC[0].reg = 2; // will generate ~375 µs pulse
TCC0->CTRLA.reg |= TCC_CTRLA_ENABLE;
}
Should work as long as i enable my PA10 as an output in F mode.
But i'm currently struggling with pin enabling, seems every library has it's own way of setting the pinmux etc.
So i'm still looking through the datasheet to see how to do it in bare metal.
Also i can't even get TCC0 PWM to work with Start.Atmel just to test it.
TC1 and TC2 work fine.
WO[2] corresponds to CC[2]. So you need to set it instead of CC[2] as the code does.
I completely missed that one, now it's all working
I thought i was in the clear to go and set up DMA but looks like that's not the case.
Writing to
TCC0->CC[2].reg works fine but writing to
TCC0->CCB[2].reg does nothing, same with TCC0->CCB[0].reg.
At least i can finally make my LED's glow white.
Edit: watch window shows that the value i write does appear in
TCC0->CCB[2].reg but pulse width does not change
You need to enable buffered mode. Again, read the datasheet.
Try as i might i can not get CCB[2] to transfer over to CC[2].
I've looked through the datasheet and the registers:
29.6.2.6 Double Buffering
The Pattern (PATT), Waveform (WAVE), Period (PER) and the Compare Channels (CCx) registers are all double
buffered. Each buffer register has a Buffer Valid (PATTBV, WAVEBV, PERBV or CCBVx) bit in the STATUS register,
which indicates that the buffer register contains a value that can be copied into the corresponding register. When
double buffering is enabled by writing a one to the Lock Update bit in the Control B Clear register (CTRLBCLR.LUPD)
and the PER and CCx are used for a compare operation, the Buffer Valid bit is set when data has been written to a
buffer register and cleared on an update condition.
Bit 1 – LUPD: Lock Update
This bit controls the update operation of the TCC buffered registers. When this bit is set, no update of the buffered registers is performed, even though an UPDATE condition has occurred. Locking the update ensures that all buffers registers are valid before an update is performed. This bit has no effect when input capture operation is enabled.
1: The CCBx, PERB, PGVB, PGEB, and SWAPBx buffer registers value are not copied into the corresponding CCx, PER, PGV, PGE and SWAPx registers.
0: The CCBx, PERB, PGVB, PGEB, and SWAPBx buffer registers value are copied into the corresponding CCx, PER, PGV, PGE and SWAPx registers on counter update condition.
My CTRLBSET.LUPD is all 0 and i can set LUPD to 1 with
TCC0->CTRLBSET.reg |= 0b10; and then reset it again with
TCC0->CTRLBCLR.reg |= 0b10;, i can see the changes in watch window.
My LUPD is 0 so double buffering is enabled but the value from CCB[2] still does not transfer to CC[2].
Am i missing something? TCC0 works if i set CC[2] manually.
Writing to:
TCC0->PERB.reg = 8; // set period
Sets STATUS register to 129, which is 10000001, meaning STATUS.PERBV register is 1.
But writing to:
TCC0->CCB[2].reg = 6; // set counter compare
Does not change the STATUS.CCBV2 register to 1, meaning no transfer. But why?
After the TCC0 is enabled STATUS register returns to 0, meaning that transfer from PERBV to PERB was sucessful, yet CCB is completely ignored.
Writing to:
TCC0->CCB[2].reg = 2;
Before TCC is enabled or after TCC is enabled makes no difference to STATUS.CCBV
STATUS.CCBV[2] is R/W but when writing to it like this:
TCC0->STATUS.reg |= (uint32_t)0b1 << 18; // CCVB[2]
Nothing happens
Edit: Writing 1 to STATUS.CCBV[2] clears it
The bit is
cleared by writing a one to the corresponding location or automatically cleared on an UPDATE condition.
Looking at CCBV bits while TCC is running is not very useful, since the bit is cleared on each cycle. You need to be looking at the actual TCC output or the corresponding CC values. The value may have been transferred by the time you are looking at it.
But PERB would also have been transferred to PER, so this is strange.
I don't have time to experiment right now, I'll try it later.
Also, there are SET and CLR set of registers specifically so that you would not need to to read-modify-write operation, so you need to use "TCC0->CTRLBSET.reg = 0b10;", not "TCC0->CTRLBSET.reg |= 0b10;"
Ah, I see, you are trying to write them while TCC is disabled, I have no idea how it will behave in this case. Butffer registers must be written when TCC is enabled and running.
Looking at CCBV bits while TCC is running is not very useful, since the bit is cleared on each cycle. You need to be looking at the actual TCC output or the corresponding CC values. The value may have been transferred by the time you are looking at it.
But PERB would also have been transferred to PER, so this is strange.
I don't have time to experiment right now, I'll try it later.
The values do not transfer, also i tried both before and after enabling TCC0 and nothing happens.
Does the actual output of the timer change? On the pin?
Reading back registers may not work due to synchronization or something like that.
Ah, I see, you are trying to write them while TCC is disabled, I have no idea how it will behave in this case. Butffer registers must be written when TCC is enabled and running.
Writing to TCC0->PERB (and PER) worked both when TCC was disabled and enabled, same with TCC->CC[2], which updated both when TCC0 was disabled and enabled.
But writing to TCC0->CCB[2] only updates the CCB[2] register, without ever enabling STATUS.CCBV[2] or transfering the value over to CC[2] no matter whether the TCC0 is enabled or not.
Does the actual output of the timer change? On the pin?
Reading back registers may not work due to synchronization or something like that.
Only writing directly to CC[2] works.
CCB can't be transferred to CC when the timer is not running. There can't be an UPDATE even if the timer is not running. Obviously writing the registers directly works regardless.
Also, what is your period at this time? If the period is not set, then there won't be an UPDATE event. For the NPWM mode UPDATE happens when the COUNT==PER. And if both COUNT and PER == 0, then there won't be an UPDATE event.
There has to be a catch somwhere.
According to:
Bits 19:16 – CCBVx [x=3..0]: Compare Channel x Buffer Valid
For a compare channel, the bit is set when a new value is written to the corresponding CCBx register. The bit is
cleared by writing a one to the corresponding location or automatically cleared on an UPDATE condition.
For a capture channel, the bit is set when a valid capture value is stored in the CCBx register. The bit is automatically cleared when the CCx register is read.
Clearly it does not.
Double buffering is enabled as evidenced by
TCC0->PERB.reg = 8; working and setting
STATUS.PERBV register to 1. This is further confirmed by CTRLBCLR.LUPD register being 0.
The only clue i could find so far is in this sentence here:
When
double buffering is enabled by writing a one to the Lock Update bit in the Control B Clear register (CTRLBCLR.LUPD)
and the PER and CCx are used for a compare operation, the Buffer Valid bit is set when data has been written to a
buffer register and cleared on an update condition.
Also the way this sentence is worded is hard to read for me so i'll try to break it down:
:IF writing a one to the Lock Update bit in the Control B Clear register (CTRLBCLR.LUPD)
and the PER and CCx are used for a compare operation
:THEN the Buffer Valid bit is set when data has been written to a
buffer register.
But CC stands for capture compare, how would PWM work if PER and CCx aren't used for compare op?
CCB can't be transferred to CC when the timer is not running. There can't be an UPDATE even if the timer is not running. Obviously writing the registers directly works regardless.
Also, what is your period at this time? If the period is not set, then there won't be an UPDATE event. For the NPWM mode UPDATE happens when the COUNT==PER. And if both COUNT and PER == 0, then there won't be an UPDATE event.
PER is set to 8 and at 8MHz that gives me a pretty good waveform.
Also writing to CCB[2] even when running does nothing and i'll get a screenshot of the watch window that will show just how BS this whole situation is.
Ok, I don't know what you are doing wrong. Here is the code that was tested on D10 Xmini:
//-----------------------------------------------------------------------------
void tcc_init(void)
{
HAL_GPIO_PWM_pmuxen(PORT_PMUX_PMUXE_F_Val);
PM->APBCMASK.reg |= PM_APBCMASK_TCC0;
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(TCC0_GCLK_ID) | GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(0);
TCC0->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV1 | TCC_CTRLA_PRESCSYNC_PRESC;
TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;
TCC0->PER.reg = 100;
TCC0->CC[2].reg = 50;
TCC0->CTRLA.reg |= TCC_CTRLA_ENABLE;
}
//-----------------------------------------------------------------------------
void tcc_test(void)
{
// TCC0->CTRLBSET.reg = TCC_CTRLBSET_LUPD;
TCC0->PERB.reg = 50;
TCC0->CCB[2].reg = 10;
// TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD;
}
Initially frequency is higher and duty cycle is 50%. After I call tcc_test() from a button press, Frequency doubles and duty cycle changes to 20%.
The idea behind LUPD is to let you atomically update multiple values and have them applied at the same time. In the code LUPD access is commented out because you don't need it for things to work, but you need it if it was actual production code.
If you only update one register (as would be the case in your real application), then you don't need to worry about LUPD.
The code was tested observing the actual output. I don't know what happens to the registers, I don't have time to run the debugger.
Watching register is not a good indication of what actually happens, it may be IDE caching the values or something like this. Observe the actual PWM output on the pin.
I took screencaps of the code and the watch window step by step. if some of them appear to be at the same spot that's because i pressed break all after running the code to capture the watch window.
And it shows that PERB works and transfers to PER.
But not CCB to CC.
step6debug1_the_bullshit.JPG sums it up basically.
CCB[2] is 2
CC[2] is 6
CCBV[2] is 0
CCB[2] was set to 2 in the EXTIRQ that's tied to the button while the MCU was running and i pressed the button
twice for good measure. Yet CCBV[2] remains 0 even though CCB[2] and CC[2] do not match.
This MCU continues to be the bane of my existence.
Ok, I have no idea what you are doing, Once more time. It is not correct to write buffer values while timer is disabled. I have no idea what would happen if you do. One of them may transfer and the other may not. It is literally not predictable.
Also, debugging this step by step also makes no sense. All of this is very timing sensitive.
Here is a complete project I'm running. Observe PA10 before and after you press the button.