Top Page PIC 12F629 によるエレKey製作
自作工程のメモとして
2006.Jun.18


 別ページにあるQRPトランシーバEQT−1の頒布を受け、組み立て後にCalKeyer【1】を追加購入し組み付けましたが、久しぶりのパドル操作のため、感覚が「おかしい」と思い込み、自分でプログラムを組むことにしました。
 また、このために新しく本を2冊買いましたが、一番参考になったのは、『おもしろいPICマイコン −PIC12F675 を使いこなす−』でした。

 

 以下はその記録ですが、6年ぶりのアセンブラ言語と、以前使った PIC 16F84 とは違う8ピンのPICを使うために、大変な頭の体操になりました。
また、最初に書きましたパドル操作の感覚も、完成してみると「思い違い」だった気がしています。
せっかく覚え直したアセンブラですので、これ以外にも作ってみたい気がしています。

続編 cwbufkey を掲載しました。(2006.07.29)

1.テスト基板の用意

 いきなりアセンブラでプログラムし、動作させる腕も知識もありませんので、テスト基板を組み立てて実験することにしました。
手持ちのPCBや秋月から取り寄せた「ソケット」類と、関ハム2006 で入手したLED点滅キット(パーツを一つひとつそろえるのが面倒だったため)を200円で購入して、やっとの思いでくみ上げました。(最近、面倒くさく感じるようになってきており、ブレッドボードに組めばもっと早く取り掛かれたかも知れません。)

 

2.ゼロプレッシャーソケットを使う

 久しぶり(6年)のアセンブラなので、100回ぐらいはPICを抜き差しする必要があると考え、PICライターに使っている物と同種のソケットを用意し、丸ピンのICソケット上にゼロプレッシャーソケットを載せます。
どうせならと、28ピンをつけて、上で 16F84 の回路も組めるように余裕を残したつもりです。

↓ これは、LEDの点滅が動き出した頃の画像で、これ以降も少しずつ構成を変えています。

 

3.スピード調節の実験

 CWのスピード調節は、PICのポートひとつを使って実現しているため、コンデンサへの充電か放電で時間を割り出していることは想定できましたが、どんなコードを書けばその動きが実現するのかで迷いました。
残念ながら、オシロスコープは持っていません。
 また、周波数カウンタも無いため、簡易的な周波数カウンタが付いた秋月のP−16というマルチメーターも購入してしまいました。
その後、パソコン(PC)上で、オシロスコープの動きをするフリーソフトが見つかりましたのでそれを使い、やっと納得がいった次第です。

 最初は、GP2ポートをHighにする時間を、数十mSのデレイループを記述しプログラム上で確保しましたが、GP2ポートをHigh(出力)にした直後に入力に切り替えても、充分Highの状態が確保されていることが確認できました。

↓ 以下は、GP2ポートの波形です。
音源ボードを使っているため、交流専用となり、周波数が低くなると短形波では、信号がゼロ点に戻り測定できませんが、0.2mS ぐらいでは、Highレベルを保持しています。
ボリュームの操作でHighの時間が変わります。


↓ これは、パドルを接続して時定数を調節しているころの画像です。
LED出力部や、アース用の端子を追加しています。

 

4.開発環境

 開発用には、サクラエディタ、MPLAB V7.40、秋月PICプログラマーVer4を使いました。
エディタで修正して、MPLABでアセンブル(ビルド)し、書き込みを繰り返し、本当に100回くらいやったかもしれません。

 

 

6.ソースファイル

 プログラムは実験の域を出ないため、単機能に徹しています。
信号比は 1:3 の固定で、バグキーモードも付いていません。
昔、自分で作ったプログラムが解読できなかったため、コメントはしっかり入れたつもりです。
他の方も、ボリュームでのスピードコントロールを、色々な方法で実現させているようで、これと同じ方法や、コンデンサ充電用のHighレベル信号を別ポートから入れる方法や、AD変換ポート(同種の PIC 12F675 に有り)を使う方法もWeb上で公開されていました。
 そのどちらかの方法を真似れば早かったと思いますが、その他の部分を自分流に変更する時には、かえって手間取ってしまうと考え、一から(6年ぶり)書いてみました。
その意味では参考にならず、間違いがあるかも知れませんが、掲載しておきます。

シンプルなPICキーが必要になりこのバージョンを使おうとしましたが、パドルがもつれる??(ドットレバーを放すタイミング遅れ)ためドットの処理を調整しました。(2008/12/31)

(エディタ上でTAB間隔を8文字に設定し直しましたが、これでもコメント等の位置がずれています。)

--------------------------------------------------------------------

;
;PICマイコンによる、CWキーヤーの製作
;
;2006/06/18:(C)JE3JLB (最下部へコード埋め込みあり)
;2006/07/06:タブ位置を8文字に設定し直した。
;V1.01:2008/12/31:ダッシュの先頭から約1/3間は、パドルのドットを記憶しない。
;		ファイル名は、key628a.asm と変更する。


;CWコードの定数を確認
;PARISコードの1ワード(W)は、14+8+10+6+8+(4)=50ドット
;1分間に 5W( 25文字)送出するには、  250ドット/分
;			(4.2dot/Sec  240ms/dot)短点連続周波数= 2.1Hz
;1分間に10W( 50文字)送出するには、  500ドット/分
;			(8.3dot/Sec  120ms/dot)短点連続周波数= 4.2Hz
;1分間に20W(100文字)送出するには、1,000ドット/分
;			(16.7dot/Sec  60ms/dot)短点連続周波数= 8.3Hz
;1分間に30W(150文字)送出するには、1,500ドット/分
;			(25.0dot/Sec  40ms/dot)短点連続周波数=12.5Hz
;1分間に40W(200文字)送出するには、2,000ドット/分
;			(33.3dot/Sec  30ms/dot)短点連続周波数=16.7Hz
;
;
;
;◆回路構成の概略(全半角等幅文字)
;
;
;               PIC 12F629            3V  3V
;           3V  ┌──V──┐        │  │
;           │  │          │    100K│  │100K
;           └─│1        8│─┐    (R) (R)
;               │          │  E    │  │
;    CW出力 ──│2        7│────┴────(パドル:ドット)
;               │          │            │
;         NC:<--│3        6│──────┴──(パドル:ダッシュ)
;               │          │            10K
;         PU:-->│4        5│───┬──(R)-┐
;               │          │      (C)      (VR) 50K
;               └─────┘      │103     │
;                                   E        E
;
;     Eはアース、NC:は未接続、PU:はプルアップ。
;
;◆動作について
;SQUEEZE Key  二枚羽のパドルを持つキーヤー の、
;アイアンビック mode B の動作になるとは思いますが、明確には不明です。
;
;◆参考書籍
;中尾真治氏著  オーム社 (平成17年3月20日 第1版)
;おもしろいPICマイコン −PIC12F675 を使いこなす−
;
;固有定数のメモ:3474
;固有定数のメモ:3468
;
;
	LIST	P=12F629
	INCLUDE	P12F629.INC

CB = _CPD_OFF			;データEEPROMもプログラム・メモリも
CB &= _CP_OFF			;コード・プロテクトしない。
CB &= _BODEN_ON			;ブラウンアウト・リセットを使う。
CB &= _MCLRE_OFF		;MCLRリセットを使わない。
CB &= _PWRTE_ON			;パワーアップ・タイマを使う。
CB &= _WDT_OFF			;ウォッチドッグ・タイマを使わない。
CB &= _INTRC_OSC_NOCLKOUT ;内部発振器を使う。

	__CONFIG CB

	__IDLOCS H'0100'	;IDロケーションへ、バージョン1.00

	CBLOCK	H'20'

	GPIO_OUT		;GPIOポートの出力データ

	W_TEMP			;割込み処理用
	S_TEMP			;割込み処理用

	CCC			;カウンター
	CCC256			;カウンター上位

	PAD_MEMO		;パドルの状態をメモしておく(Hi アクティブ)
;				;bit1   1=ダッシュ が押された。
;				;bit0   1=ドット が押された。

	RUN_MEMO		;実行中の状態をメモ(Hi アクティブ)
;bit3	1=ダッシュ信号 0=ダッシュ後の間隔(1ドット分)
;bit2	1=ドット信号  0=ドット後の間隔(1ドット分)
;bit1   1=ダッシュ送出中
;bit0   1=ドット送出中
;

	ENDC

;
GPIO_MODE   EQU	B'00001011'
;				;GP0-1.3 をインプット  GP2.4-5 をアウトプット

DEF_INTCON	equ	B'10010000'	;割り込みの設定。
;bit7	GIE 割り込みを使用する=1
;bit6	PIE1 レジスタの割り込みを使わないので=0
;bit5	タイマ0オーバーフロー割り込みを使わないので=0
;bit4	INTピン入力エッジ割り込みを使うので=1
;bit3	◆◆GPIO入力変化割り込みを使わないので=0
;bit2	タイマ0オーバーフロー時=0
;bit1	GP2/INTピン入力信号のエッジがあったときだけ=1
;bit0	◆◆GPIOポートで入力データが変化したときだけ=1
;
PAD_DOT		EQU	O'0'		;パドル:ドットのビット位置。
PAD_DAS		EQU	O'1'		;パドル:ダッシュのビット位置。
CR_BIT		EQU	O'2'		;外部でボリュームをつけている端子
ST_BIT		EQU	O'4'		;ボリュームにHを出力して、
;					;その後Lowにする。
LED_BIT		EQU	O'5'		;LEDのビット

DOT_STA		EQU	O'2'
DAS_STA		EQU	O'3'

;
TIME_1		EQU	D'20'		;ドットの長さ
TIME_3		EQU	D'60'		;ダッシュの長さ(ドット×3倍)
;					;(例)1:3.4 にするなら、20 X 3.4 = 68
;					; を記述する。
CCC_DEF		equ	D'240'		;タイミングカウンター下位の初期値
;
;GP0=1 入力		パドル ドット
;GP1=1 入力		パドル ダッシュ
;GP2=1 出力		初期は出力で、1を出力指定しておく。
;			外部のボリュームを読み込む時は、瞬間入力に設定して
;			放電時間経過後のLowエッジ割り込みを待つ。
;GP3=1 入力		未使用
;GP4=0 出力		未使用
;GP5=0 出力		キー出力(ハイアクティブ)
;________________________


	org	H'0'			;電源onスタート
	goto	START_DEF

	org	4			;割り込み発生時の処理
;//////////////////////////////////////////////////////////////
	movwf	W_TEMP			;以下の4行は割込み処理前の定型処理。
	movf	STATUS,W		;
	clrf	STATUS			;
	movwf	S_TEMP			;
;
ISR_INT:
	btfss	INTCON,INTF		;INT入力エッジ割り込みか?
	goto 	ISR_INT_END		;それ以外なら、割り込みの終了へ行く。
;
	bsf	STATUS,RP0		;バンク 1 にする。
	bcf	TRISIO,CR_BIT		;GP2ビットを出力に戻す。
;					(High=コンデンサへ充電)
	bcf	STATUS,RP0		;バンク 0 にする。

;----------------------------充電のための時間稼ぎ(実質不要)

	nop
;	goto	$+1
;	goto	$+1
	nop

;----------------------------割込み発生の再スタート処理
	bsf	STATUS,RP0		;バンク 1 にする。
	bsf	TRISIO,CR_BIT		;GP2ビットを一時的に入力にする。
	bcf	STATUS,RP0		;バンク 0 にする。


;----------------------------◆◆ここに割込み処理を書く

	incfsz	CCC,F			;カウンター +1
	goto	ISR_INT_END
	incf	CCC256,F

	movlw	CCC_DEF
	movwf	CCC			;◆タイミングカウンター下位を初期セット
;
;----------------------------◆◆ここまで割込み処理を書く

ISR_INT_END:

	bcf	INTCON,INTF		;INTF 入力エッジ割込みフラグを、
					;プログラムでクリヤーして復帰する。

	movf	S_TEMP,W		;以下の4行は割込み復帰の定型処理。
	movwf	STATUS			;
	swapf	W_TEMP,F		;
	swapf	W_TEMP,W		;
	
	retfie

;////////////////////////////////////////////////////////////

MAIN_LOOP0:


	bcf	INTCON,INTF		;エッジ割込みフラグをクリヤー
;----------------------------		割込み発生のスタート処理
	bsf	STATUS,RP0		;バンク 1 にする。
	bsf	TRISIO,CR_BIT		;GP2ビットを一時的に入力にする。
	bcf	STATUS,RP0		;バンク 0 にする。


MAIN_LOOP:

;--------------------------		パドル入力の確認
PAD_CHECK0:
	btfsc	RUN_MEMO,PAD_DOT	;ドット送出中以外ならドットを確認
	goto	PAD_CHECK1		;ダッシュチェックへ
	btfsc	GPIO,PAD_DOT		;ドット側が押されたか?
	goto	PAD_CHECK1		;ダッシュチェックへ
	bsf	PAD_MEMO,PAD_DOT	;パドルメモへドットをセット
PAD_CHECK1:
	btfsc	RUN_MEMO,PAD_DAS	;ダッシュ送出中以外ならダッシュを確認
	goto	PAD_CHECK_E		;パドル確認終了へ
	btfsc	GPIO,PAD_DAS		;ダッシュ側が押されたか?
	goto	PAD_CHECK_E
	bsf	PAD_MEMO,PAD_DAS	;パドルメモへドットをセット
PAD_CHECK_E:
	goto	RUN_CHECK0

;-------------------------		実行中の処理を確認
RUN_CHECK0:
	btfss	RUN_MEMO,PAD_DOT	;ドット送出中か?
	goto	RUN_CHECK1		;次のチェックへ
	goto	RUN_DOT_0		;ドット送出処理へ行く。
RUN_CHECK1:
	btfss	RUN_MEMO,PAD_DAS	;ダッシュ送出中か?
	goto	RUN_CHECK_E		;次のチェックへ
	goto	RUN_DAS_0		;ダッシュ送出処理へ
RUN_CHECK_E:

	call	MEMO_DOT_CHECK		;サブルーチンでドット押下を確認
	call	MEMO_DAS_CHECK		;サブルーチンでダッシュ押下を確認

	goto	MAIN_LOOP_E		;ループ最終処理へ
;

;-------------------------
RUN_DOT_0:				;ドット送出処理
	btfss	RUN_MEMO,DOT_STA	;信号部分か判定する。
	goto	RUN_DOT_1


	movlw	TIME_1			;単位時間1を W へ呼び出す。
	subwf	CCC256,W		;CCC256カウンタからTIME_1(ドット時間)を引く。
;					;結果を W レジスタに入れる。
	btfss	STATUS,C		;C=1 で、時間が経過した。
	goto	RUN_DOT_E

	call	CNT_CLR			;タイミングカウンタークリヤー(初期化)

	call	CW_OFF			;信号送出を止める。
	bcf	RUN_MEMO,DOT_STA	;状態を信号送出後の間隔時間にする。
	goto	RUN_DOT_E		;ループ最終処理へ

RUN_DOT_1:


	movlw	TIME_1			;単位時間1を W へ呼び出す。
	subwf	CCC256,W		;CCC256カウンタからTIME_1(ドット時間)を引く。
;					;結果を W レジスタに入れる。
	btfss	STATUS,C		;C=1 で、時間が経過した。
	goto	RUN_DOT_E

	call	CNT_CLR			;タイミングカウンタークリヤー(初期化)

	bcf	RUN_MEMO,PAD_DOT	;ドット送出の終了
	bcf	PAD_MEMO,PAD_DOT	;パドルのドットメモをクリヤー
;	bcf	PAD_MEMO,PAD_DAS	;◆これを有効にすると、アイアンビックの
;					; mode A になるはず。(もう一箇所有り)
;

	call	MEMO_DAS_CHECK		;ドット処理直後は、
;					;ダッシュの状態を優先確認する。
;					;(アイアンピック動作)

RUN_DOT_E:


	goto	MAIN_LOOP_E

;-------------------------
RUN_DAS_0:				;ダッシュ送出処理
	btfss	RUN_MEMO,DAS_STA	;信号部分か判定する。
	goto	RUN_DAS_1
;
;					;----------------------------------
	movlw	TIME_1			;単位時間 1 を W へ呼び出す。
	subwf	CCC256,W		;CCC256カウンタからTIME_1(ドット時間相当)を引く。
;					;結果を W レジスタに入れる。
	btfss	STATUS,C		;C=1 で、時間が経過した。
	bcf	PAD_MEMO,PAD_DOT	;パドルのドットメモをクリヤ
					;◆◆◆ダッシュの先頭から約1/3間は、
					;パドルのドットを記憶しない。
;					;----------------------------------
;
	movlw	TIME_3			;単位時間3を W へ呼び出す。
	subwf	CCC256,W		;CCC256カウンタからTIME_3(ダッシュ時間)を引く。
;					;結果を W レジスタに入れる。
	btfss	STATUS,C		;C=1 で、時間が経過した。
	goto	RUN_DAS_E

	call	CNT_CLR			;タイミングカウンタークリヤー(初期化)

	call	CW_OFF			;信号送出を止める。
	bcf	RUN_MEMO,DAS_STA	;状態を信号送出後の間隔時間にする。
	goto	RUN_DAS_E		;ループ最終処理へ

RUN_DAS_1:


	movlw	TIME_1			;単位時間1を W へ呼び出す。
	subwf	CCC256,W		;CCC256カウンタからTIME_1(ドット時間)を引く。
;					;結果を W レジスタに入れる。
	btfss	STATUS,C		;C=1 で、時間が経過した。
	goto	RUN_DAS_E

	call	CNT_CLR			;タイミングカウンタークリヤー(初期化)

	bcf	RUN_MEMO,PAD_DAS	;ダッシュ送出の終了
	bcf	PAD_MEMO,PAD_DAS	;パドルのダッシュメモをクリヤー
;	bcf	PAD_MEMO,PAD_DOT	;◆これを有効にすると、アイアンビックの
;					; mode A になるはず。(もう一箇所有り)

	call	MEMO_DOT_CHECK		;ダッシュ処理直後は、
;					;ドットの状態を優先確認する。
;					;(アイアンピック動作)

RUN_DAS_E:

	goto	MAIN_LOOP_E

;-------------------------
	nop				;無意味
	nop				;無意味
	nop				;無意味

MAIN_LOOP_E:


	sleep
	nop				;sleep のおまじない
	goto	MAIN_LOOP



START_DEF:
;-------------------------------------------------------------------
	bcf	STATUS,RP0		;バンク 0 にする。
	call	H'3FF'			;内部発振器をキャリブレーションする。
	bsf	STATUS,RP0		;バンク 1 にする。
	movwf	OSCCAL
	bcf	STATUS,RP0		;バンク 0 にする。

	movlw	B'00000111'		;コンパレーターoffにする
	movwf	CMCON

	bsf	STATUS,RP0		;バンク 1 にする。

;	clrf	ANSEL			;GPIOポートをデジタルI/Oモードにする。

	movlw	GPIO_MODE		;先頭で定義  B'00001011';GP2 は出力指定。
	movwf	TRISIO

	movlw	B'00000000'		;ウィーク・プルアップを使わない。
	movwf	WPU

	bcf	OPTION_REG,INTEDG	;INTピン入力信号の
;					;立下りエッジ割込み(bank1)

	bcf	STATUS,RP0		;バンク 0 にする。

;-------------------------------------------------------------------
	movlw	B'00000100'		;GP2 だけはHighを出力指定。Sleep時に注意。
	movwf	GPIO_OUT		;GPIO_OUTの内容を初期化する (レジスター上のポート)
	call	IOPORT			;初期値を出力ポートに書き込み(必須)
;-------------------------------------------------------------------
;
	call	CNT_CLR			;タイミングカウンタークリヤー(初期化)
	
	clrf	RUN_MEMO		;送出中信号メモをクリヤー
	clrf	PAD_MEMO		;パドルの状態をクリヤー


	movlw	DEF_INTCON		;割り込みのセット
	movwf	INTCON
	


	goto	MAIN_LOOP0
;-------------------------------------------------------------------
;					;割込みタイミング用のカウンタクリヤー
CNT_CLR:
	movlw	CCC_DEF
	movwf	CCC			;タイミングカウンターを初期セット
	clrf	CCC256			;タイミングカウンター上位を初期セット
	return



;-------------------------------	;何も実行中で無い時に実行する。
;					;パドルメモの確認
MEMO_DOT_CHECK:
	btfss	PAD_MEMO,PAD_DOT	;ドットが押されているか?
	goto	MEMO_DOT_CHECK_E

	call	CNT_CLR			;タイミングカウンタークリヤー(初期化)

	bsf	RUN_MEMO,DOT_STA	;状態ビットの信号部分処理中にセット
	call	CW_ON			;CW信号ON
	bsf	RUN_MEMO,PAD_DOT	;ドット処理中をセット
	bcf	PAD_MEMO,PAD_DOT	;ドットが押されていたメモをクリヤー

MEMO_DOT_CHECK_E:
	return
;--------------------------------

MEMO_DAS_CHECK:
	btfss	PAD_MEMO,PAD_DAS	;ダッシュが押されているか?
	goto	MEMO_DAS_CHECK_E

	call	CNT_CLR			;タイミングカウンタークリヤー(初期化)

	bsf	RUN_MEMO,DAS_STA	;状態ビットの信号部分処理中にセット
	call	CW_ON			;CW信号ON
	bsf	RUN_MEMO,PAD_DAS	;ダッシュ処理中をセット
	bcf	PAD_MEMO,PAD_DAS	;ダッシュが押されていたメモをクリヤー

MEMO_DAS_CHECK_E:

	return

;-------------------------------------------------------------------
CW_ON:					;CALL命令で呼ぶ:Key出力 ON

	bsf	GPIO_OUT,LED_BIT
	goto	IOPORT

CW_OFF:					;CALL命令で呼ぶ:Key出力 OFF

	bcf	GPIO_OUT,LED_BIT
	goto	IOPORT

IOPORT:					;I/Oピンの設定とデータ出力

	movf	GPIO_OUT,W		;GPIOに出力データを書く。
	movwf	GPIO

	return

;-------------------------------------------------------------------

;-------------------------------------------------------------------
	data	'2','0','0','6','/','0','6','/','1','8',':'
	data	'(','C',')','J','E','3','J','L','B',' '
;-------------------------------------------------------------------

	org	3FFH			;念のため用意している、
	dw	3468H			;チップ固有のキャリブレDATA。

	end


------------------------------------------------------------------------

 

ソースファイル key629.asm (2006/07/06)

Hexファイル key629.hex (2006/06/18)

ソースとHexファイルV1.01 key629a (2008/12/31)