こんにちは、めのんです!
これまでAVRの個々のメモリやレジスタについて見てきました。
今回はそれらをまとめたデータ構造を作ってみようと思います。
以前の記事を書いたあと、「もっとこうした方がいい」といったアイデアが出てきたこともあります。
ですので、以前書いた内容に固執することなくよりよいものにしていきますので、あらかじめご了承ください。
AVRの構造
おさらいになりますが、最初にAVRのデータ構造を確認することにします。
いつも参考にさせていただいている「picoPower®技術を持つAVR® マイクロ コントローラ」から引用した図をご覧ください。
ここに書かれているうち、ALUなど一部は今回扱いませんが、メモリとレジスタは一通り網羅することになります。
レジスタのうち命令レジスタだけはちょっと違って、これはプログラムメモリから読み込んだ命令の格納先ですので、命令セットシミュレータではローカル変数で対応できると思います。
命令セットシミュレータでの実装
まずはソースコードをご覧ください。
// atmega328.h
#ifndef ATMEGA328_H_
#define ATMEGA328_H_
#include <stdint.h>
// ATmega329 CPU部分のデータ構造
typedef struct
{
// データメモリ
union
{
struct
{
// レジスタファイル
union
{
uint8_t r[32];
struct
{
uint8_t _r[26];
uint16_t x;
uint16_t y;
uint16_t z;
};
};
// I/Oレジスタ
union
{
uint8_t io[64];
struct
{
uint8_t _io[61];
// スタックポインタ
uint8_t spl;
uint8_t sph;
//m ステータスレジスタ
uint8_t sr;
};
};
// 拡張I/Oレジスタ
uint8_t eio[160];
};
uint8_t data[0x900];
};
// プログラムメモリ
uint16_t program[0x4000];
// プログラムカウンタ
uint16_t pc;
} atmega328_t;
// ステータスレジスタのフラグ位置
#define flag_I 0x80
#define flag_T 0x40
#define flag_H 0x20
#define flag_S 0x10
#define flag_V 0x08
#define flag_N 0x04
#define flag_Z 0x02
#define flag_C 0x01
#endif // !ATMEGA328_H_
ちょっと長くて複雑になってしまいましたが、これが現時点でのヘッダファイルになります。
実際にコーディングしやすいように、以前投稿したときとは異なる名前を付けている箇所もありますし、構造が変わっている部分もあります。
以前の記事と比較しながら確認していただいてもよろしいかと思います。
せっかくですので使い方にも少し触れておきます。
たとえば、次のような感じでatmega328_t型の変数cpuを作り、その汎用レジスタr4を取り出すことができます。
atmega328_t cpu;
uint8_t value = cpu.r[4];
スタックポインタだけはちょっと面倒で、下位8ビットのsplと上位8ビットのsphが分離して配置されています。
これはsplが奇数アドレスに配置されているからで、境界調整の問題が発生するので16ビット単位でアクセスするのを断念しました。
x86だけを考えれば16ビットアクセスしてしまっても問題ないんですけど、以前定めた「使用するコンパイラの要件」にはなかったのでこういう形になっています。
あと、前回触れるのを忘れたんですけど、ステータスレジスタにもアドレスがあります。
データメモリの0x5f番地がステータスレジスタのアドレスになります。
これはちょうどスタックポインタの次の番地にあたりますね。
データメモリの一部の領域はI/Oレジスタと呼ばれる特殊な機能を持つレジスタになっています。
たとえばタイマーの機能などを使おうと思うとI/Oレジスタの一部を制御します。
I/Oレジスタには通常のアドレス以外にI/O専用のアドレスが振られています。
I/Oレジスタ専用のアドレスは0x00から0x3fまでで、データメモリのアドレス0x20がI/Oアドレス0x00に相当します。
今回はここまでにしておきます。
次回は、今回作ったデータ構造を操作するためのいくつかの関数を追加していくことにします。
どうぞご期待ください!