• 基于 I2C 协议的 AD实验(附代码)


    前言:因篇幅过长本实验涉及的知识点在文章中。

    实验目标:使用板载 AD/DA 芯片 PCF8591 测量来自电位器输入的模拟电压信号并在PC端显示。

    最终实验效果如下:Serial Port Utility 软件截图

    1. 硬件资源

     PCF8591 实物图

    PCF8591 原理图

    2. 程序设计

    2.1 模块框图设计

     以上模块重点学习掌握I2C协议。 

    2.2 工程整体框图

    2.2.1 I2C控制模块

    1. 模块框图

         功能按照 I2C 协议对PCF8591芯片执行数据读写操作。

    输入输出信号功能简介: 

          由表和下面的状态机可知输入由8路,首先读/写使能信号且I2C开始信号有效时,该模块开始工作,其他时刻SDA和SCL均为休闲状态。然后生成PCF8591芯片的工作时钟,方便对其进行读写操作。先进行控制命令的写入当最高位为0时,实现主机对从设备的写数据操作。当从设备接收到完该数据后回传一个应答信号。然后进行主机对从机存储命令的写入,依据存储地址字节标志信号对从设备进行单/双字节存储地址的写入(高位在前),当从设备接收到完该地址后回传一个应答信号。最后单/多字节数据写入,完成后主机接收到从机的应答信号后,向从机发送停止信号,数据
    写入完成。
         当执行读数据操作时,与写数据不同的是没有存储地址的写入操作以及最后的不响应信号。若想要实现数据的连续读/写,可持续拉高读/写使能 rd_en/wr_en,并输入有效的单字节数据读/写开始信号 i2c_start 即可。

    状态机示意图: 

    2. 波形图分析

    写数据:

     第一部分:从机设备时钟

       为了满足PCF8591芯片的模数转换速率,iic工作时钟使用250Khz。已知板载时钟是50Mhz,为什么不直接设计一个模200的计数器得到250Khz时钟?为了生成1byte数据(存储地址、控制命令、读写数据)更方便。

     读数据:

     3. RTL代码设计
    1. `timescale 1ns/1ns
    2. module i2c_ctrl
    3. #(
    4. parameter DEVICE_ADDR = 7'b1010_000 , //i2c设备地址
    5. parameter SYS_CLK_FREQ = 26'd50_000_000 , //输入系统时钟频率 50MHZ
    6. parameter SCL_FREQ = 18'd250_000 //i2c设备scl时钟频率 250khz
    7. )
    8. (
    9. input wire sys_clk , //输入系统时钟,50MHz
    10. input wire sys_rst_n , //输入复位信号,低电平有效
    11. input wire wr_en , //输入写使能信号
    12. input wire rd_en , //输入读使能信号
    13. input wire i2c_start , //输入i2c触发信号
    14. input wire addr_num , //输入i2c字节地址字节数
    15. input wire [15:0] byte_addr , //输入i2c字节地址
    16. input wire [7:0] wr_data , //输入i2c设备数据
    17. output reg i2c_clk , //i2c驱动时钟
    18. output reg i2c_end , //i2c一次读/写操作完成
    19. output reg [7:0] rd_data , //输出i2c设备读取数据
    20. output reg i2c_scl , //输出至i2c设备的串行时钟信号scl
    21. inout wire i2c_sda //输出至i2c设备的串行数据信号sda
    22. );
    23. // parameter define
    24. localparam CNT_CLK_MAX = (SYS_CLK_FREQ/SCL_FREQ) >> 2'd3 ; //cnt_clk计数器计数最大值 (SYS_CLK_FREQ/SCL_FREQ) = 200, 25 = 200/(2^3)
    25. localparam CNT_START_MAX = 8'd100; //cnt_start计数器计数最大值
    26. localparam IDLE = 4'd00, //初始状态
    27. START_1 = 4'd01, //开始状态1
    28. SEND_D_ADDR = 4'd02, //设备地址写入状态 + 控制写
    29. ACK_1 = 4'd03, //应答状态1
    30. SEND_B_ADDR_H = 4'd04, //字节地址高八位写入状态
    31. ACK_2 = 4'd05, //应答状态2
    32. SEND_B_ADDR_L = 4'd06, //字节地址低八位写入状态
    33. ACK_3 = 4'd07, //应答状态3
    34. WR_DATA = 4'd08, //写数据状态
    35. ACK_4 = 4'd09, //应答状态4
    36. START_2 = 4'd10, //开始状态2
    37. SEND_RD_ADDR = 4'd11, //设备地址写入状态 + 控制读
    38. ACK_5 = 4'd12, //应答状态5
    39. RD_DATA = 4'd13, //读数据状态
    40. N_ACK = 4'd14, //非应答状态
    41. STOP = 4'd15; //结束状态
    42. // wire define
    43. wire sda_in ; //sda输入数据寄存
    44. wire sda_en ; //sda数据写入使能信号
    45. // reg define
    46. reg [7:0] cnt_clk ; //系统时钟计数器,控制生成clk_i2c时钟信号
    47. reg [3:0] state ; //状态机状态
    48. reg cnt_i2c_clk_en ; //cnt_i2c_clk计数器使能信号
    49. reg [1:0] cnt_i2c_clk ; //clk_i2c时钟计数器,控制生成cnt_bit信号
    50. reg [2:0] cnt_bit ; //sda比特计数器
    51. reg ack ; //应答信号
    52. reg i2c_sda_reg ; //sda数据缓存
    53. reg [7:0] rd_data_reg ; //自i2c设备读出数据
    54. // cnt_clk:系统时钟计数器,控制生成clk_i2c时钟信号
    55. always@(posedge sys_clk or negedge sys_rst_n)
    56. if(sys_rst_n == 1'b0)
    57. cnt_clk <= 8'd0;
    58. else if(cnt_clk == CNT_CLK_MAX - 1'b1) //0-24
    59. cnt_clk <= 8'd0;
    60. else
    61. cnt_clk <= cnt_clk + 1'b1;
    62. // i2c_clk:i2c驱动时钟
    63. always@(posedge sys_clk or negedge sys_rst_n)
    64. if(sys_rst_n == 1'b0)
    65. i2c_clk <= 1'b1;
    66. else if(cnt_clk == CNT_CLK_MAX - 1'b1)
    67. i2c_clk <= ~i2c_clk;
    68. // cnt_i2c_clk_en:cnt_i2c_clk计数器使能信号
    69. always@(posedge i2c_clk or negedge sys_rst_n)
    70. if(sys_rst_n == 1'b0)
    71. cnt_i2c_clk_en <= 1'b0;
    72. else if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
    73. cnt_i2c_clk_en <= 1'b0;
    74. else if(i2c_start == 1'b1)
    75. cnt_i2c_clk_en <= 1'b1;
    76. // cnt_i2c_clk:i2c_clk时钟计数器,控制生成cnt_bit信号
    77. always@(posedge i2c_clk or negedge sys_rst_n)
    78. if(sys_rst_n == 1'b0)
    79. cnt_i2c_clk <= 2'd0;
    80. else if(cnt_i2c_clk <= 2'd3 || cnt_i2c_clk_en == 1'b0 )
    81. cnt_i2c_clk <= 2'd0;
    82. else if(cnt_i2c_clk_en == 1'b1)
    83. cnt_i2c_clk <= cnt_i2c_clk + 1'b1;
    84. // cnt_bit:sda比特计数器
    85. always@(posedge i2c_clk or negedge sys_rst_n)
    86. if(sys_rst_n == 1'b0)
    87. cnt_bit <= 3'd0;
    88. else if((state == IDLE) || (state == START_1) || (state == START_2)
    89. || (state == ACK_1) || (state == ACK_2) || (state == ACK_3)
    90. || (state == ACK_4) || (state == ACK_5) || (state == N_ACK))
    91. cnt_bit <= 3'd0;
    92. else if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
    93. cnt_bit <= 3'd0;
    94. else if((cnt_i2c_clk == 2'd3) && (state != IDLE))
    95. cnt_bit <= cnt_bit + 1'b1;
    96. // state:状态机状态跳转
    97. always@(posedge i2c_clk or negedge sys_rst_n)
    98. if(sys_rst_n == 1'b0)
    99. state <= IDLE;
    100. else case(state)
    101. IDLE:
    102. if(i2c_start == 1'b1)
    103. state <= START_1;
    104. else
    105. state <= state;
    106. START_1:
    107. if(cnt_i2c_clk == 3)
    108. state <= SEND_D_ADDR;
    109. else
    110. state <= state;
    111. SEND_D_ADDR:
    112. if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
    113. state <= ACK_1;
    114. else
    115. state <= state;
    116. ACK_1:
    117. if((cnt_i2c_clk == 3) && (ack == 1'b0))
    118. begin
    119. if(addr_num == 1'b1)
    120. state <= SEND_B_ADDR_H;
    121. else
    122. state <= SEND_B_ADDR_L;
    123. end
    124. else
    125. state <= state;
    126. SEND_B_ADDR_H:
    127. if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
    128. state <= ACK_2;
    129. else
    130. state <= state;
    131. ACK_2:
    132. if((cnt_i2c_clk == 3) && (ack == 1'b0))
    133. state <= SEND_B_ADDR_L;
    134. else
    135. state <= state;
    136. SEND_B_ADDR_L:
    137. if((cnt_bit == 3'd7) && (cnt_i2c_clk == 3))
    138. state <= ACK_3;
    139. else
    140. state <= state;
    141. ACK_3:
    142. if((cnt_i2c_clk == 3) && (ack == 1'b0))
    143. begin
    144. if(wr_en == 1'b1)
    145. state <= WR_DATA;
    146. else if(rd_en == 1'b1)
    147. state <= START_2;
    148. else
    149. state <= state;
    150. end
    151. else
    152. state <= state;
    153. WR_DATA:
    154. if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
    155. state <= ACK_4;
    156. else
    157. state <= state;
    158. ACK_4:
    159. if((cnt_i2c_clk == 3) && (ack == 1'b0))
    160. state <= STOP;
    161. else
    162. state <= state;
    163. START_2:
    164. if(cnt_i2c_clk == 3)
    165. state <= SEND_RD_ADDR;
    166. else
    167. state <= state;
    168. SEND_RD_ADDR:
    169. if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
    170. state <= ACK_5;
    171. else
    172. state <= state;
    173. ACK_5:
    174. if((cnt_i2c_clk == 3) && (ack == 1'b0))
    175. state <= RD_DATA;
    176. else
    177. state <= state;
    178. RD_DATA:
    179. if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
    180. state <= N_ACK;
    181. else
    182. state <= state;
    183. N_ACK:
    184. if(cnt_i2c_clk == 3)
    185. state <= STOP;
    186. else
    187. state <= state;
    188. STOP:
    189. if((cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
    190. state <= IDLE;
    191. else
    192. state <= state;
    193. default: state <= IDLE;
    194. endcase
    195. // ack:应答信号
    196. always@(*)
    197. case (state)
    198. IDLE,START_1,SEND_D_ADDR,SEND_B_ADDR_H,SEND_B_ADDR_L,
    199. WR_DATA,START_2,SEND_RD_ADDR,RD_DATA,N_ACK:
    200. ack <= 1'b1;
    201. ACK_1,ACK_2,ACK_3,ACK_4,ACK_5:
    202. if(cnt_i2c_clk == 2'd0)
    203. ack <= sda_in;
    204. else
    205. ack <= ack;
    206. default: ack <= 1'b1;
    207. endcase
    208. // i2c_scl:输出至i2c设备的串行时钟信号scl
    209. always@(*)
    210. case (state)
    211. IDLE:
    212. i2c_scl <= 1'b1;
    213. START_1:
    214. if(cnt_i2c_clk == 2'd3)
    215. i2c_scl <= 1'b0;
    216. else
    217. i2c_scl <= 1'b1;
    218. SEND_D_ADDR,ACK_1,SEND_B_ADDR_H,ACK_2,SEND_B_ADDR_L,
    219. ACK_3,WR_DATA,ACK_4,START_2,SEND_RD_ADDR,ACK_5,RD_DATA,N_ACK:
    220. if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2))
    221. i2c_scl <= 1'b1;
    222. else
    223. i2c_scl <= 1'b0;
    224. STOP:
    225. if((cnt_bit == 3'd0) &&(cnt_i2c_clk == 2'd0))
    226. i2c_scl <= 1'b0;
    227. else
    228. i2c_scl <= 1'b1;
    229. default: i2c_scl <= 1'b1;
    230. endcase
    231. // i2c_sda_reg:sda数据缓存
    232. always@(*)
    233. case (state)
    234. IDLE:
    235. begin
    236. i2c_sda_reg <= 1'b1;
    237. rd_data_reg <= 8'd0;
    238. end
    239. START_1:
    240. if(cnt_i2c_clk <= 2'd0)
    241. i2c_sda_reg <= 1'b;
    242. else
    243. i2c_sda_reg <= 1'b0;
    244. SEND_D_ADDR:
    245. if(cnt_bit <= 3'd6)
    246. i2c_sda_reg <= DEVICE_ADDR[6 - cnt_bit];
    247. else
    248. i2c_sda_reg <= 1'b0;
    249. ACK_1:
    250. i2c_sda_reg <= 1'b1;
    251. SEND_B_ADDR_H:
    252. i2c_sda_reg <= byte_addr[15 - cnt_bit];
    253. ACK_2:
    254. i2c_sda_reg <= 1'b1;
    255. SEND_B_ADDR_L:
    256. i2c_sda_reg <= byte_addr[7 - cnt_bit];
    257. ACK_3:
    258. i2c_sda_reg <= 1'b1;
    259. WR_DATA:
    260. i2c_sda_reg <= wr_data[7 - cnt_bit];
    261. ACK_4:
    262. i2c_sda_reg <= 1'b1;
    263. START_2:
    264. if(cnt_i2c_clk <= 2'd1)
    265. i2c_sda_reg <= 1'b1;
    266. else
    267. i2c_sda_reg <= 1'b0;
    268. SEND_RD_ADDR:
    269. if(cnt_bit <= 3'd6)
    270. i2c_sda_reg <= DEVICE_ADDR[6 - cnt_bit];
    271. else
    272. i2c_sda_reg <= 1'b1;
    273. ACK_5:
    274. i2c_sda_reg <= 1'b1;
    275. RD_DATA:
    276. if(cnt_i2c_clk == 2'd)
    277. rd_data_reg[7 - cnt_bit] <= sda_in;
    278. else
    279. rd_data_reg <= rd_data_reg;
    280. N_ACK:
    281. i2c_sda_reg <= 1'b1;
    282. STOP:
    283. if((cnt_bit == 3'd0) && (cnt_i2c_clk < 2'd3))
    284. i2c_sda_reg <= 1'b0;
    285. else
    286. i2c_sda_reg <= 1'b1;
    287. default:
    288. begin
    289. i2c_sda_reg <= 1'b1;
    290. rd_data_reg <= rd_data_reg;
    291. end
    292. endcase
    293. // rd_data:自i2c设备读出数据
    294. always@(posedge i2c_clk or negedge sys_rst_n)
    295. if(sys_rst_n == 1'b0)
    296. rd_data <= 8'd0;
    297. else if((state == RD_DATA) && (cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
    298. rd_data <= rd_data_reg;
    299. // i2c_end:一次读/写结束信号
    300. always@(posedge i2c_clk or negedge sys_rst_n)
    301. if(sys_rst_n == 1'b0)
    302. i2c_end <= 1'b0;
    303. else if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
    304. i2c_end <= 1'b1;
    305. else
    306. i2c_end <= 1'b0;
    307. // sda_in:sda输入数据寄存
    308. assign sda_in = i2c_sda;
    309. // sda_en:sda数据写入使能信号
    310. assign sda_en = ((state == RD_DATA) || (state == ACK_1) || (state == ACK_2)
    311. || (state == ACK_3) || (state == ACK_4) || (state == ACK_5))
    312. ? 1'b0 : 1'b1;
    313. // i2c_sda:输出至i2c设备的串行数据信号sda
    314. assign i2c_sda = (sda_en == 1'b1) ? i2c_sda_reg : 1'bz;
    315. endmodule

    2.2.2 数据生成模块

    1. 模块框图

    2. 波形图分析 

    3. RTL代码编写
    1. `timescale 1ns/1ns
    2. module pcf8591_ad
    3. (
    4. input wire sys_clk ,
    5. input wire sys_rst_n ,
    6. input wire i2c_end , //i2c设备一次读/写操作完成
    7. input wire [7:0] rd_data , //输出i2c设备读取数据
    8. output reg rd_en , //输入i2c设备读使能信号
    9. output reg i2c_start , //输入i2c设备触发信号
    10. output reg [15:0] byte_addr , //输入i2c设备字节地址
    11. output wire [7:0] po_data ,
    12. output wire po_flag
    13. );
    14. parameter CTRL_DATA = 8'b0100_0000; //AD/DA控制字
    15. parameter CNT_WAIT_MAX= 18'd6_9999 ; //采样间隔计数最大值
    16. parameter IDLE = 3'b001,
    17. AD_START = 3'b010,
    18. AD_CMD = 3'b100;
    19. //wire define
    20. //wire [31:0] data_reg/* synthesis keep */; //数码管待显示数据缓存
    21. //reg define
    22. reg [17:0] cnt_wait; //采样间隔计数器
    23. reg [4:0] state ; //状态机状态变量
    24. reg [7:0] ad_data ; //AD数据
    25. assign po_flag = i2c_end ;
    26. //cnt_wait:采样间隔计数器
    27. always@(posedge sys_clk or negedge sys_rst_n)
    28. if(sys_rst_n == 1'b0)
    29. cnt_wait <= 18'd0;
    30. else if(state == IDLE)
    31. if(cnt_wait == CNT_WAIT_MAX)
    32. cnt_wait <= 18'd0;
    33. else
    34. cnt_wait <= cnt_wait + 18'd1;
    35. else
    36. cnt_wait <= 18'd0;
    37. //state:状态机状态变量
    38. always@(posedge sys_clk or negedge sys_rst_n)
    39. if(sys_rst_n == 1'b0)
    40. state <= IDLE;
    41. else
    42. case(state)
    43. IDLE:
    44. if(cnt_wait == CNT_WAIT_MAX)
    45. state <= AD_START;
    46. else
    47. state <= IDLE;
    48. AD_START:
    49. state <= AD_CMD;
    50. AD_CMD:
    51. if(i2c_end == 1'b1)
    52. state <= IDLE;
    53. else
    54. state <= AD_CMD;
    55. default:state <= IDLE;
    56. endcase
    57. //i2c_start:输入i2c设备触发信号
    58. always@(posedge sys_clk or negedge sys_rst_n)
    59. if(sys_rst_n == 1'b0)
    60. i2c_start <= 1'b0;
    61. else if(state == AD_START)
    62. i2c_start <= 1'b1;
    63. else
    64. i2c_start <= 1'b0;
    65. //rd_en:输入i2c设备读使能信号
    66. always@(posedge sys_clk or negedge sys_rst_n)
    67. if(sys_rst_n == 1'b0)
    68. rd_en <= 1'b0;
    69. else if(state == AD_CMD)
    70. rd_en <= 1'b1;
    71. else
    72. rd_en <= 1'b0;
    73. //byte_addr:输入i2c设备字节地址
    74. always@(posedge sys_clk or negedge sys_rst_n)
    75. if(sys_rst_n == 1'b0)
    76. byte_addr <= 16'b0;
    77. else
    78. byte_addr <= CTRL_DATA;
    79. //ad_data:AD数据
    80. always@(posedge sys_clk or negedge sys_rst_n)
    81. if(sys_rst_n == 1'b0)
    82. ad_data <= 8'b0;
    83. else if(i2c_end == 1'b1) //(state == AD_CMD) && (i2c_end == 1'b1))
    84. ad_data <= rd_data;
    85. //data_reg:数码管待显示数据缓存
    86. //assign data_reg = ((ad_data * 3300) >> 4'd8);
    87. //po_data:数码管待显示数据
    88. assign po_data = ad_data[7:0];
    89. endmodule

    2.3.4 串口接收模块

    前面文章详细讲解过,这里不在复述。

    总结: (1) 根据I2C时序将读写过程,使用状态机进行描述。

  • 相关阅读:
    原型——重构数组方法chunk,新写css方法, 函数柯里化——全网最优原型继承继承
    Linux——进程间通信——管道(文件)通信
    DAY38:内网信息搜集
    JAVA 0基础 基本数据类型之间的互相转换
    【论文阅读】DeepLab:语义图像分割与深度卷积网络,自然卷积,和完全连接的crf
    亚商投资顾问 早餐FM/1130物流需求延续逐步恢复态势
    flask celery定时任务
    Pr怎么消除人声?三个方法解决!
    QT系列教程(11) TextEdit实现Qt 文本高亮
    腾讯云重新注册算不算新用户?算!
  • 原文地址:https://blog.csdn.net/m0_72885897/article/details/132394262