MCUdude / MegaCoreX

An Arduino hardware package for ATmega4809, ATmega4808, ATmega3209, ATmega3208, ATmega1609, ATmega1608, ATmega809 and ATmega808
GNU Lesser General Public License v2.1
239 stars 47 forks source link

PWM resolution increase in hardware timer #182

Open ltwin8 opened 11 months ago

ltwin8 commented 11 months ago

could one increase PWM resolution to more than 8bit in megacorex on the 4809? "The TCAn.PER register defines the PWM resolution. The minimum resolution is 2 bits (TCA.PER = 0x0002), and the maximum resolution is 16 bits (TCA.PER = MAX-1)"

https://ww1.microchip.com/downloads/en/DeviceDoc/ATmega4808-4809-Data-Sheet-DS40002173A.pdf

page 191 to 194

SpenceKonde commented 11 months ago

Yeah - of course. This requires:

  1. Write TCA0.SPLIT.CTRLA = 0; - this stops the timer, allowing the next command to be executed.
  2. Write TCA0.SPLIT.CTRLESET = 0x0C; - this commands the timer to hard reset.
  3. Now the timer is back at the state it was in when the device was powered on (If MegaCoreX does millis the way DxCore and mTC does, this does not break millis. If it uses the implementation of millis from the stock core, millis will be completely hosed by this. You now write to it as TCA0.SINGLE.REGISTER.
  4. TCA0.SINGLE.PER = TOP-1
  5. create something like function like

    void setDutyCycle(unt8_t channel, uint16_t duty)  {
    if (channel > 2) return;
    if (duty == 0 || duty == 1) {
    PORT_t* prt = portToPortStruct(PORTMUX.TCAROUTEA);
    if (duty) {
      prt->OUTSET = 1 << channel;
    } else {
      prt->OUTCLR = 1 << channel;
    }
    } else {
    
    volatile uint16_t * t=&TCA0_SINGLE_CMP0BUF; //Using the buffering is more commonly the reason one does something like this than needing more resolution. 
    *(t+channel) = duty;
    } 

(the buffering bit: If you use PER/CMP0/1/2 registers they take effect immediately and can result in glitches. If you use the ones ending in BUF, they are only updated at the start of the cycle to prevent glitches

One challenge on MegaCoreX is that there's no function like TakeOverTCA0() on DxCore which tells the core to pretend that a timer doesn't exist, so there's no way to tell it to make analogWrite() treat it like it had no PWM, and hence not try to fiddle with the timer settings, nor make digitalWrite() to the pins where the PWM was originally located try to turn off PWM channels. This is fine if you haven't changed portmux. But if you have changed portmux, you have to use direct port access or fast I/O to access it on MCX

DxC and mTC do have takeOverTCA0 (and takeOverTCA1 where appropriate) so on those cores you can skip the first 3 steps and instead call takeOverTCA0() and analogWrite() and digitalWrite() will not do anything funky no matter what you have also done to the portmux.

I don't think MCX still uses the stock millis implementation. The stock implementation gives one extra PWM channel on some parts, at the cost reduced timekeeping accuracy, increased timekeeping overhead, and makes the dreaded reverse time travel in micros() (which has consequences only slightly less severe than reverse time travel in the physical realm) much harder to avoid. (basically, on my cores, the only time that TCBs are configured automatically to use the clock from a TCA is when they're in PWM mode, and PWM mode is not used for timekeeping - we instead set a TCB clocked from CLK_PER/2 in periodic interrupt mode and the period set so it overflows once per millisecond, and I think MCX adopted this method too. I hope so, at least.

ltwin8 commented 11 months ago

Sadly I am not that familiar with the Atmega4809 but do I understand you correctly?

i would like to output more than 10b PWM on PC0, PC1, PC2 and PC3

is that possible?

but if I also would like to use millis() it is not possible? And there is no function in MCX to utilize this feature?