Ladenie obsahu registra z mikrokontroléra

Zápisník experimentátora

Aj ako užívatelia Arduina viete, že v mikrokontroléri sa nachádza veľa systémových registrov. Nastavením jednotlivých bitov v registri ovládate správanie rôznych periférií. Pred užívateľmi Arduina sú registre zabalené do funkcií. Pri hlbšom skúmaní mikrokontroléra sa ich nastavovaniu nevyhnete. Vtedy sa vám môže hodiť zobrazenie jednotlivých bitov tak, ako ich nájdete v datasheete mikrokontroléra.

Pripravujem článok o PWM režimoch mikrokontroléra v Arduine. Ten okrem bežného 8-bitového PWM vie na timeri 1 robiť 9, 10 až 16 bitové PWM. 8-bitové PWM si zapnete pomocou funkcie analogWrite. Tá interne nastaví potrebné registre tak, aby dosiahla požadovanú funkciu. Keď sa ponoríte do zdrojových kódov funkcie (1, 2), nájdete tam množstvo podmieneného kódu, ktorého zmysel nie je vždy úplne jasný. Je to tak preto, lebo vo funkcii sa zohľadňuje väčšie množstvo mikrokontrolérov, ktoré rovnakú funkciu nastavujú pomocou rôznych bitov v rôznych registroch. Aby bolo možné zobraziť nastavenie bitov tak, ako ich vidíte vypísané v datasheete, napísal som tento zdrojový kód.

Elektrický obvod je v tomto prípade jednoduchý. Potrebujete iba Arduino a 2 LED a 2 rezistory. Zapojte si ich na piny 9 a 11, aby sa nachádzali na timeri 1 a 2. Ja obvykle používam modré LED a rezistory 1k. To stačí na to, aby LED svietila, ale súčasne neoslepovala vaše oči.

Na nasledujúcom obrázku je ukážka z datasheetu mikrokontroléra ATmega328P v Arduine. Register TCCR1A umožňuje nastaviť niekoľko rôznych bitov, ktoré ovplyvňujú správanie timera. Aby sme nemuseli neustále kontrolovať, či sú bity nastavené správne, napísal som tento zdrojový kód, ktorý formátuje nastavenie bitov v registri a vypíše ich na sériový port tak, ako to vidíte v datasheete. Príklad výpisu nájdete na konci článku.

Program

Program sa skladá z dvoch súborov.

  • dump.h
  • arduino_pwm_modes01.ino

dump.h

V zdrojovom kóde vidíte triedu DumpRegister a niekoľko makier a funkcií, ktoré slúžia na zabezpečenie želanej funkcionality tak, aby sa programátor veľmi nenarobil. Trieda slúži na uloženie názvov registra a jednotlivých bitov. Trieda má v konštruktore veľa parametrov a na zjednodušenie kódu slúži makro DREG, ktoré robí magické operácie s premennými a mení ich na textové reťazce, ktoré vstupujú ako parametre do konštruktora. Na vypísanie aktuálneho nastavenia registra slúži kombinácia operátora << a ďalšie makro _().

#define RESERVED -1

class DumpRegister {
    int reg;
    const char *regname;
    int bits[8];
    const char *bitnames[8];

  public:
    DumpRegister(int _reg, const char *_regname,
                 int b7, int b6, int b5, int b4, int b3, int b2, int b1, int b0,
                 const char *bn7, const char *bn6, const char *bn5, const char *bn4,
                 const char *bn3, const char *bn2, const char *bn1, const char *bn0)
      : reg(_reg),
        regname(_regname)
    {
      bits[7] = b7; bitnames[7] = bn7;
      bits[6] = b6; bitnames[6] = bn6;
      bits[5] = b5; bitnames[5] = bn5;
      bits[4] = b4; bitnames[4] = bn4;
      bits[3] = b3; bitnames[3] = bn3;
      bits[2] = b2; bitnames[2] = bn2;
      bits[1] = b1; bitnames[1] = bn1;
      bits[0] = b0; bitnames[0] = bn0;
    }

    friend Print& operator << (Print &obj, DumpRegister &d);
};

Print& operator << (Print &obj, DumpRegister &d) {
  obj.print(d.regname);
  obj.print("=");
  int v = *(volatile uint8_t *)(d.reg);
  obj.print(v, BIN);
  obj.print(":");
  for (int i = 7; i >= 0; i--)
    if (d.bits[i] != -1 && v & (1 << i)) {
      obj.print(" ");
      obj.print(d.bitnames[i]);
    }
  obj.println("");
  return obj;
}

#define DREG(var, b7, b6, b5, b4, b3, b2, b1, b0) \
  DumpRegister dr_##var(&var, #var, b7, b6, b5, b4, b3, b2, b1, b0, #b7, #b6, #b5, #b4, #b3, #b2, #b1, #b0);

#define _(var) dr_##var

#define DUMPVAL(var)          \
  {                           \
    Serial.print(#var);       \
    Serial.print("=");        \
    Serial.println(var, BIN); \
  }

arduino_pwm_modes01.ino

V tomto zdrojovom kóde vidíte, ako sa má predchádzajúci zdrojový kód používať. Pomocou makra DREG si zadefinujeme podľa datasheetu jednotlivé registre a bity, ktoré nás zaujímajú. V tomto prípade sú to registre pre timer 1 a 2. Nastavíme im nejaké hodnoty a potom si ich môžeme vypísať na sériový port pomocou operátora << a makra _().

Takto to môžete urobiť pri ľubovoľnom registri a pohodlne zistiť nastavenie bitov, ktoré robí Arduino. Tak isto môžete zistiť, či vaše nastavenie bitov je v registri v takej podobe, ako ste si predstavovali. Logické operácie v registri už potrápili nejedného skúseného programátora a toto je forma ladenia, ktorá vám pri Arduine pomôže, pretože vo vývojovom prostredí Arduina nemáte k dispozícii debugger, ktorým by ste si nastavenie skontrolovali pomocou neho.

#include "dump.h"

DREG(TCCR2A, COM2A1, COM2A0, COM2B1, COM2B0, RESERVED, RESERVED, WGM21, WGM20)
DREG(TCCR2B, FOC2A, FOC2B, RESERVED, RESERVED, WGM22, CS22, CS21, CS20)
DREG(TCCR1A, COM1A1, COM1A0, COM1B1, COM1B0, RESERVED, RESERVED, WGM11, WGM10)
DREG(TCCR1B, FOC1A, FOC1B, RESERVED, RESERVED, WGM12, CS12, CS11, CS10)

void setup() {
  pinMode(11, OUTPUT); // TIMER2A
  pinMode(9, OUTPUT);  // TIMER1A
  analogWrite(11, 1);  // minimal PWM value
  analogWrite(9, 1);   // minimal PWM value

  Serial.begin(9600);
  Serial.println("PWM modes");

  // TIMER2
  Serial << _(TCCR2A);
  Serial << _(TCCR2B);
  DUMPVAL(OCR2A)
  DUMPVAL(OCR2B)
 
  // TIMER1
  Serial << _(TCCR1A);
  Serial << _(TCCR1B);
  DUMPVAL(OCR1A)
  DUMPVAL(OCR1B)
}

void loop() {
}

Zdrojový kód

Zdrojový kód sa nachádza na serveri GitHub.



Video


28.05.2020


Menu