Skip to content
Odin Holmes edited this page Feb 2, 2015 · 13 revisions

Welcome to the Kvasir wiki! We hope to provide a growing amount of documentation of the Kvasir library for developers and users alike.

Design goals and justification:

Sometimes its easiest to show a code example, take the following excerpt from LPCOpen:

void Chip_IOCON_PinMux(LPC_IOCON_T *pIOCON, uint8_t port, uint8_t pin, uint32_t mode, uint8_t func)
{
	uint8_t reg, bitPos;
	uint32_t temp;
	bitPos =  IOCON_BIT_INDEX(pin);
	reg = IOCON_REG_INDEX(port,pin);
	temp = pIOCON->PINSEL[reg] & ~(0x03UL << bitPos);
	pIOCON->PINSEL[reg] = temp | (func << bitPos);
	temp = pIOCON->PINMODE[reg] & ~(0x03UL << bitPos);
	pIOCON->PINMODE[reg] = temp | (mode << bitPos);
}
//... used somewhere
Chip_IOCON_PinMux(LPC_IOCON, 0, 7, IOCON_MODE_INACT, IOCON_FUNC2);
Chip_IOCON_PinMux(LPC_IOCON, 0, 6, IOCON_MODE_INACT, IOCON_FUNC2);
Chip_IOCON_PinMux(LPC_IOCON, 0, 8, IOCON_MODE_INACT, IOCON_FUNC2);
Chip_IOCON_PinMux(LPC_IOCON, 0, 9, IOCON_MODE_INACT, IOCON_FUNC2);

Does this code meet modern quality standards? I would argue no, first of all IOCON_MODE_INACT and IOCON_FUNC2 are macros which are evil right? Actually the main problem I see, besides having to look up what IOCON_FUNC2 is in the LPC17xx documentation, this code violates the Scott Meyers "most important guideline" Make interfaces easy to use correctly and hard to use incorrectly found here. All of the parameters except the first are ints, I could easily switch them around or use garbage data like port number 142 and not get a compiler error. I don't want to pick on the LPCOpen guys specifically here, this is actually pretty common practice in the embedded world. So why do we do things this way? First of all compatibility, which is a fair point, if you are not using a C++11 conforming compiler then I am sorry to say the Kvasir library is not for you. The second reason is speed and code size, we are in the embedded world here, we need to make sacrifices right? I say wrong and hope to prove that claim with this library.

Potential for a more efficent alternative:

So far I have explained why I find the status quo unsafe due to lack of static checking. It is actually much less efficient than it needs to be as well. We are asking a lot of the optimizer here, it needs to figure out that all parameters passed to this function are literals and it can therefore in-line everything and reduce it down to two read-and-or-write routines. The Amazing thing is that, in most cases, it can do that! However there is still some wasted potential. Notice that all four function calls actually manipulate bits in the same register and in this particular case we do not care about the order of these operations. However since we usually do care about the order of reads and writes to registers LPCOpen has correctly marked all register representations as volatile. This forbids the optimizer from consolidating everything down to one single read modify write for PINSEL and PINMODE rather than four (one for each function call).

The Kvasir alternative:

In order to make the interface better we need to have a better way to encapsulate information about bit combinations in registers. Kvasir solves this with an Option template (in the Register namepace) which can be used to associate specific bit combinations and register address information with the name which they represent. Using the variadic function apply (in the Register namepace) you can apply any number of register Options to their respective registers. Here is the equivalent code to the above snippet:

namespace Sspcfg = Hardware::SSP1::PinCfg;
namespace Pmode = Hardware::Port0::Mode;
Register::apply(Sspcfg::sselIsP0_6, Sspcfg::sckIsP0_7, Sspcfg::misoIsP0_8, Sspcfg::mosiIsP0_9,
	Pmode::P6::normal, SPmode::P7::normal, Pmode::P8::normal, Pmode::P9::normal );

No more accidentally switching parameters, the order does not matter any way. No more using unsupported parameters either, if they are in the namespace they are supported. We don't need to look up what IOCON_FUNC2 is either because its right in the name. If you are wondering what Hardware::SSP1::PinCfg::SselIsP0_6 actually is its a constexpr variable who's type in a specialization of Register::Option, an empty struct, which contains the necessary address and bitmask information we need. The apply function sorts its parameters, merges any bit manipulations on the same address and applies the bit manipulations to the respective registers. With optimizer turned on this will result in two read-and-or-write's, one for each register which is 4 times more efficient and smaller than the LPCOpen equivalent above. If you are wondering how this meta programming magic stuff works, well its complex, here is a tutorial for the highly motivated. Otherwise just use it, most programmers don't understand whats going on under the hood of the C++ Standard Library either. If you understand meta programming and work in the small processor embedded domain please contact us, we would love to hear your experience and input.