目次
- 短波(HF, 3MHz~30MHz)
- 10MHz
- 11MHz
- 12MHz
- 13MHz
- 14MHz
- 15MHz
脳内だけで記憶するのには限界があるので、記録を付けることにしました。これは固定ページとします。
画像の容量が重いので携帯で見るのは余りおすすめしません…。
※なるべく見つけたら直すようにはしていますが、間違いが多分結構あります。
続きを読む過去自分が買ったものにケジメを付けていくシリーズ。
今回は、Broadcom(Avago、HP)のHCMS-2912というドットマトリクスLEDで、2014年ごろ、Digikeyで購入したものです。
目次




まずパーツの説明から。
ドット・マトリクス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)。
マイコンで制御します。どのマイコンにするか…大したことはしないので、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-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との通信プロトコルが、SPI(Mode 3)そのままではないので、ちょっとした工夫が必要でした。
↓が具体的な流れなのですが、以下2点はケアが必要でした。
前者は、GPIOで#SSより先に操作すれば解決します。
ただ後者は、SCKがSPIペリフェラル優先になっているので、単純にGPIOで操作するわけにもいきません。
解決策として、最初は「SPIの割り当てを一旦解除し(PORTMUX.SPIROUTEA = 0x07)、GPIOとして操作」していましたが、途中で「#SSを下げずに空データをSPIで送る」でよいことに気づき、そちらを使っています(SCKを2回以上下げる分には問題ないようだった)。

↓ を使用しました。(ヘッダをインクルード)
bb-lcd/font-5x7.c at master · ChuckM/bb-lcd https://github.com/ChuckM/bb-lcd/blob/master/font-5x7.c
色々考えた末、「必要な情報を持った構造体の単方向リスト」を用意することで落ち着きました。
表示のアップデート用関数も持っておいて、温度や時刻については、それを呼び出すことで更新するようにしました。
文字列も、UARTから更新すると長さが変わったりするのですが、いちいちstrlenなんてしていられないので、アップデート時にだけ長さを再計算するようにしました。
サンプルが少ないのでやりづらい…。一応公式ドキュメントにそこそこ書いてはあるのですが。
一番悩んだのはADCでの温度計測でした。MCCでは、サンプリングレートを適切な範囲内に収めたりしてくれません(ボーレートはエラー率とか出るのに)。
結局、関数以外のハード仕様はMelodyのドキュメントだけ見てもわからず、データシートを見ながら設定しました。
USART0受信の割り込みによる処理は、ほぼMelodyのドキュメントそのままです。
time.hを使っています。avr-libcのtime.hは、RTCで1秒毎に呼び出すための関数 system_tick() が既に用意されているので(便利!)、簡単です。










ヘッダ
/* * 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); }
ヘッダ
/* * 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 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); } } } }
Timerを使ってLチカ。なお、これでHMCS400は一旦終了。

目次
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 Mode Register A & Bによって設定できます。といっても簡素なもので、4-bitのうち3-bitがプリスケーラーの分周比設定ビットで占められています。
A-chはこれしか設定するものがありません。B-chは、その他にオートリロードのON/OFFビットがあります。
なお、デフォルトでTMA = TMB = $000です。すなわち、最大分周比になっています。

割り込みフラグ・割り込みマスクと、全体の割り込み許可は、Interrupt Control Bit Area ($000~$003)で設定します。
\$001のIFTA・IMTAが、それぞれTimer Aのフラグとマスクです。全体割り込み許可は、$000のbit 0のI/Eです。

というだけの簡単なプログラムです。
初期設定以外では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
LEDを光らせるまで(1日かかった)

目次
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.について。
以下のメモリマップのとおりで、EPROM ADDRESS $0000・$0001を合わせて、MCU ROM ADDRESSの $0000 として扱います。
(これならもう1ワード = 8-bitにすればよかったのでは?と思うのですが、10-bitにすることによるメリットの方が上回ったということなのでしょう。あまり思いつきませんが、4-bit即値をop codeで扱いやすい、とかでしょうか)

では、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で変換することにしました。
幸い、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
バグなのか、それともHD614P080とHD614P081の違いなのか。
単純で、Long jumpであるLJMP、CALLの機械語が違います。
最初、このことなど知る由もなく、動かなくて随分悩みました。JMPLを書かずに、リセットベクタにLAI・LRAを書いたら動いたので、原因を何とか特定できました。
まず、UMの説明は、JMPLの上位ワード = 01 0101 p3p2p1p0 = 0x015P です。メモリに格納するなら、01010 0p3p2p1p0の分割で、0x0P, 0x0Aになります

このようなコード
; 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が変になります。
幸い、ワード長は変わらないので、
とやれば動きます。
先の例で行くと
; 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
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に載っているペリフェラルは、以下で全てです。
SPIがUARTとして転用可能なのかもよく分かりません。Timerと割り込み使うくらいかなあ…。
ポートは沢山ありますけどね…。使い道に悩む。
窓付きではなくピギーバック・タイプなので、AT29C256を使ってイレース時間なしでスムーズに触れるのは良いのですが。
むしろ、特筆すべきは蛍光表示管を駆動可能なポートがあることで、余っている2桁の蛍光表示管をどうにか組み合わせられないだろうか…
(書きかけです)
目次
若松通商にて、HD614P080Sなるマイコンが売っています。前までありましたっけ…?
高いですが、珍しいのと、開発環境・ドキュメントがあることは確認できたので、買ってみました。

HMCS400シリーズの1つ。
特徴
アキュムレータ・マシンで、レジスタは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は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は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回積むのが限界 |


UM p.48 §3.3 Instruction Setより
ここが一番独自色が強いのではないでしょうか。
かなりシンプルです。
命令表は以下のとおりですが、前身であるHMCS40との比較があるのがちょっと面白いです。
乗除算は流石にありません。4-bitだし。
割り込みのフラグ指定は全部違う命令になっています。また、シリアル転送開始命令 STSや、タイマー/カウンター操作命令 LTI、LTA、LATなどがあるあたり、完全にメモリ操作でSFRを扱うようにはなっておらず、若干こなれてない感じがします。
レジスタで述べたように、フラグ・レジスタはあんまり数がなく、それゆえ更新されることも少ないです。
I/OはDポートとRポートで扱いが異なります。Dポートはbit操作可能です。
あと、1文字ニーモニック Pは珍しい気がします。MIPSのJ(ジャンプ)、ARMのB(といってもフラグ付きだと思うが…)は知っていますが、他は思いつきません。
…やっぱ6800の方が扱いやすいように見えます。6800って相当綺麗ですよね。


UM p.43 §3.1・3.2より
メモリ・レジスタは1 ワード命令・1サイクルのLAMR・XMRAでAと交換できるので、少しだけRAM一般へのアクセスよりも有利です。
| アドレッシング・モード | アドレス指定方法 |
|---|---|
| レジスタ間接 | W & X & Yレジスタを結合した10-bitでアドレス指定 |
| 直接 | op codeの即値をそのままアドレスとして扱う。1ワード 10-bitなので1ワードでアドレス指定可能 |
| メモリレジスタ | $020~$02Fは op code の下4-bitで指定可能 |
| アドレッシング・モード | アドレス指定方法 |
|---|---|
| 直接 | 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 ポート出力レジスタを更新 |
入出力の区別はありません。8051方式というべきか。
高耐圧ピンは、PMOSのオープン・ドレインです。入力として使うには、出力を'0'とする必要があります('1'だとピン=Vccになって意味がない)
高耐圧ピン以外のピンは、NMOSのオープン・ドレインです。入力として使うには、出力を'1'とする必要があります('0'だとピン=GNDになって意味がない)
#SCK・SOだけはプッシュプルに見えますが、データシート上は"NMOS Open Drain"と書かれています。


システム・クロック = 発振周波数の1/8となります。
ポートは全てOFFです。

引っかかった点
目次
ロード命令はLD(B) dst, srcなので油断していました。
× STB dst, src
○ STB src, dst
データシートではオペランドの並びまでは書いていないのでわからず、Embedded Controller Handbookを見てようやく気付きました。
最初、全く気にもしなかったので、ロジアナでアドレスバスを見たり、アセンブル結果の機械語やヘッダの番地を確かめたりして、解消に3時間位かかりました。
もはやSFRのアドレスが違うのでは?と最初から最後までSFRをCLRB命令でCLRしたところ、ポートが動き、そこでようやく「別の命令なら動く」とわかり、それが解消の糸口となりました。
しかし…ADDでもなんでも、左がdst、右がsrcに思うので、直感に反した並びだと思います。
反論として、第一オペランドがCPUに近い方、第二オペランドがCPUから遠い方ってのはあるかと思いますが…。


最初、SJMP・LJMP命令の存在に気づかず、無条件ジャンプはBR命令だと思っていました。
困ったことにBR命令はオペランドに任意のメモリを取り、例えばラベルをオペランドに書いてもアセンブル出来てしまいます(BR LOOPのように)。
しかし実際はBR (LOOP) を実行することになるので、これでLOOPラベルの場所に飛ぶと思っていると大間違いです。
正解はSJMP/LJMP LOOP。
ループごとにLEDを+1しようとしたところ、最初の+1で止まってしまったので困ってしまったわけですが、まさかただのジャンプが(しかもアセンブルが通る)問題だとは思っていなかったので、解消に時間がかかりました。
これは自分のミス。なぜ気付かなかったのかと思うほど。何も考えずに書いてたんでしょうね…。
ジャンプ命令 JNE を介すと挙動が変になるのでジャンプを疑っていましたが、そうではなくて自分のプログラムフローのミスでした。アセンブラだとありがち。
問題のコード(毎回代入するため無限ループとなる):
DELAY2: LDB R2, #07FH SUBB R2, #001H JNE DELAY2
正しいコード:
LDB R2, #07FH DELAY2: SUBB R2, #001H JNE DELAY2
やはり動かさなければ、ということで最小構成のボードを作りました。

目次
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 |
8096といっても色々あるようですが(世代別には8096, 80C196とあり、8096世代でも8096(ROMなし)・8396(ROM内蔵)がある。BHとつくものとつかないものもある)、今回使うのは、若松で販売しているものです(@1,390円)。
株式会社若松通商 / A8096-90 https://wakamatsu.biz/products/detail/456050

スペックは、ハードウェア面に限って書くと、
で、PGAパッケージを今の時代に扱おうとすると地味に困るもののひとつが、このPGAソケットが入手困難なことです。
幸い、68ピンはまだ若松に在庫があったので、これを買って使っています。
株式会社若松通商 / PCPS-68-001 https://wakamatsu.biz/products/detail/455601
若松の写真だと解像度が低く分からないですが、表面に"PCPS-A68" "JAE"と書かれています。
…PGAソケットが入手できなくなったら、2.54mmピッチであれば、「丸ピンIC用ソケット (シングル40P)」などを切って並べるんですかね。

最小構成なのでありません。
RAMとしては、内蔵のレジスタファイルの232Bを使うことにします。
どうせテスト程度でしか使わないので、十分だと思います。
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だと思います。
アドレス・データ共用なのでラッチが必要です。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Ωの方が機能しそう)

以下、説明
ピンが幾つかあります。
| 信号名 | 処理 | コメント |
|---|---|---|
| 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のテスト用 |
アドレス・データバスは共用なので、74HCT373でALEのタイミングでラッチします。16-bitだとラッチ入れるだけでも2個必要で面倒ですね…。
| ポート | 方向 | 処理 | コメント |
|---|---|---|---|
| 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 | - |
まず、アドレス入力は、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、書き込みに少し癖?があります。
具体的には、左下の"Options" - "Check ID"を外して読み込み・書き込みします。こうしないと、Read時 0x01 0x80 がひたすら読み出されてしまい、Write時もそのせいで書き込めない判定になってしまいます。
また、"PROG."ボタンの下あたりに"8 Bits" "16 Bits"というチェックボタンがあり、これによってダンプ表示が1バイト毎か2バイト毎かが変わります。
このとき、16-bit表示だと、アドレスが1/2されますが、例えば0x2080のバイトを読むために、実際にメモリにアクセスするときは1ビットシフトした0x1040で読むことに相当するので、問題ありません。



新シリーズです。Intelの8096、具体的には若松で売っているA8096-90を使います。
8086ではありません。eBayでは中古が沢山売られている気がしますが、なぜか全然使っている人がいません。
いい加減マイコンばかりでなくプロセッサーを触れよ、と思われているのかもしれませんが…まあ、作っちゃったし…ボード…。

目次
(以下、加筆修正中)
Intelの16-bitマイコンです。
Application Noteの先頭に以下のように書かれているように、とにかく応答速度が早いことを念頭に置かれたマイコンです。
The 8096 has been designed for high speed/high performance control applications.
なんか…全体的にIntelっぽさはありません。どちらかというと、Z8に似ているなと思いました(特に割り込み、乗除算サポート、内蔵RAM)。組み込みの中でも高速処理向けってところが似ているんでしょうか(Z8はそうなのだったか?)。


面白いのは、割り込みベクタは2000H、RESET時は2080Hから始まるところでしょうか…流石にこんな番地初めてです。0000Hでも0FFFFHでもないっていう。Cortex-Mが0x2000 0000からSRAM始まりますけど…SRAMだしな。
しかも、(多分)割り込みベクタはジャンプ先の番地を置きますが、RESET時は命令を置きます。ここがまた微妙に違います。
あと、SFRは0000H~に置かれていますが、PORT3・4についてだけは1FFEH・1FFFHに置かれています。

特にx86とかと互換性はありません。
全体的に、x86を想像してから見てみると違いに驚きます(した)。
ダイジェストでまとめると、以下でしょうか:
全体的なこと
個別のニーモニックについて


| グループ | 命令 | コメント |
|---|---|---|
| 加算 | 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しかありません |
| グループ | 命令 | コメント |
|---|---|---|
| リセット | 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

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の値も(バイナリで)送信しています。これで何文字目が違っているか分かります。
一致している時:

一致していない時:

この問題で、数時間を失いました(ただし大半はイレース時間)。
.
.
.
最初、以下のようなコードを書いていたら、全く動きませんでした。様子としては、全く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です。

さて、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と一致するが…)

MOV D&MOV Eでは動くと分かった段階で、そちらを使ったコードに切り替えて片付けてもよかったのですが。単純なMOVW DEが動かないのはどうにももやもやするので調べ続けた結果、一応の理由は分かってよかったです。
しかしこんなこともあるんですね…。まさか最後の最後でこんな落とし穴があるとは。
ソフトウェア割り込みを使ってみました。ついでに、context switching 割り込みも使ってみました。
78K/Ⅲには、珍しく?ソフトウェア割り込み機能があります。
専用命令 BRK(※)により発生させることが出来る割り込みです。0除算などにより生じる例外とは異なります。
x86でいうINTやsyscall、ARMでのSVCといったところでしょうか。
ただ、システムコールが特権跨ぎとかOS機能呼び出しなどに使われるのに対し、こちらのソフトウェア割り込みはいまいち使い道がよく分かりません。小規模な組み込みOSで使われることを企図しているのでしょうか…?
(※)コンテキスト・スイッチで割り込みに応答する場合は、BRK命令ではなくBRKCS命令を使用する。
通常の?割り込みと異なり、割り込み時に以下のとおり動作します。
また、

本来なら割り込みが発生するところ、指定回数までは、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値をオペランドに設定します。
これ、データシートの図ではオペランドが省略されていて混乱したのですが、コンパイルが通らず気づきました。(プログラム依存とはいえ、省略しないでほしいなあ…)

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の定義がreg78310.incにあることを知りました。そっちをincludeするようにしようとしましたが、少し名前が違うものがあり(例えばEXIC0 → EIC0)、プログラムの修正が面倒なので、現状は.incファイルを読み込まず、SFR定義を残しています。
あと、16-bitのSFR名もありませんね(PWM0とか)。
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にはしない)。

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とか、シリアル経由で与えた数値を用いて計算した方が、動的に計算結果が変わるため、計算速度がわかりやすくてよいのですが、面倒なので固定値同士の計算としました。
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

今度は 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

気を取り直して、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があるということですね。なんでコンパイルできないのか…


PWMを使って2色LEDを制御します。
目次
78K/Ⅲはモーター制御を意識しているのか、PWMが2ch付いています。古めのマイコンにしては珍しい気がします。
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となります。

制御レジスタは、PWMM。加えて、FRCC(Free Running Counter Control Register)にも該当箇所があります。なんで跨っているのか。
FRCC側のENPWMnを'1'に設定しないと、ピンに出力されません。


I/Oピンも設定が必要です:
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となり、ほぼ想定通りでした。


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