Who Am I?

  • Mikael Rosbacke
  • Independent SW developer, active in Stockholm/Sweden.
  • Focus on microcontrollers, embedded Linux, C, C++.

e-mail: mikael.rosbacke@gmail.com

github: http://www.github.com/rosbacke

http://www.akaza.se

Using C++ to get type safe access to hardware registers

Vendor include file

#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region                                */
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000)
#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000)
//...
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
//...
typedef struct {
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  // ...
} GPIO_TypeDef;

/******************  Bits definition for GPIO_MODER register  *****************/
#define GPIO_MODER_MODER0                    ((uint32_t)0x00000003)
#define GPIO_MODER_MODER0_0                  ((uint32_t)0x00000001)
#define GPIO_MODER_MODER0_1                  ((uint32_t)0x00000002)

#define GPIO_MODER_MODER1                    ((uint32_t)0x0000000C)
...

The traditional C way

#include <stm32f4xx.h>

// GPIO setup.
GPIOA->ODR |= GPIO_ODR_ODR_9;

uint32_t t = GPIOA->MODER;
t &= ~(GPIO_MODER_MODER9 | GPIO_MODER_MODER10);
t |= GPIO_MODER_MODER9_1 | GPIO_MODER_MODER10_1;
GPIOA->MODER = t;

// Alternate function 7 (usart1) to pin 9 + 10.
t = GPIOA->AFR[1];
t &= ~((uint32_t)0xff0);
t |= (uint32_t)0x770;
GPIOA->AFR[1] = t;

Notes on current practice

  • Very heavy use of preprocessor macros.
  • Hardware access requires volatile. Handled via struct here. (behind __IO macro)
  • Lots of naked unsigned integer operations. No types to help the compiler detect errors.
  • No help with sub fields of registers. It has to be done by hand using bit manipulations.
  • Include files for different controllers will name clash.

A better way

// Set bit flag 'te' in register 'cr1' in devicetype 'Usart' to '1'.
// Writing to device module usart1.

// Type safe writes, various degress of static typing.
write<Usart::cr1::te>(Addr::usart1, 1);
write<Usart::cr1::te, Addr::usart1>(1);
write<Usart::cr1::te, Addr::usart1, 1>();

// Read out status bit rxne from 'st' register.
auto t = read<Usart::sr::rxne, Addr::usart1>();

Notes on the syntax

  • write<usart::cr1::te, Addr::usart1>(1);
  • usart : Device type. A collection of registers for a device
  • sr : A 32 bit register in the device.
  • te : 1 bit bitfield in the register.
  • Addr::usart1 : Indicate the device to write to.
  • 1 value to be written to the bit.

How to represent this?

  • Can't use traditional objects. Objects needs a unique address. Bitfields are to small for this.
  • Types are used. Nested structs will give the intended user syntax.
  • En enum value represent the device (Addr::usart1) giving a usabe type. Address is calculated from this.

What do we want?

  • Simple succinct syntax. Must feel natural to work with.
  • Type safety: Reading/writing bitfield as if they are first class types.
  • Handle volatile and other cermony when working with HW registers.
  • Bit manippulation for registers handled automatically.
  • Use namespaces to allow working with several microcontrollers in the same code base.
  • Be able to operate on cached values to help the optimizer.
  • Bonus: Overriding device base addresses in unit testing.

Example device description

template<>
struct DeviceType<DeviceTypes::usart> {
    // Universal synchronous asynchronous receiver transmitter
    using DevType = DeviceType<DeviceTypes::usart>;
    using devices = Devices::Devices_e;
    using DeviceData = DeviceDataTpl<true>;;
    struct sr_reg {
        // Status register
        enum { addressOffset = 0 };
        using DeviceType = DevType;
        using Register = DevType::sr_reg;
        using RegStorage = uint32_t;
        struct cts_f {
            // CTS flag
            using Field = bitops::BitField<RegStorage, int, 9, 1>;
            using FieldName = cts_f;
            using Register = sr_reg;
        };
        using cts = struct cts_f;
    };
    using sr = struct sr_reg;
};

Typical use

#include "MyFsm.h"

int main()
{
    // The state machine is an ordinary object.
    MyFsm fsm;

    // Perform initial jump to start state. entry events called.
    fsm.setStartState<state1>();

    MyEvent ev1;
    fsm.postEvent(ev1);
    // ...
    fsm.postEvent(ev1);
}
// Upon destruction, relevant exit events are called.

Typical fsm class

#include <StateChart.h>

// Ordinary class, needs to inherit from FsmBase.
// MyFsmDesc contain configuration information about the fsm.
class MyFsm : public FsmBase<MyFsmDesc> {
public:
    // No special requirements on constructors.
    MyFsm() {}

    // Additional user defined data.
    // Can be accessed from state classes.
    int myFsmData = 0;

    // FsmBase contain the 'postEvent' function.
};

Typical state class

// State classes inherits StateBase with needed template arguments.
class MyState1 : public StateBase<MyFsmDesc, States::state1> {
public:
    // Required constructor. Use body for entry event code.
    MyState1(StateArgs& args) : StateBase(args), stateData(0)
    { /* entry event code */ }

    // Optional destructor. default is fine.
    ~MyState1() { /* exit event code */ }

    // Handle all incoming events.
    bool event(const MyEvent& ev) {
        if (ev.foo()) {
            transition<MyState2>();
            return true; // We want to stop processing this event.
        }
        return false; // We want parent state to see the event.
    }
    int stateData;  // Additional data possible as normal.
};
   

Fsm description class

// States represented both with a type and an enum value. 
enum class States { myState1, myState2, /* ... */ };

class MyEvent;
class MyFsm;

class MyFsmDesc {
public:
    
    using Event = MyEvent; // Event class. Can be primitive.
    using Fsm = MyFsm;     // State machine class.

    // Called during setup. Will build up the state hierarchy.
    // Forward declared here.
    static void setupStates(FsmSetup<MyFsmDesc>& sc);
};

State setup function

void MyFsmDesc::setupStates(FsmSetup<UserFsmDesc>& sc)
{
    // Add a root state. (level 0)
    sc.addState<State1>();

    // Another root state.
    sc.addState<State2>();

    // sub state to state 1. (level 1)
    sc.addState<State3, State1>();

    // sub state to state 2. (level 1)
    sc.addState<State4, State2>();
}

State features

// Typical support functions inherited from statebase
template </* ... */ > class StateBase 
{
    // All copy/move/assign deleted.

    // Transition to new state once eventprocessing is done.
    template <typename TargetState>
    void transition();
    void transition(StateId id);

    /// Reference to our state machine object. (MyFsm in our case)
    Fsm& fsm();

    // Return a reference to the parent state. 
    template <class ParentState>
    ParentState& parent();
};

Fsm features

// Typical support functions inherited from FsmBase
template </* ... */ > class FsmBase {

    // Currently active state id.
    StateId currentStateId() const;

    // Get current active state object. nullptr if type mismatch.
    template <class State>
    const State* currentState() const;

    // Get any of the currently active state object.
    template <class State>
    const State* activeState() const;

    // (Planned): Set a callback to be called upon state change.
    using StateCB = std::function<void(Fsm&, StateId)>;
    void setStatechangedCB(StateCB cb);
 };

Mechanisms

  • Uses placement new/delete for state entry/exit.
  • Function setStartState will allocate enough memory at each state level to contain the largest state.
  • State tree is set up using function static objects. Done at first use, then reused.
  • State classes are not polymorphic. Event calls are done using Sean Parents methods for virtual calls at call site.

Current status

  • Code is available at github. Licence TBD, but MIT or BSD are likely.
  • Still prototype quality. I've used it in internal projects. Some unit tests are available. But the interface is still settling in. Use it, but be prepared for changes if you want to upgrade later.
  • I've used an earlier version based on static function pointers in a commercial setting. Had good experience with that. (Inspired by Miro Sameks work)

Thank you for your attention

Code available at github:

  http://www.github.com/rosbacke/MCU-tools

To get in touch:

  email: mikael.rosbacke@gmail.com

  web: http://www.akaza.se

 

Register access from C++

By mikael rosbacke

Register access from C++

Discuss type safe accesses to hardware registers using C++

  • 378