altso3
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| altso3 [2026/06/23 02:23] – admin | altso3 [2026/07/18 04:17] (current) – admin | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ====== AltSO3 ====== | ====== AltSO3 ====== | ||
| + | Exploring modern alternatives to the Kenwood SO-3 TCXO. Watch this space, maybe? | ||
| - | ==== Programming ==== | + | {{:altso3:altso3_v001.jpeg? |
| - | Micropython code for programming the MS5351M. Not yet tested. | + | |
| - | + | ||
| - | <file python main.py> | + | |
| - | import machine | + | |
| - | import time | + | |
| - | + | ||
| - | I2C_ADDR = 0x60 # Default Si5351A I2C address | + | |
| - | I2C_SDA_PIN = 8 # RP2040 GP8 | + | |
| - | I2C_SCL_PIN = 9 # RP2040 GP9 | + | |
| - | + | ||
| - | def setup_si5351(i2c): | + | |
| - | def write_reg(reg, | + | |
| - | i2c.writeto_mem(I2C_ADDR, | + | |
| - | + | ||
| - | # 1. Disable all outputs | + | |
| - | write_reg(3, | + | |
| - | + | ||
| - | # 2. Power down all output drivers | + | |
| - | for reg in range(16, 24): | + | |
| - | write_reg(reg, | + | |
| - | + | ||
| - | # 3. Calculate and configure PLLA registers for 624 MHz | + | |
| - | # Multiplier: a=24, b=24, c=25 | + | |
| - | a_pll, b_pll, c_pll = 24, 24, 25 | + | |
| - | p1_pll = 128 * a_pll + int(128 * b_pll / c_pll) - 512 | + | |
| - | p2_pll = 128 * b_pll - c_pll * int(128 * b_pll / c_pll) | + | |
| - | p3_pll = c_pll | + | |
| - | + | ||
| - | write_reg(26, | + | |
| - | write_reg(27, | + | |
| - | write_reg(28, | + | |
| - | write_reg(29, | + | |
| - | write_reg(30, | + | |
| - | write_reg(31, | + | |
| - | write_reg(32, | + | |
| - | write_reg(33, | + | |
| - | + | ||
| - | # 4. Calculate and configure MultiSynth 0 registers for divide-by-40 | + | |
| - | # Divider: a=40, b=0, c=1 | + | |
| - | a_ms, b_ms, c_ms = 40, 0, 1 | + | |
| - | p1_ms = 128 * a_ms + int(128 * b_ms / c_ms) - 512 | + | |
| - | p2_ms = 128 * b_ms - c_ms * int(128 * b_ms / c_ms) | + | |
| - | p3_ms = c_ms | + | |
| - | + | ||
| - | write_reg(42, | + | |
| - | write_reg(43, | + | |
| - | write_reg(44, | + | |
| - | write_reg(45, | + | |
| - | write_reg(46, | + | |
| - | write_reg(47, | + | |
| - | write_reg(48, | + | |
| - | write_reg(49, | + | |
| - | + | ||
| - | # 5. Connect MS0 to CLK0, Power Up, Integer Mode, PLLA Source, 8mA drive | + | |
| - | # 0x4F = 0b01001111 | + | |
| - | write_reg(16, | + | |
| - | + | ||
| - | # 6. Reset PLLA | + | |
| - | write_reg(177, | + | |
| - | + | ||
| - | # 7. Enable CLK0 | + | |
| - | write_reg(3, | + | |
| - | + | ||
| - | print(" | + | |
| - | + | ||
| - | def burn_to_nvram(i2c): | + | |
| - | """ | + | |
| - | WARNING: The Si5351A NVRAM is One-Time Programmable (OTP). | + | |
| - | This permanently burns the current RAM configuration into the chip. | + | |
| - | """ | + | |
| - | print(" | + | |
| - | print(" | + | |
| - | time.sleep(3) # Give user a chance to interrupt execution (Ctrl+C) if accidental | + | |
| - | + | ||
| - | def write_reg(reg, | + | |
| - | i2c.writeto_mem(I2C_ADDR, | + | |
| - | + | ||
| - | # Write 0xC0 to register 161 (NVM_WRITE) to trigger NVRAM burn | + | |
| - | write_reg(161, | + | |
| - | time.sleep(1) # Wait for NVM burn to complete | + | |
| - | print(" | + | |
| - | + | ||
| - | if __name__ == " | + | |
| - | # Initialize I2C bus 0 | + | |
| - | i2c = machine.I2C(0, | + | |
| - | + | ||
| - | # 1. Configure the RAM first to test the output | + | |
| - | setup_si5351(i2c) | + | |
| - | + | ||
| - | # 2. ⚠️ DANGER ZONE: Uncomment the line below ONLY AFTER verifying the 15.6 MHz | + | |
| - | # output with an oscilloscope or frequency counter. | + | |
| - | # burn_to_nvram(i2c) | + | |
| - | </ | + | |
| - | + | ||
| - | <file python mainv2.py> | + | |
| - | import machine | + | |
| - | import time | + | |
| - | + | ||
| - | I2C_ADDR = 0x60 | + | |
| - | I2C_SDA_PIN = 8 | + | |
| - | I2C_SCL_PIN = 9 | + | |
| - | + | ||
| - | # --- Si5351 Register Map --- | + | |
| - | REG_ENABLE_CTRL = 3 | + | |
| - | REG_CLK0_CTRL = 16 | + | |
| - | REG_PLL_A_PARAMS = 26 | + | |
| - | REG_MS0_PARAMS = 42 | + | |
| - | REG_NVM_BURN = 161 | + | |
| - | REG_PLL_RESET = 177 | + | |
| - | REG_CRYSTAL_LOAD = 183 | + | |
| - | + | ||
| - | def calc_fractional_regs(a, | + | |
| - | """ | + | |
| - | # Use floor division (//) for exact integer math, avoiding float inaccuracies | + | |
| - | p1 = 128 * a + ((128 * b) // c) - 512 | + | |
| - | p2 = 128 * b - c * ((128 * b) // c) | + | |
| - | p3 = c | + | |
| - | + | ||
| - | # Return as a bytearray so it can be sent in a single I2C block write | + | |
| - | return bytes([ | + | |
| - | (p3 >> 8) & 0xFF, | + | |
| - | p3 & 0xFF, | + | |
| - | ((p1 >> 16) & 0x03) | (((p2 >> 16) & 0x03) << 4), | + | |
| - | (p1 >> 8) & 0xFF, | + | |
| - | p1 & 0xFF, | + | |
| - | (((p3 >> 16) & 0x0F) << 4) | ((p2 >> 16) & 0x0F), | + | |
| - | (p2 >> 8) & 0xFF, | + | |
| - | p2 & 0xFF | + | |
| - | ]) | + | |
| - | + | ||
| - | def setup(i2c): | + | |
| - | def write_reg(reg, | + | |
| - | i2c.writeto_mem(I2C_ADDR, | + | |
| - | + | ||
| - | # 1. Disable all outputs and power down clocks safely | + | |
| - | write_reg(REG_ENABLE_CTRL, | + | |
| - | for reg in range(16, 24): | + | |
| - | write_reg(reg, | + | |
| - | + | ||
| - | # 2. Set Crystal Load Capacitance to 10pF | + | |
| - | # 0xD2 sets 10pF (11b) while preserving the required reserved bits (010010b) | + | |
| - | write_reg(REG_CRYSTAL_LOAD, | + | |
| - | + | ||
| - | # 3. Configure PLLA (Multiplier: | + | |
| - | plla_regs = calc_fractional_regs(24, | + | |
| - | i2c.writeto_mem(I2C_ADDR, | + | |
| - | + | ||
| - | # 4. Configure MultiSynth0 (Divider: a=40, b=0, c=1) | + | |
| - | ms0_regs = calc_fractional_regs(40, | + | |
| - | i2c.writeto_mem(I2C_ADDR, | + | |
| - | + | ||
| - | # 5. Power up CLK0, set integer mode, route to PLLA | + | |
| - | write_reg(REG_CLK0_CTRL, | + | |
| - | + | ||
| - | # 6. Reset PLLA to apply frequency parameters | + | |
| - | write_reg(REG_PLL_RESET, | + | |
| - | + | ||
| - | # 7. Re-enable CLK0 Output | + | |
| - | write_reg(REG_ENABLE_CTRL, | + | |
| - | + | ||
| - | print(" | + | |
| - | + | ||
| - | def burn(i2c): | + | |
| - | def write_reg(reg, | + | |
| - | i2c.writeto_mem(I2C_ADDR, | + | |
| - | + | ||
| - | print(" | + | |
| - | write_reg(REG_NVM_BURN, | + | |
| - | time.sleep(1) # Wait for NVM burn to complete | + | |
| - | print(" | + | |
| - | + | ||
| - | if __name__ == " | + | |
| - | i2c = machine.I2C(0, | + | |
| - | + | ||
| - | setup(i2c) | + | |
| - | + | ||
| - | # Ensure VDD is stable at 3.3V before executing the burn process | + | |
| - | burn(i2c) | + | |
| - | </ | + | |
altso3.1782181414.txt.gz · Last modified: by admin
