Shop OBEX P1 Docs P2 Docs Learn Events
Tachyon 5 - SI5351 dual RF signal source control — Parallax Forums

Tachyon 5 - SI5351 dual RF signal source control

bob_g4bbybob_g4bby Posts: 440
edited 2021-08-21 19:47 in Forth
I got this code prototype working just this evening, to control the frequency of the two independent channels of an SI5351 chip over the range 4.5kHz to 120MHz.

N.B. fref needs changing to the clock frequency driving your SI5351. My adafruit style board from ebay had a 25MHz crystal. Some others are 27MHz.

To use the code:-
1. the command 0 RFpins! selects the signal out of CLK0 as the active channel, and 1 RFpins! selects the signal from CLK1 as the active channel.
2. the command <frequency> RFtune will then set the frequency of the active channel.
e.g. 1 RFpins! 1000000 RFtune 500 ms 2000000 RFtune would set CLK1 to 1MHz for 1/2s, then change to 2 MHz (no need to reselect the same channel between frequency changes). Frequency setting precision is to within 1-2Hz. Frequency accuracy depends on the crystal used to drive the SI5351 which are never spot-on as marked (mine was 25.008325MHz ). A later version of the code will allow this to be corrected on the fly.

VFOCLKSOFF turns both channels off again. That wants splitting up into per-channel for individual control. (For radio use, they say that best signal purity is achieved when only one channel is used)

There are some test words at the bottom to check the data being created and read the SI5351 settings. During development, the application note AN619 from SI was useful, as was this website. Referring to the SI5351 diagram on this website, my code holds three of the frequency setting parameters constant - e=0, f=1 and c = 1048575, to simplify the maths. The value of c is the largest permitted and this allows b to vary over the widest range and thus give the smallest tuning resolution. The OMD division ratio is maintained as an even integer which is said to minimise clock jitter.

The intention now is to optimise this code for radio tuning application - e.g. only reset the pll when needed (because it briefly interrupts the clock output) and only send changed bytes to the si5351 when a frequency change is made, making for smoother swept tuning. I'll post the result when it's done.
--- SI5351 DRIVER version 1.1 FOR TACHYON 5V7 Bob Edwards Dec 2020

TACHYON V5

IFDEF SI5351
	FORGET SI5351
}
module SI5351 ." SI5351 dual channel RF oscillator driver" ;

--- Record types

--- start a record definition, leave zero offset into the record tos
0	:=	[FIELDS		( 0 -- )

--- define a new field within a record
pre FIELD				  
	CREATE OVER W, +        ( offset1 datasize -- offset2 )
	DOES> R> W@ +           ( recaddr -- recaddr+offset )
	;

--- finalise a record definition
ALIAS := FIELDS]


--- define a record array type
pre RECORDS ( fielddef recordnumber <arrayname> -- ; arrayindex -- adr )
			GRAB 
			CREATE			--- in the dictionary entry...
				org@ W,		--- save start address of data
				OVER W,		--- and save the record size
				* @org W+!  --- then in the dataspace allot reqd no. of bytes
			DOES>
				R@ W@		--- read the start address of the data
				R> 2+ W@	--- read the record size
				ROT * +		--- and compute the start addres of the reqd record
	;
	
--- constants

$C0      	:= vfoadr				--- SI5351 i2c address
25008325 	:= fref					--- nominal crystal frequency driving the si5351
750000000	:= pllmid				--- PLL frequency midrange
600000000	:= pllmin				--- PLL minimum permitted frequency
900000000	:= pllmax				--- PLL maximum permitted frequency
1048575		:= C					--- this FMD factor is maintained a constant 	

--- variables
long A
long B

--- Storage definition for one RF channel - remember word and long boundaries - notice order below longs-words-bytes
--- preserve this - the size of the record has to be maintained a multiple of 4, so all records will be aligned
--- This can be achieved by a dummy entry if need be

[FIELDS
	4 FIELD .frequency		--- output frequency
	4 FIELD .Rfreq			--- OMD o/p frequency
	4 FIELD .OMD			--- last OMD
	4 FIELD .pllfreq		--- last Fvco
	4 FIELD .msna_p1		--- FMD intermediate value
	4 FIELD .msna_p2		--- FMD intermediate value
	4 FIELD .msna_p3		--- FMD intermediate value
	4 FIELD .msx_p1			--- OMD intermediate value
	2 FIELD .changed		--- records which registers reg2 - reg12 have changed since last time
	1 FIELD .R				--- R divider
	1 FIELD .reg0			--- SI5351 registers
	1 FIELD .reg1
	1 FIELD .reg2
	1 FIELD .reg3
	1 FIELD .reg4
	1 FIELD .reg5
	1 FIELD .reg6
	1 FIELD .reg7
	1 FIELD .reg8
	1 FIELD .reg9
	1 FIELD .reg10
	1 FIELD .reg11
	1 FIELD .reg12
	1 FIELD .reg13
	1 FIELD .reg14
	1 FIELD .reg15
	1 FIELD .msx_divby4
	1 FIELD .rx_div
	3 FIELD dummy
FIELDS] RFparam


--- Create a 2 record array for the two independent RF sources in SI5351

4 ALIGNORG								--- Ensure array starts on a long boundary
RFparam 2 RECORDS RFparams

--- RFpin! sets which RF channel is current
byte RFsource
pub RFpin! RFsource C! ;					( 0 or 1 -- )
pri RFpin@ RFsource C@ ;					( -- current RF source )
pri RFparams@ RFpin@ RFparams ; 			( -- start adr of reqd params record )

--- Calculation of SI5351 register values

--- from the new output frequency and old OMD setting - the latter is allowed to be any number on start up
--- calculate the new OMD as d + e=0 / f=1 and new pll frequency required

--- calculate R divider and equivalent o/p frequency
pri RCALC	( freq1 -- R freq2 )
	DUP 500000 <
	IF
	  2							--- R = 2 seed value 
	  7 FOR 
			2DUP * 500000 >		--- check if freq1 * R > 500kHz
			IF
				LEAVE			--- yes, f now > 500kHz,so leave loop
			ELSE
				2*				--- no, then set R = R * 2
			THEN 
		NEXT 					--- and try again
		SWAP 2DUP * SWAP DROP
	ELSE
		1 SWAP					--- R is 1 for frequencies over 500000, freq2 is no change
	THEN
;

--- calculate the new Output Multisynth Divider
pri OMD		( freq oldOMD -- newOMD pllfreq )
	2DUP *
	DUP pllmin pllmax WITHIN	
	IF					( freq oldOMD pllfreq )
		ROT DROP
	ELSE				( freq oldOMD pllfreq )
		DROP DROP DUP	( freq freq )
		pllmid SWAP /	( freq newOMD )
		DUP 1 AND
		IF
			1+
		THEN			( freq newOMD' )
		SWAP OVER *		( newOMD pllfreq )						
	THEN
;


--- calculates a,b for parameters  a + b / c for the Feedback Multisynth Divider from the pll frequency
pri FMD					( pllfreq -- a b )
DUP fref / SWAP			( -- a pllfreq )
C fref */				( -- a c*pllfreq/Fref )
OVER C * - 				( -- a b )
;						( -- a b )

--- The SI5351 requires the above to be packed in the following way...
--- calculate msna_p1
pri msna_p1!
A @ 7 <<		--- A*128
B @ 7 <<
C /				--- 128*B/C
+
512 -
RFparams@ .msna_p1 !
;

--- calculate msna_p2
pri msna_p2!
B @ 7 << DUP				--- 128*B
C /							--- (128*B)/C
C *							--- C*((128*B)/C)
- RFparams@ .msna_p2 !		--- 128*B-C*((128*B)/C)
;

--- calculate msna_p3
pri msna_p3!
C
RFparams@ .msna_p3 !
;

--- calculate msx_p1
pri msx_p1!
RFparams@ >R
R@ .OMD @
128 * 512 -
R> .msx_p1 !
;

--- calculate msx_divby4
pri msx_divby4!
RFparams@ >R
R@ .Rfreq @ 150000000 >
	IF
		$0C
	ELSE
		0
	THEN
R> .msx_divby4 C!
;

--- calculate rx_div
pri rx_div!
RFparams@ >R
R@ .R C@ DUP 1 =
	IF
		DROP 0
	ELSE
	    >| 4 <<
	THEN
R> .rx_div C!
;

--- Now form all byte registers ready to send to si5351
pri reg!
RFparams@ >R
R@ .msna_p1 @
DUP >B R@ .reg4 C!			--- reg4 = LS byte of msna_p1
8 >>
DUP >B R@ .reg3 C!			--- reg3 = middle byte of msna_p1
8 >> 3 AND
R@ .reg2 C!					--- reg2 = MS byte of msna_p1 AND 3
R@ .msna_p2 @
DUP >B R@ .reg7 C!			--- reg7 = LS bytes of msna_p2
8 >>
DUP >B R@ .reg6 C!			--- reg6 = middle byte of msna_p2
8 >> $0F AND				--- MS byte of msna_p2
R@ .msna_p3 @ 
12 >> $F0 AND
OR R@ .reg5 C!				--- reg5 = Top 4 bits msna_p2 ORed with top 4 bits of msna_p3  
R@ .msna_p3 @
DUP 8 >> >B
R@ .reg0 C!					--- reg0 = middle byte of msna_p3
>B R@ .reg1 C!   			--- reg1 = LS byte of msna_p3
1 R@ .reg9 C!				--- reg9 = 1 permanently
R@ .msx_p1 @
DUP >B R@ .reg12 C!			--- reg12 = LS byte of msx_p1
DUP 8 >> >B R@ .reg11 C!	--- reg11 = middle byte of msx_p1
16 >> 3 AND
R@ .rx_div C@ +
R@ .msx_divby4 C@ +
R> .reg10 C!				--- reg10 = MS byte of msx_p1
;

--- calculate and store all division settings in the currently selected channel record
pri PARAM!	( frequency -- )
RFparams@ >R
DUP R@ .frequency !
RCALC SWAP
R@ .R C!
DUP 
R@ .Rfreq !
R@ .OMD @
OMD
DUP R@ .pllfreq !
FMD
B !
A !
R> .OMD !
msna_p1!
msna_p2!
msna_p3!
msx_p1!
msx_divby4!
rx_div!
reg!
;

--- SI5351 I2C words - we can use auto register address increment in later versions, but for now...

--- Read byte at register adr in si5351
: VFOC@ ( adr -- byte )
<I2C vfoadr I2C! I2C!				--- select the register at 'addr' 
<I2C vfoadr 1+ I2C! nakI2C@ I2C>	--- read the contents
--- SWAP DROP					--- drop the mask value and return the byte
;
--- Write byte to register adr in si5351
pub VFOC! ( byte adr -- )
<I2C vfoadr I2C! I2C! I2C! I2C>
;

--- Wait until si5351 initialisation complete
pri VFOINIT?	( -- ) 
BEGIN
	0 VFOC@
	$80 AND 0=
UNTIL
;

--- Disable all clock outputs
pri VFO_OPS_DIS	( -- )
$FC 3 VFOC! ;

--- Power down all clocks using register 16-23
pri VFOCLKSOFF	( -- )
	16 FROM 8 FOR
			$80 I VFOC!
	NEXT
;

--- Set crystal as both PLL source
pri VFOXTAL	( -- )
0 15 VFOC!
;

--- Set all disabled outputs low
pri VFOOUTLOW	( -- )
0 24 VFOC!
;

--- Init Multisynths
pri VFOSYNTHINIT	( -- )
0 42 VFOC!
1 43 VFOC!
0 47 VFOC!
0 48 VFOC!
0 49 VFOC!
0 50 VFOC!
1 51 VFOC!
0 55 VFOC!
0 56 VFOC!
0 57 VFOC!
; 

--- Power up CLK, PLL, MS for currently selected channel
pri VFOPLLON
RFpin@ IF $4F 16 ELSE $6F 17 THEN
VFOC!
;

--- Reference load setup
pri VFOREFSET
$12 183 VFOC!
;


--- CLK output enable  for currently selected channel
: VFOCLK_ON
3 VFOC@
RFpin@ IF $FE ELSE $FD THEN
AND 3 VFOC!
;

--- reset the PLL on the currently active channel
: RESETPLL
RFpin@
	IF
		$80	
	ELSE
		$20
	THEN
	177 VFOC!
;

--- Initialise the SI5351 ready for frequency setting
pri VFO!		( -- )
VFOINIT?		--- wait until si5351 has initialised
VFO_OPS_DIS		--- disable both outputs
VFOCLKSOFF		--- power down all clocks
VFOXTAL			--- set crystal as PLL source
VFOOUTLOW		--- set all disabled O/Ps low
VFOSYNTHINIT	--- initialise multisynth 0
VFOREFSET		--- Reference load start up
RFpin@
2 0 DO
	I RFpin!
	VFOPLLON	--- power up CLK, PLL, MS0
	VFOCLK_ON	--- CLK output enable
LOOP
RFpin!
;

--- Send the FMD to the current si5351 channel
pri FMD!
RFparams@ >R
RFpin@
	IF
		R@ .reg0 C@ 34 VFOC!
		R@ .reg1 C@ 35 VFOC!
		R@ .reg2 C@ 36 VFOC!
		R@ .reg3 C@ 37 VFOC!
		R@ .reg4 C@ 38 VFOC!
		R@ .reg5 C@ 39 VFOC!
		R@ .reg6 C@ 40 VFOC!
		R> .reg7 C@ 41 VFOC!
	ELSE
		R@ .reg0 C@ 26 VFOC!
		R@ .reg1 C@ 27 VFOC!
		R@ .reg2 C@ 28 VFOC!
		R@ .reg3 C@ 29 VFOC!
		R@ .reg4 C@ 30 VFOC!
		R@ .reg5 C@ 31 VFOC!
		R@ .reg6 C@ 32 VFOC!
		R> .reg7 C@ 33 VFOC!
	THEN
;

--- Send the OMD to the current si5351 channel
pri OMD!
RFparams@ >R
RFpin@
	IF
		R@ .reg10 C@ 52 VFOC!
		R@ .reg11 C@ 53 VFOC!
		R> .reg12 C@ 54 VFOC!
	ELSE
		R@ .reg10 C@ 44 VFOC!
		R@ .reg11 C@ 45 VFOC!
		R> .reg12 C@ 46 VFOC!
	THEN
;

--- set the si5351 current channel to frequency
pub RFtune		( frequency -- )
	  VFO!		--- initialise si5351
	  PARAM!	--- calculate register values
	  FMD!		--- send the FMD registers to the SI5351
	  OMD!		--- send the OMD registers to the SI5351
	  RESETPLL	--- reset the pll
;

--- Test Words

--- display all params for the selected channel
pub RFparam.		( -- )
CR CR ." RFparams settings..."
RFparams@
CR ."  frequency = " DUP .frequency @.
CR ."          R = " DUP .R C@ .
CR ."      Rfreq = " DUP .Rfreq @.
CR ."        OMD = " DUP .OMD @.
CR ."    pllfreq = " DUP .pllfreq @.
CR ."          A = " A @.
CR ."          B = " B @.
CR ."          C = " C .
CR ."    msna_p1 = " DUP .msna_p1 @.
CR ."    msna_p2 = " DUP .msna_p2 @.
CR ."    msna_p3 = " DUP .msna_p3 @.
CR ."     msx_p1 = " DUP .msx_p1 @.
CR ."       reg0 = " DUP .reg0 C@ .
CR ."       reg1 = " DUP .reg1 C@ .
CR ."       reg2 = " DUP .reg2 C@ .
CR ."       reg3 = " DUP .reg3 C@ .
CR ."       reg4 = " DUP .reg4 C@ .
CR ."       reg5 = " DUP .reg5 C@ .
CR ."       reg6 = " DUP .reg6 C@ .
CR ."       reg7 = " DUP .reg7 C@ .
CR ."       reg8 = " DUP .reg8 C@ .
CR ."       reg9 = " DUP .reg9 C@ .
CR ."      reg10 = " DUP .reg10 C@ .
CR ."      reg11 = " DUP .reg11 C@ .
CR ."      reg12 = " DUP .reg12 C@ .
CR ."      reg13 = " DUP .reg13 C@ .
CR ."      reg14 = " DUP .reg14 C@ .
CR ."      reg15 = " DUP .reg15 C@ .
CR ." msx_divby4 = " DUP .msx_divby4 C@ .
CR ."     rx_div = "  .rx_div C@ .
CR
;


--- Display a byte as 8 binary digits
pub .BIN8	( n -- )
8 FOR
	DUP 7 SHR >b
	48 + EMIT
	2*
NEXT
DROP ;



--- Display ONE si5351 register
pub .VFOREG	( adr -- )
	TAB ." Register "
	DUP DECIMAL . TAB ." : "	--- display register address
	DUP VFOC@					--- read the register
	DUP . ." decimal " TAB		--- and display it in decimal
	DUP .BYTE ."  hex " TAB		--- also in hex
	BINARY .BIN8 ."  binary"	--- and also in binary
	DROP CR
	DECIMAL
;

pri .RES TAB ." Reserved" CR ;

--- Display all si5351 registers
pub .VFOREGS ( -- )
	CR CR
	TAB ." si5351 REGISTER READOUT"
	CR CR
	4 FOR I .VFOREG NEXT
	.RES
	9 .VFOREG
	.RES
	15 FROM 13 FOR I .VFOREG NEXT .RES
	29 FROM 7 FOR I .VFOREG NEXT .RES 
	37 FROM 134 FOR I .VFOREG NEXT .RES
	177 .VFOREG .RES
	183 .VFOREG .RES 
	187 .VFOREG 
;

--- This is a cross check that the params were calculated correctly
pub freqcheck	( -- calculated frequency from parameters )
	  RFparams@ >R
	  fref A @ *			--- fref * A
	  fref B @ C */			--- fref * B / C
	  +
	  R@ .R C@ /
	  R> .OMD @ /
;

--- Display the PLL lock status - PLL locked if healthy
pub LOCK
0 VFOC@
CR ." Channel A pll "
DUP $20 AND IF ." unlocked" ELSE ." locked" THEN
CR ." Channel B pll "
$40 AND IF ." unlocked" ELSE ." locked" THEN
CR
;

: t 3700000 RFtune ;

END


Comments

  • Well done Bob, this should come in handy for the P2D2 as well. I will have to test it out this weekend!
    When I first looked at all the registers on the Si5351 that need to be setup, I decided I'd leave that job for another day.
    Maybe I should do this more often :smile:
  • Super useful! Thanks for sharing.
  • bob_g4bbybob_g4bby Posts: 440
    edited 2020-12-05 11:22
    Thanks both - the code is far from optimal, but is a working starter. Took a while to get so many things right, with a lot of interactive debugging, which only Forth really provides. I'll start cleaning it up over the next few weeks. Peter - I saw your comment about starting with programming the SI5351 and finished this off quick in case you could use it as a 'short cut'.

    Wet and cold and murky here this weekend, so the ham radio's hissing gently away on 80 metres, the study's nice and warm and I'm fiddling with the SI5351 and listening to the result on the radio. The OWON scope comes in handy too. Was out in the little three wheel car the other day, to blow the spiders out of her and fill up with juice. I soon came home - too cold on the face, despite padded suit, woolly hat and wrap-around specs! Feels just like when you eat icecream too fast - aaagh!
  • bob_g4bbybob_g4bby Posts: 440
    edited 2020-12-06 15:32
    I found a bug in PARAM! so I've updated the code at the top of this discussion. I've also removed .A .B and .C from the RFparam field. A, B are just normal variables and C is a constant now. Both channels seem reliable now.

    Whilst monitoring CLK1 set at 400kHz, ripple from CLK0 at 40MHz was easily seen. Not enough to impinge on any digital system, but I see why radio buffs recommend only using one channel per chip for best purity.
  • bob_g4bbybob_g4bby Posts: 440
    edited 2020-12-23 06:42
    Here's a new, optimised version of the SI5351 controller suitable for radio tuning. The number of bytes sent to the SI5351 when a frequency change is demanded is always kept to the minimum required. E.g. if a 10Hz frequency change is made, only 4 bytes are sent to the SI5351. 1kHZ, 10kHz step change requires 6 bytes. This means:-

    1. Maybe - Less disturbance to the output signal, which needs to be as jitter-free as possible for best performance.
    2. The SI5351 can be swept in frequency that bit faster (I intend to add a rotary control eventually).

    The commands are as follows:-

    RFinit ( -- ) Initialise the SI5351 ready for use. Turns output pins CLK0 and CLK1 off ( no output pulses )
    CLK0 ( -- ) Select the output from pin CLK0 as the currently selected channel - all commands will go to that channel until the other channel is selected
    CLK1 ( -- ) Select the output from pin CLK1 as the currently selected channel - all commands will go to that channel until the other channel is selected
    RFtune ( frequency -- ) Set the currently selected channel to 'frequency' Hz. This has been tested over the frequency range 4500 to 120000000 Hz
    RFon ( -- ) Turn the currently selected channel on, approx 3.3V high pulses will appear at the output
    RFoff ( -- ) Turn the currently selected channel off
    --- SI5351 DRIVER version 2 FOR TACHYON 5V7 Bob Edwards Dec 2020
    --- Optimised for radio tuning by minimising the number of bytes sent to the SI5351
    --- when a tuning step is demanded
    
    TACHYON V5
    
    IFDEF SI5351
    	FORGET SI5351
    }
    module SI5351 ." SI5351 dual channel RF oscillator driver" ;
    
    
    --- Record types
    
    --- start a record definition, leave zero offset into the record tos
    0	:=	[FIELDS		( 0 -- )
    
    --- define a new field within a record
    pre FIELD				  
    	CREATE OVER W, +        ( offset1 datasize -- offset2 )
    	DOES> R> W@ +           ( recaddr -- recaddr+offset )
    	;
    
    --- finalise a record definition
    ALIAS := FIELDS]
    
    
    --- define a record array type
    pre RECORDS ( fielddef recordnumber <arrayname> -- ; arrayindex -- adr )
    	GRAB 
    	CREATE			--- in the dictionary entry...
    		org@ W,		--- save start address of data
    		OVER W,		--- and save the record size
    		* @org W+!  --- then in the dataspace allot reqd no. of bytes
    	DOES>
    		R@ W@		--- read the start address of the data
    		R> 2+ W@	--- read the record size
    		ROT * +		--- and compute the start address of the reqd record
    ;
    	
    --- constants
    $C0      	:= vfoadr				--- SI5351 i2c address
    25008325 	:= fref					--- nominal crystal frequency driving the si5351
    750000000	:= pllmid				--- PLL frequency midrange
    600000000	:= pllmin				--- PLL minimum permitted frequency
    900000000	:= pllmax				--- PLL maximum permitted frequency
    1048575		:= C					--- FMD parameter C is maintained as a constant 	
    
    --- variables
    long frequency						--- required o/p frequency for the active channel
    long Rfreq							--- reqd OMD o/p frequency, before R divider
    long pllfreq						--- reqd pll frequency 
    long A								--- A parameter of the FMD divider
    long B								--- B parameter of the FMD divider
    byte R								--- The R divider parameter
    byte LASTREG						--- address of SI5351 register last written to in autoincrement mode
    byte pllreset						--- pllreset set 1 if pll reset needed
    byte RFactive						--- set to 0 or 1 to select CLK0 or CLK1 as active channel for cmds
    
    --- Storage definition for one RF channel - remember word and long boundaries
    --- the size of the record has to be maintained a multiple of 4, so all records will be aligned
    --- This can be achieved by an extra dummy entry if need be
    --- NB keep .msx_p1, .msna_p2 and .msna_p1 in this order and together
    --- This ordering is relied on to speed up register transmission to the SI5351
    
    [FIELDS
    	4 FIELD .frequency		--- output frequency
    	4 FIELD .OMD			--- OMD
    	4 FIELD .msx_p1			--- OMD intermediate value + rx_div + msx_divby4
    	4 FIELD .msna_p2		--- FMD intermediate value
    	4 FIELD .msna_p1		--- FMD intermediate value
    FIELDS] RFparam
    
    
    --- Create a 3 record array for the two independent RF sources + another record as scratchpad
    
    4 ALIGNORG								--- Ensure array starts on a long boundary
    RFparam 3 RECORDS RFparams				--- record 0 = CLK0 params, record 1 = CLK1 params
    										--- record 3 = copy of last params of active channel
    										--- to allow SI5351 register changes to be detected
    
    --- CLK0, CLK1 are used to select the active channel to be worked on, this state stored in variable RFactive
    
    pri RFparams@ RFactive C@ RFparams ; 	( -- adr ) --- start adr of active channel params
    pri RFoldparams@ 2 RFparams ;			( -- adr ) --- start adr of params from last cycle
    pub CLK0 0 RFactive C! ;				( -- ) --- select CLK0 as the active channel
    pub CLK1 1 RFactive C! ;				( -- ) --- select CLK1 as the active channel
    
    --- Calculation of SI5351 register values
    
    --- from the new output frequency and old OMD setting - the latter is allowed to be any number on start up
    --- calculate the new OMD as d + e=0 / f=1, which is just d, and new pll frequency required
    
    --- calculate R divider and equivalent o/p frequency
    pri RCALC	( freq1 -- R freq2 )
    	DUP 500000 <
    	IF
    	  2							--- R = 2 seed value 
    	  7 FOR 
    			2DUP * 500000 >		--- check if freq1 * R > 500kHz
    			IF
    				LEAVE			--- yes, f now > 500kHz,so leave loop
    			ELSE
    				2*				--- no, then set R = R * 2
    			THEN 
    		NEXT 					--- and try again
    		SWAP 2DUP * SWAP DROP
    	ELSE
    		1 SWAP					--- R is 1 for frequencies over 500000, freq2 is no change
    	THEN
    ;
    
    --- calculate the new Output Multisynth Divider, flg=1 if pll reset reqd.
    pri OMD		( freq oldOMD -- newOMD pllfreq flg )
    	2DUP *
    	DUP pllmin pllmax WITHIN	
    	IF					( freq oldOMD pllfreq )
    		ROT DROP 0
    	ELSE				( freq oldOMD pllfreq )
    		DROP DROP DUP	( freq freq )
    		pllmid SWAP /	( freq newOMD )
    		DUP 1 AND
    		IF
    			1+
    		THEN			( freq newOMD' )
    		SWAP OVER * 1	( newOMD pllfreq )						
    	THEN
    ;
    
    
    --- calculate a,b for parameters  a + b / c for the Feedback Multisynth Divider from the pll frequency
    --- C is a constant, $FFFFF, the largest value permitted, so that widest b range gives best frequency precision
    pri FMD					( pllfreq -- a b )
    	DUP fref / SWAP			( -- a pllfreq )
    	C fref */				( -- a c*pllfreq/Fref )
    	OVER C * - 				( -- a b )
    	;						( -- a b )
    
    --- The SI5351 requires the above parameters to be packed in the following way...
    
    --- calculate msna_p1
    pri msna_p1!
    	A @ 7 <<		--- A*128
    	B @ 7 <<
    	C /				--- 128*B/C
    	+
    	512 -
    	RFparams@ .msna_p1 !
    	;
    
    --- calculate msna_p2
    pri msna_p2!
    	B @ 7 << DUP				--- 128*B
    	C /							--- (128*B)/C
    	C *							--- C*((128*B)/C)
    	- 
    	$F00000 +					--- Set top four bits of msna_p3, which is a constant $FFFFF
    	RFparams@ .msna_p2 !		--- 128*B-C*((128*B)/C)
    	;
    
    
    --- calculate msx_p1, complete with msx_divby4 and rx_div bit fields
    pri msx_p1!		( -- )
    	RFparams@ >R
    	R@ .OMD @
    	128 * 512 -
    	Rfreq @ 150000000 >
    		IF
    			$0C0000 +			--- MS0_DIVBY4
    		THEN
    	R C@ DUP 1 =
    		IF
    			DROP 0
    		ELSE
    			>| 20 <<			--- R0_DIV
    		THEN
    	+
    	R> .msx_p1 !
    	;
    
    --- Copy the active channel params record to the scratchpad record
    pri OLDPARAM!	( -- )
    	RFparams@ RFoldparams@ RFparam CMOVE
    	;
    
    --- calculate and store all parameters in the currently selected channel record
    pri PARAM!	( frequency -- )
    	RFparams@ >R
    	DUP frequency !			--- o/p frequency in active channel record
    	RCALC SWAP
    	R C!						--- save the R divider parameter
    	DUP 
    	Rfreq !						--- save the OMD divider o/p frequency
    	R@ .OMD @
    	OMD
    	pllreset C!					--- save pllreset - 1 = reset needed
    	DUP pllfreq !				--- save pllfreq
    	FMD
    	B !							--- save A
    	A !							--- save B
    	R> .OMD !					--- save OMD
    	msna_p1!					--- calculate msna_p1
    	msna_p2!					--- calculate msna_p2
    	msx_p1!						--- calculate msx_p1
    ;
    
    --- SI5351 I2C words
    
    --- Read byte at register adr in si5351
    pri VFOC@ ( adr -- byte )
    	<I2C vfoadr I2C! I2C!				--- select the register at 'addr' 
    	<I2C vfoadr 1+ I2C! nakI2C@ I2C>	--- read the contents
    	;
    	
    --- Write byte to register adr in si5351
    pri VFOC! ( byte adr -- )
    	<I2C vfoadr I2C! I2C! I2C! I2C>
    	;
    
    --- Write a byte to SI5351 register using autoincrement address where possible
    pri INCVFOC! ( byte adr -- )
    DUP LASTREG C@ 1+ =			--- is this register = last register +1?
    IF
    	DROP 
    	I2C!					--- yes, just send the register value
    ELSE
    	<I2C vfoadr I2C! I2C! I2C!	--- no, set up the register address and then send value
    THEN
    ;
    
    --- part of sendFMD and sendOMD
    pri sendbytes	( -- )
    	I C@ 1@ C@ <>			--- Are the new and old byte values different?
    	IF
    		I C@ 2@ INCVFOC!	--- Yes, so send the new byte to the SI5351 register
    		2@ LASTREG C!		--- Save reg address to check for autoincrement
    	THEN
    	@1 --					--- Point to next lowest old byte
    	@2 ++					--- Increment register address
    	;
    
    --- Send FMD to the SI5351 - all bytes if 1st time, else only changed bytes
    pri sendFMD	( -- )
    	RFactive C@
    	IF 36 ELSE 28 THEN 2!		--- register start address, depends on active channel selected, store in memory 2
    	RFoldparams@ .msna_p1 2 + 1!	--- memory 1 = pointer to MS byte of old .msna_p1
    	RFparams@ .msna_p1 2 +		--- pointer to MS byte of .msna_p1
    	FROM -1 BY 3 FOR			--- Do for all 3 bytes, decrementing new byte pointer
    		sendbytes
    	NEXT
    	RFoldparams@ .msna_p2 2 + 1!	--- memory 1 = pointer to MS byte of old .msna_p1
    	RFparams@ .msna_p2 2 +		--- pointer to MS byte of .msna_p1
    	FROM -1 BY 3 FOR			--- Do for all 3 bytes, decrementing new byte pointer
    		sendbytes
    	NEXT
    	; 
    
    --- Send OMD to the SI5351 - all bytes if 1st time, else only changed bytes
    pri sendOMD ( -- )
    	RFactive C@
    	IF 52 ELSE 44 THEN 2!		--- register start address, depends on active channel selected, store in memory 2
    	RFoldparams@ .msx_p1 2 + 1!	--- memory 1 = pointer to bottom of previous params, MS byte of old .msx_p1
    	RFparams@ .msx_p1 2 +		--- point to MS byte of new .msx_p1
    	FROM -1 BY 3 FOR			--- Do for all 3 bytes, decrementing new byte pointer
    		sendbytes
    	NEXT
    	;
    
    --- Wait until si5351 initialisation complete
    pri VFOINIT?	( -- ) 
    	BEGIN
    		0 VFOC@
    		$80 AND 0=
    	UNTIL
    	;
    
    --- Disable all clock outputs
    pri VFO_OPS_DIS	( -- )
    	$FC 3 VFOC! ;
    
    --- Power down all clocks using register 16-23
    pri VFOCLKSOFF	( -- )
    	16 FROM 8 FOR
    			$80 I VFOC!
    	NEXT
    ;
    
    --- Set crystal as both PLL source
    pri VFOXTAL	( -- )
    	0 15 VFOC!
    	;
    
    --- Set all disabled outputs low
    pri VFOOUTLOW	( -- )
    	0 24 VFOC!
    	;
    
    --- Init Multisynth constants
    pri VFOSYNTHINIT	( -- )
    	$FF 26 VFOC!
    	$FF 27 VFOC!
    	$FF 34 VFOC!
    	$FF 35 VFOC!
    	0 42 VFOC!
    	1 43 VFOC!
    	0 47 VFOC!
    	0 48 VFOC!
    	0 49 VFOC!
    	0 50 VFOC!
    	1 51 VFOC!
    	0 55 VFOC!
    	0 56 VFOC!
    	0 57 VFOC!
    	; 
    
    --- Power up CLK, PLL, MS for currently selected channel
    pri VFOPLLON
    	RFactive C@ 
    	IF 
    		$4F 16
    	ELSE
    		$6F 17
    	THEN
    	VFOC!
    	;
    
    --- Reference load setup
    pri VFOREFSET
    	$12 183 VFOC!
    	;
    
    
    --- CLK output enable  for currently selected channel
    pub RFon
    3 VFOC@
    RFactive C@
    IF
    	$FD
    ELSE
    	$FE
    THEN
    AND 3 VFOC!
    ;
    
    pub RFoff
    3 VFOC@
    RFactive C@
    IF
    	$02
    ELSE
    	$01
    THEN
    OR 3 VFOC!
    ;
    
    --- reset the PLL on the currently active channel
    : RESETPLL
    RFactive C@
    	IF
    		$80	
    	ELSE
    		$20
    	THEN
    	177 VFOC!
    ;
    
    --- Initialise the SI5351 ready for frequency setting
    pri RFinit		( -- )
    	VFOINIT?		--- wait until si5351 has initialised
    	VFO_OPS_DIS		--- disable both outputs
    	VFOCLKSOFF		--- power down all clocks
    	VFOXTAL			--- set crystal as PLL source
    	VFOOUTLOW		--- set all disabled O/Ps low
    	VFOSYNTHINIT	--- initialise multisynth constants
    	VFOREFSET		--- Reference load start up
    	RFactive C@
    	2 0 DO
    		  I RFactive C!
    		  VFOPLLON	--- power up CLK, PLL, MS0
    		  RFoff		--- but set both o/ps off for now
    		LOOP
    	RFactive C!
    ;
    
    --- set the si5351 current channel to frequency
    pub RFtune		( frequency -- )
    	  0 LASTREG C!	--- Ensure we get a proper I2C address setting start
    	  OLDPARAM!
    	  PARAM!	--- calculate register values
    	  sendOMD	--- send the OMD registers to the SI5351
    	  sendFMD	--- send the FMD registers to the SI5351
    	  I2C>		--- end the i2C transfer
    	  pllreset C@
    	  IF
    		RESETPLL
    	  THEN		--- reset the pll
    ;
    
    --- Test Words
    
    --- display all params for the selected channel
    pub RFparam.		( -- )
    CR CR ." RFparams settings..."
    RFparams@
    CR ."  frequency = " frequency @.
    CR ."          R = " R C@ .
    CR ."      Rfreq = " Rfreq @.
    CR ."        OMD = " DUP .OMD @.
    CR ."    pllfreq = " pllfreq @.
    CR ."          A = " A @.
    CR ."          B = " B @.
    CR ."          C = " C .
    CR ."    msna_p1 = " DUP .msna_p1 @.
    CR ."    msna_p2 = " DUP .msna_p2 @.
    CR ."    msna_p3 = " $FFFFF .
    CR ."     msx_p1 = " DUP .msx_p1 @.
    CR ."   pllreset = " pllreset C@ .
    CR
    ;
    
    --- Display a byte as 8 binary digits
    pub .BIN8	( n -- )
    8 FOR
    	DUP 7 SHR >b
    	48 + EMIT
    	2*
    NEXT
    DROP ;
    
    --- Display ONE si5351 register
    pub .VFOREG	( adr -- )
    	TAB ." Register "
    	DUP DECIMAL . TAB ." : "	--- display register address
    	DUP VFOC@					--- read the register
    	DUP . ." decimal " TAB		--- and display it in decimal
    	DUP .BYTE ."  hex " TAB		--- also in hex
    	BINARY .BIN8 ."  binary"	--- and also in binary
    	DROP CR
    	DECIMAL
    ;
    
    pri .RES TAB ." Reserved" CR ;
    
    --- Display all si5351 registers
    pub .VFOREGS ( -- )
    	CR CR
    	TAB ." si5351 REGISTER READOUT"
    	CR CR
    	4 FOR I .VFOREG NEXT
    	.RES
    	9 .VFOREG
    	.RES
    	15 FROM 13 FOR I .VFOREG NEXT .RES
    	29 FROM 7 FOR I .VFOREG NEXT .RES 
    	37 FROM 134 FOR I .VFOREG NEXT .RES
    	177 .VFOREG .RES
    	183 .VFOREG .RES 
    	187 .VFOREG 
    ;
    
    --- This is a cross check that the params were calculated correctly
    pub freqcheck	( -- calculated_frequency_from_parameters )
    	  RFparams@ >R
    	  fref A @ *			--- fref * A
    	  fref B @ C */			--- fref * B / C
    	  +
    	  R C@ /
    	  R> .OMD @ /
    ;
    
    --- Display the PLL lock status - PLL locked if healthy
    pub LOCK
    0 VFOC@
    CR ." Channel A pll "
    DUP $20 AND IF ." unlocked" ELSE ." locked" THEN
    CR ." Channel B pll "
    $40 AND IF ." unlocked" ELSE ." locked" THEN
    CR
    ;
    
    --- Tune from start to stop frequency, stepping 10Hz as fast as possible, stop if key pressed
    pub SWEEP	( startfreq stopfreq -- )
    RFinit >R
    RFon
    BEGIN					--- save the stop frequency on the R stack
    	DUP RFtune
    	10 +
    	DUP R@ => KEY 0<> OR
    UNTIL
    DROP
    R> DROP					--- clear the R and data stacks
    ;
    
    : SWEEPtest
    3700000 3770000 SWEEP
    ;
    
    END
    
    
  • MaciekMaciek Posts: 675
    edited 2020-12-17 17:50
    @bob_g4bby ,

    Should you have posted this two hours earlier I would've included a Si5351 breakout board with my order to Mouser just to play with your code and my ugly P1 board :).

    Now I need to wait until the next one and that would come no sooner than in the 2021.
    I'm trying to save some money for the P2D2 board(s) I will hopefully be able to get sooner rather than later.
  • OK, I finaly got the tiny Si5351 dev board from Mouser so I'll give it a go and play with your code. Most probably next week at the earliest. I'm looking forward to it.

  • OK Maciek, I hope it just works for you and maybe you can go on to add some code for an lcd display and shaft encoder? ;-)

  • It's possible and definitely a huge challenge for me so...why not ?
    It will take some time as I'm far from being literate in Tachyon but these two are on my list.
    Do you have any specific LCD or encoder in mind ? What interfaces or how many free pins do you anticipate to be available for this purpose ?

  • bob_g4bbybob_g4bby Posts: 440
    edited 2021-03-03 11:11

    If it was me, I have plenty of two line LCD displays in the junk box, but they do need level converter mosfets to drive them at 5V logic. You need four data lines + 2 control lines I think. Otherwise I'd look on the Parallax forum to see what display has been popular and use that? Look at Taqoz and use one of the displays supported there - converting the code to Tachyon might be quicker than writing a driver from scratch?

    A shaft encoder just needs two inputs and runs off 3v3 OK - it's just two grounded switches that need two pullups. I usually measure how many encoder pulses have occurred in a fixed time period, say 25mS and the sign denotes in which direction the encoder was turned. For radio tuning, it's useful to either square or perhaps cube this value, above a certain threshold so that a quick spin of the dial moves the frequency by a much larger amount than a slow one. I dislike having a separate button to select tuning rate. This feature does need tuning after trying it out - it needs to feel natural to the user, not a nuisance.

    Cheers, Bob G4BBY

  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2021-03-03 12:03

    Fortunately character LCD displays use TTL logic thresholds so you can drive them easily from 3.3V logic. Tachyon has a complete LCD driver that supports dimming and digital contrast control and control characters etc. It also supports big digits for 4 line displays.

    You can divert output to this device and you can use 2 or 4 line displays etc.
    There is no need to worry about any delays etc but I think I connected the display in 8-bit mode as I have found 4-bit mode can glitch and lose nibble synch. You won't need to worry about resistors on the data lines either since there is never a need to read from the display because instruction delays are compensated for in software.

    Look for CHARLCD.FTH and you can configure your hardware using LCDPINS like this:

    --- ch ln bl vc  ce rw rs d
        &20.4.18.14 &17.16.15.0 LCDPINS 
    
  • Bob mentioned this is meant for a radio. The real challenge is to integrate all of the code pieces together in a complete application and that calls for a top down approach, I think.
    I need to understand the requirements I would have for a radio in terms of user experience like for example I wouldn't like to transmit when I tune the radio but I would also like for the radio to "know" when I'm finished with tuning and dim a display when it's dark or light it up a bit when in gets brighter around. I need to separate the "must have" features from the "nice to have" ones. Not every feature can be added later on easily...

Sign In or Register to comment.