10-bitové PWM na ATtiny85

Zápisník experimentátora

Hierarchy: ATtiny85

Mikrokontrolér ATtiny85 má dva 8-bitové timery. Pomocou nich je možné ovládať stmievanie LED diód. 8 bitov je ale málo v prípade, že chcete mať zmenu jasu prispôsobenú ľudskému oku. Gama korekcia spôsobuje viditeľné skoky v zmene jasu, najmä pri nízkej hodnote jasu. Mikrokontrolér ATtiny85 umožňuje pomocou timera 1 vytvoriť aj lepšie rozlíšenie. V tomto článku si ukážeme, ako sa to robí.

8-bitové stmievanie

Na týchto stránkach nájdete niekoľko príkladov LED stmievačov, ktoré som vytvoril. Všetky ale trpia rovnakou vadou. Pri malom jase je viditeľná skokovitá zmena. Príklady si môžete pozrieť aj s videami, na ktorých to veľmi dobre vidno.

16-bitové stmievanie

Pokiaľ máte mikrokontrolér s 16-bitovým timerom, je stmievanie LED diódy jednoduchšie. Musíte si len nastaviť vhodný režim na timeri a mikrokontrolér sa o všetko sám postará. Príklad s vyššimi bitovými rozlíšeniami nájdete na týchto stránkach.

10-bitové PWM pomocou ATtiny85

Inšpiráciu pre tento kód som našiel na Internete v blogu 10 or 12-bit DAC from the ATtiny85. Autor tam podrobne popisuje techniku zvýšenia bitového rozlíšenia. Funguje to tak, že timer 1 má špecifické vlastnosti, ktoré mu umožňujú nastaviť ho aj na hodnoty HIGH a LOW. Pomocou prerušenia sa vo vhodných intervaloch nastavuje timer tak, že sa najprv nastaví v jednom cykle na HIGH a v nasledovnom cykle na 8-bitovú hodnotu PWM. Výsledkom je napríklad 512 hodnôt namiesto 256. V článku je vysvetlené 10-bitové a 12-bitové rozlíšenie, ktoré sa ale ničím podstatným nelíši od tohto algoritmu.

Podľa blogu som mierne upravil dva programy. V súboroch pwm10bit a pwm12bit nájdete moje príklady.

volatile int Dac = 0;
volatile int Cycle = 0;

// Overflow interrupt
ISR (TIMER1_OVF_vect) {
  static int remain;
  if (Cycle == 0)
    remain = Dac;
  if (remain >= 256) {
    OCR1A = 255; // high (Table 12-2)
    remain = remain - 256;
  }
  else {
    OCR1A = remain;
    remain = 0;
  }
  Cycle = (Cycle + 1) & 0x03;
}

void analogWrite10 (int value) {
  cli();
  Dac = value;
  sei();
}

void setup() {
  // Top value for high (Table 12-2)
  OCR1C = 255;
  // Timer/Counter1 doing PWM on OC1A (PB1)
  TCCR1 = 1 << PWM1A    // Pulse Width Modulator A Enable
          | 1 << COM1A0 // OC1x cleared on compare match. Set when TCNT1 = $00
          | 1 << CS10;  // PWM clock = CK
  TIMSK |= 1 << TOIE1; // Timer/Counter1 Overflow Interrupt Enable
  pinMode(1, OUTPUT);
}

void loop () {
  for (int i = 0; i < 100; i++) {
    analogWrite10(i);
    delay(10);
  }
}

Gama korekcia

Predchádzajúce príklady nepôsobia na ľudské oko prirodzene. Pri postupnom zvyšovaní striedy sa veľmi rýchlo zvyši jas diódy tak, že následné zmeny jasu už poriadne nevidíte. Preto je potrebné jas upraviť pomocou gama korekcie. O výpočte tabuľky hodnôt gama korekcie som napísal samostatný článok.

Obvykle sa používa vypočítaná tabuľka pre 256 8-bitových hodnôt. Takáto tabuľka nám ale pre viac bitov nestačí a preto je potrebné použiť napríklad 16-bitovú hodnotu a v tabuľke mať čo najviac hodnôt. Tu ale narážame na pamäťové limity mikrokontroléra ATtiny85. Keby sme chceli uložiť celú tabuľku pre 10-bitové PWM, potrebovali by sme 1024 x 2 bajtov, čo nám výrazne zmenší priestor pre samotný program. Preto je vhodné mať tabuľku menšiu a chýbajúce hodnoty lineárne interpolovať. Príklady nájdete v programoch gamma_255, gamma_65535 a gamma_interpolation.

Výsledný program

Keď všetky tieto informácie dáme dokopy, výsledkom bude program, ktorý je v súbore pwm10bithuman. Kombinuje predchádzajúci program s gama korekciou a výsledkom je pekný stmievač, ktorý nemá viditeľné skoky v jase ani pri nízkych hodnotách striedy PWM.

const uint16_t PROGMEM gamma_8b[] = {
  0,    0,    0,    0,    1,    1,    2,    3,    4,    6,    8,   10,   13,   16,   19,   24,
  28,   33,   39,   46,   53,   60,   69,   78,   88,   98,  110,  122,  135,  149,  164,  179,
  196,  214,  232,  252,  273,  295,  317,  341,  366,  393,  420,  449,  478,  510,  542,  575,
  610,  647,  684,  723,  764,  806,  849,  894,  940,  988, 1037, 1088, 1140, 1194, 1250, 1307,
  1366, 1427, 1489, 1553, 1619, 1686, 1756, 1827, 1900, 1975, 2051, 2130, 2210, 2293, 2377, 2463,
  2552, 2642, 2734, 2829, 2925, 3024, 3124, 3227, 3332, 3439, 3548, 3660, 3774, 3890, 4008, 4128,
  4251, 4376, 4504, 4634, 4766, 4901, 5038, 5177, 5319, 5464, 5611, 5760, 5912, 6067, 6224, 6384,
  6546, 6711, 6879, 7049, 7222, 7397, 7576, 7757, 7941, 8128, 8317, 8509, 8704, 8902, 9103, 9307,
  9514, 9723, 9936, 10151, 10370, 10591, 10816, 11043, 11274, 11507, 11744, 11984, 12227, 12473, 12722, 12975,
  13230, 13489, 13751, 14017, 14285, 14557, 14833, 15111, 15393, 15678, 15967, 16259, 16554, 16853, 17155, 17461,
  17770, 18083, 18399, 18719, 19042, 19369, 19700, 20034, 20372, 20713, 21058, 21407, 21759, 22115, 22475, 22838,
  23206, 23577, 23952, 24330, 24713, 25099, 25489, 25884, 26282, 26683, 27089, 27499, 27913, 28330, 28752, 29178,
  29608, 30041, 30479, 30921, 31367, 31818, 32272, 32730, 33193, 33660, 34131, 34606, 35085, 35569, 36057, 36549,
  37046, 37547, 38052, 38561, 39075, 39593, 40116, 40643, 41175, 41711, 42251, 42796, 43346, 43899, 44458, 45021,
  45588, 46161, 46737, 47319, 47905, 48495, 49091, 49691, 50295, 50905, 51519, 52138, 52761, 53390, 54023, 54661,
  55303, 55951, 56604, 57261, 57923, 58590, 59262, 59939, 60621, 61308, 62000, 62697, 63399, 64106, 64818, 65535
};

volatile int Dac = 0;
volatile int Cycle = 0;

// Overflow interrupt
ISR (TIMER1_OVF_vect) {
  static int remain;
  if (Cycle == 0)
    remain = Dac;
  if (remain >= 256) {
    OCR1A = 255; // high (Table 12-2)
    remain = remain - 256;
  }
  else {
    OCR1A = remain;
    remain = 0;
  }
  Cycle = (Cycle + 1) & 0x03;
}

void analogWrite10 (int value) {
  cli();
  Dac = value;
  sei();
}

void setup() {
  // Top value for high (Table 12-2)
  OCR1C = 255;
  // Timer/Counter1 doing PWM on OC1A (PB1)
  TCCR1 = 1 << PWM1A    // Pulse Width Modulator A Enable
          | 1 << COM1A0 // OC1x cleared on compare match. Set when TCNT1 = $00
          | 1 << CS10;  // PWM clock = CK
  TIMSK |= 1 << TOIE1; // Timer/Counter1 Overflow Interrupt Enable
  pinMode(1, OUTPUT);
}

void loop () {
  for (int i = 0; i < 1024; i++) {
    uint16_t y = pgm_read_word(&gamma_8b[i / 4]);
    uint16_t z1 = (i / 4 == 0) ? 0 : pgm_read_word(&gamma_8b[i / 4 - 1]);
    uint16_t z = (y - z1) / 4 * (i % 4 + 1) + z1;

    // without gamma correction
    //analogWrite10(i);

    // with gamma correction
    analogWrite10(z >> 6);

    delay(8000 / 1024);
  }
}

Video

Video s 10-bitovým PWM pre ATtiny85. Vo videu som sa snažil zachytiť najmä stmievanie pri nízkom jase, čo je presne to miesto, kde sú pri 8-bitovom rozlíšení viditeľné jasné skoky vďaka vplyvu gama korekcie. Tie dva bity navyše stačia na to, aby to už oko skoro nevnímalo.

Zdrojové kódy

Zdrojové kódy sa nachádzajú na serveri GitHub.



Video


16.12.2017


Menu