④FPGA的IIC程序
首先说,这里FPGA的位置相当于是IIC控制器,也就是主设备。这个FPGA的IIC模块是“lb2iic_module.vhd”,也就是LocalBus to iic。所以得知道啥是LocalBus。
不太清楚的请看我之前的讲解。
视频:【教程3】LocalBus总线及FPGA总线编程_哔哩哔哩_bilibili
文档:【乌拉喵.教程】LocalBus总线介绍及FPGA总线编程_乌拉大喵喵-CSDN博客
先跳过localbus这一部分,先看看IIC这里的主要思路。
首先是时钟,既然是IIC控制器,那么SCL就得主设备来产生了。同时,我们需要产生start、stop信号。所以在门阵内部逻辑中,我们要有一个比SCL时钟快的时钟,并且最好是SCL的2倍,4倍,8倍这个样子的。
模块需要输入两个时钟进来:
之后,根据IIC的时序,有start、写器件地址、写寄存器地址、数据、ack、noack、stop等操作时序,分别写了对应的状态机。这样就把每一种操作时序都变成了积木形式,根据总的大时序,按顺序调用状态机即可。
这里先不展开每个状态机是怎么写的,我们先看一个字节写操作的控制状态机怎么写:
也就是说,主流程状态机,只是控制调用了各个子状态机,把他们有序的组织起来了,至于每个时序中应该怎么做,那是每个子状态机的事情。
这里不展开所有状态机,讲了start和写器件地址基本包含了主要的操作过程,其他的就大家自己再去看程序了。
这个S_sda_0i1o_s和S_sda_s是干啥的呢?
这里就要先明确一件事情,就是模块的port中,不能出现inout型的信号。
所有类似于SDA这样的双向信号都要在模块的port中拆开,拆成一个in、一个out、外加一个方向控制端。
也就是这样的:
这个是为啥呢?我们看quartus中的双向信号实现的示例程序:
看下这样写出来的RTL是什么:
下面再看下字节写,字节写里包含了ack的判断:
那么,这个S_sda_s和S_sda_w、S_sda_0i1o_s和S_sda_0i1o_w都是在自己状态机里控制的,这个怎么传到最终总的流程控制上呢?
结合下面的程序,就能实现把每个子状态机的控制传到上层模块了。
看下RTL:
之后我们通读一遍程序代码lb2iic_module.vhd:
建立最顶层,树形结构是这样的:
顶层程序的写法:
单字节写
测试程序:
process(I_rst, S_clk_40M) begin if I_rst = '0' then S_cs_n <= '1'; S_wr_n <= '1'; S_rd_n <= '1'; ST_wr <= M_idle; elsif S_clk_40M'event and S_clk_40M = '1' then S_busy_bufs(0) <= S_busy; S_busy_bufs(1) <= S_busy_bufs(0); --单字节写 case ST_wr is when M_idle => if I_key_start = '0' then ST_wr <= M_wr; end if; when M_wr => S_cs_n <= '0'; S_wr_n <= '0'; S_addr <= x"15"; S_data_in <= x"5a"; S_byte_num <= 1; if S_abyte = '1' then S_cs_n <= '1'; S_wr_n <= '1'; end if; when others => ST_wr <= M_idle; end case; end if; end process;
连续写:
测试程序:
process(I_rst, S_clk_40M) begin if I_rst = '0' then S_cs_n <= '1'; S_wr_n <= '1'; S_rd_n <= '1'; ST_wr <= M_idle; elsif S_clk_40M'event and S_clk_40M = '1' then S_busy_bufs(0) <= S_busy; S_busy_bufs(1) <= S_busy_bufs(0); --连续写 case ST_wr is when M_idle => if I_key_start = '0' then S_cs_n <= '0'; S_wr_n <= '0'; S_addr <= x"15"; S_data_in <= x"5a"; S_byte_num <= 8; ST_wr <= M_wr; end if; when M_wr => if S_abyte = '1' then S_data_in <= S_data_in + '1'; end if; if S_busy_bufs(1) = '1' and S_busy_bufs(0) = '0' then S_cs_n <= '1'; S_wr_n <= '1'; end if; when others => ST_wr <= M_idle; end case; end if; end process;
单字节读
测试程序:
process(I_rst, S_clk_40M) begin if I_rst = '0' then S_cs_n <= '1'; S_wr_n <= '1'; S_rd_n <= '1'; ST_wr <= M_idle; elsif S_clk_40M'event and S_clk_40M = '1' then S_busy_bufs(0) <= S_busy; S_busy_bufs(1) <= S_busy_bufs(0); --单字节读 case ST_wr is when M_idle => if I_key_start = '0' then S_cs_n <= '0'; S_rd_n <= '0'; S_addr <= x"15"; S_byte_num <= 1; ST_wr <= M_wr; end if; when M_wr => if S_busy_bufs(1) = '1' and S_busy_bufs(0) = '0' then S_cs_n <= '1'; S_rd_n <= '1'; S_data_reg <= S_data_out; end if; when others => ST_wr <= M_idle; end case; end if; end process;
连续读
测试程序:
process(I_rst, S_clk_40M) begin if I_rst = '0' then S_cs_n <= '1'; S_wr_n <= '1'; S_rd_n <= '1'; ST_wr <= M_idle; elsif S_clk_40M'event and S_clk_40M = '1' then S_busy_bufs(0) <= S_busy; S_busy_bufs(1) <= S_busy_bufs(0); --连续读 case ST_wr is when M_idle => if I_key_start = '0' then S_cs_n <= '0'; S_rd_n <= '0'; S_addr <= x"15"; S_byte_num <= 8; ST_wr <= M_wr; end if; when M_wr => if S_abyte = '1' then S_data_reg <= S_data_out; end if; if S_busy_bufs(1) = '1' and S_busy_bufs(0) = '0' then S_cs_n <= '1'; S_rd_n <= '1'; end if; when others => ST_wr <= M_idle; end case; end if; end process;
波形: