Ladění obsahu registru z mikrokontroléru

Zápisník experimentátora

I jako uživatelé Arduina víte, že v mikrokontroléru se nachází mnoho systémových registrů. Nastavením jednotlivých bitů v registru ovládáte chování různých periferií. Před uživateli Arduina jsou registry zabalené do funkcí. Při hlubším zkoumání mikrokontroléru se jejich nastavování nevyhnete. Tehdy se vám může hodit zobrazení jednotlivých bitů tak, jak je najdete v datasheetu mikrokontroléru.

Připravuji článek o PWM režimech mikrokontroléru v Arduinu. Ten kromě běžného 8-bitového PWM umí na timeru 1 dělat 9, 10 až 16 bitové PWM. 8-bitové PWM si zapnete pomocí funkce analogWrite. Ta interně nastaví potřebné registry tak, aby dosáhla požadovanou funkci. Když se ponoříte do zdrojových kódů funkce (1, 2), najdete tam množství podmíněného kódu, jehož smysl není vždy zcela jasný. Je to tak proto, že ve funkci se zohledňuje větší množství mikrokontrolérů, které stejnou funkci nastavují pomocí různých bitů v různých registrech. Aby bylo možné zobrazit nastavení bitů tak, jak je vidíte vypsané v datasheetu, napsal jsem tento zdrojový kód.

Elektrický obvod je v tomto případě jednoduchý. Potřebujete pouze Arduino a 2 LED a 2 rezistory. Zapojte si je na piny 9 a 11, aby se nacházely na timeru 1 a 2. Já obvykle používám modré LED a rezistory 1k. To stačí na to, aby LED svítila, ale současně neoslepovala vaše oči.

Na následujícím obrázku je ukázka z datasheetu mikrokontroléru ATmega328P v Arduinu. Registr TCCR1A umožňuje nastavit několik různých bitů, které ovlivňují chování timeru. Abychom nemuseli neustále kontrolovat, zda jsou bity nastaveny správně, napsal jsem tento zdrojový kód, který formátuje nastavení bitů v registru a vypíše je na sériový port tak, jak to vidíte v datasheetu. Příklad výpisu najdete na konci článku.

Program

Program se skládá ze dvou souborů.

  • dump.h
  • arduino_pwm_modes01.ino

dump.h

Ve zdrojovém kódu vidíte třídu DumpRegister a několik maker a funkcí, které slouží k zajištění požadované funkcionality tak, aby se programátor moc nenadělal. Třída slouží k uložení názvů registru a jednotlivých bitů. Třída má v konstruktoru mnoho parametrů a na zjednodušení kódu slouží makro DREG, které dělá magické operace s proměnnými a mění je na textové řetězce, které vstupují jako parametry do konstruktoru. Na vypsání aktuálního nastavení registru slouží kombinace operátoru << a další 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 zdrojovém kódu vidíte, jak se má předchozí zdrojový kód používat. Pomocí makra DREG si nadefinujeme podle datasheetu jednotlivé registry a bity, které nás zajímají. V tomto případě jsou to registry pro timer 1 a 2. Nastavíme jim nějaké hodnoty a pak si je můžeme vypsat na sériový port pomocí operátoru << a makra _().

Takto to můžete udělat při libovolném rejstříku a pohodlně zjistit nastavení bitů, které dělá Arduino. Stejně můžete zjistit, zda vaše nastavení bitů je v registru v takové podobě, jak jste si představovali. Logické operace v rejstříku již potrápili nejednoho zkušeného programátora a toto je forma ladění, která vám při Arduinu pomůže, protože ve vývojovém prostředí Arduina nemáte k dispozici debugger, kterým byste si nastavení zkontrolovali pomocí něj.

#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 se nachází na serveru GitHub.



Video


29.05.2020


Menu