NAME
Device::Chip::Authoring
- guidance on writing a Device::Chip
class
DESCRIPTION
This file documents the Device::Chip interface from the perspective of an author of a Device::Chip
driver class; explaining how to actually write a driver instance.
It is suggested that a driver for a particular hardware chip or module provides a concrete class named within the Device::Chip
heirarchy, adding the basic name of the chip or module as a suffix; for example the driver for a Maxim MAX7219 LED driver would be called:
package Device::Chip::MAX7219;
If the driver is suitable for a range of different chips, as is often the case with closely-related parts, a lowercase letter x
can be used in the package name to suggest this variation, as for example:
package Device::Chip::TSL256x;
which can drive both the TSL2560
and TSL2561
variants. If this is the case the documentation should name all the individual variants directly, in order to make it show up properly on keyword searches and the like.
As most chip names are given in all-capitals and include digits as well, this naming scheme should be sufficient to distinguish driver module names from other parts of infrastructure, which are all named in title case and lack digits.
The Device::Class 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 the main Device::Class documentation. The documentation in this file mostly concerns itself with the abilities and utilities provided by the base class implementation.
In the current version this base class happens to be a blessed hash reference, but eventually it will be built using Object::Pad or some similar and compatible future replacement of it. Subclasses may wish to use Object::Pad as their own implementation to prepare for this.
METADATA METHODS
The following class-level methods provide information about the chip and how to drive it. They will be invoked by methods in the base classes to perform the common boilerplate configuration steps required to set up the adapter to communicate with the chip.
PROTOCOL
$pname = Device::Chip->PROTOCOL;
This method is invoked by "mount" in Device::Chip to enquire which protocol the chip wishes to use, as the first stage of configuring the adapter. This method should return a protocol name suitable for "make_protocol" in Device::Chip::Adapter.
<PNAME>_options
%options = $chip->PNAME_options( %params );
Optional methods of this general form (where PNAME is the protocol name being used) will be called by "mount" in Device::Chip once the protocol instance is available. If such a method exists, the values it returns will be used to invoke the protocol's configure
method. It is passed a copy of the parameters that were given to the mount
method.
A typical SPI_options
method usually just gives the SPI mode and maximum supported bitrate.
method SPI_options ( %params )
{
return (
mode => 0,
max_bitrate => 1E6, # 1MHz
);
}
A typical I2C_options
method would return the chip address and maximum supported bitrate. Often a mount parameter called addr
is accepted to set a different address. As this may be provided by the user in a string with the 0x..
prefix, don't forget to call oct
on it to parse that hex string into an integer.
method I2C_options ( %params )
{
my $addr = $params{addr} // 0x20;
$addr = oct $addr if $addr =~ m/^0/;
return (
addr => $addr,
max_bitrate => 400E3, # 400kHz
);
}
BASE CLASS METHODS
The following methods are provided by the Device::Chip base class itself as a convenience to chip driver authors.
adapter
$adapter = $chip->adapter;
Returns the current adapter that the chip is mounted on. This will be some instance implementing the companion interface, Device::Chip::Adapter.
protocol
$protocol = $chip->protocol;
Returns the adapter protocol module that the chip driver code will use to actually communicate with the chip.
SUGGESTED METHODS
The following method ideas are suggestions for what the API of a chip driver module should look like. While circumstances and specific details will of course differ from chip to chip, by following these general conventions where possible a greater level of consistency between drivers can be obtained. This makes it easier for users to learn and understand the features offered by a new driver, as well as to modify code to switch between them as required for different use-cases.
read_config
$config = await $chip->read_config;
Most chips this module is applicable to will contain some amount of configuration, stored inside the chip, which is accessible over its interface. This Future-returning method should return a HASH
reference whose keys map to values containing this configuration.
Keys should be named to match the names used in the manufacturer's datasheet where possible. Simple boolean values can be left as they are, but more complex values such as enumerated integer fields ought ideally to be converted, perhaps into strings, to make their values more self-explanatory.
This method should only concern itself with those values of the chip that are true configuration; that is, values which will not spontaneously change as a result of some process internal to the chip. The user program ought to be able to rely on these values not changing once set. As a result, this method may cache its results, with help from the "change_config" method.
In order to easily parse and convert the values, a driver module may find Data::Bitfield convenient for implementing this.
change_config
await $chip->change_config( %changes );
This Future-returning method should be provided in conjunction with "read_config", to allow the user to make changes to the chip's configuration. It should take as arguments, a list of name-value pairs containing the changes to make. Each name-value pair should correspond to an element that the read_config
method would have yielded.
This method should work by modifying the configuration of the chip taking into account the values provided by the caller, along with the existing configuration (as would be returned by read_config
). It is called change_...
rather than write_...
because it should preserve the existing values of any other configuration settings not given as arguments.
If read_config
uses a cache then this method should make sure to keep it updated.
read_<register> and write_<register>
$value = await $chip->read_...;
await $chip->write_...( $value );
If there is other register-like storage accessible on the chip that for some reason would not be suitable for or considered as configuration (such as live updated values from sensor readings or similar), then access to these should instead be provided by individually named Future-returning methods.
A driver module would only need to provide either reading or writing method if only one direction of transfer was available on the chip. Typically sensor output registers are not writable, and sometimes chips provide register-like storage in which drivers can write command instructions, but which cannot be read. Or it may be the case that the chip can provide both reading and writing, but values can be internally altered by some process; for example interrupt flags. User code should not expect to read the same value as was written, and the driver should not attempt to cache these.
Documentation for these methods should make it clear what sort of values are being returned or passed. Specifically, whether they are raw binary values directly mapping to the value on the chip, or whether some amount of conversion has taken place.
A driver may consider providing two sets of methods here. The first set operating on a fairly low level directly in raw binary values, and a second set that converts these values into some more abstracted value, perhaps taking into account the current configuration of the chip.
For example, an ADC with a switchable gain or reference voltage might provide a raw integer value directly with one method, and a second method to convert that value into an actual voltage. The latter should be named to suggest the units it returns, for example:
$intvalue = await $adcchip->read_adc;
$voltage = await $adcchip->read_adc_voltage;
Finally, if the chip's main purpose is to act as some kind of sensor, making measurements about the world around it, consider using Device::Chip::Sensor to declare metadata about its abilities in a standard way, so that other tooling can query and make use of it.
AUTHOR
Paul Evans <leonerd@leonerd.org.uk>