16-bitové rozlíšenie PWM pre Arduino

Zápisník experimentátora

Hierarchy: Časovač (timer)

Arduino má implicitne všetky PWM kanály nastavené na 8-bitové rozlíšenie. Toto nie je maximálne rozlíšenie. Timer1 vie použiť až 16-bitové rozlíšenie. V tomto článku si ukážeme, ako to urobiť.

Nákup súčiastok

Na fotografiách je použité Arduino Pro Mini. To vás nelimituje, môžete použiť v podstate ľubovoľné Arduino.

  • Arduino Pro Mini {linkArduino} - Toto Arduino som použil.
  • Breadboard {linkBreadboard} - Sem som zastrčil všetky súčiastky.
  • Modrá LED dióda - Modrá farba je vhodná preto, lebo LED dióda má vysokú svietivosť a dobre vidíte aj jemné zmeny jasu.
  • Rezistor 1k {linkR} - Rezistor obmedzuje veľkosť prúdu, ktorý tečie cez LED diódu. Pretože používame vysokosvietivú LED diódu, stačí nám prúd pár miliampérov.

Zapojenie je jednoduché. LED diódu pripojíme na pin 9, pretože na tomto pine máme výstup z timer1. Pripojíme ju pomocou rezistora na GND výstup z Arduina. Mohli by sme použiť aj pin 10, ktorý je pripojený k tomu istému časovaču. Musíme použiť niektorý z týchto pinov, pretože iba timer1 má rozlíšenie 16 bitov.

PWM

Arduino Uno má tri časovače. Dajú sa používať na rôzne úlohy. Jednou z nich je generovanie PWM signálu. Princíp je jednoduchý. Do časovača prichádza hodinový signál. Ten je možné pomocou preddeličky znížiť na požadovanú úroveň. V našom prípade budeme generovať signál s veľkým rozlíšením a preto budeme používať hodinový signál, ktorý je rovný frekvencii hodín Arduina.

V časovači sa nastaví vhodný mód (fast PWM, phase-correct pWM). V závislostí na zvolenom móde je potrebné nastaviť niekoľko ďalších registrov. Ale stále sú nastavené dve hodnoty. Prvá určuje,kedy sa signál preklopí z hodnoty HIGH na LOW. A druhá určuje, kedy sa to vráti naspäť. Vzájomný pomer medzi HIGH a LOW sa nazýva strieda a určuje napríklad jas našej LED diódy. Čím bude výstup dlhšie v stave HIGH, tým jasnejšie bude LED dióda svietiť.

Pretože potrebujeme meniť aj rozlíšenie, musíme si vybrať taký mód, ktorý umožňuje upravovať aj druhú hodnotu. Táto hodnota ovplyvňuje frekvenciu signálu. Máme na výber dva módy a v našom prípade budeme používať mód 14. Podrobnosti nastavenia tohto módu nájdete v datasheete mikrokontroléra ATmega328P v časti timer1.

Príklady

Napísal som dva vzorové príklady. Prvý nastavuje 16-bitové rozlíšenie a nastavuje jas v nízkych hodnotách. Druhý príklad je interaktívny a môžete si sami nastaviť rozlíšenie a jas.

16-bitové PWM

Funkcia setupPWM16 nastavuje rozlíšenie PWM. Piny sa nastavia ako výstup, nastaví sa vhodný tvar PWM, mód a preddelička sa nastaví na rovnakú hodnotu, ako má hodinový signál z Arduina. Podstatné je, akú hodnotu nastavíte do registra ICR1. Timer bude počítať od nuly po vašu hodnotu a potom zase začne od nuly. Signál sa bude preklápať na tejto hodnote a na hodnote, ktorú nastavíte do registra OCR1A. Čím bude hodnota v OCR1A nižšia, tým bude LED dióda svietiť menej. Vo funkcii loop vidíte nastavovanie tohto registra na prvých 2000 hodnôt.

Záleží na vašom zraku, či budete vidieť pri takomto vysokom rozlíšení blikanie diódy. Ja ho napríklad vidím, ale stačí znížiť rozlíšenie na 15 bitov a moje oči prestanú blikanie vnímať. To môžete urobiť zmenou hodnoty v registri ICR1. Lepšie vysvetlenie nájdete v kóde druhého prí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;
  }
}

Interaktívne PWM

Aby ste si mohli lepšie nastavovať rozlíšenie a striedu generovaného PWM signálu, môžete použiť druhý príklad. Spustite si monitor sériového portu. Zadaním písmena a-i nastavíte rozlíšenie. Nastaviť môžete 8-16 bitov. Zadaním čísla od 0-65535 nastavíte striedu. Musíte ale vedieť, že ak máte rozišenie 8 bitov, maximálna hodnota je 255. Pri rozlíšení 16 bitov je maximálna hodnota 65535. Vyskúšajte si rôzne kombinácie nastavenia a sledujte, čo dokážete rozoznať svojimi očami. Každé oko je iné a niekto vidí slabšie jemné zmeny jasu a niekto vníma blikanie diódy pri nižšom rozlíšení, ako iný človek.

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 sa nachádza na serveri GitHub.


29.07.2017


Menu