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

Day31 - TS-120S専用PLL VFOを作る(2)

最終更新:

ts-120s

- view
管理者のみ編集可

Micropythonでコーディング。

Raspberry Pi Pico + 秋月Si5351A基板でVFO製作、続きです。
まずはそれらしいコードを書いちゃいましょう。
基本、si5351.set_freq()で、適当にCL0に周波数を吐けるはずです。

下記コードは、コピるなり試すなりいじるなり、個人でお好きに使っていただいて構いません。転載・引用も無断でどうぞ。ただし、①当方に質問やケチ付けてきても関知しません。②無断の商用利用禁止。③何が起ころうと自己責任!!


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)  
        self.write_register(16, 0x80) 
        self.write_register(177, 0xAC) 
 
    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)) 
 
        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), 
            (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))  
 
 
# --------------------------
# 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)
 
# --------------------------
# 状態初期化
# --------------------------
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)
 
# --------------------------
# メインループ
# --------------------------
while True:
    if update_flag:
        update_flag = False
        base_freq = new_freq
        freq = base_freq
        si5351.set_freq(freq)
    sleep(0.05)
 

まあとりあえずこんなかんじでしょうか。
  • Si5351Aのライブラリを参照してもよかったのですが、たいそうなものじゃないのでクラスとしてコード内に定義しちまいました。
  • ロータリーエンコーダのチャタリング対策は、ポーリングと状態遷移定義程度で、超甘いです。
  • 周波数やステップを表示させたければ、コンソールなり液晶なりに吐くよう機能を足してください。
(※家主が実際組んだものは機能ゴテゴテです。このコードは一通りテストしたけど、使ってません)

つづく。
ウィキ募集バナー