基于STM32F103驱动SI5351 3通道时钟信号发生器输出不同频率信号

前言

Si5351 是一款 I2C 可配置时钟发生器,非常适合在成本敏感型应用中替代晶体、晶体振荡器、压控晶体振荡器 (VCXO)、锁相环 (PLL) 和扇出缓冲器。Si5351 基于 PLL/VCXO + 高分辨率 MultiSynth 小数分频器架构,可在每路输出上产生高达 200 MHz 的任意频率。Si5351 提供三种版本,以满足各种应用需求。Si5351A 使用内部振荡器生成多达 8 个自由运行时钟,可替代晶体和晶体振荡器。Si5351B 增加了一个内部 VCXO,可灵活地替代自由运行时钟和同步时钟。它无需使用成本更高的定制可调晶体,同时在较宽的调谐范围内提供可靠的运行。Si5351C 具有相同的灵活性,但与外部参考时钟 (CLKIN) 同步。

一、SI5351发生器简介

SI5351A是一款I2C 接口、低功耗、高集成度的多输出时钟发生器,由一个固定的晶振25MHz输入,生成最多 3 路独立频率输出(CLK0/CLK1/CLK2),频率范围从8 kHz 到 160MHz(典型为 100MHz)。 主要特性

  • 输入时钟源:25MHz 或 27MHz 晶振
  • 输出端口:CLK0、CLK1、CLK2(最多三路)
  • 输出频率范围:8kHz ~ 160MHz(典型使用不高于 100MHz)
  • 内部结构:内部 PLL x2、MSynth x3、分频器、R-divider
  • 通讯接口:I2C,支持 400kHz 标准通信速率,器件地址:1100000+读写位
  • 输出类型:CMOS 方波输出
  • 电源电压:3.3 ~ 5VDC
  • 工作温度:-40℃ ~ 85℃

二、功能框图

PLL:两个(PLLA、PLLB),由 XTAL 倍频得到高频时钟(600-900MHz) MSynth:三组,分别控制 CLK0/1/2,通过分频得到最终输出 R-0/1/2:在 MSynth 后,还可进一步进行 1~128 的 2ⁿ次分频(影响输出频率)

三、主要寄存器

地址3:配置输出使能控制,地址1523:配置外部时钟输入分频,每路输出开关,通道时钟源等 地址26 41:配置PLLA/B(设置VCO频率),P1, P2, P3 构成 PLL 分数乘法器,使得 PLL freq 在 600 ~ 900 MHz 之间。 地址42~91:配置Multisynth 分频器(设置输出),也是 P1/P2/P3 三段式分频,要求 R_DIV 分频值在 4~900 且必须为偶数

四、输出频率的计算和配置说明

由上面的功能框图可知,SI5351A包括PLL+MultiSynth(多合成分频器)+输出R分频器,每个通道都有一个独立的多合成分频器和R分频器。此外,还内置了一个工作频率为 25 MHz 或 27 MHz 的普通振荡器电路,但它需要一个外部晶体。PLL由三个主要电子电路组成:相位比较器、VCO 和反馈分频器(FMD)。对于 FMD 的每个不同编程设置,VCO 将产生不同的频率。   为了提高频率分辨率(从宽步长到更细的步长),Si5351A 的PLL后接了一个分频器电路,Skyworks 将其命名为多合成分频器 (OMD)。在 Si5351A 中,Skyworks 增加了第二个分频器级(称为 R),可以进一步划分合成器产生的频率。   输出分频器的缺点是输出频率 (fout) 将低于 VCO 频率 (fvco)。在 Si5351A 中,最小分频为 4。这意味着 VCO 频率总是比输出频率高至少四倍。但是,频率步进可以小得多,例如 1 Hz,而不是很宽的频率步进,例如 25 kHz。这完全取决于如何设置 FMD 和 OMD 比例。   如何根据参考频率 (fref) 生成所需的输出频率 (fout)。输出频率由下面的公式表示

f_{out}=frac{f_{ref}·FMD}/{OMD·R}=f_{vco}/{OMD·R}

其中FMD和OMD的比率也可以表示为 FMD=a+b/c,OMD=d+e/f,即基准频率和输出频率完整关系为:

f_{out}={f_{ref}·(a+b/c)}/{R·(d+e/f)}

根据上述公式,除了 <math xmlns="http://www.w3.org/1998/Math/MathML"> f o u t f_{out} </math>fout和 <math xmlns="http://www.w3.org/1998/Math/MathML"> f r e f f_{ref} </math>fref的数值已知,有七个未知变量, <math xmlns="http://www.w3.org/1998/Math/MathML"> f r e f f_{ref} </math>fref为输入时钟源25M或27M, <math xmlns="http://www.w3.org/1998/Math/MathML"> f o u t f_{out} </math>fout为输出的频率。看手册可知FMD和OMD的比率是有范围限制的,FMD比率可以从15 + 0/1048575到90 + 0/1048575,OMD比率可以是4,也可以是8+1/1048575~2400,根据这个范围可以得到a=15到90,b=0,c=1048575,注意分母不能为0。R分频器只能为1,2,4,8,16,32,64,128。输出低于500KHz,则需使用R分频。   同时你还需要了解小数分频模式 与 整数分频模式,两者的区别是对相位噪声 / 杂散 / 抖动 的影响,FMD和OMD都是可以使用整数或小数进行分频,小数分频方式允许更灵活地产生任意频率,FMD 和 OMD 的比率被称为小数。缺点就是会引入杂散和相位抖动。故如果要求低抖动(比如用于 ADC/DAC 时钟),应尽可能让分频系数为整数,避免小数。使用什么模式则看你的应用,可以都为整数(只要输出频率允许),这样杂散最低,不过产生频率就没有那么灵活。如果你只能选择一个在小数模式下工作,那就尽量让 OMD 是整数模式,FMD 允许小数,因为这样输出频率的杂散更小。OMD 后面直接连接着你的"输出引脚",所以它分频后的信号质量最直接影响外部设备。此外,"a"和"d"分别是 输出频率公式中的分子/分母,如果能选择偶数,可以改善调制器行为,降低杂散。   这里以输出59.779MHz为例来分析计算,通过反推的技巧,计算出未知变量的具体值。由手册的R分频说明,意味着R=1。VCO频率范围要在600M到900MHz内,使用900MHz进行计算。

R·OMD=OMD=d+e/f}={f_{voc}}/{f_{out}}=900000000/59779000=15.05545425651148

这意味着d=15,又因为R=1,所以e / f的比率是0.44133412745682。比率四舍五入,这既不是整数,也不是偶数。因此将d进行四舍五入为偶数整数,即d=16,然后待回去检查VCO频率是否还在600M到900MHz内。如果不在,则增大或减少2个数值。这里16代入换算是超出VCO频率范围的,即d=14。由于希望OMD要在整数模式下,e / f比值是不重要的。因此可以得到,d=14,e=0,f=1或 ≤ 1048575(最大分辨率)的值都可以。 现在OMD的未知变量知道了,后面继续找出FMD的未知变量,即a,b,c。 FMD=a+b/c={R·(d+e/f)·f_{out}}/{f_{ref}}=1·(14+0/1))·59779000/25000000=33.47624

其中33为FMD整数部分的a,b / c = 0.47624。如何求解余数部分等式,最直接的方法,就是将c设为1048575,这样,b就等于499373.358。 通过以上的计算,得到a=33,b=499373.358,c=1048575,d=14,e=0,f=1,R=1。把这些数值代入验证: f_{out}={f_{ref}·(a+b/c)}/{R·(d+e/f)}={25000000·(33+499373.358/1048575)}/{1·(14+0/1)}=59779000

输出频率没有问题。如果输出频率低于500KHz,就要使用R分频,重复上面步骤,变换公式,将有效R值(1,2,4,8,...,128)代入即可。如果想要SI5351A产生最低频率,边界频率就要设置为2048,R分频设置为最大128。 最低输出f_{out}={VCO}/{OMD·R}={600MHz}/{2048·128}≈ 2.288KHz

通过上述的分析,输出频率的计算和配置就基本明了了,SI5351A的基本工作原理可以理解为:将时钟输入源送入到两个PLL锁相环中,然后PLL将将基准频率倍频,输出一个高频的参考时钟,高频范围要在600M~900MHz内,再将PLL输出时钟送入到MultiSynth(多合成器模块),合成器接收到PLL信号,合成器则进行可编程分频,将 PLL 的高频信号变成你需要的输出频率。如果输出频率低于 500 kHz,则需加上R分频。 最后说明下,虽然Si5351A芯片理论上支持最高200MHz,最低2.5kHz。但实际会受晶振偏差,带宽限制,板子电路设计等物理限制因素影响。建议输出频率要在【160MHz,8KHz】使用。

五、STM32F103驱动SI5351A

准备工作

STM32F103C8T6最小系统板、SI5351时钟信号发生器、测量信号的示波器、连接线若干。

引脚接线

STM32F103C8T6 SI5351时钟信号发生器
3.3V VIN
GND GND
PA3 SCL
PA5 SDA

代码示例

SI5351A.c

c 复制代码
#include "si5351a.h"

static void I2C_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_APB2PeriphClockCmd  (RCC_APB2Periph_GPIOA, ENABLE );  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_3;  /* PA3-I2C_SCL、PA
	5-I2C_SDA*/
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; 
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; 
  GPIO_Init(GPIOA, &GPIO_InitStructure); 
}

///////////IIC初始化//////////////
void IIC_SI5351A_GPIO_Init(void)
{	   
  I2C_GPIO_Config();
}  
////////////粗略延时函数//////////
void Delay_1us(u16 n)//约1us,1100k
{
  unsigned int x=5,i=0;
  for(i=0;i<n;i++)
  {while(x--);x=5;}
}
void Delay_ms(u8 n)//约0.5ms,2k
{
  unsigned int x=5000,i=0;
  for(i=0;i<n;i++)
  {while(x--);x=5000;}
}
////////IIC启动函数//////////
void I2C_Start(void)
{
  SDA_H; 	
  SCL_H;
  Delay_1us(1);
  if(!SDA_read) return;//SDA线为低电平则总线忙,退出
  SDA_L;
  Delay_1us(1);
  if(SDA_read) return;//SDA线为高电平则总线出错,退出
  SDA_L;
  Delay_1us(1);
  SCL_L;
}
//**************************************
//IIC停止信号
//**************************************
void I2C_Stop(void)
{
  SDA_L;
  SCL_L;
  Delay_1us(1);
  SCL_H;
  SDA_H;
  Delay_1us(1);                 //延时
} 
//**************************************
//IIC发送应答信号
//入口参数:ack (0:ACK 1:NAK)
//**************************************
void I2C_SendACK(u8 i)
{
  if(1==i)SDA_H;                  //写应答信号
  else SDA_L;
  SCL_H;                    //拉高时钟线
  Delay_1us(1);                 //延时
  SCL_L ;                  //拉低时钟线
  Delay_1us(1);    
} 
//**************************************
//IIC等待应答
//返回值:ack (1:ACK 0:NAK)
//**************************************
 bool I2C_WaitAck(void) 	 //返回为:=1有ACK,=0无ACK
{
  unsigned int i;
  SDA_H;			
  Delay_1us(1);
  SCL_H;
  Delay_1us(1);
  while(SDA_read){i++;if(i==5000)break;}
  if(SDA_read)
  {SCL_L;
  Delay_1us(1);
  return FALSE;}
  SCL_L;
  Delay_1us(1);
  return TRUE;
}

//**************************************
//向IIC总线发送一个字节数据
//**************************************
void I2C_SendByte(u8 dat)
{
  unsigned int i;
  SCL_L;
  for (i=0; i<8; i++)         //8位计数器
  {
    if(dat&0x80){SDA_H;}   //送数据口
    else SDA_L;
    SCL_H;                //拉高时钟线
    Delay_1us(1);             //延时
    SCL_L;                //拉低时钟线
    Delay_1us(1); 		  //延时
    dat <<= 1;          //移出数据的最高位  
  }					 
}

//**************************************
//从IIC总线接收一个字节数据
//**************************************
u8 I2C_RecvByte()
{
  u8 i;
  u8 dat = 0;
  SDA_H;                    //使能内部上拉,准备读取数据,
  for (i=0; i<8; i++)         //8位计数器
  {
    dat <<= 1;
    SCL_H;                //拉高时钟线
    Delay_1us(1);            //延时
    if(SDA_read) //读数据    
    {
      dat |=0x01;
    }                           
    SCL_L;                //拉低时钟线
    Delay_1us(1);
  } 	
  return dat;
} 
//**************************************
//向IIC设备写入一个字节数据
//**************************************
bool Single_WriteI2C(u8 Slave_Address,u8 REG_Address,u8 REG_data)
{
  I2C_Start();              //起始信号
  I2C_SendByte(Slave_Address);   //发送设备地址+写信号
  if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
  I2C_SendByte(REG_Address);    //内部寄存器地址,
  if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
  I2C_SendByte(REG_data);       //内部寄存器数据,
  if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
  I2C_Stop();                   //发送停止信号
	return TRUE;
}


u8 Single_ReadI2C(u8 Slave_Address,u8 REG_Address)
{
  u8 REG_data;
  I2C_Start();                   //起始信号
  I2C_SendByte(Slave_Address);    //发送设备地址+写信号
  if(!I2C_WaitAck()){I2C_Stop(); return FALSE;} 
  I2C_SendByte(REG_Address);     //发送存储单元地址,从0开始
  if(!I2C_WaitAck()){I2C_Stop(); return FALSE;} 
  I2C_Start();                   //起始信号
  I2C_SendByte(Slave_Address+1);  //发送设备地址+读信号
  if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
  REG_data=I2C_RecvByte();       //读出寄存器数据
  I2C_SendACK(1);                //发送停止传输信号
  I2C_Stop();                    //停止信号
  return REG_data;
}


uint8_t i2cSendRegister(uint8_t reg, uint8_t data)
{
  I2C_Start();              //起始信号
  I2C_SendByte(0xC0);   //发送设备地址+写信号
  if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
  I2C_SendByte(reg);    //内部寄存器地址,
  if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
  I2C_SendByte(data);       //内部寄存器数据,
  if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
  I2C_Stop(); 
  return 0;
}


void setupPLL(uint8_t pll, uint8_t mult, uint32_t num, uint32_t denom)
{
  uint32_t P1;					// PLL config register P1
  uint32_t P2;					// PLL config register P2
  uint32_t P3;					// PLL config register P3

  P1 = (uint32_t)(128 * ((float)num / (float)denom));
  P1 = (uint32_t)(128 * (uint32_t)(mult) + P1 - 512);
  P2 = (uint32_t)(128 * ((float)num / (float)denom));
  P2 = (uint32_t)(128 * num - denom * P2);
  P3 = denom;

  i2cSendRegister(pll + 0, (P3 & 0x0000FF00) >> 8);
  i2cSendRegister(pll + 1, (P3 & 0x000000FF));
  i2cSendRegister(pll + 2, (P1 & 0x00030000) >> 16);
  i2cSendRegister(pll + 3, (P1 & 0x0000FF00) >> 8);
  i2cSendRegister(pll + 4, (P1 & 0x000000FF));
  i2cSendRegister(pll + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
  i2cSendRegister(pll + 6, (P2 & 0x0000FF00) >> 8);
  i2cSendRegister(pll + 7, (P2 & 0x000000FF));
}


void setupMultisynth(uint8_t synth,uint32_t divider,uint8_t rDiv)
{
  uint32_t P1;					// Synth config register P1
  uint32_t P2;					// Synth config register P2
  uint32_t P3;					// Synth config register P3

  P1 = 128 * divider - 512;
  P2 = 0;							// P2 = 0, P3 = 1 forces an integer value for the divider
  P3 = 1;

  i2cSendRegister(synth + 0,   (P3 & 0x0000FF00) >> 8);
  i2cSendRegister(synth + 1,   (P3 & 0x000000FF));
  i2cSendRegister(synth + 2,   ((P1 & 0x00030000) >> 16) | rDiv);
  i2cSendRegister(synth + 3,   (P1 & 0x0000FF00) >> 8);
  i2cSendRegister(synth + 4,   (P1 & 0x000000FF));
  i2cSendRegister(synth + 5,   ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
  i2cSendRegister(synth + 6,   (P2 & 0x0000FF00) >> 8);
  i2cSendRegister(synth + 7,   (P2 & 0x000000FF));
}


void si5351aSetFrequency(uint32_t frequency , u8 Chanal )
{
  uint32_t freq_temp;
  uint32_t pllFreq;
  uint32_t xtalFreq = XTAL_FREQ;// Crystal frequency
  uint32_t l;
  float f;
  uint8_t mult;
  uint32_t num;
  uint32_t denom;
  uint32_t divider;
	
  freq_temp = frequency * 1;   //分频系数
  divider = 900000000 / freq_temp;// Calculate the division ratio. 900,000,000 is the maximum internal 
                                                                  // PLL frequency: 900MHz
  if (divider % 2) divider--;		// Ensure an even integer division ratio

  pllFreq = divider * freq_temp;	// Calculate the pllFrequency: the divider * desired output frequency
	
  mult = pllFreq / xtalFreq;		// Determine the multiplier to get to the required pllFrequency  36 0 0
  l = pllFreq % xtalFreq;			// It has three parts:
  f = l;							// mult is an integer that must be in the range 15..90
  f *= 1048575;					// num and denom are the fractional parts, the numerator and denominator
  f /= xtalFreq;					// each is 20 bits (range 0..1048575)
  num = f;						// the actual multiplier is  mult + num / denom
  denom = 1048575;				// For simplicity we set the denominator to the maximum 1048575
  // Set up PLL A with the calculated multiplication ratio
  setupPLL(SI_SYNTH_PLL_A, mult, num, denom);
                                                           // Set up MultiSynth divider 0, with the calculated divider. 
                                                                  // The final R division stage can divide by a power of two, from 1..128. 
                                                                  // reprented by constants SI_R_DIV1 to SI_R_DIV128 (see si5351a.h header file)
                                                                  // If you want to output frequencies below 1MHz, you have to use the 
                                                                  // final R division stage
  if( Chanal == 0 ){
		setupMultisynth(SI_SYNTH_MS_0,divider,SI_R_DIV_1);		//设置分频后,设置频率的系数也需要更改
                                                                  // Reset the PLL. This causes a glitch in the output. For small changes to 
                                                                  // the parameters, you don't need to reset the PLL, and there is no glitch
		i2cSendRegister(SI_PLL_RESET,0xA0);	
                                                                  // Finally switch on the CLK0 output (0x4F)
                                                                  // and set the MultiSynth0 input to be PLL A
		i2cSendRegister(SI_CLK0_CONTROL, 0x4F|SI_CLK_SRC_PLL_A);
	}
	else if ( Chanal == 1 ){
		setupMultisynth(SI_SYNTH_MS_1,divider,SI_R_DIV_1);
		i2cSendRegister(SI_PLL_RESET,0xA0);	
		i2cSendRegister(SI_CLK1_CONTROL, 0x4F|SI_CLK_SRC_PLL_A);
	}
		else if ( Chanal == 2 ){
		setupMultisynth(SI_SYNTH_MS_2,divider,SI_R_DIV_1);
		i2cSendRegister(SI_PLL_RESET,0xA0);	
		i2cSendRegister(SI_CLK2_CONTROL, 0x4F|SI_CLK_SRC_PLL_A);
		}
}

void SI5351_SetFreq_Clk0(uint32_t frequency)
{
	uint32_t freq_temp;
	uint8_t div_factor, R_diV;
	uint32_t pllFreq;
	uint32_t xtalFreq = XTAL_FREQ;// Crystal frequency
	uint32_t l;
	float f;
	uint8_t mult;
	uint32_t num;
	uint32_t denom;
	uint32_t divider;

	if(frequency < 500000)
	{
		div_factor = 64;
		R_diV = SI_R_DIV_64;
	}else{
		div_factor = 1;
		R_diV = SI_R_DIV_1;
	}
	
	freq_temp = frequency * div_factor;  //fout * R
	divider = 900000000 / freq_temp;// OMD = 900M /(fout * R)
																  
	if (divider % 2) divider--;		// VCO = 600M - 900M, 取偶,整数模式
	pllFreq = divider * freq_temp;	// VCO = OMD * fout * R

	mult = pllFreq / xtalFreq;		// 取整数a,VCO = fref * (a + b/c)
	l = pllFreq % xtalFreq;			// 取小数:b/c
	f = l;							
	f *= 1048575;					// b放大
	f /= xtalFreq;					// 获取b
	num = f;						 
	denom = 1048575;				
	setupPLL(SI_SYNTH_PLL_A, mult, num, denom);
	
	setupMultisynth(SI_SYNTH_MS_0,divider,R_diV);
	i2cSendRegister(SI_PLL_RESET,0xA0);	
	i2cSendRegister(SI_CLK1_CONTROL, 0x4F|SI_CLK_SRC_PLL_A);
}

main.c

c 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "si5351a.h"
 
int main(void)
{
	int Fre_1M = 10000;
	int fre = 5;
	delay_init();	    //延时函数初始化	  
	
	IIC_SI5351A_GPIO_Init();
	
	si5351aSetFrequency(112500000 , 0);
	si5351aSetFrequency(1000000 , 1);
	si5351aSetFrequency(10000 , 2);
	
	while(1)
	{
//		SI5351_SetFreq_Clk0(fre*Fre_1M);
//		delay_ms(50);
//		fre += 100;
//		if(fre*Fre_1M > 150000000) fre = 5;
	}
}

效果展示

注意事项

  1. PLL 频率需始终在 600 ~ 900 MHz 范围
  2. Multisynth 分频必须为偶数,4~900
  3. 输出小于 500KHz 时,建议使用 R_DIV 分频
  4. 3通道不能同时输出高于112MHz
相关推荐
嵌入式×边缘AI:打怪升级日志2 小时前
【无标题】
单片机·嵌入式硬件
DIY机器人工房5 小时前
开发板RK3568和stm32的异同:
嵌入式硬件·嵌入式·diy机器人工房
酷飞飞16 小时前
库函数版独立按键用位运算方式实现(STC8)
单片机·嵌入式硬件
我怕是好17 小时前
STM32 输入捕获,串口打印,定时器,中断综合运用
stm32·单片机·嵌入式硬件
zhmc20 小时前
MCU程序的ARM-GCC编译链接
arm开发·单片机·嵌入式硬件
点灯小铭21 小时前
基于STM32单片机的OneNet物联网环境检测系统
stm32·单片机·物联网·毕业设计·课程设计
滴啦嘟啦哒1 天前
【从0到1制作一块STM32开发板】5. 整体布局
stm32·单片机·嵌入式硬件
Skylar_.1 天前
嵌入式 - 数据结构:哈希表和排序与查找算法
数据结构·算法·嵌入式·哈希算法·散列表
普中科技1 天前
【普中STM32精灵开发攻略】--第 11 章 SysTick系统定时器
stm32·单片机·嵌入式硬件·物联网·arm·普中科技