- Characteristics of an embedded environment
- Bare metal interaction with hardware
- No operating system
- No virtual memory
- No C/C++ runtime
- No RTTI
- No exceptions
- Custom start up code
- Peripheral access for I/O
- More important to be on-time than fast
- Memory-mapped I/O
- Physical address space
- Read-only, write-only and read/write registers
- Register fields
- Value written is not value read
- Funny clear behavior
- Bit-banding
- Direct memory access (DMA) transfers
- Interrupt routines
- Tricky race conditions
- Debugging
- Platform emulator
- In-circuit emulator (ICE)
- Debug port (JTAG, etc.)
- Serial port
- Memory Map
- Graphics registers
- Audio registers
- DMA registers
- Timer registers
- Serial communications
- Keypad input
- Interrupts, wait states and power control
- DevKitPro toolchain
- Tonc library and tutorial
- No$gba emulator
- Design Goals
- Scott Meyers: Make interfaces easy to use correctly and hard to use incorrectly.
- Bjarne Stroustrup: Make simple things simple.
- Requirements
- Zero cost
- Intuitive interface
- Static checking for unexpected register behavior
- Atomic actions and thread safe support
- Well packaged meta programming
- C++11 support
- Header only and easily configurable
- Few macros
- Static analysis tool friendly
- No indirect calls
- No recursion
- Template expression library
- Meta programming tools
- Uses compile-time programming to optimize interaction with hardware
- Model of special function register (SFR)
- SFR access types:
- Readable
- Writable
- Clear on read (e.g. interrupt status bits)
- Poppable (e.g. FIFO queues)
- Set to clear
- SFR address characteristics:
- Location in memory
- Write ignored if zero mask
- Write ignored if one mask
- Data type
- Bit locations
- Field locations
- Reading values
- Setting values
- Atomic operations
- SFR access types:
- Advantages of a better interface
- Code readability
- Code reviewability
- Programmer productivity
- Encapsulation of expertise
- Better than hand-coded C?
- Use bit-banding (ARM specific... so far)
- Use blind writes to avoid read-modify-write
- Local temporaries encouraged by public interface
- Reorder access
- Merge access
Kvasir::StartUp
- Inject start up code into the application
- Facilitate merged initialization
- Encourages modularized start up
- Initialization conflicts can be detected
- Hook up ISRs
- Configure the system clock
- Provide hooks for power users to inject other init steps
- Macro architecture
- Generic code calls chip specific code via traits specialization
- Start up code is injected via macro
using Kvasir::Io;
constexpr auto statusLed = makePinLocation(port0, pin13);
apply(makeOpenDrain(statusLed),
makeOutput(statusLed),
set(statusLed));
if (something) {
apply(toggle(statusLed));
}
// main thread
apply(atomic(set(Can::txPacketSent)));
// interrupt service routine
apply(atomic(set(Can::rxPacketReceived)));
What about this?
// main thread
apply(atomic(set(Can::txPacketSend)));
// interrupt service routine
apply(set(Can::rxPacketReceived));
Reading into a temporary and testing pieces of it is more efficient than repeatedly reading the register.
auto res = apply(read(thing1, thing2));
if (get<0>(res)) {
// ...
}
if (get<1>(res)) {
// ...
}
On ARM, LDR
and STR
instructions can have a hard-coded offset.
(LDM
and STM
still have untapped potential.)
LDR rn, [pc, #offset to literal pool]
unsigned volatile ®1 = *reinterpret_cast<unsigned *>(0x40013004);
unsigned volatile ®2 = *reinterpret_cast<unsigned *>(0x40024000);
unsigned volatile ®3 = *reinterpret_cast<unsigned *>(0x40013008);
reg1 += 4;
reg2 += 5;
reg3 += 6;
Reorder the access to reg1
and reg3
so that their adjacency in
memory can be exploited.
When modifying multiple bit fields in the same register, merge their modifications into a single modification.
auto i = reg;
i &= ~0x10;
i |= 0x100;
reg = i;
i = reg;
i &= ~0x03;
i |= 0x08;
reg = i;
becomes
auto i = reg;
i &= ~0x13;
i |= 0x108;
reg = i;
This is a work in progress. The idea is to revisit the tonc library and layer on top of Kvasir.