Here’s my proposed I2C interface for uLisp. I’d welcome your comments.
In keeping with the Lisp philosophy of using streams for communicating with devices, the uLisp I2C functions will use a stream object to represent each I2C device being addressed. Printing an I2C stream will show the address of the device; for example:
<i2c-stream #x68>
Master writing to slave
The simplest operation is writing to a slave. In this case you simply put a series of write-byte
commands within a with-i2c
form. The with-i2c
form takes care of the start and stop commands. For example:
(with-i2c (str #x70)
(write-byte #x21 str)))
Within the form the first parameter to with-i2c
, for example str
, is bound to a stream object.
The second parameter is the I2C address, and the optional third parameter is omitted or nil
for a write, and t
or a number to specify a read.
Master reading from slave
When the master device is reading from a slave it has to terminate each read operation with an ACK apart from the last one, which is terminated by a NAK. This tells the slave not to send any further bytes.
The uLisp I2C interface allows you to identify the last read-byte
in either of two ways:
You can specify the total number of bytes you are going to read, as the third parameter of the with-i2c
form or restart-i2c
function. With this approach read-byte
will automatically terminate the last call with a NAK:
(defun get ()
(with-i2c (str #x68 nil)
(write-byte #x00 str)
(restart-i2c str 3)
(list
(read-byte str)
(read-byte str)
(read-byte str))))
Alternatively you can just specify the third parameter of the with-i2c
form or restart-i2c
function as t
(to indicate read), and explicitly identify the last read-byte
command by specifying an additional t
parameter:
(defun get ()
(with-i2c (str #x68 nil)
(write-byte #x00 str)
(restart-i2c str t)
(list
(read-byte str)
(read-byte str)
(read-byte str t))))
Issuing a restart
If a master wants to send a value to a slave, and then immediately read bytes back, the master should issue a restart-i2c
command to switch between write mode and read mode. This ensures that the master retains control of the I2C bus between the write and the read.
A typical application is reading bytes from an I2C EEPROM. The master would first write the address, then send a restart and read the data from the EEPROM starting at that address.