groov: Asynchronous Handling of Special Function Registers

Michael Caisse

michael.caisse@intel.com

Introduction

"Registers"

auto my_reg = (volatile uint8_t * const)(0x4000'1234);

auto a = 0xd  << 4;
auto b = 0b1  << 3;
auto c = 0b10 << 1;
auto d = 0b0  << 0;

*my_reg = a | b | c | d;

auto my_reg = (volatile uint8_t * const)(0x4000'1234);
constexpr auto c_mask = uint8_t(0b11 << 1);

auto c = 0b10 << 1;

*my_reg =
    (*my_reg & ~b_mask)
  | (c & b_mask)
  ;

inline void rmw(volatile uint32_t * const address,
                uint8_t mask, uint8_t value) {
  *address = (*address & ~mask) | (value & mask);
}

auto my_reg = (volatile uint8_t * const)(0x4000'1234);
constexpr auto a_mask = uint8_t(0xf  << 4);
constexpr auto b_mask = uint8_t(0b1  << 3);
constexpr auto c_mask = uint8_t(0b11 << 1);
constexpr auto d_mask = uint8_t(0b1  << 0);

auto c = 0b10 << 1;
rmw(my_reg, c_mask, c);

auto my_reg = (volatile uint8_t * const)(0x4000'1234);
constexpr auto a_mask = uint8_t(0xf  << 4);
constexpr auto b_mask = uint8_t(0b1  << 3);
constexpr auto c_mask = uint8_t(0b11 << 1);
constexpr auto d_mask = uint8_t(0b1  << 0);

auto a = 0xd  << 4;
auto c = 0b10 << 1;
rmw(my_reg, a_mask | c_mask, a | c);

auto my_reg = (volatile uint8_t * const)(0x4000'1234);
constexpr auto a_mask = uint8_t(0xf  << 4);
constexpr auto b_mask = uint8_t(0b11 << 2);
constexpr auto c_mask = uint8_t(0b1  << 1);
constexpr auto d_mask = uint8_t(0b1  << 0);

auto a = 0xd  << 4;
auto b = 0b1  << 3;
auto c = 0b10 << 1;
auto d = 0b0  << 0;

rmw(my_reg,
    a_mask | b_mask | c_mask | d_mask,
    a | b | c | d);

auto my_reg = (volatile uint8_t * const)(0x4000'1234);
constexpr auto a_mask = uint8_t(0xf  << 4); // read-only field
constexpr auto b_mask = uint8_t(0b1  << 3);
constexpr auto c_mask = uint8_t(0b11 << 1);
constexpr auto d_mask = uint8_t(0b1  << 0);

auto b = 0b1  << 3;
auto c = 0b10 << 1;
auto d = 0b0  << 0;

rmw(my_reg,
    b_mask | c_mask | d_mask,
    b | c | d);

auto my_reg = (volatile uint8_t * const)(0x4000'1234);
constexpr auto a_mask = uint8_t(0xf  << 4); // read-only field
constexpr auto b_mask = uint8_t(0b1  << 3); // write 1 to clear
constexpr auto c_mask = uint8_t(0b11 << 1);
constexpr auto d_mask = uint8_t(0b1  << 0);

auto c = 0b10 << 1;
auto d = 0b0  << 0;

rmw(my_reg,
    c_mask | d_mask,
    c | d);

Complications

  • Implicit int promotion
    • TDD. FDD (Fear Driven Development ~ Ben)
  • Bit twiddling errors
  • Missed/wrong optimizations
  • In the weeds

Landscape

Landscape

Landscape

Landscape

Landscape

  • Read/Write
  • Reserved, Write reset value
  • Reserved, Write 0's
  • Read Only
  • Read/Set
  • Write Only

Abstraction without Cost

A Register

  • Name
  • Data size
  • Addressable
  • Fields
using my_reg =
  reg< "my_reg"
     , uint8_t
     , 0x4000'1234
  // ...
  >;

Add Fields

  • Name
  • Representation
  • Location
using my_reg =
  reg< "my_reg"
     , uint8_t
     , 0x4000'1234
  // ....
     , field<"A", uint8_t, 7, 4>
     , field<"B", bool   , 3, 3>
     , field<"C", uint8_t, 2, 1>
     , field<"D", bool   , 0, 0>
  >;

Compose a Group

  • Name
  • Bus
  • Registers
using grp =
  group< "my_group"
       , groov::mmio_bus<>
       , my_reg
       // ... additional registers
  >;

Write Fields

native style

auto a = 0xd  << 4;
auto b = 0b1  << 3;
auto c = 0b10 << 1;
auto d = 0b0  << 0;

*my_reg = a | b | c | d;

groov style

sync_write(grp(
        "my_reg.A"_f = 0xd,
        "my_reg.B"_f = true,
        "my_reg.C"_f = 0b10,
        "my_reg.D"_f = false
    ));

Write Fields

native style

*my_reg =
    (0xd  << 4)
  | (0b1  << 3)
  | (0b10 << 1)
  | (0b0  << 0)
  ;

groov style

sync_write(grp(
        "my_reg.A"_f = 0xd,
        "my_reg.B"_f = true,
        "my_reg.C"_f = 0b10,
        "my_reg.D"_f = false
    ));

Write Fields

native style

constexpr auto c_mask =
  uint8_t(0b11 << 1);
auto c = 0b10 << 1;

*my_reg =
    (*my_reg & ~c_mask)
  | (c & c_mask)
  ;

groov style

sync_write(grp(
        "my_reg.C"_f = 0b10
    ));

Write Fields

native style

constexpr auto a_mask =
  uint8_t(0xf  << 4);
constexpr auto c_mask =
  uint8_t(0b11 << 1);
auto a = 0xd  << 4;
auto c = 0b10 << 1;

rmw(my_reg, a_mask | c_mask, a | c);

groov style

sync_write(grp(
        "my_reg.A"_f = 0xd,
        "my_reg.C"_f = 0b10
    ));

Write Fields

native style (extra read)

constexpr auto a_mask =
  uint8_t(0xf  << 4); // ro
constexpr auto b_mask =
  uint8_t(0b1  << 3); // wr 1 clr
constexpr auto c_mask =
  uint8_t(0b11 << 1);
constexpr auto d_mask =
  uint8_t(0b1  << 0);

auto c = 0b10 << 1;
auto d = 0b0  << 0;

rmw(my_reg,
    c_mask | d_mask,
    c | d);

groov style

using my_reg =
  reg< "my_reg"
     , uint8_t
     , 0x4000'1234
  // ....
     , field<"A", uint8_t, 7, 4, ro>
     , field<"B", bool   , 3, 3, wr_1_clr>
     , field<"C", uint8_t, 2, 1>
     , field<"D", bool   , 0, 0>
  >;
sync_write(grp(
        "my_reg.C"_f = 0b10,
        "my_reg.D"_f = false
    ));

Read Fields

native style

constexpr auto a_mask =
  uint8_t(0xf  << 4); // ro

auto value =
  (*my_reg & a_mask) >> 4;

groov style

auto value =
  sync_read(grp / "my_reg.A"_f);

Read Fields

native style

constexpr auto a_mask =
  uint8_t(0xf  << 4); // ro
constexpr auto c_mask =
  uint8_t(0b11 << 1);

auto r = *my_reg;
auto a = (r & a_mask) >> 4;
auto c = (r & c_mask) >> 1;

groov style

auto value =
  sync_read(grp(
    "my_reg.A"_f,
    "my_reg.C"_f));

auto a = value["my_reg.A"_f];
auto c = value["my_reg.C"_f];

Read Fields

native style

constexpr auto a_mask =
  uint8_t(0xf  << 4); // ro
constexpr auto c_mask =
  uint8_t(0b11 << 1);

auto r = *my_reg;
auto a = (r & a_mask) >> 4;
auto c = (r & c_mask) >> 1;

groov style

auto value =
  sync_read(grp / "my_reg"_r);

auto a = value["my_reg.A"_f];
auto c = value["my_reg.C"_f];

Read Fields

native style

constexpr auto a_mask =
  uint8_t(0xf  << 4); // ro
constexpr auto c_mask =
  uint8_t(0b11 << 1);


auto r = *my_reg;
auto a = (r & a_mask) >> 4;
auto c = (r & c_mask) >> 1;

groov style

auto value =
  sync_read(grp / "my_reg"_r);

auto a = value["A"_f];
auto c = value["C"_f];

template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

enum class rs_request : bool {
  REQUEST = true
};
template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

enum class bit_enable : bool {
  ENABLE  = true,
  DISABLE = false,
  ON      = true,
  OFF     = false,
  zero    = false,
  one     = true
};

template <typename T>
auto is_enabled(T v) -> bool {
  return v == T::ENABLED;
}
template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

enum class bit_enable_bar : bool {
  ENABLE  = false,
  DISABLE = true,
  ON      = false,
  OFF     = true,
  zero    = false,
  one     = true
};

template <typename T>
auto is_enabled(T v) -> bool {
  return v == T::ENABLED;
}
template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

namespace i2c {
  enum class rd_wrn : bool {
    WRITE_XFER = false,
    READ_XFER = true,
    zero = false,
    one = true
  };
}
template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

Register Declared

template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

Create Group

template <stdx::ct_string Name, std::uint32_t BaseAddress>
using i2cx_t =
  groov::group<
    Name, groov::mmio_bus<>,
    i2c_cr1<BaseAddress>,
    i2c_cr2<BaseAddress>,
    i2c_oar1<BaseAddress>,
    i2c_oar2<BaseAddress>,
    i2c_timingr<BaseAddress>,
    i2c_timeoutr<BaseAddress>,
    i2c_isr<BaseAddress>,
    i2c_icr<BaseAddress>,
    i2c_pecr<BaseAddress>,
    i2c_rxdr<BaseAddress>,
    i2c_txdr<BaseAddress>
  >;

Create Group and Instances

template <stdx::ct_string Name, std::uint32_t BaseAddress>
using i2cx_t =
  groov::group<
    Name, groov::mmio_bus<>,
    i2c_cr1<BaseAddress>,
    i2c_cr2<BaseAddress>,
    /* ... more regs ...*/
    i2c_txdr<BaseAddress>
  >;

constexpr std::uint32_t I2C1_BASE = 0x4000'5400;
constexpr std::uint32_t I2C3_BASE = 0x4000'5c00;

using i2c1_t = i2cx_t<"i2c1", I2C1_BASE>;
constexpr auto i2c1 = i2c1_t{};

using i2c3_t = i2cx_t<"i2c2", I2C3_BASE>;
constexpr auto i2c3 = i2c3_t{};

Reset the Peripheral

void i2c1_reset() {
    // per docs, reset is
    //   - write PE = 0
    //   - check PE = 0
    //   - write PE = 1
    // This sequence ensures the correct APB clock cycles
    groov::write(stm32::i2c1("cr1.PE"_f = groov::disable))
        | async::seq(groov::read(stm32::i2c1 / "cr1.PE"_f))
        | async::seq(groov::write(stm32::i2c1("cr1.PE"_f = groov::enable)))
        | async::seq(groov::read(stm32::i2c1 / "cr1.PE"_f))
        | async::sync_wait()
        ;
}

Setup GPIO

groov::sync_write(
  stm32::gpiob(
            "moder.6"_f   = gpio::mode::alternate,
            "otyper.6"_f  = gpio::outtype::open_drain,
            "ospeedr.6"_f = gpio::speed::high_speed,
            "pupdr.6"_f   = gpio::pupd::pull_up,
            "afrl.6"_f    = gpio::afsel::AF4,
            "moder.7"_f   = gpio::mode::alternate,
            "otyper.7"_f  = gpio::outtype::open_drain,
            "ospeedr.7"_f = gpio::speed::high_speed,
            "pupdr.7"_f   = gpio::pupd::pull_up,
            "afrl.7"_f    = gpio::afsel::AF4
));

Design

Layers

Write Functions

template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

Write Functions

template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    field<"AUTOEND" , bit_enable    , 25, 25>,
    field<"RELOAD"  , bit_enable    , 24, 24>,
    field<"NBYTES"  , uint8_t       , 23, 16>,
    field<"NACK"    , rs_request    , 15, 15, access::rs>,
    field<"STOP"    , rs_request    , 14, 14, access::rs>,
    field<"START"   , rs_request    , 13, 13, access::rs>,
    field<"HEAD10R" , bit_enable_bar, 12, 12>,
    field<"ADD10"   , bit_enable    , 11, 11>,
    field<"RD_WRN"  , i2c::rd_wrn   , 10, 10>,
    field<"SADD"    , uint32_t      ,  9,  0>,
    field<"SADD7"   , uint8_t       ,  7,  1>
  >;

Write Functions

namespace access {
    using rw = groov::w::replace;
    using ro = groov::read_only<groov::w::ignore>;
}


template <uint32_t BaseAddress>
using i2c_cr2 =
  reg<
    "cr2", uint32_t,
    BaseAddress+0x04, access::rw,

    field<"reserved", uint8_t       , 31, 27, access::ro>,
    field<"PECBYTE" , rs_request    , 26, 26, access::rs>,
    // ...
  >;

Paths and Read/Write Spec

auto w_spec = grp / ("reg"_r = 42);
w_spec["reg.field"_f] = 1;

The Bus

Addressable

using my_reg =
  reg< "my_reg"
     , uint8_t
     , 0x4000'1234
  // ....
     , field<"A", uint8_t, 7, 4>
     , field<"B", bool   , 3, 3>
     , field<"C", uint8_t, 2, 1>
     , field<"D", bool   , 0, 0>
  >;

Addressable

using remote_reg =
  reg< "remote_reg"
     , uint8_t
     , i2c::address<
                     port,
                     addr
       >{}
    // ....
  >;

Addressable

auto boot_address = [](){
  /* ... */
 };

using my_reg =
  reg< "my_reg"
     , uint8_t
     , boot_address
  // ....
     , field<"A", uint8_t, 7, 4>
     , field<"B", bool   , 3, 3>
     , field<"C", uint8_t, 2, 1>
     , field<"D", bool   , 0, 0>
  >;

The Bus

  struct bus {

    template <stdx::ct_string RegName, auto Mask, auto IdMask, auto IdValue>
    static auto write(auto addr, auto data) -> async::sender auto;

    template <stdx::ct_string RegName, auto Mask>
    static auto read(auto addr) -> async::sender auto;
}

The Bus - Mask

The Bus - IdMask/IdValue

  struct bus {

    template <stdx::ct_string RegName, auto Mask, auto IdMask, auto IdValue>
    static auto write(auto addr, auto data) -> async::sender auto {
      return async::just_result_of([=](){
                    // if Mask | IdMask covers all bits.... no rmw
                    auto a = (volatile decltype(Mask)*)(addr);
                    auto prev = *a & ~(Mask | IdMask);
                    *a = data | prev | IdValue;
      });
    }

    template <stdx::ct_string RegName, auto Mask>
    static auto read(auto addr) -> async::sender auto;
}

The Bus

  struct bus {

    template <stdx::ct_string RegName, auto Mask, auto IdMask, auto IdValue>
    static auto write(auto addr, auto data) -> async::sender auto;

    template <stdx::ct_string RegName, auto Mask>
    static auto read(auto addr) -> async::sender auto
      return async::just_result_of([=](){
                    auto a = (volatile decltype(Mask)*)(addr);
                    return *a;
      });
    }
}

Remote Registers

I2C

I2C

I2C

  • Send control byte with target address
  • Callback on success
  • Send register address
  • Callback on success
  • Send register data as individual bytes
  • Callback on success
  • Different callback data on last byte
  • Different callback on errors
  • Need timeouts

auto set_brightness(uint8_t bright) -> async::sender auto {
    // ... 
}

auto set_brightness(uint8_t bright) -> async::sender auto {

  auto setup_controller =
      groov::write(
                   stm32::i2c1(
                               "cr2.ADD10"_f = groov::disable,
                               "cr2.SADD7"_f = 0x6f,
                               "cr2.RD_WRN"_f = i2c::rd_wrn::WRITE_XFER,
                               "cr2.NBYTES"_f = 2,
                               "cr2.RELOAD"_f = groov::disable,
                               "cr2.AUTOEND"_f = groov::enable,
                               "cr2.START"_f = true
                               ))
    | seq(spin_for_txis)
    ;

  /*  ... more things ... */
}

auto spin_for_txis =
    groov::read(stm32::i2c1 / "isr.TXIS"_f)
  | async::repeat_until([](auto v) { return v == true; })
  ;

auto set_brightness(uint8_t bright) -> async::sender auto {

  auto setup_controller =
    groov::write(
                 stm32::i2c1(
                             // ... fields ...
                             ))
    | seq(spin_for_txis)
    ;

  /*  ... more things ... */
}

template <stdx::ct_string BitName>
auto spin_for_flag() {
  using stdx::literals::operator""_cts;
  constexpr auto field = groov::path<"isr"_cts, BitName>{};
  return
    groov::read(stm32::i2c1 / field)
    | async::repeat_until([](auto v) { return v == true; })
    ;
}

auto set_brightness(std::uint8_t bright) -> async::sender auto {

  auto setup_controller =
    groov::write(
                 stm32::i2c1(
                             // ... fields ...
                             ))
    | seq(spin_for_flag<"TXIS">())
    ;

  /*  ... more things ... */
}

auto set_brightness(std::uint8_t bright) -> async::sender auto {

  auto setup_controller =
    // setup_controller things
    ;

  auto write_address =
    groov::write(
                 stm32::i2c1(
                             "txdr.TXDATA"_f = 0x19 // LED Brightness address
                             ))
    | seq(spin_for_flag<"TXIS">())
    ;

  /*  ... more things ... */
}

auto set_brightness(std::uint8_t bright) -> async::sender auto {

  auto setup_controller =
    // setup_controller things
    ;

  auto write_address =
    // write_address things
    ;

  auto write_data = 
    groov::write(
                 stm32::i2c1(
                             "txdr.TXDATA"_f = bright
                             ))
    | seq(spin_for_flag<"STOPF">())
    | seq(groov::write(
                       stm32::i2c1(
                                   "icr.STOPCF"_f = groov::set
                                   )))
    ;

  /*  ... more things ... */
}

auto set_brightness(std::uint8_t bright) -> async::sender auto {

  auto setup_controller =
    // setup_controller things
    ;

  auto write_address =
    // write_address things
    ;

  auto write_data = 
    // write_data things
    ;

  return
    setup_controller
    | seq(write_address)
    | seq(write_data)
    ;
}

std::uint8_t bright = 0x00;
while(true) {
  button::set_brightness(++bright)
    | async::sync_wait();
}

std::uint8_t bright = 0x00;
while(true) {
  // spin little heater, spin
  button::set_brightness(++bright)
    | async::sync_wait();
}

auto set_brightness(uint8_t bright) -> async::sender auto {

  auto setup_controller =
    // ... setup controller things
    | seq(spin_for_flag<"TXIS">())
    ;

  auto write_address =
    // ... setup address things
    | seq(spin_for_flag<"TXIS">())
    ;

  auto write_data =
    // ... setup data things
    | seq(spin_for_flag<"STOPF">())
    // ... send stop
    ;

  return /* ... */ ;
}

auto set_brightness(uint8_t bright) -> async::sender auto {

  auto setup_controller =
    // ... setup controller things
    | seq(wait_for_flag<"TXIS">())
    ;

  auto write_address =
    // ... setup address things
    | seq(wait_for_flag<"TXIS">())
    ;

  auto write_data =
    // ... setup data things
    | seq(wait_for_flag<"STOPF">())
    // ... send stop
    ;

  return /* ... */ ;
}

How would you solve this?

template <stdx::ct_string BitName>
auto spin_for_flag() {
  using stdx::literals::operator""_cts;
  constexpr auto field = groov::path<"isr"_cts, BitName>{};
  return
    groov::read(stm32::i2c1 / field)
    | async::repeat_until([](auto v) { return v == true; })
    ;
}

template <stdx::ct_string BitName>
auto wait_for_flag() {
  using stdx::literals::operator""_cts;
  constexpr auto field = groov::path<"isr"_cts, BitName>{};
  return
    async::trigger_scheduler<"i2c1_ev">{}.schedule()
    | seq(groov::read(stm32::i2c1 / field))
    | async::repeat_until([](auto v) { return v == true; })
    ;
}

template <stdx::ct_string BitName>
auto wait_for_flag() {
  using stdx::literals::operator""_cts;
  constexpr auto field = groov::path<"isr"_cts, BitName>{};
  return
    async::trigger_scheduler<"i2c1_ev">{}.schedule()
    | seq(groov::read(stm32::i2c1 / field))
    | async::repeat_until([](auto v) { return v == true; })
    ;
}

// ......
extern "C" {
  void I2C1_EV_Handler(void) {
    async::run_triggers<"i2c1_ev">();
  }
}

template <stdx::ct_string BitName>
auto wait_for_flag() {
  using stdx::literals::operator""_cts;
  constexpr auto field = groov::path<"isr"_cts, BitName>{};
  using write_spec_t = decltype(groov::sync_read(stm32::i2c1 / "isr"_r));

  return
    async::trigger_scheduler<"i2c1_ev", write_spec_t>{}.schedule()
    | async::repeat_until([field](auto r) { return r[field] == true; })
    ;
}

// ......
extern "C" {
  void I2C1_EV_Handler(void) {
    auto v = groov::sync_read(stm32::i2c1 / "isr"_r);
    async::run_triggers<"i2c1_ev">(v);
  }
}

What could go wrong?

auto set_brightness(std::uint8_t bright) -> async::sender auto {

  auto setup_controller =
    // setup_controller things
    ;

  auto write_address =
    groov::write(
                 stm32::i2c1(
                             "txdr.TXDATA"_f = 0x19 // LED Brightness address
                             ))
    | seq(wait_for_flag<"TXIS">())
    ;

  /*  ... more things ... */
}

auto set_brightness(std::uint8_t bright) -> async::sender auto {

  auto setup_controller =
    // setup_controller things
    ;

  auto write_address =
    async::when_all(
                    wait_for_flag<"TXIS">(),

                    groov::write(
                                 stm32::i2c1(
                                             "txdr.TXDATA"_f = 0x19 // LED Brightness address
                                             ))
                    )
    ;

  /*  ... more things ... */
}

Handling Errors

auto write_address =
  async::when_all(
                  wait_for_flag<"TXIS">(),

                  groov::write(
                               stm32::i2c1(
                                           "txdr.TXDATA"_f = 0x19 // LED Brightness address
                                           ))
                  );

Handling Errors

auto tx_done_or_error =
  async::when_any(
                  wait_for_flag<"TXIS">(),
                  wait_for_error()
                  );

auto write_address =
  async::when_all(
                  tx_done_or_error,

                  groov::write(
                               stm32::i2c1(
                                           "txdr.TXDATA"_f = 0x19 // LED Brightness address
                                           ))
                  );

Handling Errors

auto wait_for_error() {
  using write_spec_t = decltype(groov::sync_read(stm32::i2c1 / "isr"_r));
  return
      async::trigger_scheduler<"i2c1_err", write_spec_t>{}.schedule()
    | asnyc::then_error([](auto r){ return r; });
}

auto tx_done_or_error =
  async::when_any(
                  wait_for_flag<"TXIS">(),
                  wait_for_error()
                  );

auto write_address =
  async::when_all(
                  tx_done_or_error,
                  groov::write(
                               stm32::i2c1(
                                           "txdr.TXDATA"_f = 0x19 // LED Brightness address
                                           ))
                  );

Breaking it Down

auto write_byte = [](std::uint8_t b) {
  return
          async::when_all(
                          xfer_done_or_error,
                          groov::write(stm32::i2c1("txdr.TXDATA"_f = b))
                          );
};

auto write_last_byte = [](std::uint8_t b) {
  return
          async::when_all(
                            xfer_stop_or_error,
                            groov::write(stm32::i2c1("txdr.TXDATA"_f = b))
                        )
    | async::seq(
                            groov::write(stm32::i2c1("icr.STOPCF"_f = groov::set))
                 );
};

The Button Bus

template <std::uint8_t i2c_addr>
struct bus {
  static constexpr auto control_spec_ =
    stm32::i2c1(
                    "cr2.ADD10"_f = groov::disable,
                    "cr2.SADD7"_f = i2c_addr,
                    "cr2.RD_WRN"_f = stm32::i2c::rd_wrn::WRITE_XFER,
                    "cr2.NBYTES"_f = 0,
                    "cr2.RELOAD"_f = groov::disable,
                    "cr2.AUTOEND"_f = groov::enable,
                    "cr2.START"_f = true
                );

  template <stdx::ct_string RegName, auto Mask, auto IdMask, auto IdValue>
  static auto write(uint8_t addr, decltype(Mask) data) -> async::sender auto;
};

template <std::uint8_t i2c_addr>
struct bus {

  template <stdx::ct_string RegName, auto Mask, auto IdMask, auto IdValue>
  static auto write(uint8_t addr, decltype(Mask) data) -> async::sender auto {

    data |= IdValue;

    constexpr std::uint8_t bytes_to_write = sizeof(data) + 1;
    auto control_spec = control_spec_;
    control_spec["cr2.NBYTES"_f] = bytes_to_write;

    auto setup_controller =
      async::when_all(
                      xfer_done_or_error,
                      groov::write(control_spec)
                      );

    auto values = stdx::bit_unpack<std::uint8_t>(data);
    auto write_data =
      std::apply([](auto ... v, auto last) {
                    return
                      (async::seq(write_byte(v)) | ... | async::seq(write_last_byte(last)));
              }, values);

    return
      setup_controller
      | async::seq(write_byte(addr))
      | write_data;
  }
};

The Button Bus

using my_button_t =                             
    qwiic_button::reg::qwiic_button_t<
              "my_button", button::bus<0x6f>
          >;

my_button_t my_button;

The Button Bus

std::uint8_t bright = 0x00;
while(true) {
  groov::write(
               my_button("led_brightness"_r = ++bright)
               )
    | async::timeout_after(10ms, timeout_error{})
    | async::upon_error(/* ... */)
    | async::sync_wait();
}

Testing

#include <groov/test.hpp>

namespace groov::test {
  using test_bus_list =
    make_test_bus_list<
      default_test_bus<"my_group">
    >;
}

/// more includes

TEST_CASE("read/write the thing", "[test]") {
  test::reset_store<grp>();
  test::set_value<grp>("reg"_r, 0xdeadbeef);

  // do stuff

  auto v = test::get_value<grp>("reg"_r);
  REQUIRE(v);
  CHECK(*v == 0xdeadbef1u);
}

#include <groov/test.hpp>

namespace groov::test {
  using test_bus_list =
    make_test_bus_list<
      default_test_bus<"my_group">
    >;
}

/// more includes

TEST_CASE("test write of thing", "[test]") {
  test::reset_store<grp>();

  // do stuff

  auto v = test::get_value<grp>("reg"_r);
  REQUIRE(v); // x-propogation testing
  CHECK(*v == 0xdeadbef1u);
}

#include <groov/test.hpp>

namespace groov::test {
  using test_bus_list =
    make_test_bus_list<
      default_test_bus<"my_group">,
      test_bus<"other_group", my_test_bus>
    >;
}

/// more includes

#include <groov/test.hpp>

namespace groov::test {
  using test_bus_list =
    make_test_bus_list<
      test_bus<"my_button", button::test_bus>
    >;
}

// .....

Wrapping it Up

Thoughts

  • Declarative
  • Design for success
  • Abstractions eliminate bugs
  • Abstractions do not require runtime cost
  • Senders is an ideal design for complex embedded

https://github.com/intel/generic-register-operation-optimizer/

https://github.com/intel/cpp-baremetal-senders-and-receivers/

https://github.com/intel/cpp-std-extensions/

https://github.com/intel/compile-time-init-build/



Thank you

Questions?