勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

チョコッと作るのに便利な基板とツール

この年末は結構忙しいところへ、趣味で使うメインPCの交換を始めてしまったため時間がひっ迫。こうなると記事はパスしようかと思いましたが、ほんの小さな工作をしたのでそれを書きます。月に1回は出さないと。。

f:id:a-tomi:20191222224129j:plain

デスクトップPCは机の下の左右にあるのですが、左のPCがメインでディスプレイは左と中央を使用中。このメインPCをSSD搭載のより高速大容量の新型に入れ替えているところです。ところが使用しているソフトの種類が多いために、期間をかけて順次移行する必要があります。

そのため、右のPCのディスプレイを新PCと共有しながら(切り替えながら)時々作業を行います。右側のキーボーのうち手前が新PCのもの。キーボードはそれでいいですし、ディスプレイは切り替えが可能ですが、困るのは外付けスピーカー。ボーカロイドソフトなども使うため、左右のPCとも結構大きいのを使っています。移行中はとりあえず小さなスピーカーをとも考えましたが、それでも置き場がひっ迫。まてよ、スピーカーも右のPCと共有すればよいか、と気づきました。

回路は次のようにしますが、最近の音声の出し方にはディジタルもあるため共有で問題なしといえず、念のためにR1~R4を入れます。殆どの場合は必要ないかと思いますが。

f:id:a-tomi:20191222225850j:plain

アンプの入力インピーダンスは10KΩですので、Rは1KΩなら影響なしで2つのPC出力間に2KΩが念のために入ることになります。

さて、これを少しでも短時間できちんと作りたいわけです。そのため、PCのライン出力のステレオプラグ2個の半田付けを省略するため出来合いの不要なステレオコードをカット。アンプ側の雌ジャックは過去に適当に部品取りしてあったものを流用。

基板はカットしながら使うのが最速のやりかたと思います。

結局30分で次ができました。テスターで確認してOKです。しかし、最終的にこれをケースに入れる必要があります。

f:id:a-tomi:20191222230701j:plain

裏から見ると次です。

f:id:a-tomi:20191222230922j:plain

基板の配線を拡大すると次。バタバタと作ったので汚い配線ですね^^;

f:id:a-tomi:20191222231000j:plain

ホットメルトで固め、ケースとしてビニールシートを巻いて出来上がり。これで結構頑丈になります。

f:id:a-tomi:20191222231211j:plain

ジャック側に少しつけたホットメルトが固まれば輪ゴムをはずしてよいのですが、このままでOKとしました。PCの後ろ側にあるので恰好は気にしないわけで。写真撮影含めて、全部で40分ほどでスピーカーの共有作業が完了してホッと一息。これで記事はすぐ書けました。

さて、これだけですとあまり参考になりませんので、チョコっと速く作る便利ツールをご紹介します。

まずこのような用途に使う汎用基板は、蛇の目基板よりも次のパターンのものが配線が少なく最適です。ストライプ基板でもよいですが、それだとパターンカットが少し要り、最短時間にはならないですね。

f:id:a-tomi:20191222231833j:plain

基板を何で切るかといえば、一番速いのはミニバンドソー。数秒で好きなようにカットできますし、部品をはんだ付けした後などでもカットが容易です。つけているバンドの種類が一目で分るように品番を貼っています。また、カットの手元にごく簡単なLED照明(百均)をおいてます。

f:id:a-tomi:20191222232018j:plain

金属も問題なく切れます(電話機だって切断できます)が、相手が金属の場合は切削油をつけます。そうしないとバンドの寿命に影響します。

なお、蛇の目基板と上記の基板以外にも次のようなパターンを時間短縮のために使っていますが、似たような急ぐ用途によっては、写真中央のストライプ基板をカットして使うことがとても多いです。基板の余りはいくら小さくとも保管しておくと、それがいつも役立ちますね。

f:id:a-tomi:20191222232418j:plain

以上、速く作る方法のご参考になれば幸いです。

 

ところで、継続しているArduino地震計は本番機へと少しずつ取組中ですが、SRAM取り外し時の機器本体の揺れを防ぐために、スイッチに赤外線信号を使うかと考え中。できると分かったら途端に取り組みが遅くなっている典型^^; もう少し進んだら続きを書きます。そちらは気の長い話ですが、この記事は正反対の、気の短い話でした。

 

©2019 Akira Tominaga, All rights reserved.

 

しっかり記録! Arduino地震計 その2 プログラム1

f:id:a-tomi:20191125114831j:plain

この連載の途中でだいぶ日にちが空いてしまいましたが、Arduino地震計の第2回目、プログラムの説明に入ります。

オリジナル工作が、「できる」ということがわかったとたんに、その後の取り組みスピードが遅くなりますね^^; 今月は仕事と旅程でだいぶふさがったのが原因ではありますが、掲載がゆっくりですみません。

上の写真は試作回路の現状で、前の記事の後でRTC(リアルタイムクロック)のDS3231をI2C接続し、そして使用中のSRAMを表示するためのLED(赤、緑、青)をつけた状態です。最初はLEDは右上のブレッドボードにつけていましたが、配線が込み合うので左上に小さいブレッドボードを加えてつけなおしました。右上のブレッドボードからLEDのグランド配線(右下に3本ある裸ジャンパー)を取り除くの忘れてますが後で外しておきます。

次に回路図を再掲します。

f:id:a-tomi:20191125115705j:plain

LED3個とI2C接続のDS3231を、この回路図のとおりに加えただけです。

Arduinoスケッチのグローバル変数等の定義部分は次のとおりです。

/**********************************************************
   New Seismometer  Nov.24,2019
   (c)2019 Akira Tominaga, All rights reserved.
**********************************************************/
#include "Wire.h"
#include "SPI.h"
// SPI MOSI:11  MISO:12  SCK:13
int cS;               // chip-select pin (variable)
#define cS0 10        // chip-select 0 for SRAM0
#define cS1 9         // chip-select 1 for SRAM1
#define cS2 8         // chip-select 2 for SRAM2
#define cSmin 8       // for chip-select shifting
#define cSmax 10      // for chip-select shifting

int LED;              // LED to show currently used SRAM
#define LED0 7        // SRAM0 used now - Red
#define LED1 6        // SRAM1 used now - Green
#define LED2 5        // SRAM2 used now - Blue
#define LEDmin 5      // for LED shifting
#define LEDmax 7      // for LED shifting

#define Sw 3          // Tact switch at D3 
// (to be replaced by PIC signal(L) generated by IR sig)
#define Timing 16     // Timing signal(L) at A2=D16
#define testPin 2     // Test signal pin D2

// Accelerometer LSM6DS3
#define Accel 0x6B    // Accelorometer addr 0x6B (PIC=D6)
int16_t Galx10[3] = {0, 0, 0}; // measured Gal x 10
int32_t usualGalx10[3]; // usual Galx10 for calculation

byte Qflags = B00000000;  // status to record to SRAM
#define nowQ B10000000    // currently quaking
#define wasQ B01000000    // there was a quake

float qVal = 0;       // quake value
int32_t magQ = 0;     // total/avg Q to check quake
#define magQth 20     // quake threshold (10 in real env ?)
int16_t magQctr = 0;  // counter to get average magQ
#define magQcmax 50   // magQ counter max value  (1 sec)
int16_t qEctr = 0;    // Q end counter to judge end of Q
#define qEcmax 250    // no quake for continuous 5 secs

#define serSpeed 9600

#define srCM 0x01     // Mode command of SRAM
#define srCMseq 0x40  // Mode=sequential
#define srCW 0x02     // Write command of SRAM
#define srCR 0x03     // Read command of SRAM
uint32_t srAdr = 0;   // address in SRAM
#define srAdrMax 131071

/* One rec consists of 4x16bit-int [fileID+Qflags,X,Y,Z]
   [rec at Time of quakeEnd to be replaced with the Time
   of which format is ["FFFF",YY*12+MM,DD*24+hh,mm*60+ss]
*/
int16_t Rec[4];       // integer array for recording
int16_t fileID = 0;   // file ID(repeat 0-250, 16384 recs/file)
#define fileIDmax 250 // repeat from 0 to 250

// for RTC (real time clock)
byte  vR[8];            // values in RTC registers
int vI[8] = { 0, 0, 0, 0, 0, 0, 0, 0}; // integers for RTC data
#define RTC_addr 0x68   // I2C address
#define mds 1           // ss
#define mdm 2           // mm
#define mdh 3           // hh
#define mdW 4           // WW=Day of Week
#define mdD 5           // DD
#define mdM 6           // MM
#define mdY 7           // YY

ピンの割り当てはスケッチをみていただいたほうが、分りやすいかもしれません。

これから本番機を少しずつ作りながら、Arduinoのスケッチを説明していこうと思います。

本番機では、SRAMを交換する際に振動を感知させない処理をいれることと、取り出したSRAMのデータをSDカードへテキストに変換して移す別装置を加えるつもりです。

次にこのArduinoスケッチの現在のSetup内容を掲載します。ここは今後の本番機の仕上がり状況によっては少し変える可能性がありますが。

void setup() {
  Serial.begin(serSpeed);
  pinMode(Sw, INPUT_PULLUP);
  pinMode(Timing, INPUT_PULLUP);

  for (cS = 0; cS < 3; cS++) {
    pinMode(cS, OUTPUT);
    digitalWrite(cS, HIGH);
  }
  for (LED = 0; LED < 3; LED++) {
    pinMode(LED, OUTPUT);
    digitalWrite(cS, LOW);
  }
  pinMode(testPin, OUTPUT);
  digitalWrite(testPin, LOW);

  //Initialize accelerometer
  Wire.begin();
  delay(1);     // unneeded ?
  initAcc();

  SPI.begin();
  // SPISettings mySPI = SPISettings(4000000, MSBFIRST, SPI_MODE0);
  // SPI.beginTransaction(mySPI); // default values are okay

  //Set SRAMs sequential mode (though default)
  for (cS = 0; cS < 3; cS++) {
    digitalWrite(cS, LOW);
    SPI.transfer(srCM);     // command "Mode Writing"
    SPI.transfer(srCMseq);  // set seequential mode
    digitalWrite(cS, HIGH);
    delay(1);                // delay for debug purpose
  }
  cS = cS0;       // prepare chip-select as SRAM0
  LED = LED0;     // prepare to show SRAM0 is used

  // get usual galx10 values
#define nAvg 100
  delay(500);      // avoid initial vibration
  for (int j = 0; j < nAvg; j++) {
    readAcc(); // read values in Galx10[3]
    delay(1);     // set plenty of time
    for (int i = 0; i < 3; i++) {
      usualGalx10[i] = usualGalx10[i] + Galx10[i];
    }
  }
  for (int i = 0; i < 3; i++) {
    usualGalx10[i] = usualGalx10[i] / nAvg;
  }

  /*
    Serial.print("10x  usualX=");
    Serial.print(usualGalx10[0]);
    Serial.print(" Y=");
    Serial.print(usualGalx10[1]);
    Serial.print(" Z=");
    Serial.println(usualGalx10[2]);
  */
  /* wPushButton();          // wait for button pushed
    delay(500);             // avoid vibration by button
    Serial.println("Start");
  */
}

 Loop部分以降は今後の本番機製作続行に合わせて載せていきたいと思います。現時点でのユーザー定義関数は次のとおりです。まだコメントが十分に入っておらずすみませんが、とりあえず現状です。装置の初期化などもこちらに入っています。

ライブラリーを使う代わりに必要箇所だけ装置異存のコーディングにしたところは、絶対値等含め良いコーディングではないです^^; しかし、Loop部分を含め全体でスケッチ、グローバル変数共に容量の1/4未満しか使用せず、ATmega328Pの余裕はたっぷりです。

/***************************************
   User defined functions
 ***************************************/

// *** Initialize LSM6DS3 "initAcc()"
void initAcc(void) { //
  Wire.beginTransmission(Accel);
  Wire.write(0x18); //CTLr9_L
  Wire.write(0x38); //Acc XYZ axis enabled
  Wire.endTransmission();

  Wire.beginTransmission(Accel);
  Wire.write(0x11); //CTL2_G
  Wire.write(0x00); //Gyro not used
  Wire.endTransmission();

  Wire.beginTransmission(Accel);
  Wire.write(0x10); //CTRL1_XL
  Wire.write(B00110011); //range2G, anti-alias 50Hz
  Wire.endTransmission();

  Wire.beginTransmission(Accel);
  Wire.write(0x13); //CTL4_C
  Wire.write(10000000); //BandW set by CTRL1_XL
  Wire.endTransmission();

  Wire.beginTransmission(Accel);
  Wire.write(0x15); //CTL4_C
  Wire.write(0x80); // disable Gyro trig and XL high performance
  Wire.endTransmission();

  Wire.beginTransmission(Accel);
  Wire.write(0x11); //CTL2_G
  Wire.write(0x00); //Gyro not used
  Wire.endTransmission();
}

// *** read LSM6DS3 "readAcc()"
void readAcc(void) {
  Wire.beginTransmission(Accel);
  Wire.write(0x28);   // address of top of measured values
  Wire.endTransmission();
  Wire.requestFrom(Accel, 6);
  delay(1);  // wait for measurement ready
  while (Wire.available() < 6) {} // wait for data ready
  int16_t Bytes;
  for (int i = 0; i < 3; i++) {
    // Data sent LSB first (little endian by default),
    // defined by BLE bit1=0 in CTRL3_C reg(0X12)
    byte ByteL = Wire.read();
    byte ByteH = Wire.read();
    Bytes = ByteH * 256 + ByteL;
 // followings are of no use for Arduino (for other mc only)
// if ((ByteH & 0x80) == 0x80) // if negative value
// {
// ByteH = ~ByteH; // get complement of ByteH
// ByteL = ~ByteL; // get complement of BYteL
// Bytes = -(uint16_t)(ByteH * 256 + ByteL)-1;
// } // calc Galx10: refer to Table18 in AN4650 of LSM6DS3 Galx10[i] = Bytes * 10.0 * (980.655 / 16393.0); } } // *** Write 8 byte Rec at srAdr "wRec()" void wRec(void) { digitalWrite(cS, LOW); SPI.transfer (srCW); SPI.transfer ((byte)(srAdr >> 16)); SPI.transfer ((byte)(srAdr >> 8)); SPI.transfer ((byte)srAdr); for (int i = 0; i < 4; i++) { SPI.transfer ((byte)(Rec[i] >> 8)); SPI.transfer ((byte)Rec[i]); } digitalWrite(cS, HIGH); } // *** Shift SRAM to next void shiftSram(void) { // write quake-end inf, as the last record // which is ["FFFF",YY*12+MM,DD*24+hh,mm*60+ss] rRTC();
 cR2I(); // convert RTC to Integer format Rec[0] = -1; Rec[1] = vI[mdY] * 12 + vI[mdM]; Rec[2] = vI[mdD] * 24 + vI[mdh]; Rec[3] = vI[mdm] * 60 + vI[mds]; wRec(); // write Rec to current srAdr // shift cS (shift SRAM) cS--; srAdr = 0; if (cS < cSmin) { cS = cSmax; } // shift LED digitalWrite(LED, LOW); LED--; if (LED < LEDmin) { LED = LEDmax; } digitalWrite(LED, HIGH); } // *** Read 8 byte Rec at srAdr : rRec() void rRec(void) { digitalWrite(cS, LOW); SPI.transfer (srCR); SPI.transfer ((byte)(srAdr >> 16)); SPI.transfer ((byte)(srAdr >> 8)); SPI.transfer ((byte)srAdr); for (int i = 0; i < 4; i++) { byte rByte0 = SPI.transfer(0x00); byte rByte1 = SPI.transfer(0x00); Rec[i] = rByte0 * 256 + rByte1; } digitalWrite(cS, HIGH); } // *** wait for button pushed : wPushButton() void wPushButton(void) { Serial.println("Button pls"); while (digitalRead(Sw) == HIGH) {} while (digitalRead(Sw) == LOW) {} } // *** read Real-Time-Clock void rRTC(void) { Wire.beginTransmission(RTC_addr); Wire.write(0x00); Wire.endTransmission(); Wire.requestFrom(RTC_addr, 7); while (Wire.available() < 7) {} // wait for data ready for (int i = 1; i < 8; i++) { vR[i] = Wire.read(); } // Wire.endTransmission(); } // *** convert RTC-format to Integers void cR2I(void) { vI[mds] = ((vR[mds] & B01110000) / 16) * 10 + (vR[mds] & B00001111); vI[mdm] = ((vR[mdm] & B01110000) / 16) * 10 + (vR[mdm] & B00001111); vI[mdh] = ((vR[mdh] & B00100000) / 32) * 20 + ((vR[mdh] & B00010000) / 16) * 10 + (vR[mdh] & B00001111); // vI[mdW] = vR[mdW]; vI[mdD] = ((vR[mdD] & B01110000) / 16) * 10 + (vR[mdD] & B00001111); vI[mdM] = ((vR[mdM] & B00010000) / 16) * 10 + (vR[mdM] & B00001111); vI[mdY] = ((vR[mdY] & B11110000) / 16) * 10 + (vR[mdY] & B00001111); } // *** for test only void testEnd(void) { for (srAdr = 0; srAdr < 131072; srAdr = srAdr + 8) { Serial.print(srAdr); Serial.print("="); rRec(); for (int i = 0; i < 4; i++) { Serial.print(Rec[i]); Serial.print(","); } Serial.println(""); } while (1) {} }

 最後にある関数は単なるテスト用ですので無視してください。

 

上のコーディング中、Junichi Takao様にご指摘をいただき直させていただいたのは次のミスです(2019.11.26)。的確なご指摘ありがとうございます。また、お手数をおかけしました。

間違い: if ((ByteH && 0x80) == 0x80) { // if negative value

修正済: if ((ByteH & 0x80) == 0x80) { // if negative value

 テストではロジックアナライザーで受けた信号を手計算した値とこのプログラムのアウトプットの値とが、どれも一致していたため気づきませんでした。前の記事の最後のグラフはこのプログラムのアウトプットで不自然な点もありませんですし。

(以下2019.11.29追加記述)簡単なテスト結果はif文以下が不要で削除(コメント化)しました。追加のご指摘もそのように頂きました(ありがとうございます)。

他のマイクロコントローラー機種では演算がintegerにどう変換されるかが違うことも多くありますので、ATmega環境に不案内にて余計なコーディングをしたうえ、そこへ行かない間違いをしました<(_ _)>。テストをして確認したところ、前のif文以下がもし実行されると下位1ビットに誤差1(0.03ガルほど)が出ます(0をブール反転すると-1になるためです)。使うのが0.1ガル単位なのでほぼ無視できそうですが、後でFFT変換したりするのに若干影響しそう。今ではコメントの中とはいえ一応正しい式に直しておきました。これと関係ありませんが、例えばArduinoでReferenceになかったatoiかitoa関数が動きましたが、使うとLoop内で多数回に1度だけエラーがでるのをかつて経験、以後自信のない仕様は面倒なので避けるという癖となっておりました^^; 今回の件の簡単なテスト結果をこの記事の最後につけておきます。

 

さて、前の記事で書いたとおり、正確なタイミングで20mS単位のサンプリングをするためのトリガーを出しているのは8ピンPICです。そのアセンブラー・プログラムを示します。正確な50.00Hz(周期20.00mS)信号が得られます。

Cで正確に作るのは困難のためアセンブラーで作っています。ですが作る時間はわずか。筆者が普段作っているPICプログラムからのコピペだけで殆どできましたから。

;U1910-Xtal-Timer-12F1822-V00-01.asm			As of Oct. 30, 2019
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;										;
; 	Xtal Timer with PIC12F1822	Version 00-01				;
;	        All rights reserved. 2018(C) Akira Tominaga			;
;	  Function								;
;										;
;	  Input/Output for 16F1823						;
;		RA0 LEDr output (on =H)						;
;		RA1 NC								;
;		RA2 Timing Signal output Tmg(on =L)				;
;		RA4 Osc2 for Xtal HS 16MHz					;
;		RA5 Osc1 for Xtal HS 16MHz					;
;	  Remarks								;
;		1. Clock = HF Xtal (	16.000MHz)				;
;			Hence 1 step = 0.25 micro seconds			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	page
	list		p=12F1822      ; list directive to define processor
	#include	 ;P12F1822 processor specific variable definitions
    __CONFIG _CONFIG1, _FOSC_HS & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
    __CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _BORV_19 & _LVP_OFF
;
	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Macro definitions						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Device dependent Macros	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
DEVset	macro	
	BANKSEL	OSCCON		; Bank=1
	movlw	B'00000000'	; HS Xtal oscillator
	movwf	OSCCON
;
;	BANKSEL	INTCON		; Interrupt Con (in all banks hence comment)
	clrf	INTCON		; Disable all interrupts
;
; PORTA initialization
	BANKSEL	PORTA		; Bank=0
	clrf	PORTA
	BANKSEL	LATA		; Bank=2
	clrf	LATA
	BANKSEL	ANSELA		; Bank=3
	movlw	B'00000000'	; No use of Analog input
	movwf	ANSELA
	BANKSEL	TRISA		; Bank=1
	movlw	B'11111010'	; RA0 and 2 are output
	movwf	TRISA
;
	BANKSEL	ADCON0		; Bank=1
	clrf	ADCON0		; No use of ADC	
;
	BANKSEL	OPTION_REG	; Bank=1
	bcf	OPTION_REG,7	; Enable weak pull-up
	BANKSEL	WPUA		; Bank=4
	movlw	B'00001000'	; Weak Pull-up for RA3
	movwf	WPUA		;  
;
	clrf	BSR		; Bank=0		
	endm
;	
OUTini	macro			; L for LEDs, and H for Signal
	nop			; Timing for PORTA
	movlw	B'11001100'	; Set space (H) to RA2
	movwf	PORTA
	goto	$+1		; Timing for PORTA
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I/O macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For general purpose	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LEDon	macro			
	goto	$+1
	bsf	PORTA,LED
	goto	$+1
	endm
;
LEDoff	macro			
	goto	$+1
	bcf	PORTA,LED
	goto	$+1
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Constant time consuming		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic	macro			;Consume 1 μS only
	goto	$+1
	goto	$+1
	endm
;
Mic2	macro	mic2p		;Consume 2 μS x n
	movlw	mic2p
	call	Mic2r
	endm
;
Mic2p5	macro 	mic25p		; Consume 2.5μS x n
	movlw	mic25p
	call	Mic25r
	endm
;
Mic5	macro	mic5p		; Consume 5μS x n
	movlw	mic5p
	call	Mic25r
	movlw	mic5p
	call	Mic25r
	endm
;
Mic50	macro	mic50p		; Consume 50μS x n
	movlw	mic50p
	call	Mic50r
	endm
;
Milli	macro	millip		; Consume mS x n
	movlw	millip
	call 	Millir
	endm
;
Mil100	macro	mil100p		; Consume 100 mS x n
	movlw	mil100p
	call 	Mil100r
	endm
; 
Secs	macro	secsp		; Consume Second x n
	movlw	secsp
	call	Secsr
	endm
;
Mins	macro	minsp		; Consume Minute x n
	movlw	minsp
	call	Minsr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Debug and Abend macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Udebug	macro 	Addr
	movf	Addr,W
	call	Udbgr
	endm
;
Ustop	macro				; Stop until Sw2 on (=L)
	call	Udstopr			; 
	endm
;
Trigger	macro				; DSO external trigger
	goto	$+1
	bsf	PORTtrg,Trig
	Mic2
	bcf	PORTtrg,Trig
	goto	$+1
	endm
;
Uabend	macro	abn			; User Abnormal-end number
	movlw	abn
	goto	Uabendr
	endm
;
Ublink	macro	bno			; Blink LED for specified times
	movlw	bno
	call	Ublinkr
	endm
;
Ucp	macro	CPNo 		; Check point literal chr to display to LCD
	movlw	CPNo
	movwf	Ucpc
	Udebug	Ucpc
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Files and Equations					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Files				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	cblock	H'20'		; available from H'20' to H'6F' (80 bytes)
;
; For application support
	Flags		; Flag byte
;
; Areas for time consuming subroutines
; Do not change the sequences from Mic5c to Minsc
;	as these areas are co-used with calculation parameters
	Mic25c
	Mic50c
	Millic
	Mil100c
	Secsc
	Minsc
;
; Areas for debugging routine
	DmpA 		; Display byte area 
	Dmp8C 		; Bit counter for loop (Initial Dmp8V =8)
	BlinkC 		; Counter for blinking (set for Debugb or Abendb)
	Abendn		; Abend number
	Ucpc		; User Check point chr to trace 
;
	endc		
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Equations			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For PORTA		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LED	equ	0		; LED
Tmg	equ	2		; Timing signal(L)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Values			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Flags Byte		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LEDsv	equ	0		; LED save bit for debug
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For Debug		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Dmp8V	equ	D'8'		; Debug display bit counter initial value
Debugb	equ	D'8'		; number of blinkings to show Debugging
Abendb	equ	D'30'		; number of blinkings to notify Abend
;
	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Initializing						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	org	0
	goto	Startp		; Go to start entry
	org	4		; This is Interrupt entry
	retfie			; 
;
Startp	DEVset			; Define ports
	OUTini			; L for LED(0), and H for Clk and Dio
	clrf	Flags		; Clear Flags byte
;
	LEDon			; Indicate I am awake
	Mil100	1		;                      ***
	LEDoff
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Main program loop = precise 20mS/loop required		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mainp	bcf	PORTA,Tmg	; Tmg on (L)
	Mic5	D'10'		; On for 50 micro seconds
	bsf	PORTA,Tmg	; Yes, clear Tmg signal
;
	goto	$+1		; +0.5microS = 51microS up to here
;
; Requires 20mS-0.052mS =     	    19.948mS here
	Milli	D'19'		; / 19.0mS
	Mic50	D'18' 		; / 19.9mS
	Mic5	D'9'		; / 19.945mS
	Mic2	1		; / 19.947mS
	Mic			; / 19.948mS Total 19.999 mS
;
; Last segment consumes 0.001mS				; 
	goto	$+1		; consume 1microS hereafter 
	goto	Mainp		; and loop
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Timing subrooutines for general purposes;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.0 micro S x n	(Mic2)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic2r	movwf	Mic25c		; + Wset + call = 1 micro sec 
;
Mic2l	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic2li		; else go out (2nd time 1.75 mic sec)
	return		
;
Mic2li	goto	$+1		;            (2nd time 2.25 mic sec)
	nop			;	     (2nd time 2.5 mic sec)
	goto	Mic2l		; go back    (2nd time 3 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.5 micro S x n (Mic2p5)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic25r	movwf	Mic25c		; + Wset + call = 1 micro sec 
	nop			; 1.25 micro sec
;
Mic25l	nop			; 1.5 micro sec (2nd time 4 mic sec)
	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic25li		; else go out (2nd time 2.25 mic sec)
	return		
;
Mic25li	Mic			;	      (2nd time 3.25 mic sec)
	goto	Mic25l		; go back    (2nd time 3.75 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	50 Microseconds	x n	Mic50  	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Mic50r	movwf	Mic50c	 	; set how many 50 microsec (1 micro sec to here)
	nop			; 1.25 micro sec up to here
;
Mic50l	Mic2p5	D'19'		; + 47.5 = 48.75 mic sec (2nd time 98.75 mic sec)
	nop			; + 0.25 = 49 micro sec (2nd time 99 mic sec)
; 
	decfsz	Mic50c,F	; If exhausted then 1 mic S hereafter
	goto	Mic50li		; else go out (2nd time 49.75 mic sec)
	return
;
;
Mic50li	Mic			; 	  (2nd time 50.75 mic sec)
	goto	Mic50l		; go back (2nd time 51.25 mic sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Milliseconds x n	(Milli)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Millir	movwf	Millic 		; set how many 1 mil sec (1 mic S up to here)
	nop			; 1.25 micro sec
;
Millil	Mic50	D'19'		; + 50 mic x 19 = 951.25 mic S (2nd, 1951.25) 
	Mic2p5	D'19'		; + 47.5 mic = 998.75 micro S  (2nd, 1998.75)
	nop			; +0.25 mic = 999 micro sec    (2nd, 1999)
;
	decfsz	Millic,F	; If  exhausted then 1 micro sec hereafter
	goto	Millili		; else go out (2nd, 999.75 mic S)
	return
;
Millili	Mic			; 		(2nd time 1000.75 mic S)
	goto	Millil		; go back (2nd time 1001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	100 Milliseconds x n	(Mil100);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mil100r	movwf	Mil100c		;set how many 100 ms(1 micr sec up to here)
	nop			; 1.25 micro sec
;
Milhl	Milli	D'99'		;+1ms x 99 = 99001.25 micS (2nd,199001.25mic)
	Mic50	D'19'		; + 950 mic = 99951.25 micS(2nd.199951.25mic)
	Mic2p5	D'19'		; + 47.5 mic = 99998.75micS(2nd,199998.75mic)
	nop			; + 0.25 mic = 99999 mic S (2nd,199999 micS)
;
	decfsz	Mil100c,F	; If exhausted then 1 micro sec hereafter
	goto	Milhli		; else go out (2nd time, 99999.75 mic S)
	return
;
Milhli	Mic			;  	    	(2nd time, 100000.75 mic S)
	goto	Milhl		; 		(2nd time, 100001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Seconds x n	 (Secs)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Secsr	movwf	Secsc 		; set how many sec ( 1 mic sec up to here)
	nop			; 1.25 micro sec
;
Secsl	Mil100	D'9'		; 
	Milli	D'99'		; + 999 milli sec = 999001.25 micro sec
;
	Mic50	D'19'		; + 950 mic = 999951.25 micro sec
	Mic2p5	D'19'		; + 47.5 mic = 999998.75 micro sec
	nop			; + 0.25 mic = 999999 micro sec
;
	decfsz	Secsc,F		; If exhausted then 1 micro sec hereafter
	goto	Secsli		; else, go out 
	return
;
Secsli	Mic
	goto	Secsl		; (Second time, Sec + 1.25 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Minutes	x n	 (Mins)		;
;	 Overhead ignored, that is only	;
;	 751.25 Mic S even when 100 Min	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Minsr	movwf	Minsc		;set how many minutes from parameter
;
Minsl	Secs	D'60'		; 1 Seconds x 60
	decfsz	Minsc,F
	goto	Minsl
	return
;
	space
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; User debug subroutine		;
;	Show bit 7 to 0 	;
;	of specified area	;
;	by Udebug macro		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Udbgr	clrf	BSR		; Set Bank=0
	movwf	DmpA		; move data to Dmpa
;
	btfss	PORTA,LED	; Check if LED=on (on=High)
	goto	UdbLoff		;  if LED=off(Low), skip saving process
	bsf	Flags,LEDsv	; Save LEDon status
	LEDoff			; and off LED
	Mil100	D'5'		; wait for 0.5 sec5 in case LED has been on
;
UdbLoff	movlw	Dmp8V		; set counter 8
	movwf	Dmp8C		; to Dmp8C
;
Udblp	Ublink	Debugb		; Blink for Debugging = 8 times
	btfsc	DmpA,7		; check top bit 7
	goto	UdbOn		; if on then to UdbOn
	goto	UdbOff		; if off then to UdbOff
;
UdbOn	LEDon
	Mil100	D'5'		;
	goto	Udbeck
;
UdbOff	LEDoff
	Mil100	D'5'		;
;	goto	Udbeck
;
Udbeck	decfsz	Dmp8C,F
	goto	Udbnext
	goto	Udbend
;
Udbnext	rlf	DmpA,F
	goto	Udblp
;
Udbend	Ublink	Debugb		; end blinking and 
	Mil100	D'30'		; blank for 3 seconds to write down
;
	btfss	Flags,LEDsv	; Check if LED was on
	goto	Udbret		;  no, goto return
	LEDon			;  if it was on, then on again
	bcf	Flags,LEDsv	; Clear LED save flag
;
Udbret	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Blinking to show debug or abend	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Ublinkr	movwf	BlinkC
Ublinkl	LEDon			; LED on
	Milli	D'10'		; for 10ms
	LEDoff			; LED off
	Milli	D'50'		; for 50ms
;
	decfsz	BlinkC,F
	goto	Ublinkl
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Abend routine			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Uabendr	clrf	BSR
	movwf	Abendn			; Set Uabend number
;
	Ublink	Abendb			; Blinking 30 times
	Udebug	Abendn			; Show Abend number
	goto	$
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	End of program						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 

 

 この場合PIC内蔵のオシレータは使いません。外付けの16MHzクリスタルを用いることで十分な精度が出せます。回路図のPIC部分をご参照ください。

  

 このArduino地震計連載への追加記事は今後も少しずつ載せていく予定です。途中で別の記事が入るかもしれませんが、本番機ができあがるまでしつこく書き足していくつもりです。据え付け完了までにはまだいくつかの難関が予想されますがクリアできそう。

趣味だけでなく、仕事もこんなに少しずつのマイペースでゆっくりできると楽ですね^^; 

 

以下に珍しく余談を書いておきますので読み飛ばされてもかまいません。

こういう活動はプロジェクトというよりは長期の「プログラム」です。個人がコツコツとやりながら何かを成し遂げるにはうってつけのやり方だと思います。

 

f:id:a-tomi:20191125133012j:plain

 目的意識が強くないとそうはいきませんね。できるかどうか不明確なオリジナル電子工作が、「できる」と分ったとたんにペースがおちる理由は、目的意識、言い換えれば追求する意欲が減るからでしょう。目的のために活動しますが、全体的に言えばたとえば次の図が一例です。

f:id:a-tomi:20191125133452j:plain

 余談を書き始めるときりがありません。こういうことは次のKindle本にしたためてありますのでご参考まで。

https://www.amazon.co.jp/ /dp/B079WNNHW8/

 

(2019.11.29追記)

符号なしバイト属性の演算結果を符号付きInteger 属性に変換する簡単なテストの結果を添付します。Arduino言語を熟知されている方には初歩的なことかと思いますので、これまた読み飛ばしてください。今回の簡単なテストプログラムは次です。

byte ByteL;
byte ByteH;
int16_t Bytes;

void setup()
{
  Serial.begin(9600);
// case 01
  Serial.print("Case 01: ");  
  ByteH = B10000000;
  ByteL = B00000000;
  Bytes = ByteH * 256 + ByteL;
  printResults();

 // Case 02
  Serial.print("Case 02: ");  
  ByteH=B10000000;
  ByteL=B00000001;
  Bytes = ByteH * 256 + ByteL;
  printResults();

 // Case 03
  Serial.print("Case 03: ");  
  ByteH=B11111111;
  ByteL=B00000000;
  Bytes = ByteH * 256 + ByteL;
  printResults();

 // Case 04
  Serial.print("Case 04: ");  
  ByteH=B11111111;
  ByteL=B00000001;
  Bytes = ByteH * 256 + ByteL;
  printResults();

   // Case 05
  Serial.print("Case 05: ");  
  ByteH=B11111111;
  ByteL=B11111111;
  Bytes = ByteH * 256 + ByteL;
  printResults();

   // Case 06
  Serial.print("Case 06: ");  
  ByteH=B11111111;
  ByteL=B11111110;
  Bytes = ByteH * 256 + ByteL;
  printResults();

 Serial.println("** Past negative process");
 
 // Case 11
  Serial.print("Case 11: ");  
    ByteH=B10000000;
  ByteL=B00000000;
  ByteH=~ByteH;  // complement
  ByteL=~ByteL;  // complement
  Bytes = -(ByteH * 256 + ByteL);
  printResults();
  
  // Case 12
  Serial.print("Case 12: ");  
  ByteH=B10000000;
  ByteL=B00000001;
  ByteH=~ByteH;
  ByteL=~ByteL;
  Bytes = -(ByteH * 256 + ByteL);
  printResults();

// *** Corrected
 Serial.println("** Corrected negative process");

  // Case 21
  Serial.print("Case 21: ");  
  ByteH=B10000000;
  ByteL=B00000000;
  ByteH=~ByteH;  // complement
  ByteL=~ByteL;  // complement
  Bytes = -(ByteH * 256 + ByteL)-1;
  printResults();

    // Case 22
  Serial.print("Case 22: ");  
  ByteH=B10000000;
  ByteL=B00000001;
  ByteH=~ByteH;  // complement
  ByteL=~ByteL;  // complement
  Bytes = -(ByteH * 256 + ByteL)-1;
  printResults();
  
  // infinit loop
  while(1){}
}

void loop()
{
}

void printResults(void)
{
  Serial.print(" ByteH=");
  Serial.print(ByteH,HEX);
  Serial.print(" and ByteL=");
  Serial.print(ByteL,HEX);
  Serial.print(", then Bytes=");
  
  Serial.println(Bytes); 
}

 次がテストの結果です。

f:id:a-tomi:20191129140904j:plain

 

では今回はここまでにします。

 

 

©2019 Akira Tominaga, All rights reserved.

しっかり記録! Arduino地震計 その1

最近の優秀なセンサーなら地震計作りは簡単そう?とはいえ東西、南北、上下の加速度を50分の1秒以下の単位で、常時正確に記録する装置は工夫のし甲斐がありそう。色々考えたらついハマってしまい、少しずつチャレンジすることにしました。

f:id:a-tomi:20191031210639j:plain

机上の実験回路で、X、Y、Z方向に軽く揺らすのは意外に難しいものですね^^;

さて、これをArduinoで作る構想が固まり試作回路でのテストを終えた段階です。

この地震計では、開始から終わりまで5分以内の地震の記録を初期微動の前から終わりまで連続3回分は逃さず取れるように考えています。地震があった際はLED表示でわかるようにし、2つ目の地震が来る前までに使った記録媒体1個を交換するか、3つ目の前までに2個を交換することで、いくらでも取れるという方法です。もちろん稼働状態のままで交換できるように考えていますが、万一交換前に4つ目がきてしまったら?1個目の記録が上書きされます?、いやいやローコスト(1つ200円ほど)の記録媒体なので、もっとつないでおけばよいかな?・・^^;

回路は次のように考えました。毎度汚い手書きですみませんが。

f:id:a-tomi:20191031211425j:plain

左上のPICで正確な20.00mSを作って測定のトリガーにしています。右上の3つは1024KbitSRAM、1個あたり1024x1024ビット=131,072バイトのシリアルRAMです。最終的にはそれぞれボタン電池でバックアップしとり外せるように作ります。つまり書き込み中の1つを避ければ運用中の交換ができるようにする予定。

そのため地震を記録するまでは同じRAMに上書きし続けます。地震が終わったら終了時刻を記録して次のRAMへと移ります。それにより地震の始まり時刻も分かるように記録できます。回路としては上図のままで、I2C接続にRTC(リアルタイムクロック)も追加するわけですが。

まず、プログラムしたPICの出すトリガー信号を確認すると正確な50.00Hzになっています(較正済オシロの写真右下)。

f:id:a-tomi:20191101112617j:plain

 

地震計は種類によって記録の対象が揺れ幅、速度、加速度の3種類あるようですが、前の2つは大型になるため難しそう。ところが、建築などに使われる定番の実用地震波データは、20mS単位の縦横上下の加速度(単位:gal ガル)になっています。よって作成の簡単な加速度型だけを考えます。

現代の3軸加速度センサーは超小型なのに測定は精密で正確です。ためしに鉛直軸を測るとどの軸でも正確な1G(約981ガル)が正しく計測されるのに驚きます。スマホに使われて発達したようですが、中に使われているマイクロマシン技術の高さが推察されます。手持ちの加速度センサーICのブレークアウトモジュールは次の4種で、いずれも簡単にI2C接続できるものです。世界にはもっともっとたくさんの種類が出ています。

f:id:a-tomi:20191031212706j:plain

この中で左下の種類だけは3軸の回転加速度用のジャイロなので使えません。どれもセットアップとして細かい設定が必要です。試しにMPU6050とLSM6DS3を比較してみた結果では、LSM6DS3の方がノイズが小さく、演算すれば有効数字4桁近くの正確なガル値が取れるようなので、これを使ってみることにします。

この場合はブレークアウトモジュールに3.3Vへのレギュレータと信号電圧変換ICが搭載されていますので、電圧は5Vを直接接続できます。

ブレッドボードで次の様に試作しました。

f:id:a-tomi:20191031222336j:plain

ほぼどのセンサー用にも、汎用のArduinoライブラリーが数種あるようですが、正しく使うには詳しいデータシートとアプリケーションノートを読む必要があります。厚い(合計200頁ほど)のですが、必要な個所を全て読んだらライブラリーを使う必要性がなくなります。I2Cで直接扱うことができるので資源が大幅に節約されます^^;また、好き勝手に使えることで、Arduinoの通常のLoop1サイクル内での測定と記録が悠々と間に合います。

シリアルRAMもライブラリーがありますが、このSPIはそれを使わずに手で書いてもごく単純にできます。SPI設定のDefaultもATmegaと同じだし、Microchip社製品だけあって電圧範囲も5Vまでが広く許されており助かります。

プログラム等の作成方法を含めて、これから何回かにわけて説明して行こうと思います。

とりあえずこの試作状態で、期待通りにきちんと動かすことができましたので、次にLoopのタイミング測定結果を示します。以下、ロジックアナライザーの写真が見えにくい時は写真の上でクリックしてください。

f:id:a-tomi:20191031215402j:plain

ロジックアナライザーの各チャネルは次のとおりです。

1)I2C接続(A4,A5)

 チャネル1:SCL

 チャネル2:SDA

 2)SPI接続(D11,12,13および使用CSの接続先)

 チャネル3:CS(Chip SelectまたはSlave Select)

 チャネル4:SPIクロックSCK(ここでは4MHz)

 チャネル5:MOSI(Master-Out Slave-In)

 チャネル6:MISO(Master-In Slave-Out)

3)Arduino Loop内処理時間 

 チャネル7:Loop中の空き時間(トリガー待ちの余裕時間)を出力するテスト端子です。

ちょっと余談ですが、このためにdigitalWrite命令でCPUサイクルをかけたくないですから、筆者の常套手段として待ち命令の上下をExclusive ORで挟むことで、ピンを機械語1命令で直接反転させます。まさかと思われるかもしれませんがArduinoではdigitalReadもdigitalWriteも結構CPUサイクルがかかります。つまりトリガー待ち時間がオーバーヘッド最小で計測できるようにしてあります。これで、Arduino Loop内処理にどれだけ時間がかかり、どれだけ待ち時間があるのかが一目瞭然です。この例では次のようコーディングしてあります:


 #define Timing 16        // Timing signal(L) at A2=D16
 #define testSig B00000100   // test signal at D2 pin
 
 // Wait for 20mS time-trigger (LOW for 50 microseconds)
PORTD = PORTD ^ testSig; // Reverse test signal while (digitalRead(Timing) == HIGH) {} PORTD = PORTD ^ testSig; // Reverse test signal // time to wait for trigger measured at D2 Test pin

 

測定結果を見ると、今回の試作では20mS単位の処理ならArduinoのLoop処理のなかで余裕しゃくしゃくで処理可能なことが分ります。もちろんプログラミングでは他にもいくつかの工夫をしていますが、回をあらためて説明したいと思います。

さらに短い単位で測定をすることも十分可能ですが、そうするとSRAM1つの記録時間が減ります。地震は最長せいぜい4分のようですが、このしかけですとSRAM1つに5分20秒以上記録できます。とはいえ地震計の記録としては50Hzのサンプリングで大丈夫なので、Simple is the best、これで進めることにします。地震のこない時は1つのSRAMにラップラウンドで書き続け、地震があればその記録と時刻表記は1つのSRAM内に収まり、次のSRAMに書き始めるようにします。

センサー読み込み部分の測定結果を詳しく示すと次のようになっています。

バイスからのバイト受け取り順序はLSM6DS3のアプリケーションノート(最新版ではAN4650.pdfの102頁中の31頁)Table18に書かれているとおりになります。符号付の値が2バイトにわかれているため、受取順序をビッグエンディアン(方法は3.5.1Big-little endian selection参照)に指定しても、負の場合の値のとりこみには少し工夫が必要です(送出時に上位を右シフト演算する場合ならArduinoでは符号が維持される約束ですが、受信で左シフト演算して下位バイトを加える場合にはそうはいきません。これを間違えると誤った値になります)。いずれどこかの回で説明したいと思います。

f:id:a-tomi:20191031220733j:plain

SRAM書き込み部分を拡大すると次です。

f:id:a-tomi:20191031221904j:plain

さらに拡大するとクロックが0.25µ秒周期で、正確な4MHzで動いていることが分ります。

f:id:a-tomi:20191031222035j:plain

試作では3つのSPIデバイスを浮遊容量の大きいブレッドボード上でつないでいますから、4MHzは少し厳しいかなと思いましたが、なんと全然問題なく動いています。もっとつなぎたいときは、これを1MHz以下に落としても処理タイミングの上では十分にいけますね。

この仕組みでは地震の判定は3軸の平方和の平方根で行いますが、常時連続記録をしているので地震発生の判定は必要なく、地震終了の判定だけが必要です。

加速度データは最初に多数回を測り、以後は各軸をその平均値で相殺することにより地震波だけにしています。器械をきちんと固定した場合には各波の中心がゼロガルになるわけですが、机上の簡単な実験では微妙に動くのでそうはいきません。

簡単なテストの記録ではZが2ガルほど上にあります。詳しく見るとどの軸も2ガル程度のノイズ(か常微動?)があるらしいことがわかります。とりあえずは1ガル単位で記録していたデータなので今後さらに詳しく見ていきたいと思います。

f:id:a-tomi:20191031223159j:plain

2019年11月3日以後追記:
ノイズ(?)を調べるために次の3つのことをやってみました。①記録を0.1ガル単位に変更、②相殺用の初期値取得間隔を多少長めに変更  ③テスト装置を安定した台に置く。

その結果は次のグラフです。

f:id:a-tomi:20191103173758j:plain

 各軸ともほぼ±1ガル未満の小さくて周波数の高い振れですが、近くの道路を自動車が通ると幅が増えたりします。よって常微動(常時微動)の影響が無視できないと気づきました。テスト中の工作室は2X4木造の3Fにありますから当たり前かも。

1ガル未満の弱い地震まで確実に記録するには、適切な据え付け場所をえらばないといけませんね。本番用を作ったらコンクリートのテラスなどに設置して試そうと思います。

なお、このセンサーの仕様は分解能0.03ガルのようで、正しく使えば0.1ガル単位で記録しても信用できそうです。最大2Gの地震でも1961.3ガル、記録の単位を10倍にしても値は2バイトに収まりちょうどよい感じです。

その後さらに少しずつテストしてみた結果、ODR(出力周波数)とAntiAliasなどの設定の効き目が顕著(もちろん仕様どおりですが^^;)と確認できました。設定を最適にして20mSの1ループで1度だけ測るのがやはり的確という結論になりました。

 今後の進捗ペースにあわせて(時間があるときのマイペースですみませんが)、センサーへのパラメータ、SRAMの使い方などプログラム全体を少しずつ説明して行きたいと思います。今回はこのへんで。

 

 

©2019 Akira Tominaga, All rights reserved.

RTC writer(リアルタイムクロック・ライター) 8ピンPICで簡素に

リアルタイムクロックにデータを書き込むためのシンプルなツールです。

f:id:a-tomi:20191014163655j:plain

マイコン機器にRTCを組み込むつど、RTC更新機能を入れていると煩わしくなります。少なくとも3つのボタンスイッチをつけなければなりませんし、メモリーも少し消耗します。Arduino-Uno(ATmega328P)などでメモリーやピン数が限られている場合は省略したいものです。

今度創ろうとする機器では、RTCは読取だけにしようと思っていますのでこれを作っておきます。RTC-writerを常備していればそれで更新すればよいわけです。筆者の場合Arduinoで泥縄式に作ることが多いので、この際、最も単純なツールを準備しておくことにしました。意外なほど短時間でできました。

8ピンでできる単純な回路は次でしょう。明るい7セグメントへの表示を含めて8ピンマイコン1つです。毎度汚い手書きの図ですみませんが次の回路です。

f:id:a-tomi:20191014164435j:plain

回路図を書くより前にブレッドボードで同じものを試作しました。なにせ少し前に書いた「アセンブラーで芸当・・・」の記事で書いた回路の一部ですので。一発で問題なく動きました。

f:id:a-tomi:20191014164750j:plain

ブレッドボードを拡大すると次のとおりです。

f:id:a-tomi:20191014164853j:plain

下にあるタクトスイッチ3つは左から順に Set, Time, Mode です。パワーオン時にはRTCにあるデータの時刻を表示しますが、Setを長押しするとRTC更新モードに入り、カレンダー、時刻の更新ができます。

設定は年(Y=xx)から始まり、Timeボタンを押すと数値xxが現在値から最大値まで順次上がり最小値に戻り再び上がりますので、所用の値がセットできます。モードキーを押すごとに更新する対象が変わり、現在の数値がM=xx、d=xx、W=xx(曜日で日曜が01、月曜が02、・・・)、h=xx、m=xx、S=xxと表示されます。Modeボタンを更に押せばY=xxに戻り繰り返せます。 

すべてセットしたら最後にSetボタンを押せばRTCが更新され、時刻表示モードに戻ります。

もし更新せずに戻りたい場合は、Modeキーを長押しします。

以上の仕様は、この記事の最後に掲げるソースプログラムをご覧いただくと、却って分かり易いかもしれません。7セグで表示しにくいMやWは略字です。

基板は54㎜x40㎜の小さなもので、表から見た切削原稿は次です。毎度ながらランドなどは全て省略する基板です。

f:id:a-tomi:20191014170439j:plain

CNCルータの切削図(銅箔面)に変換したものが次です。

f:id:a-tomi:20191014170538j:plain

削った基板は次ですが、10分程で加工完了。大きな穴(といっても2.5φ)は手作業で開けたものです。

f:id:a-tomi:20191014170816j:plain

表(部品面)は次。カットをちょっと失敗しましたがとくに問題なし。

f:id:a-tomi:20191014170940j:plain

部品とりつけは45分程で終わり。

f:id:a-tomi:20191014171219j:plain

この状態で十分テストし、問題なく作動したのでケースを加工して入れます。

ケースは百均のセリア(Seria)にちょうど良いのがあり、100円で2個。これは丈夫で加工もしやすく文句なしですね。日本製だけあって加工精度もよく素晴らしいと思いました。少し乱暴に穴あけしても割れたりしません。

f:id:a-tomi:20191014171504j:plain

袋に収まっている写真は次です。

f:id:a-tomi:20191014171606j:plain

製造会社は次です。

f:id:a-tomi:20191014171629j:plain

ポリスチレンなので加工はごく簡単です。次は出来上がりのテストの様子です。

f:id:a-tomi:20191014172050j:plain

蓋を開けなくとも操作がすべてできるように、タクトスイッチは柄が長いもの(18㎜)を使っています。外付け電源(ACアダプター3V~5Vどれでも可能)を使っています。電池を入れると保守が煩わしいからです。入れっぱなすと破裂してひどい目に遭いますし。

ラベルライターでスイッチの名前だけ貼り付けました。

f:id:a-tomi:20191014172531j:plain

裏から見ると次です。

f:id:a-tomi:20191014172602j:plain

前に作ったRTCライターは電池式で出し入れが煩わしい、液晶表示が見づらい、大きい、などでこれで見限り、今回のに切り替えます。

ちなみに5年程前に作ったRTCライターはもう少しピンの多いPICでこしらえました(今回は8ピンですっきり)。これは大きいので廃棄ですね^^;

f:id:a-tomi:20191014173023j:plain

ほかにこんなバラックもありましたが、こちらも液晶以外は廃棄に。

f:id:a-tomi:20191014173342j:plain

RTCのブレークアウトモジュールは、同じモデルでも製品種類によってピンの並び方が一律ではありません。ですので4ピンの雌コネクターではなく、バラバラのデュポンワイヤー(雌コネクターつき)を使っています。色でピンを決めておきます。

後々での間違いを防ぐため、今回から分かり易いようにVcc赤、グランド黒、SCL(クロックライン)黄、SDA(データライン)白にしています。

ソースプログラムは次のとおりです。

;U191006-RTC-TM1637-V00.asm			     	As of Oct. 06, 2019
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;										;
; 	8pin PIC RTC Reader/Writer with TM1637 display V00			;
;	        (C)2015-2019 Akira Tominaga, All rights reserved.		;
;	  Major revisions							;
;		V00 Original version    Oct. 6, 2019				;
;										;
;	  Function								;
;		1.Show time, on TM1637 4 digit display				;
;		2.Re-write RTC contents using three switches			;
;		  Set-switch: enter into update-mode 		 		;
;		  Mode-switch: change calendar/time unit			;
;		  Time-switch: change value of the unit within its range	;
;		  Set-switch: reflect update and restart RTC			;
;		  Long push of Mode-sw: exit update-mode without updating RTC 	;
;										;
;	  Input/output       							;
;		RA0 Mode-switch (swMode) input pulled-up			;
;		RA1 Time-switch (swTime) input pulled-up			;
;		    Used as LED or DSO trigger when debugging			;
;		RA2 DIO for TM1637 outuput usually and input occasionally	;
;		RA3 Set-switch (swSet) input pulled-up				;
;		RA4 SDA for I2C-RTC  output  usually and input occasionally	;
;		RA5 SCL for I2C-RTC  output and CLK for TM1637 output		;
;										;
;	  Remarks								;
;		1. Clock = HFINTOSC (	16MHz)					;
;			Hence 1 step = 0.25 micro seconds			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
	list		p=12F1822      ; list directive to define processor
	#include	 ; <p12F1822.inc> specific variables
    __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
    __CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _BORV_LO & _LVP_OFF
;	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Macro definitions					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Device dependent Macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
DEVset	macro	
	BANKSEL	OSCCON		; Bank=1
	movlw	B'01111010'	; 16MHz and internal oscillator
	movwf	OSCCON
;
;	BANKSEL	INTCON		; Interrupt Con (in all banks hence comment)
	clrf	INTCON		; Disable all interrupts
;
; PORTA initialization
	BANKSEL	PORTA		; Bank=0
	clrf	PORTA
;	BANKSEL	LATA		; Bank=2
;	clrf	LATA
	BANKSEL	ANSELA		; Bank=3
	clrf	ANSELA		; No use of ADC
	BANKSEL	ADCON0		; Bank=1
	clrf	ADCON0		; No use of ADC
;
	BANKSEL	TRISA		; Bank=1
	movlw	B'11001011'	; RA2,4 and 5 are output
	movwf	TRISA
;
	BANKSEL	OPTION_REG	; Bank=1
	bcf	OPTION_REG,7	; Enable weak pull-up
	BANKSEL	WPUA		; Bank=4
	movlw	B'00001011'	; Weak Pull-up for RA 0,1,and 3
	movwf	WPUA		;  
;
	clrf	BSR		; Bank=0
	InitP			; Initialize ports
	endm
;
InitP	macro			; Initialize ports
	movlw	B'11111111'	; All inputs pulled-up and outputs H
	movwf	PORTA
	endm
;		
; for I2C-Master protocol
; Sending data to I2C slave
I2Csnd	macro	slave,dataa,datalen
	movlw	slave
	movwf	I2Cadr
	movlw	dataa
	movwf	FSR0L
	movlw	datalen
	movwf	DataLen
	call	I2Csndr
	endm	
;
; Receiving data from I2C slave
I2Crcv	macro	slave,dataa,datalen
	movlw	slave
	movwf	I2Cadr
	movlw	dataa
	movwf	FSR0L
	movlw	datalen
	movwf	DataLen
	call	I2Crcvr
	endm	
;
; I2C start signal
I2Cstat macro
	call	I2Cstar
	endm
;
; I2C stop signal
I2Cstop	macro
	call	I2Cstpr
	endm
;
; For TM16xx-Master protocol
; Sending Literal to TM16xx
TMsendL	macro	tslit		; Send a literal to TM1637
	movlw	tslit
	call	TMsndLr
	endm
;
; Sending a Byte to TM16xx
TMsendB	macro	tsbyte		; Send a Byte specified, to TM1637
	movf	tsbyte,W
	movwf	Sbyte
	call	TMsndBr
	endm
;
; TM16xx start signal
TMstart macro			; Start TM16xx lines
	call	TMstrtr
	endm
;
; TM16xx stop signal
TMstop	macro			; Stop TM16xx lines
	call	TMstopr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	API and I/O macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;for TM1637 application interfaces
;display 4 literal characters directly to TM1637
dsp4chr	macro 	d1000,d100,d10,d1
	movlw	d1000
	movwf	Dig1000
	movlw	d100
	movwf	Dig100
	movlw	d10
	movwf	Dig10
	movlw	d1
	movwf	Dig1
	call	TMdspr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Time cosuming macros		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic	macro			;Consume 1 μS only
	goto	$+1
	goto	$+1
	endm
;
Mic2	macro	mic2p		;Consume 2 μS x n
	movlw	mic2p
	call	Mic2r
	endm
;
Mic2p5	macro 	mic25p		; Consume 2.5μS x n
	movlw	mic25p
	call	Mic25r
	endm
;
Mic5	macro	mic5p		; Consume 5μS x n
	movlw	mic5p
	call	Mic25r
	movlw	mic5p
	call	Mic25r
	endm
;
Mic50	macro	mic50p		; Consume 50μS x n
	movlw	mic50p
	call	Mic50r
	endm
;
Milli	macro	millip		; Consume mS x n
	movlw	millip
	call 	Millir
	endm
;
Mil100	macro	mil100p		; Consume 100 mS x n
	movlw	mil100p
	call 	Mil100r
	endm
; 
Secs	macro	secsp		; Consume Second x n
	movlw	secsp
	call	Secsr
	endm
;
Mins	macro	minsp		; Consume Minute x n
	movlw	minsp
	call	Minsr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Debug and Abend macros		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LEDon	macro			; LED on macro for debugging
	bcf	Flags,LEDpH	; Clear LEDpin flags beforehand,
	bcf	Flags,LEDpIn	; as timing before PORT accessed
	btfsc	PORTdbg,LED	; Has LED been H before debug?
	bsf	Flags,LEDpH	;  Yes, set LEDpH flag
	BANKSEL	TRISdbg		; Select TRIS bank
	btfss	TRISdbg,LED	; Has LED pin been input?
	goto	$+4		;   No, skip the next 3 steps
	clrf	BSR		; Point Bank0
	bsf	Flags,LEDpIn	; Show LED pin was input
	BANKSEL	TRISdbg		; Select Tris bank again
;
	bcf	TRISdbg,LED	; Set LED output mode, in any case
	clrf	BSR		; Point Bank 0
	bsf	PORTdbg,LED	; LED on
	goto	$+1		; Timing after PORTdbg changed
	endm
;
LEDoff	macro			; LED off macro for debugging
	goto	$+1		; Avoid PORT-update timing problem
	bcf	PORTdbg,LED	; LED off
	btfss	Flags,LEDpIn	; Was LEDpin input before debug?
	goto	$+7		;  No, skip the following 6 steps
;
	BANKSEL	TRISdbg		; Select TRISdbg bank
	bsf	TRISdbg,LED	; Set LED pin input again
	BANKSEL	WPUdbg		; Select WPUdbg, assuming Pulled-up
	bsf	WPUdbg,LED	; Weak Pull-up for input pin
	clrf	BSR		; Point Bank 0
	goto	$+3		; skip the next 2 steps
;
	btfsc	Flags,LEDpH	; Was LEDpin High
	bsf	PORTdbg,LED	;  Yes, set LED pin High
	bcf	Flags,LEDpH	; Clear LED flags,
	bcf	Flags,LEDpIn	;  as timing after PORTdbg changed
	endm 
;
Trigger	macro			; Trigger for DSO
	bcf	Flags,TrigpH	; ClearTrigger flags beforehand
	bcf	Flags,TrigpIn	; for timing before PORT changed
	btfsc	PORTdbg,Trig	; Has Trig pin been H before debug?
	bsf	Flags,TrigpH	;  Yes, set TigpH flag
	BANKSEL	TRISdbg		; Select TRIS bank
	btfss	TRISdbg,Trig	; Has Trig pin been input?
	goto	$+4		;  No, skip the following 3 steps
	clrf	BSR		;  Yes, point Bank 0,
	bsf	Flags,TrigpIn	;   and show Trig pin was input,
	BANKSEL	TRISdbg		;   select TRISdbg bank again
;
	bcf	TRISdbg,Trig	; Set Trig pin output, in any case
	clrf	BSR		; Point Bank 0
	btfss	Flags,TrigpH	; Was Trigger pin H?
	goto	$+3		;  No, goto slope-rising trigger
	bcf	PORTdbg,Trig	;  Yes, set slope-falling trigger
	goto	$+2		;    and skip next line
	bsf	PORTdbg,Trig	; Slope-rising trigger
;
	Mic2	2		; Trigger for 4 micro seconds 
	movlw	TrigpV		; Set trigger pin value to W
	xorwf	PORTdbg,F	; Stop trigger (H/L to L/H)
;
	btfss	Flags,TrigpIn	; Was Trig pin input before debug?
	goto	$+10		;  No, skip the following 9 steps
;
	BANKSEL	TRISdbg		; Select TRISdbg bank
	bsf	TRISdbg,Trig	; Set Trig-pin input
	clrf	BSR		; Select Bank 0
	btfsc	Flags,TrigpH	; Was Trigger pin L?
	goto	$+4		;  Yes, skip the followings
	BANKSEL	WPUdbg		;  No,Bank select for WPUdbg
	bsf	WPUdbg,Trig	;     Weak Pull-up for input pin
	clrf	BSR		;     Re-select Bank 0
	goto	$+7		;     skip next 6 steps for output
;
	btfss	Flags,TrigpH	; Was Trigger pin H?
	goto	$+3		;   No, skip next 2 steps
	bsf	PORTdbg,Trig	;   Yes, Set trig pin H
	goto	$+2		;      and skip next line
	bcf	PORTdbg,Trig	; Set trig pin L
;
	bcf	Flags,TrigpH	; Clear flags for trigger
	bcf	Flags,TrigpIn	; as timing after PORTdbg changed
	endm
;
Udebug	macro 	Addr
	movf	Addr,W
	call	Udbgr
	endm
;
Uabend	macro	abn		; User Abnormal-end number
	movlw	abn
	goto	Uabendr
	endm
;
Ublink	macro	bno		; Blink LED for specified times
	movlw	bno
	call	Ublinkr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Logic macros				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Comparison macros		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Compare A w/ B and branch to Smaller, Equal, or Larger(next statm't line) 
CabSEL	macro	Aadr,Badr,Smladr,Eqladr
	movf	Badr,W		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	Smladr		; 	If A<B goto Smaladr
	btfsc	STATUS,Z	; A=B? No skip next
	goto	Eqladr		;	If A=B goto Eqladr
;				; 	If A>B then next (here)
	endm
;
;	Compare A w/ B and branch to Smaller or Others (Equal or Larger) 
CabSO	macro	Aadr,Badr,Smladr
	movf	Badr,W		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	Smladr		; 	If A<B goto Smaladr
;				; 	If A≧B then next (here)
	endm
;
;	Compare A w/ B and branch to Equal, or Other(next statm't line) 
CabEO	macro	Aadr,Badr,Eqladr
	movf	Badr,W		; W=B
	subwf	Aadr,W		; W=A-B
	btfsc	STATUS,Z	; A=B? 
	goto	Eqladr		;	If A=B goto Eqljmp
;				; 	else next (here)
	endm
;
;	Compare A w/ literal and branch to Smaller, Equal, or Larger(next statm't) 
CalSEL	macro	Aadr,Blit,Smladr,Eqladr
	movlw	Blit		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	Smladr		; 	If A<B goto Smaladr
	btfsc	STATUS,Z	; A=B? No skip next
	goto	Eqladr		;	If A=B goto Eqladr
;				; 	If A>B then next (here)
	endm
;
;	Compare A w/ literal and branch to Smaller, or Others (Equal or Larger) 
CalSO	macro	Aadr,Blit,Smladr
	movlw	Blit		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	Smladr		; 	If A<B goto Smaladr
;				; 	If A≧B then next (here)
	endm
;
;	Compare A w/ literal and branch to Equal, or Others 
CalEO	macro	Aadr,Blit,Eqladr
	movlw	Blit		; W=B
	subwf	Aadr,W		; W=A-B
	btfsc	STATUS,Z	; A=B? No skip next
	goto	Eqladr		;	If A=B goto Eqladr
;				; 	else next (here)
	endm
;
;	Compare A w/ literal and branch to Large, or Others
CalLO	macro	Aadr,Blit,Ladr
	movlw	Blit		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	$+3		; 	If A<B goto Others
	btfss	STATUS,Z	; A=B? Yes skip next
	goto	Ladr		;	If A>B goto Laddr
;				; 	If A≦B then next (here)
	endm
;
;	Compare A with Literal and branch to Un-equal, or Others
CalUO	macro	Aadr,Blit,Uneqladr
	movlw	Blit		; W=B literal
	subwf	Aadr,W		; W=A-B	
	btfss	STATUS,Z	; A=B?
	goto	Uneqladr	; No, goto unequal
;				;	If yes then next
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Files and Equations					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Files				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	cblock	H'20'
;
; For Application support
	ModeN		; Mode to point BCD mem addr of RTC
	T7Chr		; 7 segment character to show ModeN
	RvMax		; Max number of RTC data in ModeN
	RvMin		; Minimum num of RTC data in ModeN
	Byteh		; Work area as high byte of a word
	Bytel		; Work area as low byte of a word
	Sbyte		; Byte to be sent/received by devices
	Bytectr		; Byte counter for device processes
	Bitctr		; Bit counter for device processes 
;
; For RTC
	RTCmem		; RTC memory address
	BCDsec		; RTC memory 0 = Second BCD	00-59
	BCDmin		; RTC memory 1 = Min BCD	00-59
	BCDhr		; RTC memory 2 = Hour BCD	00-23
	BCDdow		; RTC memory 3 = Day of Week 	01-07
	BCDday		; RTC memory 4 = Day BCD	01-31
	BCDmon		; RTC memory 5 = Month BCD 	01-12
	BCDyr		; RTC memory 6 = Year yy BCD 	00-99
;
; For I2C protocols
	I2Cadr		; Destinated slave I2C address
	DataLen		; Number of bytes to be sent/received
;
; For TM1637 4 digit display
;	Sbyte		; Byte to send, shared with I2C
	Dig1000		; Digit 1000
	Dig100		; Digit 100
	Dig10		; Digit 10
	Dig1		; Digit 1
;
; Areas for time consuming subroutines
 	Mic25c
	Mic50c
	Millic
	Mil100c
	Secsc
	Minsc
;
; Areas for debugging routines
	DmpA 		; Display byte area
	Dmp8C 		; Bit counter for loop (Initial Dmp8V =8)
	BlinkC 		; Counter for blinking (set for Debugb or Abendb)
	Abendn		; Abend number
	Ucpc		; User Check point chr to trace
	Flags		; Flags mainly for debug
;
	endc
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Equations			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For PORTA		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;swMode	equ	0	; PORTsw Mode Switch
;swTime	equ	1	; PORTsw Time switch
;LED	equ	1	; PORTdbg LED  for debugging
;Trig	equ	1	; PORTdbg Trig for debugging
;Dio	equ	2	; PORTtm TM16xx DIO
;swSet	equ	3	; PORTsw Set switch
;Dl	equ	4	; PORTI2C SDA
;Cl	equ	5	; PORTI2C SCL
;Clk	equ	5	; PORTtm TM16xx CLK
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTI2C		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTI2C equ	PORTA	; 
Dl	equ	4	; I2C Data line = SDA
Cl	equ	5	; I2C Clock line = SCL
LATI2C	equ	LATA	; LATch for I2C port
TRISI2C equ	TRISA	; TRIS reg for I2C
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTtm		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTtm	equ	PORTA	; 
Dio	equ	2	; TM16xx Data line = DIO
Clk	equ	5	; TM16xx Clock line = CLK
LATtm	equ	LATA	; Latch for tm port
TRIStm	equ	TRISA	; TRIS for tm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTsw		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTsw	equ	PORTA
swMode	equ	0	; Mode switch input ulled-up
swTime	equ	1	; Time switch input pulled-up
swSet	equ	3	; Set switch input pulled-up
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTdbg		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdbg	equ	PORTA
TRISdbg	equ	TRISA
WPUdbg	equ	WPUA
LED	equ	1	; LED when debugging
Trig	equ	1	; Trigger for DSO when debugging
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Values & Characters	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Equations for RTC DS3231 
Clkt	equ	1	; Clock-time/4 multiplier of 2.5μS
Clkq	equ	1	; Quick time within clock  2μS

DS3231	equ	H'D0'	; RTC DS3231 address (to write)
RTCdata equ	BCDsec	; RTC memory data top
ModeMax	equ	6	; RTC data pointer ModeN max
ModeMin	equ	0	; RTC data pointer ModeN minimum
;
secMax	equ	H'59'
secMin	equ	0
;
minMax	equ	H'59'
minMin	equ	0
;
hrMax	equ	H'23'
hrMin	equ	0
;
dowMax	equ	H'07'
dowMin	equ	H'01'
;
dayMax	equ	H'31'
dayMin	equ	H'01'
;
monMax	equ	H'12'
monMin	equ	H'01'
;
Modeyr	equ	6
yrMax	equ	H'99'
yrMin	equ	0
;
; 7 segment LED characters for TM1637
Ch7Y	equ	H'6E'
Ch7Eq	equ	H'48'
Ch7M	equ	H'37'
Ch7d	equ	H'5E'
Ch7w	equ	H'1C'
Ch7h	equ	H'74'
Ch7m	equ	H'54'
Ch7S 	equ	H'6D'
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For Debug		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdbg	equ	PORTA	;
Trig	equ	1	; DSO trigger pulse port for test use
TrigpV	equ	B'00000010' ; = 2^Trig
;
Dmp8V	equ	D'8'	; Debug display bit counter initial value
Debugb	equ	D'8'	; number of blinkings to show Debugging
Abendb	equ	D'25'	; number of blinkings to notify Abend
; Flags byte
LEDpH	equ	0	; LED pin was High before debug use
LEDpIn	equ	1	; LED pin was input before debug use
TrigpIn	equ	2	; Trigger pin was input before debug use
TrigpH	equ	3	; Trigger pin was High before debug use
;
	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Initializing						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	org	0
	goto	Startp	; Go to start entry
	org	4	; This is Interrupt entry
	retfie		;
;
Startp	DEVset		; Define ports
	clrf	FSR0H	; Clear FSR0H forever
	clrf	FSR1H	; Clear FSR1H forever
	clrf	Flags	; Clear all bits of Flags
;
	Milli	D'50'	; Wait devices stabilized
	call	IniTMr	; Set-up TM1638 display
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Main program loop					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mainp	call	RTCrdr	; Read clock
	call	Dsptime	; Display time
	Secs	1	; for 1 second
;
	btfss	PORTsw,swSet ; Set switch ?
	call	RTCupdr ; Yes, call RTC updating routine
;
	goto	Mainp	; Contilue infinite loop
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	RTC updating routine					;
;		Display value for current ModeN			;
;		If swMode pushed, display next unit to update	;
;		If swTime pushed, increase value and display it	;
;		If swSet pushed,  update RTC (RTCwtr)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RTCupdr	btfss	PORTsw,swSet ; swSet still on?
	goto	$-1	; then wait for off
 	movlw 	Modeyr	; set initial mode year
	movwf	ModeN
;
RTCudm	movf	ModeN,W	; Large loop on ModeN
	brw
	goto	Mdsecr	; case ModeN=0 
	goto	Mdminr	; case ModeN=1 
	goto	Mdhrr	; case ModeN=2 
	goto	Mddowr	; case ModeN=3 
	goto 	Mddayr	; case ModeN=4 
	goto	Mdmonr	; case ModeN=5 
	goto	Mdyrr	; case ModeN=6 
;
; Macro used within  this section
Mdsetup macro	T7c,Rmax,Rmin
	movlw	T7c
	movwf	T7Chr
	movlw	Rmax
	movwf	RvMax
	movlw	Rmin
	movwf	RvMin
	goto	Mdnext
	endm
;
; set up environment for update and display
Mdsecr	Mdsetup	Ch7S,secMax,secMin
Mdminr	Mdsetup	Ch7m,minMax,minMin
Mdhrr	Mdsetup	Ch7h,hrMax,hrMin
Mddowr	Mdsetup	Ch7w,dowMax,dowMin
Mddayr	Mdsetup	Ch7d,dayMax,dayMin
Mdmonr	Mdsetup	Ch7M,monMax,monMin
Mdyrr	Mdsetup	Ch7Y,yrMax,yrMin
;
Mdnext 	call	Rdspr		; Display mode"X=nn"
	btfss	PORTsw,swTime	; Is Time pushed ?
	goto	Rinc		;  Yes, goto  Rinc
	btfss	PORTsw,swMode	; Is Mode pushed ?
	goto	Rnxtm		;  Yes, goto Rnxtm
	btfss	PORTsw,swSet	; Is Set pushed ?
	goto	Rwrite		;  Yes, goto Rwrite
	goto	Mdnext		; If no button, small loop for button
;
; 	Time pushed here
Rinc	call	Rincr		; Increase value in that mode
	Mil100	2		; 0.2 sec for increasing value
	goto	Mdnext		; return to loop for button
;
;	Mode pushed here
Rnxtm	btfss	PORTsw,swMode	; Mode button still pushed
	goto	Rmdck		; Yes, goto mode switch cheking
;	short push of Mode sw released here
Rmdsht	CalEO	ModeN,0,Msmax	; If current ModeN=0 goto Msmax
	decf	ModeN,F		; set next mode
Rnxtme	goto	RTCudm		; and return to large loop for mode
;
Rmdck	Mil100	3		; wait for 300milli sec
	btfsc	PORTsw,swMode	; Mode button still pushed ?
	goto	Rmdsht		; No, goto short push process
;
	Mil100 	6		; wait for additional 600 milli sec
	btfsc	PORTsw,swMode	; Mode button still pushed ?
	goto	Rmdsht		; No, goto short push process
;
;	Mode switch pushed long, hence exit	
	btfss	PORTsw,swMode	; wait for Mode sw released
	goto	$-1
	goto	Rexit		; and goto exit of RTC update routine
; 
Msmax	movlw	Modeyr		; get maximum mode
	movwf	ModeN		; and set it
	goto	RTCudm		; and return to large loop for mode
;	
Rwrite	btfss	PORTsw,swSet	; Set button still on?
	goto	$-1		; then wait for release
	call	RTCwtr		; and write to RTC
;
Rexit	return			; exit from RTC updating routine
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Display value pointed by ModeN	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Rdspr	movf	T7Chr,W
	movwf	Dig1000
	movlw	Ch7Eq
	movwf	Dig100
;
	movlw	BCDsec	; Point BCD area top
	movwf	FSR1L		; set it to FSR1L
	movf	ModeN,W	; Get Mode number
	addwf	FSR1L,F	; Point current BCD area	
	swapf	INDF1,W	; get high nibble to low in W
	call	TMser	; clear high nibble & edit
	movwf	Dig10	; set it to Dig10
	movf	INDF1,W	; get low nibble to low
	call	TMser	; clear high nibble to low
	movwf	Dig1	; set it to Dig1
;
	call	TMdspr
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Increase value pointed by ModeN	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Rincr	movlw	BCDsec	; Point BCD area top
	movwf	FSR1L	; set it to pointer
	movf	ModeN,W	; get ModeN to add
	addwf	FSR1L,F	; Point current BCD area
;
	CabSO	INDF1,RvMax,Rigo ;If smaller than max, goto Rigo
	movf	RvMin,W	; Else, get minimum value
	movwf	INDF1	; and set it to INDF1 (BCD area)
	return
;
Rigo	movf	INDF1,W	; Get BCD value
	andlw	H'0F'	; clear left nibble
	movwf	Bytel	; and set to Bytel
	swapf	INDF1,W	; Get BCD nibbles swapped
	andlw	H'0F'	; clear left nibble
	movwf	Byteh	; and set to Byteh
;
	incf	Bytel,F	; Bytel+1
	CalSO	Bytel,D'10',Rie ; If Bytel≦9,goto Rie
	movlw	D'16'	; Else increase digit 10
	addwf	INDF1,F	;
	movlw	H'F0'	; and clear digit 1
	andwf	INDF1,F
	return

Rie	incf	INDF1,F	; Increase BCD value
	return		; and return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Displaying Time routine					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Dsptime movf	BCDmin,W ; get minute dig 1
	call	TMser	; edit bit 0-3 value to 7 seg char
	movwf	Dig1	; set it to Digit 1
;	
	swapf	BCDmin,W ; get minutes dig 10
	call	TMser	; edit bit 0-3 value to 7 seg char
	movwf	Dig10	; set it to Digit 10
;
	movf	BCDhr,W ; get hour dig 1
	call	TMser	; edit bit 0-3 value to 7 seg char
	movwf	Dig100	; set it to Digit 100
	bsf	Dig100,7 ; set colon after Digit 100
;
	swapf	BCDhr,W ; get hour dig 10
	call	TMser	; edit bit 0-3 value to 7 seg char
	movwf	Dig1000	; set it to Digit 1000
;
	call	TMdspr	; show them to TM1637
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	RTC reading routine	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RTCrdr	clrf	RTCmem	; set RTC memory addr 0
	I2Csnd DS3231,RTCmem,1 ; send it to DS3231
	Milli	1	; wait for readiness (unnecessary?)
	I2Crcv DS3231,RTCdata,7	; receive 7 byte data from DS3231
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	RTC writing routine	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RTCwtr	clrf	RTCmem	; Set memory address zero
	I2Csnd	DS3231,RTCmem,8 ; Send data, with memory addr on top
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C Send byte-string				;
;         when called,					;
;		I2C device adr in I2Cadr byte		;
;		Data length in DataLen			;
;		Data Addr in FSR0L (INDF0)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Csndr	movf	I2Cadr,W
	movwf	Sbyte
;	rlf	Sbyte,F	; shift leftt (if adr for Arduino)
	bcf	Sbyte,0	; indicate write
;
	I2Cstat		; Start I2C
	call	Sendr

Sdata	movf	INDF0,W	; Get a sending character
	movwf	Sbyte	; Set it to Sbyte
	call	Sendr	; Send it to the device
;
	incf	FSR0L,F	; point next char
	decfsz	DataLen,F ; Have all chars sent ?
	goto	Sdata		; No, loop
;
; Stop signal and return
	I2Cstop
	return	
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	I2C Receive byte-string				;
;         when called,					;
;		I2C device adr in I2Cadr byte		;
;		Data length in DataLen			;
;		Data Addr in FSR0L (INDF0)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Crcvr	equ	$
	I2Cstat		; Start I2C
	movf	I2Cadr,W ; Get I2C dev addr
	movwf	Sbyte	; Set it into Sbyte
;	rlf	Sbyte,F	; Shift left (if adr for Arduino)
	bsf	Sbyte,0	; indicate read
	call	Sendr	; Send slave addr to read
;
I2Crbyt	equ	$
	BANKSEL	TRISI2C	;
	bsf	TRISI2C,Dl ; Set Dline input mode
	clrf	BSR	; Point Bank 0
;
	movlw	8	; Bits in a reciving byte
	movwf	Bitctr	; into loop counter 
I2Crblp	Mic2p5	2*Clkt	; Timing before raising clock
	bsf	PORTI2C,Cl ; Set clock high
	Mic2p5	Clkt	; Timing after raising clock
;
	btfsc	PORTI2C,Dl
	goto	Rbith
;	goto	Rbitl
;
Rbitl	bcf	Sbyte,0	; Clear bit 0
	goto	Rbitnxt	; and goto next
;
Rbith	bsf	Sbyte,0	; Set bit 0
Rbitnxt	Mic2	Clkq	; Timing after checking
	bcf	PORTI2C,Cl ; Set clock low
;	Mic2p5	Clkt	; Timing after clock falling
	decfsz	Bitctr,F ; Still bits?
	goto	Rbcont	; Yes, continue
	goto	Rbend	; No, end of one byte
;
Rbcont	rlf	Sbyte,F	; Shit left
	goto	I2Crblp	; and goto loop
;
Rbend	movf	Sbyte,W	; Get received byte
	movwf	INDF0	; Set it to INDF0
	incf	FSR0L,F	; increase index
	decfsz	DataLen,F ; DataLen-1
	goto	Sackr	; Send Ack and continue
	goto	Snackr	; Send Nack and stop
;
;Set Dl output mode and send Ack to conitinue
Sackr	equ	$
	BANKSEL	LATI2C	; Data line high when output mode
	bsf	LATI2C,Dl ; Lat-Dl on
	BANKSEL	TRISI2C	; 
	bcf	TRISI2C,Dl ; Set Dl output mode
	clrf	BSR	; Point Bank 0
;
	bcf	PORTI2C,Dl ; Send Ack
	Mic2	Clkq	; Timing after clock falling
	bsf	PORTI2C,Cl ; Show Ack
	Mic2p5	2*Clkt
	bcf	PORTI2C,Cl ; Set clock low
	Mic2	Clkq
	bsf	PORTI2C,Dl ; Return Dline to high
	goto	I2Crbyt		; Next byte process
;
;Set Dl output mode and send Nack
Snackr	equ	$
	BANKSEL	LATI2C	; Data line high when output mode
	bsf	LATI2C,Dl ; Lat-Dl on
	BANKSEL	TRISI2C	; 
	bcf	TRISI2C,Dl ; Set Dl output mode
	clrf	BSR	; Point Bank 0
;
	bsf	PORTI2C,Dl ; Send Nack
	Mic2	Clkq	; Timing after clock falling
	bsf	PORTI2C,Cl ; Show Nack
	Mic2p5	2*Clkt
	bcf	PORTI2C,Cl	; Set clock low
	Mic2p5	Clkt
	I2Cstop		; and stop I2C once
	Mic5	D'6'	; for 30 micro sec
;
	I2Cstat		; then start I2C again
	movf	I2Cadr,W ; Get I2C dev addr
	movwf	Sbyte	; Set it into Sbyte
;	rlf	Sbyte,F	; Shift left (if adr for Arduino)
	bcf	Sbyte,0	; indicate write
	call	Sendr	; Send it
;
	I2Cstop
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C Start signal routine 			;
;	   	when dataline mode is output		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Cstar	bcf	PORTI2C,Dl ; set SDA low
	Mic2	Clkq
	bcf	PORTI2C,Cl ; set SCL low
	Mic2p5	2*Clkt
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C Stop signal routine 			;
;	   	when dataline mode is output		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Cstpr	bcf	PORTI2C,Cl ; set SCL low
	Mic2	Clkq
	bcf	PORTI2C,Dl ; set SDA low
	Mic2p5	2*Clkt
	bsf	PORTI2C,Cl ; Clock line rasing
	Mic2p5	Clkt
	bsf	PORTI2C,Dl ; Data line rasing
	Mic2	Clkq
	return	
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C One-byte sending routine 			;
;	   	with data in Sbyte			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Sendr	movlw	8	; Set bit count in a byte
	movwf	Bitctr	; into loop counter 
;
Sloop	btfsc	Sbyte,7	; Check top bit of Sbyte
	goto	Slpdh	; 
	bcf	PORTI2C,Dl ; If bit low then Data L
	goto	Slpnxt
Slpdh	bsf	PORTI2C,Dl ; If bit high then Data H
;
Slpnxt	Mic2	Clkq
	bsf	PORTI2C,Cl ; Show bit
	Mic2p5	2*Clkt
	bcf	PORTI2C,Cl ;
	Mic2	Clkq
;
	rlf	Sbyte,F	; Shift left 
	decfsz	Bitctr,F ; All bits done?
	goto	Sloop	; No, loop within a byte
;	
;Receive Ack 		
; set data line input mode
	BANKSEL	TRISI2C	; 
	bsf	TRISI2C,Dl ; Set Dline input mode
	clrf	BSR	; Point Bank 0
;
	Mic2	Clkq	; wait for Ack timing
	bsf	PORTI2C,Cl ; Clock H for Acq confirmation
	Mic2p5	2*Clkt	; 
	bcf	PORTI2C,Cl ; Cl L for Dl release by slave
	Mic2p5	2*Clkt
;
; Set Data line output
	BANKSEL	LATI2C	; Data line high when output mode
	bsf	LATI2C,Dl ; Lat-Dl on
	BANKSEL	TRISI2C	; 
	bcf	TRISI2C,Dl ; Set Dl output mode
	clrf	BSR	; Point Bank 0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 support routines 			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	TM1637 display brightness setting	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IniTMr	equ	$
; TM brightness initial setting	;
;   H'88'+ duty ratio:
;       H'00'=1/16, H'01'=2/16, H'02'=4/16, H'03'=10/16
;	H'04'=11/16, H'05'=12/16, H'06'=13/16, H'07'=14/16
	movlw	H'88'+H'02'	; Initial brightness value
	movwf	Sbyte		; Set to Sbyte
	TMstart
	TMsendB Sbyte
	TMstop
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TM 1637 Char to 7 segment editing routine	;
;	replaceubg Char in W with 7 seg in W	;
;	  7 seg format is B'xgfe-dcba'		;
;	  if colon required,IOR H'80' to Dig100	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMser	andlw	H'0F'	; Prevent wild branch
	brw
Case0	retlw	H'3F'
Case1	retlw	H'06'
Case2	retlw	H'5B'
Case3	retlw	H'4F'
Case4	retlw	H'66'
Case5	retlw	H'6D'
Case6	retlw	H'7D'
Case7	retlw	H'07'
Case8	retlw	H'7F'
Case9	retlw	H'6F'
CaseA	retlw	H'77'	; A
CaseB	retlw	H'7C'	; b
CaseC	retlw	H'39'	; C
CaseD	retlw	H'5E'	; d
CaseE	retlw	H'79'	; E
CaseF	retlw	H'71'	; F
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 4digits displaying routine	;
;	  	using Dig1000 to Dig1		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMdspr	movlw	Dig1000	; Point Byte0 address
	movwf	FSR0L	; set it to use INDF0
	movlw	4	; Set four digits
	movwf	Bytectr	; to Byte counter
;
	TMstart	
	TMsendL	H'40'	; 
	TMstop 		; 
;
	TMstart
	TMsendL	H'C0'	; Set addr zero of TM
;
TMdspl	movf	INDF0,W	; Get byte contents
	movwf	Sbyte	; Set them to Sbyte
	TMsendB	Sbyte	; 
	incf	FSR0L,F	; point next byte
	decfsz	Bytectr,F ; Check if all done
	goto	TMdspl	; No. loop
;
	TMstop		; When all sent, Stop
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 Start sending routine 		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMstrtr	nop
	BANKSEL	TRIStm
	bsf	LATtm,Dio ; Dio high beforhand
	bcf	TRIStm,Dio ; Set Dio output
	clrf	BSR	; return to Bank0
;
	bsf	PORTtm,Dio ; Dio high
	Mic2	Clkq
	bsf	PORTtm,Clk ; Clk high
	Mic2	Clkq
	bcf	PORTtm,Dio ; Dio low
	Mic2	Clkq
	bcf	PORTtm,Clk ; Clk low
	Mic2	Clkq	
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 Stop sending routine 		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMstopr	nop
	BANKSEL	TRIStm
	bcf	LATtm,Dio ; Dio low beforehand
	bcf	TRIStm,Dio ; Set Dio output
	clrf	BSR	; Return to Bank0
;
	Mic2	Clkq
	bsf	PORTtm,Clk ; Clk high
	Mic2	Clkq
	bsf	PORTtm,Dio ; Data high
	Mic2p5	D'10'
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 Byte sending routine 		;
;		called by TMsendB with Sbyte or	;
;		TMsendL	with value in W reg	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMsndLr	movwf	Sbyte	; Set sending byte
;
TMsndBr	movlw	8	; Set bit count
	movwf	Bitctr	; into loop counter
;
Tloop	btfsc	Sbyte,0	; Check LSb for little-endian-
	goto	Tlpdh	; 
	bcf	PORTtm,Dio ; If bit low then Data L
	goto	Tlpnxt
;
Tlpdh	bsf	PORTtm,Dio ; If bit high then Data H
Tlpnxt	Mic2	Clkq; 
	bsf	PORTtm,Clk ; Clock high
	Mic2p5	2*Clkt	; (5 micro sec if standard)
	bcf	PORTtm,Clk ; Clock low	
	Mic2	Clkq
	rrf	Sbyte,F	; Shift right for little-endian 
	decfsz	Bitctr,F
	goto	Tloop
;	
;	Receiving Ack 	;
	BANKSEL	TRIStm
	bsf	TRIStm,Dio ; Set Dio input mode
	clrf	BSR	; return to Bank0
	Mic2p5	Clkt	; Wait for Ack=low
	bsf	PORTtm,Clk ; Clk high for Ack confirmation
	Mic2p5	2*Clkt	; (5 micro sec if standard)
	bcf	PORTtm,Clk ; Clk L for Data release to H by Slave
	Mic2p5	Clkt	; Wait for Dio set H 
;
	BANKSEL	TRIStm
	bsf	LATtm,Dio ; Dio high beforhand
	bcf	TRIStm,Dio ; Set Dio output
	clrf	BSR	; return to Bank0
	Mic2p5	5	; Enough time for TM1637 after Ack
	return		; 
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Timing subrooutines for general purposes		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.0 micro S x n	(Mic2)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic2r	movwf	Mic25c		; + Wset + call = 1 micro sec 
;
Mic2l	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic2li		; else go out (2nd time 1.75 mic sec)
	return		
;
Mic2li	goto	$+1		;            (2nd time 2.25 mic sec)
	nop			;	     (2nd time 2.5 mic sec)
	goto	Mic2l		; go back    (2nd time 3 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.5 micro S x n (Mic2p5)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic25r	movwf	Mic25c		; + Wset + call = 1 micro sec 
	nop			; 1.25 micro sec
;
Mic25l	nop			; 1.5 micro sec (2nd time 4 mic sec)
	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic25li		; else go out (2nd time 2.25 mic sec)
	return		
;
Mic25li	Mic			;	      (2nd time 3.25 mic sec)
	goto	Mic25l		; go back    (2nd time 3.75 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	50 Microseconds	x n	Mic50  	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Mic50r	movwf	Mic50c	 	; set how many 50 microsec (1 micro sec to here)
	nop			; 1.25 micro sec up to here
;
Mic50l	Mic2p5	D'19'		; + 47.5 = 48.75 mic sec (2nd time 98.75 mic sec)
	nop			; + 0.25 = 49 micro sec (2nd time 99 mic sec)
; 
	decfsz	Mic50c,F	; If exhausted then 1 mic S hereafter
	goto	Mic50li		; else go out (2nd time 49.75 mic sec)
	return
;
;
Mic50li	Mic			; 	  (2nd time 50.75 mic sec)
	goto	Mic50l		; go back (2nd time 51.25 mic sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Milliseconds x n	(Milli)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Millir	movwf	Millic 		; set how many 1 mil sec (1 mic S up to here)
	nop			; 1.25 micro sec
;
Millil	Mic50	D'19'		; + 50 mic x 19 = 951.25 mic S (2nd, 1951.25) 
	Mic2p5	D'19'		; + 47.5 mic = 998.75 micro S  (2nd, 1998.75)
	nop			; +0.25 mic = 999 micro sec    (2nd, 1999)
;
	decfsz	Millic,F	; If  exhausted then 1 micro sec hereafter
	goto	Millili		; else go out (2nd, 999.75 mic S)
	return
;
Millili	Mic			; 		(2nd time 1000.75 mic S)
	goto	Millil		; go back (2nd time 1001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	100 Milliseconds x n	(Mil100);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mil100r	movwf	Mil100c		;set how many 100 ms(1 micr sec up to here)
	nop			; 1.25 micro sec
;
Milhl	Milli	D'99'		;+1ms x 99 = 99001.25 micS (2nd,199001.25mic)
	Mic50	D'19'		; + 950 mic = 99951.25 micS(2nd.199951.25mic)
	Mic2p5	D'19'		; + 47.5 mic = 99998.75micS(2nd,199998.75mic)
	nop			; + 0.25 mic = 99999 mic S (2nd,199999 micS)
;
	decfsz	Mil100c,F	; If exhausted then 1 micro sec hereafter
	goto	Milhli		; else go out (2nd time, 99999.75 mic S)
	return
;
Milhli	Mic			;  	    	(2nd time, 100000.75 mic S)
	goto	Milhl		; 		(2nd time, 100001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Seconds x n	 (Secs)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Secsr	movwf	Secsc 		; set how many sec ( 1 mic sec up to here)
	nop			; 1.25 micro sec
;
Secsl	Mil100	D'9'		; 
	Milli	D'99'		; + 999 milli sec = 999001.25 micro sec
;
	Mic50	D'19'		; + 950 mic = 999951.25 micro sec
	Mic2p5	D'19'		; + 47.5 mic = 999998.75 micro sec
	nop			; + 0.25 mic = 999999 micro sec
;
	decfsz	Secsc,F		; If exhausted then 1 micro sec hereafter
	goto	Secsli		; else, go out 
	return
;
Secsli	Mic
	goto	Secsl		; (Second time, Sec + 1.25 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Minutes	x n	 (Mins)		;
;	 Overhead ignored, that is only	;
;	 751.25 Mic S even when 100 Min	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Minsr	movwf	Minsc		;set how many minutes from parameter
;
Minsl	Secs	D'60'		; 1 Seconds x 60
	decfsz	Minsc,F
	goto	Minsl
	return
;
	space
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; User debug subroutine		;
;	Show bit 7 to 0 	;
;	of specified byte	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Udbgr	clrf	BSR		; Set Bank=0
	movwf	DmpA		; move data to Dmpa
;
	btfss	PORTdbg,LED	; Check if LED=on (on=High)
	goto	UdbLoff		;  if LED=off(Low), skip saving process
	bsf	Flags,LEDpH	; Save LEDon status
	LEDoff			; and off LED
	Mil100	D'10'		; wait for a second in case LED has been on
;
UdbLoff	movlw	Dmp8V		; set counter 8
	movwf	Dmp8C		; to Dmp8C
;
Udblp	Ublink	Debugb		; Blink for Debug = 8 times
	btfsc	DmpA,7		; check top bit 7
	goto	UdbOn		; if on then to UdbOn
	goto	UdbOff		; if off then to UdbOff
;
UdbOn	LEDon
	Mil100	D'30'		;
	goto	Udbeck
;
UdbOff	LEDoff
	Mil100	D'30'		;	
;	goto	Udbeck
;
Udbeck	decfsz	Dmp8C,F
	goto	Udbnext
	goto	Udbend
;
Udbnext	rlf	DmpA,F
	goto	Udblp
;
Udbend	Ublink	Debugb		; end blinking and 
	Mil100	D'100'		; blank for 10 seconds to write down
;
	btfss	Flags,LEDpH	; Check if LED was on
	goto	Udbret		;  no, goto return
	LEDon			;  if it was on, then on again
	bcf	Flags,LEDpH	; Clear LED save flag
;
Udbret	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Blinking to show debug or abend	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Ublinkr	movwf	BlinkC
Ublinkl	LEDon			; LED on
	Milli	D'30'		; for 30ms
	LEDoff			; LED off
	Milli	D'200'		; for 200ms
;
	decfsz	BlinkC,F
	goto	Ublinkl
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Abend routine			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Uabendr	clrf	BSR
	movwf	Abendn			; Set Uabend number
;
	Ublink	Abendb			; Blink 25 times
	Udebug	Abendn			; Show Abend number
	goto	$
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	End of program						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	end

 では今回はこのへんで。

 

©2019 Akira Tominaga, All rights reserved.

 

オリジナル・レーザープロッター その5 極座標の簡単なプログラム紹介

前回書いた「極座標レーザーエングレーバー」の全プログラムを紹介します。

f:id:a-tomi:20190927133619j:plain

プログラムは、JPGファイルからプロッター用データを作るPCのプログラムと、それを刻印するArudinoスケッチの2本だけです。それぞれ、200ステップ強と300ステップ弱しかないとても単純なものです。

上の図は左のjpgファイルから、輝度が一定値以下の部分を白で右に取り出した画面です。PCのプログラムでこれを行いプロットする点を取り出します。そして刻印時間が速く、それでも回転メカの向きが1方向で収まるように並べなおします。

そのためのPCのプログラム言語は何でもできますが、ここではマイクロソフトフリーソフトであるSmall Basicを使っています。これについてはこの連載の初めの方でもご紹介しましたが下の方に再掲しておきます。

刻印までの処理全体の構成は次の図(DFD-GS表記)のようにしています。

f:id:a-tomi:20190927134312j:plain

 jpgの画像ファイルはPICxxxという名前にして、プログラムを入れるフォルダー直下の特定フォルダー(ここではDat5という名前にしています)に格納しておきます。データ化プログラムへのキーイン操作で3桁の番号xxxを指定すると、そのjpgファイルがロードされ画面表示されます。また、そのフォルダー内にプロット用のテキストファイルPICTxxx.txtが出力されますので、マイクロSDへコピーし名前を単なるPICT.txtに変更しておけばArudinoの刻印プログラムはそれを読みエングレーブします。

データ化プログラムではまずプロットする点を表示し、次に刻印しやすいように並べ変えたベクトルのような形で出力ファイルを作ります。次の動画のようになります。処理速度はPCの能力に応じてそれなりの時間がかかります。処理が終わると刻印画像は黄色に表示され、最後にベルがチーンとなります。

youtu.be

このプログラムではテキスト画面とグラフィック画面を出します。テキスト画面はスクリーン上の場所を移動して構いませんが、グラフィック画面は各点の輝度を取り出して計算をしているため、スクリーン上で場所を移動してはいけません。

また、データ収集密度はプログラム内で指定しています。使う画像によりますが、おおざっぱで良いものはこれを疎に(たとえば2.0とか3.5とかに)しますが、一番上に示したように点が抜けない方がよい図は密(たとえば1.0とか1.5とかに)にします。この図の場合は元々粗い図なので、疎(例2.0)に指定して処理すると次の図のようにさらに粗くなります。

f:id:a-tomi:20190927145447j:plain

後はプログラムをごらんいただけばわかるかと思います。

実験に使ったデータ化プログラム(Smallbasic)とArduinoスケッチの順に以下にソースプログラムを掲載します。

 

1)データ化プログラム

 

PgmName="190923-JpgToPolarCoordinatesTxt-V00"
CopyRight="(c)2019 Akira Tominaga, All rights reserved"
' Function
'   Get brightness data from jpg and make txt file for plotter
' Major revisions
'   V00 Sept. 23, 2019

TextWindow.Title=PgmName +"   "+CopyRight
TextWindow.BackgroundColor="Black"

' Set data path  ***** PgmName must be correct to get data path *****
myPath=File.GetSettingsFilePath()       'set current path name all
Tlen1=Text.GetLength(mypath)            ' get current path name length
Tlen2=Text.GetLength(PgmName+".Settings") ' get unneeded length
myPath=Text.GetSubText(myPath,1,Tlen1-Tlen2) 'set folder name only
myPath=myPath+"Dat5\"                   ' and add the name of data folder

' Load picture PICnnn.jpg specified by keyed-in nnn
TextWindow.WriteLine("") ' skip one line For ease of key-in
KeyIn:
TextWindow.Write("     Please type-in PIC#. -> ")
keyinN=TextWindow.Read()
myPict=myPath+"PIC"+keyinN+".jpg"
PsizeX=ImageList.GetWidthOfImage(myPict)
If PsizeX>18 Then   ' exists with enough size to plot ?
  Goto ImageOK
Else
  TextWindow.WriteLine("*** No such PCL#.jpg ***")
  Goto Keyin
EndIf
ImageOK:
PsizeY=ImageList.GetHeightOfImage(myPict)

'Graphics window
GraphicsWindow.BackgroundColor = "Black"
GraphicsWindow.Width=PsizeX*2
GraphicsWindow.Height=PsizeY
GraphicsWindow.Title=PgmName
inPic=ImageList.LoadImage(myPict)
GraphicsWindow.DrawImage(inPic,0,0)

'Setup Polar Coordinates values for tested rotator 
thetaPi=1211 ' device specific number to rotate pi (half circle)
rMax=Math.SquareRoot(PsizeX*PsizeX/4+PsizeY*PsizeY)+1

'Setup work file name and create header record
wkFile=myPath+"Work"+KeyinN+".txt"
lnum=1 'PsizeX,PsizeY characters with left zero
szX=PsizeX+1000 ' to secure 4 digits, add 1000
szY=PsizeY+1000 ' to secure 4 digits, add 1000
rMax4=Text.GetSubText(rMax+1000,1,4) 'delete value under 1
thetaPi4=thetaPi+1000 ' to secure 4 digits, add 1000
header=szX+","+szY+","+rMax4+","+thetaPi4
File.WriteLine(wkFile,lnum,header)

'Get RGB data and convert them to Brightness data
Density=2.0 ' Set data density *********** Specify density*********
For Ptheta=0 To thetaPi step Density
  For Pr=0 To rMax Step Density
    'Convert polar-coordinates to XY
    CalcXY()
    xX=XX+1000 'force 4digits
    YY=YY+1000 'force 4digits
    X=Text.GetSubText(XX,1,4)-1000
    Y=Text.GetSubText(YY,1,4)-1000
    If X<=0 or X>=PsizeX or Y<=0 or Y>=PsizeY then
      Goto Ignore
    EndIF  
    'Get color and convert it to brightness
    color=GraphicsWindow.GetPixel(X,Y)
    getkido() ' convert RGB to Brightness
    gcolor="#000000"
    txdata=""
    'If dark then make SD record and  plot picture
    If kido<128 Then
      gcolor="#FFFFFF"
      'Set text data
      X4=X+1000
      Y4=Y+1000 
      rp4=Pr+1000
      thetap4=ptheta+1000
      txdata=X4+","+Y4+","+rp4+","+thetap4
      lnum=lnum+1      
      File.WriteLine(wkFile,lnum,txdata)
    EndIf
    GraphicsWindow.SetPixel(X+PsizeX,Y,gcolor)
    Ignore:
  EndFor
EndFor  
' When all data done, write EOF
lnum=lnum+1
eof=9999
File.WriteLine(wkFile,lnum,eof)

TextWindow.Write("Work")
TextWindow.Write(KeyinN)
TextWindow.WriteLine(".txt has been created as a work file")
TextWindow.Write("  with ")
TextWindow.Write(lnum-2)
TextWindow.WriteLine(" points to plot")

' Optimization for quick plotting

'Prepare output file
OutFile=myPath+"PICT"+KeyinN+".txt"

' Transfer header (Xsize,Ysize,rMax,thetaPi)
InLnum=1
InText=File.ReadLine(wkFile,InLnum)
OutLnum=1
OutText=InText
File.WriteLine(OutFile,OutLnum,OutText)

' Convert wkFile to OutFile
OutReverse=0
InLnum=InLnum+1
InText=File.ReadLine(wkFile,InLnum)
OutText=InText
OutRec=1
Tsave=Text.GetSubText(InText,16,4)

OutLoop:
InLnum=InLnum+1
InText=File.ReadLine(wkFile,InLnum)
XX=Text.GetSubText(InText,1,4)
If XX=9999 Then
  OutLnum=OutLnum+1
  File.WriteLine(OutFile,OutLnum,OutText)
  OutText=InText
  OutLnum=OutLnum+1
  File.WriteLine(OutFile,OutLnum,OutText) 
  Goto Eof
EndIf

' Change color of process point to yellow
X=XX-1000
YY=Text.GetSubText(InText,6,4)
Y=YY-1000
gcolor="#FFFF00"
GraphicsWindow.SetPixel(X+PsizeX,Y,gcolor)

' Go on to check next input
Tchk=Text.GetSubText(InText,16,4)
If Tchk=Tsave Then  ' if same theta then 
  Goto accumText    '   accumulate data without output
Else                ' else output accumulated data
  Tsave=Tchk
  Goto accumOut
EndIf

' Accumulate data records with opposite sequences alternatively
accumText:
If OutReverse=0 Then
   OutText=Text.GetSubText(OutText,1,22*OutRec-2)+"##"+InText 
Else
  OutText=Text.GetSubText(InText,1,20)+"##"+OutText
EndIf
OutRec=OutRec+1
Tsave=Tchk
Goto OutLoop

accumOut:
OutLnum=OutLnum+1
File.WriteLine(OutFile,OutLnum,OutText)
OutText=InText
If OutReverse=0 Then
  OutReverse=1
Else
  OutReverse=0
EndIf
Goto OutLoop

' End of file process
Eof:
TextWindow.Write("Edited ")
TextWindow.Write(InLnum-2)
TextWindow.Write(" input to ")
TextWindow.Write(OutLnum)
TextWindow.WriteLine(" output text-lines")
Sound.PlayBellRing()

Sub getkido   ' from color code #rrggbb  to brightness
  HexV=0
  Hexc=Text.GetSubText(color,2,1)
  Hex2V()
  R10=HexV
  Hexc=Text.GetSubText(color,2,1)
  Hex2V()
  R01=HexV
  Rv=R10*16+R01
  Hexv=Text.GetSubText(color,3,1)
  Hex2V()
  G10=HexV
  Hexc=Text.GetSubText(color,4,1)  
  Hex2V()
  G01=HexV
  Gv=G10*16+G01
  Hexc=Text.GetSubText(color,5,1)
  Hex2V()
  B10=HexV
  Hexc=Text.GetSubText(color,6,1)
  Hex2V()
  B01=HexV
  Bv=B10*16+B01
  Kido=0.299*Rv+0.587*Gv+0.114*Bv
EndSub

Sub Hex2V   ' from hexa-decimal one digit to value 
  If Hexc < "9" Then
    Hexv=Hexc
  EndIf
  If Hexc="A" Then
    HexV=10
  EndIf
  If Hexc="B" Then
    HexV=11
  EndIf  
  If Hexc="C" Then
    HexV=12
  EndIf
  If Hexc="D" Then
    HexV=13
  EndIf   
  If Hexc="E" Then
    HexV=14
  EndIf
  If Hexc="F" Then
    HexV=15
  EndIf     
EndSub

Sub CalcXY ' Calculate XX and YY from Pr and Ptheta
  xx=Pr*Math.Cos(Math.Pi*Ptheta/thetaPi)
  XX=xx+PsizeX/2
  yy=Pr*Math.Sin(Math.Pi*Ptheta/thetaPi)
  YY=PsizeY-yy
EndSub

次にArduinoスケッチです。

/* ******************************************************
*    Polar-coordinates Laser Engraver 			*
*    (c)2019 Akira Tominaga, All rights reserved.	*
*    Version 00 Sept. 19, 2019				*
*    Major revisions:					*
*							*
******************************************************* */
#include     // <SPI.h>
#include      // <SD.h>
// pin assignment and values
// *** A4988 stepper driver *** X for Radius, Y for Theta
#define NsleepX 14  // !sleepX
#define StepX 9     // StepX
#define DirX 8      // DirectionX
#define NsleepY 7   // !sleepY
#define StepY 6     // StepY
#define DirY 4      // DirectionY
// ***for laser
#define Pwm 5       // PWM 980Hz for Laser
#define DutyCtl 2   // Duty rate control pin (A2=D16)
// *** for micro SD (SPI)
// D13=SCK,D12=MISO,D11=MOSI
# define ChipSel 10
File mySD;          // symbol for SD file
// *** for LED and sitches
#define Led 2       // LED for operations
#define Sw 3        // Tact switch for operation processes
#define Rotate  3   // Fwd/bkwd sw for initial pos(A3=D17)
#define Stoppers 15  // OR for limitters (A0=D15)
// constants and values
#define tmStpX 700   // stepperX phase time in μS
#define tmStpY 700   // stepperY phase time in μS
#define enlargeX 2   // enlarge-multiplier for X
#define enlargeY 1   // enlarge-multiplier for Y (must be 1)
int dutyOn;          // duty ratio when laser On
#define dutyOff 0    // duty ratio when laser Off
#define dutyPos 1    // duty ratio for positioning
#define burnTime 14  // burning time (milliseconds)
int Rmax;            // Radius max-value
int ThetaPi;         // Theta value for Pi radian
int curR = 0;        // current Radius = 0
int curTheta = 0;    // current Theta =0
int newR = 0;        // new Radius position
int newTheta = 0;    // new Theta position Y
int Delta;           // amount to move for both R & Theta
byte Byte;           // byte work area for SD data
int Value;           // value work area
int k;               // common loop counter
int loopNum = 0;     // data sequence number of SD file

void setup() { // Arduino Setup *******************
  analogWrite(Pwm, dutyOff);  // laser off
  pinMode(NsleepX, OUTPUT);
  digitalWrite(NsleepX, LOW); // sleep X = Radius
  pinMode(NsleepY, OUTPUT);
  digitalWrite(NsleepY, LOW); // sleep Y = Theta
  pinMode(DirX, OUTPUT);
  pinMode(DirY, OUTPUT);
  pinMode(StepX, OUTPUT);
  digitalWrite(StepX, LOW);
  pinMode(StepY, OUTPUT);
  digitalWrite(StepY, LOW);
  pinMode(Sw, INPUT_PULLUP);
  pinMode(Led, OUTPUT);
  digitalWrite(Led, LOW);
  Serial.begin(9600);
  delay(1000);        // wait for all devices stabilized

  // ** Process 1) 12V power-on for laser and motors
  Serial.println("Pwr 12V on");
  while (digitalRead(Sw) == HIGH) {  // wait for sw pushed
    bLED(1, 50, 10);           // repeat LED on while waiting
    delay(20);
  }
  delay(500);     //avoid chattering and long-push detections
  while (digitalRead(Sw) == LOW) { // wait for Sw released
  }
 
  // ** Process 2) adjust center position
  Serial.println("Adj Center");
  while (digitalRead(Sw) == HIGH) {  // loop while Sw not pushed
    analogWrite(Pwm, dutyPos); // blink weak laser-beam
    digitalWrite(Led, HIGH);   // LED on while waiting
    // Fwd and Bkwd by tact switches
    int RotPos = analogRead(Rotate);
    if (RotPos > 767) {
      digitalWrite(DirY, HIGH);   // Theta fwd
      Delta=1;
      RotateY();
    }
    if (RotPos < 255) {
      digitalWrite(DirY, LOW);   // Theta bkwd
      Delta=1;
      RotateY();
    }
    delay(5);
    analogWrite(Pwm, dutyOff); // blink laser-beam
    digitalWrite(Led, LOW);    // blink LED, too
    delay(5);
  }
  // Process-Sw pushed
    while (digitalRead(Sw) == LOW) { // wait for Sw released
  }
  analogWrite(Pwm, dutyOff); // laser off while (digitalRead(Sw) == HIGH)
  delay(500); //avoid chattering
  
  // ** Process 3) prompt to wear protection glasses
  Serial.println("Protect eyes!");
  delay(500);
  while (digitalRead(Sw) == HIGH) {
    bLED(1, 15, 30);         // blinkking LED
  }
  while (digitalRead(Sw) == LOW) { // wait for Sw released
  }

  // check if SD card exists
  if (!SD.begin(ChipSel)) {
    Serial.println("Chk SD");  // if error, show error
    while (1) {                // and stop,
      bLED(20, 20, 30);        // blinking LED forever
    }
  }
  // read PICT.txt header
  mySD = SD.open("PICT.txt");
  Rdskip(10);           // skip header for XY sizes
  Rd4();                // read 4bytes to Value
  Rmax = Value - 1000;  // get Rmax length
  Rdskip(1);            //skip comma
  Rd4();                // read 4bytes to Value
  ThetaPi = Value - 1000; // get Theta value for Pi radian
  Rdskip(2);           // skip CR+LF

  digitalWrite(NsleepX, HIGH); // wake-up X
  delay(2);             // delay for A4988 charge-pump
}

void loop() {   // Arduino Loop ******************
  /* ********************************************
      1.Read SD file and get new(newR,newTheta) data
   *  ***************************************** */
  // read PICT.text
  Rd4();                // read 4bytes to Value
  if (Value >= 9999) {
    EndProc();
  }
  Rdskip(6);            // skip comma, Y, and comma
  Rd4();                // read 4 bytes to Value
  newR = Value - 1000;
  Rdskip(1);            // skip comma
  Rd4();                // read 4bytes to Value
  newTheta = Value - 1000;  // get newTheta
  Rdskip(2);           // skip CRLF (2bytes)

  loopNum++;
  Serial.print("#");
  Serial.print(loopNum);     // show data number

  /* ********************************************
      2.Set laser duty-ratio on potentiometer
   *  ***************************************** */
  dutyOn = 250.0 * analogRead(DutyCtl) / 1023;
  if (dutyOn < 5) {         // if small value
    dutyOn = 1;             // then regard it as min.
  }

  /* ********************************************
      3.Drive steppers and irradiate laser beam
   *  ***************************************** */
  // move R and Theta
  Serial.print (" ");
  Serial.print (curR);
  Serial.print (",");
  Serial.print (curTheta);
  Serial.print (" to ");
  Serial.print (newR);
  Serial.print (",");
  Serial.println (newTheta);
  movY();         // move Y=Theta first
  movX();         // move X=Radius next
  laserBeam();    // beam on for the burning time
  curR = newR;    // set newR to curR
  curTheta = newTheta; // set newTheta to curTheta

}                 // last line of main loop ***

/***********************************
     User-defined functions
* **********************************/
// *** Read SD 4 bytes into integer Value ***
void Rd4(void) {
  Value = 0;
  for (k = 0; k < 4; k++) {
    Byte = mySD.read();
    Byte = Byte & B00001111;
    Value = Value * 10 + Byte;
  }
}

// *** Read SD and skip given bytes ***
void Rdskip(int bn) {
  for (k = 0; k < bn; k++) {
    Byte = mySD.read();
  }
}

// *** move stepperX (no sleep actions) ***
void movX(void) {
  if (newR == curR) {
    return;
  }
  if (newR > curR) {
    digitalWrite(DirX, HIGH);   // X fwd
    Delta = newR - curR;
  }
  if (newR < curR) {
    digitalWrite(DirX, LOW);   // X bkwd
    Delta = curR - newR;         // set absolute value
  }
  for (int l = 0; l < Delta * enlargeX; l++) {
    for (int m = 0; m < 8; m++) { // 1 cycle for MicroStep1
      digitalWrite(StepX, HIGH);
      delayMicroseconds(tmStpX);
      digitalWrite(StepX, LOW);
      delayMicroseconds(tmStpX);
    }
  }
  delay(2);
}

// *** move stepperY (sleep actions inside) ***
void movY(void) {
  if (newTheta == curTheta) {
    return;
  }
  if (newTheta > curTheta) {
    digitalWrite(DirY, HIGH);   // Y fwd
    Delta = newTheta - curTheta;
  }
  if (newTheta < curTheta) {
    digitalWrite(DirY, LOW);   // Y bkwd
    Delta = curTheta - newTheta;     // and change sign
  }
  RotateY();   // rotate Y with sleep actions
}

// *** rotate stepperY (with sleep actions) ***
void RotateY(void) {
  digitalWrite(NsleepY, HIGH); // wake-up Y
  delay(2);                    // for A4988 charge-pump
  for (int l = 0; l < Delta * enlargeY; l++) {
    for  (int m = 0; m < 8; m++) { // 1 cycle MicroStep1
      digitalWrite(StepY, HIGH);
      delayMicroseconds(tmStpY);
      digitalWrite(StepY, LOW);
      delayMicroseconds(tmStpY);
    }
  }
  delay(2);                   // avoid mechanical inertia
  digitalWrite(NsleepY, LOW); // sleep Y
}

// *** Laser beam-on for burnTime ***
void laserBeam(void) {
  analogWrite(Pwm, dutyOn);
  delay(burnTime);
  analogWrite(Pwm, dutyOff);
}

// *** End of File process ***
void EndProc(void) {
  newR = 0;
  newTheta = ThetaPi*2;
  movY();
  movX();
  delay(2);        // avoid mechanical inertia
  digitalWrite(NsleepX, LOW);  // sleep X
  Serial.println("Power-off 12V-DC");
  mySD.close();
  while (1) {}
}

// *** blink LED ***
void bLED(int nTimes, int onmS, int offmS)
{
  for (int m = 0; m < nTimes; m++) {
    digitalWrite(Led, HIGH);
    delay(onmS);
    digitalWrite(Led, LOW);
    delay(offmS);
  }
}

// end of program

 

ちょっとした処理に便利な、Microsoft Small Basicの簡単なご紹介をしておきます。

Windowsの場合に、すぐ使うプログラムを作るのに一番単純で手っ取り早いのはマイクロソフトがフリーで提供している Small Basicかと思います。これですと作成時間が僅かで済むし、Windows自体との相性もいい(それはあたりまえか)。さらに、完成したプログラムをVisual Basicへ自動変換する機能も備えています。

もちろん、pythongccなど便利な言語環境はたくさんあるわけで、筆者もそれらを使っていますが、関連コンポネントとの相性など整合性確保に手間がかかることがあります。仕事や複雑なものを作るとき以外は、つまり趣味にはこの単純なSmallBasicを使うことが多く、時間もエネルギーも最小でたいへん助かっています。

f:id:a-tomi:20190810155827j:plain

この言語には型とか変換とかの概念がなく、数値は常に最大桁数の高精度であるなど、超単細胞(!)なのは筆者から見ると却って魅力的です。それどころか文字と数値との区別すらありません!

よって機能は限られますが、使う側の工夫次第。例えば多次元の配列がないため代わりに1行のベクトル内に自分で作るとか。その代わり、わけのわからない環境設定問題に遭遇しないのがたいへん気楽です。つまり全ては自分の手の内でできるというわけ。

限られた構造化文だけでなく、タブーのgoto(ジャンプ)も許容されtているので却って書きやすいこともあります。逆にswitch case (Select when)などの構造化文がないので、If文を並べなければならないことがあります。

なにはともあれマイクロソフトからダウンロードしてみてください。ネットでの情報は整っていて、手元にマニュアルが要らない簡単さです。

 さて、以上のようなしかけはレーザー装置でなく、CNCルーターや単なるプロッターでも同じように手軽に作れます。(なお、線が主体の場合には線を引く方式にする方がよい場合があります。そのような汎用ソフトはXY用しかない感じですから工夫して自作がいいですね。)

 

最後にレーザーを使う場合の大事な注意点です。

レーザーは失明、傷害、火災等の危険を伴い、周囲にも危険がおよびます。取扱う場合、必ず関連法規と基準(http://kikakurui.com/c6/C6802-2011-01.html)などに従って、自己責任にて正しく管理をしてください。もしこの記事をみて実験や自作をされる場合でも、一切の責任を免除させていただきます。

 

ではこの辺で。

 

©2019 Akira Tominaga, All rights reserved.

オリジナル・レーザープロッター その4 極座標型がいい!

f:id:a-tomi:20190923203023j:plain

この連載の中では初の新メカニズムをご紹介します。オリジナルの極座標エングレーバーで、上の写真はJPG画像から10x5センチに刻印したところです。

f:id:a-tomi:20190924121635j:plain

カニズムの形はこれまでの連載中のXY型と同じに見えますが、実は大違いです。

f:id:a-tomi:20190923205823j:plain

 

下にあるのはY軸に沿う水平移動板ではなく、今回は単なる回転板です。

f:id:a-tomi:20190923210534j:plain

つまりXY座標を使わずに極座標を使っています。ご存じのとおり極座標は長さと角度だけで位置が決まります(電子回路好きの方は交流の複素表記、オイラーの式みたいなものとお考えいただくと分かり易いかも)。こんな感じで刻印します。

youtu.be

このメカニズムの利点は作り易くコストが安いことです。それでもプロッティングはXY型とほぼ同じ精度が出せます。(ただし一般のソフトはXY用なので使えません。)特徴は、

(1)メカの構成が楽:リニアステージ(直線軸)1つと回転板を使う。回転板は1方向にだけ回すようにすれば、バックラッシュ(あそび)の影響を小さくできる。

(2)リニアステージの短縮:直線軸の必要スライド長は、ほとんどの場合にXY型より短くて済む(例:アスペクト比2対1の画像なら、必要長はXY方式X軸の1/√2で済む。例えば30cmスライドを20cmスライドに。)

(3)縮尺の設定が簡単:エングレーブ画像の縮尺拡大値は直線軸のステッパーだけで設定できる。

(4)部品数が少ない:回転軸側側にはストッパー(リミッター)が不要。これまでに書いたステッパーの動かし方であれば、刻印終了時に回転位置を初期値まで進めておくことで楽ちんになる。(バックラッシュによる問題を避けるため、回転方向は常に1方向で回してます。これまでこの連載に書いたとおりのステッパー駆動方法なら位置決めは正確で、その後の初期位置調整が不要となる!)。

(5)工作精度での利点:軸と回転のメカの水平角度に精度が要求されない(どっちみち回して初期位置を設定するわけなので、かなり荒っぽくて問題なし)。

回路やソフトはXYの場合と殆ど同じ(長さと角度を使う位置決めのやり方以外は)で、ステッパーのX側を長さ用の直線軸に、Y側を角度用の回転軸に使います。

このスタンドアローン型エングレーバーはArduino-Uno(というよりマイコンATmega328P)1つだけを使って、容量の余裕十分で動かすことができます(当連載でご紹介したように、PCで画像から刻印データを作りSDカードに入れて使う方法です。一般的なXY用ソフトは使いません)。

次は他の刻印例です。

youtu.be

 密度を粗くして刻印(長さ軸、回転軸それぞれ一つ置きにプロット数を減少)しても結果はあまり粗くなりませんが、フルにプロットすると次のように精密にできます。

 

f:id:a-tomi:20190924112053j:plain

 欠点はいくつかありますが、主なものは次の点だけです。

(6)回転板の初期位置設定スイッチが必要:最初に回転板の位置を設定する際、減速比が大きいメカなので外力では回せません。よってプログラムで回せるようにしておきます(もっとも、最初に他へ接続して設定するなら不要と言えば不要です。しかしデバッグや調整での手数を省くためには必要ですね)。残り少ないピンを工夫して使わないといけません。

前回紹介した回路では、空いているピンはA3,A4,A5くらいですね。A4とA5は今後のI2C接続(例えば液晶をつけるなど)のために開けておきたいところなので、A3を使います。これに順方向用(Fwd)と逆方向用(Bkwd)の2つのプッシュスイッチをつける回路として、次のようにしました。

f:id:a-tomi:20190924114003j:plain

アナログ入力値が高ければ(例えば1000以上なら)順方向(Fwd)スイッチが押されたとみなし、低ければ(例えば24以下なら)逆方向(Bkwd)スイッチが押されたとみなします。もちろんそれ以外なら押されていないわけです。このやり方はどのマイコンにも通用するピン節約法ですね。

使い方として、逆方向の場合には回しすぎておき、最後に順方向に戻すことでバックラッシュが避けられます。

 

回路図は今までのと同じで、それにA3ピン(=D17ピン)にこれらのスイッチを加えただけです。前の手書き回路図をいれておきます。汚い手書きですが。

f:id:a-tomi:20190820220221j:plain

前回作ったPCB基板はXY用の本番機に使っているので、今後また時間のある時に上の変更を入れて作るとします。 ブレッドボードでは次のようにしています。右上にあるのが今回追加したスイッチ2個です。

f:id:a-tomi:20190924114652j:plain

当面はこれを実験機の後ろにおいて操作しています。


f:id:a-tomi:20190924114851j:plain

 

この連載の最初(その0)にメカの種類を3つ書きましたが、今回さらに3種類を付け足しておきます。今回のメカは次のメモの5番目です。毎度汚いメモですみませんが。

f:id:a-tomi:20190924115552j:plain

4)は刻印対象が曲げられる材料のときに限られますが、同じように作れますね。ただし原理はXYと同じです。

6)は投射型ですがそのままだと大変危険です。たとえ実験段階でもレーザー光を横へ向けるのは絶対に避けないといけません。強いレーザー光直視は瞬時で失明するし、事故の元でもあります。実装でも実験でも必ず下へ向けますし、どれもレーザーの運用基準に沿うことが必須ですね。

下向きにしている場合でも、反射光が見えるときは保護メガネ必須です。この実験ではカメラだけに反射光を見させていますが、カメラにとっても危険ですね。万一ネジなどに反射すると直射と同じ危険がありますので厳重注意。

 

この記事は連休中の昨日に書こうとしていましたが、途中で用事がはいったので書くのが今になりました。本日はこれに使える時間が限られているためこの辺で。。。使ったプログラム全部(といっても少量でできています)を次回あたりに掲載して説明したいと思います。

 

最後に、毎度書きますが大事な注意です。

*レーザーは失明や傷害、火災等の危険を伴い、周囲にも危険がおよびます。関連法規と取扱い基準(http://kikakurui.com/c6/C6802-2011-01.html)などに従って、正しく管理してください。この記事をみて自作される場合も全て自己責任でお願いします。

 

 

©2019 Akira Tominaga, All rights reserved.

 

PICアセンブラーで芸当(?)その4)もっと接続-余裕の8ピン!

前回の回路に別のI2C接続センサーを加えても余裕で処理できるでしょう。それだと面白くないので、試しに別の1ワイヤー・プロトコル(1-wire protocol)の温湿度センサーを加え、温湿度計付時計に拡張してみます。

f:id:a-tomi:20190914220754j:plain

ディスプレイの表示を2秒ごとに時刻、温度、湿度と順次切り替えて動きます。次の動画のように時刻設定もできます。実は思わぬ問題が出て解決したのですが、それは後ろのほうに書きます。改良したソースプログラム全体を最後につけておきます。

youtu.be

「手が空いたときには前のプログラムの解説を」と考えていましたが、この3連休の真ん中で拡張を思いつきました。こちらのほうが面白く役立つかも。(なお、これとは別に、少しずつ作っている新案のオリジナル・レーザーエングレーバーが進んでいますが、このブログの別記事に後で記載するとします。)

この記事では2ピンのI2Cプロトコル、2ピンのTM16xxプロトコル、1ピンの1ワイヤプロトコルの3種を、たった3ピンで動かしています。加えてタクトスイッチ3つの計6ピン。PIC12F1822の容量は、プログラム、データともに6割以上の余裕があります。これこそアセンブラー以外ではできない芸と、勝手に楽しんでいます^^; そもそも8ピンPICは小さくてDIP実装ですむので扱いやすいですからね。

プログラムは他のPICに簡単に乗せ換えられるようにコーディングしています。ポートとピンのEqu、始まりのDevsetマクロのピン設定を変えるだけで動きます。例えば14ピンPIC16F1825は12F1822からの上位互換性があり、データメモリーは8倍の1024バイト、プログラムメモリーは4倍の8Kワード。この場合そんなに要りませんが。

 

さて、ここでの温湿度センサーは、安価なDHT11のブレークアウト・モジュールです。

f:id:a-tomi:20190914225000j:plain

かなり前に1個数十円で沢山買った部品です。 作りもいかにも安っぽく、グリルがまがって付いていたりして。。とはいえ、測定温度も湿度も妥当な値を示し、熱容量も小さくて敏感です。小数点以下も表示する同シリーズのDHT22とインターフェイスもデータ構成も同じです(DHT11では小数点以下のデータにゼロが埋められているだけです)。

このデバイスは標準の1ワイヤープロトコルからID(アドレス)が省かれた、シンプルなものです。1ワイヤー接続は便利で簡単ですが、グランドとのツイストペアで通信距離が長くとれます。非同期通信とは違い、エラーが起こりにくい通信方式でよく考えられたものだと感心します。原理はモールス符号のようなものです。

長短のH信号をその長さの差で検出すればよいので、処理は簡単です。各符号1ビットの終わりから計時を再開すれば常に同期が保たれ、非同期のように狂ってきません。簡単さは最後につけたプログラムをご参照下さい。

次の測定は、このプログラムでの長短符号(Channel-1)と、その検出タイミング(Channel-4)を示します。

f:id:a-tomi:20190916121220j:plain

当プログラムでは、測定用信号はタクトスイッチTime用入力ピンを共有して出力できるようにしてあります。プログラム中の1か所にあるTriggerマクロ命令使用箇所のコメント(;)をはずせばそのまま動きます。(そのままコメントを外しておいても支障はありません。時刻設定モードでこれをタクトスイッチとして使いますが、そのモードではDHT11との通信はしないからです。)

1ワイヤー・デバイスの信号は、検出タイミングでHなら1、Lなら0という単純な形式でできていて、最後にエラーチェック符号が送られます。こういう表示用途の場合は、テスト時以外はそのチェックは不要でしょうし、実際にエラーは起きません。通信距離が長いような時はチェックが要るかもしれませんね。

1ワイヤー・インターフェイスはI2C接続と同様にプルアップされ(デバイスの写真に4.7KΩ=472が写っていますね)、オープンコレクタ(オープンドレーン)での接続を前提にしています。ですので、デジタル入出力ピンで直接接続する際は以前の記事に書いたように、100Ω程の抵抗を介した方が安全。ただし、1ワイヤーでの反対極での引き合いは通信スタート時に短時間生じるだけなので、なしでよいかもしれません。この例では後のほうでご紹介しますが、別の理由でCRをつけました。

さて、回路は最初は次のものでテストしていました。

f:id:a-tomi:20190916121648j:plain

 順調に動いていましたが、時刻設定をする際にTM1637の表示が点滅したりするのに気づきました。何が起きているかを調べたら、クロック信号数百回に1度ぐらい、DHT11が反応しているではありませんか!これには本当にびっくり。

f:id:a-tomi:20190916132112j:plain

そのためTM1637へ向けたクロック信号はすっかり邪魔をされています。上の測定でチャネル3はTM1637のDIOです。その形を詳しく分析すると、TM1637向け信号がたまに輝度変更コマンドに化けたりしています。他のコマンドにも化けるでしょう。

そもそも、1ワイヤではクロック線のLが18mS以上続いて次に一定時間Hにならないとデバイス(DHT11)は反応しないことになっています。よって共用で問題なしと確信していたのですが、何が起こっているのでしょうか?ここではクロックはI2C用にもTM1637用にも大体100KHzにしてあり、長く継続してLowということは絶対ありません。

しかし、それに反応したDHT11からの信号は、ホストのクロック送信を上書きする程強力(低インピーダンス)です。そのため、上の測定例ではTM1637向けのクロック信号を、遅いその信号が上書きしています!

そこで考え込んでしまいました。

もともと長いLowで信号開始の合図とする理由は、アナログ時代にデバイスがプルアップ電圧を抜く時間として設定された経緯からです。ところが、現代のセンサー機器は悉くデジタル処理で、電圧はサンプリングしてみている筈です。

DHT11が18mS以上継続してLowと認識したということは、一定間隔でサンプリングした結果運悪く全てがLowの点だけとなり、さらにH検出でもHだけになったということで、それがたまに起きているに相違ないでしょう。クロック信号自体が一定周期で繰り返されますし、10倍ほどゆっくりのDHT11のサンプリング間隔は長い筈ですから。それでDHT11側で「空耳」がおきているわけです!

もしそれが原因ならば対策は簡単!信号約10KHzの1ワイヤDHT11には必要のない100KHzブロック波を聞こえなくします。つまりDHT11へ向かう信号線に簡単なRCローパスフィルタを入れればよいわけです。時定数RxC=2μSを入れたら、DHT11側では100KHzブロック波は十分になまり、全く識別しなくなりました。DHT11が必要とする遅い信号には全く影響しない点もオシロで確認しました。

 

それが次の最終回路図です。

f:id:a-tomi:20190916163454j:plain

そして、ブレッドボードは最終的に次のようになりました。右上のほうにRとCを入れただけですが。

f:id:a-tomi:20190916163605j:plain

 

これで完全なものになったと思いますので、次にソースプログラムをつけておきます。

I2C用とTM16xx用の各2線プロトコル、それにDHTxx用の1線プロトコルの3つが共存しています。それでもアプリケーションを含めてプログラムメモリーは815ワード(2048ワード中の39.8%)、データメモリーは39バイト(128バイト中の30.5%)しか使っていません。もっと色々できますね。 

 

;U190915-RTC-DHT11-TM16-V01s.asm			     	As of Sept. 15, 2019
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;										;
; RTC Reader/Writer, DHT11 reader & TM1637 display with PIC12F1822 V01		;
;	        (C)2015-2019 Akira Tominaga, All rights reserved.		;
;	  Major revisions							;
;		V00 Original version    Sept.8, 2019				;
;		V01 Raise 2-wire speed (both I2C and TM16xx) to 100kHz		;
;										;
;	  Function								;
;		1.Show time, temperature, & humidity on TM1637 4 digit display	;
;		2.Update DS3231 calendar and time when Set switch pushed	;
;		  Mode switch to change calendar/time unit			;
;		  Time switch to change value of the unit within its range	;
;										;
;	  Input/output       							;
;		RA0 Set switch (swSet) input pulled-up				;
;		RA1 Time switch (swTime) input pulled-up			;
;		    Used as LED or DSO trigger when debugging			;
;		RA2 DIO for TM1637 outuput usually and input occasionally	;
;		RA3 Mode switch (swMode) input pulled-up			;
;		RA4 SDA for I2C-RTC  output  usually and input occasionally	;
;		RA5 SCL for I2C-RTC  output, CLK for TM1637 output, and		;
;		    DHT11 1-wire output/input with Low-pass filter 200ohm+0.01μF;
;										;
;	  Remarks								;
;		1. Clock = HFINTOSC (	16MHz)					;
;			Hence 1 step = 0.25 micro seconds			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
	list		p=12F1822      ; list directive to define processor
	#include	 ; <p12F1822.inc> specific variables
    __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
    __CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _BORV_LO & _LVP_OFF
;	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Macro definitions					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Device dependent Macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
DEVset	macro	
	BANKSEL	OSCCON		; Bank=1
	movlw	B'01111010'	; 16MHz and internal oscillator
	movwf	OSCCON
;
;	BANKSEL	INTCON		; Interrupt Con (in all banks hence comment)
	clrf	INTCON		; Disable all interrupts
;
; PORTA initialization
	BANKSEL	PORTA		; Bank=0
	clrf	PORTA
;	BANKSEL	LATA		; Bank=2
;	clrf	LATA
	BANKSEL	ANSELA		; Bank=3
	clrf	ANSELA		; No use of ADC
	BANKSEL	ADCON0		; Bank=1
	clrf	ADCON0		; No use of ADC
;
	BANKSEL	TRISA		; Bank=1
	movlw	B'11001011'	; RA2,4 and 5 are output
	movwf	TRISA
;
	BANKSEL	OPTION_REG	; Bank=1
	bcf	OPTION_REG,7	; Enable weak pull-up
	BANKSEL	WPUA		; Bank=4
	movlw	B'00001011'	; Weak Pull-up for RA 0,1,and 3
	movwf	WPUA		;  
;
	clrf	BSR		; Bank=0
	InitP			; Initialize ports
	endm
;
InitP	macro			; Initialize ports
	movlw	B'11111111'	; All inputs pulled-up and outputs H
	movwf	PORTA
	endm
;		
; for I2C-Master protocol
; Sending data to I2C slave
I2Csnd	macro	slave,dataa,datalen
	movlw	slave
	movwf	I2Cadr
	movlw	dataa
	movwf	FSR0L
	movlw	datalen
	movwf	DataLen
	call	I2Csndr
	endm	
;
; Receiving data from I2C slave
I2Crcv	macro	slave,dataa,datalen
	movlw	slave
	movwf	I2Cadr
	movlw	dataa
	movwf	FSR0L
	movlw	datalen
	movwf	DataLen
	call	I2Crcvr
	endm	
;
; I2C start signal
I2Cstat macro
	call	I2Cstar
	endm
;
; I2C stop signal
I2Cstop	macro
	call	I2Cstpr
	endm
;
; For TM16xx-Master protocol
; Sending Literal to TM16xx
TMsendL	macro	tslit		; Send a literal to TM1637
	movlw	tslit
	call	TMsndLr
	endm
;
; Sending a Byte to TM16xx
TMsendB	macro	tsbyte		; Send a Byte specified, to TM1637
	movf	tsbyte,W
	movwf	Sbyte
	call	TMsndBr
	endm
;
; TM16xx start signal
TMstart macro			; Start TM16xx lines
	call	TMstrtr
	endm
;
; TM16xx stop signal
TMstop	macro			; Stop TM16xx lines
	call	TMstopr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	API and I/O macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;for TM1637 application interfaces
;display 4 literal characters directly to TM1637
dsp4chr	macro 	d1000,d100,d10,d1
	movlw	d1000
	movwf	Dig1000
	movlw	d100
	movwf	Dig100
	movlw	d10
	movwf	Dig10
	movlw	d1
	movwf	Dig1
	call	TMdspr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Time cosuming macros		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic	macro			;Consume 1 μS only
	goto	$+1
	goto	$+1
	endm
;
Mic2	macro	mic2p		;Consume 2 μS x n
	movlw	mic2p
	call	Mic2r
	endm
;
Mic2p5	macro 	mic25p		; Consume 2.5μS x n
	movlw	mic25p
	call	Mic25r
	endm
;
Mic5	macro	mic5p		; Consume 5μS x n
	movlw	mic5p
	call	Mic25r
	movlw	mic5p
	call	Mic25r
	endm
;
Mic50	macro	mic50p		; Consume 50μS x n
	movlw	mic50p
	call	Mic50r
	endm
;
Milli	macro	millip		; Consume mS x n
	movlw	millip
	call 	Millir
	endm
;
Mil100	macro	mil100p		; Consume 100 mS x n
	movlw	mil100p
	call 	Mil100r
	endm
; 
Secs	macro	secsp		; Consume Second x n
	movlw	secsp
	call	Secsr
	endm
;
Mins	macro	minsp		; Consume Minute x n
	movlw	minsp
	call	Minsr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Debug and Abend macros		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LEDon	macro			; LED on macro for debugging
	bcf	Flags,LEDpH	; Clear LEDpin flags beforehand,
	bcf	Flags,LEDpIn	; as timing before PORT accessed
	btfsc	PORTdbg,LED	; Has LED been H before debug?
	bsf	Flags,LEDpH	;  Yes, set LEDpH flag
	BANKSEL	TRISdbg		; Select TRIS bank
	btfss	TRISdbg,LED	; Has LED pin been input?
	goto	$+4		;   No, skip the next 3 steps
	clrf	BSR		; Point Bank0
	bsf	Flags,LEDpIn	; Show LED pin was input
	BANKSEL	TRISdbg		; Select Tris bank again
;
	bcf	TRISdbg,LED	; Set LED output mode, in any case
	clrf	BSR		; Point Bank 0
	bsf	PORTdbg,LED	; LED on
	goto	$+1		; Timing after PORTdbg changed
	endm
;
LEDoff	macro			; LED off macro for debugging
	goto	$+1		; Avoid PORT-update timing problem
	bcf	PORTdbg,LED	; LED off
	btfss	Flags,LEDpIn	; Was LEDpin input before debug?
	goto	$+7		;  No, skip the following 6 steps
;
	BANKSEL	TRISdbg		; Select TRISdbg bank
	bsf	TRISdbg,LED	; Set LED pin input again
	BANKSEL	WPUdbg		; Select WPUdbg, assuming Pulled-up
	bsf	WPUdbg,LED	; Weak Pull-up for input pin
	clrf	BSR		; Point Bank 0
	goto	$+3		; skip the next 2 steps
;
	btfsc	Flags,LEDpH	; Was LEDpin High
	bsf	PORTdbg,LED	;  Yes, set LED pin High
	bcf	Flags,LEDpH	; Clear LED flags,
	bcf	Flags,LEDpIn	;  as timing after PORTdbg changed
	endm 
;
Trigger	macro			; Trigger for DSO
	bcf	Flags,TrigpH	; ClearTrigger flags beforehand
	bcf	Flags,TrigpIn	; for timing before PORT changed
	btfsc	PORTdbg,Trig	; Has Trig pin been H before debug?
	bsf	Flags,TrigpH	;  Yes, set TigpH flag
	BANKSEL	TRISdbg		; Select TRIS bank
	btfss	TRISdbg,Trig	; Has Trig pin been input?
	goto	$+4		;  No, skip the following 3 steps
	clrf	BSR		;  Yes, point Bank 0,
	bsf	Flags,TrigpIn	;   and show Trig pin was input,
	BANKSEL	TRISdbg		;   select TRISdbg bank again
;
	bcf	TRISdbg,Trig	; Set Trig pin output, in any case
	clrf	BSR		; Point Bank 0
	btfss	Flags,TrigpH	; Was Trigger pin H?
	goto	$+3		;  No, goto slope-rising trigger
	bcf	PORTdbg,Trig	;  Yes, set slope-falling trigger
	goto	$+2		;    and skip next line
	bsf	PORTdbg,Trig	; Slope-rising trigger
;
	Mic2	2		; Trigger for 4 micro seconds 
	movlw	TrigpV		; Set trigger pin value to W
	xorwf	PORTdbg,F	; Stop trigger (H/L to L/H)
;
	btfss	Flags,TrigpIn	; Was Trig pin input before debug?
	goto	$+10		;  No, skip the following 9 steps
;
	BANKSEL	TRISdbg		; Select TRISdbg bank
	bsf	TRISdbg,Trig	; Set Trig-pin input
	clrf	BSR		; Select Bank 0
	btfsc	Flags,TrigpH	; Was Trigger pin L?
	goto	$+4		;  Yes, skip the followings
	BANKSEL	WPUdbg		;  No,Bank select for WPUdbg
	bsf	WPUdbg,Trig	;     Weak Pull-up for input pin
	clrf	BSR		;     Re-select Bank 0
	goto	$+7		;     skip next 6 steps for output
;
	btfss	Flags,TrigpH	; Was Trigger pin H?
	goto	$+3		;   No, skip next 2 steps
	bsf	PORTdbg,Trig	;   Yes, Set trig pin H
	goto	$+2		;      and skip next line
	bcf	PORTdbg,Trig	; Set trig pin L
;
	bcf	Flags,TrigpH	; Clear flags for trigger
	bcf	Flags,TrigpIn	; as timing after PORTdbg changed
	endm
;
Udebug	macro 	Addr
	movf	Addr,W
	call	Udbgr
	endm
;
Uabend	macro	abn		; User Abnormal-end number
	movlw	abn
	goto	Uabendr
	endm
;
Ublink	macro	bno		; Blink LED for specified times
	movlw	bno
	call	Ublinkr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Logic macros				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Comparison macros		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Compare A w/ B and branch to Smaller, Equal, or Larger(next statm't line) 
CabSEL	macro	Aadr,Badr,Smladr,Eqladr
	movf	Badr,W		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	Smladr		; 	If A<B goto Smaladr
	btfsc	STATUS,Z	; A=B? No skip next
	goto	Eqladr		;	If A=B goto Eqladr
;				; 	If A>B then next (here)
	endm
;
;	Compare A w/ B and branch to Smaller or Others (Equal or Larger) 
CabSO	macro	Aadr,Badr,Smladr
	movf	Badr,W		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	Smladr		; 	If A<B goto Smaladr
;				; 	If A≧B then next (here)
	endm
;
;	Compare A w/ B and branch to Equal, or Other(next statm't line) 
CabEO	macro	Aadr,Badr,Eqladr
	movf	Badr,W		; W=B
	subwf	Aadr,W		; W=A-B
	btfsc	STATUS,Z	; A=B? 
	goto	Eqladr		;	If A=B goto Eqljmp
;				; 	else next (here)
	endm
;
;	Compare A w/ literal and branch to Smaller, Equal, or Larger(next statm't) 
CalSEL	macro	Aadr,Blit,Smladr,Eqladr
	movlw	Blit		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	Smladr		; 	If A<B goto Smaladr
	btfsc	STATUS,Z	; A=B? No skip next
	goto	Eqladr		;	If A=B goto Eqladr
;				; 	If A>B then next (here)
	endm
;
;	Compare A w/ literal and branch to Smaller, or Others (Equal or Larger) 
CalSO	macro	Aadr,Blit,Smladr
	movlw	Blit		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	Smladr		; 	If A<B goto Smaladr
;				; 	If A≧B then next (here)
	endm
;
;	Compare A w/ literal and branch to Equal, or Others 
CalEO	macro	Aadr,Blit,Eqladr
	movlw	Blit		; W=B
	subwf	Aadr,W		; W=A-B
	btfsc	STATUS,Z	; A=B? No skip next
	goto	Eqladr		;	If A=B goto Eqladr
;				; 	else next (here)
	endm
;
;	Compare A w/ literal and branch to Large, or Others
CalLO	macro	Aadr,Blit,Ladr
	movlw	Blit		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	$+3		; 	If A<B goto Others
	btfss	STATUS,Z	; A=B? Yes skip next
	goto	Ladr		;	If A>B goto Laddr
;				; 	If A≦B then next (here)
	endm
;
;	Compare A with Literal and branch to Un-equal, or Others
CalUO	macro	Aadr,Blit,Uneqladr
	movlw	Blit		; W=B literal
	subwf	Aadr,W		; W=A-B	
	btfss	STATUS,Z	; A=B?
	goto	Uneqladr	; No, goto unequal
;				;	If yes then next
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Files and Equations					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Files				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	cblock	H'20'
;
; For Application support
	ModeN		; Mode to point BCD mem addr of RTC
	T7Chr		; 7 segment character to show ModeN
	RvMax		; Max number of RTC data in ModeN
	RvMin		; Minimum num of RTC data in ModeN
	Byteh		; Work area as high byte of a word
	Bytel		; Work area as low byte of a word
	Sbyte		; Byte to be sent/received by devices
	Bytectr		; Byte counter for device processes
	Bitctr		; Bit counter for device processes 
;
; For RTC
	RTCmem		; RTC memory address
	BCDsec		; RTC memory 0 = Second BCD	00-59
	BCDmin		; RTC memory 1 = Min BCD	00-59
	BCDhr		; RTC memory 2 = Hour BCD	00-23
	BCDdow		; RTC memory 3 = Day of Week 	01-07
	BCDday		; RTC memory 4 = Day BCD	01-31
	BCDmon		; RTC memory 5 = Month BCD 	01-12
	BCDyr		; RTC memory 6 = Year yy BCD 	00-99
;
; For I2C protocols
	I2Cadr		; Destinated slave I2C address
	DataLen		; Number of bytes to be sent/received
;
; For TM1637 4 digit display
;	Sbyte		; Byte to send, shared with I2C
	Dig1000		; Digit 1000
	Dig100		; Digit 100
	Dig10		; Digit 10
	Dig1		; Digit 1
;
; For DHT11 input data area
	DHTrh		; Relative humidity integer
	DHTrhd		; Relative humidity digit under 1
	DHTt		; Temperature integer
	DHTtd		; Temperature digit under 1
	DHTcks		; Checksum
;
; Areas for time consuming subroutines
 	Mic25c
	Mic50c
	Millic
	Mil100c
	Secsc
	Minsc
;
; Areas for debugging routines
	DmpA 		; Display byte area
	Dmp8C 		; Bit counter for loop (Initial Dmp8V =8)
	BlinkC 		; Counter for blinking (set for Debugb or Abendb)
	Abendn		; Abend number
	Ucpc		; User Check point chr to trace
	Flags		; Flags mainly for debug
;
	endc
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Equations			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For PORTA		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;swSet	equ	0	; PORTsw Set switch
;swTime	equ	1	; PORTsw Time switch
;LED	equ	1	; PORTdbg LED  for debugging
;Trig	equ	1	; PORTdbg Trig for debugging
;Dio	equ	2	; PORTtm TM16xx DIO
;swMode	equ	3	; PORTsw Mode Switch
;Dl	equ	4	; PORTI2C SDA
;Cl	equ	5	; PORTI2C SCL
;Clk	equ	5	; PORTtm TM16xx CLK
;DHT11	equ	5	; PORTdht DHT11 one wire signal
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTI2C		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTI2C equ	PORTA	; 
Dl	equ	4	; I2C Data line = SDA
Cl	equ	5	; I2C Clock line = SCL
LATI2C	equ	LATA	; LATch for I2C port
TRISI2C equ	TRISA	; TRIS reg for I2C
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTtm		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTtm	equ	PORTA	; 
Dio	equ	2	; TM16xx Data line = DIO
Clk	equ	5	; TM16xx Clock line = CLK
LATtm	equ	LATA	; Latch for tm port
TRIStm	equ	TRISA	; TRIS for tm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTdht		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdht	equ	PORTA	; 
DHT11	equ	5	; DHT11 one-wire signal
LATdht	equ	LATA	; Latch for dht port
TRISdht	equ	TRISA	; TRIS for dht
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTsw		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTsw	equ	PORTA
swSet	equ	0	; Set switch input pulled-up
swTime	equ	1	; Time switch input pulled-up
swMode	equ	3	; Mode switch input ulled-up
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTdbg		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdbg	equ	PORTA
TRISdbg	equ	TRISA
WPUdbg	equ	WPUA
LED	equ	1	; LED when debugging
Trig	equ	1	; Trigger for DSO when debugging
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Values & Characters	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Equations for RTC DS3231 
Clkt	equ	1	; Clock-time/4 multiplier of 2.5μS
Clkq	equ	1	; Quick time within clock  2μS

DS3231	equ	H'D0'	; RTC DS3231 address (to write)
RTCdata equ	BCDsec	; RTC memory data top
ModeMax	equ	6	; RTC data pointer ModeN max
ModeMin	equ	0	; RTC data pointer ModeN minimum
;
secMax	equ	H'59'
secMin	equ	0
;
minMax	equ	H'59'
minMin	equ	0
;
hrMax	equ	H'23'
hrMin	equ	0
;
dowMax	equ	H'07'
dowMin	equ	H'01'
;
dayMax	equ	H'31'
dayMin	equ	H'01'
;
monMax	equ	H'12'
monMin	equ	H'01'
;
Modeyr	equ	6
yrMax	equ	H'99'
yrMin	equ	0
;
; 7 segment LED characters for TM1637
Ch7Y	equ	H'6E'
Ch7Eq	equ	H'48'
Ch7M	equ	H'37'
Ch7d	equ	H'5E'
Ch7w	equ	H'1C'
Ch7h	equ	H'74'
Ch7m	equ	H'54'
Ch7S 	equ	H'6D'
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For Debug		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdbg	equ	PORTA	;
Trig	equ	1	; DSO trigger pulse port for test use
TrigpV	equ	B'00000010' ; = 2^Trig
;
Dmp8V	equ	D'8'	; Debug display bit counter initial value
Debugb	equ	D'8'	; number of blinkings to show Debugging
Abendb	equ	D'25'	; number of blinkings to notify Abend
; Flags byte
LEDpH	equ	0	; LED pin was High before debug use
LEDpIn	equ	1	; LED pin was input before debug use
TrigpIn	equ	2	; Trigger pin was input before debug use
TrigpH	equ	3	; Trigger pin was High before debug use
;
	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Initializing						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	org	0
	goto	Startp	; Go to start entry
	org	4	; This is Interrupt entry
	retfie		;
;
Startp	DEVset		; Define ports
	clrf	FSR0H	; Clear FSR0H forever
	clrf	FSR1H	; Clear FSR1H forever
	clrf	Flags	; Clear all bits of Flags
;
	Milli	D'50'	; Wait devices stabilized
	call	IniTMr	; Set-up TM1638 display
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Main program loop					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mainp	call	RTCrdr	; Read clock
	call	Dsptime	; Display time
	Secs	1	; for 1 second
;
	btfss	PORTsw,swSet ; Set switch  when time displayed ?
	call	RTCupdr
	Secs	1	; Time for another second
;
	call	DHTrd	; Read DHT11
	call	DspCT	; Display celsium degree
	Secs	2	; for 2 seconds
	call	DspHT	; Disuplay relative humidity
	Secs	2	; for 2 seconds
;
	goto	Mainp	; Contilue infinite loop
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	RTC updating routine					;
;		Display value for current ModeN			;
;		If swMode pushed, display next unit to update	;
;		If swTime pushed, increase value and display it	;
;		If swSet pushed,  update RTC (RTCwtr)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RTCupdr	btfss	PORTsw,swSet ; swSet still on?
	goto	$-1	; then wait for off
 	movlw 	Modeyr	; set initial mode year
	movwf	ModeN
;
RTCudm	movf	ModeN,W	; Large loop on ModeN
	brw
	goto	Mdsecr	; case ModeN=0 
	goto	Mdminr	; case ModeN=1 
	goto	Mdhrr	; case ModeN=2 
	goto	Mddowr	; case ModeN=3 
	goto 	Mddayr	; case ModeN=4 
	goto	Mdmonr	; case ModeN=5 
	goto	Mdyrr	; case ModeN=6 
;
; Macro used within  this section
Mdsetup macro	T7c,Rmax,Rmin
	movlw	T7c
	movwf	T7Chr
	movlw	Rmax
	movwf	RvMax
	movlw	Rmin
	movwf	RvMin
	goto	Mdnext
	endm
;
; set up environment for update and display
Mdsecr	Mdsetup	Ch7S,secMax,secMin
Mdminr	Mdsetup	Ch7m,minMax,minMin
Mdhrr	Mdsetup	Ch7h,hrMax,hrMin
Mddowr	Mdsetup	Ch7w,dowMax,dowMin
Mddayr	Mdsetup	Ch7d,dayMax,dayMin
Mdmonr	Mdsetup	Ch7M,monMax,monMin
Mdyrr	Mdsetup	Ch7Y,yrMax,yrMin
;
Mdnext 	call	Rdspr		; Display X=nn
	btfss	PORTsw,swTime	; Is Time pushed ?
	goto	Rinc		;  Yes, goto  Rinc
	btfss	PORTsw,swMode	; Is Mode pushed ?
	goto	Rnxtm		;  Yes, goto Rnxtm
	btfss	PORTsw,swSet	; Is Set pushed ?
	goto	Rwrite		;  Yes, goto Rwrite
	goto	Mdnext		; If no button, small loop for button
;
; 	Time pushed here
Rinc	call	Rincr		; Increase value in that mode
	Mil100	2		; 0.2 sec for increasing value
	goto	Mdnext		; return to loop for button
;
;	Mode pushed here
Rnxtm	btfss	PORTsw,swMode	; Mode button still pushed
	goto	$-1		; then wait for release
	CalEO	ModeN,0,Msmax	; If current ModeN=0 goto Msmax
	decf	ModeN,F		; set next mode
Rnxtme	goto	RTCudm		; and return to large loop for mode
;
Msmax	movlw	Modeyr		; get maximum mode
	movwf	ModeN		; and set it
	goto	RTCudm		; and return to large loop for mode
;	
Rwrite	btfss	PORTsw,swSet	; Set button still on?
	goto	$-1		; then wait for release
	call	RTCwtr		; and write to RTC
;
	return			; exit from RTC updating routine
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Display value pointed by ModeN	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Rdspr	movf	T7Chr,W
	movwf	Dig1000
	movlw	Ch7Eq
	movwf	Dig100
;
	movlw	BCDsec	; Point BCD area top
	movwf	FSR1L		; set it to FSR1L
	movf	ModeN,W	; Get Mode number
	addwf	FSR1L,F	; Point current BCD area	
	swapf	INDF1,W	; get high nibble to low in W
	call	TMser	; clear high nibble & edit
	movwf	Dig10	; set it to Dig10
	movf	INDF1,W	; get low nibble to low
	call	TMser	; clear high nibble to low
	movwf	Dig1	; set it to Dig1
;
	call	TMdspr
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Increase value pointed by ModeN	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Rincr	movlw	BCDsec	; Point BCD area top
	movwf	FSR1L	; set it to pointer
	movf	ModeN,W	; get ModeN to add
	addwf	FSR1L,F	; Point current BCD area
;
	CabSO	INDF1,RvMax,Rigo ;If smaller than max, goto Rigo
	movf	RvMin,W	; Else, get minimum value
	movwf	INDF1	; and set it to INDF1 (BCD area)
	return
;
Rigo	movf	INDF1,W	; Get BCD value
	andlw	H'0F'	; clear left nibble
	movwf	Bytel	; and set to Bytel
	swapf	INDF1,W	; Get BCD nibbles swapped
	andlw	H'0F'	; clear left nibble
	movwf	Byteh	; and set to Byteh
;
	incf	Bytel,F	; Bytel+1
	CalSO	Bytel,D'10',Rie ; If Bytel≦9,goto Rie
	movlw	D'16'	; Else increase digit 10
	addwf	INDF1,F	;
	movlw	H'F0'	; and clear digit 1
	andwf	INDF1,F
	return

Rie	incf	INDF1,F	; Increase BCD value
	return		; and return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Displaying Time routine					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Dsptime movf	BCDmin,W ; get minute dig 1
	call	TMser	; edit bit 0-3 value to 7 seg char
	movwf	Dig1	; set it to Digit 1
;	
	swapf	BCDmin,W ; get minutes dig 10
	call	TMser	; edit bit 0-3 value to 7 seg char
	movwf	Dig10	; set it to Digit 10
;
	movf	BCDhr,W ; get hour dig 1
	call	TMser	; edit bit 0-3 value to 7 seg char
	movwf	Dig100	; set it to Digit 100
	bsf	Dig100,7 ; set colon after Digit 100
;
	swapf	BCDhr,W ; get hour dig 10
	call	TMser	; edit bit 0-3 value to 7 seg char
	movwf	Dig1000	; set it to Digit 1000
;
	call	TMdspr	; show them to TM1637
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	RTC reading routine	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RTCrdr	clrf	RTCmem	; set RTC memory addr 0
	I2Csnd DS3231,RTCmem,1 ; send it to DS3231
	Milli	1	; wait for readiness (unnecessary?)
	I2Crcv DS3231,RTCdata,7	; receive 7 byte data from DS3231
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	RTC writing routine	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RTCwtr	clrf	RTCmem	; Set memory address zero
	I2Csnd	DS3231,RTCmem,8 ; Send data, with memory addr on top
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	DHT11 Reading routine				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DHTrd	movlw	5	; Set # of DHT bytes 5
	movwf	Bytectr	;  to Bytectr
;
	movlw	DHTrh 	; Point top of DHT data input
	movwf	FSR0L	; Set it to INDF0
;
;	Start signal
	bcf	PORTdht,DHT11	; Set DHT-line L
;
	Milli	D'20'	; for 20mS
	bsf	PORTdht,DHT11	; Set DHT-line H
	Mic2	D'10'	; for 20 micro sec
;
;Header checking from DHT11
	BANKSEL	TRISdht	; Point tris bank
	bsf	TRISdht,DHT11	; Set TRIS-dht input
	clrf	BSR	; Point bank 0
	Mic		; wait 1 micro sec for ready
;
	btfsc	PORTdht,DHT11	; Wait response 1/2 from DHT L
	goto	$-1	;  until L
	Mic5	D'13'	; Wait 65 within 80 micS
;
	btfss	PORTdht,DHT11	; Wait response 2/2 from DHT H
	goto	$-1	;  until H
	Mic5	D'13'	; Wait 65 within 80 micS
;
	btfsc	PORTdht,DHT11 ; Wait Start (L 50 mic sec) from DHT
	goto	$-1
	Mic5	D'6'	; When L, wait for 30 mic sec
;
; Read each byte from DHT11
DHbc	movlw	8	; Set # of bits = 8 in a byte of DHT
	movwf	Bitctr	;  to Bitctr
;
DHbcl	btfss	PORTdht,DHT11	; Wait Data signal H from DHT
	goto	$-1
	Mic50	1	; When H (1=70,0=27 micS), wait 50 micS
;	Trigger		; *** for timing-validation use only ***
;
	btfss	PORTdht,DHT11	; If still on then data is one, else zero
	goto	DHszero
;
DHsone	bsf	Sbyte,0	; Set 1 to Sbyte bit 0
	Mic5	D'4'	; Consume residual H 20 mic S of "1"
	goto	DHeck	; Goto Check end of one byte
;
DHszero	bcf	Sbyte,0	; Set zero to Sbyte bit 0
DHeck	decfsz	Bitctr,F ; One byte (8 bits) end ?
	goto	DHnxtbi	; No, goto continue bit proc
;
DHbye	movf	Sbyte,W	; If one byte end, here
	movwf	INDF0	; Set data to DHTdata entry
	incf	FSR0L,F	; Point next entry of DHTdata
;
	decfsz	Bytectr,F ; All bytes end?
	goto	DHbc	; If not end of bytes, next byte
	goto	DHcksr	; If end, checksum validation
;
DHnxtbi	rlf	Sbyte,F	; Bit continues. Shift left.
	goto	DHbcl	; and next bit checking
;
DHcksr	equ	$	; Checksum validation, if needed
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;	
;  End of receive		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Change data line mode to ooutput
;	Wait the last space ending
	Mic2	D'40'		; Residual 28 mic sec space + alpha 
;				; + 50 mic sec until pull-down end
	BANKSEL	LATdht		; Data line high when output mode
	bsf	LATdht,DHT11	; Lat-DHT11 high
	BANKSEL	TRISdht		; Select tris bank
	bcf	TRISdht,DHT11	; Set DHT11 output mode
	clrf	BSR		; Select bank 0
;
	Mic			; Wait one micro Sec
	bsf	PORTdht,DHT11	; Signal high
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Display Celsius to TM1637	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DspCT	clrf	Dig1000	; Clear Dig1000
	movf	DHTt,W	; Get DHTt
	movwf	Dig100	;  set it to Dig100
;
DspCTc	CalSO	Dig100,D'10',CTnext
	incf	Dig1000,F ; carry over to 1000
	movlw	D'10'	; and subtract 10
	subwf	Dig100,F ; from Dig100
	goto 	DspCTc	; repeat check
;
CTnext	CalEO	Dig1000,0,CTnxtg ; IF top zero goto CTnxtg
	movf	Dig1000,W
	call	TMser	; Convert Dig1000 to 7 seg pattern
	movwf	Dig1000 
;
CTnxtg	movf	Dig100,W
	call	TMser	; Convert Dig100 to 7seg pattern
	movwf	Dig100
;
	movlw	H'63'	; Char Degree
	movwf	Dig10
	movlw	H'39'	; Char C
	movwf	Dig1
;
	call	TMdspr
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Display Humidity to TM1637	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DspHT	clrf	Dig1000	; Clear Dig1000
	movf	DHTrh,W	; Get DHTrh
	movwf	Dig100	;  set it to Dig100
;
DspHTc	CalSO	Dig100,D'10',HTnext
	incf	Dig1000,F ; carry over to 1000
	movlw	D'10'	; and subtract 10
	subwf	Dig100,F ; from Dig100
	goto 	DspHTc	; repeat check
;
HTnext	CalEO	Dig1000,0,HTnxtg ; IF top zero goto HTnxtg
	movf	Dig1000,W
	call	TMser	; Convert Dig1000 to 7 seg pattern
	movwf	Dig1000 
;
HTnxtg	movf	Dig100,W
	call	TMser	; Convert Dig100 to 7seg pattern
	movwf	Dig100
;
	movlw	H'63'	; Char % 1/2
	movwf	Dig10
	movlw	H'5C'	; Char % 2/2
	movwf	Dig1
;
	call	TMdspr
	return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C Send byte-string				;
;         when called,					;
;		I2C device adr in I2Cadr byte		;
;		Data length in DataLen			;
;		Data Addr in FSR0L (INDF0)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Csndr	movf	I2Cadr,W
	movwf	Sbyte
;	rlf	Sbyte,F	; shift leftt (if adr for Arduino)
	bcf	Sbyte,0	; indicate write
;
	I2Cstat		; Start I2C
	call	Sendr

Sdata	movf	INDF0,W	; Get a sending character
	movwf	Sbyte	; Set it to Sbyte
	call	Sendr	; Send it to the device
;
	incf	FSR0L,F	; point next char
	decfsz	DataLen,F ; Have all chars sent ?
	goto	Sdata		; No, loop
;
; Stop signal and return
	I2Cstop
	return	
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	I2C Receive byte-string				;
;         when called,					;
;		I2C device adr in I2Cadr byte		;
;		Data length in DataLen			;
;		Data Addr in FSR0L (INDF0)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Crcvr	equ	$
	I2Cstat		; Start I2C
	movf	I2Cadr,W ; Get I2C dev addr
	movwf	Sbyte	; Set it into Sbyte
;	rlf	Sbyte,F	; Shift left (if adr for Arduino)
	bsf	Sbyte,0	; indicate read
	call	Sendr	; Send slave addr to read
;
I2Crbyt	equ	$
	BANKSEL	TRISI2C	;
	bsf	TRISI2C,Dl ; Set Dline input mode
	clrf	BSR	; Point Bank 0
;
	movlw	8	; Bits in a reciving byte
	movwf	Bitctr	; into loop counter 
I2Crblp	Mic2p5	2*Clkt	; Timing before raising clock
	bsf	PORTI2C,Cl ; Set clock high
	Mic2p5	Clkt	; Timing after raising clock
;
	btfsc	PORTI2C,Dl
	goto	Rbith
;	goto	Rbitl
;
Rbitl	bcf	Sbyte,0	; Clear bit 0
	goto	Rbitnxt	; and goto next
;
Rbith	bsf	Sbyte,0	; Set bit 0
Rbitnxt	Mic2	Clkq	; Timing after checking
	bcf	PORTI2C,Cl ; Set clock low
;	Mic2p5	Clkt	; Timing after clock falling
	decfsz	Bitctr,F ; Still bits?
	goto	Rbcont	; Yes, continue
	goto	Rbend	; No, end of one byte
;
Rbcont	rlf	Sbyte,F	; Shit left
	goto	I2Crblp	; and goto loop
;
Rbend	movf	Sbyte,W	; Get received byte
	movwf	INDF0	; Set it to INDF0
	incf	FSR0L,F	; increase index
	decfsz	DataLen,F ; DataLen-1
	goto	Sackr	; Send Ack and continue
	goto	Snackr	; Send Nack and stop
;
;Set Dl output mode and send Ack to conitinue
Sackr	equ	$
	BANKSEL	LATI2C	; Data line high when output mode
	bsf	LATI2C,Dl ; Lat-Dl on
	BANKSEL	TRISI2C	; 
	bcf	TRISI2C,Dl ; Set Dl output mode
	clrf	BSR	; Point Bank 0
;
	bcf	PORTI2C,Dl ; Send Ack
	Mic2	Clkq	; Timing after clock falling
	bsf	PORTI2C,Cl ; Show Ack
	Mic2p5	2*Clkt
	bcf	PORTI2C,Cl ; Set clock low
	Mic2	Clkq
	bsf	PORTI2C,Dl ; Return Dline to high
	goto	I2Crbyt		; Next byte process
;
;Set Dl output mode and send Nack
Snackr	equ	$
	BANKSEL	LATI2C	; Data line high when output mode
	bsf	LATI2C,Dl ; Lat-Dl on
	BANKSEL	TRISI2C	; 
	bcf	TRISI2C,Dl ; Set Dl output mode
	clrf	BSR	; Point Bank 0
;
	bsf	PORTI2C,Dl ; Send Nack
	Mic2	Clkq	; Timing after clock falling
	bsf	PORTI2C,Cl ; Show Nack
	Mic2p5	2*Clkt
	bcf	PORTI2C,Cl	; Set clock low
	Mic2p5	Clkt
	I2Cstop		; and stop I2C once
	Mic5	D'6'	; for 30 micro sec
;
	I2Cstat		; then start I2C again
	movf	I2Cadr,W ; Get I2C dev addr
	movwf	Sbyte	; Set it into Sbyte
;	rlf	Sbyte,F	; Shift left (if adr for Arduino)
	bcf	Sbyte,0	; indicate write
	call	Sendr	; Send it
;
	I2Cstop
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C Start signal routine 			;
;	   	when dataline mode is output		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Cstar	bcf	PORTI2C,Dl ; set SDA low
	Mic2	Clkq
	bcf	PORTI2C,Cl ; set SCL low
	Mic2p5	2*Clkt
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C Stop signal routine 			;
;	   	when dataline mode is output		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Cstpr	bcf	PORTI2C,Cl ; set SCL low
	Mic2	Clkq
	bcf	PORTI2C,Dl ; set SDA low
	Mic2p5	2*Clkt
	bsf	PORTI2C,Cl ; Clock line rasing
	Mic2p5	Clkt
	bsf	PORTI2C,Dl ; Data line rasing
	Mic2	Clkq
	return	
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C One-byte sending routine 			;
;	   	with data in Sbyte			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Sendr	movlw	8	; Set bit count in a byte
	movwf	Bitctr	; into loop counter 
;
Sloop	btfsc	Sbyte,7	; Check top bit of Sbyte
	goto	Slpdh	; 
	bcf	PORTI2C,Dl ; If bit low then Data L
	goto	Slpnxt
Slpdh	bsf	PORTI2C,Dl ; If bit high then Data H
;
Slpnxt	Mic2	Clkq
	bsf	PORTI2C,Cl ; Show bit
	Mic2p5	2*Clkt
	bcf	PORTI2C,Cl ;
	Mic2	Clkq
;
	rlf	Sbyte,F	; Shift left 
	decfsz	Bitctr,F ; All bits done?
	goto	Sloop	; No, loop within a byte
;	
;Receive Ack 		
; set data line input mode
	BANKSEL	TRISI2C	; 
	bsf	TRISI2C,Dl ; Set Dline input mode
	clrf	BSR	; Point Bank 0
;
	Mic2	Clkq	; wait for Ack timing
	bsf	PORTI2C,Cl ; Clock H for Acq confirmation
	Mic2p5	2*Clkt	; 
	bcf	PORTI2C,Cl ; Cl L for Dl release by slave
	Mic2p5	2*Clkt
;
; Set Data line output
	BANKSEL	LATI2C	; Data line high when output mode
	bsf	LATI2C,Dl ; Lat-Dl on
	BANKSEL	TRISI2C	; 
	bcf	TRISI2C,Dl ; Set Dl output mode
	clrf	BSR	; Point Bank 0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 support routines 			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	TM1637 display brightness setting	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IniTMr	equ	$
; TM brightness initial setting	;
;   H'88'+ duty ratio:
;       H'00'=1/16, H'01'=2/16, H'02'=4/16, H'03'=10/16
;	H'04'=11/16, H'05'=12/16, H'06'=13/16, H'07'=14/16
	movlw	H'88'+H'02'	; Initial brightness value
	movwf	Sbyte		; Set to Sbyte
	TMstart
	TMsendB Sbyte
	TMstop
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TM 1637 Char to 7 segment editing routine	;
;	replaceubg Char in W with 7 seg in W	;
;	  7 seg format is B'xgfe-dcba'		;
;	  if colon required,IOR H'80' to Dig100	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMser	andlw	H'0F'	; Prevent wild branch
	brw
Case0	retlw	H'3F'
Case1	retlw	H'06'
Case2	retlw	H'5B'
Case3	retlw	H'4F'
Case4	retlw	H'66'
Case5	retlw	H'6D'
Case6	retlw	H'7D'
Case7	retlw	H'07'
Case8	retlw	H'7F'
Case9	retlw	H'6F'
CaseA	retlw	H'77'	; A
CaseB	retlw	H'7C'	; b
CaseC	retlw	H'39'	; C
CaseD	retlw	H'5E'	; d
CaseE	retlw	H'79'	; E
CaseF	retlw	H'71'	; F
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 4digits displaying routine	;
;	  	using Dig1000 to Dig1		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMdspr	movlw	Dig1000	; Point Byte0 address
	movwf	FSR0L	; set it to use INDF0
	movlw	4	; Set four digits
	movwf	Bytectr	; to Byte counter
;
	TMstart	
	TMsendL	H'40'	; 
	TMstop 		; 
;
	TMstart
	TMsendL	H'C0'	; Set addr zero of TM
;
TMdspl	movf	INDF0,W	; Get byte contents
	movwf	Sbyte	; Set them to Sbyte
	TMsendB	Sbyte	; 
	incf	FSR0L,F	; point next byte
	decfsz	Bytectr,F ; Check if all done
	goto	TMdspl	; No. loop
;
	TMstop		; When all sent, Stop
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 Start sending routine 		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMstrtr	nop
	BANKSEL	TRIStm
	bsf	LATtm,Dio ; Dio high beforhand
	bcf	TRIStm,Dio ; Set Dio output
	clrf	BSR	; return to Bank0
;
	bsf	PORTtm,Dio ; Dio high
	Mic2	Clkq
	bsf	PORTtm,Clk ; Clk high
	Mic2	Clkq
	bcf	PORTtm,Dio ; Dio low
	Mic2	Clkq
	bcf	PORTtm,Clk ; Clk low
	Mic2	Clkq	
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 Stop sending routine 		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMstopr	nop
	BANKSEL	TRIStm
	bcf	LATtm,Dio ; Dio low beforehand
	bcf	TRIStm,Dio ; Set Dio output
	clrf	BSR	; Return to Bank0
;
	Mic2	Clkq
	bsf	PORTtm,Clk ; Clk high
	Mic2	Clkq
	bsf	PORTtm,Dio ; Data high
	Mic2p5	D'10'
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	TM1637 Byte sending routine 		;
;		called by TMsendB with Sbyte or	;
;		TMsendL	with value in W reg	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TMsndLr	movwf	Sbyte	; Set sending byte
;
TMsndBr	movlw	8	; Set bit count
	movwf	Bitctr	; into loop counter
;
Tloop	btfsc	Sbyte,0	; Check LSb for little-endian-
	goto	Tlpdh	; 
	bcf	PORTtm,Dio ; If bit low then Data L
	goto	Tlpnxt
;
Tlpdh	bsf	PORTtm,Dio ; If bit high then Data H
Tlpnxt	Mic2	Clkq; 
	bsf	PORTtm,Clk ; Clock high
	Mic2p5	2*Clkt	; (5 micro sec if standard)
	bcf	PORTtm,Clk ; Clock low	
	Mic2	Clkq
	rrf	Sbyte,F	; Shift right for little-endian 
	decfsz	Bitctr,F
	goto	Tloop
;	
;	Receiving Ack 	;
	BANKSEL	TRIStm
	bsf	TRIStm,Dio ; Set Dio input mode
	clrf	BSR	; return to Bank0
	Mic2p5	Clkt	; Wait for Ack=low
	bsf	PORTtm,Clk ; Clk high for Ack confirmation
	Mic2p5	2*Clkt	; (5 micro sec if standard)
	bcf	PORTtm,Clk ; Clk L for Data release to H by Slave
	Mic2p5	Clkt	; Wait for Dio set H 
;
	BANKSEL	TRIStm
	bsf	LATtm,Dio ; Dio high beforhand
	bcf	TRIStm,Dio ; Set Dio output
	clrf	BSR	; return to Bank0
	Mic2p5	5	; Enough time for TM1637 after Ack
	return		; 
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Timing subrooutines for general purposes		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.0 micro S x n	(Mic2)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic2r	movwf	Mic25c		; + Wset + call = 1 micro sec 
;
Mic2l	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic2li		; else go out (2nd time 1.75 mic sec)
	return		
;
Mic2li	goto	$+1		;            (2nd time 2.25 mic sec)
	nop			;	     (2nd time 2.5 mic sec)
	goto	Mic2l		; go back    (2nd time 3 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.5 micro S x n (Mic2p5)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic25r	movwf	Mic25c		; + Wset + call = 1 micro sec 
	nop			; 1.25 micro sec
;
Mic25l	nop			; 1.5 micro sec (2nd time 4 mic sec)
	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic25li		; else go out (2nd time 2.25 mic sec)
	return		
;
Mic25li	Mic			;	      (2nd time 3.25 mic sec)
	goto	Mic25l		; go back    (2nd time 3.75 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	50 Microseconds	x n	Mic50  	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Mic50r	movwf	Mic50c	 	; set how many 50 microsec (1 micro sec to here)
	nop			; 1.25 micro sec up to here
;
Mic50l	Mic2p5	D'19'		; + 47.5 = 48.75 mic sec (2nd time 98.75 mic sec)
	nop			; + 0.25 = 49 micro sec (2nd time 99 mic sec)
; 
	decfsz	Mic50c,F	; If exhausted then 1 mic S hereafter
	goto	Mic50li		; else go out (2nd time 49.75 mic sec)
	return
;
;
Mic50li	Mic			; 	  (2nd time 50.75 mic sec)
	goto	Mic50l		; go back (2nd time 51.25 mic sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Milliseconds x n	(Milli)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Millir	movwf	Millic 		; set how many 1 mil sec (1 mic S up to here)
	nop			; 1.25 micro sec
;
Millil	Mic50	D'19'		; + 50 mic x 19 = 951.25 mic S (2nd, 1951.25) 
	Mic2p5	D'19'		; + 47.5 mic = 998.75 micro S  (2nd, 1998.75)
	nop			; +0.25 mic = 999 micro sec    (2nd, 1999)
;
	decfsz	Millic,F	; If  exhausted then 1 micro sec hereafter
	goto	Millili		; else go out (2nd, 999.75 mic S)
	return
;
Millili	Mic			; 		(2nd time 1000.75 mic S)
	goto	Millil		; go back (2nd time 1001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	100 Milliseconds x n	(Mil100);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mil100r	movwf	Mil100c		;set how many 100 ms(1 micr sec up to here)
	nop			; 1.25 micro sec
;
Milhl	Milli	D'99'		;+1ms x 99 = 99001.25 micS (2nd,199001.25mic)
	Mic50	D'19'		; + 950 mic = 99951.25 micS(2nd.199951.25mic)
	Mic2p5	D'19'		; + 47.5 mic = 99998.75micS(2nd,199998.75mic)
	nop			; + 0.25 mic = 99999 mic S (2nd,199999 micS)
;
	decfsz	Mil100c,F	; If exhausted then 1 micro sec hereafter
	goto	Milhli		; else go out (2nd time, 99999.75 mic S)
	return
;
Milhli	Mic			;  	    	(2nd time, 100000.75 mic S)
	goto	Milhl		; 		(2nd time, 100001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Seconds x n	 (Secs)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Secsr	movwf	Secsc 		; set how many sec ( 1 mic sec up to here)
	nop			; 1.25 micro sec
;
Secsl	Mil100	D'9'		; 
	Milli	D'99'		; + 999 milli sec = 999001.25 micro sec
;
	Mic50	D'19'		; + 950 mic = 999951.25 micro sec
	Mic2p5	D'19'		; + 47.5 mic = 999998.75 micro sec
	nop			; + 0.25 mic = 999999 micro sec
;
	decfsz	Secsc,F		; If exhausted then 1 micro sec hereafter
	goto	Secsli		; else, go out 
	return
;
Secsli	Mic
	goto	Secsl		; (Second time, Sec + 1.25 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Minutes	x n	 (Mins)		;
;	 Overhead ignored, that is only	;
;	 751.25 Mic S even when 100 Min	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Minsr	movwf	Minsc		;set how many minutes from parameter
;
Minsl	Secs	D'60'		; 1 Seconds x 60
	decfsz	Minsc,F
	goto	Minsl
	return
;
	space
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; User debug subroutine		;
;	Show bit 7 to 0 	;
;	of specified byte	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Udbgr	clrf	BSR		; Set Bank=0
	movwf	DmpA		; move data to Dmpa
;
	btfss	PORTdbg,LED	; Check if LED=on (on=High)
	goto	UdbLoff		;  if LED=off(Low), skip saving process
	bsf	Flags,LEDpH	; Save LEDon status
	LEDoff			; and off LED
	Mil100	D'10'		; wait for a second in case LED has been on
;
UdbLoff	movlw	Dmp8V		; set counter 8
	movwf	Dmp8C		; to Dmp8C
;
Udblp	Ublink	Debugb		; Blink for Debug = 8 times
	btfsc	DmpA,7		; check top bit 7
	goto	UdbOn		; if on then to UdbOn
	goto	UdbOff		; if off then to UdbOff
;
UdbOn	LEDon
	Mil100	D'30'		;
	goto	Udbeck
;
UdbOff	LEDoff
	Mil100	D'30'		;	
;	goto	Udbeck
;
Udbeck	decfsz	Dmp8C,F
	goto	Udbnext
	goto	Udbend
;
Udbnext	rlf	DmpA,F
	goto	Udblp
;
Udbend	Ublink	Debugb		; end blinking and 
	Mil100	D'100'		; blank for 10 seconds to write down
;
	btfss	Flags,LEDpH	; Check if LED was on
	goto	Udbret		;  no, goto return
	LEDon			;  if it was on, then on again
	bcf	Flags,LEDpH	; Clear LED save flag
;
Udbret	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Blinking to show debug or abend	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Ublinkr	movwf	BlinkC
Ublinkl	LEDon			; LED on
	Milli	D'30'		; for 30ms
	LEDoff			; LED off
	Milli	D'200'		; for 200ms
;
	decfsz	BlinkC,F
	goto	Ublinkl
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Abend routine			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Uabendr	clrf	BSR
	movwf	Abendn			; Set Uabend number
;
	Ublink	Abendb			; Blink 25 times
	Udebug	Abendn			; Show Abend number
	goto	$
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	End of program						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	end

 

ピンがこれでいっぱいかといえばそんなことはありません。無理しない手がまだあります。

入出力をもっと追加するには例えば次のようにします。

もともとこの8ピンPICの仕様上RA3が単にデジタル入力しかできないので、これをModeボタンとして使わずSetボタンとして使うように入れ替えます。Setボタンが押された時(時刻設定モード)以外はTimeボタンとModeボタンは空いています!から(今回すでにTimeボタンをデバッグ用Trigger出力またはLED点灯に使えるようにプログラムして使っています)、自由に使えます。

同様に、時刻設定モード以外の通常時にいつも空いているModeボタンが自由に使えるようになるわけです。例えば通常時はアナログ入力ピンとして使えば、アナログ入力デバイスも読み込めます。照度計、ホール素子、マイク、風速計など、それこそ色々ありますね。もちろん出力ピンにして動くものなど繋いでもよいですが。

つまり時刻設定モードのときだけ入力ボタンにすればよいのであって、それ以外は空いているのと同じこと。

また、TM1637は、クロック信号(CLK)を与えない間は表示を保持します。ですので、表示データ送出時以外は単に2秒待たせるのではなく、データ信号(DIO)ピンが別用途に使えます。TM1637側でのプルアップを考慮した上で、ブザーをならすとかトランジスタ経由で何かを動かすとか、逆にデジタル入力ピン(プルアップ)として使っても問題ないはずです。

つまり、「もったいないばあさん」に言わせれば、まだ2つのピンが使える状態です。

これに充てる時間が次にできたときには、プログラムの解説よりも先にどれかを足してみようかとも。。そうすればインターフェイス満載の参照用プログラムになるかも。。え?部品取りのジャンク?・・^^; 

 

では今回はこの辺で。

 

©2019 Akira Tominaga, All rights reserved.