Building a Library using Static Inheritance (CRTP)

Go To Last Post
10 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Has anyone attempted/written a General Purpose Microcontroller library using Static Inheritance (i.e. The Curiously Recurring Template Pattern?

If so, can you link the me post so i don't have to reinvent the wheel/ find out why its a bad idea?!

The next post shows an example of what I am trying todo.

This thread may belong in the GCC forum, because it deals mainly with code constructs, but I placed it here because of its practical application with AVRs.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here is an example using my library to flash an led:

In the main.cpp file:

#include 

#include "avr/IPort.hpp"
#include "led.hpp"

using namespace StaticDrivers; 

void Init();
// Create an LED driver that uses PortB pin 0 as the output
static LED > ledNative;

int main(void)
{
    Init();
	
    for(;;) 
    {
        ledNative.On();
        ledNative.Off();
    }
}

void Init()
{
    cli();
	
    ledNative.Init();
		
}

This compiles down to ( Atmel Studio 6, ATmega32, using -Os):

00000066 <_Z4Initv>:
  66:	f8 94       	cli
  68:	b8 9a       	sbi	0x17, 0	; 23
  6a:	08 95       	ret

0000006c 
: 6c: fc df rcall .-8 ; 0x66 <_Z4Initv> 6e: c0 9a sbi 0x18, 0 ; 24 70: c0 98 cbi 0x18, 0 ; 24 72: fd cf rjmp .-6 ; 0x6e

Which is nice and mimimal!!

The driver magic looks like this:

File: led.hpp <-- .hpp because it makes atmel studio highlight things correctly..

/*
 * led.hpp
 *
 * Created: 2012/04/21 10:54:42 a.m.
 * Author: Josh Bradfield
 */ 


#ifndef LED_H_
#define LED_H_

#include 

template  // If true, the LED is ON when the driver is Set
                                 // If false, the LED is OFF when the driver is Set
class LED
{
    static StaticDrivers::IOutput driver;
    
    public:
    
    /*! @brief Switches the LED "On" **/
    static inline void On()     __attribute__ ((always_inline))    
    { 
        if(OnWhenSet)
            driver.Set();
        else
            driver.Clear();
    }
    
    /*! @brief Switches the LED "Off" **/
    static inline void Off() __attribute__ ((always_inline))    
    {
        if(!OnWhenSet)
            driver.Set();
        else
            driver.Clear();
    }            
    
    /*! @brief Setup the LED driver
     *  @attention This function must be run before the LED driver can be used.
     **/
    static inline void Init() __attribute__ ((always_inline))     
    {
        driver.Init(); // Initialize the IOuput driver the LED is attached to
    }
  
};

IOutput.hpp

/*
 * IOutput.hpp
 *
 * Created: 2012/04/20 02:59:19 PM
 *  Author: Josh
 */ 


#ifndef IOUTPUT_H_
#define IOUTPUT_H_

namespace StaticDrivers
{

	template 
	class IOutput 
	{
	
		public:
		
		friend class IOutput;
	
		static inline void Init()	__attribute__ ((always_inline))	
									{ Derived::IOutput_Init(); }
		static inline void Set()	__attribute__ ((always_inline))	
									{ Derived::IOutput_Set(); }
		static inline void Clear()  __attribute__ ((always_inline))	
									{ Derived::IOutput_Clear(); }
		static inline void Toggle() __attribute__ ((always_inline))	
									{ Derived::IOutput_Toggle(); }
		
		#ifdef StaticDrivers_UseObjects	
		static IOutput_* toObject() __attribute__ ((always_inline))	
									{ return Derived::IOutput_toObject(); }
	    #endif
	
		protected:
		static inline void IOutput_Init()	__attribute__ ((always_inline))	
									{ Init(); }
		static inline void IOutput_Set()	__attribute__ ((always_inline))	
									{ Set(); }
		static inline void IOutput_Clear()	__attribute__ ((always_inline))	
									{ Clear(); }
		static inline void IOutput_Toggle() __attribute__ ((always_inline))	
									{ Toggle(); }
			
		#ifdef StaticDrivers_UseObjects
		static inline IOutput_* IOutput_toObject() __attribute__ ((always_inline))	
									{ return Derived::IOutput_toObject(); }
		#endif	
	};

}



#endif /* IOUTPUT_H_ */

I will post the rest of the code later, if people are interested!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The really exciting part happens when you want to place that led onto a shift register, or even a nested shift register!!

In the following file i demonstrate flashing an LED on:
1) A Native Port Pin
2) A Shift Register Pin
3) A Nested Shift Register Pin

#include 

#include "avr/IPort.hpp"
#include "io_shiftRegister.hpp"
#include "led.hpp"

using namespace StaticDrivers; 

//I use typedefs here to reduce typing later on

// Define a shift register driven from native pins
typedef IOutputBus, // Data
							PortB::Bit<2>, // Clock
							PortB::Bit<3>, // Latch
							uint8_t>, 
				   uint8_t>
		ShiftRegister;


// Define a shift register driven from the shiftregister above!
typedef IOutputBus , // Data
							ShiftRegister::Bit<2> , // Clock
							ShiftRegister::Bit<3> , // Latch
							uint8_t>, 
				   uint8_t>
		ShiftRegisterNested;


void Init();

static LED > ledNative;
static LED >ledShift;
static LED >ledNestedShift;


int main(void)
{
	Init();
	
    forever 
	{
		ledNative.On();
		ledNative.Off();
		
		ledShift.On();
		ledShift.Off();
		
		ledNestedShift.On();
		ledNestedShift.Off();
	}
}


void Init()
{
	cli();
	
	ledNative.Init();
	ledShift.Init();
	ledNestedShift.Init();
		
}

The above code compiles to:
AVR Memory Usage
----------------
Device: atmega32
Program: 282 bytes (0.9% Full)
(.text + .data + .bootloader)
Data: 2 bytes (0.1% Full)
(.data + .bss + .noinit)

I think that's pretty simple!

The 2 bytes of data are caused by the shift register drivers having to remember the content of their outputs.

The code above appears to occupy 174 Bytes above the normal needed for c.

I will post a timing diagram later, as I have a dinner to get to!

I think that that code looks pretty simple!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So why?

A) Code readability
B) Code Portability
C) Minimal memory and speed overhead for having the above two things.

D) Auto complete and IntelliSense in Atmel Studio. (This helps when coding something quickly, and when your memory is failing you)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For anyone who is interested here is some timing information to detail the effectiveness of the library
(Running on the ATmega8 at 16Mhz, compiled with -Os)

#include 
#include "io_shiftRegister.hpp"
#include "avr/IPort.hpp"
#include "led.hpp"

using namespace StaticDrivers; 



typedef IOutputBus, // Data
                            PortB::Bit<2>, // Clock
                            PortB::Bit<3>, // Latch
                            uint8_t>, 
                   uint8_t>
        ShiftRegister;
		
typedef IOutputBus , // Data
                            ShiftRegister::Bit<2> , // Clock
                            ShiftRegister::Bit<3> , // Latch
                            uint8_t>, 
                   uint8_t>
        ShiftRegisterNested;


void Init();

static LED > ledNative;
static LED >ledShift;
static LED >ledNestedShift;


int main(void)
{

    Init();
	
    for(;;)
    {
        ledNative.On();
        ledNative.Off();
        ledNative.On();
        ledNative.Off();
		
        ledShift.On();
        ledShift.Off();
        ledShift.On();
        ledShift.Off();
        
        ledNestedShift.On();
        ledNestedShift.Off();
        ledNestedShift.On();
        ledNestedShift.Off();
    }
}


void Init()
{
    cli();
	
    ledNative.Init();

    ShiftRegister::Init();
    ledShift.Init();

    ShiftRegisterNested::Init();
    ledNestedShift.Init();
		
}

The toggle frequency for each led as follows:
Native LED: 2MHz (0.25 micro seconds to effect pin change)
Shifted LED: 28.4698 KHz (17.5417 microseconds to effect pin change)
Nested Shifted LED: 1.0833 KHz (461.75 microseconds to effect pin change)

I think we can see that my Shift Register is under performing a little, though this is due to my implementation(which has a few extra cycles than it needs) not the design paradigm.

I have attached a Saleae Logic File which contains the above analysis. The software to view the file is free.

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have no idea about what's going on here but you seem to be having fun :-)

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I have no idea about what's going on here but you seem to be having fun

I'm attempting to create a design pattern and library, which will allow you to create a driver for a device (e.g. an led or perhaps an LCD display) without knowing what type of inputs or outputs it will be using at build time.

This means, that my driver for my LED or LCD display is unaware of my decision later on to use native pins (e.g. PortB) or some sort of mux (e.g. a Shift register, or even something like I2C)

This simplifies driver libraries, and also means if i write an LCD driver, and it works on the native PortB, it will also work on a Shift Register, and if i want todo this, i don't even have to change the driver code!!! Which means it works straight away.

The other advantage is the Autocomplete/IntelliSense works when writing the device drivers, and when using the drivers in your code, this makes it much easier to use a library for the first time/ after a long time.

I am also trying to have little/no memory and speed overhead for using the design paradigm.

Does that make a little more sense?

Thanks for reading btw, I'm trying to get some opinion on what i am doing, and maybe if people like it, some help with some issues I am having.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

One word of caution when it comes to numeric template arguments - let's say you have declared 10 "bits" using the values from 0 through 9. Will you then be able to iterate through the "bits" or otherwise access them by ordinal ?

I didn't study your code, but I have come across this sort of thing in other contexts, where I actually had to use the same command up to 100 times in a row where a simple loop should have had the same effect. Needless to say, the source code ended up looking like bloated crap.

(100 almost identical declarations where an array would have worked, 100 almost identical definitions, 100 almost identical function calls whenever you want to do something to all of them.)

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

One word of caution when it comes to numeric template arguments - let's say you have declared 10 "bits" using the values from 0 through 9. Will you then be able to iterate through the "bits" or otherwise access them by ordinal ?

This is one of this issues I have, the answer is yes and no!

For any set of inputs/outputs/drivers in general, I define them as a "bus" or a "server". This means you can write to multiple bits with a command like setBit(0). I could easily write in a [] operator as well.

IOutput> singleOutput;
IOutputBus manyOutputs;

singleOutput.Set();
manyOutputs.Set(0);

However, you cannot do

singleOutput = manyOutputs[0];
singleOutput.Set();

So you are right, if you wanted to loop that, it would become messy. The whole point of this library however, is to try and remain as close as possible to "native speed", so the bits are not the same object!

But sometimes, you may want loops like you say. I'm experimenting with ways to create "objects" of my static drivers, with as small an overhead as possible.

so you could go:

manyOutputs.toObject()->[0]->set();

But the overhead due to virtual inheritance is really annoying.

Quote:
I didn't study your code, but I have come across this sort of thing in other contexts, where I actually had to use the same command up to 100 times in a row where a simple loop should have had the same effect. Needless to say, the source code ended up looking like bloated crap.

*shudders* thats scary! I'll keep that in mind more as I write! In your oppinion, would the "Bus" approach i just described solve that issue?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm not sure - like I said I didn't study your code.

The point would be to avoid using the bit index as a template parameter and rather have it as a member variable and attempt to get the compiler to optimize it away by inlining as many methods as possible. That may be doable with simple classes like the ones you're talking about, otherwise I would just accept the "overhead" of keeping the indices.

I like the simple declarations your solution allows for, but without the flexibility of addressing by ordinal it just isn't good enough for anything but the simplest of systems.

I am not opposed to using templates, I am just suggesting caution when it comes to "typifying" scalar values.

Sid

Life... is a state of mind