最終更新日

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

はじめに

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

エミュレーション対象

この文書でサンプルとしているハードは、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
# デバッガー付き起動(エミュレーション CPU の最初のステップで停止する)
./kanon64 -debug -window
# ランチャーが不要の場合はドライバー名を引数に指定すると一気に起動
./kanon64 kanon -window -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

VSCode 設定

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

  • includePath${workspaceFolder}/src/** に設定。
  • #ifdef などがうまく処理できるように defines でプラットフォームに合わせた SDLMAME_UNIX=1 などに設定。
{
    "configurations": [
        {
            "name": "Linux",
            "intelliSenseMode": "clang-x64",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "includePath": [
                "${workspaceFolder}/src/**"
            ],
            "defines": [
                "SDLMAME_UNIX=1"
            ]
        }
    ],
    "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": "(gdb) 起動",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/kanon64",
            "args": [
                "kanon",
                "-window",
                "-debug",
            ],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "gdb の再フォーマットを有効にする",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

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

デバイス定義

  • 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 オプションからソースマップ付きの逆アセンブルができるか確認。
  • [x] avr8.cpp に ATMEGA328 の定義を追加しているので資料化、ソースコードを整理してどこかにコミット。→ mame にマージ済み
  • [x] 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)