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 来存储实现

rom 初始化数据计算如下:

电压曲线:

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] 状态的判断和转移
//[b] 根据状态进行输出
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
用示波器观察波形如下:

|
1 |
