Jan-Langevad

SI5351 RF signal generator ESP32FORTH

Dec 23rd, 2025
59
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.92 KB | Software | 0 0
  1. \ SI5351 RF signal generator driver for ESP32forth - ver. 1.0 by Bob Edwards Sept 2025
  2. \ https://github.com/bob-g4bby/Bob-Edwards--Forth-Stuff/blob/main/ESP32/SI5351%20driver%20for%20ESP32forth%20v7.0.7.21.fth
  3. \ Bob-Edwards--Forth-Stuff/ESP32
  4. \ Written using ESP32forth version 7.0.7.21
  5. \ Does not currently run on v7.0.6.19 maybe due to 'Wire' library changes
  6. \ Does not compile on v7.0.5.4 since 'Structures' are not supported
  7.  
  8. \ Optimised for radio tuning by minimising data transfers to SI5351
  9. \ N.B. value fref will need tuning for accurate frequency output
  10. \ The crystals on the SI5351 modules aren't exactly 25MHz
  11. \ A new vocabulary SI5351 is created to contain the driver words
  12.  
  13. only \ make sure we're in the FORTH vocabulary
  14.  
  15. \ If this code is alerady compiled, let's forget it
  16. DEFINED? *SI5351* [IF] forget *SI5351* [THEN]
  17. : *SI5351* ;
  18.  
  19. \ common forth words missing from ESP32forth
  20.  
  21. only
  22. forth definitions
  23.  
  24. \ returns true if n is within range of low and high inclusive, else returns false
  25. : within ( n lo hi -- flag )
  26. 1+ over -
  27. >r - r>
  28. u< 0<>
  29. ;
  30.  
  31. \ returns the bit position 0-31 of the MS bit of n1
  32. : >| ( n1 -- bitpos )
  33. 0
  34. begin
  35. swap 1 rshift dup 0<>
  36. while
  37. swap 1+
  38. repeat
  39. drop
  40. ;
  41.  
  42. \ display one tab
  43. : .TAB
  44. 9 emit
  45. ;
  46.  
  47. vocabulary SI5351 \ create the new SI5351 vocabulary
  48. SI5351 definitions \ all definitions that follow will go there
  49.  
  50. \ constants
  51.  
  52. $60 constant vfoadr \ SI5351 i2c address
  53. 750000000 constant pllmid \ PLL frequency midrange
  54. 600000000 constant pllmin \ PLL minimum permitted frequency
  55. 900000000 constant pllmax \ PLL maximum permitted frequency
  56. 1048575 constant C \ FMD parameter C is maintained as a constant
  57.  
  58. \ values
  59.  
  60. \ May need adjustment to suit your SI5351 board - the onboard oscillators are not trimmed accurately
  61. 25008325 value fref \ nominal crystal frequency driving the SI5351
  62.  
  63. \ variables
  64.  
  65. only STRUCTURES also SI5351
  66.  
  67. variable changedCLKX \ flag set true if CLK0 or CLK1 has been executed
  68. variable i2creg \ required i2c register address
  69. variable i2cdata \ data to send to an SI5351 register
  70. variable frequency \ required o/p frequency for the active channel
  71. variable Rfreq \ reqd OMD o/p frequency, before R divider
  72. variable pllfreq \ reqd pll frequency
  73. variable A \ A parameter of the FMD divider
  74. variable B \ B parameter of the FMD divider
  75. variable R \ The R divider parameter
  76. variable LASTREG \ address of SI5351 register last written to in autoincrement mode
  77. variable pllreset \ pllreset set 1 if pll reset needed
  78. variable m1 \ scratch register 1
  79. variable m2 \ scratch register 2
  80.  
  81. struct RFparam
  82. \ i32 field .frequency \ output frequency
  83. i32 field .OMD \ OMD
  84. i32 field .msx_p1 \ OMD intermediate value + rx_div + msx_divby4
  85. i32 field .msna_p2 \ FMD intermediate value
  86. i32 field .msna_p1 \ FMD intermediate value
  87.  
  88. create CLK0data RFparam allot \ create CLK0 workspace
  89. create CLK1data RFparam allot \ create CLK1 workspace
  90. create SCRATCHdata RFparam allot \ create workspace so we can compare old and new data
  91. \ and thus determine which changed bytes need sending
  92. \ Scratch register access
  93. : m1@ m1 @ ;
  94. : m2@ m2 @ ;
  95. : m1! m1 ! ;
  96. : m2! m2 ! ;
  97.  
  98. \ CLK0, CLK1 are used to select the active channel to be worked on, this state stored in vector RFactive
  99.  
  100. defer RFActive
  101.  
  102. : CLK0 ( -- )
  103. ['] CLK0data is RFActive \ CLK0 channel will receive all subsequent commands
  104. -1 changedCLKX ! \ signal all frequency setting bytes need to be sent next time
  105. ;
  106.  
  107. : CLK1 ( -- )
  108. ['] CLK1data is RFActive \ CLK1 channel will receive all subsequent commands
  109. -1 changedCLKX ! \ signal all frequency setting bytes need to be sent next time
  110. ;
  111.  
  112. CLK0
  113.  
  114. \ Read which CLK channel is currently set to take commands, true=CLK0, false-CLK1
  115. : CLK? ( -- flg )
  116. RFActive
  117. CLK1data =
  118. ;
  119.  
  120.  
  121. \ Calculation of register values from a wanted frequency
  122.  
  123. \ At frequencies below 500kHz an R prescaler is necessary
  124. \ so calculate the required R divider and the equivalent o/p frequency, freq2
  125.  
  126. : RCALC ( freq1 -- R freq2 )
  127. DUP 500000 <
  128. IF
  129. 2 \ R = 2 seed value
  130. 7 0 DO
  131. 2DUP * 500000 > \ check if freq1 * R > 500kHz
  132. IF
  133. LEAVE \ yes, f now > 500kHz,so leave loop
  134. ELSE
  135. 2* \ no, then set R = R * 2
  136. THEN
  137. LOOP \ and try again
  138. SWAP 2DUP * SWAP DROP
  139. ELSE
  140. 1 SWAP \ R is 1 for frequencies over 500000, freq2 is no change
  141. THEN
  142. ;
  143.  
  144. \ Calculate the new Output Multisynth Divider, flg=1 if pll reset reqd.
  145. : OMD ( freq oldOMD -- newOMD pllfreq flg )
  146. 2DUP *
  147. DUP pllmin pllmax WITHIN
  148. IF \ freq oldOMD pllfreq
  149. ROT DROP 0
  150. ELSE \ freq oldOMD pllfreq
  151. DROP DROP DUP \ freq freq
  152. pllmid SWAP / \ freq newOMD
  153. DUP 1 AND
  154. IF
  155. 1+
  156. THEN \ freq newOMD'
  157. SWAP OVER * 1 \ newOMD pllfreq
  158. THEN
  159. ;
  160.  
  161. \ calculate a,b for parameters a + b / c for the Feedback Multisynth Divider from the pll frequency
  162. \ C is a constant, $FFFFF, the largest value permitted, so that widest b range gives best frequency precision
  163.  
  164. : FMD ( pllfreq -- a b )
  165. DUP fref / SWAP \ -- a pllfreq
  166. C fref */ \ -- a c*pllfreq/Fref
  167. OVER C * - \ -- a b
  168. ; \ -- a b
  169.  
  170. \ The SI5351 requires the above parameters to be packed in the following way...
  171.  
  172. \ calculate msna_p1
  173. : msna_p1!
  174. A @ 7 lshift \ A*128
  175. B @ 7 lshift
  176. C / \ 128*B/C
  177. +
  178. 512 -
  179. RFactive .msna_p1 !
  180. ;
  181.  
  182. \ calculate msna_p2
  183. : msna_p2!
  184. B @ 7 lshift DUP \ 128*B
  185. C / \ (128*B)/C
  186. C * \ C*((128*B)/C)
  187. -
  188. $F00000 + \ Set top four bits of msna_p3, which is a constant $FFFFF
  189. RFactive .msna_p2 ! \ 128*B-C*((128*B)/C)
  190. ;
  191.  
  192. \ calculate msx_p1, complete with msx_divby4 and rx_div bit fields
  193. : msx_p1! ( -- )
  194. RFactive >R
  195. R> DUP >R .OMD @
  196. 128 * 512 -
  197. Rfreq @ 150000000 >
  198. IF
  199. $0C0000 + \ MS0_DIVBY4
  200. THEN
  201. R @ DUP 1 =
  202. IF
  203. DROP 0
  204. ELSE
  205. >| 20 lshift \ R0_DIV
  206. THEN
  207. +
  208. R> .msx_p1 !
  209. ;
  210.  
  211. \ Copy the active channel params record to the scratchpad record
  212. : OLDPARAM! ( -- )
  213. RFactive SCRATCHdata RFparam CMOVE
  214. ;
  215.  
  216. \ Set all of Scratchdata <> RFactive, so all frequency setting bytes will get sent
  217. : OLDPARAM<> ( -- )
  218. RFparam 0 do
  219. RFActive I + C@
  220. invert 255 and
  221. SCRATCHdata I + C!
  222. loop
  223. ;
  224.  
  225. \ Calculate and store all parameters in the currently selected channel record
  226. : PARAM! ( frequency -- )
  227. RFactive >R
  228. DUP frequency ! \ save o/p frequency
  229. RCALC SWAP
  230. R ! \ save the R divider parameter
  231. DUP
  232. Rfreq ! \ save the OMD divider o/p frequency
  233. R> DUP >R .OMD @
  234. OMD
  235. pllreset ! \ save pllreset - 1 = reset needed
  236. DUP pllfreq ! \ save pllfreq
  237. FMD
  238. B ! \ save A
  239. A ! \ save B
  240. R> .OMD ! \ save OMD
  241. msna_p1! \ calculate msna_p1
  242. msna_p2! \ calculate msna_p2
  243. msx_p1! \ calculate msx_p1
  244. ;
  245.  
  246. \ I2C words to communicate with SI5351 ***************************************************************
  247.  
  248. only wire also SI5351 \ 'wire' is the vocabulary with the i2c interface words
  249.  
  250. \ initialise the I2C interface, define the GPIO pins used
  251. \ and wait for the SI5351 to initialise
  252. : Wire.start ( -- )
  253. 21 22 Wire.begin drop \ gpio21 set as sda, gpio22 set as scl
  254. ;
  255.  
  256.  
  257. \ Read byte at register regadr in SI5351
  258. : VFOC@ ( regadr -- byte )
  259. i2creg ! \ save the register address
  260. vfoadr Wire.beginTransmission
  261. i2creg 1 Wire.write drop \ send the SI5351 register address required -- that data needs to be at an address, not on the stack
  262. 0 Wire.endTransmission drop
  263. vfoadr 1 -1 Wire.requestFrom drop \ request one byte of register data and send a stop message
  264. Wire.Available drop \ # bytes to read
  265. Wire.Read \ read the SI5351 register contents
  266. ;
  267.  
  268. \ Write byte to register regadr in SI5351
  269. : VFOC! ( byte regadr -- )
  270. \ 2dup ." VFO!: sending to SI5351 register " . ." data byte " . cr \ debug display
  271. i2creg ! \ save the register address
  272. i2cdata ! \ save the data to be written to the register
  273. vfoadr Wire.beginTransmission
  274. i2creg 1 Wire.write drop \ send the SI5351 register required
  275. i2cdata 1 Wire.write drop \ send the data for the SI5351 register
  276. 1 Wire.endTransmission drop
  277. ;
  278.  
  279. \ Write a byte to SI5351 register using autoincrement address where possible
  280. : INCVFOC! ( byte regadr -- )
  281. \ 2dup ." INCVFO!: sending to SI5351 register " . ." data byte " . cr \ debug display
  282. i2creg ! \ save the register address
  283. i2cdata ! \ save the data to be written to the register
  284. i2creg @ LASTREG @ 1+ = \ is this register = last register +1?
  285. IF
  286. i2cdata 1 Wire.write drop \ send the data for the SI5351 register
  287. ELSE
  288. -1 Wire.endTransmission drop
  289. vfoadr Wire.beginTransmission \ send the required SI5351 register address
  290. i2creg 1 Wire.write drop \ send the SI5351 register required
  291. i2cdata 1 Wire.write drop \ send the data for the SI5351 register
  292. THEN
  293. ;
  294.  
  295. \ used internally by sendFMD and sendOMD
  296. \ variable @1 points to the old data
  297. \ variable @2 holds the SI5351 register address
  298. \ top of stack points to the new data
  299. : sendbytes ( ptr_to_new_data -- )
  300. 3 0 DO \ Do for all 3 bytes, decrementing new byte pointer
  301. DUP I - C@ m1@ C@ <> \ Are the new and old byte values different?
  302. IF
  303. DUP I - C@ m2@ INCVFOC! \ Yes, so send the new byte to the SI5351 register
  304. m2@ LASTREG ! \ Save reg address to check for autoincrement
  305. THEN
  306. -1 m1 +! \ Point to next lowest old byte
  307. 1 m2 +! \ Increment register address
  308. LOOP
  309. DROP
  310. ;
  311.  
  312. \ Send FMD to the SI5351 - all bytes if 1st time, else only changed bytes
  313. : sendFMD ( -- )
  314. CLK? \ read which clock is currently taking commands
  315. IF 36 ELSE 28 THEN m2! \ register start address, depends on active channel selected, store in memory 2
  316. SCRATCHdata .msna_p1 2 + m1! \ memory 1 = pointer to MS byte of old .msna_p1
  317. RFActive .msna_p1 2 + \ pointer to MS byte of .msna_p1
  318. sendbytes
  319. SCRATCHdata .msna_p2 2 + m1! \ memory 1 = pointer to MS byte of old .msna_p1
  320. RFActive .msna_p2 2 + \ pointer to MS byte of .msna_p2
  321. sendbytes
  322. ;
  323.  
  324. \ Send OMD to the SI5351 - all bytes if 1st time, else only changed bytes
  325. : sendOMD ( -- )
  326. CLK? \ read which clock is currently taking commands
  327. IF 52 ELSE 44 THEN m2! \ register start address, depends on active channel selected, store in memory 2
  328. SCRATCHdata .msx_p1 2 + m1! \ memory 1 = pointer to bottom of previous params, MS byte of old .msx_p1
  329. RFActive .msx_p1 2 + \ point to MS byte of new .msx_p1
  330. sendbytes
  331. ;
  332.  
  333. \ Wait until SI5351 initialisation complete
  334. : VFOINIT? ( -- )
  335. BEGIN
  336. 0 VFOC@
  337. $80 AND 0=
  338. UNTIL
  339. ;
  340.  
  341. \ Disable all clock outputs
  342. : VFO_OPS_DIS ( -- )
  343. $FC 3 VFOC!
  344. ;
  345.  
  346. \ Power down all clocks using register 16-23
  347. : VFOCLKSOFF ( -- )
  348. 8 0 DO
  349. $80 I 16 + VFOC!
  350. LOOP
  351. ;
  352.  
  353. \ Set crystal as both PLL source
  354. : VFOXTAL ( -- )
  355. 0 15 VFOC!
  356. ;
  357.  
  358. \ Set all disabled outputs low
  359. : VFOOUTLOW ( -- )
  360. 0 24 VFOC!
  361. ;
  362.  
  363. \ Init Multisynth constants
  364. : VFOSYNTHINIT ( -- )
  365. $FF 26 VFOC!
  366. $FF 27 VFOC!
  367. $FF 34 VFOC!
  368. $FF 35 VFOC!
  369. 0 42 VFOC!
  370. 1 43 VFOC!
  371. 0 47 VFOC!
  372. 0 48 VFOC!
  373. 0 49 VFOC!
  374. 0 50 VFOC!
  375. 1 51 VFOC!
  376. 0 55 VFOC!
  377. 0 56 VFOC!
  378. 0 57 VFOC!
  379. ;
  380.  
  381. \ Power up CLK, PLL, MS for currently selected channel
  382. : VFOPLLON
  383. CLK?
  384. IF
  385. $4F 16
  386. ELSE
  387. $6F 17
  388. THEN
  389. VFOC!
  390. ;
  391.  
  392. \ Reference load setup
  393. : VFOREFSET ( -- )
  394. $12 183 VFOC!
  395. ;
  396.  
  397. \ CLK output enable for currently selected channel
  398. : RFon ( -- )
  399. 3 VFOC@
  400. CLK?
  401. IF
  402. $FD
  403. ELSE
  404. $FE
  405. THEN
  406. AND 3 VFOC!
  407. ;
  408.  
  409. : RFoff ( -- )
  410. 3 VFOC@
  411. CLK?
  412. IF
  413. $02
  414. ELSE
  415. $01
  416. THEN
  417. OR 3 VFOC!
  418. ;
  419.  
  420. \ Reset the PLL on the currently active channel
  421. : RESETPLL ( -- )
  422. CLK?
  423. IF
  424. $80
  425. ELSE
  426. $20
  427. THEN
  428. 177 VFOC!
  429. ;
  430.  
  431. \ Initialise the SI5351 ready for frequency setting
  432. : RFinit ( -- )
  433. VFOINIT? \ wait until si5351 has initialised
  434. VFO_OPS_DIS \ disable both outputs
  435. VFOCLKSOFF \ power down all clocks
  436. VFOXTAL \ set crystal as PLL source
  437. VFOOUTLOW \ set all disabled O/Ps low
  438. VFOSYNTHINIT \ initialise multisynth constants
  439. VFOREFSET \ Reference load start up
  440. CLK1
  441. VFOPLLON \ power up CLK, PLL, MS0
  442. RFoff \ set o/p off for now
  443. CLK0
  444. VFOPLLON \ power up CLK, PLL, MS0
  445. RFoff \ set o/p off for now
  446. ;
  447.  
  448. \ set the si5351 current channel to frequency ( Hz )
  449. : RFtune ( frequency -- )
  450. 0 LASTREG ! \ Ensure we get a proper I2C address setting start
  451. OLDPARAM!
  452. PARAM! \ calculate register values
  453. changedCLKX @ IF \ are all frequency setting bytes needed to be sent?
  454. OLDPARAM<> \ Set all Scratchdata <> RFactive data
  455. 0 changedCLKX ! \ and reset that flag, so only changed bytes sent thereafter
  456. THEN
  457. sendOMD \ send the OMD registers to the SI5351
  458. sendFMD \ send the FMD registers to the SI5351
  459. 1 Wire.endTransmission drop \ end the i2C transfer
  460. pllreset @
  461. IF
  462. RESETPLL \ reset the pll
  463. THEN
  464. ;
  465.  
  466. \ Using CLK0, tune from start to stop frequency, stepping 10Hz as fast as possible, stop if key pressed
  467. \ This produces 1024 steps per second on an ESP32 WROOM 32, so a nice smooth sweep at 10Hz per step
  468. : RFsweep ( startfreq stopfreq -- )
  469. RFinit >R
  470. RFon
  471. BEGIN \ save the stop frequency on the R stack
  472. DUP RFtune
  473. 10 + \ step 10Hz higher
  474. DUP R> DUP >R >= KEY? OR \ stop when stopfreq reached or user presses key
  475. UNTIL
  476. DROP
  477. R> DROP \ clean up the R and data stacks
  478. ;
  479.  
  480.  
  481. \ Test words **********************************************************************
  482.  
  483. : *VFOTESTWORDS* ; \ Easy to forget from here if not needed
  484.  
  485. \ Test write / read from SI5351 works
  486. : VFO!@ ( byte regadr -- )
  487. 2dup swap ." Writing " . ." to register " .
  488. 2dup VFOC!
  489. swap drop
  490. dup VFOC@ \ regaddr data
  491. CR ." Reading " . ." from register " . CR
  492. ;
  493.  
  494. \ display all params for the selected channel
  495. : RFparam. ( -- )
  496. CR CR ." RFparams settings..."
  497. RFactive
  498. CR ." frequency = " frequency ?
  499. CR ." R = " R ?
  500. CR ." Rfreq = " Rfreq ?
  501. CR ." OMD = " DUP .OMD ?
  502. CR ." pllfreq = " pllfreq ?
  503. CR ." A = " A ?
  504. CR ." B = " B ?
  505. CR ." C = " C .
  506. CR ." msna_p1 = " DUP .msna_p1 ?
  507. CR ." msna_p2 = " DUP .msna_p2 ?
  508. CR ." msna_p3 = " $FFFFF .
  509. CR ." msx_p1 = " .msx_p1 ?
  510. CR ." pllreset = " pllreset ?
  511. CR
  512. ;
  513.  
  514.  
  515. \ Display ONE SI5351 register
  516. : VFOREG. ( adr -- )
  517. .TAB ." Register "
  518. DUP . .TAB ." : " \ display register address
  519. DUP VFOC@ \ read the register
  520. DUP . ." decimal " .TAB \ and display it in decimal
  521. HEX . DECIMAL ." hex " .TAB \ also in hex
  522. DROP CR
  523. ;
  524.  
  525. \ Display "reserved" for an illegal register number
  526. : .RES .TAB ." Reserved" CR ;
  527.  
  528. \ Display all SI5351 registers
  529. : VFOREGS. ( -- )
  530. CR CR
  531. .TAB ." SI5351 REGISTER READOUT"
  532. CR CR
  533. 4 0 DO I VFOREG. LOOP
  534. .RES
  535. 9 VFOREG.
  536. .RES
  537. 13 0 DO I 15 + VFOREG. LOOP .RES
  538. 7 0 DO I 29 + VFOREG. LOOP .RES
  539. 134 0 DO I 37 + VFOREG. LOOP .RES
  540. 177 VFOREG. .RES
  541. 183 VFOREG. .RES
  542. 187 VFOREG.
  543. ;
  544.  
  545. \ This checks that the params were calculated correctly by back-calculating the output frequency
  546. : FREQCHECK. ( -- )
  547. RFactive >R
  548. fref A @ * \ fref * A
  549. fref B @ C */ \ fref * B / C
  550. +
  551. R @ /
  552. R> .OMD @ /
  553. CR CR
  554. ." The si5351 params will give an output of " . ." Hz" CR
  555. ;
  556.  
  557. \ This displays all three structures that hold the SI5351 register settings
  558. : Strucs. ( -- )
  559. CR CLK0
  560. ." CLK0 settings" CR
  561. ." OMD = " RFactive .OMD ? CR
  562. ." msx_p1 = " RFactive .msx_p1 ? CR
  563. ." msna_p2 = " RFactive .msna_p2 ? CR
  564. ." msna_p1 = " RFactive .msna_p1 ? CR
  565. CLK1
  566. ." CLK1 settings" CR
  567. ." OMD = " RFactive .OMD ? CR
  568. ." msx_p1 = " RFactive .msx_p1 ? CR
  569. ." msna_p2 = " RFactive .msna_p2 ? CR
  570. ." msna_p1 = " RFactive .msna_p1 ? CR
  571. ." Scratch data settings" CR
  572. ." OMD = " SCRATCHdata .OMD ? CR
  573. ." msx_p1 = " SCRATCHdata .msx_p1 ? CR
  574. ." msna_p2 = " SCRATCHdata .msna_p2 ? CR
  575. ." msna_p1 = " SCRATCHdata .msna_p1 ? CR
  576. ;
  577.  
  578. forth definitions
  579. SI5351
  580. \ forget *VFOTESTWORDS*
  581.  
Advertisement
Add Comment
Please, Sign In to add comment