1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
| --------------------------------------------------------------------------------
--
-- FileName: i2c_master.vhd
--------------------------------------------------------------------------------
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_unsigned.all;
ENTITY i2c_master IS
PORT(
clk : IN STD_LOGIC; --system clock
rst : IN STD_LOGIC; --active HIGH reset
ena : IN STD_LOGIC; --latch in command
-- addr : IN STD_LOGIC_VECTOR(6 DOWNTO 0); --address of target slave
rw : IN STD_LOGIC; --'0' is write, '1' is read
-- data_wr : IN STD_LOGIC_VECTOR(7 DOWNTO 0); --data to write to slave
CNT_ST : out std_logic_vector(3 downto 0);
-- clk_out : out std_logic;
data_rd : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); --data read from slave
sda : INOUT STD_LOGIC; --serial data output of i2c bus
scl : INOUT STD_LOGIC); --serial clock output of i2c bus
END i2c_master;
ARCHITECTURE logic OF i2c_master IS
TYPE machine IS(ready, start, command, slv_ack1, wr, wr2, rd, slv_ack2, mstr_ack, stop,slv_ack3); --needed states
SIGNAL ps,ns : machine; --state machine
SIGNAL data_clk : STD_LOGIC; --clock edges for sda
SIGNAL scl_clk : STD_LOGIC; --constantly running internal scl
SIGNAL scl_ena : STD_LOGIC := '0'; --enables internal scl to output
SIGNAL sda_int : STD_LOGIC := '1'; --internal sda
SIGNAL sda_ena_n : STD_LOGIC; --enables internal sda to output
SIGNAL addr_rw : STD_LOGIC_VECTOR(7 DOWNTO 0); --latched in address and read/write
SIGNAL data_tx : STD_LOGIC_VECTOR(7 DOWNTO 0); --latched in data to write to slave
SIGNAL data_rx : STD_LOGIC_VECTOR(7 DOWNTO 0); --data received from slave
SIGNAL bit_cnt : INTEGER RANGE 0 TO 7 := 7; --tracks bit number in transaction
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
SIGNAL addr : STD_LOGIC_VECTOR(6 DOWNTO 0):= "1010000"; --address of target slave
-- SIGNAL rw : STD_LOGIC:= '0'; --'0' is write, '1' is read
SIGNAL data_wr : STD_LOGIC_VECTOR(7 DOWNTO 0):= "11110000"; --data to write to slave
SIGNAL WR_ADR : STD_LOGIC_VECTOR(7 DOWNTO 0):= "00000001";
signal delay : std_logic_vector(30 downto 0):= (others => '0');
signal count : INTEGER RANGE 0 TO 200;--divider*4; --timing for clock generation
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
BEGIN
process(clk,rst)
begin
if(rst = '1')then
count <= 0;
elsif rising_edge(clk)then
IF(count = 199)then --end of timing cycle
count <= 0; --reset timer
ELSE --clock stretching from slave not detected
count <= count + 1; --continue clock generation timing
END IF;
end if;
end process;
--generate the timing for the bus clock (scl_clk) and the data clock (data_clk)
PROCESS(clk,rst,count)
-- VARIABLE count : INTEGER RANGE 0 TO divider*4; --timing for clock generation
BEGIN
IF(rst = '1') THEN --reset asserted
scl_clk <= '0';
data_clk <= '0';
ELSIF RISING_EDGE(clk) THEN
CASE count IS
WHEN 0 TO 49 => --first 1/4 cycle of clocking
scl_clk <= '0';
data_clk <= '0';
WHEN 50 to 99 => --second 1/4 cycle of clocking
scl_clk <= '0';
data_clk <= '1';
WHEN 100 to 149 => --third 1/4 cycle of clocking
scl_clk <= '1'; --release scl
data_clk <= '1';
WHEN 150 to 199 => --last 1/4 cycle of clocking
scl_clk <= '1';
data_clk <= '0';
WHEN others => null ;
END CASE;
END IF;
END PROCESS;
process(clk,rst)
begin
if(rst = '1')then
ps <= ready;
elsif rising_edge(clk)then
ps <= ns ;
end if;
end process;
--state machine and writing to sda during scl low (data_clk rising edge)
PROCESS(data_clk, rst)
BEGIN
IF(rst = '1') THEN --reset asserted
ns <= ready; --return to initial state
scl_ena <= '0'; --sets scl high impedance
sda_int <= '1'; --sets sda high impedance
bit_cnt <= 7; --restarts data bit counter
data_rd <= "00000000"; --clear data read port
CNT_ST <= "1111";
ELSIF RISING_EDGE(data_clk) THEN
CASE ps IS
WHEN ready => --idle state
CNT_ST <= "0001";
IF(ena = '1') THEN --transaction requested
addr_rw <= addr & rw; --collect requested slave address and command
data_tx <= WR_ADR;--data_wr; --collect requested data to write
ns <= start; --go to start bit
ELSE --remain idle
ns <= ready; --remain idle
END IF;
WHEN start => --start bit of transaction
CNT_ST <= "0010";
scl_ena <= '1'; --enable scl output
sda_int <= addr_rw(bit_cnt); --set first address bit to bus
ns <= command; --go to command
WHEN command => --address and command byte of transaction
CNT_ST <= "0011";
IF(bit_cnt = 0) THEN --command transmit finished
sda_int <= '1'; --release sda for slave acknowledge
bit_cnt <= 7; --reset bit counter for "byte" states
ns <= slv_ack1; --go to slave acknowledge (command)
ELSE --next clock cycle of command state
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
sda_int <= addr_rw(bit_cnt-1); --write address/command bit to bus
ns <= command; --continue with command
END IF;
WHEN slv_ack1 => --slave acknowledge bit (command)
CNT_ST <= "0100";
IF(addr_rw(0) = '0') THEN --write command
sda_int <= data_tx(bit_cnt); --write first bit of data
ns <= wr; --go to write byte
ELSE --read command
sda_int <= '1'; --release sda from incoming data
ns <= rd; --go to read byte
END IF;
WHEN wr => --write byte of transaction
CNT_ST <= "0101";
IF(bit_cnt = 0) THEN --write byte transmit finished
sda_int <= '1'; --release sda for slave acknowledge
bit_cnt <= 7; --reset bit counter for "byte" states
ns <= slv_ack2; --go to slave acknowledge (write)
ELSE --next clock cycle of write state
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
sda_int <= data_tx(bit_cnt-1); --write next bit to bus
ns <= wr; --continue writing
END IF;
WHEN slv_ack2 => --slave acknowledge bit (write)
CNT_ST <= "0110";
IF(ena = '1') THEN --continue transaction
-- addr_rw <= addr & rw; --collect requested slave address and command
data_tx <= data_wr; --collect requested data to write
-- IF(rw = '1') THEN --continue transaction with a read
-- state <= start; --go to repeated start
-- ELSE --continue transaction with another write
sda_int <= data_wr(bit_cnt); --write first bit of data
ns <= wr2; --go to write byte
-- END IF;
ELSE --complete transaction
scl_ena <= '0'; --disable scl
ns <= stop; --go to stop bit
END IF;
WHEN wr2 => --write byte of transaction
CNT_ST <= "0111";
IF(bit_cnt = 0) THEN --write byte transmit finished
sda_int <= '1'; --release sda for slave acknowledge
bit_cnt <= 7; --reset bit counter for "byte" states
ns <= slv_ack3; --go to slave acknowledge (write)
ELSE --next clock cycle of write state
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
sda_int <= data_tx(bit_cnt-1); --write next bit to bus
ns <= wr2; --continue writing
END IF;
WHEN slv_ack3 => --slave acknowledge bit (write)
CNT_ST <= "1000";
scl_ena <= '0'; --disable scl
ns <= stop; --go to stop bit
WHEN stop => --stop bit of transaction
CNT_ST <= "1001";
if(rw = '1')then
ns <= ready;
else
ns <= stop;
end if;
WHEN rd => --read byte of transaction
CNT_ST <= "1010";
IF(bit_cnt = 0) THEN --read byte receive finished
IF(ena = '1' AND rw = '1') THEN --continuing with another read
sda_int <= '0'; --acknowledge the byte has been received
ELSE --stopping or continuing with a write
sda_int <= '1'; --send a no-acknowledge (before stop or repeated start)
END IF;
data_rd(0) <= sda;
data_rd(7 downto 1) <= data_rx(7 downto 1); --output received data
bit_cnt <= 7; --reset bit counter for "byte" states
ns <= stop;--mstr_ack; --go to master acknowledge
ELSE --next clock cycle of read state
data_rx(bit_cnt) <= sda;
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
ns <= rd; --continue reading
END IF;
WHEN mstr_ack => --master acknowledge bit after a read
CNT_ST <= "1011";
IF(ena = '1') THEN --continue transaction
addr_rw <= addr & rw; --collect requested slave address and command
data_tx <= data_wr; --collect requested data to write
IF(rw = '0') THEN --continue transaction with a write
ns <= start; --repeated start
ELSE --continue transaction with another read
sda_int <= '1'; --release sda from incoming data
ns <= rd; --go to read byte
END IF;
ELSE --complete transaction
scl_ena <= '0'; --disable scl
ns <= stop; --go to stop bit
END IF;
WHEN others => null;
END CASE;
END IF;
--reading from sda during scl high (falling edge of data_clk)
END PROCESS;
--set sda output
WITH ps SELECT
sda_ena_n <= data_clk WHEN start, --generate start condition
NOT data_clk WHEN stop, --generate stop condition
sda_int WHEN OTHERS; --set to internal sda signal
--set scl and sda outputs
scl <= scl_clk ;--WHEN scl_ena = '1' ELSE 'Z';
sda <= '0' WHEN sda_ena_n = '0' ELSE sda_ena_n;--'Z';
--clk_out <= data_clk;
END logic; |