働きたくない

5000兆円欲しい

受信記録(概要/目的別/自宅以外/自宅(~3MHz))

脳内だけで記憶するのには限界があるので、記録を付けることにしました。これは固定ページとします。

画像の容量が重いので携帯で見るのは余りおすすめしません…。

※なるべく見つけたら直すようにはしていますが、間違いが多分結構あります。

続きを読む

[光り物] ドット・マトリックスLED HCMS-2912を使った簡易電光掲示板

過去自分が買ったものにケジメを付けていくシリーズ。

今回は、Broadcom(Avago、HP)のHCMS-2912というドットマトリクスLEDで、2014年ごろ、Digikeyで購入したものです。

目次

とりあえず動作画面

MODE 1(時間表示、固定)/ MODE 2(年月日時間表示、スクロール)

MODE 3(温度表示)/ MODE 5(任意の文章)

キー・パーツ概説

まずパーツの説明から。

HCMS-2912

仕様

ドット・マトリクスLEDではありますが、今よく見かける8x8の大型のものではなく、5x7で小型の、文字表示向けのものです。

ASCIIのフォント内蔵のものもありますが、HCMS-2912はありません。ただし、公式のアプリケーション・ノート(Application Brief D-003 "AB D003 HCMS-29xx LED displays set (ASCII Katakana)")に、フォントデータがあります。

また、接続方法も、パラレル・バス接続、シリアル接続などありますが、HCMS-2912はシリアルです。SPIに似ていますがちょっとややこしいです(後述)。

電源は+5Vです。一応V{LOGIC}とV{LED}は分かれていて、LOGIC側はもっと下に取れるのですが、LEDの制約があり、+5V系統がどちらにしろ必要です。

公式ページ:
HCMS-2912 https://jp.broadcom.com/products/leds-and-displays/smart-alphanumeric-displays/serial-interface/hcms-2912

入手方法

2013年9月24日にDigikeyに注文したらしいです。

購入後、一旦は動かしました(NXPのLPC1114で…)。↓が当時のブログ。

[電子工作]HCMS-2912を動かす。 | fenrir's memorandum https://karlsnautr.blogspot.com/2014/04/hcms-2912led-hcms2912.html

一応今でもdigikeyに登録されていて、何ならアクティブですが、1個10,000円近くします。また、在庫0です。類似の(HCMS-29xxシリーズ)製品では一部在庫ありのものもあります。

購入当時はここまで高くなかった気もしますが、正確にいくらだったかは分かりません。Digikey、2010年には登録していたのに…購入記録が消えています…。

eBayには類似商品が多数出品されていて、そちらのほうが入手難度は低い気がします。

あと、鈴商にはパラレルバス接続のHDSP-2111というのがまだ在庫ありです(https://suzushoweb.shop-pro.jp/?pid=100888086)。

AVR64DD32

マイコンで制御します。どのマイコンにするか…大したことはしないので、AVRにしました。

AVRの中でも、最近扱ったAVR DDシリーズは、高速オシレータ内蔵・書き込み線が少ない・複電圧対応と便利に思えたので、早速これを使ってみることにしました。

(AVR DUならUSB-CDC機能も内包できて、更に部品点数が減ると思いますが、まだそこまでの技量がないため、シリアル変換はCH340を使いました)

仕様

まず、常用しないことにします(消費電流が比較的大きいことと、時計は既に沢山あるから…)。

そのうえで、以下くらいができればいいかなと思いました。

機能 実現方法
時間を表示する AVRの32.768kHz オンチップオシレータ(精度は悪い)+ Real-time counterのtickを使う。RTCは、大半が非通電の場合は勿体ないので使わない。
温度を表示する AVRのオンチップ温度センサーを使う。基板の外に独立で温度センサーを用意することも考えたが、どうせLEDの発熱の影響を受けるので、精度は期待できないから、勿体ない。
PCから送ったメッセージを表示する USB-UART機能をオンボードで設け、UARTでメッセージを更新する
ニュースを表示する(今後) ESP8266で送受信する。ESP8266は+3.3V系統なので、VCCIO2を使う。サーバーを用意して配信することになるが、それが面倒なのでやってない。
スクロール速度を可変にする DIP-SWを用意して、それで設定することにします
小型にする 本当はLEDマトリクスと同サイズにしたいが、それは無理なので、努力する(限界はある)

最初はもっと色々機能を付け足そうとして、回路も複雑だったんですが、面倒だし、どうせ作ったら仕舞うしな…と思って削っていき、上のようになりました。

(ついでに、最初はATmega328PBを使おうとしていたが、それだと水晶外付けだったりして面積を喰っていた)

回路

色々削っていった結果、小じんまりしたものになりました。

電源周り

+5V入力として、USB-Cからの電源と、ACアダプターの2系統を用意しました。

USBは数百mAが限度なので、一応後者も用意した形になります。

ただ、実際はUSB-Cの宮殿能力の範囲内に収まっていました。輝度最大(Control0 レジスタが0x7F)で全点灯でも0.3Aくらい。文字列を流すと0.1A程度。

+3.3VはLDOで生成します。蛍光表示管+RL78時計の時と同じ回路です。

マイコン周り

5Vと3.3Vを分ける他は特にありません。

AVRのペリフェラルのピン割り当ては、基板の配置にあわせて変更・調整しています。

ESP8266は、接続のためのピンヘッダを用意するだけにしました。ただ、#RESETは出来たほうがよいと考え、シリアル線の他にRESET線も出しています。 なお、AVR DDはオープンドレイン出力がない?みたいなので、デジタル・トランジスタを外に用意しています。

USB-UART線には、一応ジャンパを入れています。これはCH340NとAVRのUART線の接続を逆にしてしまったときのためなのですが、今回本当に間違えてしまい、このソルダ・ジャンパに救われました。

スイッチ

スクロール速度を設定するBCDスイッチと、表示モードを変更するプッシュ・スイッチの2つがあります。

ケースに入れても操作できるよう、BCDはつまみがあるものを探して、***を使いました。

プッシュスイッチは、ノブ?が長い物を探して、***を使いました。

USB周り

まず、USB-Cコネクタを使うことにします。

今回はUSB-UART機能を載せるので、通信用の16ピンのコネクタです。

通信線の保護回路を一応載せてありますが、おまじないのようになっています。

USB-UARTは、安価なICチップ CH340Nを使いました。CH340xはピン数にバラエティがありますが、最低限の8ピンのものにしています。

回路図

基板

V_{LED}は大電流が流れそうなのでかなり太くしました。それ以外は特に工夫もありません。表面実装品(特にUSB-Cコネクタ)は手ハンダしやすいように、少しランドを長くしたくらいでしょうか。

AVR Dxになって、ペリフェラルのピン割り当てが更に柔軟になり、基板の配置にあわせて変更・調整出来るようになりました。このお陰もあってか、かなりすっきりしました。

USB-Cコネクタ周りですが、D-/D+は等長配線にすらしていません。また、どうしてもD-とD+が交差してしまい、ビアを打っています。これで一応動いてはいますが、あまり良い例ではないでしょう…。

基板パターン 表/裏

組み立て

特に苦労することはありません。

別に密度も(実際に目で見てみると)そこまで高くないはずですが、リード部品とSMDが混在しているので、先にSMDから付けたほうがコテの取り回しがしやすいです。

USB-Cコネクタのはんだ付けに難儀するかと思っていましたが、フラックス使ったら一発でうまくいってしまい、少し拍子抜けしました(むしろAVRの方に時間がかかった)。

LEDマトリクスは、28ピン300milのICソケットを用意し、使わない(割り当てのない)ピンは全てソケットから抜くことで、必要な形に整えました。

プログラム

プログラムです。今回はハードというよりソフト機能が物を言うので、少し手間がかかりました。

開発環境はMPLAB(MCCも使う)、ライブラリとしてMelodyを使用しています。

なお、ESP8266周りは全く作っていません。まだ。

HCMS-2912との通信

そもそもなんですけど、HCMS-2912との通信プロトコルが、SPI(Mode 3)そのままではないので、ちょっとした工夫が必要でした。

↓が具体的な流れなのですが、以下2点はケアが必要でした。

  • RS(コマンド/データの選択)は#SS↓時に読み取る
  • データ送信後、SCKを下げる必要がある

前者は、GPIOで#SSより先に操作すれば解決します。

ただ後者は、SCKがSPIペリフェラル優先になっているので、単純にGPIOで操作するわけにもいきません。

解決策として、最初は「SPIの割り当てを一旦解除し(PORTMUX.SPIROUTEA = 0x07)、GPIOとして操作」していましたが、途中で「#SSを下げずに空データをSPIで送る」でよいことに気づき、そちらを使っています(SCKを2回以上下げる分には問題ないようだった)。

HCMS-2912の書き込み

フォント

↓ を使用しました。(ヘッダをインクルード)

bb-lcd/font-5x7.c at master · ChuckM/bb-lcd https://github.com/ChuckM/bb-lcd/blob/master/font-5x7.c

表示モードの制御

色々考えた末、「必要な情報を持った構造体の単方向リスト」を用意することで落ち着きました。

表示のアップデート用関数も持っておいて、温度や時刻については、それを呼び出すことで更新するようにしました。

文字列も、UARTから更新すると長さが変わったりするのですが、いちいちstrlenなんてしていられないので、アップデート時にだけ長さを再計算するようにしました。

Melodyを使うことについて

サンプルが少ないのでやりづらい…。一応公式ドキュメントにそこそこ書いてはあるのですが。

一番悩んだのはADCでの温度計測でした。MCCでは、サンプリングレートを適切な範囲内に収めたりしてくれません(ボーレートはエラー率とか出るのに)。

結局、関数以外のハード仕様はMelodyのドキュメントだけ見てもわからず、データシートを見ながら設定しました。

USART0受信の割り込みによる処理は、ほぼMelodyのドキュメントそのままです。

時間周りについて

time.hを使っています。avr-libcのtime.hは、RTCで1秒毎に呼び出すための関数 system_tick() が既に用意されているので(便利!)、簡単です。

MCCによる設定

ピン割り当て

ピン設定

ペリフェラル設定

ペリフェラル全体

CLKCTRL / Interrupt Manager

USART0(Driver / PLIB)

ADC

SPI・RTC

コード

HCMS-2912.c

ヘッダ

/* 
 * File:   HCMS-2912.h
 * Author: fenrir_
 *
 * Created on 2026/08/06, 18:53
 */

#ifndef HCMS_2912_H
#define    HCMS_2912_H

#ifdef __cplusplus
extern "C" {
#endif


void HCMS_2912_WriteStr(const uint8_t* , const uint16_t );
void HCMS_2912_WriteReg(const uint8_t);
void HCMS_2912_FillData(const uint8_t);
void HCMS_2912_init();


#ifdef __cplusplus
}
#endif

#endif  /* HCMS_2912_H */

ソース

#include "mcc_generated_files/system/system.h"
#include "mcc_generated_files/timer/delay.h"

#include "font-5x7.h"

void HCMS_2912_WriteStr(const uint8_t* str, const uint16_t startpos){
    
    HCMS_2912_RS_SetLow();
    
    while(!SPI0_IsTxReady()){}
    
    HCMS_2912_SS1_SetLow();
    
    for (int i = 0; i < 8; i++){
        const uint8_t c = str[startpos + i];
        
        for (int j = 0; j < 5; j++){
            const uint16_t pos = (c) * 5 + j;
            uint8_t data = font_data[pos];            

            
            SPI0_ByteExchange(data);
        }
    }
    
    HCMS_2912_SS1_SetHigh();
    HCMS_2912_RS_SetLow();
    
    while(!SPI0_IsTxReady()){}
    
    SPI0_ByteExchange(0x00);    // push dummy data 
    
    /*
    
    DELAY_microseconds(1);
    
    
    // SCKをGPIOとしてセットする
    PORTD_DIRSET = 0b11010000;
    PORTD.OUTSET = 0b11010000; // High
    // 一度SPIを切り離す
    PORTMUX.SPIROUTEA = 0x07;
    
    PORTD.OUTCLR = 0b01000000; // Low
    
    DELAY_microseconds(1);
    
    PORTD.OUTSET = 0b11010000; // High
    
    // SPIに設定し直す
    PORTMUX.SPIROUTEA = 0x04;
    */
}

void HCMS_2912_WriteReg(const uint8_t data){
    
    HCMS_2912_RS_SetHigh();
    
    while(!SPI0_IsTxReady()){}
    
    HCMS_2912_SS1_SetLow();
    
    SPI0_ByteExchange(data);
    
    HCMS_2912_SS1_SetHigh();
    HCMS_2912_RS_SetLow();
    
    while(!SPI0_IsTxReady()){}
    
    SPI0_ByteExchange(0x00);    // push dummy data 
    /*
    // 一度SPIを切り離す
    
    // SCKをGPIOとしてセットする
    PORTD_DIRSET = 0b11010000;
    PORTD.OUTSET = 0b11010000; // High
    // 一度SPIを切り離す
    PORTMUX.SPIROUTEA = 0x07;
    
    PORTD.OUTCLR = 0b01000000; // Low
    
    DELAY_microseconds(1);
    
    PORTD.OUTSET = 0b11010000; // High
    
    // SPIに設定し直す
    PORTMUX.SPIROUTEA = 0x04;
    /**/
}

void HCMS_2912_FillData(const uint8_t data){
    
    HCMS_2912_RS_SetLow();
    
    while(!SPI0_IsTxReady()){}
    
    HCMS_2912_SS1_SetLow();
    
    for (int i = 0; i < 40; i++){
        SPI0_ByteExchange(data);
    }
    
    HCMS_2912_SS1_SetHigh();
    
    HCMS_2912_RS_SetLow();
    
    while(!SPI0_IsTxReady()){}
    SPI0_ByteExchange(0x00);    // push dummy data 
    
    /*
    DELAY_microseconds(1);
    
    
    // SCKをGPIOとしてセットする
    PORTD_DIRSET = 0b11010000;
    PORTD.OUTSET = 0b11010000; // High
    // 一度SPIを切り離す
    PORTMUX.SPIROUTEA = 0x07;
    
    PORTD.OUTCLR = 0b01000000; // Low
    
    DELAY_microseconds(1);
    
    PORTD.OUTSET = 0b11010000; // High
    
    // SPIに設定し直す
    PORTMUX.SPIROUTEA = 0x04;
    /**/
}

void HCMS_2912_init(){
    
    SPI0_Open(0);
    
    HCMS_2912_SS1_SetHigh();
    HCMS_2912_BLANK_SetLow();
    
    // Display hardware reset
    HCMS_2912_nRESET_SetLow();
    DELAY_milliseconds(100);
    HCMS_2912_nRESET_SetHigh();
    
    // Control reg
    HCMS_2912_WriteReg(0b01001101);
    HCMS_2912_WriteReg(0b10000000);
    
    // transfer start message
    HCMS_2912_WriteStr("HCMS2912", 0);
}

BCD-SW

ヘッダ

/* 
 * File:   BCD_SW.h
 * Author: fenrir_
 *
 * Created on 2026/08/06, 18:55
 */

#ifndef BCD_SW_H
#define    BCD_SW_H

#ifdef __cplusplus
extern "C" {
#endif


typedef enum _SCROLL_SPEED {
    SCROLL_SPEED_50MS = 0,
    SCROLL_SPEED_100MS,
    SCROLL_SPEED_200MS,
    SCROLL_SPEED_300MS,
    SCROLL_SPEED_400MS,
    SCROLL_SPEED_500MS,
    SCROLL_SPEED_600MS,
    SCROLL_SPEED_700MS,
    SCROLL_SPEED_800MS,
    SCROLL_SPEED_900MS,
    SCROLL_SPEED_1000MS,
    SCROLL_SPEED_2000MS,
    SCROLL_SPEED_STOP
} SCROLL_SPEED;

uint16_t GetScrollSpeed();


#ifdef __cplusplus
}
#endif

#endif  /* BCD_SW_H */

ソース

#include "mcc_generated_files/system/system.h"

#include "BCD_SW.h"

uint8_t _GetBCDSwitchVal(){
    return ((VPORTA.IN & 0xF0) >> 4); 
}

SCROLL_SPEED _GetScrollSpeedFromSW(const uint8_t bcd_sw_val){
    switch(bcd_sw_val) {
        case 0x00: return SCROLL_SPEED_50MS;
        case 0x01: return SCROLL_SPEED_100MS;
        case 0x02: return SCROLL_SPEED_200MS;
        case 0x03: return SCROLL_SPEED_300MS;
        case 0x04: return SCROLL_SPEED_400MS;
        case 0x05: return SCROLL_SPEED_500MS;
        case 0x06: return SCROLL_SPEED_600MS;
        case 0x07: return SCROLL_SPEED_700MS;
        case 0x08: return SCROLL_SPEED_800MS;
        case 0x09: return SCROLL_SPEED_900MS;
        case 0x0A: return SCROLL_SPEED_1000MS;
        case 0x0B: return SCROLL_SPEED_2000MS;
        case 0x0C: return SCROLL_SPEED_STOP;
        case 0x0D: return SCROLL_SPEED_STOP;
        case 0x0E: return SCROLL_SPEED_STOP;
        case 0x0F: return SCROLL_SPEED_STOP;
        default:    return SCROLL_SPEED_STOP;
    }
}

uint16_t _ScrollSpeedToInt(const SCROLL_SPEED ss){
    switch(ss) {
        case SCROLL_SPEED_50MS: return 50;
        case SCROLL_SPEED_100MS: return 100;
        case SCROLL_SPEED_200MS: return 200;
        case SCROLL_SPEED_300MS: return 300;
        case SCROLL_SPEED_400MS: return 400;
        case SCROLL_SPEED_500MS: return 500;
        case SCROLL_SPEED_600MS: return 600;
        case SCROLL_SPEED_700MS: return 700;
        case SCROLL_SPEED_800MS: return 800;
        case SCROLL_SPEED_900MS: return 900;
        case SCROLL_SPEED_1000MS: return 1000;
        case SCROLL_SPEED_2000MS: return 2000;
        case SCROLL_SPEED_STOP: return 1000;
        default:    return 1000;
    }
}

uint16_t GetScrollSpeed(){
    const uint8_t bcd_sw_val = _GetBCDSwitchVal();
    const SCROLL_SPEED scr_spd = _GetScrollSpeedFromSW(bcd_sw_val);
    return _ScrollSpeedToInt(scr_spd);
}

main.c

 /*
 * MAIN Generated Driver File
 * 
 * @file main.c
 * 
 * @defgroup main MAIN
 * 
 * @brief This is the generated driver implementation file for the MAIN driver.
 *
 * @version MAIN Driver Version 1.0.2
 *
 * @version Package Version: 3.1.2
*/

/*
? [2026] Microchip Technology Inc. and its subsidiaries.

    Subject to your compliance with these terms, you may use Microchip 
    software and any derivatives exclusively with Microchip products. 
    You are responsible for complying with 3rd party license terms  
    applicable to your use of 3rd party software (including open source  
    software) that may accompany Microchip software. SOFTWARE IS ?AS IS.? 
    NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS 
    SOFTWARE, INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT,  
    MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT 
    WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, 
    INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY 
    KIND WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF 
    MICROCHIP HAS BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE 
    FORESEEABLE. TO THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP?S 
    TOTAL LIABILITY ON ALL CLAIMS RELATED TO THE SOFTWARE WILL NOT 
    EXCEED AMOUNT OF FEES, IF ANY, YOU PAID DIRECTLY TO MICROCHIP FOR 
    THIS SOFTWARE.
*/
#include "mcc_generated_files/system/system.h"
#include "mcc_generated_files/timer/delay.h"

#include "mcc_generated_files/adc/adc0.h"
#include "mcc_generated_files/adc/adc_types.h"

#include <stdio.h>
#include <string.h>
#include <time.h>

#include "HCMS-2912.h"
#include "BCD_SW.h"
/*
    Main application
*/
#define LOOP_PERIOD_IN_MS 10

typedef enum _DISPLAY_MODE {
    DISP_TIME_NO_SCROLL,
    DISP_TIME_SCROLL,
    DISP_TEMP,
    DISP_STRING_UART,
    DIPS_INIT_MESSAGE,
    DIPS_TEST
} DISPLAY_MODE;

typedef struct _MODE_SETTINGS MODE_SETTINGS;    // 自己参照構造体なので先に宣言

struct _MODE_SETTINGS {
    DISPLAY_MODE mode;
    const bool isMsgSourceFunc;
    const uint8_t* (*UpdateMessageFunc)(MODE_SETTINGS * );
    const uint8_t* msg;
    uint16_t current_start_pos;
    uint16_t msg_length;
    const bool  isScrollArrowed;
    const bool  isTimeoutAllowed;
    const uint16_t timeout_in_ms;
    MODE_SETTINGS* next_mode_settings;
};

// 各モードに必要なバッファとか(静的に確保)
volatile uint8_t str[] = "        Welcome to the Shinkansen. This is the NOZOMI Super Express bound for Tokyo. We will be stopping at Kyoto, Nagoya, Shin-Yokohama and Shinagawa stations before arriving at Tokyo terminal. This train is completely non-smoking. Please refrain from smoking anywhere on the train. The conductor’s room is in car No.8. The oversized baggage area at the rear of some reserved-seat cars and the oversized baggage corner next to the washbasins are only available to passengers who have reserved a specific seat. Passengers who have not made a reservation will not be able to use it. Please use the overhead rack. Please be careful when you put your baggage on the overhead rack, as it may fall. Hazardous items are prohibited on stations and trains. If you notice any suspicious item or behavior, please notify staff immediately. Thank you.        ";

#define UART_MSG_BUF_SIZE   2048
volatile uint8_t uart_msg_buf[UART_MSG_BUF_SIZE];
volatile uint8_t uart_msg_temp_buf[UART_MSG_BUF_SIZE];   // UART受信時に貯めとく用のバッファ

volatile uint8_t clock_noscroll_msg_buf[] = "00:00:00 ";
volatile uint8_t clock_scroll_msg_buf[] = "        2026/08/06 THU 00:00:00        ";
volatile uint8_t temp_scroll_msg_buf[] = "        Temperature:yyy.ydeg        ";

#define SCALING_FACTOR 4096 // Used to get a whole number in the signature row

const uint8_t* UpdateMessageFunc_temp(MODE_SETTINGS * current_mode){
    ADC0_ChannelSelect(ADC0_CHANNEL_TEMPSENSE);
    ADC0_ConversionStart();

    while(!ADC0_IsConversionDone()){}

    const adc_result_t val = ADC0_ConversionResultGet();
    
    // from datasheet
    
    const uint16_t sigrow_offset = SIGROW.TEMPSENSE1; // Read unsigned offset from signature row
    const uint16_t sigrow_slope = SIGROW.TEMPSENSE0; // Read unsigned gain/slope from signature row
    
    uint16_t adc_reading = ADC0_ConversionResultGet(); // ADCn conversion result
    uint32_t temp = sigrow_offset - adc_reading;
    temp *= sigrow_slope; // Result can overflow 16-bit variable
    temp += SCALING_FACTOR / 2; // Ensures correct rounding on division below
    temp /= SCALING_FACTOR; // Round off to nearest degree in Kelvin
    
    uint16_t temperature_in_K = temp;
    int16_t temperature_in_C = temp - 273;
    
    sprintf(current_mode->msg, "        Temperature:%03ddeg        ", temperature_in_C);
}

const uint8_t* UpdateMessageFunc_clock_noscroll(MODE_SETTINGS * current_mode){
    /*
    sec++;
    
    if (sec == 60){
        sec = 0;
        min++;
        if (min == 60) {
            min = 0;
            hour++;
            if (hour == 24) {
                hour = 0;
            }
        }
    }
    
    sprintf(current_mode->msg, "%02d:%02d:%02d", hour, min, sec); */
    
//    system_tick();
    
    time_t nowtime = time(NULL);
    struct tm * cur_time = localtime(&nowtime);
    strftime(current_mode->msg, current_mode->msg_length, "%H:%M:%S", cur_time);
}

const uint8_t* UpdateMessageFunc_clock_scroll(MODE_SETTINGS * current_mode){
//    system_tick();
    
    time_t nowtime = time(NULL);
    struct tm * cur_time = localtime(&nowtime);
    strftime(current_mode->msg, current_mode->msg_length, "        %Y/%m/%d %a %H:%M:%S        ", cur_time);
}

MODE_SETTINGS testmsg = {
    .mode               = DIPS_TEST,
    .UpdateMessageFunc  = NULL,
    .msg                = "        Hello world! with AVR64DD32 and HCMS-2912        ",
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = true,
    .isTimeoutAllowed   = false,
    .timeout_in_ms      = 9999,
    .next_mode_settings = NULL
};

MODE_SETTINGS testmsg_init = {
    .mode               = DIPS_TEST,
    .UpdateMessageFunc  = NULL,
    .msg                = "MODE 5  ",
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = false,
    .isTimeoutAllowed   = true,
    .timeout_in_ms      = 1000,
    .next_mode_settings = &testmsg
};

MODE_SETTINGS uart_msg = {
    .mode               = DISP_STRING_UART,
    .UpdateMessageFunc  = NULL,
    .msg                = uart_msg_buf,// str,//uart_msg_buf,
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = true,
    .isTimeoutAllowed   = false,
    .timeout_in_ms      = 9999,
    .next_mode_settings = &testmsg_init
};

MODE_SETTINGS uart_msg_init = {
    .mode               = DISP_STRING_UART,
    .UpdateMessageFunc  = NULL,
    .msg                = "MODE 4  ",
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = false,
    .isTimeoutAllowed   = true,
    .timeout_in_ms      = 1000,
    .next_mode_settings = &uart_msg
};

MODE_SETTINGS temp = {
    .mode               = DISP_TIME_SCROLL,
    .UpdateMessageFunc  = UpdateMessageFunc_temp,
    .msg                = temp_scroll_msg_buf,
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = true,
    .isTimeoutAllowed   = false,
    .timeout_in_ms      = 9999,
    .next_mode_settings = &uart_msg_init
};

MODE_SETTINGS temp_init = {
    .mode               = DISP_TIME_SCROLL,
    .UpdateMessageFunc  = NULL,
    .msg                = "MODE 3  ",
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = false,
    .isTimeoutAllowed   = true,
    .timeout_in_ms      = 1000,
    .next_mode_settings = &temp
};

MODE_SETTINGS clock_scroll = {
    .mode               = DISP_TIME_SCROLL,
    .UpdateMessageFunc  = UpdateMessageFunc_clock_scroll,
    .msg                = clock_scroll_msg_buf,
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = true,
    .isTimeoutAllowed   = false,
    .timeout_in_ms      = 9999,
    .next_mode_settings = &temp_init
};

MODE_SETTINGS clock_scroll_init = {
    .mode               = DISP_TIME_SCROLL,
    .UpdateMessageFunc  = NULL,
    .msg                = "MODE 2  ",
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = false,
    .isTimeoutAllowed   = true,
    .timeout_in_ms      = 1000,
    .next_mode_settings = &clock_scroll
};

MODE_SETTINGS clock_noscroll = {
    .mode               = DISP_TIME_NO_SCROLL,
    .UpdateMessageFunc  = UpdateMessageFunc_clock_noscroll,
    .msg                = clock_noscroll_msg_buf,
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = false,
    .isTimeoutAllowed   = false,
    .timeout_in_ms      = 9999,
    .next_mode_settings = &clock_scroll_init
};

MODE_SETTINGS clock_noscroll_init = {
    .mode               = DISP_TIME_NO_SCROLL,
    .UpdateMessageFunc  = NULL,
    .msg                = "MODE 1  ",
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = false,
    .isTimeoutAllowed   = true,
    .timeout_in_ms      = 1000,
    .next_mode_settings = &clock_noscroll
};

MODE_SETTINGS startup_init = {
    .mode               = DISP_TIME_SCROLL,
    .UpdateMessageFunc  = NULL,
    .msg                = "        Hello world! with AVR64DD32 and HCMS-2912        ",
    .msg_length         = 0,
    .current_start_pos  = 0,
    .isScrollArrowed    = true,
    .isTimeoutAllowed   = true,
    .timeout_in_ms      = 2000,
    .next_mode_settings = &clock_noscroll_init
};
    
void mode_initializer(MODE_SETTINGS * cur){
    cur->msg_length = strlen(cur->msg);
    cur->current_start_pos = 0; // reset
}

MODE_SETTINGS * current_mode;
uint16_t rest_to_update;

void MODE_SW_InterruptHandler(){
    LED_TEST_Toggle();
    current_mode = current_mode->next_mode_settings;
    
    mode_initializer(current_mode);
    
    rest_to_update = current_mode->timeout_in_ms;
}

void RTC_PIT_InterruptHandler(){
    system_tick();
}


//uart_msg_temp_buf
bool is_buf_add_state;

uint8_t rx_buffer_current_pos = 0;

// https://onlinedocs.microchip.com/oxy/GUID-420E6AAC-9141-47BF-A4C7-A6EA17246D0D-en-US-21/GUID-BC229F28-29AC-46A3-9FAA-1681C2E93A5C.html
// UART Use Case 3: Control Commands - Using Interrupts
void USART0_Rx_InterruptHandler(){
    LED_TEST_Toggle();
    
    if(USART0_IsRxReady())
    {
        const uint8_t read_msg = USART0_Read();
        
        // Echo back
        USART0_Write(read_msg);
       
        if(read_msg != '\n' && read_msg != '\r')
        {
            if (read_msg == '@' && (!is_buf_add_state)){
                is_buf_add_state = true;
            } else if (is_buf_add_state) {
                if (rx_buffer_current_pos < (UART_MSG_BUF_SIZE - 21)) {  // 最後は'\0'のために1文字あける
                    uart_msg_temp_buf[rx_buffer_current_pos++] = read_msg;
                }
            }
        }
        if(read_msg == '\n')
        {
            if (rx_buffer_current_pos < (UART_MSG_BUF_SIZE - 20)) {
                //uart_msg_temp_buf[rx_buffer_current_pos++] = '\0';
                
                uart_msg_buf[0] = ' ';
                uart_msg_buf[1] = ' ';
                uart_msg_buf[2] = ' ';
                uart_msg_buf[3] = ' ';
                uart_msg_buf[4] = ' ';
                uart_msg_buf[5] = ' ';
                uart_msg_buf[6] = ' ';
                uart_msg_buf[7] = ' ';
                
                strncpy(uart_msg_buf + 8, uart_msg_temp_buf, rx_buffer_current_pos);
                
                uart_msg_buf[rx_buffer_current_pos + 8 + 0] = ' ';
                uart_msg_buf[rx_buffer_current_pos + 8 + 1] = ' ';
                uart_msg_buf[rx_buffer_current_pos + 8 + 2] = ' ';
                uart_msg_buf[rx_buffer_current_pos + 8 + 3] = ' ';
                uart_msg_buf[rx_buffer_current_pos + 8 + 4] = ' ';
                uart_msg_buf[rx_buffer_current_pos + 8 + 5] = ' ';
                uart_msg_buf[rx_buffer_current_pos + 8 + 6] = ' ';
                uart_msg_buf[rx_buffer_current_pos + 8 + 7] = ' ';
                uart_msg_buf[rx_buffer_current_pos + 8 + 8] = '\0';
                
                // これを辞めたい
                uart_msg.msg_length = strlen(uart_msg_buf);
                
                uart_msg_temp_buf[0] = '\0';
                
                rx_buffer_current_pos = 0;
                is_buf_add_state = false;
            }
        }
    }
}
    
int main(void)
{
    SYSTEM_Initialize();
    
    // 1s単位の割り込み設定
    RTC_SetPITIsrCallback(RTC_PIT_InterruptHandler);
    RTC_Start();
    
    // タイムゾーン
    set_zone(9 * ONE_HOUR);
    
    // ディスプレイの初期化
    HCMS_2912_init();
    
    // ADC追加の初期化
    VREF.ADC0REF = VREF_REFSEL_2V048_gc; // 誰が設定するのか?よく分からない。温度センサーを使う場合、Vref = 2.048Vにする
    ADC0_Enable();
    
    // MODE SW settings
    MODE_SW_SetDigitalInput();
    MODE_SW_EnableInterruptForFallingEdge();
    MODE_SW_SetInterruptHandler(MODE_SW_InterruptHandler);
    
    // モードリストの初期設定
    testmsg.next_mode_settings = &clock_noscroll_init;
    current_mode = &startup_init;
    
    // モード切り替えまでの残り時間
    rest_to_update = current_mode->timeout_in_ms;
    
    // スクロール速度
    uint16_t tick_period    = GetScrollSpeed();
    uint16_t tick           = tick_period;
    
    // USART0 (PC) の設定
    
    memset(uart_msg_buf, '\0', UART_MSG_BUF_SIZE);
    memset(uart_msg_temp_buf, '\0', UART_MSG_BUF_SIZE);
    
    is_buf_add_state = false;
    
    const uint8_t uart_default_str[] = "        USART display mode. Please sent via USART 115200bps, 8n1        ";
    strncpy(uart_msg_buf, uart_default_str, sizeof(uart_default_str) + 1);
    
    // USART0_TransmitEnable();
    // USART0_TransmitInterruptEnable();
    
    USART0_RxCompleteCallbackRegister(USART0_Rx_InterruptHandler);
    
    printf("Hello, world from AVR64DD32 and HCMS-2912\r\n");
    
    while(1)
    {
        
        // スクロール管理
        DELAY_milliseconds(LOOP_PERIOD_IN_MS);
        
        bool is_timeout         = false;
        
        // timeoutでも更新する
        rest_to_update = (rest_to_update < LOOP_PERIOD_IN_MS) ? 0 : rest_to_update - LOOP_PERIOD_IN_MS;
        
        if (rest_to_update == 0 && current_mode->isTimeoutAllowed){
            is_timeout   = true;
            current_mode = current_mode->next_mode_settings;
            rest_to_update = current_mode->timeout_in_ms;
    
            mode_initializer(current_mode);
        }
        
        bool is_display_update = false;
        
        // tickを更新する
        const uint16_t new_tick_period = GetScrollSpeed();
        
        if ((tick_period != new_tick_period) || (is_timeout)) {
            // 更新されたのでやり直し
            tick_period = new_tick_period;
            tick        = new_tick_period;
            is_display_update   = true;
        } else if (tick == 0) {
            tick        = tick_period;
            is_display_update   = true;
        } else {
            tick = (tick < LOOP_PERIOD_IN_MS) ? 0 : tick - LOOP_PERIOD_IN_MS;
            is_display_update   = false;
        }
        
        if (is_display_update){
            // update content
            if (current_mode->UpdateMessageFunc != NULL){
                current_mode->UpdateMessageFunc(current_mode);
            }
            
            // update display
            if (current_mode->isScrollArrowed) {
                // const uint16_t msglen = strlen(current_mode->msg);
                
                if (current_mode->current_start_pos < (current_mode->msg_length - 7)) {
                    current_mode->current_start_pos++;
                } else {
                    current_mode->current_start_pos = 0;
                }
                
                HCMS_2912_WriteStr(current_mode->msg, current_mode->current_start_pos);
            } else {
                HCMS_2912_WriteStr(current_mode->msg, 0);
            }
        }
    }
}

[HMCS400] HD614P080S (2) Timer

Timerを使ってLチカ。なお、これでHMCS400は一旦終了。

HMCS400 Lチカ

目次

概要

Timer概要

8-bit、2系統あるTimerです。

A-chはフリーランのみ、B-chは外部入力(#INT1ピン)との協調や、プリセット値のリロードなどに対応しています。

4-bitマイコンと言えども、Timerとして実用に耐えるのは8-bitということなのでしょう。16だけ数えられてもね。知らんけど。

なお、11-bitのプリスケーラーが付いていて、周期は19-bitまで落とせます。かなり分周できます。デフォルトが÷2048設定です。4.5MHzのセラロックを使っているので、オーバーフロー割り込み周期は4.5MHz ÷8 ÷2048 = 274.65…Hzとなります。

フルでカウントする場合、274 / 256 = 1.07… Hz、0.93秒ごとにオーバーフローとなります。

Timer ブロック図

Timer設定レジスタ

Timer Mode Register A & Bによって設定できます。といっても簡素なもので、4-bitのうち3-bitがプリスケーラーの分周比設定ビットで占められています。

A-chはこれしか設定するものがありません。B-chは、その他にオートリロードのON/OFFビットがあります。

なお、デフォルトでTMA = TMB = $000です。すなわち、最大分周比になっています。

Timer Mode Register

割り込みフラグ・割り込みマスクと、全体の割り込み許可は、Interrupt Control Bit Area ($000~$003)で設定します。

\$001のIFTA・IMTAが、それぞれTimer Aのフラグとマスクです。全体割り込み許可は、$000のbit 0のI/Eです。

Interrupt Control Bit Area

プログラム

  • 割り込みビットを設定する
  • 割り込みベクタにジャンプ命令を置いておく
  • 割り込みルーチンでは、LEDを+1して割り込みから戻る(RTNI命令)

というだけの簡単なプログラムです。

初期設定以外ではAレジスタを使わないので、割り込みルーチンではAが保存されている前提で書いています。

RAM上のビットのセット/リセットは、SEMD・REMD命令によって行っています。割り込み許可(1 → I/E)にも、EIのような専用命令はありません。

HMCS 40時代には、それぞれが専用命令(例えばSET I/EならSELE)があったようですが、HMCS 400では汎用になっています。

即値加算はAレジスタに対してのみ可能です(AI命令)。そのせいか、逆にインクリメント命令は、Bレジスタに対してのみ用意されていて(IB命令)、Aレジスタには出来ません。そのため、AI 1でインクリメントを代用しています。

なお、最初、割り込みルーチン内で今のLED出力値を取得するためにLAR(Load A from R Port)を使っていましたが、ポート6は出力専用のため、うまくいきませんでした。

        cpu HD614081

; --------------------------------------------------------------------------------------------------------------
; constants
; --------------------------------------------------------------------------------------------------------------
ICB0     EQU      $000   ; Interrupt Control Bit Area 0 (IM0, IF0, RSP, I/E)
ICB1     EQU      $001   ; Interrupt Control Bit Area 1 (IMTA, IFTA, IM1, IF1)
ICB2     EQU      $002   ; Interrupt Control Bit Area 2 (Not used, Not used, IMTB, IFTB)
ICB3     EQU      $003   ; Interrupt Control Bit Area 3 (Not used, Not used, IMS, IFS)
PMR      EQU      $004   ; Port Mode Reg.
SMR      EQU      $005   ; Serial Mode Reg.
SRL      EQU      $006   ; Serial Data Reg. Lower
SRU      EQU      $007   ; Serial Data Reg. Upper
TMA      EQU      $008    ; Timer Mode Reg. A
TMB      EQU      $009    ; Timer Mode Reg. B
TCBL     EQU      $00A ; Timer/Event Counter B Lower (if Read)
TCBU EQU      $00B ; Timer/Event Counter B Upper (if Read)
TLRL     EQU      $00A ; Timer Load Reg. Lower    (if Write)
TLRU EQU      $00B ; Timer Load Reg. Upper    (if Write)

; --------------------------------------------------------------------------------------------------------------
; RAM variable
; --------------------------------------------------------------------------------------------------------------

; --------------------------------------------------------------------------------------------------------------
; Interrupt vectors
; --------------------------------------------------------------------------------------------------------------
    ; Reset vector
    org $0000

;  JMPL    START   ;    16/   0 : 0250 0040                    JMPL    START
    DATA $0150 | (START >> 10)
    DATA START & $00FF

    ; Timer-A IFR
    org  $0006

;  JMPL    START   ;    16/   0 : 0250 0040                    JMPL    START
    DATA $0150 | (TMAOVF >> 10)
    DATA TMAOVF & $00FF
    

; --------------------------------------------------------------------------------------------------------------
; main routine (init)
; --------------------------------------------------------------------------------------------------------------
    org  $0040
TMAOVF:
    REMD 2, ICB1; Reset IFTA

    ; LED +1
    AI   1
    LRA  6

    RTNI     ; 1 <- I/E, CA restore 

START:
    LAI      7
    LRA      6

    REMD 2, ICB1; Reset IFTA
    REMD 3, ICB1; Reset IMTA
    SEMD 0, ICB0 ; Set I/E

LOOP:

    SEC      ; set carry
    TC       ; test carry
    BR       LOOP

    SBY

    END
    

[HMCS400] HD614P080S (1) LEDを光らせる

LEDを光らせるまで(1日かかった)

HD614P080SでLEDを光らせる

目次

説明

P6に接続したLEDを、プログラムで指定したパターンで光らせます。

Lチカは…Timerを使わないと、4重ループとかになって大変なので、やっていません。

指定したパターンを変えれば、LEDもそれに合わせて変わるので、それを以てプログラムが動いていると思うことにします。

回路

64ピンもあるため、KiCadのシンボルを、KiCadルールに(概ね?)従って作ったら、あまりにも縦長になりました…。

inputのみのP6に、LEDを4つ付けています。このMCUはポートによってハード構成が違うので注意が必要です。しかもオープンドレインがほとんどで、ポートによってPMOSかNMOSかが違います。

RESETが正論理なので注意です。最初逆にしてしまいました。RESETの時定数はよくわからず長めにしてあります。

OSCの間の抵抗も必要です。最初、これをつけなかったところ発振しませんでした。大体無くても動く事が多いのですが…このMCUは違うようです。

回路図

組み立て

シュリンクピッチなので、サンハヤトのICB-01Bに組みました。

aitendoの1.778mm → 2.54mmピッチ変換基板を使ったうえで、普通のユニバーサル基板に組み立てるのでも良かったかもしれない。

なお、電源のインジケーターとして、マルツで在庫処分セールだったLED EMPY3368Sを使ったら、史上最強に光が弱かった。

基板

プログラム

これが今回の鬼門でした。

ソースコード

まずはプログラムを記します。JMPL・CALLをコメントアウトしている理由は後述します。

これを実行すると、LEDが1つおき($A)に光ります。

        cpu HD614081

; --------------------------------------------------------------------------------------------------------------
; Interrupt vectors
; --------------------------------------------------------------------------------------------------------------
    ; Reset vector
    org $0000
;  JMPL    START   ;    16/   0 : 0250 0040                    JMPL    START
    DATA $0150 | (START >> 10)
    DATA START & $00FF

; --------------------------------------------------------------------------------------------------------------
; main routine (init)
; --------------------------------------------------------------------------------------------------------------
    org  $0040
START:
    LAI      $7
    LRA      $6
LOOP:
;  CALL    TEST        ;      30/  44 : 0260 004C                      CALL    TEST
    DATA $0160 | (TEST >> 10)
    DATA TEST & $00FF

    SEC      ; set carry
    TC       ; test carry
    BR       LOOP

    SBY

TEST:
    LAI  $A
    LRA  $6
    RTN

    END

一応、出来上がった.hexも置いておきます。

:10000000100A00021F1F1F1F1F1F1F1F1F1F1F1F60
:100010001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1FF0
:100020001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1FE0
:100030001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1FD0
:100040001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1FC0
:100050001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1FB0
:100060001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1FA0
:100070001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F90
:1000800017111616000B08020F070F03021A0C0AAD
:060090001A111616100003
:10400000100A00021F1F1F1F1F1F1F1F1F1F1F1F20
:104010001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1FB0
:104020001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1FA0
:104030001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F90
:104040001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F80
:104050001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F70
:104060001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F60
:104070001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F50
:1040800017111616000B08020F070F03021A0C0A6D
:064090001A1116161000C3
:00000001FF

実行に際しての問題

さて、プログラム周りで問題が2つ発生しました。

  1. ピギーバックのROM関連
    • そもそもこのMCUは1 word = 10-bitである。8-bitのメモリを外付けする際は、10-bitを5-bitずつに二分して、下位バイト、上位バイトの順に格納しないといけない
    • ピギーバックROM = 27128の想定で、27256のA_{14}に相当するピンは常に'1'。よって27256を載せるためには0x4000のオフセットが必要(と思う)
  2. Macroassemblerにバグ?がある

それぞれ、対策が必要です。以下で詳述します。

問題1. ピギーバックROM用の.hexファイル変換

まず1.について。

(前提)ピギーバックROMへの格納ルール

以下のメモリマップのとおりで、EPROM ADDRESS $0000・$0001を合わせて、MCU ROM ADDRESSの $0000 として扱います。

(これならもう1ワード = 8-bitにすればよかったのでは?と思うのですが、10-bitにすることによるメリットの方が上回ったということなのでしょう。あまり思いつきませんが、4-bit即値をop codeで扱いやすい、とかでしょうか)

ROM マップ

Macroassemblerが吐き出す.hexを見てみる

では、Macroassemblerがどんな.hexを吐き出すのでしょうか。

結果は以下のとおりで、分割はなされず、あたかも1ワード = 2バイト(16-bit)であるかのように扱います。オプションでいいように変換出来るのかもしれませんが、設定方法が分かりません。

例えば、LAB命令は機械語で0b0 0010 0 1000です。これを半分にしてROMに格納すると、先に下位バイトの0b0000 1000 = 0x08、ついで0b0000 0010 = 0x02です。 しかし、実際にMacroassemblerが吐き出すのは0x48です。これでは0b 00 0100 1000になってしまいうまくロードされなさいです。何らかの方法で変換する必要があります。

   32/   0 :                             org $0000   
   33/   0 :                            start:
   34/   0 : 023F                           LAI     $0F
   35/   1 : 0048                           LAB     ; 0b00010 01000 = 0x08 0x02
   36/   2 :                                END

Pythonで.hexファイルを変換

ということで、Pythonで変換することにしました。

幸い、1ワード = 2バイトと思ってMacroassemblerが吐き出しているので、1ワードの中でのビット操作だけすればよいです。これが、Macroasseblerが1ワード10ビットにしていたら、却って処理が大変だったと思います。

↓のようなPythonスクリプトを書きます。hexの処理は、intelhexというライブラリに投げています。

.hexを処理するの面倒だなあ…と思っていましたが、ライブラリがあり助かりました。

※最初のloadhex()の引数を、.hexのファイルパスに変更する必要があります

27256のA_{14}問題は、念のため0x0000と0x4000の両方に同じコードを書き込むことにしました。

from intelhex import IntelHex
import itertools
import os
import time

ih = IntelHex()
ih.loadhex("D:\\Users\\XXXXXX\\HMCS400(HD641P080S)\\aswcurr\\HD614P080_test.hex")

print("Conversion start.\r\nhex loaded.\r\n")
print("loaded hex dump:\r\n")
print(ih.dump())

barr = ih.tobinarray()

# Converting sample
#
# 1. LAI    $0F
# 0x23F = 0b10 0011 1111
# -> 0bxxx10001, 0bxxx11111 = 0x11, 0x1F = 17, 31
#
# 2. LAB
# 
# 0x048 = 0b00 0100 1000
# -> 0b00000010, 0b00001000 = 0x02, 0x08 = 2, 8

barr2 = []

for i in range(0, len(barr), 2):
    # concat 2 byte
    dbyte = barr[i+1] * 256 + barr[i]

    # separete for 5-bit & 5-bit
    highbyte = min(int(dbyte / 32), 31)
    lowbyte = (dbyte % 32)

    # print(i, highbyte, lowbyte)

    # add to list
    barr2.append(lowbyte)
    barr2.append(highbyte)


# generate hex
idst = IntelHex()
idst.frombytes(barr2)
idst.frombytes(barr2, offset=0x4000)        # add 2 times because A14 is always Vcc

print("converted hex dump:\r\n")
# print(idst.dump())                    # do not display because it's too long

idst.write_hex_file('converted_hex.hex')

print("hex file \"converted_hex.hex\" generated. END\r\n")

converted_hex.hexというファイルが出来るので、ASのアセンブル用.batの最後に以下の1文を追記します

python.exe ToPiggypackROM.py

問題2. MacroassemblerのJMPL/CALLに関するバグ?

バグなのか、それともHD614P080とHD614P081の違いなのか。

単純で、Long jumpであるLJMP、CALLの機械語が違います。

最初、このことなど知る由もなく、動かなくて随分悩みました。JMPLを書かずに、リセットベクタにLAI・LRAを書いたら動いたので、原因を何とか特定できました。

(前提)UMにおける定義

まず、UMの説明は、JMPLの上位ワード = 01 0101 p3p2p1p0 = 0x015P です。メモリに格納するなら、01010 0p3p2p1p0の分割で、0x0P, 0x0Aになります

分岐系命令の機械語(1988 UM p.51)

Macroassemblerの吐き出す機械語

このようなコード

 ; Reset vector
    org $0000
    JMPL START

    org  $0040
START:

に対して、以下の.lstが吐き出されます。

16/   0 : 0250 0040                      JMPL    START

0x0040への移動なら、機械語は0x0150 0040になるはずが、0x0250 0040になっています。

このため、cpu HD614081でアセンブルすると、JMPLとCALLが変になります。

対策

1. 直書き

幸い、ワード長は変わらないので、

  1. まずJMPL/CALLで普通に書く
  2. .lstを見る
  3. 出てきた機械語をDATAで定義し、JMPLなら$0250 → $0150にする
  4. 元のJMPL/CALLはコメントアウトする

とやれば動きます。

先の例で行くと

 ; Reset vector
    org $0000
;  JMPL    START   ;    16/   0 : 0250 0040                    JMPL    START
    DATA $0150
    DATA $0040

生成されるコードは

   17/   0 : 0150                        DATA    $0150
   18/   1 : 0040                           DATA    $0040

2. マクロを使う

Macroassemblerは高機能なので、以下のように書くこともできます。

ResetのJMPLならアドレス固定だからよいでしょうが、コード中となるとジャンプ先アドレスが変わることも多いでしょう。そんなときに、いちいち.lstからコピペして変換して…とするのは面倒です。この方法なら一度書いておけば、ジャンプ先アドレスが変わっても対応不要です。

   30/  44 :                            ; CALL    TEST        ;      30/  44 : 0260 004A                      CALL    TEST
   33/  44 : 0160                        DATA $0160 | (TEST >> 10)
   34/  45 : 004A                          DATA TEST & $00FF

今後

どうしましょう。とりあえず動かしてはみましたが…。

このMCUに載っているペリフェラルは、以下で全てです。

  • Timer x2
  • SPI(モード3固定?)
  • 割り込み(割り込み源は5つ)
  • 省電力モード(Standby, Stop)

SPIがUARTとして転用可能なのかもよく分かりません。Timerと割り込み使うくらいかなあ…。

ポートは沢山ありますけどね…。使い道に悩む。

窓付きではなくピギーバック・タイプなので、AT29C256を使ってイレース時間なしでスムーズに触れるのは良いのですが。

むしろ、特筆すべきは蛍光表示管を駆動可能なポートがあることで、余っている2桁の蛍光表示管をどうにか組み合わせられないだろうか…

[HMCS400] HD614P080S (0) 下調べ

(書きかけです)

目次

入手まで

若松通商にて、HD614P080Sなるマイコンが売っています。前までありましたっけ…?

高いですが、珍しいのと、開発環境・ドキュメントがあることは確認できたので、買ってみました。

若松通商のページ

マイコン概要

HMCS400シリーズの1つ。

特徴

  • メモリのデータ幅が10-bit
  • 高耐圧(Vcc - 40V)ポートあり
  • メモリ配置とアドレッシングモード
  • I/Oは基本オープン・ドレイン
  • 2種類あるI/O(DポートとRポート)

リソース

  • データシート:ネットにあります
  • User's manual:ネットにあります
  • アセンブラ:Macroassemblerが対応しています(cpu = HD614081)

ブロック図

レジスタ

アキュムレータ・マシンで、レジスタはA、B、X、Y、Wと、SPX、SPY、PCがあります。6800に似てますね。

ステータスフラグがかなり少なく、Cと、OVF・NZ兼用のSTしかありません。意味するものが前の命令によって違います。

名称 幅 用途
A・B 4-bit ALUの結果保持、データ転送のsrc/dst
W 2-bit RAMの間接アドレス指定用。書き込み専用
X・Y 4-bit RAMの間接アドレス指定用。YはDポートのアドレスにも使う
SPX・SPY 4-bit X・Yレジスタの補助
CA 1-bit ステータス(キャリー)
ST 1-bit ステータス(オーバーフロー、ノン・ゼロ)
PC 14-bit プログラム・カウンタ
SP 10-bit スタック・ポインタ。上位4-bitは'1'固定で$030 ~ $03Fを指し示す。1回のpushで-4される

レジスタ以外

4-bitマシンなのでI/Oは4つずつ並んでいます。

ペリフェラルは、Timer x2、外部割り込み、クロック同期式シリアルのみです。シンプルですね。

ブロック図を眺めると、同じ日立の6800系のHD63701や、NECで同じ4-bitのμ75CG08Eに似ているなと思います。

ただ、64-SDIPなのでI/Oの数は圧倒的にこちらの方が多いです。

システム制御用のピンはRESET、OSC1、OSC2、#TESTのみ。このあたりのシンプルさもHD63701に似ています。

やや特殊なものとしては、EPROMからの読み出しポートに、10-bitの命令を2byteに分けて格納しているため、Instruction decoderの手前にLower 5-bitsのラッチが付いている点。

ブロック図

メモリ・マップ

ROM

ROMは4セクションに分かれます。1ワード = 10-bit

なお、外付けEPROMの扱いは特殊です。外付けEPROMの2ワードがMCUにとっての1ワードに相当します。

例えば$0000と$0001のそれぞれ下位5-bitの内容を、結合して10-bitとし、MCUの$0000に対する内容として扱われます。

アドレス(MCU) 用途 設定する値
$0000 ~ $000F 割り込みベクタ・アドレス JMPL命令のop code(2ワード)
$0010 ~ $003F ゼロ・ページ・サブルーチン op code
$0040 ~ $0FFF パターン
$1000 ~ $1FFF プログラム op code

RAM

RAMは4セクションに分かれます。1ワード = 4-bit。

576 ワード x 4-bitと少し中途半端な容量です。アドレスは1k ワード分を前提としています。HD407419・HD404019は、ROM 16kワード、RAM 992ワードです。

アドレス 用途 コメント
SFR $000 ~ $01F SFRは僅か11個($00C ~ 01Fは未使用)
Memory register $020 ~ $02F アドレッシング・モードで優遇あり
データ $030 ~ $21F -
未使用 $220 ~ $3BF -
スタック $3C0 ~ $3FF 固定で64 Word分確保。スタックは後ろからということで、$3FF付近に配置されています。なお、スタックは1回の退避で4アドレス分使います(PCが14-bitのため。加えて、STとCAを退避)。よって、64 アドレスですが16回積むのが限界

ROM/RAM メモリ・マップ

命令

UM p.48 §3.3 Instruction Setより

ここが一番独自色が強いのではないでしょうか。

  • 99命令
  • op codeは1か2ワード構成(10-bit = 1ワード)
  • 実行は1~3サイクル

かなりシンプルです。

命令表は以下のとおりですが、前身であるHMCS40との比較があるのがちょっと面白いです。

乗除算は流石にありません。4-bitだし。

割り込みのフラグ指定は全部違う命令になっています。また、シリアル転送開始命令 STSや、タイマー/カウンター操作命令 LTI、LTA、LATなどがあるあたり、完全にメモリ操作でSFRを扱うようにはなっておらず、若干こなれてない感じがします。

レジスタで述べたように、フラグ・レジスタはあんまり数がなく、それゆえ更新されることも少ないです。

I/OはDポートとRポートで扱いが異なります。Dポートはbit操作可能です。

あと、1文字ニーモニック Pは珍しい気がします。MIPSのJ(ジャンプ)、ARMのB(といってもフラグ付きだと思うが…)は知っていますが、他は思いつきません。

…やっぱ6800の方が扱いやすいように見えます。6800って相当綺麗ですよね。

UM pp.54-55

アドレッシングモード

UM p.43 §3.1・3.2より

RAM

メモリ・レジスタは1 ワード命令・1サイクルのLAMR・XMRAでAと交換できるので、少しだけRAM一般へのアクセスよりも有利です。

アドレッシング・モード アドレス指定方法
レジスタ間接 W & X & Yレジスタを結合した10-bitでアドレス指定
直接 op codeの即値をそのままアドレスとして扱う。1ワード 10-bitなので1ワードでアドレス指定可能
メモリレジスタ $020~$02Fは op code の下4-bitで指定可能

ROM

アドレッシング・モード アドレス指定方法
直接 14-bit 即値
ページ内直接 8-bit 即値でPCの下位8-bitを置き換え。ページは256 ワード単位。境界でBR命令実行する場合を除く
ゼロ・ページ内直接 6-bit 即値で$0000 ~ $0030を指定
テーブル・データ op codeの4-bit即値 & Bレジスタ & Aレジスタで$0000~$0FFFを指定
P命令 テーブル・データを参照し、その内容に応じてA・Bレジスタ かつ/または R1・R2 ポート出力レジスタを更新

I/Oポート

入出力の区別はありません。8051方式というべきか。

高耐圧ピンは、PMOSのオープン・ドレインです。入力として使うには、出力を'0'とする必要があります('1'だとピン=Vccになって意味がない)

高耐圧ピン以外のピンは、NMOSのオープン・ドレインです。入力として使うには、出力を'1'とする必要があります('0'だとピン=GNDになって意味がない)

#SCK・SOだけはプッシュプルに見えますが、データシート上は"NMOS Open Drain"と書かれています。

I/Oポートの回路 データシートより

クロック

システム・クロック = 発振周波数の1/8となります。

リセット時の値

ポートは全てOFFです。

UM Table 2-21より

[MCS-96] Intel 8096 (3?) アセンブリのつまりどころ

引っかかった点

目次

ストア命令のオペランドの並びはsrc, dstで、ロード命令と逆

ロード命令はLD(B) dst, srcなので油断していました。

× STB dst, src
○ STB src, dst

データシートではオペランドの並びまでは書いていないのでわからず、Embedded Controller Handbookを見てようやく気付きました。

最初、全く気にもしなかったので、ロジアナでアドレスバスを見たり、アセンブル結果の機械語やヘッダの番地を確かめたりして、解消に3時間位かかりました。

もはやSFRのアドレスが違うのでは?と最初から最後までSFRをCLRB命令でCLRしたところ、ポートが動き、そこでようやく「別の命令なら動く」とわかり、それが解消の糸口となりました。

しかし…ADDでもなんでも、左がdst、右がsrcに思うので、直感に反した並びだと思います。

反論として、第一オペランドがCPUに近い方、第二オペランドがCPUから遠い方ってのはあるかと思いますが…。

LDB, STB命令詳細

BRは間接ジャンプ

最初、SJMP・LJMP命令の存在に気づかず、無条件ジャンプはBR命令だと思っていました。

困ったことにBR命令はオペランドに任意のメモリを取り、例えばラベルをオペランドに書いてもアセンブル出来てしまいます(BR LOOPのように)。

しかし実際はBR (LOOP) を実行することになるので、これでLOOPラベルの場所に飛ぶと思っていると大間違いです。

正解はSJMP/LJMP LOOP。

ループごとにLEDを+1しようとしたところ、最初の+1で止まってしまったので困ってしまったわけですが、まさかただのジャンプが(しかもアセンブルが通る)問題だとは思っていなかったので、解消に時間がかかりました。

DELAYで代入の手前まで戻ってしまう

これは自分のミス。なぜ気付かなかったのかと思うほど。何も考えずに書いてたんでしょうね…。

ジャンプ命令 JNE を介すと挙動が変になるのでジャンプを疑っていましたが、そうではなくて自分のプログラムフローのミスでした。アセンブラだとありがち。

問題のコード(毎回代入するため無限ループとなる):

DELAY2:
    LDB  R2,  #07FH
    SUBB R2, #001H
    JNE  DELAY2

正しいコード:

 LDB  R2,  #07FH
DELAY2:
    SUBB R2, #001H
    JNE  DELAY2

[MCS-96] Intel 8096 (2) 最小構成のボードを作る

やはり動かさなければ、ということで最小構成のボードを作りました。

とりあえずLEDをチカらせる

目次

説明

IntelのApplication Note AP-248 "Using The 8096" (by Ira Horden, 1987) に、"5.0 HARDWARE EXAMPLE" - "5.1. EPROM Only Minimum System" という最強の例が載っています。

ほぼそのままパクリで作りました(多分これがなかったら作れていなかった。他力本願)。

「最小構成」とわざわざ書いてあるのは、上のANに"Minimum"と入っているからです。

ボード概要

オンボード・デバイス:

デバイス 型番 メーカー 説明
CPU A8096-90 Intel
RAM - - 0kB(MCU内蔵のレジスタファイル 232Bのみを使用)
ROM M27C4002-10F6 STMicro 256kB × 16-bit

デバイス詳細

CPU(A8096-90)

8096といっても色々あるようですが(世代別には8096, 80C196とあり、8096世代でも8096(ROMなし)・8396(ROM内蔵)がある。BHとつくものとつかないものもある)、今回使うのは、若松で販売しているものです(@1,390円)。

株式会社若松通商 / A8096-90 https://wakamatsu.biz/products/detail/456050

若松(https://wakamatsu.biz/products/detail/456050)

スペックは、ハードウェア面に限って書くと、

  • 16-bitマイコン
  • 5V単一電源
  • 水晶 6-12MHz
  • ROMなし
  • PGAパッケージ 68ピン

PGAソケット

で、PGAパッケージを今の時代に扱おうとすると地味に困るもののひとつが、このPGAソケットが入手困難なことです。

幸い、68ピンはまだ若松に在庫があったので、これを買って使っています。

株式会社若松通商 / PCPS-68-001 https://wakamatsu.biz/products/detail/455601

若松の写真だと解像度が低く分からないですが、表面に"PCPS-A68" "JAE"と書かれています。

…PGAソケットが入手できなくなったら、2.54mmピッチであれば、「丸ピンIC用ソケット (シングル40P)」などを切って並べるんですかね。

PGAソケット PCPS-68-001

RAM(なし)

最小構成なのでありません。

RAMとしては、内蔵のレジスタファイルの232Bを使うことにします。

どうせテスト程度でしか使わないので、十分だと思います。

ROM(M27C4002-10F6)

8096が16-bit MCUで、ROMは内蔵していないので、外にROMを用意する必要があります。

8096BHであれば、データ・バス = 8-bitにも設定できるのですが、8096-90はデータ・バス = 16-bit固定…だと思います。

AP-248の構成例では、2764 or 27128を2個使っています(下位バイト分と上位バイト分)。

が、2個を焼くのは面倒だと思い、秋月で売られているデータ幅 = 16-bitのUV-EPROM M27C4002-10F6を使うことにしました。

UV-EPROM M27C4002-10F6: 半導体 秋月電子通商-電子部品・ネット通販 https://akizukidenshi.com/catalog/g/g115078

ただ、やってて思ったのは、やはり消去の待ち時間が非効率。2個このROMを持って交代交代に使っていますが、それでも待ちます。しかし困ったことに16-bitの良さそうなEEPROMが見つかりません。結局、いつも使っているAT29C256 x2で良かったのではないか…と今は思っています。

ちなみに、秋月のページには「読出時間:45ns」と書いてありますが、それは最速品の場合であって、-10F6だと100nsだと思います。

アドレス・ラッチ(74HCT373)

アドレス・データ共用なのでラッチが必要です。Intelも8212?のようなラッチではなく、373を使っています。

ここで、A8096-90の出力はTTLレベル(2.0V / 0.6V)のようなので、一応74HCT373を使いました。

HCTシリーズってあまり入手性が良くなく、できればHCシリーズで済ませたいんですけど…。仕方なし。

樫木総業や共立、マルツでも入手できると思いますが(樫木がお安め)、送料節約のため、若松でついでに買いました。HCTはSNとTAを良く見かけます。

回路

Github : https://github.com/fenr1r/MCS-96_MinimalBoard/tree/main/KiCad_MCS-96_MinimalBoard

回路図

(ツェナー D14に抵抗 R5を挟んで接続したのは間違いでした…R5 → 0Ωの方が機能しそう)

回路図

以下、説明

8096まわり

電源関連

ピンが幾つかあります。

信号名 処理 コメント
V_{CC} +5V -
V_{PD} +5V CMOS RAM保存用の電源ピン
V_{SS} GND -
V_{REF} +5VかGNDをピンヘッダで選択 お好みだが、「通常+5V」と書いてあるので選択式とした。アナログポートはVrefを超えて入力してはいけないので、その意味でも+5Vにしたほうが保護になる
V_{BB} 0.01μFを挟んでANGND
ANGND GND -

タイミング制御線(出力)

信号名 方向 処理 コメント
#WR OUT 接続先なし(N.C.) 今回はROMのみ外付けで、書き込み(RAM)がないため
#RD OUT ROMの#OEに直結 #CEに直結するという手もあるが、今回は#OEコントロールドにする(このほうが一般的なような?)
#BHE OUT 接続先なし(N.C.) Byte High Enable信号で、16-bit特有。RAMアクセス時、上位バイトと下位バイトそれぞれの書き込み有無を示すための信号(A0と#BHEで判別する)。ROMアクセスだけなら、読まないバイトはMCU側で無視するので不要
ALE OUT 74HCT373へ アドレスラッチ用
CLKOUT OUT 接続先なし(N.C.) WAITサイクルを用いないため、使わない。なお、デューティ比33%で出てくる。

その他制御ピン

信号名 方向 処理 コメント
#RESET IN いつもの 定数はApplication Noteに従った(思考停止だが外れない)。一応DSに"input low for at least 2 state times"とあり、これに従えば12MHz × 3clock (=1state) × 2 = 0.5μs。R = 33kΩ、C = 22μFでは0.7sくらいの時定数なのだが…
NMI IN プルダウン・スイッチ 正論理。一応試せるようにした
READY IN プルアップ Lowにすると、WAITサイクルが挟まるっぽい。今回使うメモリはアクセスタイム100nsで十分早いし、READYの処理も面倒なので使用していない
#EA IN プルダウン 外部メモリからプログラムを読むため、落とす。内部でプルダウンされているらしいが、一応外付け回路も用意した。
#TEST IN プルアップ Intelのテスト用

I/O

アドレス・データバス(P3・4)

アドレス・データバスは共用なので、74HCT373でALEのタイミングでラッチします。16-bitだとラッチ入れるだけでも2個必要で面倒ですね…。

その他I/Oポート(P0・1・2・HSO・HSI)

ポート 方向 処理 コメント
P0 / アナログポート 入力 保護回路(ダイオード・抵抗) アナログだけは一応入力オーバーしないようにした
P1 入出力 LEDを接続 LEDのテスト用
P2 入出力 P2.0-1はUARTへ
P2.2-7はプルダウン付きピンヘッダへ
P2.2はEXTINT、P2.5はPWM
HSO・HSI 入力 / 出力 プルアップ付きピンヘッダ ―(なんでプルアップにしたんだろう)

結果として、オンボードで以下のお試し用のパーツがくっついています。

ポート 接続しているデバイス コメント
ACH0/P0.0 半固定抵抗 10kΩ つまみやすいものにしましたが、ピッチが2.54mmじゃなくて使いにくかった…
P1.0-7 LED x8 シンク接続。デバッグ用
TXD/P2.0・RXD/P2.1 USB-UART用コネクタ FTDIのアダプタに合わせたピン配置
EXTINT/P2.2 プッシュスイッチ -
NMI プッシュスイッチ -
PWM/P2.5 LED -

ROM周り

アドレスバスの接続

まず、アドレス入力は、A0の入力にCPUのA1を、A1の入力にCPUのA2を、…というように、1ビットシフトして接続します。

CPUは、1ワード = 1アドレス = 8ビットで扱います。

一方、このメモリは1ワード = 1アドレス = 16ビットで扱います。

そのため、CPUの2アドレス分 = メモリの1アドレス分となるので、アドレスを1ビット・シフトしてあげる必要があります。

その他

A15-17はGNDに落としています。そもそも8096のメモリ空間が64kBしかないため。

#CE(#E)はGNDに落とし、常時ONにしています。

#OE(#G)は#RDで制御します。

Vppですが、回路図上はVccとなっていますが、GNDに落としています。どちらでもいいようなので。また、VccにするとCheck IDになってしまいそうで

電源

逆電圧防止にD13を入れています。

過電圧防止にツェナーを入れています。これで本当に有効なのだろうか…

その他

オシロやロジアナ、テスターが使いやすいよう、テストポイントやピンヘッダなどを右上に用意。

パターン

Github : https://github.com/fenr1r/MCS-96_MinimalBoard/tree/main/KiCad_MCS-96_MinimalBoard

基板サイズは、横 113.79 mm × 縦 96.52 mmとしました。

JLCPCBは、100 x 100 を超えてもすぐに滅茶苦茶値段が跳ね上がるわけではないので、やりやすいサイズにしました。

載せるICも最小限なので、作るのも比較的簡単でした。Z8のような苦労はなかった。

ピンが多くなってくると中のピンから信号を引き出せなくな(り多層基板にしないといけなくなる心配があ)りますが、幅2列なので特に心配ありませんでした。

真ん中に8096が来ますが、パターンを引きやすい向きにした結果、横向きになってしまいました。ちょっと不満。

PGAソケットは面倒で外形線(Courtyard)を引いていません。1:1スケールで印刷し、周囲の部品と干渉しないことを確かめてOKとしました。

図

基板パターン 表 / 裏

組み立て

基板発注はJLCPCBでした。

ハンダ付けは特に苦労することなし。

LEDは黄緑色、低輝度のものを選びました。それでも5V / 2.2kΩ = 2.2mAで十分明るいです。

なお、このLEDは黄緑色を選んだはずなのですが、波長が560nmで、一般的な黄緑色LED(570nm)よりもすこし緑寄りです。何なら届いたパッケージには「緑色LED」と書かれていました…。

黄緑色チップLED 1608 560nm 6.3mcd 140° SML-E12P8WT86: オプトエレクトロニクス 秋月電子通商-電子部品・ネット通販
https://akizukidenshi.com/catalog/g/g111878/

ボード

サンプルプログラム

お楽しみのプログラムタイム。アセンブリで書いて、Macroassemblerでアセンブルします。

コード

"i8096_test.asm"

ソフトウェア的にDELAYしながら、ポートに繋がっているLEDをインクリメントしていって点滅させます。

        cpu 8096

    include "include\reg96.inc"

R1   equ  020H
R2   equ  022H
R3   equ  024H
; --------------------------------------------------------------------------------------------------------------
; Memory map (64kB space)
; 0000H - 0017H    : Register-mapped I/O (SFRs)
; 0018H - 0019H    : Stack Pointer
; 001AH - 00FFH    : Internal Register File (RAM 232B)
; 1FFEH - 1FFFH        : Port 3 and 4
; 2000H - 2011H    : Interrupt Vectors
; 2012H - 207FH    : Factory Test Core
; 2080H            : Reset Location
; 2080H - 3FFFH    : Internal program strage ROM
; 4000H - FFFFH    : External memory or I/O
; --------------------------------------------------------------------------------------------------------------
    

; --------------------------------------------------------------------------------------------------------------
; Interrupt vector
; --------------------------------------------------------------------------------------------------------------
    ORG  2000H
    DW   BAD_INTR     ; 2001H Timer overflow
    DW   BAD_INTR     ; 2003H A/D Conversion complete
    DW   BAD_INTR     ; 2005H HSI Data available
    DW   BAD_INTR     ; 2007H HSO
    DW   BAD_INTR     ; 2009H HSI.0
    DW   BAD_INTR     ; 200BH Software timers
    DW   BAD_INTR     ; 200DH Serial Port
    DW   BAD_INTR     ; 200FH Extint
    DW   BAD_INTR     ; 2011H Software

    ORG  2080H

START:
    DI
    CLRB R1
    CLRB Port1
    
    ORB  Port1, #044H
;  
    LDB  R1, #020H
    STB  R1, Port1
    
LOOP:
    LDB  R1, Port1
    INCB R1
    STB  R1, Port1
    
    LD   R2, #00009H
DELAY2:
    LD   R3,  #0FFFFH
DELAY3:
    DJNZ R3, DELAY3
    DJNZ R2, DELAY2

    SJMP LOOP

BAD_INTR:
    LDB  R1, #0AAH
    STB  R1, Port1
    SJMP BAD_INTR

    END

次の.batファイルでコンパイルしています。

.\bin\asl.exe -cpu "8096" -L -u .\i8096_test.asm
.\bin\p2hex.exe -F intel i8096_test.p
pause

書き込み

出来た.hexを、TL866Ⅱで書き込めば、ページトップのLED点滅を見ることが出来ます。

ただ、M27C4002-10F6、書き込みに少し癖?があります。

M27C4002-10F6書き込み時の注意

具体的には、左下の"Options" - "Check ID"を外して読み込み・書き込みします。こうしないと、Read時 0x01 0x80 がひたすら読み出されてしまい、Write時もそのせいで書き込めない判定になってしまいます。

データ幅 = 16-bitのROMの一般的な注意

また、"PROG."ボタンの下あたりに"8 Bits" "16 Bits"というチェックボタンがあり、これによってダンプ表示が1バイト毎か2バイト毎かが変わります。

このとき、16-bit表示だと、アドレスが1/2されますが、例えば0x2080のバイトを読むために、実際にメモリにアクセスするときは1ビットシフトした0x1040で読むことに相当するので、問題ありません。

Check IDあり。0x01 0x80の繰り返しとなってしまう

Check IDなし。きちんと読めている

16-bitモード。表示されるアドレスが1/2される

[MCS-96] Intel 8096 (1) MCU概要

新シリーズです。Intelの8096、具体的には若松で売っているA8096-90を使います。

8086ではありません。eBayでは中古が沢山売られている気がしますが、なぜか全然使っている人がいません。

いい加減マイコンばかりでなくプロセッサーを触れよ、と思われているのかもしれませんが…まあ、作っちゃったし…ボード…。

A8096-90(40年倉庫に眠っていたのか…?)

目次

(以下、加筆修正中)

MCS-96(Intel 8096)とは

Intelの16-bitマイコンです。

Application Noteの先頭に以下のように書かれているように、とにかく応答速度が早いことを念頭に置かれたマイコンです。

The 8096 has been designed for high speed/high performance control applications.

アーキテクチャ

なんか…全体的にIntelっぽさはありません。どちらかというと、Z8に似ているなと思いました(特に割り込み、乗除算サポート、内蔵RAM)。組み込みの中でも高速処理向けってところが似ているんでしょうか(Z8はそうなのだったか?)。

特徴

  • レジスタ・マシン(AとかXとか、ありません)
  • パイプラインはなさそうです
  • 実行は、3クロック = 1ステートを単位とする
  • 実行に必要なステートは命令によりかなり幅がある
  • 可変長命令

搭載ペリフェラル

  • High Speed Input/Output
  • ADC
  • Serial port
  • Timer (2ch)
  • Watchdog timer
  • PWM (1ch)

ブロック図

PSW

メモリ空間

  • 64kB
  • メモリ・マップドI/O
  • プログラムとデータは同一空間上
  • ただし、内部ではA-BUSとD-BUSに分かれる。が、外部アクセス時はアドレス・データ共用(時分割)

面白いのは、割り込みベクタは2000H、RESET時は2080Hから始まるところでしょうか…流石にこんな番地初めてです。0000Hでも0FFFFHでもないっていう。Cortex-Mが0x2000 0000からSRAM始まりますけど…SRAMだしな。

しかも、(多分)割り込みベクタはジャンプ先の番地を置きますが、RESET時は命令を置きます。ここがまた微妙に違います。

あと、SFRは0000H~に置かれていますが、PORT3・4についてだけは1FFEH・1FFFHに置かれています。

メモリ・マップ

命令

特にx86とかと互換性はありません。

全体的に、x86を想像してから見てみると違いに驚きます(した)。

ダイジェストでまとめると、以下でしょうか:

全体的なこと

  • 可変長命令(1バイト、最長で7バイト)
  • 同一ニーモニックでも2オペランド命令と3オペランド命令がある
  • ワード命令とバイト命令(ニーモニックの接尾辞 B)に分かれている
  • 組み込み向けだと思うのだが、ビット操作はそこまで多くない
  • 符号については割と気にされている方

個別のニーモニックについて

  • ロード・ストア(MOV / MVIではない)
  • 乗除算あり
  • 無条件ジャンプでは、ロング(LJMP)とショート(SJMP)の区別がある
  • VTフラグ(Overflow trap)の存在
  • 制御命令にも珍しい命令があり、RST(リセットさせる)、SKIP(NOP×2)、TRAP(ソフトウェア例外)

命令表

算術演算

グループ 命令 コメント
加算 ADD(B), ADDC(B), INC(B) -
減算 SUB(B), SUBC(B), DEC(B) -
比較 CMP(B) -
乗算 MUL(B), MULU(B) -
除算 DIV(B), DIVU(B) -
符号反転 NEG(B) -
符号拡張 EXT(B) -
正規化? NORML long-integer (32-bit) の正規化。良く分かっていない

論理演算

グループ 命令 コメント
論理積・和 AND(B), OR(B), XOR(B) -
否定 NOT(B) -
クリア CLR(B) -
シフト・ローテート SHL(B, L), SHR(B. L), SHRA(B, L) -

移動

グループ 命令 コメント
ロード LD(B), LDBSE -
ストア ST(B), STBSE -
プッシュ PUSH, PUSHF FはPSWを退避のうえ、PSW = 0, I = 0
ポップ POP, POPF PSW・Iを復帰

(プログラム)制御

グループ 命令 コメント
無条件ジャンプ SJMP, LJMP Sは11-bit offset, Lは16-bit offset。JNCとかあるならJにしてほしいような
無条件ジャンプ BR indirectです
条件ジャンプ J(N)C, J(N)E, JGE, JGT, JLE, JLT, J(N)H, J(N)V, J(N)VT, J(N)ST, JBS, JBC 一応ビットに関連する条件分岐があります
条件ジャンプ DJNZ -

フラグ操作

グループ 命令 コメント
キャリー SETC, CLRC -
VT CLRVT これはCLRしかありません

CPU制御

グループ 命令 コメント
リセット RST ソフトウェアでRESETを起こせます。PC <- 2080Hです。珍しいかも
割り込みフラグ制御 EI, DI -
何もしない NOP, SKIP NOPはop codeが0xFDで、0x00ではありません。4ステート休みです。
SKIPもNo operationです。しかもNOPと同じ4ステートで、使わないオペランドを1つ要求します(何でもいい)。君は…何?
ソフトウェア例外 TRAP -

アドレッシングモード

かなり直交性が高いと思います。これがIntelなのか?と思うくらいです。

(加筆中)

アドレッシングモード

開発環境

今の人向けにはMacroassemblerだと思います(cpu "8096"、include fileは"reg96.inc")。

が、Intel公式にはASM-96、C-96、PL/M-96がありました。

リンカはRL 96。ライブラリはLIB 96、浮動小数点ライブラリがFPAL 96。

ミドルウェアっぽいのにiDCX 96、評価キットがiSBE-96、In-circuit emulatorがVLSiCE-96

8096 Software Development Process (UM p.21-303)

[78K/Ⅲ] μPD78P312 (9) 文字列命令

78K/Ⅲ、一通り触ってきまして、最終回は文字列命令です。

なお、今回で一旦終了にしようと思います。買ったのが2026.03、書き込み基板作ったのが2026.05だったので長かった。

いまいちこれで何か作る気にはならず…ADCとPWM付いているので、ある程度応用は利きそうですが…(LM35で温度計とか。温度計持ってるけど…。照度計とかだろうか)。

それよりも、明日MCS-96(Intel 8096)のボードが届くので、そちらにつきっきりになりそう。

目次

説明

文字列操作命令(string instructions)が12個あります。

Z80にあったLDIR、CPIRのようなブロック処理命令が進化したと思えばよいでしょうか。ポスト・インクリメントもポスト・デクリメントも選べます。

操作 \ 対象 [DE+/-] ⇔ [HL+/-](~BK) [DE+/-] ⇔ A(~M)
移動 MOVBK MOVM
交換 XCHBK XCHM
比較 CMPBKE(一致チェック)
CMPBKNE(不一致チェック)
CMPBKC(小なり比較)
CMPBKNC(大なり比較)
CMPME(一致チェック)
CMPMNE(不一致チェック)
CMPMC(小なり比較)
CMPMNC(大なり比較)

ステート数は、CMPBKEで2 + 10nです。29文字比較しようとすると、292ステート掛かります。

実は1ステート=何クロックなのか分かっていないのですが、1クロックだとしても292クロック、f_{CLK} = 1.25MHzなら230μsです。

使ってみる

以下のようなアセンブラを書いてみました。

LOOP中、タイマーでwake upするごとに、CMPBKE命令を用いて、文字列STR_HELLOとSTR_HELLO2の一致不一致を確認します。strcmpのようなものです。

STR_HELLOとSTR_HELLO2では1文字違うので(78P312と78P310)、この場合はLEDが0x01のパターンで点灯します。

文字列を一致させると、0x02のパターンで光ることが確認できます(した)。

…なぜMOVW DEではなくMOVW RP5なのかは後述します。

LOOP:
    MOV  STBC, #00100001B    ; Set clock div 8, HALT mode

    MOV  C, #01DH    ; str len = 29
    MOVW HL, #STR_HELLO
    MOVW RP5, #STR_HELLO2    ; MOVW DE

    CMPBKE   [DE+], [HL+]

    MOV  A, C
    CALL UART_PUTC
;  MOV P0, A

    CMP  A, #00H
    BNE  NOTEQ

    MOV  P0, #002H
    BR   LOOP_END

NOTEQ:
    MOV  P0, #001H
LOOP_END:

    BR   LOOP

;(略)

STR_HELLO:
    DB   "Hello world from uPD78P312\r\n\0"

STR_HELLO2:
    DB   "Hello world from uPD78P310\r\n\0"
        END

シリアルで、SMPBKE終了時のCの値も(バイナリで)送信しています。これで何文字目が違っているか分かります。

一致している時:

C = 0になる

一致していない時:

C = 3。最後から4番目の文字が違っている

なぜMOVW DE(RP6)ではなくMOVW RP5?

この問題で、数時間を失いました(ただし大半はイレース時間)。
.
.
.
最初、以下のようなコードを書いていたら、全く動きませんでした。様子としては、全くDEに代入されている気配がありません。

MOVWなんて単純な代入命令で、直後にLEDにDの中身を表示をすると代入値が入っていない。そんなことがあるだろうか…。

MOVW DE, #STR_HELLO

レジスタの中身を表示していたりするうちに、どうも、「MOVW HL」は問題ないが、「MOVW DE」は動作していない事がわかりました。

(ここでモニタープログラムを作っていれば、DEではなくRP3に代入されていると気付けたかもしれない)

DEだけ、別の場所でレジスタが上書きされているのだろうかと思い調べましたが、当該箇所が見つかりません。

しかも困ったことに、同じ場所でMOVによってDとEを別々に代入すると、普通に動きます。つまり、DEそのものに問題があるとかではなく、MOVW DEが変なのです。

MOV D, <#STR_HELLOの上位バイト>
MOV E, <#STR_HELLOの下位バイト>

機械語も念のため確かめました。DE = RP6、HL = RP7なので、1だけ差が出ていて問題なさそうです。

一応表も確認します。MOVW rp1, #wordというのが今回該当するオペコードで、RP7なら0x67です。

MOVWのオペコード

さて、Q2-Q0でレジスタを指定します。その表は以下に貼ったとおりです。

ここで奇妙なことに気付きます。rpもrp1も、同じレジスタ群を3-bitで指定するのですが、なんとrpとrp1でオペコードの振り方が違う…。

しかも、AX(RP0)とHL(RP7)だけ都合よくオペコードが同じです。私は今までMOVWをほとんどAXとHLにだけ使ってきました。

まさか…?と思って、rp1でのRP6に相当するRP5に代入する MOVW RP5で書いてみると、あっさりコードが動きました。

いやいや、そんなことある?と思いましたが、今のところ最有力説はこれです。うーん、Macroassemblerも細かいところまで作り込まれているので、間違っているとは思いたくないですが…。

そもそもコードの振り方が分かりにくいだろ…と思いましたが、rpはdstに、rp1はsrcに使うものです。一応、使われる位置は違います。変えないといけない事情があったんでしょうか…(P2をQ0の位置に持ってきて、P1, P0, P2とすれば、Q2, Q1, Q0と一致するが…)

rp, rp1, rp2のOP code

MOV D&MOV Eでは動くと分かった段階で、そちらを使ったコードに切り替えて片付けてもよかったのですが。単純なMOVW DEが動かないのはどうにももやもやするので調べ続けた結果、一応の理由は分かってよかったです。

しかしこんなこともあるんですね…。まさか最後の最後でこんな落とし穴があるとは。

[78K/Ⅲ] μPD78P312 (8) ソフトウェア割り込み・コンテキスト・スイッチ

ソフトウェア割り込みを使ってみました。ついでに、context switching 割り込みも使ってみました。

説明

ソフトウェア割り込み

78K/Ⅲには、珍しく?ソフトウェア割り込み機能があります。

専用命令 BRK(※)により発生させることが出来る割り込みです。0除算などにより生じる例外とは異なります。

x86でいうINTやsyscall、ARMでのSVCといったところでしょうか。

ただ、システムコールが特権跨ぎとかOS機能呼び出しなどに使われるのに対し、こちらのソフトウェア割り込みはいまいち使い道がよく分かりません。小規模な組み込みOSで使われることを企図しているのでしょうか…?

(※)コンテキスト・スイッチで割り込みに応答する場合は、BRK命令ではなくBRKCS命令を使用する。

コンテキストスイッチ割り込み

挙動

通常の?割り込みと異なり、割り込み時に以下のとおり動作します。

  • 8段階の優先度によって決まるレジスタ・バンクに切り替え
  • 切り替え先バンクのR4-R5に格納されている値をPCにロード
  • 切り替え先バンクのR4-R7(A, Xなどの機能によるレジスタ名が振られていない)にPC、PSWを保存します

また、

  • RSS ← 0になります
  • 戻るときはRETCS命令を使用します

メリット

  • ベクタ割り込みテーブルは不要(といっても、そこまで幅を取るものでもないが…先頭に置かなくて良い)
  • 退避に必要なステート数が少しだけ短い(退避後、ベクタ割り込みは自分でバンク切り替えるかスタックにレジスタをpushする必要があると考えると、もっと短い?)

デメリット

  • 割り込みハンドラ中、R4-R7の扱いには注意が必要(破壊すると戻れなくなる)

必要なステート数の比較

マクロ・サービス(説明のみ、不使用)

本来なら割り込みが発生するところ、指定回数までは、RAMにSFRの値をメモリを転送するだけで割り込みを起動せず、指定回数になったら割り込みを起動するような仕組みのようです。

例として、ADCの結果を5回はメモリに保存するだけで割り込みハンドラを呼ばず、6回目で通常の割り込みハンドラを呼ぶ事例が挙げられています。

いちいち割り込み処理を呼ぶのはコストが掛かりますから、ハードで対応して省エネという感じでしょうか。

今のRL78にあるSNOOZE モード・シーケンサとか、あるいはAVRのEvent systemと似た発想と思います。(もしくは、DMAとも近いかもしれない。DMAは割り込みハンドラは呼ばないが…)

プログラム

ソフトウェア割り込み(ベクタ割り込み)

まず、ソフトウェア割り込み(BRK)のハンドラでのみ、LEDをローテートするようにします。

次に、一定間隔でソフトウェア割り込みするために、まずHALTに落とし、Time-base counterの割り込みでHALTの次のBRK命令を実行しています。そして再びHALTに戻ります。

ソフトウェア割り込みから戻る際は、UMの記載に従い、CCWのEOSビットを1にしています。

このプログラムを実行すると、Time-base counterを使った場合と同じように、LEDの点灯パターンが変わります。

       cpu 78310

;  include "include/reg78310.inc"

; -----------------------------------------------------------------------------------
; Special function register (SFR) definition
; -----------------------------------------------------------------------------------
P0       equ  0FF00H   ; Port register
P2       equ  0FF02H

PWM0L    equ  0FF14H
PWM0H    equ  0FF15H
PWM0 equ  0FF15H
PWM1L    equ  0FF16H
PWM1H    equ  0FF17H
PWM1 equ  0FF17H

PM0      equ  0FF20H   ; Port mode register. 0 = output mode, 1 = input mode
PM1      equ  0FF21H
PM2      equ  0FF22H
PM3      equ  0FF23H
PM5      equ  0FF25H

PMC2 equ  0FF32H
PMC3 equ  0FF33H

STBC equ  0FF44H
TBM      equ  0FF46H
INTM     equ  0FF48H

CCW      equ  0FF4EH

SCM      equ  0FF50H
SCC      equ  0FF52H
BRG      equ  0FF53H
RXB      equ  0FF56H
TXB      equ  0FF57H

FRCC     equ  0FF60H
PWMM equ  0FF66H

ADM      equ  0FF68H
ADCR equ  0FF6AH

TMC1 equ  0FF82H
TM1L     equ  0FF8CH
TM1H equ  0FF8DH
TM1      equ  0FF8DH
MD1L equ  0FF8EH
MD1H equ  0FF8FH
MD1      equ  0FF8FH

CRIC00   equ  0FFC0H
CRIC01   equ  0FFC2H
CRIC10   equ  0FFC4H
CRIC11   equ  0FFC6H
EXIC0    equ  0FFC8H
EXIC1    equ  0FFCAH
EXIC2    equ  0FFCCH
TMIC0    equ  0FFCEH
TMIC1    equ  0FFD0H
TMIC2    equ  0FFD2H
SEIC     equ  0FFDAH
SRIC     equ  0FFDCH
STIC     equ  0FFDEH
ADIC     equ  0FFE0H
TBIC     equ  0FFE2H

; -----------------------------------------------------------------------------------
; Interrupt vectors
; -----------------------------------------------------------------------------------
    ORG  0000H
    DW   START            ; Reset (0000H)
    DW   BAD_INTR     ; NMI (0002H)
    DW   INTE0_INTR   ; EXIF0 (0004H)
    DW   BAD_INTR     ; EXIF1 (0006H)
    DW   BAD_INTR     ; EXIF2 (0008H)
    DW   BAD_INTR     ; WDT (000AH)
    DW   TBF_INTR     ; TBF (000CH)
    DW   BAD_INTR     ; TMF0 (000EH)
    DW   BAD_INTR     ; TMF1 (0010H)
    DW   TMF2_INTR    ; TMF2 (0012H)
    DW   0000H        ; dummy (0014H)
    DW   0000H        ; dummy (0016H)
    DW   0000H        ; dummy (0018H)

    ORG  001AH
    DW   BAD_INTR     ; CRF00 (001AH)
    DW   BAD_INTR     ; CRF01 (001CH)
    DW   BAD_INTR     ; CRF10 (001EH)
    DW   BAD_INTR     ; CRF11 (0020H)
    DW   BAD_INTR     ; SEF (0022H)
    DW   SRF_INTR     ; SRF (0024H)
    DW   BAD_INTR     ; STF (0026H)
    DW   BAD_INTR     ; ADF (0028H)
    
    ORG  003EH
    DW   SFT_INTR     ; BRK (003EH)

; -----------------------------------------------------------------------------------
; Interrupt functions
; -----------------------------------------------------------------------------------
    ORG  0050H

BAD_INTR:
    MOV  STBC, #00000001B    ; Set clock div 2, HALT mode
    RETI

INTE0_INTR:
    NOT1 P0.0        ; Toggle LED
    CLR1     EXIC0.7 ; Clear interrupt
    RETI

TMF2_INTR:
    RETI

TBF_INTR:
    ; PWM 
    MOV  A, PWM0L  
    ADD  A, #19H
    MOV  PWM0L, A

    MOV  A, PWM1L
    SUB  A, #19H
    MOV  PWM1L, A

    CLR1     TBIC.7  ; Clear interrupt  
    RETI

SFT_INTR:
    MOV  A, P0
    ROL  A, 1
    MOV  P0, A

    SET1 CCW.0

    RETI

; -----------------------------------------------------------------------------------
; main routine
; -----------------------------------------------------------------------------------
START:
    DI
    MOVW SP, #0FE7FH
    MOV  STBC, #00000000B    ; Set clock div 2

PORTINIT:
    MOV  PM0, #000H
    MOV  P0,  #0AAH

    MOV  PM3, #00110000B
    MOV  PMC3, #00110000B

    AND      EXIC0, #000H
PWMINIT:
    MOV  PWMM, #00100010B    ; resolution = 8-bit, PWM0 and 1 on
    MOVW PWM0, #0000H
    MOVW PWM1, #007FH

    MOV  FRCC,    #00001111B ; output enable

TMR0INIT:

    ;MOV   TMIC1, #00000000B   ; Interrupt enable

    MOVW MD1, #02626H
    MOVW TM1, #02626H

    ;MOV   TMC1, #011000000B

TBC_INIT:
    MOV  TBM, #00000011B ; TBF = 2^{20} / fCLK
TBC_IRQ_EN:
    MOV  TBIC, #00000000B    ; Clear Mask, irq flag

    EI

LOOP:
    MOV  STBC, #00000001B    ; Set clock div 2, HALT mode

    BRK

    BR   LOOP

        END

ソフトウェア割り込み(コンテキスト・スイッチ)

次にコンテキスト・スイッチを使います。

コンテキスト・スイッチで動作していることがわかるように、まずベクタ・テーブルの003EHはBAD_INTRに変更しました。

下準備として、SEL RB1でバンクを切り替えたうえで、R4・R5に割り込みハンドラのアドレスを登録します。

ソフトウェア割り込みは、BRKCS命令で発生させます。この際、使うレジスタ・バンクを一緒に設定する必要があります(BRKCS RB1のように)。

割り込みハンドラ内での処理はいつも通りですが、R4-R7は使いません。

戻るときは、SET1 CCW.0したうえで、RETCS命令で戻るのですが、このとき、次にこのバンクのコンテキスト・スイッチを呼び出す際に設定しておくべきR4-R5のPC値をオペランドに設定します。

これ、データシートの図ではオペランドが省略されていて混乱したのですが、コンパイルが通らず気づきました。(プログラム依存とはいえ、省略しないでほしいなあ…)

文章ではRETCSにオペランドがあると書いてあるが、図では省略されている…(BRKCSも)

       cpu 78310

;  include "include/reg78310.inc"

; -----------------------------------------------------------------------------------
; Special function register (SFR) definition
; -----------------------------------------------------------------------------------
; (略)
; -----------------------------------------------------------------------------------
; Interrupt vectors
; -----------------------------------------------------------------------------------
    ORG  0000H
    DW   START            ; Reset (0000H)
    DW   BAD_INTR     ; NMI (0002H)
    DW   INTE0_INTR   ; EXIF0 (0004H)
    DW   BAD_INTR     ; EXIF1 (0006H)
    DW   BAD_INTR     ; EXIF2 (0008H)
    DW   BAD_INTR     ; WDT (000AH)
    DW   TBF_INTR     ; TBF (000CH)
    DW   BAD_INTR     ; TMF0 (000EH)
    DW   BAD_INTR     ; TMF1 (0010H)
    DW   TMF2_INTR    ; TMF2 (0012H)
    DW   0000H        ; dummy (0014H)
    DW   0000H        ; dummy (0016H)
    DW   0000H        ; dummy (0018H)

    ORG  001AH
    DW   BAD_INTR     ; CRF00 (001AH)
    DW   BAD_INTR     ; CRF01 (001CH)
    DW   BAD_INTR     ; CRF10 (001EH)
    DW   BAD_INTR     ; CRF11 (0020H)
    DW   BAD_INTR     ; SEF (0022H)
    DW   SRF_INTR     ; SRF (0024H)
    DW   BAD_INTR     ; STF (0026H)
    DW   BAD_INTR     ; ADF (0028H)
    
    ORG  003EH
    ;DW    SFT_INTR        ; BRK (003EH)
    DW   BAD_INTR

; -----------------------------------------------------------------------------------
; Interrupt functions
; -----------------------------------------------------------------------------------
    ORG  0050H

BAD_INTR:
    MOV  STBC, #00000001B    ; Set clock div 2, HALT mode
    RETI

INTE0_INTR:
    NOT1 P0.0        ; Toggle LED
    CLR1     EXIC0.7 ; Clear interrupt
    RETI

TMF2_INTR:
    ; INC_LED
    ;MOV   A, P0
    ;INC   A
    ;ROR   A, 1
    ;MOV   P0, A

    ;CLR1      TMIC1.7 ; Interrupt flag clear
    RETI

TBF_INTR:
    ; Make a change on LED
    ; MOV  A, P0
    ; ROL  A, 1
    ; MOV  P0, A

    ; PWM 
    MOV  A, PWM0L  
    ADD  A, #19H
    MOV  PWM0L, A

    MOV  A, PWM1L
    SUB  A, #19H
    MOV  PWM1L, A

    CLR1     TBIC.7  ; Clear interrupt  
    RETI

    ORG 0100H
SFT_INTR:
    MOV  A, P0
    ROL  A, 1
    MOV  P0, A

    SET1 CCW.0

    RETCS SFT_INTR

; -----------------------------------------------------------------------------------
; main routine
; -----------------------------------------------------------------------------------
START:
    DI
    MOVW SP, #0FE7FH

    MOV  STBC, #00000000B    ; Set clock div 2

    ;SWRS

PORTINIT:
    MOV  PM0, #000H
    MOV  P0,  #0AAH

    MOV  PM3, #00110000B
    MOV  PMC3, #00110000B

    AND      EXIC0, #000H
PWMINIT:
    MOV  PWMM, #00100010B    ; resolution = 8-bit, PWM0 and 1 on
    MOVW PWM0, #0000H
    MOVW PWM1, #007FH

    MOV  FRCC,    #00001111B ; output enable

TMR0INIT:

    ;MOV   TMIC1, #00000000B   ; Interrupt enable

    MOVW MD1, #02626H
    MOVW TM1, #02626H

    ;MOV   TMC1, #011000000B

TBC_INIT:
    MOV  TBM, #00000011B ; TBF = 2^{20} / fCLK
TBC_IRQ_EN:
    MOV  TBIC, #00000000B    ; Clear Mask, irq flag

    EI

    SEL  RB1
;  MOVW    RP2, #SFT_INTR
    MOV  R5, #001H
    MOV  R4, #000H
    SEL  RB0

LOOP:
    MOV  STBC, #00000001B    ; Set clock div 2, HALT mode

    BRKCS    RB1

    BR   LOOP

; -----------------------------------------------------------------------------------
; sub routines
; -----------------------------------------------------------------------------------

        END

その他

SFR定義ファイル

今更ですが、SFRの定義がreg78310.incにあることを知りました。そっちをincludeするようにしようとしましたが、少し名前が違うものがあり(例えばEXIC0 → EIC0)、プログラムの修正が面倒なので、現状は.incファイルを読み込まず、SFR定義を残しています。

あと、16-bitのSFR名もありませんね(PWM0とか)。

RSSによるレジスタマッピング切り替え

78K/Ⅲは、SWRS命令で、PSWのRSS (Register Set Select) を反転させることが出来ます。これにより、レジスタA, X, B, Cを、R0-R3とするか、R4-R7とするかが切り替わります。

ただ、RSSにかかわらず機械語でR0-R7の割り当ては代わりません。では一体何に作用するだろうか?と思いましたが、命令のオペランドがA, AX, X, B, C, BC固定のものについて、その宛先が実行時に切り替わるのだろう…と思っています。

これとは別に、アセンブラ上、AとかくとR1と翻訳されて機械語に変換されることもありますが、この場合の変換はどう扱うのでしょうか? これについては、ASのマニュアルに記載があって、RSS = 0前提で機械語にするようです。

例えば、MOV D, Aは24 D1(D = R13 = 0xD、A = R1 = 0x1で、下位バイトが0xD1になる。A = R5 = 5として0xD5にはしない)。

RSSについてのASの記載

[78K/Ⅲ] μPD78P312 (7) 乗除算命令

78K/Ⅲの命令セットに含まれる乗除算命令を使ってみます。

目次

説明

78K/Ⅲには以下の乗除算命令があります。

16-bit同士の演算ができるのが特徴的、でしょうか。

ニーモニック 機能 レジスタ 必要ステート数
MULU 8-bit × 8-bit A × r1 → AX 18
MULUW 16-bit × 16-bit AX × rp1 → AXrp1 27
DIVUW 16-bit ÷ 8-bit AX ÷ r1 → AX (quot.), r1 (rem.) 26
DIVUX 32-bit ÷ 16-bit AXDE ÷ rp1 → AXDE (quot.), rp1 (rem.) 50

プログラム

折角なので16-bit演算を使ってみます。

本当であれば、例えばポートに接続したDIP SWとか、シリアル経由で与えた数値を用いて計算した方が、動的に計算結果が変わるため、計算速度がわかりやすくてよいのですが、面倒なので固定値同士の計算としました。

MULUW

Time-base counterのオーバーフロー割り込みに、以下のプログラムを書き足します。

60.000 (0xEA60) × 50,000 (0xC350) = 3,000,000,000 (0xB2D0 5E00)

で、計算がうまくいっていれば、その最上位バイト(0xB2)がLEDに表示されます。

なお、オペランドのrp1としてはRP0~RP7を取れますが、これはAXやDE、HLなどの別名です(どちらかというとRP0とかの別名がAXとかなのだが)。 別物ではないので衝突に注意が必要です。

TBF_INTR:
    MOVW AX, #0EA60H
    MOVW RP7, #0C350H
    MULUW    RP7
    MOV  P0, A

    CLR1     TBIC.7  ; Clear interrupt  

    RETI

0xB2を表示

DIVUX

今度は 3,000,000,000 (0xB2D0 5E00) ÷ 60,000 (0xEA60)をしてもらいます。

答えは50,000 (0xC350) で、0b1100 0011 0101 0000。しかし、これがうまく動きません。

実際はA = 0x00, X = 0x00, D = 0b0101 1110(0x5E)、E = 0x00となり、DEは命令実行前の0x5E00から変わっていないようです。

レジスタ 計算前 計算後(想定) 計算後(実際)
AX 0xB2D0 (3,000,000,000の上位バイト) 0x0000 0x??00
DE 0x5E00 (3,000,000,000の下位バイト) 0xC350 (50,000) 0x5E00
UP 0xEA60 (60,000) 0x0000 未確認

流石にμPD78312AのデータシートにもUMにも記載のある命令なので、実装されていないなんてことはないと思うのですが…。

私がなにか勘違いしているのでしょうけれど、解決できず。

(なお、MOV A, Dが出来ません(MOV r1, r)。不便です。MOV D, A(MOV r, r1/A)、MOVW AX, DEはできます。代用としてはXCH D, Aだろうか…。)

 MOVW AX, #0B2D0H
    MOVW DE, #05E00H
    MOVW UP, #0EA60H
    DIVUX    UP
    MOVW AX, DE
    MOV  P0, A

一応、.lstを見る限り、機械語は正しそうです。

05 ED DIVUX UP

Multiply / divide instructions のOP code

DIVUW

気を取り直して、16-bit ÷ 8-bitとサイズの小さいDIVUWを使うことにします。

ところが、Macroassemblerだとunknown instructionになってしまいます。DIVU, DIVにしてもダメ。DIVUXが実装されているのにDIVUWが実装されていないなんてあるんでしょうか。

仕方ないので、奥の手で、DBでDIVUW Bのオペコード(0x05 1B)を置いてみることにしました。

(追記)ソースコード asl-current\code78k3.cを見に行ったところ、DIVUで定義されているっぽいです。なぜ最初見落としたのだろう。DIVUといいつつDIVWで試したのか…?

MULはMULU・MULUWなので、DIVもDIVU・DIVUWであると勘違いしてしまうのは分かる気がします。その割にはDIVUXはDIVUWではなくDIVUXで定義されていますが…。

 MOVW AX, #03E8H
    MOV  B, #0C8H
    ; DIVUW B
    DB   00000101B
    DB   00011011B    ; R3
    MOV  A, X
    MOV  P0, A

計算結果は、1,000 (0x03E8) ÷ 200(0xC8)= 5 (0x0005) 。下位バイトのXをLEDに表示するので、0x05が表示されれば正しいです。

実際、0x05を表示してくれました。一応機能としてはDIVUWがあるということですね。なんでコンパイルできないのか…

0x05 を表示

(おまけ)随分.hexも賑やかになった

[78K/Ⅲ] μPD78P312 (6) PWM

PWMを使って2色LEDを制御します。

目次

説明

78K/Ⅲはモーター制御を意識しているのか、PWMが2ch付いています。古めのマイコンにしては珍しい気がします。

PWM unit

Pulse input/output unitの1パーツとして、PWM unitがあります。

概要(ブロック図)

f_{CLK}によってデクリメントされるダウン・カウンタと、Time-base counterからくるf_{CLK} / 2nを使ってPWM信号を生成しています。

nは、PWMの分解能も決めます。8, 10, 12, 16から選べます。

16-bitにすれば、かなりきめ細かな制御ができそうですが、今回は n = 8 としています。この場合、f_{CLK} = 1.25MHz なので、 PWMの周波数 f = f_{CLK} ÷ 28 = 4.88kHzとなります。

PWM unit ブロック図

制御レジスタ

制御レジスタは、PWMM。加えて、FRCC(Free Running Counter Control Register)にも該当箇所があります。なんで跨っているのか。

FRCC側のENPWMnを'1'に設定しないと、ピンに出力されません。

PWMMレジスタ / FRCCレジスタ

I/Oピン設定

I/Oピンも設定が必要です:

  • PM3のPM34、PM35を出力('1')にする
  • PMC3のPMC34、PMC35を'1'(PWMn output mode)にする

回路

PWM0(P34)とPWM1(P35)に赤・緑の2色LEDをつなぎました。

ずっと前から在庫している2色LEDで、かなり暗いです。カソードコモンなので仕方なくソース接続しています。

回路図

プログラム

8-bitなので、PWM0とPWM1の下位バイトのみを使っています。

Time-base counterのオーバーフロー割り込みごとに、ch 0は閾値を+25、ch 1は-25しています。

これにより、LEDの色が、赤→緑へと段階的に変化します。

オシロで見たところ、PWMの周波数は4.878kHzとなり、ほぼ想定通りでした。

LEDの色の変化 / オシロでの観測

        cpu 78310

; -----------------------------------------------------------------------------------
; Special function register (SFR) definition
; -----------------------------------------------------------------------------------
P0       equ  0FF00H   ; Port register
P2       equ  0FF02H

PWM0L    equ  0FF14H
PWM0H    equ  0FF15H
PWM0 equ  0FF15H
PWM1L    equ  0FF16H
PWM1H    equ  0FF17H
PWM1 equ  0FF17H

PM0      equ  0FF20H   ; Port mode register. 0 = output mode, 1 = input mode
PM1      equ  0FF21H
PM2      equ  0FF22H
PM3      equ  0FF23H
PM5      equ  0FF25H

PMC2 equ  0FF32H
PMC3 equ  0FF33H

STBC equ  0FF44H
TBM      equ  0FF46H
INTM     equ  0FF48H

SCM      equ  0FF50H
SCC      equ  0FF52H
BRG      equ  0FF53H
RXB      equ  0FF56H
TXB      equ  0FF57H

FRCC     equ  0FF60H
PWMM equ  0FF66H

ADM      equ  0FF68H
ADCR equ  0FF6AH

TMC1 equ  0FF82H
TM1L     equ  0FF8CH
TM1H equ  0FF8DH
TM1      equ  0FF8DH
MD1L equ  0FF8EH
MD1H equ  0FF8FH
MD1      equ  0FF8FH

CRIC00   equ  0FFC0H
CRIC01   equ  0FFC2H
CRIC10   equ  0FFC4H
CRIC11   equ  0FFC6H
EXIC0    equ  0FFC8H
EXIC1    equ  0FFCAH
EXIC2    equ  0FFCCH
TMIC0    equ  0FFCEH
TMIC1    equ  0FFD0H
TMIC2    equ  0FFD2H
SEIC     equ  0FFDAH
SRIC     equ  0FFDCH
STIC     equ  0FFDEH
ADIC     equ  0FFE0H
TBIC     equ  0FFE2H

; -----------------------------------------------------------------------------------
; Interrupt vectors
; -----------------------------------------------------------------------------------
    ORG  0000H
    DW   START            ; Reset (0000H)
    DW   BAD_INTR     ; NMI (0002H)
    DW   INTE0_INTR   ; EXIF0 (0004H)
    DW   BAD_INTR     ; EXIF1 (0006H)
    DW   BAD_INTR     ; EXIF2 (0008H)
    DW   BAD_INTR     ; WDT (000AH)
    DW   TBF_INTR     ; TBF (000CH)
    DW   BAD_INTR     ; TMF0 (000EH)
    DW   BAD_INTR     ; TMF1 (0010H)
    DW   TMF2_INTR    ; TMF2 (0012H)
    DW   0000H        ; dummy (0014H)
    DW   0000H        ; dummy (0016H)
    DW   0000H        ; dummy (0018H)

    ORG  001AH
    DW   BAD_INTR     ; CRF00 (001AH)
    DW   BAD_INTR     ; CRF01 (001CH)
    DW   BAD_INTR     ; CRF10 (001EH)
    DW   BAD_INTR     ; CRF11 (0020H)
    DW   BAD_INTR     ; SEF (0022H)
    DW   SRF_INTR     ; SRF (0024H)
    DW   BAD_INTR     ; STF (0026H)
    DW   BAD_INTR     ; ADF (0028H)
    
    ORG  003EH
    DW   BAD_INTR     ; BRK (003EH)

; -----------------------------------------------------------------------------------
; Interrupt functions
; -----------------------------------------------------------------------------------
    ORG  0050H

BAD_INTR:
    MOV  STBC, #00000001B    ; Set clock div 2, HALT mode
    RETI

INTE0_INTR:
    NOT1 P0.0        ; Toggle LED
    CLR1     EXIC0.7 ; Clear interrupt
    RETI

TMF2_INTR:
    ; INC_LED
    MOV  A, P0
    ;INC   A
    ROR  A, 1
    MOV  P0, A

    CLR1     TMIC1.7 ; Interrupt flag clear
    RETI

TBF_INTR:
    ; Make a change on LED
    MOV  A, P0
    ROL  A, 1
    MOV  P0, A

    ; PWM 
    MOV  A, PWM0L  
    ADD  A, #19H
    MOV  PWM0L, A

    MOV  A, PWM1L
    SUB  A, #19H
    MOV  PWM1L, A

    CLR1     TBIC.7  ; Clear interrupt 

    RETI

SRF_INTR:

    MOV  A, RXB
    CALL UART_PUTC

    CLR1 STIC.7  ; Clear interrupt
    RETI

; -----------------------------------------------------------------------------------
; main routine
; -----------------------------------------------------------------------------------
START:
    DI
    MOVW SP, #0FE7FH

    ;MOV   STBC, #00000000B    ; Set clock div 2

PORTINIT:
    MOV  PM0, #000H
    MOV  P0,  #0AAH

    MOV  PM3, #00110000B
    MOV  PMC3, #00110000B

    AND      EXIC0, #000H
PWMINIT:
    MOV  PWMM, #00100010B    ; resolution = 8-bit, PWM0 and 1 on
    MOVW PWM0, #0000H
    MOVW PWM1, #007FH

    MOV  FRCC,    #00001111B ; output enable

TMR0INIT:
    ;MOV   TMIC1, #00000000B   ; Interrupt enable
    MOVW MD1, #02626H
    MOVW TM1, #02626H
    ;MOV   TMC1, #011000000B

UARTINIT:
    MOV  PMC2, #0FFH     ; Port mode set to uart
    MOV  SCM, #00000001B ; Set to asynchronous mode
    MOV  SCM, #11001001B ; Tx en, Rx en, No parity, 8-bits, 1 stop bit, asynchronous mode
    MOV  SCC, #00000010B ; div fCLK / 16
    MOV  BRG, #08H       ; G = 8

    MOV  SRIC, #00H; receive interrupt enable

TBC_INIT:
    MOV  TBM, #00000011B ; TBF = 2^{20} / fCLK
TBC_IRQ_EN:
    MOV  TBIC, #00000000B    ; Clear Mask, irq flag

    EI
    
    MOVW HL, #STR_HELLO
    CALL UART_PUTS

LOOP:
    BR   LOOP

; -----------------------------------------------------------------------------------
; sub routines
; -----------------------------------------------------------------------------------
; Arguments : A = char to be sent
UART_PUTC:
    BF   STIC.7, UART_PUTC    ; wait until transfer is ready
    CLR1 STIC.7
    MOV  TXB, A
    RET

UART_PUTS:
    MOV  A, [HL]
    CMP  A, #'\0'
    BZ   UART_PUTS_END    
    CALL UART_PUTC
    INCW HL
    BR   UART_PUTS
UART_PUTS_END:
    RET

STR_HELLO:
    DB   "Hello world from uPD78P312\r\n\0"

        END