DAC配置—SPI的使用

SPI总线详解见:SPI总线协议
DAC3282的datasheet可去免费下载:DAC3282datasheet免费下载

本文基于TI公司的DAC3283进行讲解,通过SPI对其进行讲解,并且配有详细的Verilog代码和testbench。

DAC寄存器:一共有32个寄存器,每个寄存器的详细配置手册上均有写。
在这里插入图片描述
每个寄存器可以写入的数据为5Byte,其中第1Byte的用法在Table2中详细说明,包括读写控制,剩余4Byte的用法,寄存机地址等。
在这里插入图片描述
读写时序如下图所示:Figure32中的写时序中,默认的是[N1:N0]=00,表示只传输1Byte的数据,SDENB为片选信号,SDIO为双向接口,此图是按照三线模式分析的,和四线模式的唯一差别就是四线模式用SDI、SDO来分别表示写数据和读数据。
在这里插入图片描述
在这里插入图片描述

详细设置过程:
a. 首先明确SDIO数据中心对应着SCLK时钟的上升沿,在时钟的下降沿读出数据,因此在产生SCLK的数据时,需要将时钟进行180度的相移。此时的建立保持时间也最大,效果最好。
b. 建立状态机,确定SPI的状态迁移,一上电为IDLE状态,其中WAIT状态用于分割两次命令的等待时间,READ_MEM是读取ROM中的状态信息,WRITE_REG将读取的信息写入DAC芯片,当32个寄存器均被配置完成,跳入STOP状态。

在这里插入图片描述

Verilog代码如下:

`timescale 1ns / 1ps
//
// Engineer:mxGe_UESTC 
// 
// Create Date: 2020/04/25 19:02:16
// Design Name: 
// Module Name: spi_ctrl
// Project Name: 
//
module spi_ctrl(
	input	wire		sclk,//系统时钟50Mhz
	input	wire		rst_n,
	input	wire		work_en,//触发配置操作的使能
	output	reg			conf_end,
	output	wire		spi_clk,//50-60mhz
	output	wire		spi_sdi,
	output	wire		spi_csn,
	input	wire		spi_sdo//读输入管脚不进行编程
);

parameter	IDLE = 5'b0_0001;
parameter	WAIT = 5'b0_0010;
parameter	R_MEM= 5'b0_0100;
parameter	W_REG= 5'b0_1000;
parameter	STOP = 5'b1_0000;

parameter	H_DIV_CYC	=	5'd25-1;



reg		[4:0]	state;//状态机的寄存器变量,编码方式采用独热码
reg		[4:0]	div_cnt;
reg				clk_p=1'b0;
wire			clk_n;
reg				pose_flag;
reg		[3:0]	wait_cnt;
reg		[3:0]	shift_cnt;
reg		[4:0]	r_addr;
wire	[15:0]	r_data;
wire			wren;
reg		[15:0]	shift_buf;
reg				data_end;
reg				sdi;
reg				csn;
reg				tck;

//分频计数器
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		div_cnt <= 5'd0;
	else if( div_cnt == H_DIV_CYC )
		div_cnt <= 'd0;
	else 
		div_cnt <= div_cnt + 1'b1;
//分频时钟不允许做寄存器的触发时钟,也就是不能写在always块的触发列表中
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		clk_p <= 1'b0;
	else if(div_cnt == H_DIV_CYC)
		clk_p <= ~clk_p;
		
assign	clk_n=~clk_p;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		pose_flag <= 1'b0;
	else if(clk_p == 1'b0 && div_cnt == H_DIV_CYC)
		pose_flag <= 1'b1;
	else	pose_flag <= 1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		wait_cnt <= 'd0;
	else if(state == WAIT && pose_flag == 1'b1)
		wait_cnt <= wait_cnt + 1'b1;
	else if(state != WAIT)
		wait_cnt <= 4'd0;

//fsm  命令8bit,数据8bit,一共16bit
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		state <= IDLE;
	else case (state)
		IDLE :if(work_en == 1'b1)
			state <= WAIT;
		WAIT :if(wait_cnt[3] == 1'b1)
			state <= R_MEM;
		R_MEM:	state <=W_REG;
		W_REG:	if(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end != 1'b1)
				state <= WAIT;
			else if(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end == 1'b1)
				state <= STOP;
		STOP:	state <= STOP;
		default : state <= IDLE;
		
	endcase

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		shift_cnt <= 'd0;
	else if(state == W_REG && pose_flag == 1'b1)
		shift_cnt <= shift_cnt + 1'b1;
	else if( state != W_REG)
		shift_cnt <= 4'd0;
//读mem的地址产生
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		r_addr <= 'd0;
	else if(state == R_MEM)
		r_addr <= r_addr + 1'b1;
//data_end 最后一个需要移位数据
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		data_end <= 1'b0;
	else if(state == R_MEM && (&r_addr) == 1'b1)//等效于r_addr == 5'd31
		data_end <= 1'b1;

assign	wren =1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		shift_buf<='d0;
	else if(state == R_MEM)
		shift_buf <= r_data;
	else if(state == W_REG && pose_flag == 1'b1)
		shift_buf <= {shift_buf[14:0],1'b1};

//数据的输出
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		sdi<=1'b0;
	else if(state == W_REG)
		sdi<=shift_buf[15];
	else
		sdi <= 1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		csn <= 1'b1;
	else if(state == W_REG)
		csn <= 1'b0;
	else
		csn <= 1'b1;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		tck<=1'b0;
	else if(state == W_REG )
		tck<=clk_n;
	else
		tck<=1'b0;

assign spi_clk = tck;
assign spi_csn = csn;
assign spi_sdi = sdi;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		conf_end <= 1'b0;
	else if(state == STOP)
		conf_end <= 1'b1;
		
ram_16x32_sr ram_16x32_sr_inst (
  .clka(sclk),    // input wire clka
  .ena(1'b1),      // input wire ena
  .wea(wren),      // input wire [0 : 0] wea  写使能高有效,读使能低有效
  .addra(r_addr),  // input wire [4 : 0] addra
  .dina(16'd0),    // input wire [15 : 0] dina
  .douta(r_data)  // output wire [15 : 0] douta
);			
	
endmodule

testbench

`timescale 1ns / 1ps
//
// Engineer: mxGe_UESTC 
// Create Date: 2020/04/25 20:32:06
// Design Name: 
// Module Name: tb_spi_ctrl
// Project Name: 
//
module tb_spi_ctrl();

reg	sclk,rst_n;
reg	work_en;
wire	spi_clk,sclk_csn,spi_sdi;
reg	[15:0]	send_mem [31:0];
reg	[15:0]	shift_buf;	

initial	begin
	rst_n =0;
	sclk =0;
	#100;
	rst_n =1;
end  

initial	begin
	work_en =0;
	#150
	work_en =1;
end

initial begin
	$readmemb("dac_ini_16x32.mif",send_mem);
end

always #10 sclk = ~sclk;

initial begin
	rec_spi();
end

spi_ctrl	spi_ctrl_inst(
	.sclk		(sclk),//系统时钟50Mhz
	.rst_n		(rst_n),
	.work_en	(work_en),//触发配置操作的使能
	.conf_end	(),
	.spi_clk	(spi_clk),//50-60mhz
	.spi_sdi	(spi_sdi),
	.spi_csn	(spi_csn),
	.spi_sdo	()//读输入管脚不进行编程
);

task rec_spi();
	integer i,j;
	begin
		for (i=0;i<32;i=i+1)begin
			for(j=0;j<16;j=j+1)begin
				@(posedge spi_clk);
					shift_buf = {shift_buf[14:0],spi_sdi};
				if(j==15 && shift_buf == send_mem[i])
					$display("ok data index is %d rec_d=%d send_d=%d",i,shift_buf,send_mem[i]);
				else if(j== 15)
					$display("error");	
					
			end
		end
	end
endtask
endmodule

<think>好的,我需要回答用户关于DAC配置方法和教程的问题。首先,用户提到了几个引用来源,我需要先理解这些引用内容,看看它们提供了哪些具体的信息。 引用[1]和[2]都提到了STM32CubeMX中配置DAC的步骤,比如在Analog菜单下选择DAC,勾选OUT1和OUT2的配置,并使用默认设置生成代码。这说明STM32CubeMX是配置DAC的一个常用工具,用户可能需要具体的步骤指导。 引用[3]涉及树莓派的HiFiBox DAC和红外遥控配置,需要覆盖lircd.conf文件。这可能适用于树莓派用户,但用户的问题可能更偏向于硬件配置而非遥控设置,所以这部分可能需要简要提及,但重点可能放在其他部分。 引用[4]提到了使用定时器触发DAC输出,并涉及DMA配置。这里可能需要解释如何结合定时器和DMA来实现固定频率的DAC输出,这对于需要精确控的场景很重要,比如音频生成或信号发生。 用户的问题是关于DAC配置的方法或教程,所以需要覆盖不同平台(如STM32和树莓派)以及不同的配置方法(如基础配置、定时器触发、DMA使用)。同时,用户可能需要代码示例,比如STM32的HAL库代码或树莓派的Python示例。 我需要确保回答结构清晰,分点说明不同情况下的配置步骤,并引用正确的来源。此外,用户要求生成相关问题,可能需要涉及性能优化、常见问题、应用场景等。 需要注意的是,用户提供的引用中有一些配置步骤的具体命令或操作,比如使用STM32CubeMX的界面操作,或者树莓派的文件覆盖步骤,这些都需要准确反映在回答中,并正确标注引用标识。 最后,要确保回答使用中文,数学公式和代码块格式正确,独立公式用$$...$$,行内公式用$...$,代码块用三个反引号加语言标识。同时,末尾生成的相关问题要符合用户需求,可能涉及不同平台或高级配置技巧。</think>### DAC配置方法及教程 #### 1. **STM32系列DAC基础配置(基于STM32CubeMX)** - **步骤1**:在STM32CubeMX中,选择芯片型号后,进入`Analog`→`DAC`选项卡,勾选需要启用的输出通道(如`OUT1 Configuration`或`OUT2 Configuration`)[^1][^2]。 - **步骤2**:配置触发源(如定时器触发或软件触发)。默认情况下,DAC使用软件触发,通过HAL库函数`HAL_DAC_Start()`启动输出[^4]。 - **步骤3**:生成代码后,通过以下代码设置输出电压值(以通道1为例): ```c HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2048); // 输出1.65V(假设参考电压3.3V) HAL_DAC_Start(&hdac, DAC_CHANNEL_1); ``` #### 2. **定时器触发+DMA的DAC输出(固定频率)** - **应用场景**:需要周期性输出波形(如正弦波、方波)。 - **配置步骤**: 1. 在STM32CubeMX中,配置定时器(如TIM6)为所需频率,并在`Trigger Output`中启用触发信号[^4]。 2. 在DAC配置中,选择触发源为对应定时器(如TIM6 TRGO)。 3. 启用DMA,将波形数据从内存传输到DAC寄存器: ```c // 示例:生成正弦波数据 uint32_t sine_wave[128]; for (int i = 0; i < 128; i++) { sine_wave[i] = 2048 + 2047 * sin(2 * PI * i / 128); } HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_wave, 128, DAC_ALIGN_12B_R); ``` #### 3. **树莓派DAC配置(以HiFiBox为例)** - **硬件连接**:通过I²S或SPI接口连接外部DAC模块(如PCM5122)。 - **软件配置**: 1. 修改系统音频输出设备: ```bash # 在/boot/config.txt中添加 dtoverlay=hifiberry-dac ``` 2. 配置红外遥控(如需自定义): ```bash sudo cp ~/lircd.conf /etc/lirc/lircd.conf # 覆盖配置文件[^3] ``` #### 4. **常见问题与优化** - **分辨率限**:12位DAC的电压精度为$V_{\text{ref}} / 4096$,需注意量化误差。 - **抗锯齿滤波**:高频输出时,建议在DAC输出端添加低通滤波器。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值