基于串行DAC实现一款正弦波发生器
本项目是模拟电路前端工程化第2部分的课程实践,基于串行DAC AD5626设计一款任意波形发生器。
标签
嵌入式系统
FPGA
DDS
maskmoo
更新2023-06-30
1215
项目要求;
使用串行DAC AD5626设计一款任意波形发生器
搭配单片机或FPGA或其它方式的控制逻辑能够产生10Hz到20KHz范围内的正弦波信号
输出信号幅度要达到3Vpp,直流偏移可调1.5V到3.5V
使用套件中的Micro USB适配器通过USB给面包板供电,供电电压为5V
使用ADALP2000套件中的运算放大器搭建一个Sallen-Key滤波器,滤除40KHz以上的混叠信号
 

整体设计思路

本项目中采用WeDesign中设计的开发板MM32F0140来产生驱动串行DAC模块所需要的时序逻辑,来驱动DAC模块产生10Hz到20KHz的正弦信号数据, 使用AD8542搭建一个Sallen-Key低通滤波器,滤除40KHz以上的混叠信号。通过AD8542搭建同向求和运算电路来实现输出信号的幅度的放大以及直流偏移的可调。

串行DAC AD5626驱动
AD5626的驱动时序图图如下,如图可以驱动该芯片需要按照CPOL = 1, CPHA = 1 12bit的SPI通讯模式给AD5626内部的数据寄存器传输数据,然后还需要通过控制LDAC引脚给一个低电平完成一次模拟量的转换。
Fshal3UQLhyRkxwWznQHBssQtkjn
这部分是采用MM32F0140单片机的SPI外设进行驱动,根据AD5626的是时序要求进行相关驱动引脚的初始化。
/* Setup SPI. */
void app_spi_init(void)
{
    /* Setup SPI module. */
    SPI_Master_Init_Type spi_init;
    spi_init.ClockFreqHz = BOARD_LOOP_SPI_FREQ;
    spi_init.BaudRate = BOARD_LOOP_SPI_BAUDRATE;
    spi_init.XferMode = SPI_XferMode_TxRx;
    spi_init.PolPha = SPI_PolPha_Alt2;
    spi_init.DataWidth = SPI_DataWidth_12b;
    spi_init.LSB = false;
    spi_init.AutoCS = true;
    SPI_InitMaster(BOARD_LOOP_SPI_PORT, &spi_init);

    /* Enable SPI. */
    SPI_Enable(BOARD_LOOP_SPI_PORT, true);
}
    
    GPIO_Init_Type gpio_init;
    gpio_init.Pins  = AD5635_LDAC_PIN | AD5635_CLR_PIN ;
    gpio_init.PinMode  = GPIO_PinMode_Out_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &gpio_init);

具体的驱动接口函数实现如下

/* SPI tx. */
void ad5626_update(uint32_t c)
{		
    /* Polling for tx empty. */
//    while ( SPI_STATUS_TX_FULL & SPI_GetStatus(BOARD_LOOP_SPI_PORT) )
//    {}
    SPI_PutData(BOARD_LOOP_SPI_PORT, c);
		
		while ( !(SPI_STATUS_TX_EMPTY & SPI_GetStatus(BOARD_LOOP_SPI_PORT)) )
    {}

		GPIO_ClearBits(GPIOB, AD5635_LDAC_PIN);
		GPIO_SetBits(GPIOB, AD5635_LDAC_PIN);
}

MM32F0140单片机SPI的工作频率为20MHz的情况下实际的时序如下图所示,更新一次数据的时间大约为1.7us左右。

Fvi3ShcZMsghYKhzmIbzk8DzjMBz
 
为了提高在单片机上的更新显示效率,正弦波显示上首先是通过波表初始化函数在内存上生成所需的正弦波数值查找表。
然后循环将数据输出给DAC模块。
#define SIN_TABLE_LEN		256
uint32_t g_sintable_len = SIN_TABLE_LEN;
uint16_t sin_table[SIN_TABLE_LEN] = {0};
uint16_t sin_table1[SIN_TABLE_LEN] = {0};
uint16_t sin_table2[SIN_TABLE_LEN] = {0};
uint16_t sin_table3[SIN_TABLE_LEN] = {0};
uint16_t sin_table4[SIN_TABLE_LEN] = {0};

void sin_table_init(){
	uint16_t temp_value;
	for(uint16_t i=0; i< g_sintable_len; i++){
		sin_table[i] = sin(3.14/2.0/(float)(g_sintable_len)*i)*2048;
		printf("sin_table[%d];%d \r\n", i, sin_table[i]);
	}
	for(uint16_t i=0; i< g_sintable_len; i++){
		sin_table1[i] = 2048+sin_table[i];
		sin_table2[i] = 2048+sin_table[g_sintable_len-i-1];
		sin_table3[i] = 2048-sin_table[i];
		sin_table4[i] = 2048-sin_table[g_sintable_len-i-1];
	}
}
    while (1)
    {
		for(uint16_t i=0; i< g_sintable_len; i++){
			for(uint16_t ki=0; ki< g_keepconut; ki++)
			{
				ad5626_update(sin_table1[i]);
			}
		}
		for(uint16_t i=0; i< g_sintable_len; i++){
			for(uint16_t ki=0; ki< g_keepconut; ki++)
			{
				ad5626_update(sin_table2[i]);
			}
		}		
		for(uint16_t i=0; i< g_sintable_len; i++){
			for(uint16_t ki=0; ki< g_keepconut; ki++)
			{
				ad5626_update(sin_table3[i]);
			}
		}	
		for(uint16_t i=0; i< g_sintable_len; i++){
			for(uint16_t ki=0; ki< g_keepconut; ki++)
			{
				ad5626_update(sin_table4[i]);
			}
		}				
    }

频率计算是根据波表位数g_sintable_len和保持次数g_keepconut共同决定的。

void set_sin_frequency(uint32_t freq){

		// Fsin-clk = 1/(Tsingle_tranfer*SIN_TABLE_LEN*4*g_keepconut)
		// Tsingle_tranfer = 1.71us
		uint32_t min_freq = 571;//1/(Tsingle_tranfer*SIN_TABLE_LEN*4*g_keepconut
	
		if(freq >= min_freq && freq<=20000){
			g_keepconut = 1;
			g_sintable_len = 1/(0.00000684*freq);
		}
		if(freq < min_freq){
			g_sintable_len = 256;
			g_keepconut = 1/(0.00000684*freq*g_sintable_len);
		}
		printf("g_sintable_len:%d g_keepconut:%d\r\n", g_sintable_len, g_keepconut);
}

频率的的控制是通过UART接口来是实现的,串口接收中断接收到频率控制字符后进行解析,解析完成后对波表重新进行初始化。

void app_uart_rx_isr_hook(void)
{
    if (   (0u != (UART_INT_RX_DONE & UART_GetEnabledInterrupts(BOARD_DEBUG_UART_PORT)))
        && (0u != (UART_INT_RX_DONE & UART_GetInterruptStatus(BOARD_DEBUG_UART_PORT))) )
    {
        uint8_t data = UART_GetData(BOARD_DEBUG_UART_PORT); /* read data to clear rx interrupt bits. */
				if(receive_status == 1){
					cmd_string[cmd_string_index++] = data;
					if(cmd_string_index >10){
						receive_status = 0;
					}
				}
				if(data == 'f'){
					memset(cmd_string, 0, 10);
					cmd_string_index = 0;
					receive_status = 1;
				}

				if(data == '\r' && receive_status ==1){
					receive_status = 0;
					flag_setfreq = 1;
				}
    }
}
			if(flag_setfreq == 1){
				
				uint32_t target_freq = atoi(cmd_string);
				printf("Frequency :%d \r\n", target_freq);
				if(target_freq>20000 || target_freq<10){
					printf("Frequency out of range \r\n");
				}
				else{
					set_sin_frequency(target_freq);
				}
				flag_setfreq =0 ;
			}
Sallen-Key滤波器
 
sallen-key低通滤波器的实现是通过ADI的Filter Wizard工具实现的,如下是根据题目要求以及所选器件等限定条件利用Analog Filter Wizard工具进行的设置。
Fj16gSZ2DdTAcBO-_Gi1r7CxIxSG
FuVK2D2Y7s1tHRr7O0HvAT__mrky
FmxChUSpeRljA2yNDySu74J-mzd_
Ft5GlAGME2YKq3WLk5m7afhkG5Qk
在LTSpice中的仿真结果如下所示
Fk_rdduFqv5CwgFa5BT-JJMGDN2v
 
偏置电路设计
 
直流偏置电路为基于ad8542运放的同相比例相加电路。先用一个电压跟随器将滤波后的直流偏置电压进行阻抗变换,通过改变滑动变阻器的阻值来调整偏置电压的范围。
FnMOZOtbrbrJAWAZt-Likjb11R5i
 
效果展示:
 
生成频率为20kHz信号时的波形,此时的波表长度为32。
FiPFbJ9s-Vu3Z5v1K9-Pj8m9PSQd
 
生成频率为10Hz信号时的波形,此时的波表长度为1024,保持次数为。
FhBou62Wp97H5rgMKovBJNnQNVgE
 
未加入保持逻辑时的timing测试
FrY3EgCYHKALXdLrCL4kQwg-0wRz
 

 

参考链接

  1. 上海灵动微电子股份有限公司 (mindmotion.com.cn)
  2. mindmotion.com.cn
  3. Mini-F0140_SCH.pdf (mindmotion.com.cn)
  4. ADI的Filter Wizard
  5. DAC AD5626
  6. DDS的原理、技术文章及常用器件 - 电子森林 (eetree.cn)
附件下载
pokt-f0140_mdk_dds.zip
DCbias.asc
Sk-LTspice.zip
团队介绍
1
团队成员
maskmoo
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号