AVRを使ったエレキーの製作

[公開:any]

[電子工作/アマチュア無線]
[電子工作/AVR]
[AVR,FT-817ND,ATtiny861A,PWM,HT7733]

origin 2012-12-03


 CWに必要なエレキーを探し出してきました。ハイモンドのマニピュレータ(パドル)とカツミ電機のエレキーです。その昔、CQ誌の「買います・売ります」コーナーで数千円で入手した中古です。当時は、これでコンテストをがんばっていました。
 ネットで調べてみるとハイモンドは、まだ営業していました。同じ形状のパドルを販売しています。カバーやプラスティックパドルなどの予備品も取り扱いがあるので、まだまだ使えそうです。しかし、残念ながら、カツミ電機は倒産したようです。エレキー本体も電源をつないでも動作しません。30年近く前の製品なので修理するのもあきらめました。

 FT-817NDには、エレキーが内蔵されています。パドルをつないで使ってみたのですが、速度の可変がいちいちファンクションキーを長押ししてメニューを呼び出して・・・・と面倒です。速度の可変は、運用中に交信相手にあわせて変更するので専用のエレキーが必要だと感じました。


 昔なら大変だったでしょうが、現在はワンチップマイコンで簡単にエレキーが作れます。早速、AVRを使ってテストしてみました。スクイーズ動作や短点、長音メモリーも簡単にプログラミングで実装できます。ブレッドボードとATtiny861Aのみで基本的なエレキーが完成です。あとは、これに実際にキーイングするトランジスタがあればOKです。現在の無線機のキー入力は数mAしか流れないのでAVRのみでもキーイング動作するでしょう。


 速度可変機能などを追加しながらサイドトーンのスピーカーを選びました。単純なブザー(電圧をかけると音が出る)が一番手軽ですが、手持ちの5V用ブザーでは、電池駆動では音量が小さいことと音程が変えられないのが問題です。かといって普通のマグネティックスピーカではサイズが大きいので、小型の圧電スピーカーを使うことにしました。AVRから高速PWMで圧電スピーカーを鳴らします。音程は好みで決められますが、CWのゼロインがやりやすいように800Hz前後がいいと思います。そのままでは音量がやや大きいのでトリマで可変できるようにしました。


 電源は、移動運用などにも使えるように電池動作としました。ATtiny861Aは、1.8Vから動作するので、電源の安定化の必要はないのですが、キーイング速度をAD変換値から得ているので、DC-DCコンバーターICのHT7733で3.3Vに安定化しました。基本的な機能を追加して、消費電流を測ると10mA弱となりました。HT7733は、電池1本でも問題なく動作します。今回は電池2本とすることで長時間の利用が可能です。(単純計算でエネループで200時間程度はいけそう)


 ブレッドボードのままFT-817NDにつないでキーイングしてみました。内蔵のエレキーと比較してもキーイングにまったく違和感ありません。無線機本体からもサイドトーンが出るので、エレキーのサイドトーンはオンオフできるようにしたほうがよさそうです。


 最終的な回路図です。電源スイッチは、プッシュスイッチをソフトスイッチとして使用します。電源OFFの状態でプッシュすると電源が入ります。その状態では、通常のエレキー動作とアンテナや無線機の調整などに必要な連続キーダウンができるマニュアルモードへの切り替えスイッチとして動作します。スイッチを2秒以上連続して押すと電源OFFとなります。ソフトスイッチとして使うことで自動電源OFFなども可能となります。
 
 使用するトランジスタは、部品数を減らすためにデジタルトランジスタとしています。2SC1815などの汎用のトランジスタと抵抗でも問題ありません。キーイング出力のトランジスタのみUN4219とは別のDTC143を選びましたが、これは消費電流を下げるためです。
 
 回路図には、オプションとして、サイドトーンの音程やウェイト(短点と長音の比の可変)も追加してありますが、あまり必要性を感じなかったので実装はしていません。液晶などの表示部がない状態で、音程やウェイトを可変とすると変化量がモニターできないため、迷うことがありそうだからです。ただ、AVRのポートには空きがあるのでI2C液晶を追加してみるのも面白そうです。

エレキー回路図

 基板への実装図です。基板は秋月電子の小型基板で設計しました。スピーカーのON/OFFスイッチ(実装図にはありません)や音量調整トリマは、使用頻度が少ないと考えて基板上に実装しました。部品数も少ないので空きスペースもあります。音程やウェイトの可変を行うならトリマが追加できます。


 部品を取り付けて、バラックで動作確認をしました。


 ケースは、秋月電子のポリカーボネートケース中を使用しました。ふたがヒンジで開け閉めできるので電池交換や音量調整が簡単です。

AVRエレキー1AVRエレキー2

 FT-817NDに接続して運用しています。・・・・・といってもキーイングは体が(指が)覚えているので、高速キーイングできるのですが、受信がまったくダメなので実際のQSOはまだまだです。和文も文字を目で見ながら指が勝手に動いてキーイングできるのですが、実際のQSOを受信してみる文字が頭に浮かびません。しばらく受信練習が必要です。


 ソフトウェアです。久しぶりのプログラミングでした。パソコンも新しくなったのでAVRStudio6をインストールしてはじめて使ってみました。エディタが良くなったのですが、補間が効きすぎて逆に使いづらい所があります。AVRISPmk2経由での書き込みが面倒なのは閉口しました。
 パドルからのキー入力は、タイマー割り込みで読み取っています。短音または長音出力中でも2ms間隔で割り込みがかかり、キー入力を読み取るので結果として短音・長音メモリー機能を実現しています。カツミのエレキーとの比較はできていませんが、市販のものと同じ動作だと思います。短音と長音を同時押しすると交互に音が出るスクイーズ機能も動作します。
 エネループ電池などのニッケル水素電池を使用する場合は、過放電に注意する必要があります。このため、AVRのAD変換機能で電池電圧を常時モニターして、ある一定電圧以下になるとLEDを1秒間隔で点滅させる電圧低下アラーム機能を搭載しました。その後、さらに電圧が低下すると自動的に電源が切れるようにしてあります。また、10分以上、パドル入力などの操作がない場合も自動的に電源が切れるオートパワーオフ機能も搭載しました。
 マニュアルモードは、無線機やアンテナの調整時などの連続キーダウンが必要なときに使用します。長音側パドル操作で連続してキーダウンできます。短音側は通常動作なので、昔のバグキーのような使い方ができます。マニュアルモード時は、LEDが若干暗くなるようにしてあります。
 音程は、やや低めの750Hzとしました。ソースの中には音程の調整機能もありますが、使わないのでコメントアウトしてあります。ウェイトは、短点1に対して長音3.2としてあります。その他、短点や長音のあとの空き時間等は、好みでチューニングしています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
 * ele_key.c for ATtiny861A 
 *
 * Created: 2012/11/25 10:49:51
 *  Author: henteko.org
 *
 * Fuse bit set: Extended 0xff, High 0xd9, Low 0x52
 * (8MHz internal clock and CLKDIV8)
 */ 

#ifndef F_CPU
#define F_CPU 1000000UL
#endif

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

#define cbi(addr, bit)     addr &= ~(1<<bit)
#define sbi(addr, bit)     addr |=  (1<<bit)
#define ADC_ENABLE (_BV(ADEN)|_BV(ADIF)|0b011)
#define ADC_START  (ADC_ENABLE|_BV(ADSC))

unsigned int dot, dash;        // dot and dash flag
unsigned int cnt;            // interruput times
unsigned int bkmode;        // manual mode flag

// millisecond order delay
void delay_ms(unsigned int t)
{
    while(t--)
        _delay_ms(1);
}

// get ADC routine
unsigned int get_adc(char ch)
{
    ADMUX = ch;
    ADCSRA = ADC_START;
    loop_until_bit_is_set(ADCSRA, ADIF);

    return ADCW;
}

// interrupt routine
ISR(TIMER0_OVF_vect)
{
    cnt++;
    if(bit_is_clear(PINA, PA0)) {
        dot = 1;
    }
    if(bit_is_clear(PINA, PA1) && bkmode == 0) {
        dash = 1;
    }
}

// power down routine
void powerdown()
{
    cbi(PORTB, PB3);
    cbi(PORTB, PB6);
}

int main(void)
{
    unsigned int tim;                            // switch input time count
    unsigned int dot_speed, dash_speed, space;    // keying speed
    unsigned int ratio;                            // dash ratio    
    unsigned int sec;                            // second
    unsigned int batt, battcnt;                    // battery voltage and count
    unsigned int on, off;                        // PWM interval
    unsigned int lowbatt;                        // lowbatt count

    DDRA = 0b10011000;        // PA0,PA1 key input. PA2,PA5 ADC 
    DDRB = 0b11011111;        // PB5 power switch input
     
    sbi(PORTB, PB6);            // power hold tr on
    sbi(PORTB, PB3);            // status LED on
     
     PORTA = 0b00000011;    // pullup PA0,PA1
    PORTB |= _BV(PB5);        // pullup PB5
    
    ADCSRA = ADC_ENABLE;
    
    TCCR0B = 2;                // Timer0 prescaler 1/8 .. 2.048ms
    TIMSK = _BV(TOIE0);        // Timer0 Overflow Interrupt Enable
    
    TCCR1A = _BV(COM1A0)|_BV(COM1A1)|_BV(PWM1A);    // PWM1A
    TCCR1B = 4;                // Timer1 prescaler 1/8 .. 2.048ms
    
    dot = dash = 0;     
    ratio = 2;    // ratio=1 dot1:dash3.1  ratio=2 dot1:dash3.2
    sec = 0;
    cnt = 0;
    bkmode = 0;
    // sidetone frequency ( off = 128000 / freq )
    off = 170;            // 750Hz
    on = (int)off / 2;        // PWM duty 50%

    OCR1C = off;
    OCR1A = off;
    
    sei();
     
     while(1) {
        // sense keying speed
        dot_speed = (int)(get_adc(2) / 10) + 20;
        dash_speed = (int)dot_speed * (3 + ratio * 0.1);
        space = (int)dot_speed * 0.9;
        
/*
        // sidedone frequency
        off = (int)get_adc(5) / 4;
        on = (int)off / 2;
        OCR1C = off;
        OCR1A = off;
*/

        // check battery 
        batt = get_adc(4);
        if(batt < 651)        // under 2.1V 
            lowbatt++;
        else {
            lowbatt = 0;
            sbi(PORTB, PB3);
        }        
        if(batt < 558)        // under 1.8V
            battcnt++;
        else
            battcnt = 0;
        if(battcnt > 10)    // low battery power down
            powerdown();

        // auto power off        
        if(cnt >= 488) {    // 1s / 4.096ms = 244.14
            sec++;
            cnt = 0;
            if(sec > 600)            // 600sec(10min)
                powerdown();
            if(lowbatt > 10) {        // low battery alert blink LED
                if(bit_is_set(PINB, PB3))
                    cbi(PORTB, PB3);
                else
                    sbi(PORTB, PB3);
            }
        }

        // power switch check and check manual keying mode
        while(bit_is_clear(PINB, PB5)) {
            tim++;
            delay_ms(10);
            if(tim > 200)
                powerdown();
        }
        if(tim > 20) {
            tim = 0;
            sec = 0;
            if(bkmode)
                bkmode = 0;
            else
                bkmode = 1;
        }
        
        // manual keying mode
        if(bkmode) {
            while(bit_is_clear(PINA, PA1)) {
                sbi(PORTA, PA7);
                OCR1A = on;
            }
            cbi(PORTA, PA7);
            OCR1A = off;
            cbi(PORTB, PB3);
            delay_ms(10);
            sbi(PORTB, PB3);                    
        }
        
        // send dot        
         if(dot) {
            sbi(PORTA, PA7);
            cbi(PORTB, PB3);
            OCR1A = on;
            delay_ms(dot_speed);
            cbi(PORTA, PA7);
            sbi(PORTB, PB3);
            OCR1A = off;
            delay_ms(space);
            dot = 0;
            sec = 0;            
         }
         
        // send dash
         if(dash) {
            sbi(PORTA, PA7);
            cbi(PORTB, PB3);
            OCR1A = on;
            delay_ms(dash_speed);
            cbi(PORTA, PA7);
            sbi(PORTB, PB3);
            OCR1A = off;
            delay_ms(space);
            dash = 0;
            sec = 0;
         }

    }
}

▲ページ Top へ...