The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Device::Chip::Adapter - an abstraction of a hardware communication device

DESCRIPTION

This package describes an interfaces tha classes can use to implement a driver to provide access to some means of connecting an electronic chip or hardware module to a computer. An instance implementing this interface provides some means to send electrical signals to a connected chip or module, and receive replies back from it; this device is called the adapter. This is provided as a service to some instance of the related interface, Device::Chip.

It is suggested that a driver for a particular adapter provides a concrete class named within the Device::Chip::Adapter heirarchy, adding the basic name of the product or means of communication as a suffix; for example the driver for communication device based on the FDTI range of devices would be called:

   package Device::Chip::Adapter::FTDI;

This package provides a base class that such a specific implementation class could use as a superclass, but it is not required to. The important detail is that it provides the interface described by this documentation.

UTILITY CONSTRUCTOR

new_from_description

   $adapter = Device::Chip::Adapter->new_from_description( $DESC );

This utility method is provided to allow end-user programs a convenient way to construct a useable Device::Chip::Adapter instance from a given single string value. This string takes the form of a the main name of the adapter class (minus the leading Device::Chip::Adapter:: prefix), optionally followed by a single colon and some comma-separated options. Each option takes the form of a name and value, separated by equals sign. For example:

   FTDI
   FTDI:
   FTDI:product=0x0601
   BusPirate:serial=/dev/ttyUSB3,baud=57600

This utility method splits off the base name from the optional suffix, and splits the options into an even-sized name/value list. It loads the class implied by the base name and invokes a method called new_from_description on it. This is passed the even-sized name/value list obtained by splitting the option string. Any option named without a value will be passed having the value true, as a convenience for options that are simple boolean flags.

If the class does not provide the new_from_description method (and of course, simply inheriting the base class one from here does not count), then if there are no other options given, the plain new constructor is invoked instead. If this is not possible because there are user-specified options that must be honoured, then an exception is thrown instead.

Note for example that in the case above, the product option would be passed to the FTDI adapter class still as a string value; it is likely that this class would want to implement a new_from_description method to parse that using the oct operator into a plain integer.

It is intended that this method is used for creating an adapter that a standalone program can use from a description string specified by the user; likely in a commandline option or environment variable.

If $DESC is undefined, a default value is taken from the environment variable DEVICE_CHIP_ADAPTER, if defined. If not, an exception is thrown.

METHODS

The following methods documented in an await expression return Future instances.

make_protocol

   $protocol = await $adapter->make_protocol( $pname );

Returns an object that satisfies one of the interfaces documented below in "PROTOCOLS", depending on the protocol name given by $pname. This should be one of the following values:

   GPIO
   SPI
   I2C
   UART

It is unspecified what class these objects should belong to. In particular, it is permitted that an adapter could even return itself as the protocol implementation, provided it has the methods to satisfy the interface for that particular protocol. This is especially convenient in the case that the adapter is only capable of one kind of protocol.

shutdown

   $adapter->shutdown;

Shuts down the adapter in whatever manner is appropriate at the end of the lifetime of the containing program; or at least, at the point when the program has finished using the connected device.

This method is allowed to block; it does not yield a Future. It is suitable to call from a DESTROY method or END block.

PROTOCOLS

The following methods are common to all protocol instances:

sleep

   await $protocol->sleep( $secs );

Causes a fixed delay, given in (fractional) seconds. Adapter module authors should attempt to perform this delay concurrently, overlapping IO with other operations where possible.

configure

   await $protocol->configure( %args );

Sets configuration options for the protocol. The actual set of options available will depend on the type of the protocol.

Chip drivers should attempt to bundle their changes together into as few configure calls as possible, because adapters may find it most efficient to apply multiple changes in one go.

power

   await $protocol->power( $on );

Switches on or off the power to the actual chip or module, if such ability is provided by the adapter.

list_gpios

   @pin_names = $protocol->list_gpios;

Returns a list of the names of GPIO pins that are available for the chip driver to use. This list would depend on the pins available on the adapter itself, minus any pins that are in use by the protocol itself.

Adapters should name GPIO pins in a way that makes sense from the hardware; for example MOSI, SDA, DTR, Q0, etc...

meta_gpios

   @pin_definitions = $protocol->meta_gpios;

Returns a list of definition objects that define the behavior of the GPIO pins. This should be returned in the same order as the "list_gpios" method.

Each returned value will be an instance with the following methods:

name
   $def->name = STR

Gives the device's name for that GPIO pin - the name that would be returned by "list_gpios" and recognised by the other methods.

dir
   $def->dir = "r" | "w" | "rw"

Gives the data directions that the GPIO pin supports. r for pins that are read-only, w for pins that are write-only, and rw for pins that are bidirectional.

invert
   $def->invert = BOOL

If true, the hardware itself will invert the sense of reads or writes to this pin - that is, a low voltage on the pin will be represented by a true value in the "write_gpios" and "read_gpios" methods, and a high voltage represented by a false value.

Adapter implementations may wish to use a helper class definition provided by this package by calling Device::Chip::Adapter::GPIODefinition to implement these.

   $def = GPIODefinition( $name, $dir, $invert );

write_gpios

   await $protocol->write_gpios( \%pin_values );

Sets the named GPIO pins as driven outputs, and gives their new values. Any GPIO pins not named are left as they are; either driving outputs at the current state, or high-impedence inputs.

Pins are specified as a HASH reference, mapping pin names (as returned by the "list_gpios" method) to boolean logic levels.

read_gpios

   \%pin_values = await $protocol->read_gpios( \@pin_names );

Sets the named GPIO pins as high-impedence inputs, and reads their current state. Any GPIO pins not named here are left as they are; either driving outputs at the current state, or other inputs.

Pins are specified in an ARRAY reference giving the names of pins (as returned by the "list_gpios" method); read values are given in the returned HASH reference which maps pin names to boolean logic levels.

tris_gpios

   await $protocol->tris_gpios( \@pin_names );

Sets the named GPIO pins as high-impedence inputs ("tristate"). Any GPIO pins not named here are left as they are.

This method is similar to "read_gpios" except that it does not return the current pin values to the caller. Adapter implementations may implement this by simply calling "read_gpios" or they may have a more efficient variant that does not have to transfer these extra readings back from the adapter hardware.

GPIO PROTOCOL

The GPIO protocol adds no new abilities or methods; it is the most basic form of protocol that simply provides access to the generic GPIO pins of the device.

SPI PROTOCOL

Configuration Options

The following configuration options are recognised:

mode => 0 | 1 | 2 | 3

The numbered SPI mode used to communicate with the chip.

max_bitrate => INT

The highest speed, in bits per second, that the chip can accept. The adapter must pick a rate that is no higher than this. Note specifically that not all adapters are able to choose a rate arbitrarily, and so the actually communication may happen at some rate slower than this.

wordsize => INT

The number of bits per word transferred. Many drivers will not be able to accept a number other than 8.

For values less than 8, the value should be taken from the least-significant bits of each byte given to the readwrite or write methods.

For values greater than 8, use character strings with wide codepoints inside; such as created by the chr() function.

readwrite

   $words_in = await $spi->readwrite( $words_out );

Performs a complete SPI transaction; assert the SS pin, synchronously clock the data given by the $words_out out of the MOSI pin of the adapter while simultaneously capturing the data coming in to the MISO pin, then release the SS pin again. The values clocked in are eventually returned as the result of the returned future.

write

   await $spi->write( $words );

A variant of readwrite where the caller does not intend to make use of the data returned by the device, and so the adapter does not need to return it. This may or may not make a material difference to the actual communication with the adapter or device; it could be implemented simply by calling the readwrite method and ignoring the return value.

read

   $words = await $spi->read( $len );

A variant of readwrite where the chip will not care what data is written to it, so the caller does not need to supply it. This may or may not make a material difference to the actual communication with the adapter or device; it could be implemented simply by calling the readwrite method and passing in some constant string of appropriate length.

write_then_read

    $words_in = await $spi->write_then_read( $words_out, $len_in );

Performs a complete SPI transaction; assert the SS pin, synchronously clock the data given by $words_out out of the MOSI pin of the adapter, then clock in more data from the MISO pin, finally releasing the SS pin again. These two operations must be performed within a single assert-and-release SS cycle. It is unspecified what values will be sent out during the read phase; adapters should typically send all-bits-low or all-bits-high, but in general may not allow configuration of what that will be.

This differs from the /readwrite method in that it works sequentially; sending out words while ignoring the result, then reading in words while sending unspecified data.

assert_ss

release_ss

   await $spi->assert_ss;

   await $spi->release_ss;

Lower-level access methods to directly assert or release the SS pin of the adapter. These would typically be used in conjunction with "readwrite_no_ss" or "write_no_ss".

readwrite_no_ss

write_no_ss

read_no_ss

   $words_in = await $spi->readwrite_no_ss( $words_out );

   await $spi->write_no_ss( $words );

   $words = await $spi->read_no_ss( $len );

Lower-level access methods to directly perform a data transfer across the MOSI/MISO pins of the adapter, without touching the SS pin. A complete SPI transaction can be performed in conjunction with the "assert_ss" and "release_ss" methods.

   $spi->assert_ss
      ->then( sub {
         $spi->readwrite_no_ss( $words_out );
      })
      ->then( sub {
         ( $words_in ) = @_;
         $spi->release_ss->then_done( $words_in );
      });

These methods are provided for situations where it is not possible to know in advance all the data to be sent out in an SPI transaction; where the chip driver code must inspect some of the incoming data before it can determine what else needs to be sent, but when these must all be sent in one SS-asserted transaction.

Because they perform multiple independent operations on the underlying adapter, these lower-level methods may be less efficient than using the single higher-level methods of "readwrite" and "write". As such, they should only be used when the combined higher-level method cannot be used.

Note that many of these methods can be synthesized from other simpler ones. A convenient abstract base class, Device::Chip::ProtocolBase::SPI, can be used to do this, providing wrappers for some methods implemented using others. This reduces the number of distinct methods that need to be provided. Implementations may still, and are encouraged to, provide "better" versions of those methods if they can be provided more efficiently than simply wrapping others.

I2C PROTOCOL

Configuration Options

The following configuration options are recognised:

addr => INT

The (7-bit) slave address for the chip this protocol is communicating with.

max_bitrate => INT

The highest speed, in bits per second, that the chip can accept. The adapter must pick a rate that is no higher than this. Note specifically that not all adapters are able to choose a rate arbitrarily, and so the actually communication may happen at some rate slower than this.

write

    await $i2c->write( $bytes_out );

Performs a complete I²C transaction to send the given bytes to the slave chip. This includes the start condition, sending the addressing byte (which is implied; it should not be included in $bytes_out) and ending in the stop condition.

read

    $bytes_in = await $i2c->read( $len_in );

Performs a complete I²C transaction to receive the given number of bytes back from the slave chip. This includes the start condition, sending the addressing byte and ending in a stop condition.

write_then_read

    $bytes_in = await $i2c->write_then_read( $bytes_out, $len_in );

Performs a complete I²C transaction to first send the given bytes to the slave chip then reads the give number of bytes back, returning them. These two operations must be performed within a single I²C transaction using a repeated start condition.

txn

   $result = await $i2c->txn( async sub {
      my ( $helper ) = @_;
      ...
   } );

Performs a complete custom I²C transaction. Within the code block invoked by the transaction, the write, read and write_then_read methods may be called on the passed $helper instance, but they will not cause stop conditions to be sent on the wire. A stop condition will be sent after the code has finished. The return value from this method will be whatever is returned by the inner code block.

This method acts as a mutex lock, ensuring only one transaction can run concurrently. This mutex is also used by the read, write and write_then_read methods (which may optionally be implemented in terms of the txn method internally).

UART PROTOCOL

The UART protocol is still subject to ongoing design. In particular, a suitable interface for general-purpose spurious read notifications ha yet to be designed. The current API is suitable for transmit-only, or request/response interfaces where the PC side is in control of communications and knows exactly when, and how many bytes long, data will be received.

Configuration Options

baudrate => INT

The communication bitrate, in bits per second. Most adapters ought to be able to accept the common ones such as 9600, 19200 or 38400.

bits => INT

The number of bits per character. Usually 8, though some adapters may be able to offer smaller values.

parity => "n" | "o" | "e"

Disables parity generation/checking (when n), or enables it for odd (when o) or even (when e).

stop => 1 | 2

The size of the stop state, in bits. Either 1 or 2.

write

   await $uart->write( $bytes );

Transmits the given bytes over the UART TX line.

read

   $bytes = await $uart->read( $len );

Receives the given number of bytes from the UART RX line. The returned future will not complete until the requested number of bytes are available.

This API is suitable for PC-first request/response style interfaces, but will not be sufficient for chip-first notifications or other use-cases. A suitable API shape for more generic scenarios is still a matter of ongoing design investigation.

AUTHOR

Paul Evans <leonerd@leonerd.org.uk>