動作確認

出来上がったので動作確認をする。ATmega328Pによる制御回路、制御コードの中身は、だいぶ整理したが『CPUの創りかた』のときとまったく同じである。

#include "CPU1738_external.h"

/*
ニモニック(のようなもの)一覧:
※ ジャンプ命令は1クロック、それ以外は2クロック。HALTは状態を保持して停止。
LD_A(Data)    : A ← [Data]
LD_B(Data)    : B ← [Data]
LD_AB         : A ← B
LD_BA         : B ← A

ADD_AB        : A ← A + B
SUB_AB        : A ← A - B
ADD_A(Data)   : A ← A + [Data]
SUB_A(Data)   : A ← A - [Data]

OUT_A         : OUT ← A
OUT_B         : OUT ← B
OUT(Data)     : OUT ← [Data]
IN_A          : A ← IN

JUMP(Address) : PC ← [Address]
JNC(Address)  : PC ← [Address] if NOT Carry
JNZ(Address)  : PC ← [Address] if NOT Zero

HALT          : HALT
*/

int main(void){
    // アセンブル(のようなこと)をしてROM (に見立てた配列)へ格納する。
    // 1 + 2 + 3 + 4 + 5 = 15を計算する。
    _[0] = LD_A(1);
    _[1] = OUT_A;
    _[2] = ADD_A(2);
    _[3] = OUT_A;
    _[4] = ADD_A(3);
    _[5] = OUT_A;
    _[6] = ADD_A(4);
    _[7] = OUT_A;
    _[8] = ADD_A(5);
    _[9] = OUT_A;
    _[10] = LD_A(0);
    _[11] = LD_A(0);
    _[12] = LD_A(0);
    _[13] = LD_A(0);
    _[14] = LD_A(0);
    _[15] = OUT(0);


    // CPU1738用の制御回路を有効化する。
    // 第1引数: PD7から出力するクロック周波数Hz。8~32767Hz。
    // 第2引数: それを何分周して低速クロックとしてPD6から出力するか。1~65535分周。
    // ※第2引数を指定しない場合はPD6から1Hzが出力される。
    enable_CPU1738_external(10);


    return 0;
}
#ifndef CPU1738_EXTERNAL_H_
#define CPU1738_EXTERNAL_H_

#define F_CPU 8000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

// ジャンプ命令は1クロック、それ以外は2クロック。HALTは状態を保持して停止。
#define LD_A(Data)    (0 << 4 | ((Data) & 0xF))     // A ← [Data]
#define LD_B(Data)    (1 << 4 | ((Data) & 0xF))     // B ← [Data]
#define LD_AB         (2 << 4)                      // A ← B
#define LD_BA         (3 << 4)                      // B ← A

#define ADD_AB        (4 << 4)                      // A ← A + B
#define SUB_AB        (5 << 4)                      // A ← A - B
#define ADD_A(Data)   (6 << 4 | ((Data) & 0xF))     // A ← A + [Data]
#define SUB_A(Data)   (7 << 4 | ((Data) & 0xF))     // A ← A - [Data]

#define OUT_A         ( 8 << 4)                     // OUT ← A
#define OUT_B         ( 9 << 4)                     // OUT ← B
#define OUT(Data)     (10 << 4 | ((Data) & 0xF))    // OUT ← [Data]
#define IN_A          (11 << 4)                     // A ← IN

#define JUMP(Address) (12 << 4 | ((Address) & 0xF)) // PC ← [Address]
#define JNC(Address)  (13 << 4 | ((Address) & 0xF)) // PC ← [Address] if NOT Carry
#define JNZ(Address)  (14 << 4 | ((Address) & 0xF)) // PC ← [Address] if NOT Zero

#define HALT          (15 << 4)                     // HALT


volatile bool Switch_Changed = false;
ISR(INT0_vect){            // 手動クロック用のタクトスイッチが押されるか放されるかしたら、
    Switch_Changed = true; // そのフラグを立てる
}

volatile uint8_t _[16];     // 命令を格納しておく配列。
ISR(PCINT1_vect){           // アドレスが変化したら、
    PORTB = _[PINC & 0x0F]; // そのアドレスに応じたデータを出力する。
}

volatile uint16_t Prescale;    // 高速クロックを何分周して低速クロックにするか。
volatile uint16_t Counter = 0;
ISR(TIMER1_COMPA_vect){
    PORTD ^= (1 << PD7);       // 高速クロックの倍の頻度でPD7出力をトグルする。
    if(++Counter >= Prescale){ // その方形波を何分周かして低速クロックとしてPD6から出力する。
        PORTD ^= (1 << PD6);
        Counter = 0;
    }
}


void enable_reset(void){
    DDRD  |=  (1 << PD3); // PD3から!リセット信号を出力することにする。
    PORTD &= ~(1 << PD3); // 最初に!リセット信号をLにしておいて、
    _delay_ms(100 - 65);  // 適当にリセット時間を設けて、(65msはATmega328P自体のリセットからの遅延時間)
    PORTD |= (1 << PD3);  // リセット期間が明けたら!リセット信号をHに戻す。
}

void enable_manual_clock(void){
    DDRD  |=  (1 << PD5);   // PD5から手動クロックを出力することにする。 
    PORTD &= ~(1 << PD5);   // 最初はLを出力しておく。
    DDRD  &= ~(1 << PD2);   // PD2をスイッチ入力にする。
    PORTD |=  (1 << PD2);   // PD2を内部プルアップする。
    EICRA |=  (1 << ISC00); // INT0 (PD2)が変化したときに割り込み要求を出すことにする。
    EIMSK |=  (1 << INT0);  // 外部割り込みINT0を有効にする。
    sei();  
}
inline void debounce_PD2_to_PD5(void){
    if(Switch_Changed){                              // PD2に接続したスイッチが変化したら、
        Switch_Changed = false;
        _delay_ms(5);                                // バウンスの収まるまで待ってから、
        if(PIND & (1 << PD2)){PORTD &= ~(1 << PD5);} // スイッチが放されていたらPD5からLを出力し、
        else                 {PORTD |=  (1 << PD5);} // スイッチが押されていたらPD5からHを出力する。
    }
}
void enable_rom(void){
    DDRC  &= ~0x0F; // PC3:0にアドレスを入力することにする。
    //PORTC |=  0x0F; // PC3:0を内部プルアップする。※実際にTD4に接続するときは内部プルアップは不要。

    DDRB  = 0xFF; // PB7:0から命令を出力することにする。
    PORTB = _[0]; // 最初のクロックが立ち上がる前に0番地の命令を出力しておく。

    // PCINT11:8 (PC3:0)からピン変化割り込みをかけることにする。
    PCICR  |= (1 << PCIE1);
    PCMSK1 |= ((1 << PCINT11)|(1 << PCINT10)|(1 << PCINT9)|(1 << PCINT8));
    sei();
}
void enable_Hi_Lo_clock(uint16_t freq_hi){
    DDRD   |= (1 << PD6) | (1 << PD7); // 自動クロックを出力する端子を指定する。
    TCCR1B |= (1 << WGM12);            // タイマー1をCTCモードで動かす。
    TCCR1B |= (1 << CS11);             // 8分周してタイマー1でカウントする。
    OCR1A   = (F_CPU/8)/(freq_hi*2)-1; // コンペア値。(F_CPU/8分周)/(高速クロック周波数*2)-1
    TIMSK1 |= (1 << OCIE1A);           // タイマー1のコンペアマッチ割り込みを有効にする。
    sei();
}


// 全部まとめる
// 第1引数: PD7から出力するクロック周波数Hz。8~32767Hz。
// 第2引数: それを何分周して低速クロックとしてPD6から出力するか。1~65535分周。
// ※第2引数を指定しない場合はPD6から1Hzが出力される。
void enable_CPU1738_external(uint16_t freq_hi, uint16_t prescale){
    Prescale = prescale;
    enable_rom();
    enable_reset();
    enable_manual_clock();
    enable_Hi_Lo_clock(freq_hi);
    while(true) debounce_PD2_to_PD5();
}
void enable_CPU1738_external(uint16_t freq_hi){ // 分周比を指定しない場合
    enable_CPU1738_external(freq_hi, freq_hi);
}


/*動作確認*/

/*テキストリスト1 加算プログラム(ループなし)
1 + 2 + 3 + 4 + 5 = 15を計算する。
   _[0] = LD_A(1);
   _[1] = OUT_A;
   _[2] = ADD_A(2);
   _[3] = OUT_A;
   _[4] = ADD_A(3);
   _[5] = OUT_A;
   _[6] = ADD_A(4);
   _[7] = OUT_A;
   _[8] = ADD_A(5);
   _[9] = OUT_A;
   _[10] = LD_A(0);
   _[11] = LD_A(0);
   _[12] = LD_A(0);
   _[13] = LD_A(0);
   _[14] = LD_A(0);
   _[15] = OUT(0);

*/

/*
   _[0] = OUT_A;
   _[1] = ADD_A(1); // 1ずつインクリメントして、
   _[2] = JNC(0);
   _[3] = SUB_A(1); // 1ずつデクリメントして、
   _[4] = OUT_A;
   _[5] = JNZ(3);
   _[6] = JUMP(0);  // を繰り返す。
*/

#endif