SNSへはこちら

ESP8266でベアメタル(4) - 出来る範囲でのレジスタ手打ち・今後の方針

SDK からの卒業をして、本記事では出来る範囲でレジスタ手打ちをしていきたいと思います。

「出来る範囲」とは

結論から言います。この ESP8266、メーカーから出ているリファレンス等を見ても、特徴的な大半の機能は SDK 無しで実現することはできません。絶望ですよ、まったく。

この記事後半ではやっぱり SDK 使うわwという内容になります。断腸の思いですが、読者の皆様はどうか落胆なさらないでください。これは仕方のないことなのです......

レジスタ手打ちのプログラム

まずレジスタ定義を regs.h に書きました。以下のプログラムではこれを使っています。

// regs.h
#define GPIO_BASE 0x60000300
#define GPIO_OUT_OFFSET 0
#define GPIO_ENABLE_OFFSET 3
#define GPIO_IN_OFFSET 6
#define GPIO(reg) (*(volatile unsigned int *)(GPIO_BASE + 4 * GPIO_##reg##_OFFSET))

#define UART_BASE 0x60000000
#define UART_FIFO_OFFSET 0
#define UART_INT_RAW_OFFSET 4
#define UART_INT_ST_OFFSET 8
#define UART_INT_ENA_OFFSET 12
#define UART_INT_CLR_OFFSET 16
#define UART_CLKDIV_OFFSET 20
#define UART_STATUS_OFFSET 28
#define UART_CONF0_OFFSET 32
#define UART_CONF1_OFFSET 36
#define UART(reg) (*(volatile unsigned int *)(UART_BASE + UART_##reg##_OFFSET))

#define IOMUX_BASE 0x60000804
#define IOMUX(n) (*(volatile unsigned int *)(IOMUX_BASE + 4 * (n)))

#define FRC1_BASE 0x60000600
#define FRC1_LOAD_OFFSET 0
#define FRC1_COUNT_OFFSET 4
#define FRC1_CTRL_OFFSET 8
#define FRC1_INT_OFFSET 12
#define FRC1(reg) (*(volatile unsigned int *)(FRC1_BASE + FRC1_##reg##_OFFSET))

今回は SPI は面倒だったので実装していません。よって、

  • タイマ
  • UART

のみのコードとなりますので、ご了承を。それと、クロックツリー等も全く説明されていないのでよく分かりませんが、どうやらペリフェラルクロックは 52MHz であるようです。マジで仕様が何もわからんなこのマイコン。

タイマーで割り込みを使わずLチカ

割り込みが使えません(詳細は記事下部に記載)。なのでポーリングで待ちをするのみになります。

#include <regs.h>

void timer_init(void) {
    FRC1(LOAD) = 26e6 / 256 / 10 * 2 - 1;
    FRC1(CTRL) = (1 << 0) | (3 << 2) | (1 << 6); // interrupt by level, preslaced by 256, auto-reload
    FRC1(INT) = 1;
    FRC1(CTRL) |= 1 << 7; // timer enabled
}

int main(void) {
    timer_init();
    GPIO(ENABLE) = 1 << 4;
    while(1) {
        GPIO(OUT) ^= 1 << 4;
        while( !(FRC1(CTRL) & (1 << 8)) );
        FRC1(INT) = 1; // clear flag
    }
}

UART

115200bps です。エコーバック。

#include <regs.h>

void uart_init(void) {
    UART(CONF0) = (3 << 2) | (1 << 4); // 8bit, 1stop bit
    UART(CLKDIV) = 52e6 / 115200;
    UART(CONF0) |= (1 << 17) | (1 << 18); // reset TX/RX FIFO
    UART(CONF0) &= ~((1 << 17) | (1 << 18)); // reset TX/RX FIFO

    UART(INT_ENA) = 1 << 1;

    IOMUX(5) = 0; // GPIO1: TXD
    IOMUX(13) = 0; // GPIO2: RXD
}

void uart_send(char ch) {
    while( !(UART(STATUS) & 0xFF00) );
    UART(FIFO) = ch;
}

char uart_get(void) {
    while( !(UART(STATUS) & 0x7F) );
    return UART(FIFO) & 0xFF;
}

int main(void) {
    uart_init();
    while(1) {
        uart_send(uart_get());
    }
}

以下お気持ち表明です。

Wi-Fi とかのレジスタはおろか...

このマイコン、なんとその他のレジスタマップが一切公開されていないのです。NonOS SDK のソースを見ても記載はなし。一方で SDK のバイナリには API 関数が実装されています。

...じゃあ逆アセンブルすれば...って思うじゃん??

残念ながら、ほぼ不可能に近いです。できたバイナリは生の bin ファイルであって、シンボル情報などはすべて消去されています。当然ながら関数名の情報も strings コマンドからダンプさせても見当たらないです。つまり、Espressif 社は、SDK の API 関数の大半を、ソースコード非公開状態でユーザーに提供しているのです。つまりこれは「レジスタ手打ちなんかしないで SDK 使えよ」と言っているようなものです。参りました

では、この SDK 仕様によってどれだけのことができないのでしょうか。現状分かっていることについて列挙すると下のような感じです。

  • システム周りのスリープ
  • システムウォッチドッグタイマの設定
  • Wi-Fi 周り全て
  • 割り込み関連全て

上2つは許しましょう。でもね、Wi-Fi もできないなんて許せないですよ。もっと酷いのは割り込み。これはすなわちレジスタ手打ちをするなら必ずポーリング(ブロッキング処理)でしか動作させることはできないということを意味します。これはかなり落ち込みました。

それでもこのマイコンをいじりたい

この願いを叶えるための選択肢はただ1つ、SDK に出戻りするしかありません。考えてみれば NonOS SDK 自体も結構レイヤが低いほうだと考えていて、まあここに甘んじるのは怒りを感じるほど不快なものではないですね。ただし Aruduino IDE、お前は許さねえぞ

ということで、今後は NonOS SDK を用いて開発をすることになります。以後よろしくおねがいします。