/****************************************************************************** * File Name : stm32_si5351.h * Description : STM32 library/driver for the Si5351 clock chip * from Skyworks Solutions, Inc. (former SiLabs) ****************************************************************************** * @author: Thomas Kuschel KW4NZ, created 2022-05-11 * * originally written by Petr Polasek, created Feb 16, 2018 ****************************************************************************** * DO NOT EDIT THIS FILE FOR CONFIGURATION, USE THE FOLLOWING PROCEDURE: * * Inside your main.c program or within your STM32 code: * Include this header to the the main.c: #include "stm32_si5351.h" * You've to initialize the I2C functionality first (e.g. with STM32CubeIDE) * Afterwards, when there is a handle like "I2C_HandleTypeDef hi2c1;", * you simply initialize this stm32_si5351 library in your main.c , * just after the MX_I2C1_Init(); * so between the USER CODE like using the given i2c handle "hi2c1": * * /\* USER CODE BEGIN 2 *\/ * si5351_init(&hi2c1); * /\* USER CODE END 2 *\/ * * The 7-bit device (slave) address of the Si5351 consist of a 6-bit fixed * address plus a user selectable LSB bit as shown in Figure 6 of the datasheet. * The LSB bit is selectable as 0 or 1 using the optional A0 pin which is useful * for applications that require more than one Si5351 on a single I2C bus. * Only the Si5351A 20-QFN and Si5351A 16-QFN have the A0 LSB pin option. * If a part does not have the A0 pin, the default address is 0x60 with * the A0 bit set to 0. * * So additionally you may drive more then one Si5351 at the same or * another I2C bus when calling the init function with the given * I2C bus address (default: 0x60) * ( - internally this I2C address is shifted to the left * for the proper usage of the I2C HAL driver i.e. it becomes 0xC0 ) * * Example: * \\ Define handlers for the sum of three SI5351 clock generators: * si5351_HandleTypeDef h_si5351[3]; * * // 1st SI5351 chip and with an A0 = 0: * h_si5351[0] = si5351_init(&hi2c1, 25000000, 0x60); * // 2nd SI5351 chip on the same I2C bus "hi2c1" but address line A0 = 1 * h_si5351[1] = si5351_init(&hi2c1, 27000000, 0x61); * // 3rd SI5351 chip on another IC2 bus with handle "hi2c2" *\/ * h_si5351[2] = si5351_init(&hi2c2, 25000000, 0x60); * * PROs: * The library is preemptive and can be used within an operating system. * All structures and used variables are dynamically allocated. * CONs: * Not fully tested, but used with FreeRTOS and with 2 I2C bus systems. * * CHANGES: * - Removed several defines, enums, etc. from the header file, b/c we do not * want to export them to other programs * * * The old simple example to get 50 kHz output with an 25 MHz crystal: * Si5351_ConfigTypeDef Si5351_ConfigStruct; * Si5351_StructInit(&Si5351_ConfigStruct); //initialize the structure with default "safe" values * Si5351_ConfigStruct.OSC.OSC_XTAL_Load = XTAL_Load_8_pF; //use 8 pF load for crystal * Si5351_ConfigStruct.PLL[0].PLL_Clock_Source = PLL_Clock_Source_XTAL; //select xrystal as clock input for the PLL * Si5351_ConfigStruct.PLL[0].PLL_Multiplier_Integer = 32; //multiply the clock frequency by 32, this gets us 800 MHz clock * Si5351_ConfigStruct.MS[0].MS_Clock_Source = MS_Clock_Source_PLLA; //select PLLA as clock source for MultiSynth 0 * Si5351_ConfigStruct.MS[0].MS_Divider_Integer = 250; //divide the 800 MHz by 250 this gets us 3.2 MHz * Si5351_ConfigStruct.CLK[0].CLK_R_Div = CLK_R_Div64; //divide the MultiSynth output by 64, this gets us 50 kHz * Si5351_ConfigStruct.CLK[0].CLK_Enable = ON; //turn on the output * Si5351_Init(&Si5351_ConfigStruct); //apply the changes * ******************************************************************************/ /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef STM32_SI5351_H #define STM32_SI5351_H #ifdef __cplusplus extern "C" { #endif /* Includes ------------------------------------------------------------------*/ #include #include /* register map of the Si5351 */ /* Private includes ----------------------------------------------------------*/ /* Exported types ------------------------------------------------------------*/ /* Si5351 instance typedef */ typedef struct __SI5351_HandleTypeDef *si5351_inst_t; /* Exported constants --------------------------------------------------------*/ /** @enum errno_t Error Number Constants, @TODO could also errno.h included!! */ typedef enum { EPERM = 1, /*!< Operation not permitted */ EIO = 5, /*!< I/O error */ ENOMEM = 12, /*!< Out of memory */ ENODEV = 19, /*!< No such device */ EBUSY = 16, /*!< Device or resource busy */ EINVAL = 22, /*!< Invalid argument */ EADDRINUSE = 98,/*!< Address already in use */ ETIMEDOUT = 116 /*!< Connection timed out */ } si5351_errno_t; /* Exported variables --------------------------------------------------------*/ /* Exported macros -----------------------------------------------------------*/ #ifndef SI5351_NUMBER_OF_OUTPUTS #define SI5351_NUMBER_OF_OUTPUTS 8 #endif #ifdef __arm__ #ifdef __ARM_BIG_ENDIAN #define for_endian(size) for (int i = 0; i < size; ++i) #define last_loop_endian (i==size-1) #else #define for_endian(size) for (int i = size - 1; i >= 0; --i) #define last_loop_endian (i==0) #endif #else #error "Endianness not detected or another compiler" #endif /* Exported functions --------------------------------------------------------*/ si5351_inst_t si5351_init(void * i2c_handle, uint32_t xtal_frequency, uint8_t i2c_address); int si5351_deinit(si5351_inst_t si5351_handle); int si5351_isready(si5351_inst_t inst); int si5351_program(si5351_inst_t inst); char * si5351_read_debug_msg(si5351_inst_t inst); char * si5351_read_register_debug(si5351_inst_t inst, char *buf, size_t bufsize, uint8_t regaddr); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* STM32_SI5351_H */ /* * si5351.h * * Created on: Feb 16, 2018 * Author: Petr Polasek * * To make this library useable on any other device than * STM32Fxxx Cortex Mx, please edit these parts of the library: * * DEFINES: * SI5351_I2C_PERIPHERAL - the I2C peripheral name according * to your devices HAL library * I2C_TIMEOUT - time for the communication to time out * * TYPEDEFS: * Si5351_ConfigTypeDef - the I2Cx parameter should be changed * so that its type corresponds to your HAL library * * FUNCTIONS: * Si5351_WriteRegister * Si5351_ReadRegister * You need to write your own I2C handlers here * */ #if 0 #ifndef ENABLESTATE #define ENABLESTATE typedef enum { OFF = 0, ON = 1 } EnableState; #endif /* * This section contains register addresses and bit masks for * the device status registers. */ #define REG_DEV_STATUS 0 #define DEV_SYS_INIT_MASK 0x80 #define DEV_LOL_B_MASK 0x40 #define DEV_LOL_A_MASK 0x20 #define DEV_LOS_CLKIN_MASK 0x10 #define DEV_LOS_XTAL_MASK 0x08 #define DEV_REVID_MASK 0x03 #define REG_DEV_STICKY 1 #define DEV_STKY_SYS_INIT_MASK 0x80 #define DEV_STKY_LOL_B_MASK 0x40 #define DEV_STKY_LOL_A_MASK 0x20 #define DEV_STKY_LOS_CLKIN_MASK 0x10 #define DEV_STKY_LOS_XTAL_MASK 0x08 #define REG_INT_MASK 2 #define INT_MASK_SYS_INIT_MASK 0x80 #define INT_MASK_LOL_B_MASK 0x40 #define INT_MASK_LOL_A_MASK 0x20 #define INT_MASK_LOS_CLKIN_MASK 0x10 #define INT_MASK_LOS_XTAL_MASK 0x08 /* * This section contains data structures for configuring the * oscillator, VCXO and CLKIN section. */ #define REG_XTAL_CL 183 #define XTAL_CL_MASK 0xC0 #define PLL_CL_MASK 0x36 //this sets the crystal load capacitance typedef enum { XTAL_Load_4_pF = 0x00, XTAL_Load_6_pF = 0x40, XTAL_Load_8_pF = 0x80, XTAL_Load_10_pF = 0xC0 } Si5351_XTALLoadTypeDef; //The following is an unexplained parameter. However someone from SiLabs called it "VCO load cap". //Lower settings seem to be more stable on higher frequencies, higher settings are more stable on lower frequencies allowing to tune the PLL to <200 MHz. typedef enum { PLL_Capacitive_Load_0 = 0, PLL_Capacitive_Load_1 = 1, PLL_Capacitive_Load_2 = 2 } Si5351_PLLCapacitiveLoadTypeDef; #define REG_CLKIN_DIV 15 #define CLKIN_MASK 0xC0 //this sets the CLKIN pre-divider, after division, CLKIN should //fall between 10-40 MHz typedef enum { CLKINDiv_Div1 = 0x00, CLKINDiv_Div2 = 0x40, CLKINDiv_Div4 = 0x80, CLKINDiv_Div8 = 0xC0 } Si5351_CLKINDivTypeDef; #define REG_FANOUT_EN 187 #define FANOUT_CLKIN_EN_MASK 0x80 #define FANOUT_XO_EN_MASK 0x40 #define FANOUT_MS_EN_MASK 0x10 #define REG_VCXO_PARAM_0_7 162 #define REG_VCXO_PARAM_8_15 163 #define REG_VCXO_PARAM_16_21 164 #define VCXO_PARAM_16_21_MASK 0x3F #define VCXO_PARAM_MASK 0x003FFFFF #define APR_MINIMUM 30 //minimum pull range #define APR_MAXIMUM 240 //maximum pull range #define CLKIN_MINIMUM 10000 //minimum CLKIN frequency after division in kHz #define CLKIN_MAXIMUM 40000 //maximum CLKIN frequency after division in kHz typedef struct { Si5351_XTALLoadTypeDef OSC_XTAL_Load; //capacitive load of XTAL, 10pF by default Si5351_CLKINDivTypeDef CLKIN_Div; //CLKIN predivision, input f to PLL must be 10-40 MHz uint8_t VCXO_Pull_Range_ppm; //can range from +-30 ppm to 240ppm } Si5351_OSCConfigTypeDef; /* * This section contains data structures for configuring the * PLL (PLLA and PLLB) */ #define REG_PLL_CLOCK_SOURCE 15 #define PLLA_CLOCK_SOURCE_MASK 0x04 #define PLLB_CLOCK_SOURCE_MASK 0x08 //this selects the clock source for the PLL typedef enum { PLL_Clock_Source_XTAL = 0x00, PLL_Clock_Source_CLKIN = 0x0C //0x04 for PLLA, 0x08 for PLLB, use mask! } Si5351_PLLClockSourceTypeDef; #define REG_FB_INT 22 #define FB_INT_MASK 0x40 #define REG_PLL_RESET 177 #define PLLA_RESET_MASK 0x20 #define PLLB_RESET_MASK 0x80 #define REG_MSN_P1_0_7 30 #define REG_MSN_P1_8_15 29 #define REG_MSN_P1_16_17 28 #define MSN_P1_16_17_MASK 0x03 #define REG_MSN_P2_0_7 33 #define REG_MSN_P2_8_15 32 #define REG_MSN_P2_16_19 31 #define MSN_P2_16_19_MASK 0x0F #define REG_MSN_P3_0_7 27 #define REG_MSN_P3_8_15 26 #define REG_MSN_P3_16_19 31 #define MSN_P3_16_19_MASK 0xF0 #define MSNA_MSNB_OFFSET 8 typedef struct { uint32_t PLL_Multiplier_Integer; uint32_t PLL_Multiplier_Numerator; uint32_t PLL_Multiplier_Denominator; Si5351_PLLClockSourceTypeDef PLL_Clock_Source; Si5351_PLLCapacitiveLoadTypeDef PLL_Capacitive_Load; } Si5351_PLLConfigTypeDef; /* * This section contains data structures for configuring the * Spread Spectrum feature. */ #define REG_SSC_MODE 151 #define SSC_MODE_MASK 0x80 //this selects the Spread Spectrum mode typedef enum { SS_Mode_DownSpread = 0x00, SS_Mode_CenterSpread = 0x80 } Si5351_SSModeTypeDef; typedef enum { SS_NCLK_0 = 0x00, SS_NCLK_1 = 0x10, SS_NCLK_2 = 0x20, SS_NCLK_3 = 0x30, SS_NCLK_4 = 0x40, SS_NCLK_5 = 0x50, SS_NCLK_6 = 0x60, SS_NCLK_7 = 0x70, SS_NCLK_8 = 0x80, SS_NCLK_9 = 0x90, SS_NCLK_10 = 0xA0, SS_NCLK_11 = 0xB0, SS_NCLK_12 = 0xC0, SS_NCLK_13 = 0xD0, SS_NCLK_14 = 0xE0, SS_NCLK_15 = 0xF0 } Si5351_SSNCLKTypeDef; #define REG_SSDN_P1_0_7 153 #define REG_SSDN_P1_8_11 154 #define SSDN_P1_8_11_MASK 0x0F #define REG_SSDN_P2_0_7 150 #define REG_SSDN_P2_8_14 149 #define SSDN_P2_8_14_MASK 0x7F #define REG_SSDN_P3_0_7 152 #define REG_SSDN_P3_8_14 151 #define SSDN_P3_8_14_MASK 0x7F #define REG_SSUDP_0_7 155 #define REG_SSUDP_8_11 154 #define SSUDP_8_11_MASK 0xF0 #define REG_SSUP_P1_0_7 160 #define REG_SSUP_P1_8_11 161 #define SSUP_P1_8_11_MASK 0x0F #define REG_SSUP_P2_0_7 157 #define REG_SSUP_P2_8_14 156 #define SSUP_P2_8_14_MASK 0x7F #define REG_SSUP_P3_0_7 159 #define REG_SSUP_P3_8_14 158 #define SSUP_P3_8_14_MASK 0x7F #define REG_SSC_EN 149 #define SSC_EN_MASK 0x80 #define REG_SS_NCLK 161 #define SS_NCLK_MASK 0xF0 typedef struct { uint32_t SS_Amplitude_ppm; //amplitude of the SS feature in ppm of center frequency EnableState SS_Enable; Si5351_SSModeTypeDef SS_Mode; Si5351_SSNCLKTypeDef SS_NCLK; } Si5351_SSConfigTypeDef; /* * This section contains data structures for configuring the * Output Multisynth. */ //this selects the Multisynth clock source typedef enum { MS_Clock_Source_PLLA = 0x00, MS_Clock_Source_PLLB = 0x20 } Si5351_MSClockSourceTypeDef; #define REG_MS_P1_0_7 46 #define REG_MS_P1_8_15 45 #define REG_MS_P1_16_17 44 #define MS_P1_16_17_MASK 0x03 #define REG_MS_P2_0_7 49 #define REG_MS_P2_8_15 48 #define REG_MS_P2_16_19 47 #define MS_P2_16_19_MASK 0x0F #define REG_MS_P3_0_7 43 #define REG_MS_P3_8_15 42 #define REG_MS_P3_16_19 47 #define MS_P3_16_19_MASK 0xF0 #define REG_MS67_P1 90 #define REG_MS_INT 16 #define MS_INT_MASK 0x40 #define REG_MS_DIVBY4 44 #define MS_DIVBY4_MASK 0x0C #define REG_MS_SRC 16 #define MS_SRC_MASK 0x20 #define MS_SETUP_STEP 1 #define MS_DIVIDER_STEP 8 typedef struct { Si5351_MSClockSourceTypeDef MS_Clock_Source; //select source on MS input uint32_t MS_Divider_Integer; //the integer part of divider, called "a" uint32_t MS_Divider_Numerator; //the numerator, called "b" uint32_t MS_Divider_Denominator; //the denominator, called "c" } Si5351_MSConfigTypeDef; //sets MS divider ( a+(b/c) ) and clock (PLLA/PLLB) /* * This section contains data structures for configuring the * CLK, R divider and output stage (joined together because they make * a tight block without any multiplexer). */ #define REG_CLK_SRC 16 #define CLK_SRC_MASK 0x0C //this sets the CLK source clock typedef enum { CLK_Clock_Source_XTAL = 0x00, CLK_Clock_Source_CLKIN = 0x04, CLK_Clock_Source_MS0_MS4 = 0x08, //this uses MS0 for CLK0..3 and MS4 for CLK4..7 CLK_Clock_Source_MS_Own = 0x0C //this uses MSx for CLKx } Si5351_CLKClockSourceTypeDef; //configures multiplexer on CLK input #define REG_CLK_R_DIV 44 #define CLK_R_DIV_MASK 0x70 #define REG_CLK_R67_DIV 92 #define CLK_R67_DIV_MASK 0x07 //this sets the R divider ratio typedef enum { CLK_R_Div1 = 0x00, CLK_R_Div2 = 0x10, CLK_R_Div4 = 0x20, CLK_R_Div8 = 0x30, CLK_R_Div16 = 0x40, CLK_R_Div32 = 0x50, CLK_R_Div64 = 0x60, CLK_R_Div128 = 0x70 } Si5351_CLKRDivTypeDef; #define REG_CLK_DIS_STATE 24 #define CLK_DIS_STATE_MASK 0x03 //this sets output buffer behaviour when disabled typedef enum { CLK_Disable_State_LOW = 0x00, CLK_Disable_State_HIGH = 0x01, CLK_Disable_State_HIGH_Z = 0x02, //three-stated when off CLK_Disable_State_ALWAYS_ON = 0x03 //cannot be disabled } Si5351_CLKDisableStateTypeDef; #define REG_CLK_IDRV 16 #define CLK_IDRV_MASK 0x03 //this sets current drive of the output buffer typedef enum { CLK_I_Drv_2mA = 0x00, CLK_I_Drv_4mA = 0x01, CLK_I_Drv_6mA = 0x02, CLK_I_Drv_8mA = 0x03 } Si5351_CLKIDrvTypeDef; #define REG_CLK_PHOFF 165 #define CLK_PHOFF_MASK 0x7F #define REG_CLK_EN 3 #define REG_CLK_INV 16 #define CLK_INV_MASK 0x10 #define REG_CLK_PDN 16 #define CLK_PDN_MASK 0x80 #define REG_CLK_OEB 9 #define CLK_PHOFF_STEP 1 #define CLK_SETUP_STEP 1 #define CLK_R_DIV_STEP 8 typedef struct { Si5351_CLKClockSourceTypeDef CLK_Clock_Source; //clock source /* this sets the time offset of the CLK channel, basic unit * is one quarter of the VCO period (90deg offset), * set it to 4*fVCO*toffset, the value is 7-bit, the max time offset * varies between 35 and 53 ns (1 cycle for 28 and 19 MHz, respectively) * according to the current frequency of the VCO */ uint8_t CLK_QuarterPeriod_Offset; Si5351_CLKRDivTypeDef CLK_R_Div; //R divider value (only powers of 2) EnableState CLK_Invert; //invert output clock EnableState CLK_Enable; //enable flag EnableState CLK_PowerDown; //powerdown flag Si5351_CLKDisableStateTypeDef CLK_Disable_State; //sets output behaviour when disabled Si5351_CLKIDrvTypeDef CLK_I_Drv; //output driver current drive strength EnableState CLK_Use_OEB_Pin; //allows using OEB pin to enable clock } Si5351_CLKConfigTypeDef; /* * This section contains main data structure for Si5351 configuration */ typedef struct { /* * These are frequencies of the input clocks, set it in Hz. */ uint32_t f_XTAL; uint32_t f_CLKIN; //Interrupt masking - enabling it disables the int source from pulling INTR low EnableState Interrupt_Mask_SysInit; EnableState Interrupt_Mask_PLLB; EnableState Interrupt_Mask_PLLA; EnableState Interrupt_Mask_CLKIN; EnableState Interrupt_Mask_XTAL; //Fanout enable - enables internal clock routing EnableState Fanout_MS_EN; EnableState Fanout_XO_EN; EnableState Fanout_CLKIN_EN; I2C_TypeDef *I2Cx; //the I2C interface that will be used uint8_t HW_I2C_Address; //I2C address of the Si5351 for the packages with A0 pin //(also, some duds with strange address reported) Si5351_OSCConfigTypeDef OSC; //Oscillator, CLKIN and VCXO settings Si5351_PLLConfigTypeDef PLL[2]; //PLL settings for PLLA and PLLB Si5351_MSConfigTypeDef MS[8]; //MultiSynth[0..7] settings Si5351_CLKConfigTypeDef CLK[8]; //CLK[0..7], R divider and output stage settings Si5351_SSConfigTypeDef SS; //spread spectrum settings } Si5351_ConfigTypeDef; /* * Typedefs for selecting PLL, MS and CLK to be used */ //this selects PLL channel typedef enum { PLL_A = 0, PLL_B = 1 } Si5351_PLLChannelTypeDef; //this selects Multisynth channel typedef enum { MS0 = 0, MS1 = 1, MS2 = 2, MS3 = 3, MS4 = 4, MS5 = 5, MS6 = 6, MS7 = 7 } Si5351_MSChannelTypeDef; //this selects CLK channel typedef enum { CLK0 = 0, CLK1 = 1, CLK2 = 2, CLK3 = 3, CLK4 = 4, CLK5 = 5, CLK6 = 6, CLK7 = 7 } Si5351_CLKChannelTypeDef; //this selects device status flag typedef enum { StatusBit_SysInit = DEV_SYS_INIT_MASK, StatusBit_PLLA = DEV_STKY_LOL_A_MASK, StatusBit_PLLB = DEV_LOL_B_MASK, StatusBit_CLKIN = DEV_LOS_CLKIN_MASK, StatusBit_XTAL = DEV_LOS_XTAL_MASK, } Si5351_StatusBitTypeDef; //these write to and read from a Si5351 register, for porting //purposes, these functions should be the only ones which should need edits int Si5351_WriteRegister(Si5351_ConfigTypeDef *Si5351_ConfigStruct, uint8_t reg_address, uint8_t reg_data); uint8_t Si5351_ReadRegister(Si5351_ConfigTypeDef *Si5351_ConfigStruct, uint8_t reg_address); void Si5351_StructInit(Si5351_ConfigTypeDef *Si5351_ConfigStruct); void Si5351_OSCConfig(Si5351_ConfigTypeDef *Si5351_ConfigStruct); EnableState Si5351_CheckStatusBit(Si5351_ConfigTypeDef *Si5351_ConfigStruct, Si5351_StatusBitTypeDef StatusBit); EnableState Si5351_CheckStickyBit(Si5351_ConfigTypeDef *Si5351_ConfigStruct, Si5351_StatusBitTypeDef StatusBit); void Si5351_InterruptConfig(Si5351_ConfigTypeDef *Si5351_ConfigStruct); void Si5351_ClearStickyBit(Si5351_ConfigTypeDef *Si5351_ConfigStruct, Si5351_StatusBitTypeDef StatusBit); void Si5351_PLLConfig(Si5351_ConfigTypeDef *Si5351_ConfigStruct, Si5351_PLLChannelTypeDef PLL_Channel); void Si5351_PLLReset(Si5351_ConfigTypeDef *Si5351_ConfigStruct, Si5351_PLLChannelTypeDef PLL_Channel); void Si5351_PLLSimultaneousReset(Si5351_ConfigTypeDef *Si5351_ConfigStruct); void Si5351_SSConfig(Si5351_ConfigTypeDef *Si5351_ConfigStruct); void Si5351_MSConfig(Si5351_ConfigTypeDef *Si5351_ConfigStruct, Si5351_MSChannelTypeDef MS_Channel); void Si5351_CLKPowerCmd(Si5351_ConfigTypeDef *Si5351_ConfigStruct, Si5351_CLKChannelTypeDef CLK_Channel); void Si5351_CLKConfig(Si5351_ConfigTypeDef *Si5351_ConfigStruct, Si5351_CLKChannelTypeDef CLK_Channel); int Si5351_Init(Si5351_ConfigTypeDef *Si5351_ConfigStruct); #endif /* SI5351_H_ */