最終更新日

MAME ドライバーつくりかたメモ

はじめに

この文書は、Arduino を使った YM2151 の VGM Player ハードを mame でエミュレーションしてみようとドライバー(mame用語・基板固有のエミュモジュール)を追加している時に作成した雑多なメモです。

2021/03 注) MAME のエミュレーションフレームワークやサウンドシステムが刷新されたため、記事中のソースコードは現在バージョンの MAME API になっていません。またドライバーのディレクトリ構成も変わっているのでご注意ください。

エミュレーション対象

この文書でサンプルとしているハードは、Arduino マイコンから FM 音源を制御し VGM 形式となっているパッヘルベルのカノンを奏でる機械なので、mame 上では vgmduinokanonというドライバー名称にしました。

エミュレーション対象ハード

ちなみに本気でドライバーを書く場合は、 Arduino + FM音源シールドのエミュレーターということになりますので、本来的にはシリアルUSB変換の ATmega16U2 なども接続することになると思います。

  • TODO:
    • 例えば arduino ドライバーをかいて、FM音源シールドドライバーをそれを extends するような形でつくれるか調査。
    • ATmega に実装されたペリフェラル(SPI, I2C, USART, WatchDog, etc...)の処理がどれくらい mame に既に載っているか要調査。
    • mame に仮想シリアルポートがつくれるのか調査(実 MIDI デバイスは接続可能なのを確認済み)。可能でれば mame 上の Arduino にプログラマーで書き込みできるかも?

WORK IN PROGRESS

オペコードは正しく動作し YM2151 が発声しているものの、TIMER0 が未実装のため正しく楽曲になっていません。 TIMER0 を hack で実装してみたところ、正しく micros() 関数が動作するようになり発声の音長が反映され、正確ではないものの楽曲として再生できるようになりました。

作業中のソースコードなど一式

ビルドと実行

ビルド環境の準備

  • コンパイラーなどの導入方法が、公式ドキュメントに詳しく書いてある。
    • Compiling MAME
    • ここでは普段使っている Ubuntu 19.10 を使用。
    • macOS でも動作することを確認。

mame のソースコードを clone

git clone https://github.com/mamedev/mame.git
cd mame

ROM を配置

  • ドライバーで使う ROM(Arduino で動作させているバイナリー)の crc32 と sha1 を取得しておく(ここでは vgmduino.bin だった例)
  • この文書で使っている Arduino プログラムとブートローダーのバイナリー。
# sha1 は Ubuntu に入ってる
sha1sum vgmduino.bin
4a74f844114cc5dd5722f98669542a9d53a91c2d vgmduino.bin

# crc32 は標準でないため apt で Perl のライブラリーのを拝借
apt-get install libarchive-zip-perl
crc32 vgmduino.bin
9fe6d5d0 vgmduino.bin
  • roms ディレクトリにドライバー名.zip(ここでは kanon.zip) で ROM バイナリーファイルを圧縮して mame ソースコードの roms ディレクトリーに格納しておく。
    • zip 内にはディレクトリをつくらないこと。
  • 後で気がついたが、mame は誤ったハッシュ値設定でもワーニング出力のみで、おまけに正しいサムを出してくれるので、そのログからハッシュ値を取得して設定してもいいかも。

ドライバーファイルの追加

  • src/mame/drivers に .cpp ファイルを追加(ここでは vgmduino.cpp の例) - ソースの全体像は後述。
  • vgmduino.cpp で ROM_START を設定(ここでは kanon ドライバー)
    • ROM_REGION で容量(0x8000)と名前("maincpu") をかいて開始のサイン。
    • その後続で開始、長さ形式でバイナリーを配置していく。
    • 最後にオリーブオイルをかけていく。
ROM_START( kanon )
    ROM_REGION( 0x8000, "maincpu", 0 )
    /* Arduino UNO user program */
    ROM_LOAD("vgmduino.bin", 0x0000, 0x7e00, CRC(9fe6d5d0) SHA1(4a74f844114cc5dd5722f98669542a9d53a91c2d) )
    /* Arduino UNO bootloader 0x7e00 */
    ROM_LOAD( "optiboot_atmega328.bin", 0x7e00, 0x200, CRC(388b1a0e) SHA1(529a4a966913261f0bc467ef80424bb74bd2cc03) )
    /* on-die 1kbyte eeprom */
    ROM_REGION( 0x400, "eeprom", ROMREGION_ERASEFF )
ROM_END

src/mame/mame.lstvgmduino.cpp を追加

@source:vgmduino.cpp
kanon                           // vgmplayer

arcade.lua にソースを追加(micsのところでよい?) TODO: Makefile を調査

--------------------------------------------------
-- remaining drivers
--------------------------------------------------

createMAMEProjects(_target, _subtarget, "misc")
files {
    MAME_DIR .. "src/mame/drivers/vgmduino.cpp",
}

arcade.flt にソースを追加(必要?)TODO: Makefile を調査。

vgmduino.cpp

ドライバービルド

  • 初回は 10年前の Intel(R) Core(TM) i5-2520M CPU @ 2.50GHz で 20分くらい。
  • 初回後は 10秒くらい。
# j の値は CPU のコア数 + 1 が最速コンパイル。しかし全部もってかれるのでこの機械(コア4)では 3 に設定。
# SUBTARGET を指定するとそのドライバーだけビルドできる。
make -j3 SUBTARGET=kanon SOURCES=src/mame/drivers/vgmduino.cpp

ドライバーにデバイス(YM2151)などを追加した場合、いったんクリーンしないとリンクでエラーとなることがある。

make clean

起動方法

# サブターゲット付きでビルドした場合、ドライバー名 + プラットフォーム名(64bit)の実行ファイルがつくられる。
./kanon64
# ウインドウ起動
./kanon64 -window -resolution 1024x768
# デバッガー付き起動(エミュレーション CPU の最初のステップで停止する)
./kanon64 -debug -window -resolution 1024x768
# ランチャーが不要の場合はドライバー名を引数に指定すると一気に起動
./kanon64 kanon -window  -resolution 1024x768 -debug

デバッグシンボル付きのコンパイル

ドライバー(mame)自体を gdb ステップ実行する場合はSYMBOLSオプションでソースマップを付与してビルド。なお、処理速度がかなり遅くなるのでリアルタイム処理の場合は注意。

# mame 全体にシンボルつけるためいったん clean
make clean
# OPTIMIZE=0 SYMBOLS=1 つけてコンパイル
make -j3 OPTIMIZE=0 SYMBOLS=1 SUBTARGET=kanon SOURCES=src/mame/drivers/vgmduino.cpp

clang でのコンパイル(vgmplay の例)

make -j6 SUBTARGET=vgmplay SOURCES=src/mame/drivers/vgmplay.cpp OVERRIDE_CC=clang OVERRIDE_CXX=clang++

VSCode 設定

インテリセンス .vscode/c_cpp_properties.json

  • includePath${workspaceFolder}/src/** に設定。またビルド時にジェネレートされるソースコードも ${workspaceFolder}/build/generated/** として追加。
  • #ifdef などがうまく処理できるように defines でプラットフォームに合わせた SDLMAME_UNIX=1 などに設定。
{
    "configurations": [
        {
            "name": "Linux",
            "intelliSenseMode": "clang-x64",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "includePath": [
                "${workspaceFolder}/src/**",
                "${workspaceFolder}/3rdparty/**",
                "${workspaceFolder}/build/generated/**"
            ],
            "defines": [
                "SDLMAME_UNIX=1"
            ],
            "compilerPath": "/usr/bin/clang"
        }
    ],
    "version": 4
}

gdb デバッグする場合の .vscode/launch.json

programargs セクションに対象の実行ファイル名と引数を設定。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "mame 起動 (GDB debug)",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/kanon64",
            "args": [
                "kanon",
                "-window",
                "-resolution",
                "1280x768"
            ],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "gdb の再フォーマットを有効にする",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        },
        {
            "name": "mame 起動 (LLDB debug)",
            "type": "lldb",
            "request": "launch",
            "program": "${workspaceFolder}/kanon64",
            "args": [
                "kanon",
                "-window",
                "-resolution",
                "1280x768"
            ],
            "cwd": "${workspaceFolder}",
            "stopOnEntry": false
        }
    ]
}

ドライバーソースコード全体像

デバイス定義

  • AVR8 CPU、YM2151 音源、ステレオ speaker を使う例。
  • コンストラクター設定と private メンバー required_device で使うデバイスを指定する。
    • この際に定義順番がおかしいとコンパイルエラーになった記憶がなぜかあるので一応注意。TODO: 要調査
    • 以下の例はデバイス定義のみで AVR と YM2151 が接続されていない(のでまだ YM2151 は鳴らない)
    • ステレオスピーカーと YM2151 は接続済み。
  • このドライバーは小さいのでとりあえずヘッダーを使ってないが、使う場合は #include "includes/vgmduino.h" として配置。
  • なお、以下の例は mame のオリジナルソースに含まれていない(自分で追加した) ATMEGA328 を使っているのでそのままコンパイルできない。
    • ATMEGA644 は含まれるので ATMEGA328(ATMEGA644( に修正すればコンパイル自体は通るはず。
    • ただし以下のソースは ATMEGA328 用のメモリーマップになっている。
    • mame にマージしていただいた
#include "emu.h"
#include "cpu/avr8/avr8.h"
#include "sound/ym2151.h"
#include "speaker.h"

class vgmduino_state : public driver_device
{
public:
    vgmduino_state(const machine_config &mconfig, device_type type, const char *tag)
        : driver_device(mconfig, type, tag)
        , m_maincpu(*this, "maincpu")
        , m_lspeaker(*this, "lspeaker")
        , m_rspeaker(*this, "rspeaker")
        , m_ym2151(*this, "ym2151")
    {
    }

    void vgmduino(machine_config &config);

    void init_vgmduino();

private:
    required_device<avr8_device> m_maincpu;
    required_device<speaker_device> m_lspeaker;
    required_device<speaker_device> m_rspeaker;
    required_device<ym2151_device> m_ym2151;

    virtual void machine_start() override;
    virtual void machine_reset() override;

    void vgmduino_data_map(address_map &map);
    void vgmduino_io_map(address_map &map);
    void vgmduino_prg_map(address_map &map);
};

void vgmduino_state::machine_start()
{
}

uint8_t vgmduino_state::port_r(offs_t offset)
{
}

void vgmduino_state::port_w(offs_t offset, uint8_t data)
{
}

void vgmduino_state::vgmduino_prg_map(address_map &map)
{
    /* ATmega328 32KB(0x0 - 0x7fff) internal flash */
    map(0x0000, 0x7fff).rom();
}

void vgmduino_state::vgmduino_data_map(address_map &map)
{
    /* ATmega328 2KB SRAM */
    map(0x0100, 0x08ff).ram();
}

void vgmduino_state::vgmduino_io_map(address_map &map)
{
    /* ATmega328 PORTA-PORTD (PORTA not exist) */
    map(AVR8_IO_PORTA, AVR8_IO_PORTD).rw(FUNC(vgmduino_state::port_r), FUNC(vgmduino_state::port_w));
}

void vgmduino_state::init_vgmduino()
{
}

void vgmduino_state::machine_reset()
{
}

void vgmduino_state::vgmduino(machine_config &config)
{
    /* ATmega328 16MHz clock */
    ATMEGA328(config, m_maincpu, XTAL(16'000'000));
    m_maincpu->set_addrmap(AS_PROGRAM, &vgmduino_state::vgmduino_prg_map);
    m_maincpu->set_addrmap(AS_DATA, &vgmduino_state::vgmduino_data_map);
    m_maincpu->set_addrmap(AS_IO, &vgmduino_state::vgmduino_io_map);

    /* ATmega328 EEPROM */
    m_maincpu->set_eeprom_tag("eeprom");

    /* Arduino UNO setting */
    m_maincpu->set_low_fuses(0xff);
    m_maincpu->set_high_fuses(0xde);
    m_maincpu->set_extended_fuses(0xfd);
    m_maincpu->set_lock_bits(0x0f);

    /* speaker */
    SPEAKER(config, m_lspeaker).front_left();
    SPEAKER(config, m_rspeaker).front_right();

    /* YM2151 3.579545MHz clock */
    YM2151(config, m_ym2151, XTAL(3'579'545));
    m_ym2151->add_route(0, m_lspeaker, 1);
    m_ym2151->add_route(1, m_rspeaker, 1);
}

ROM_START( kanon )
    ROM_REGION( 0x8000, "maincpu", 0 )
    /* Arduino UNO user program */
    ROM_LOAD("vgmduino.bin", 0x0000, 0x7e00, CRC(9fe6d5d0) SHA1(4a74f844114cc5dd5722f98669542a9d53a91c2d) )
    /* Arduino UNO bootloader 0x7e00 */
    ROM_LOAD( "optiboot_atmega328.bin", 0x7e00, 0x200, CRC(388b1a0e) SHA1(529a4a966913261f0bc467ef80424bb74bd2cc03) )
    /* on-die 1kbyte eeprom */
    ROM_REGION( 0x400, "eeprom", ROMREGION_ERASEFF )
ROM_END

//   YEAR  NAME      PARENT  COMPAT  MACHINE    INPUT  CLASS           INIT           COMPANY     FULLNAME    FLAGS
COMP(2020, kanon,    0,      0,      vgmduino,  0,     vgmduino_state, init_vgmduino, "vgmduino", "vgmduino", MACHINE_NOT_WORKING | MACHINE_NO_SOUND)

I/O レジスタマッピング

  • AVR と YM2151 を接続するため AVR の I/O レジスタの変化をドライバーで検出。
    • この方法で動作はするが、ドライバー内でバスを実装していいものか要調査。
    • とりあえずこれで進める。
    • output_latch.cppで YM のバスを設定するとよさそう。
    • mame 内で YM2151 を使う既存ドライバーは、Z80 と YM2151 をメモリーマップドI/O で繋ぐハードが多いためバスは実装する必要がないということのようだ。
  • AVR の I/O レジスタのアクセスを次のようにマッピングし、ドライバーでフック。
    • Arduino UNO は PORTB-PORTD で PORTA は存在しないが、AVR のアドレスとしてはあるので PORTA-PORTD の範囲で定義(そうしないとズレる)
void vgmduino_state::vgmduino(machine_config &config)
{
    ...
    m_maincpu->set_addrmap(AS_IO, &vgmduino_state::vgmduino_io_map);
    ...
}

void vgmduino_state::vgmduino_io_map(address_map &map)
{
    /* ATmega328 PORTA-PORTD (PORTA not exist) */
    map(AVR8_IO_PORTA, AVR8_IO_PORTD).rw(FUNC(vgmduino_state::port_r), FUNC(vgmduino_state::port_w));
}
  • 各レジスタの値を保持するため(変化を検知するため) private メンバーを新設。
    • この private メンバーを save state 対応に設定するためのコードをどこかのドライバーで見た。→ save_item(NAME(m_bits)); のような形で実装する。
    • output_latch.cppsave_item に対応しているのでこれを使うのが適切そう(TODO:)
class vgmduino_state : public driver_device
{
private:
    /* ATmega328 PORTA-PORTD (PORTA not exist) */
    uint8_t m_port_a;
    uint8_t m_port_b;
    uint8_t m_port_c;
    uint8_t m_port_d;
    ...
}

vgmduino_state::machine_reset() で初期化

void vgmduino_state::machine_reset()
{
    m_port_a = 0;
    m_port_b = 0;
    m_port_c = 0;
    m_port_d = 0;
}

I/O レジスターリードで呼ばれる READ8_MEMBER(vgmduino_state::port_r) ではその時点の state を単純に返却。

uint8_t vgmduino_state::port_r(offs_t offset)
{
    switch( offset )
    {
        case AVR8_IO_PORTA:
        {
            return m_port_a;
        }
        case AVR8_IO_PORTB:
        {
            return m_port_b;
        }
        case AVR8_IO_PORTC:
        {
            return m_port_c;
        }
        case AVR8_IO_PORTD:
        {
            return m_port_d;
        }
        default:
            break;
    }
    return 0;
}

I/O レジスターライトで呼ばれる WRITE8_MEMBER(vgmduino_state::port_w) で、YM2151 に接続されている IC や RD/WR 足の変化を検知して、YM2151 にコマンドを送信。

void vgmduino_state::port_w(offs_t offset, uint8_t data)
{
    /* YM2151 ATMEGA328 PORT_REG
       D0     2         AVR8_IO_PORTD 2
       D1     3         AVR8_IO_PORTD 3
       D2     4         AVR8_IO_PORTD 4
       D3     5         AVR8_IO_PORTD 5
       D4     6         AVR8_IO_PORTD 6
       D5     7         AVR8_IO_PORTD 7
       D6     8         AVR8_IO_PORTB 0
       D7     9         AVR8_IO_PORTB 1
       RD     10        AVR8_IO_PORTB 2
       WR     11        AVR8_IO_PORTB 3
       A0     12        AVR8_IO_PORTB 4
       IC     13        AVR8_IO_PORTB 5
    */
    switch( offset )
    {
        case AVR8_IO_PORTA:
        {
            if (data == m_port_a) break;
            m_port_a = data;
            break;
        }
        case AVR8_IO_PORTB:
        {
            if (data == m_port_b) break;
            /* YM2151 IC 1->0 */
            if (BIT(m_port_b, 5) && !BIT(data, 5))
            {
                m_port_b = data;
                /* YM2151 RESET */
                m_ym2151->reset();
                printf("VGMDUINO: m_ym2151->reset()\n");
                break;
            }
            /* YM2151 WR 1->0 */
            if (BIT(m_port_b, 3) && !BIT(data, 3))
            {
                m_port_b = data;
                /* YM2151 A0, D0-D7 */
                uint8_t adr = BIT(m_port_b, 4);
                // D6     8         AVR8_IO_PORTB 0
                // D7     9         AVR8_IO_PORTB 1
                uint8_t dat = (0b11111100 & m_port_d) >> 2 | BIT(m_port_b, 0) << 6 | BIT(m_port_b, 1) << 7;
                m_ym2151->write(adr, dat);
                // printf("VGMDUINO: m_ym2151->write(0x%02X, 0x%02X)\n", adr, dat);
                break;
            }
            /* YM2151 RD 1->0 */
            if (BIT(m_port_b, 2) && !BIT(data, 2))
            {
                m_port_b = data;
                /* YM2151 A0 */
                uint8_t adr = BIT(m_port_b, 4);
                uint8_t state = m_ym2151->read(adr);
                // printf("VGMDUINO: m_ym2151->read(0x%02X) = 0x%02X\n", adr, state);
                /* YM2151 D0-D7 */
                m_port_d |= (state & 0b0011111) << 2;
                m_port_b |= (state & 0b1100000) >> 5;
                break;
            }
            m_port_b = data;
            break;
        }
        case AVR8_IO_PORTC:
        {
            if (data == m_port_c) break;
            m_port_c = data;
            break;
        }
        case AVR8_IO_PORTD:
        {
            if (data == m_port_d) break;
            m_port_d = data;
            break;
        }
        default:
            break;
    }
}

残課題

  • 発声していることはしているが、AVR のウエイトルーチンが正しく動作しておらずひどい音。→正確ではないが発声するようになった。
    • おそらく micros() 関数が正しい時間を返していない ので確認
    • micros関数のソース
    • TIMER0 が正しくエミュレートされていないため と確認。(TODO: どのように `avr8.cpp' にエミュレーションを実装するか調査中。途中までの実装はあるようだ)→ hack で実装
    • TIMER0 の初期化がどこで行われているか確認(TODO:)→ hack で実装
    • 解析のため avr-gcc で -g3 オプションからソースマップ付きの逆アセンブルができるか確認。
  • avr8.cpp に ATMEGA328 の定義を追加しているので資料化、ソースコードを整理してどこかにコミット。→ mame にマージ済み
  • avr8.cpp でブートストラップにまつわる不具合がいくつかあるようなので Testers に報告? → mame にマージ済み

資料リンク

メモのメモ

AVR avr8.cpp 修正箇所

  • タイマー割り込み関連を hack で有効化(未検証)
    • Arduino 関数 maicrs() は TIMER0 を利用
READ8_MEMBER( avr8_device::regs_r )
{
//  printf("--- READ offset %04x ---\n", offset);

    switch( offset )
    {
        // TODO: hack
        case AVR8_REGIDX_MCUSR:
        case AVR8_REGIDX_MCUCR:
        case AVR8_REGIDX_TCCR0A:
        case AVR8_REGIDX_TCCR0B:
        case AVR8_REGIDX_ADCSRA:
        case AVR8_REGIDX_ADCSRB:
        case AVR8_REGIDX_TCCR1A:
        case AVR8_REGIDX_TCCR1B:
        case AVR8_REGIDX_TCCR1C:
        case AVR8_REGIDX_TCNT0:
        case AVR8_REGIDX_OCR0A:
        case AVR8_REGIDX_OCR0B:
        case AVR8_REGIDX_UCSR0B:
        case AVR8_REGIDX_UCSR0C:
        case AVR8_REGIDX_TCCR2A:
        case AVR8_REGIDX_TCCR2B:
            return m_r[offset];

        case AVR8_REGIDX_TIFR0:
        case AVR8_REGIDX_TIFR1:
        case AVR8_REGIDX_TIFR2:
        case AVR8_REGIDX_TIFR3:
        case AVR8_REGIDX_TIFR4:
        case AVR8_REGIDX_TIFR5:
            return 0b00000111;

        default:
//            printf("[%08X] AVR8: Unknown Register Read: 0x%03X\n", m_shifted_pc, offset);
//          machine().debug_break();
            return 0;
    }
}

TIMER0 hack

        case WGM02_FAST_PWM:
            // TODO: hack
            if(count == m_r[AVR8_REGIDX_OCR0A]) {
                m_r[AVR8_REGIDX_TIFR0] |= AVR8_TIFR0_TOV0_MASK;
                update_interrupt(AVR8_INTIDX_TOV0);
            }
            break;

AVR avr8.cpp 修正箇所(mame マージ済み)

  • ATMEGA328 追加
    • memory mask を 0x7fff で設定。
    • internal map を 0x00-0xff で設定。
DEFINE_DEVICE_TYPE(ATMEGA328,  atmega328_device,  "atmega328",  "Atmel ATmega328")

void atmega328_device::atmega328_internal_map(address_map &map)
{
    map(0x0000, 0x00ff).rw(FUNC(atmega328_device::regs_r), FUNC(atmega328_device::regs_w));
}

//-------------------------------------------------
//  atmega328_device - constructor
//-------------------------------------------------

atmega328_device::atmega328_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
    : avr8_device(mconfig, tag, owner, clock, ATMEGA328, 0x7fff, address_map_constructor(FUNC(atmega328_device::atmega328_internal_map), this), CPU_TYPE_ATMEGA328)
{
}

//TODO: review this!
void atmega328_device::update_interrupt(int source)
{
    const CInterruptCondition &condition = s_int_conditions[source];

    int intstate = 0;
    if (m_r[condition.m_intreg] & condition.m_intmask)
        intstate = (m_r[condition.m_regindex] & condition.m_regmask) ? 1 : 0;

    set_irq_line(condition.m_intindex << 1, intstate);

    if (intstate)
    {
        m_r[condition.m_regindex] &= ~condition.m_regmask;
    }
}

ブートローダーの大きさを修正

void avr8_device::device_reset()
{
    // switch ((m_hfuses & (BOOTSZ1|BOOTSZ0)) >> 1){
    // case 0: m_boot_size = 4096; break;
    // case 1: m_boot_size = 2048; break;
    // case 2: m_boot_size = 1024; break;
    // case 3: m_boot_size = 512; break;
    // default: break;
    // }

    // TODO:
    switch ((m_hfuses & (BOOTSZ1|BOOTSZ0)) >> 1){
    case 0: m_boot_size = 2048; break;
    case 1: m_boot_size = 1024; break;
    case 2: m_boot_size = 512; break;
    case 3: m_boot_size = 256; break;
    default: break;
    }

HIGH FUSE でブートローダー指定起動時に m_pc の値が正しく初期化されてないのを修正。

void avr8_device::device_reset()
{

    if (m_hfuses & BOOTRST){
    m_shifted_pc = 0x0000;
    logerror("Booting AVR core from address 0x0000\n");
    } else {
    m_shifted_pc = (m_addr_mask + 1) - 2*m_boot_size;
    // ADD:
    m_pc = m_shifted_pc >> 1;
    logerror("AVR Boot loader section size: %d words\n", m_boot_size);
    }

Arduino メモ

Arduino UNO を Arduino UNO たらしめている部分

  • 基板の大きさ、ピンアウトなどのレイアウト。
  • ATmega328 に入る Arduino ブートローダーと HUSE 設定、ATmega16U に入る USB シリアル変換によるプログラマーの仕組み。
    • ATmega16U は USB シリアル変換を行い ATmega328 の D0/D1 に UART 転送。
    • ATmega16U は DTR 信号受付から ATmega328 をリセットできるように結線されている。
      • ATmega16U PD7 -> ATmega328 RESET
    • ATmega328 はユーザープログラムとともに 0x7e00 に配置される Arduino ブートローダーを配置。
      • ATmega328 のリセット後はブートローダー(0x7e00)に制御を移し(HIGH FUSE の設定による)、ATmega16U からのシリアルを監視し、flash のアップデートコマンドがくれば自己書換え、一定時間コマンドがなければユーザープログラム(0x0000)にジャンプ。
  • Arduino C/C++ ライブラリー
    • 一部の関数は Arduino ブートローダーの初期化に依存して動作(していると思う。要調査)

シリアル通信

  • ATmega16U は Windows などの OS 上のアプリから RS-232C として振舞う USB デバイスになるファームが入っている。
  • さらに ATmega16U は RS-232C として受けた通信を UART 通信に変換して ATmega328 にデーターを送る。
  • ATmega シリーズは D0/D1 で UART をハードウェア処理できるペリフェラルが備わっている。(要エミュレーション)

マイコンメモ

ATmega328 に実装されているペリフェラル

データーシートより

  • Two 8-bit Timer/Counters with separate prescaler and compare mode
  • One 16-bit Timer/Counter with separate prescaler, compare mode, and capture mode
  • Real time counter with separate oscillator
  • Six PWM channels
  • 8-channel 10-bit ADC in TQFP and QFN/MLF package
  • Temperature measurement
  • Programmable serial USART
  • Master/slave SPI serial interface
  • Byte-oriented 2-wire serial interface (Phillips I2C compatible)
  • Programmable watchdog timer with separate on-chip oscillator
  • On-chip analog comparator
  • Interrupt and wake-up on pin change

UART と USART

  • USART(Enhanced Universal Synchronous Asynchronous Receiver Transmitter)
  • UART(Universal Asynchronous Receiver Transmitter)