Menu Close

利用PCF8591输出50Hz正弦波(sin函数)

PCF8591 是一颗ADDA转换芯片,其广泛应用于闭环控制系统,远程数据采集的低功耗场景,汽车,音响和TV应用方面的模拟数据采集。其特点是:

1、单片、单电源低功耗8 位CMOS 数据采集器件

2、具有4 个模拟输入、一个输出和一个串行I2C 总线接口

3、3 个地址引脚A0、 A1 和A2 用于编程硬件地址,允许将最多8 个器件连接至I2C总线而不需要额外硬件。

4、器件的地址、控制和数据通过两线双向I2C 总线传输。器件功能包括多路复用模拟输入、片上跟踪和保持功能、 8 位模数转换和8 位数模拟转换。

5、最大转换速率取决于I2C 总线的最高速率。

因此可以利用该芯片做一些波形发生器。例如sin函数。

通过I2C通讯,向pcf8591里写入数据,要求模拟端口输出50Hz 正弦波,峰峰值0-2V。

分析:

1、每次写入一个值就会得到一个模拟值

2、峰峰值0-2V,对应的数值值为0-155

3、模拟量正弦波,对应的数值值也是正弦波,要求50Hz,只要每秒写入完整正弦波个数是50个

每个周期正弦波使用16个点,则要求每秒写入次数为 50*16=800次

每个周期正弦波使用32个点,则要求每秒写入次数为 50*32=1600次

4、正弦波的变化规律,可以用rom 来存储实现

%title插图%num
图1

rom 初始化数据计算如下:

%title插图%num
图2

电压曲线:

%title插图%num
图3
module I2C_W_pcf8591_sin_50hz(
input 	wire			clk,//50M 时钟
input 	wire			rst,

output 	reg 			scl,//100K
inout  	wire			sda

//output 	wire [7:0] 	data//输出当前写入的值
);

//assign data = wdata;

reg 					sda_buf;
reg 					sda_sel;

assign 	sda = sda_sel ? sda_buf : 1'bz;	//sda_sel=1 写, sda_sel=0 读


//构造一个频率是2倍于 scl 的时钟

reg clk_200K;	//频率200K

reg [15:0]	cnt;
localparam			T 		=		125;		//200K 的半个时钟周期  50M / 200_000 /2 


//做200K时钟
always@(posedge clk or posedge rst) begin
if(rst)
	begin
		cnt<=0;
		clk_200K<=0;
	end
else
	begin
		if(cnt == T -1 )
			begin
				cnt <= 0;
				clk_200K <= ~clk_200K;
			end
		else
			cnt <=cnt + 1;	
	end
end


//做scl
always@(negedge clk_200K or posedge rst) begin
if(rst)
	scl<=0;
else if(cstate != S_IDLE)
	scl<=~scl;
else 
	scl <= 1;
end


//【一】I2C 字节写操作
//0、空闲状态  SCL SDA 都是高电平
//1、主机发送开始信号:——————————————SCL 高电平时,SDA从高到低的跳变
//2、主机发送设备地址(从机地址),同时发送写命令——8bit长度,每一个bit 要求,SCL低电平时SDA可以改变,SCL高电平时,SDA保持不变
//	从机地址 7 bit位,读写命令1bit位,0-写命令,1-读命令
//3、从机发送应答(ack)主机接收应答————————主机释放SDA线,从机控制SDA发送低电平(SCL低电平时发送),主机在下一个SCL高电平时接受数据
//4、主机发送寄存器地址——————————————8bit长度,每一个bit 要求,SCL低电平时SDA可以改变,SCL高电平时,SDA保持不变
//5、从机发送应答(ack)主机接收应答————————主机释放SDA线,从机控制SDA发送低电平(SCL低电平时发送),主机在下一个SCL高电平时接受数据
//6、主机发送要写入的数据—————————————8bit长度,每一个bit 要求,SCL低电平时SDA可以改变,SCL高电平时,SDA保持不变
//7、从机发送应答(ack)主机接收应答————————主机释放SDA线,从机控制SDA发送低电平(SCL低电平时发送),主机在下一个SCL高电平时接受数据
//8、主机发送结束命令———————————————SCL高电平时,SDA从低到高的跳变


///////////////////////////////////////////////////////
//I2C 读启动命令
reg	[31:0]   cnt2;
reg				flag;

//每秒写入800次,上限 = 200_000 / 800 = 250
//每秒写入1600次,上限 = 200_000 / 1600 = 125
localparam 		T_i2c_start 	= 	125;

always@(posedge clk_200K or posedge rst)begin
if(rst)
	begin
		cnt2 <= 0;
		flag <=0;
	end
else
	begin						
	if(cnt2 == T_i2c_start -1 )
			begin
				cnt2<=0;
				flag<=1;
			end
		else
			begin
				cnt2 <= cnt2 + 1;
				flag <= 0;
			end
	end
end


////////////////////////////////////////////////////////


//[1] 定义状态空间
localparam			S_IDLE 				=		0;//0、空闲状态  SCL SDA 都是高电平
localparam			S_START 			=		1;//1、主机发送开始信号
localparam			S_DEVADD 			=		2;//2、主机发送设备地址(从机地址),同时发送写命令
localparam			S_ADDACK 			=		3;//3、从机发送应答(ack)主机接收应答
localparam			S_REGADD 			=		4;//4、主机发送寄存器地址
localparam			S_REGACK 			=		5;//5、从机发送应答(ack)主机接收应答

localparam			S_WDATA 			=		6;//6、主机发送要写入的数据
localparam			S_DATAACK 			=		7;//7、从机发送应答(ack)主机接收应答
localparam			S_STOP 				=		8;//8、主机发送结束命令



//[2] 定义状态变量

reg					[3:0]			cstate;



//[3] 写状态机
//[a] 状态的判断和转移 
// 根据状态进行输出


reg					[7:0]				devadd;	//设备地址
reg					[3:0]				num;
reg					[7:0]				regadd;	//寄存器地址
wire 				[7:0] 			    wdata;	//要写入的数据
reg 									next_read;//下次读,标志是否已经进行了第一次定位地址读


//******************************************************
reg 					[4:0] 			address;

//从rom读出要写入的数据
always@(posedge clk_200K or posedge rst)begin
if(rst)
	address <= 0;
else if(cnt2 == T_i2c_start - 1 )
	address <= (address==31) ? 0 :  (address + 1) ;
end


romip	romip_inst (
	.address ( address ),
	.clock ( clk_200K ),
	.q ( wdata )
	);


//******************************************************



always@(posedge clk_200K or posedge rst) begin
if(rst)
//初始化
	begin
		cstate <= S_IDLE;
		sda_sel <= 0;
		sda_buf <= 0;
		devadd <= 8'b0;
		num <=0;
		regadd <= 8'b0;
		next_read <= 0;
	end
else
	case(cstate)
		S_IDLE		:		if(flag)
                                begin
                                    cstate <= S_START;
                                    num <= 0;
                                end
                            else
                                begin
                                    cstate <= S_IDLE;
                                    sda_sel <= 0;
                                    sda_buf <= 0;
                                end

		S_START		:		if(scl)//SCL 高电平时,SDA从高到低的跳变
                                begin
                                    sda_sel<=1;	//控制sda线
                                    sda_buf<=0;
                                    cstate <= S_DEVADD;
                                    devadd <= {7'b1001_000,1'b0};//PCF8591地址  + 写命令
                                end
		
		S_DEVADD	:		begin//每一个bit 要求,SCL低电平时SDA可以改变,SCL高电平时,SDA保持不变
                                if(!scl) begin		//!!!!!begin end
                                    sda_sel<=1;	//控制sda线
                                    if(num < 8)
                                        begin
                                            sda_buf<= devadd[7-num];
                                            num <= num + 1;
                                        end
                                    else
                                        begin
                                            cstate <= S_ADDACK;
                                            num <= 0;
                                            sda_sel <= 0 ;//释放 sda
                                        end
                                end
                            end
								
        S_ADDACK 	:		if(scl && !sda)//主机释放SDA线 主机在下一个SCL高电平时接受数据
                                begin
                                    cstate <= S_REGADD;
                                    regadd <= 8'b0100_0011;			//*******************写入地址
                                end
//							else	如果sda 返回1 就要进行错误处理
//								cstate <= S_ADDACK;
		
		S_REGADD 	:		begin//每一个bit 要求,SCL低电平时SDA可以改变,SCL高电平时,SDA保持不变
                                if(!scl) begin		//!!!!!begin end
                                    sda_sel<=1;	//控制sda线
                                    if(num < 8)
                                        begin
                                            sda_buf<= regadd[7-num];
                                            num <= num + 1;
                                        end
                                    else
                                        begin
                                            cstate <= S_REGACK;
                                            num <= 0;
                                            sda_sel <= 0 ;//释放 sda
                                        end
                                end
                            end
								
		S_REGACK 	:		if(scl && !sda)//主机释放SDA线 主机在下一个SCL高电平时接受数据
                                begin
                                    cstate <= S_WDATA;
                                end

        S_WDATA 	:		begin
                                if(!scl) begin	
                                    sda_sel<=1;	//控制sda线
                                    if(num < 8)
                                        begin
                                            sda_buf<= wdata[7-num];
                                            num <= num + 1;
                                        end
                                    else
                                        begin
                                            cstate <= S_DATAACK;
                                            num <= 0;
                                            sda_sel <= 0 ;//释放 sda
                                        end
                                end
                            end
								
		S_DATAACK 	:		if(scl && !sda)//主机释放SDA线 主机在下一个SCL高电平时接收数据
                                begin
                                    cstate <= S_STOP;
                                end		

        S_STOP 		:		if(scl)//SCL高电平时,SDA从低到高的跳变
                                begin
                                    sda_sel <= 1;
                                    sda_buf <= 1;
                                    next_read <= 1;
                                    cstate <= S_IDLE;
                                end
									
		default		:		begin
									cstate <= S_IDLE;
									sda_sel <= 0;
									sda_buf <= 0;
									devadd <= 8'b0;
									num <= 0;
									next_read <= 0;
								end
	endcase
	
end

endmodule 

用示波器观察波形如下:

%title插图%num
图4

%title插图%num
图5
Posted in AD/DA, CMOS模拟集成电路, FPGA, FPGA, FPGA, FPGA, FPGA习题库, Quartus II, Verilog, 元器件, 开发板, 教材与教案, 文章, 编程语言

2 Comments

发表评论

相关链接