16-bitové rozlišení PWM pro Arduino

Zápisník experimentátora

Hierarchy: Časovač (timer)

Arduino má implicitně všechny PWM kanály nastaveny na 8-bitové rozlišení. Toto není maximální rozlišení. Timer1 umí použít až 16bitové rozlišení. V tomto článku si ukážeme, jak to udělat.

Nákup součástek

Na fotografiích je použito Arduino Pro Mini. To vás nelimituje, můžete použít v podstatě libovolné Arduino.

  • Arduino Pro Mini {linkArduino} - Toto Arduino jsem použil.
  • Breadboard {linkBreadboard} - Sem jsem zastrčil všechny součástky.
  • Modrá LED dioda - Modrá barva je vhodná proto, že LED dioda má vysokou svítivost a dobře vidíte i jemné změny jasu.
  • Rezistor 1k {linkR} - Rezistor omezuje velikost proudu, který teče přes LED diodu. Protože používáme vysocesvítivou LED diodu, stačí nám proud pár miliampérů.

Zapojení je jednoduché. LED diodu připojíme na pin 9, protože na tomto pinu máme výstup z timer1. Připojíme ji pomocí rezistoru na GND výstup z Arduina. Mohli bychom použít i pin 10, který je připojen k témuž časovači. Musíme použít některý z těchto pinů, protože pouze timer1 má rozlišení 16 bitů.

PWM

Arduino Uno má tři časovače. Dají se používat na různé úkoly. Jedním z nich je generování PWM signálu. Princip je jednoduchý. Do časovače přichází hodinový signál. Ten je možné pomocí preddeličky snížit na požadovanou úroveň. V našem případě budeme generovat signál s velkým rozlišením a proto budeme používat hodinový signál, který je roven frekvenci hodin Arduina.

V časovači se nastaví vhodný​ mód (fast PWM, phase-correct pWM). V závislostí na zvoleném módu je třeba nastavit několik dalších registrů. Ale stále jsou nastaveny dvě hodnoty. První určuje, kdy se signál překlopí z hodnoty HIGH na LOW. A druhá určuje, kdy se to vrátí zpět. Vzájemný poměr mezi HIGH a LOW se nazývá střída a určuje například jas naší LED diody. Čím bude výstup déle ve stavu HIGH, tím jasněji bude LED dioda svítit.

Protože potřebujeme měnit i rozlišení, musíme si vybrat takový mód, který umožňuje upravovat i druhou hodnotu. Tato hodnota ovlivňuje frekvenci signálu. Máme na výběr dva módy a v našem případě budeme používat mód 14. Podrobnosti nastavení tohoto módu najdete v datasheetu mikrokontroléru ATmega328P v části timer1.

Příklady

Napsal jsem dva vzorové příklady. První nastavuje 16bitové rozlišení a nastavuje jas v nízkých hodnotách. Druhý příklad je interaktivní a můžete si sami nastavit rozlišení a jas.

16-bitové PWM

Funkce setupPWM16 nastavuje rozlišení PWM. Piny se nastaví jako výstup, nastaví se vhodný tvar PWM, mód a preddelička se nastaví na stejnou hodnotu, jakou má hodinový signál z Arduina. Podstatné je, jakou hodnotu nastavíte do registru ICR1. Timer bude počítat od nuly po vaši hodnotu a pak zase začne od nuly. Signál bude překlápět na této hodnotě a na hodnotě, kterou nastavíte do registru OCR1A. Čím bude hodnota v OCR1A nižší, tím bude LED dioda svítit méně. Ve funkci loop vidíte nastavování tohoto registru na prvních 2000 hodnot.

Záleží na vašem zraku, zda budete vidět při takovémto vysokém rozlišení blikání diody. Já ho například vidím, ale stačí snížit rozlišení na 15 bitů a moje oči přestanou blikání vnímat. To můžete udělat změnou hodnoty v registru ICR1. Lepší vysvětlení najdete v kódu druhého příkladu.

void setup() {
  Serial.begin(9600);
  setupPWM16();
}

uint16_t icr = 0xffff;

void loop() {
  Serial.println("*");
  for (uint16_t i = 0; i < 2000; i++)
  {
    analogWrite16(9, i);
    delay(2);
  }
}

void setupPWM16() {
  DDRB  |= _BV(PB1) | _BV(PB2);       /* set pins as outputs */
  TCCR1A = _BV(COM1A1) | _BV(COM1B1)  /* non-inverting PWM */
        | _BV(WGM11);                 /* mode 14: fast PWM, TOP=ICR1 */
  TCCR1B = _BV(WGM13) | _BV(WGM12)
        | _BV(CS10);                  /* prescaler 1 */
  ICR1 = icr;                         /* TOP counter value (freeing OCR1A*/
}

/* 16-bit version of analogWrite(). Works only on pins 9 and 10. */
void analogWrite16(uint8_t pin, uint16_t val)
{
  switch (pin) {
    case  9: OCR1A = val; break;
    case 10: OCR1B = val; break;
  }
}

Interaktivní PWM

Abyste si mohli lépe nastavovat rozlišení a střídu generovaného PWM signálu, můžete použít druhý příklad. Spusťte si monitor sériového portu. Zadáním písmene a-i nastavíte rozlišení. Nastavit můžete 8-16 bitů. Zadáním čísla od 0-65535 nastavíte střídu. Musíte ale vědět, že pokud máte rozlišení 8 bitů, maximální hodnota je 255. Při rozlišení 16 bitů je maximální hodnota 65535. Vyzkoušejte si různé kombinace nastavení a sledujte, co dokážete rozeznat svými očima. Každé oko je jiné a někdo vidí slabší jemné změny jasu a někdo vnímá blikání diody při nižším rozlišení, jako jiný člověk.

uint16_t icr = 0xffff;
String line;

void setup() {
  Serial.begin(9600);
  Serial.println("Arduino interactive 16-bit PWM");  
  Serial.println("");  
  Serial.println("Resolution: a-i (8-16 bit)");  
  Serial.println("Value: number 0-65535");  
  OCR1A=0;
  OCR1B=0;
  setupPWM16(16);
}

void loop() {
  if(Serial.available()>0) {
    line=Serial.readString();
    line.trim();
    if(line.length()) {
      Serial.print("Command:");
      Serial.println(line);
      if('a'<=line[0] && line[0]<='i') {
        int resolution = 8 + line[0] - 'a';
        Serial.print("Resolution:");
        Serial.println(resolution);
        setupPWM16(resolution);
      }
      else {
        uint16_t value = line.toInt();
        Serial.print("Value:");
        Serial.println(value);
        analogWrite16(9,value);
      }
    }
  }
}

void setupPWM16(int resolution) {
  switch(resolution) {
    case 16:icr=0xffff;break;
    case 15:icr=0x7fff;break;
    case 14:icr=0x3fff;break;
    case 13:icr=0x1fff;break;
    case 12:icr=0x0fff;break;
    case 11:icr=0x07ff;break;
    case 10:icr=0x03ff;break;
    case  9:icr=0x01ff;break;
    case  8:icr=0x00ff;break;
    default:icr=0x00ff;break;
  }
  DDRB  |= _BV(PB1) | _BV(PB2);       /* set pins as outputs */
  TCCR1A = _BV(COM1A1) | _BV(COM1B1)  /* non-inverting PWM */
        | _BV(WGM11);                 /* mode 14: fast PWM, TOP=ICR1 */
  TCCR1B = _BV(WGM13) | _BV(WGM12)
        | _BV(CS11);                  /* prescaler 1 */
  ICR1 = icr;                         /* TOP counter value (freeing OCR1A*/
  Serial.print("ICR1:");
  Serial.println(icr);
}

/* 16-bit version of analogWrite(). Works only on pins 9 and 10. */
void analogWrite16(uint8_t pin, uint16_t val)
{
  switch (pin) {
    case  9: OCR1A = val; break;
    case 10: OCR1B = val; break;
  }
}

Zdrojový kód

Zdrojový kód se nachází na serveru GitHub.


04.07.2019


Menu