Using the I2C bus from uLisp


#1

I would like to talk to I2C sensors using Ulisp from an Arduino Uno.

Ideally I want to have a serial or SPI connection with a Common Lisp on a controlling computer and a few functions to implement simple sequences of I2C commands, to control muxers and read out sensor.

Has anyone done anything in this direction?


#2

There’s currently no I2C interface in uLisp, but it’s something that I’m planning to add soon.


#3

I added the obvious functions and tried writing to the port without a device connected.
The file with all modifications is here: https://github.com/plops/ulisp-i2c

Here are the main functions:

#include <Wire.h>

object *fn_i2c_begin(object*args,object*env)
{
  (void) env;
  Wire.beginTransmission(integer(first(args)));
  return nil;
}

object *fn_i2c_end(object*args,object*env)
{
  (void) env;
  (void) args;
  Wire.endTransmission();
  return nil;
}

object *fn_i2c_write(object*args,object*env)
{
  (void) env;
  Wire.write(byte(integer(first(args))));
  return nil;
}

object *fn_i2c_request(object*args,object*env)
{
  (void) env;
  int addr = integer(first(args));
  int quantity = integer(second(args));
  int stp = integer(third(args));
  
  int value = Wire.requestFrom(addr,quantity,stp);
  return number(value);
}

object *fn_i2c_read(object*args,object*env)
{
  (void) env;
  (void) args;
  return number(Wire.read());
}

Compilation shows this:

Sketch uses 20,298 bytes (62%) of program storage space. Maximum is 32,256 bytes.
Global variables use 1,739 bytes (84%) of dynamic memory, leaving 309 bytes for local variables. Maximum is 2,048 bytes.
Low memory available, stability problems may occur.

Here is oscilloscope output for this code:
(loop (i2c-begin 30) (i2c-write 32) (i2c-end))

I haven’t tried talking to a device yet (the ones I have are not 5V compatible).


#4

That looks good. The standard Wire library uses rather a lot of RAM so I’m planning to investigate other leaner libraries.

I’m also thinking about something like with-i2C, like the Lisp function with-open-file, which you would wrap around your calls to read and write and which would handle the beginTransmission() and endTransmission() automatically for you.


#5

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.


#6

The latest version of uLisp now includes I2C and SPI interfaces; see http://www.ulisp.com/show?1EK4

Thanks, David


#7

This is a work of art! Amazing.


#8