After much gnashing of teeth, I have a working driver for the Adafruit Silicon Labs si5351 clock generator break out board. This board has three clock outputs that can generate a 8KHz to 160MHz square wave.
A future upgrade will include code to optionally generate I(0 deg) and Q(90 deg) signals on CLK0 and CLK1 to support SDR processing by Quisk.
#! /usr/bin/perl
# hipisi5351.pl - si5351 Test Driver for Perl HiPi GPIO package
#
# James M. Lynes Jr. - KE4MIQ
# Created: August 25, 2019
# Last Modified: 2/14/2020 - Add the setfreq subroutine
# 3/02/2020 - Set output levels to 8ma per ZL2CTM
# 3/04/2020 - Fix typos/compile errors
# 3/06/2020 - Additional comments on register definitio
+ns
# 6/14/2020 - Begin port to HiPi GPIO package
# 6/15/2020 - Add HiPi I2C syntax changes
# 6/16/2020 - Fix bus_write calling order(reg,data)
# 6/17/2020 - Fix missing $ and several comments
# 6/20/2020 - Modify pin assignments to separate I2C
# functions from GPIO functions
# onto 5pin(I2C) and 12pin(GPIO) connec
+tors
# 6/22/2020 - Reassign GPIO0(reserved) to GPIO5
# 8/25/2020 - 2x20 RPi connector shell now available
# 8/26/2020 - Fix first compile errors/typos on RPi
# 8/28/2020 - Change test freq to 25,000,000Hz
# 8/30/2020 - Cycle through a few freq changes
# 8/31/2020 - Per Jerry, update reg 183 with |0x12
# see note at An619 pg 61
#
# Target: Raspberry PI 3B+ with Adafruit si5351 BOB
#
# Initial test version: Initilize si5351 and set frequency to 50 MHz
# Code to be integrated into a Remote Head and VFO for
# Pete Juliano's(N6QW) RADIG II home brew SDR transceiver
# that uses Jim Ahlstrom's(N2ADR) Quisk SDR software.
#
# The Silicon Labs AN619 is required to understand register
# functions used in this code...single values may be split
# across 3 registers...took me awhile...
#
# Ported from an Arduino C driver by Jerry Gaffke, KE7ER
# as modified by Dr. Ian Lee, KD8CEC
# as used in the Bitx40 and uBitx Ham transceivers
# by Ashhar Farhan, VU2ESE.
#
# To Do: Add Hans' quadrature(I&Q) output feature
#
# Note: Possible subtle bug in si5351_setfreq()
# $si5351_clken &= ~(1 << $clknum)
# Clears a bit in $si5351_clken to enable a clock
# Would require: $si5351_clken |= 1 << $clknum
# to set a bit before a subsequent call to disable a clo
+ck
#
# RPI J8 - GPIO Pin Definitions si5351 BOB Pin Definitio
+ns
# ----------------------------- ------------------------
+--
# [RED] 3V3 (1) (2) 5V (1) CLK0 - SMA
# [YEL] SDA/GPIO2 (3) (4) 5V (2) CLK1 - SMA
# [BLU] SCL/GPIO3 (5) (6) GND (3) CLK2 - NC
# GPIO4 (7) (8) GPIO14 (4) SCL - [BLU]
# [BLK] GND (9) (10) GPIO15 (5) SDA - [YEL]
# PB1/GPIO17 (11) (12) GPIO18 (6) GND - [BLK]
# PB2/GPIO27 (13) (14) GND (7) VIN - [RED]
# ENCA/GPIO22 (15) (16) GPIO23
# 3V3 (17) (18) GPIO24
# ENCB/GPIO10 (19) (20) GND
# RED/ GPIO9 (21) (22) GPIO25
# YEL/GPIO11 (23) (24) GPIO8
# GND (25) (26) GPIO7
# *GPIO0 (27) (28) GPIO1*
# GRN/ GPIO5 (29) (30) GND
# GPIO6 (31) (32) GPIO12
# GPIO13 (33) (34) GND
# GPIO19 (35) (36) GPIO16
# GPIO26 (37) (38) GPIO20
# GND (39) (40) GPIO21
# * GPIO0 & GPIO1 are reserved
use strict;
use warnings;
use HiPi qw( :i2c );
use HiPi::Device::I2C;
use Time::HiRes qw(sleep);
# Globals
my $SI5351_ADDR = 0x60; # si5351 I2C Address
my $SI5351_XTALPF = 2; # si5351 crystal capacitanc
+e in pf
# 1:6pf 2:8pf 3:10pf
my $SI5351_XTAL = 25000000; # Crystal frequency in Hz
my $SI5351_MSA = 35; # VCOA is at 25MHz*35 = 875
+MHz
# (If using 27MHz crystal
# XTAL = 27000000, MSA
+= 33
# Then VCOA is at 891MH
+z)
my $si5351_vcoa = $SI5351_XTAL * $SI5351_MSA; # VCO frequency -> 875M
+Hz
my $si5351_rdiv = 0; # 0-7, extra divisor for f
+< 500KHz
# CLK pin sees fout/(2*
+rdiv)
my @si5351_drive = (3, 3, 3); # 0=2ma 1=4ma 2=6ma 3=8ma
# for clocks 0, 1, 2
my $si5351_clken = 0xFF; # All clock output drivers
+off mask
my $calibration = 0; # Correction factor for cry
+stal error
# Main
my $si5351 = HiPi::Device::I2C->new(); # Create an I2C object
$si5351->select_address($SI5351_ADDR); # Set I2C device address
si5351_init(); # Initialize si5351 registe
+rs
si5351_set_calibration($calibration); # Apply crystal correction
for(my $f = 50000000; $f <= 50500000; $f += 1000) {
si5351_setfreq(0, $f); # Tune clock 0 up by 1000Hz
sleep(.01); # allows testing with DV
+B-T
} # dongle
# End Main
#
# ------------------------------- Subroutines ----------------------
+--------------
#
sub si5351_init {
# Init PLLA Multisyn
+th registers 26-33
my $msxp1 = (128 * $SI5351_MSA) - 512; # msxp2=0, msxp3=1,
+not fractional
my $bb2 = ($msxp1 >> 16) & 0xFF; # Split 32 bits into
+ bytes
my $bb1 = ($msxp1 >> 8) & 0xFF; # See AN619 pgs
+28-30
my $bb0 = $msxp1 & 0xFF;
my @vals = (0, 1, $bb2, $bb1, $bb0, 0, 0, 0); # a + b/c, 35 + 0/1,
+ AN619 pg 3
$si5351->bus_write(149, 0); # Spread spectrum of
+f
$si5351->bus_write(3, $si5351_clken); # Disable all clock
+output drivers
$si5351->bus_write(183, ($SI5351_XTALPF << 6) | 0x12); # Set cryst
+al load capacitance
$si5351->bus_write(26, @vals); # Write to 8 PLLA ms
+ynth regs 26-33
$si5351->bus_write(177, 0x20); # Reset PLLA(0x80 PL
+LB)
}
sub si5351_set_calibration {
my ($cal) = @_;
$si5351_vcoa = ($SI5351_XTAL * $SI5351_MSA) + $cal * 100; # Apply
+ xtal cal
$si5351->bus_write(3, $si5351_clken); # Force
+ all clocks off
}
sub si5351_setfreq {
my($clknum, $fout) = @_; # Init frequency div
+iders a + b/c
my $msa = int($si5351_vcoa / $fout); # Integer part of vc
+oa/fout
my $msb = $si5351_vcoa % $fout; # Fractional part of
+ vcoa/fout
my $msc = $fout;
while($msc & 0xfff00000) { # Divide by 2 until
+fits into 20 bits
$msb = $msb >> 1; # Numerator
$msc = $msc >> 1; # Denominator
}
# Multisynth Register Definitions - a + b/c (See SiLabs AN619)
# msxp1 - 18 bit integer part of the divider (a)
# msxp2 - 20 bit numerator part of the divider (b)
# msxp3 - 20 bit denominator part of the divider (c)
# From AN619 pg 6
# msxp1 = (128 * a) + (128 * b / c) - 512
# msxp2 = (128 * b) - (128 * b / c)
# msxp3 = c
my $msxp1 = (128 * $msa + 128 * $msb / $msc - 512) | ($si5351_rdiv
+ << 20);
my $msxp2 = 128 * $msb - 128 * $msb / $msc * $msc;
my $msxp3p2top = (($msc & 0x0F0000) << 4) | $msxp2; # Top two nib
+bles
# Code below splits a, b, & c into bytes to be copied into the r
+egisters
# Bytes do not lay into consecutive registers, see comments belo
+w
# A block of 8 registers is assigned to each clock output
# i.e. CLK0 - Registers 42-49, AN619 pgs 33-36
my $bb1msc = ($msc >> 8) & 0xFF; # msx_p3[15:8 ] -
+ Reg 42
my $bb0msc = $msc & 0xFF; # msx_p3[ 7:0 ] -
+ Reg 43
my $bb2msxp1 = ($msxp1 >> 16) & 0xFF; # msx_p1[17:16] -
+ Reg 44
my $bb1msxp1 = ($msxp1 >> 8) & 0xFF; # msx_p1[15:8 ] -
+ Reg 45
my $bb0msxp1 = $msxp1 & 0xFF; # msx_p1[ 7:0 ] -
+ Reg 46
my $bb2msxp3p2top = ($msxp3p2top >> 16) & 0xFF; # msx_p3[19:16],
# msx_p2[19:16] -
+ Reg 47
my $bb1msxp2 = ($msxp2 >> 8) & 0xFF; # msx_p2[15:8 ] -
+ Reg 48
my $bb0msxp2 = $msxp2 & 0xFF; # msx_p2[ 7:0 ] -
+ Reg 49
my @vals = ($bb1msc, $bb0msc, $bb2msxp1, $bb1msxp1, $bb0msxp1,
$bb2msxp3p2top, $bb1msxp2, $bb0msxp2);
$si5351->bus_write(42 + ($clknum * 8), @vals);
$si5351->bus_write(16 + $clknum, 0x0C | $si5351_drive[$clknum]); #
+ PLLA->MS0->CLK#
#
+ @ #ma
$si5351_clken &= ~(1 << $clknum); # Clear bit to en
+able clock
# (see "bug" note
+ above)
$si5351->bus_write(3, $si5351_clken); # Send clock enab
+le
}