10-bitové PWM na ATtiny85

Zápisník experimentátora

Hierarchy: ATtiny85

Mikrokontrolér ATtiny85 má dva 8bitové timery. Pomocí nich lze ovládat stmívání LED diod. 8 bitů je ale málo v případě, že chcete mít změnu jasu přizpůsobenou lidskému oku. Gama korekce způsobuje viditelné skoky ve změně jasu, zejména při nízké hodnotě jasu. Mikrokontrolér ATtiny85 umožňuje pomocí timeru 1 vytvořit i lepší rozlišení. V tomto článku si ukážeme, jak se to dělá.

8-bitové stmívání

Na těchto stránkách najdete několik příkladů LED stmívačů, které jsem vytvořil. Všechny ale trpí stejnou vadou. Při malém jasu je viditelná skokově změna. Příklady si můžete prohlédnout i s videi, na kterých to velmi dobře vidět.

16-bitové stmívání

Pokud máte mikrokontrolér s 16-bitovým timerem, je stmívání LED diody jednodušší. Musíte si jen nastavit vhodný režim na timeru a mikrokontrolér se o vše sám postará. Příklad s vyššími bitovými rozlišeními najdete na těchto stránkách.

10-bitové PWM pomocí ATtiny85

Inspiraci pro tento kód jsem našel na Internetu v blogu 10 or 12-bit DAC from the ATtiny85. Autor tam podrobně popisuje techniku zvýšení bitového rozlišení. Funguje to tak, že timer 1 má specifické vlastnosti, které mu umožňují nastavit ho i na hodnoty HIGH a LOW. Pomocí přerušení se ve vhodných intervalech nastavuje timer tak, že se nejprve nastaví v jednom cyklu na HIGH a v následujícím cyklu na 8-bitovou hodnotu PWM. Výsledkem je například 512 hodnot namísto 256. V článku je vysvětleno 10-bitové a 12-bitové rozlišení, které se ale ničím podstatným neliší od tohoto algoritmu.

Podle blogu jsem mírně upravil dva programy. V souborech pwm10bit a pwm12bit najdete mé pří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 korekce

Předcházející příklady nepůsobí na lidské oko přirozeně. Při postupném zvyšování střídy se velmi rychle zvyšuje jas diody tak, že následné změny jasu už pořádně nevidíte. Proto je třeba jas upravit pomocí gama korekce. O výpočtu tabulky hodnot gama korekce jsem napsal samostatný článek.

Obvykle se používá vypočtená tabulka pro 256 8-bitových hodnot. Taková tabulka nám ale pro více bitů nestačí a proto je třeba použít například 16-bitovou hodnotu a v tabulce mít co nejvíce hodnot. Zde ale narážíme na paměťové limity mikrokontroléru ATtiny85. Kdybychom chtěli uložit celou tabulku pro 10-bitové PWM, potřebovali bychom 1024 x 2 bajtů, což nám výrazně zmenší prostor pro samotný program. Proto je vhodné mít tabulku menší a chybějící hodnoty lineární interpolovat. Příklady najdete v programech gamma_255, gamma_65535 a gamma_interpolation.

Výsledný program

Když všechny tyto informace dáme dohromady, výsledkem bude program, který je v souboru pwm10bithuman. Kombinuje předchozí program s gama korekcí a výsledkem je pěkný stmívač, který nemá viditelné skoky v jasu ani při nízkých hodnotách střídy 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 pro ATtiny85. Ve videu jsem se snažil zachytit zejména stmívání při nízkém jasu, což je přesně to místo, kde jsou při 8-bitovém rozlišení viditelné jasné skoky díky vlivu gama korekce. Ty dva bity navíc stačí na to, aby to už oko skoro nevnímalo.

Zdrojové kódy

Zdrojové kódy se nacházejí na serveru GitHub.



Video


02.01.2018


Menu