トリオ TS-120/TS-130シリーズで電波を出そうの会

Day34 - TS-120S専用PLL VFOを作る(5)

最終更新:

ts-120s

- view
管理者のみ編集可

せっかくだから、CWモードでの実用性もアップさせましょう。

https://w.atwiki.jp/ts-120s/pages/21.html
上記のおさらいですが、TS-120SのCWモードにおける周波数シフトはVFO自体に任されており、「送信時(+700Hz)の周波数」が、世間に出回る大多数の無線機でCWのダイアルを合わせたときの周波数です。

さすがにTS-120S自体の周波数表示を現代準拠に変えるのはややこしいですが、とりあえずPLL VFOを少し改良し、CWで送信する際に+700Hz周波数をシフトするようにしましょう。

基本戦略。

  • TS-120SのDIN 8pinには、CWモードの送信時に限り、PIN4にCW TX +9Vが出力されます。本来外部VFO(VFO-120)のTXシフトを実現するためのものですが、これを2.0~3.3Vぐらいに落として、GPIOをhighにすることにしました。*1
  • Pi picoにはGPIO12ピンに入れることにしました。抵抗の分圧で落とすもよしですが、レギュレータを使った方が簡易なので、手持ちの低ドロップタイプ三端子レギュレータ(3.3V 100mA) LP2950L-3.3を介してぶっこみましょう。*2
  • Pythonコードは、GPIOがhighになったら、VFOの周波数に+700Hzシフトを掛けるように小変更ですね。

実装前にDIN PIN4を確認する。

状態 PIN4 出力電圧
SSB RX時 -1.9V
CW RX時 -1.9V
CW TX時 +9.0V
絶対こういうのは確認したほうがいいですね。受信中はマイナス電圧がかかるようです。
入力に直列にシリコン整流ダイオードを加えて、(電圧はドロップしますが)逆電圧がかからないようにします。
僕は接続ケーブルのコネクタの中に10D1的なものを仕込んじゃいました。


というわけで、ミニステレオジャックまわりに三端などを実装します。

ここで、Python 3分クッキング(謎)のお時間です。

CW送信時の700Hzシフトも加えちゃいましょう。
GPIO12がhighになった時に700Hzを足すようにコードをちょいと変更です。

from machine import Pin, I2C, Timer
from time import sleep, ticks_ms, ticks_diff
 
# --------------------------
# SI5351 クラス定義
# --------------------------
class SI5351:
    SI5351_ADDRESS = 0x60
 
    def __init__(self, i2c):
        self.i2c = i2c
 
    def write_register(self, register, data):
        self.i2c.writeto_mem(self.SI5351_ADDRESS, register, bytes([data]))
 
    def write_bulk(self, register, data_list):
        self.i2c.writeto_mem(self.SI5351_ADDRESS, register, bytes(data_list))
 
    def init(self):
        self.write_register(3, 0xFF)  # Disable all outputs
        self.write_register(16, 0x80) # Power down CLK0
        self.write_register(177, 0xAC)  # Crystal load = 10pF
 
    def gcd(self, a, b):
        while b:
            a, b = b, a % b
        return a
 
    def approximate_fraction(self, num, denom, max_denominator=1_048_575):
        if num == 0:
            return 0, 1
        g = self.gcd(num, denom)
        num //= g
        denom //= g
        if denom > max_denominator:
            scale = denom // max_denominator + 1
            denom //= scale
            num //= scale
        return num, denom
 
    def set_freq(self, freq_hz, clk=0):
        XTAL_FREQ = 24_999_497 
        PLL_FREQ = 900_000_000
        MULT = PLL_FREQ // XTAL_FREQ
        num = PLL_FREQ % XTAL_FREQ
        denom = XTAL_FREQ
 
        num, denom = self.approximate_fraction(num, denom)
        pll_params = self.calc_params(MULT, num, denom)
        self.set_pll(pll='A', params=pll_params)
 
        div = PLL_FREQ // freq_hz
        num = PLL_FREQ % freq_hz
        denom = freq_hz
        num, denom = self.approximate_fraction(num, denom)
 
        ms_params = self.calc_params(div, num, denom)
        self.set_ms(clk, pll='A', params=ms_params)
 
        self.write_register(3, 0xFE & ~(1 << clk))  # Enable CLKx
 
        ms_div = div + num / denom
        actual_freq = PLL_FREQ / ms_div
        return actual_freq
 
    def calc_params(self, mult, num, denom):
        P1 = 128 * mult + int(128 * num / denom) - 512
        P2 = 128 * num - denom * int(128 * num / denom)
        P3 = denom
        return (P1, P2, P3)
 
    def set_pll(self, pll='A', params=(0, 0, 1)):
        base = 26 if pll == 'A' else 34
        P1, P2, P3 = params
        data = [
            (P3 >> 8) & 0xFF, P3 & 0xFF,
            (P1 >> 16) & 0x03,
            (P1 >> 8) & 0xFF, P1 & 0xFF,
            ((P3 >> 12) & 0xF0) | ((P2 >> 16) & 0x0F),
            (P2 >> 8) & 0xFF, P2 & 0xFF
        ]
        self.write_bulk(base, data)
 
    def set_ms(self, clk, pll='A', params=(0, 0, 1)):
        base = 42 + clk * 8
        P1, P2, P3 = params
        data = [
            (P3 >> 8) & 0xFF, P3 & 0xFF,
            (P1 >> 16) & 0x03 | (0b00 << 6),  # clk invert, int mode = 0
            (P1 >> 8) & 0xFF, P1 & 0xFF,
            ((P3 >> 12) & 0xF0) | ((P2 >> 16) & 0x0F),
            (P2 >> 8) & 0xFF, P2 & 0xFF
        ]
        self.write_bulk(base, data)
        self.write_register(16 + clk, 0x0F | (0 << 6))  # CLKx Control
 
 
# --------------------------
# GPIO設定
# --------------------------
i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=100_000)
enc_a = Pin(14, Pin.IN, Pin.PULL_UP)
enc_b = Pin(15, Pin.IN, Pin.PULL_UP)
button_step = Pin(13, Pin.IN, Pin.PULL_UP)
shift_input = Pin(12, Pin.IN, Pin.PULL_DOWN)
 
# --------------------------
# 状態初期化
# --------------------------
step_sizes = [100, 500, 1000, 10000]
step_index = 2
base_freq = 5_500_000
 
# --------------------------
# Si5351 初期化
# --------------------------
si5351 = SI5351(i2c)
si5351.init()
si5351.set_freq(base_freq)
 
# --------------------------
# ステップ切替ボタン
# --------------------------
last_button_time = 0
 
def handle_button(pin):
    global step_index, last_button_time
    now = ticks_ms()
    if ticks_diff(now, last_button_time) > 300:
        step_index = (step_index + 1) % len(step_sizes)
        last_button_time = now
 
button_step.irq(trigger=Pin.IRQ_FALLING, handler=handle_button)
 
# --------------------------
# ロータリーエンコーダ
# --------------------------
enc_table = {
    (0b00, 0b01): 1,
    (0b01, 0b11): 1,
    (0b11, 0b10): 1,
    (0b10, 0b00): 1,
    (0b00, 0b10): -1,
    (0b10, 0b11): -1,
    (0b11, 0b01): -1,
    (0b01, 0b00): -1,
}
 
last_enc = (enc_a.value() << 1) | enc_b.value()
rotate_dir = 0
update_flag = False
new_freq = 0
 
def check_encoder(timer):
    global last_enc, rotate_dir, update_flag, new_freq, base_freq
 
    current = (enc_a.value() << 1) | enc_b.value()
    if current != last_enc:
        dir = enc_table.get((last_enc, current), 0)
        if dir != 0:
            step = step_sizes[step_index]
            temp_freq = base_freq + dir * step
            new_freq = temp_freq
            rotate_dir = dir
            update_flag = True
        last_enc = current
 
timer = Timer()
timer.init(freq=2000, mode=Timer.PERIODIC, callback=check_encoder)
 
 
last_shift_state = shift_input.value()
 
# --------------------------
# メインループ
# --------------------------
while True:
    if update_flag:
        update_flag = False
        base_freq = new_freq
        freq = base_freq + (700 if shift_input.value() else 0)
        si5351.set_freq(freq)
 
    current_shift_state = shift_input.value()
    if current_shift_state != last_shift_state:
        sleep(0.01)
        if current_shift_state != shift_input.value():
            last_shift_state = current_shift_state
            continue
 
        last_shift_state = current_shift_state
        freq = base_freq + (700 if current_shift_state else 0)
        si5351.set_freq(freq)
 
    sleep(0.05)
 

こんなんでましたけど。

結果。

早速試してみましたが、問題なく使えました。
120本体VOX ON、外付メモリーキーヤーを使い、7MHz CWで国内QSOしてみましたが、問題なくシフトに追従してくれます。
呼んだら応答あるし、CQ出したら呼んできてくれるので、大丈夫そうです。

もしCW運用の利便性をより追及するのなら、「+700Hzシフトを考慮した受信周波数にして、500Hzステップ」などというのをやってみてもいいかもしれませんね。
コード触って遊んでみてください。
ウィキ募集バナー
注釈

*1 or その電圧を2N7000あたりのゲートに食わせてやり、GPIOをlowに落とす、とかでももちろんOKです。

*2 秋月で手に入ります。78L02なりなんなり、3.3Vロジックのpi picoをぶっ壊さず、Highになる電圧まで落としてください。AD0に入れるという手もあります。