Tock Register Interface
This crate provides an interface for defining and manipulating memory mapped registers and bitfields.
Defining registers
The crate provides three types for working with memory mapped registers:
ReadWrite
, ReadOnly
, and WriteOnly
, providing read-write, read-only, and
write-only functionality, respectively.
Defining the registers is similar to the C-style approach, where each register is a field in a packed struct:
use ;
Defining bitfields
Bitfields are defined through the register_bitfields!
macro:
register_bitfields!
Register Interface Summary
There are three types provided by the register interface: ReadOnly
,
WriteOnly
, and ReadWrite
. They provide the following functions:
.get // Get the raw register value
.read // Read the value of the given field
. // Read value of the given field as a enum member
.is_set // Check if one or more bits in a field are set
.matches_any // Check if any specified parts of a field match
.matches_all // Check if all specified parts of a field match
.extract // Make local copy of register
.set // Set the raw register value
.write // Write the value of one or more fields,
// overwriting other fields to zero
.extract // Make local copy of register
.get // Get the raw register value
.set // Set the raw register value
.read // Read the value of the given field
. // Read value of the given field as a enum member
.write // Write the value of one or more fields,
// overwriting other fields to zero
.modify // Write the value of one or more fields,
// leaving other fields unchanged
.modify_no_read // the original value, instead of doing a register read
.is_set // Check if one or more bits in a field are set
.matches_any // Check if any specified parts of a field match
.matches_all // Check if all specified parts of a field match
.extract // Make local copy of register
The first type parameter (the IntLike
type) is u8
, u16
, u32
, or u64
.
Example: Using registers and bitfields
Assuming we have defined a Registers
struct and the corresponding bitfields as
in the previous two sections. We also have an immutable reference to the
Registers
struct, named registers
.
// -----------------------------------------------------------------------------
// RAW ACCESS
// -----------------------------------------------------------------------------
// Get or set the raw value of the register directly. Nothing fancy:
registers.cr.set;
// -----------------------------------------------------------------------------
// READ
// -----------------------------------------------------------------------------
// `range` will contain the value of the RANGE field, e.g. 0, 1, 2, or 3.
// The type annotation is not necessary, but provided for clarity here.
let range: u8 = registers.cr.read;
// Or one can read `range` as a enum and `match` over it.
let range = registers.cr.read_as_enum;
match range
// `en` will be 0 or 1
let en: u8 = registers.cr.read;
// -----------------------------------------------------------------------------
// MODIFY
// -----------------------------------------------------------------------------
// Write a value to a bitfield without altering the values in other fields:
registers.cr.modify; // Leaves EN, INT unchanged
// Named constants can be used instead of the raw values:
registers.cr.modify;
// Another example of writing a field with a raw value:
registers.cr.modify; // Leaves RANGE, INT unchanged
// For one-bit fields, the named values SET and CLEAR are automatically
// defined:
registers.cr.modify;
// Write multiple values at once, without altering other fields:
registers.cr.modify; // INT unchanged
// Any number of non-overlapping fields can be combined:
registers.cr.modify;
// In some cases (such as a protected register) .modify() may not be appropriate.
// To enable updating a register without coupling the read and write, use
// modify_no_read():
let original = registers.cr.extract;
registers.cr.modify_no_read;
// -----------------------------------------------------------------------------
// WRITE
// -----------------------------------------------------------------------------
// Same interface as modify, except that all unspecified fields are overwritten to zero.
registers.cr.write; // implictly sets all other bits to zero
// -----------------------------------------------------------------------------
// BITFLAGS
// -----------------------------------------------------------------------------
// For one-bit fields, easily check if they are set or clear:
let txcomplete: bool = registers.s.is_set;
// -----------------------------------------------------------------------------
// MATCHING
// -----------------------------------------------------------------------------
// You can also query a specific register state easily with `matches_[any|all]`:
// Doesn't care about the state of any field except TXCOMPLETE and MODE:
let ready: bool = registers.s.matches_all;
// This is very useful for awaiting for a specific condition:
while !registers.s.matches_all
// Or for checking whether any interrupts are enabled:
let any_ints = registers.s.matches_any;
// Also you can read a register with set of enumerated values as a enum and `match` over it:
let mode = registers.cr.read_as_enum;
match mode
// -----------------------------------------------------------------------------
// LOCAL COPY
// -----------------------------------------------------------------------------
// More complex code may want to read a register value once and then keep it in
// a local variable before using the normal register interface functions on the
// local copy.
// Create a copy of the register value as a local variable.
let local = registers.cr.extract;
// Now all the functions for a ReadOnly register work.
let txcomplete: bool = local.is_set;
Note that modify
performs exactly one volatile load and one volatile store,
write
performs exactly one volatile store, and read
performs exactly one
volatile load. Thus, you are ensured that a single call will set or query all
fields simultaneously.
Performance
Examining the binaries while testing this interface, everything compiles down to the optimal inlined bit twiddling instructions--in other words, there is zero runtime cost, as far as an informal preliminary study has found.
Nice type checking
This interface helps the compiler catch some common types of bugs via type checking.
If you define the bitfields for e.g. a control register, you can give them a
descriptive group name like Control
. This group of bitfields will only work
with a register of the type ReadWrite<_, Control>
(or ReadOnly/WriteOnly
,
etc). For instance, if we have the bitfields and registers as defined above,
// This line compiles, because CR and registers.cr are both associated with the
// Control group of bitfields.
registers.cr.modify;
// This line will not compile, because CR is associated with the Control group,
// while registers.s is associated with the Status group.
registers.s.modify;
Naming conventions
There are several related names in the register definitions. Below is a description of the naming convention for each:
use ReadWrite;
register_bitfields!