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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569 | --
-- Author: Brent Seidel
-- Date: 9-Aug-2024
--
-- This file is part of bbs_embed.
-- Bbs_embed is free software: you can redistribute it and/or modify it
-- under the terms of the GNU General Public License as published by the
-- Free Software Foundation, either version 3 of the License, or (at your
-- option) any later version.
--
-- bbs_embed is distributed in the hope that it will be useful, but
-- WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
-- Public License for more details.
--
-- You should have received a copy of the GNU General Public License along
-- with bbs_embed. If not, see <https://www.gnu.org/licenses/>.--
--
with SAM3x8e;
use type SAM3x8e.Bit;
with SAM3x8e.PMC; -- Needed to enable I2C clocking
with SAM3x8e.PIO; -- Needed to configure I2C pins
with SAM3x8e.TWI; -- Needed for I2C interface
with BBS;
use type BBS.uint32;
with BBS.embed;
with BBS.embed.due.serial.int;
with BBS.embed.log;
--
-- Package for the I2C interface
--
-- The Arduino Due has two I2C interfaces.
-- Interface SCL SDA TWI
-- I2C-0 PB13 PB12 TWI0
-- I2C-1 PA18 PA17 TWI1
--
package body bbs.embed.i2c.due is
--
-- Function to return access to a device record.
--
function get_interface(d : port_id) return due_i2c_interface is
begin
return i2c_port(d);
end;
--
-- Initialize an interface
--
--
-- The I2C-0 port is connected to pins PB13 and PB12. The UART
-- initialization is used as a basis for this.
--
procedure init(chan : port_id; speed : speed_type) is
pins : uint32;
begin
--
-- Initialize internal data structures for both devices.
--
i2c_port(0).dev_id := BBS.embed.due.dev.TWI1_ID;
i2c_port(0).port := TWI1'Access;
i2c_port(0).pioc := BBS.embed.GPIO.Due.PIOB'Access;
i2c_port(0).sda_pin := 12;
i2c_port(0).scl_pin := 13;
i2c_port(0).int_id := Ada.Interrupts.Names.TWI1_Interrupt;
i2c_port(0).handle := buff(0);
buff0.set_interface(i2c_0'Access);
--
i2c_port(1).dev_id := BBS.embed.due.dev.TWI0_ID;
i2c_port(1).port := TWI0'Access;
i2c_port(1).pioc := BBS.embed.GPIO.Due.PIOA'Access;
i2c_port(1).sda_pin := 17;
i2c_port(1).scl_pin := 18;
i2c_port(1).int_id := Ada.Interrupts.Names.TWI0_Interrupt;
i2c_port(1).handle := buff(1);
buff1.set_interface(i2c_1'Access);
--
pins := 2**Natural(i2c_port(chan).sda_pin) or 2**Natural(i2c_port(chan).scl_pin);
--
-- Initialize hardware for selected device.
--
-- Enable clock for I2C-x
--
SAM3x8e.PMC.PMC_Periph.PMC_PCER0.PID.Arr(Integer(i2c_port(chan).dev_id)) := 1;
--
-- Configure pins PB12 and PB13 to be I2C pins.
--
-- PER
-- PDR
i2c_port(chan).pioc.PDR.Val := SAM3x8e.UInt32(pins);
-- OER
-- ODR
i2c_port(chan).pioc.OER.Val := SAM3x8e.UInt32(pins);
-- IFER
-- IFDR
i2c_port(chan).pioc.IFDR.Val := SAM3x8e.UInt32(pins);
-- SODR
-- CODR
-- IER
-- IDR
i2c_port(chan).pioc.IDR.Val := SAM3x8e.UInt32(pins);
-- MDER
-- MDDR
i2c_port(chan).pioc.MDDR.Val := SAM3x8e.UInt32(pins);
-- PUDR
-- PUER
i2c_port(chan).pioc.PUER.Val := SAM3x8e.UInt32(pins);
-- ABSR - really needed since both TWI are function A
i2c_port(chan).pioc.ABSR.Arr(Integer(i2c_port(chan).sda_pin)) := 0;
i2c_port(chan).pioc.ABSR.Arr(Integer(i2c_port(chan).scl_pin)) := 0;
-- OWER
-- OWDR
i2c_port(chan).pioc.OWDR.Val := SAM3x8e.UInt32(pins);
--
-- Do whatever configuration is needed to configure the I2C controller.
--
--
-- Set TWI clock for 100kHz
-- The system clock is 84_000_000Hz
-- The I2C clock is 100_000Hz
-- This gives 840 system clocks per I2C clock.
--
-- The clock rate is set by three parameters: CLDIV, CHDIV, and CKDIV.
-- Instead of specifying a clock rate, these specify the high time and
-- the low time for the I2C clock. The times are given by (note that
-- the wave is symmetrical so Tlow = Thigh, meaning that CLDIV = CHDIV):
-- Tlow = ((CLDIV * 2^CKDIV) + 4) * Tmck
-- Where:
-- Tmch is the master clock period
-- This should reduce to:
-- 420 = (CLDIV * 2^CKDIV) + 4
-- or
-- 416 = CLDIV * 2^CKDIV
-- or
-- CLDIV = 416/(2^CKDIV)
--
-- CLDIV and CHDIV occupy 8 bits and this are limited to 0 - 255
-- CKDIV is 3 bits and is limited to 0 - 7.
-- So, we can get:
-- CLDIV = CHDIV = 104
-- CKDIV = 2
--
if speed = high400 then
i2c_port(chan).port.CWGR.CKDIV := 0;
else
i2c_port(chan).port.CWGR.CKDIV := 2;
end if;
i2c_port(chan).port.CWGR.CLDIV := 104;
i2c_port(chan).port.CWGR.CHDIV := 104;
--
-- Enable master mode
--
i2c_port(chan).port.CR.MSEN := 1; -- Enable master mode
i2c_port(chan).port.CR.SVDIS := 1; -- Disable slave mode
--
-- Set channel not busy
--
Ada.Synchronous_Task_Control.Set_True(i2c_port(chan).not_busy);
end init;
--
-- Non-object oriented interface.
--
-- Routines to read and write data on the i2c bus. These are based on the
-- flowcharts in the datasheet. The read routines are interrupt driven.
-- The write routine is still partially polled. It will eventually be
-- converted to interrupt driven and a block write added.
--
procedure write(chan : port_id; addr : addr7; reg : uint8;
data : uint8; error : out err_code) is
status : SAM3x8e.TWI.TWI0_SR_Register;
begin
if (addr < 16#0E#) or (addr > 16#77#) then
error := invalid_addr;
return;
end if;
Ada.Synchronous_Task_Control.Suspend_Until_True(i2c_port(chan).not_busy);
i2c_port(chan).port.CR.MSEN := 1; -- Enable master mode
i2c_port(chan).port.CR.SVDIS := 1; -- Disable slave mode
i2c_port(chan).port.MMR.MREAD := 0; -- Master write
i2c_port(chan).port.MMR.IADRSZ := SAM3x8e.TWI.Val_1_Byte; -- Register addresses are 1 byte;
i2c_port(chan).port.MMR.DADR := SAM3x8e.UInt7(addr);
i2c_port(chan).port.IADR.IADR := SAM3x8e.UInt24(reg);
i2c_port(chan).port.THR.TXDATA := SAM3x8e.Byte(data);
i2c_port(chan).port.CR.STOP := 1;
loop
status := i2c_port(chan).port.SR;
exit when status.TXRDY = 1;
exit when status.NACK = 1;
exit when status.OVRE = 1;
end loop;
if status.NACK = 1 then
error := nack;
elsif status.OVRE = 1 then
error := ovre;
else
error := none;
end if;
delay until Ada.Real_Time.Clock + i2c_delay;
Ada.Synchronous_Task_Control.Set_True(i2c_port(chan).not_busy);
end;
--
function read(chan : port_id; addr : addr7; reg : uint8;
error : out err_code) return uint8 is
begin
read(chan, addr, reg, 1, error);
return uint8(i2c_port(chan).b(0));
end;
--
-- When reading or writing two bytes, is the MSB first or second? There is
-- no standard even within a single device.
--
-- Read a word with MSB first
--
function readm1(chan : port_id; addr : addr7; reg : uint8;
error : out err_code) return UInt16 is
begin
read(chan, addr, reg, 2, error);
return UInt16(i2c_port(chan).b(0))*256 + UInt16(i2c_port(chan).b(1));
end;
--
-- Read a word with MSB second (LSB first)
--
function readm2(chan : port_id; addr : addr7; reg : uint8;
error : out err_code) return UInt16 is
begin
read(chan, addr, reg, 2, error);
return UInt16(i2c_port(chan).b(1))*256 + UInt16(i2c_port(chan).b(0));
end;
--
-- Read the specified number of bytes into the device buffer
--
procedure read(chan : port_id; addr : addr7; reg : uint8;
size : buff_index; error : out err_code) is
count : buff_index := 0;
begin
if (addr < 16#0E#) or (addr > 16#77#) then
error := invalid_addr;
return;
end if;
Ada.Synchronous_Task_Control.Suspend_Until_True(i2c_port(chan).not_busy);
buff(chan).rx_read(addr, reg, size);
Ada.Synchronous_Task_Control.Suspend_Until_True(i2c_port(chan).not_busy);
error := buff(chan).get_error;
delay until Ada.Real_Time.Clock + i2c_delay;
Ada.Synchronous_Task_Control.Set_True(i2c_port(chan).not_busy);
end;
--
--
-- Object oriented interface
--
--
-- Write a byte to a specified register on an I2C device.
--
procedure write(self : in out due_i2c_interface_record; addr : addr7; reg : uint8;
data : uint8; error : out err_code) is
status : SAM3x8e.TWI.TWI0_SR_Register;
begin
if (addr < 16#0E#) or (addr > 16#77#) then
error := invalid_addr;
return;
end if;
self.activity := self.activity + 1;
Ada.Synchronous_Task_Control.Suspend_Until_True(self.not_busy);
self.port.CR.MSEN := 1; -- Enable master mode
self.port.CR.SVDIS := 1; -- Disable slave mode
self.port.MMR.MREAD := 0; -- Master write
self.port.MMR.IADRSZ := SAM3x8e.TWI.Val_1_Byte; -- Register addresses are 1 byte;
self.port.MMR.DADR := SAM3x8e.UInt7(addr);
self.port.IADR.IADR := SAM3x8e.UInt24(reg);
self.port.THR.TXDATA := SAM3x8e.Byte(data);
self.port.CR.STOP := 1;
loop
status := self.port.SR;
exit when status.TXRDY = 1;
exit when status.NACK = 1;
exit when status.OVRE = 1;
end loop;
if status.NACK = 1 then
error := nack;
elsif status.OVRE = 1 then
error := ovre;
else
error := none;
end if;
delay until Ada.Real_Time.Clock + i2c_delay;
Ada.Synchronous_Task_Control.Set_True(self.not_busy);
end write;
--
-- Write an arbitrary number of bytes to a device on the i2c bus.
--
procedure write(self : in out due_i2c_interface_record; addr : addr7; reg : uint8;
size : buff_index; error : out err_code) is
status : SAM3x8e.TWI.TWI0_SR_Register;
index : buff_index := 0;
begin
if (addr < 16#0E#) or (addr > 16#77#) then
error := invalid_addr;
return;
end if;
Ada.Synchronous_Task_Control.Suspend_Until_True(self.not_busy);
while index < size loop
self.activity := self.activity + 1;
self.port.CR.MSEN := 1; -- Enable master mode
self.port.CR.SVDIS := 1; -- Disable slave mode
self.port.MMR.MREAD := 0; -- Master write
self.port.MMR.IADRSZ := SAM3x8e.TWI.Val_1_Byte; -- Register addresses are 1 byte;
self.port.MMR.DADR := SAM3x8e.UInt7(addr);
self.port.IADR.IADR := SAM3x8e.UInt24(reg);
self.port.THR.TXDATA := SAM3x8e.Byte(self.b(index));
index := index + 1;
if index = size then
self.port.CR.STOP := 1;
end if;
loop
status := self.port.SR;
exit when status.TXRDY = 1;
exit when status.NACK = 1;
exit when status.OVRE = 1;
end loop;
if status.NACK = 1 then
error := nack;
elsif status.OVRE = 1 then
error := ovre;
else
error := none;
end if;
end loop;
delay until Ada.Real_Time.Clock + i2c_delay;
Ada.Synchronous_Task_Control.Set_True(self.not_busy);
end;
--
-- All the read functions use the block read procedure and return the
-- specified data.
--
function read(self : in out due_i2c_interface_record; addr : addr7; reg : uint8;
error : out err_code) return uint8 is
begin
self.read(addr, reg, 1, error);
return self.b(0);
end read;
--
-- When reading two bytes, is the MSB first or second? There is no standard
-- even within a single device.
--
-- Read a word with MSB first
--
function readm1(self : in out due_i2c_interface_record; addr : addr7; reg : uint8;
error : out err_code) return UInt16 is
begin
self.read(addr, reg, 2, error);
return UInt16(self.b(0))*256 + UInt16(self.b(1));
end;
--
-- Read a word with MSB second (LSB first)
--
function readm2(self : in out due_i2c_interface_record; addr : addr7; reg : uint8;
error : out err_code) return UInt16 is
begin
self.read(addr, reg, 2, error);
return UInt16(self.b(1))*256 + UInt16(self.b(0));
end;
--
-- Write a word with MSB first.
--
procedure writem1(self : in out i2c_interface_record; addr : addr7; reg : uint8;
data : uint16; error : out err_code) is
begin
self.b(0) := uint8((data/256) and 16#FF#);
self.b(1) := uint8(data and 16#FF#);
self.write(addr, reg, buff_index(2), error);
end;
--
-- Write a word with MSB second (LSB first).
--
procedure writem2(self : in out i2c_interface_record; addr : addr7; reg : uint8;
data : uint16; error : out err_code) is
begin
self.b(0) := uint8(data and 16#FF#);
self.b(1) := uint8((data/256) and 16#FF#);
self.write(addr, reg, buff_index(2), error);
end;
--
-- Read the specified number of bytes into a buffer
--
procedure read(self : in out due_i2c_interface_record; addr : addr7; reg : uint8;
size : buff_index; error : out err_code) is
status : SAM3x8e.TWI.TWI0_SR_Register;
-- s : constant BBS.embed.due.serial.int.serial_port := BBS.embed.due.serial.int.get_port(0);
begin
if (addr < 16#0E#) or (addr > 16#77#) then
error := invalid_addr;
return;
end if;
-- s.put_line("read: Waiting for driver to be ready.");
Ada.Synchronous_Task_Control.Suspend_Until_True(self.not_busy);
self.handle.rx_read(addr, reg, size);
status := self.handle.get_status;
-- s.put_line("read: Waiting for transaction to finish.");
Ada.Synchronous_Task_Control.Suspend_Until_True(self.not_busy);
status := self.handle.get_saved_status;
error := self.handle.get_error;
-- s.put_line("read: Exiting.");
delay until Ada.Real_Time.Clock + i2c_delay;
Ada.Synchronous_Task_Control.Set_True(self.not_busy);
end read;
--
-- Get the activity counter
--
function get_activity(self : in out due_i2c_interface_record) return uint32 is
begin
return self.activity;
end;
--
-- Get busy status
--
function is_busy(self : in out due_i2c_interface_record) return Boolean is
begin
return not Ada.Synchronous_Task_Control.Current_State(self.not_busy);
end;
--
-- -------------------------------------------------------------------------
-- A protected type defining the transmit and receive buffers as well as an
-- interface to the buffers. This is based on the serial port handler, but
-- is a bit simpler since (a) tx and rx is not simultaneous, so only one
-- buffer is needed, and (b) communications are more transaction/block
-- oriented so the user only needs to be notified when the exchange is
-- completed.
--
protected body handler is
--
-- Set the address to the device record. This only needs to be called
-- once during initialization/configuration.
--
procedure set_interface(d : due_i2c_interface) is
begin
device := d;
end;
--
-- Functions to return statuses
--
function is_busy return Boolean is
begin
return not not_busy;
end;
--
function get_status return SAM3x8e.TWI.TWI0_SR_Register is
begin
return device.port.SR;
end;
--
-- Entry point to transmit a character. Per Ravenscar, there can be
-- only one entry.
--
entry send(addr : addr7; reg : uint8; size : buff_index) when not_busy is
begin
not_busy := False;
bytes := size;
index := 0;
end;
--
-- Procedure to read a specified number of characters into a buffer.
-- Calls to this procedure need to be synchronized using
-- susp_not_busy.
--
procedure rx_read(addr : addr7; reg : uint8; size : buff_index) is
begin
not_busy := False;
err := none;
bytes := size;
complete := False;
index := 0;
--
-- Disable interrupts
--
device.port.IDR.TXRDY := 1;
device.port.IDR.SVACC := 1;
device.port.IDR.GACC := 1;
device.port.IDR.OVRE := 1;
device.port.IDR.NACK := 1;
device.port.IDR.ARBLST := 1;
device.port.IDR.SCL_WS := 1;
device.port.IDR.ENDRX := 1;
device.port.IDR.ENDTX := 1;
device.port.IDR.RXBUFF := 1;
device.port.IDR.TXBUFE := 1;
--
-- Enable interrupts
--
device.port.IER.RXRDY := 1;
device.port.IER.TXCOMP := 1;
--
device.port.CR.MSEN := 1; -- Enable master mode
device.port.CR.SVDIS := 1; -- Disable slave mode
device.port.MMR.MREAD := 1; -- Master read
device.port.MMR.IADRSZ := SAM3x8e.TWI.Val_1_Byte; -- Register addresses are 1 byte;
device.port.MMR.DADR := SAM3x8e.UInt7(addr);
device.port.IADR.IADR := SAM3x8e.UInt24(reg);
device.port.CR.START := 1;
if size = 1 then
device.port.CR.STOP := 1;
end if;
end;
--
-- Return the error code, if any.
--
function get_error return err_code is
begin
return err;
end;
function get_complete return Boolean is
begin
return complete;
end;
function get_saved_status return SAM3x8e.TWI.TWI0_SR_Register is
begin
return status;
end;
--
-- This is the interrupt handler. There are three different things that
-- may cause an interrupt:
-- Transmitter ready: Currently does nothing.
--
-- Receiver ready: Add characters to the buffer.
--
-- Transmit complete:
--
procedure int_handler is
begin
status := device.port.SR;
device.activity := device.activity + 1;
if status.NACK = 1 then
err := nack;
elsif status.OVRE = 1 then
err := ovre;
end if;
--
-- Currently transmit ready does nothing.
--
if status.TXRDY = 1 then
null;
end if;
--
-- Check for receiver ready and add new data to the buffer.
--
if status.RXRDY = 1 then
device.b(index) := uint8(device.port.RHR.RXDATA);
index := index + 1;
if index = bytes then
device.port.CR.STOP := 1;
Ada.Synchronous_Task_Control.Set_True(device.not_busy);
end if;
end if;
--
-- Check for transmitter empty. From the flowcharts in the datasheet,
-- this interrupt should also fire at the end of a read operation.
--
if (status.TXCOMP = 1) then
device.port.IDR.TXCOMP := 1;
device.port.IDR.RXRDY := 1;
device.port.IDR.OVRE := 1;
device.port.IDR.NACK := 1;
Ada.Synchronous_Task_Control.Set_True(device.not_busy);
not_busy := True;
complete := True;
end if;
end int_handler;
end handler;
--
end bbs.embed.i2c.due;
|