c++ and C for AVR's

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

Hi guys,
So my C++ library is going well, but I am now thinking about what to do about interrupts and timers.

Firstly due to the many different setups of timers, I am thinking of just keeping a .c file for each timer and then going there to setup the registers. Maybe a couple of functions timerX-init() and timerX_start() and timerX_stop then the header file with the prototypes wrapped in
extern "C" {...}
and then calling the functions from my C++ code when I need it, rather than trying to make a C++ class for each timer, and getting myself in a right mess.

For the external interrupts though and hardware timers CTC etc), as they also have the ISR's these aren't really "C" functions and dont have prototypes as such Im a little unsure as to how to keep them safe when being used with C++. They might not have a header file, - or atleast anything in it.
I only want the classes for code that isnt directly related to controlling the functionality of the microcontroller (and to learn).

Can people with a bit more experience give me some ideas please.
THanks

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

Have you looked at how Arduino handles this? Look at the Serial class for example. They instantiate the class as this for up to 4 UARTs:

// Preinstantiate Objects //////////////////////////////////////////////////////

#if defined(UBRRH) && defined(UBRRL)
  HardwareSerial Serial(&rx_buffer, &tx_buffer, &UBRRH, &UBRRL, &UCSRA, &UCSRB, &UDR, RXEN, TXEN, RXCIE, UDRIE, U2X);
#elif defined(UBRR0H) && defined(UBRR0L)
  HardwareSerial Serial(&rx_buffer, &tx_buffer, &UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UDR0, RXEN0, TXEN0, RXCIE0, UDRIE0, U2X0);
#elif defined(USBCON)
  // do nothing - Serial object and buffers are initialized in CDC code
#else
  #error no serial port defined  (port 0)
#endif

#if defined(UBRR1H)
  HardwareSerial Serial1(&rx_buffer1, &tx_buffer1, &UBRR1H, &UBRR1L, &UCSR1A, &UCSR1B, &UDR1, RXEN1, TXEN1, RXCIE1, UDRIE1, U2X1);
#endif
#if defined(UBRR2H)
  HardwareSerial Serial2(&rx_buffer2, &tx_buffer2, &UBRR2H, &UBRR2L, &UCSR2A, &UCSR2B, &UDR2, RXEN2, TXEN2, RXCIE2, UDRIE2, U2X2);
#endif
#if defined(UBRR3H)
  HardwareSerial Serial3(&rx_buffer3, &tx_buffer3, &UBRR3H, &UBRR3L, &UCSR3A, &UCSR3B, &UDR3, RXEN3, TXEN3, RXCIE3, UDRIE3, U2X3);
#endif

And the ISRs are done as follows:

#ifdef USART1_UDRE_vect
ISR(USART1_UDRE_vect)
{
  if (tx_buffer1.head == tx_buffer1.tail) {
	// Buffer empty, so disable interrupts
    cbi(UCSR1B, UDRIE1);
  }
  else {
    // There is more data in the output buffer. Send the next byte
    unsigned char c = tx_buffer1.buffer[tx_buffer1.tail];
    tx_buffer1.tail = (tx_buffer1.tail + 1) % SERIAL_BUFFER_SIZE;
	
    UDR1 = c;
  }
}
#endif

#ifdef USART2_UDRE_vect
ISR(USART2_UDRE_vect)
{
  if (tx_buffer2.head == tx_buffer2.tail) {
	// Buffer empty, so disable interrupts
    cbi(UCSR2B, UDRIE2);
  }
  else {
    // There is more data in the output buffer. Send the next byte
    unsigned char c = tx_buffer2.buffer[tx_buffer2.tail];
    tx_buffer2.tail = (tx_buffer2.tail + 1) % SERIAL_BUFFER_SIZE;
	
    UDR2 = c;
  }
}
#endif

All of this is in:

arduino-1.0.1\hardware\arduino\cores\arduino\HardWareSerial.[h|cpp]

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

You can't abstract yourself away from everything, and even less so when dealing with limited hardware such as AVRs.

Post source code for something you think you should be able to abstract, but have trouble with. I'll help if I can.

Sid

Life... is a state of mind

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

I don't do macros, though.

Sid

Life... is a state of mind

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

Hi Guys,
Thanks for replies,
Yeah I get how Arduino are doing it, and I have done something similar for my serial class, however it doesnt really show me where i would put the ISR in relation to the rest of the class - and do I need to wrap it in extern "C" {}, or is it not C?
Below is the beginnings of a class to setup interrupts, can I use the variables attached to that to decide on which type of ISR to use? because I cant use preprocessing because that would happen before the developer has written the code no?

#include 
#include "../bin/ExtInterrupt.hpp"
ExtInterrupt::ExtInterrupt(char interrupt, char pin, char status)
{
	//setup interrupt as rising/falling/any/bottom
	switch (status)
	{
	case 0: //falling
		MCUCR &= ~(1<<ISC00);
		MCUCR = (1<<ISC01);
		break;
	case 1: //rising
		MCUCR |= (1<<ISC00) | (1<<ISC01);
		break;
	case 2: //low level
		MCUCR &= ~(1<<ISC00);
		MCUCR &= ~(1<<ISC01);
		break;
	case 3: //any level
		MCUCR |=(1<<ISC00);
		MCUCR &=(1<<ISC01);
		break;
	default:
		break;
	}
	//interrupt 0 or 1
	switch (interrupt)
		{
		case 0:

			DDRD &= ~(1<<PD2);
			PORTD |= (1<<PD2);
			//Turn on Interrupt
				EIMSK  |= (1<<INT0);
			break;
		case 1:

			DDRD &= ~(1<<PD3);
			PORTD |= (1<<PD3);
			//Turn on Interrupt
				EIMSK  |= (1<<INT1);
			break;
		default:
			break;
		}
	}

Currently written for mega8, but will adjust for other chips using defines.
Also, lets say the ISR sets a flag, what is the best way to allow some other method to be able to see the state of the (volatile) flag. Make it totally public, or a public method as part of this class, and then pass the instantiation of this class around?
Lastly, I dont want more than one instantiation of each external interrupt existing, so should I have a flag that is checked to see if it has already been made inside the switch/case that checks the value of the variable interrupt?

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

The ISR() are not part of the class in that Arduino serial example. The only shared links are the rx_buffer1, tx_buffer1, rx_buffer3 and so on. The class enables the interrupting mechanism when SerialN.begin() is called and when a write methods are used it puts data into the tx_bufferN and when the read methods are used it extracts from rx_bufferN. But it's like the ISR() is outside the class acting as a "data" pump and he class is producing/consuming the data it uses with the link being the shared buffer. They are not member variables, not even static because the ISR() needs to be able to "see" them so they are global. This does of course mean that you could not instantiate the class more than once for a given serial port though I cannot think of a reason why you'd want to.

I try to avoid C++ as much as possible as my little brain was brought up on C which is much simpler but I'm guessing that if there were a singleton instance of the class for each physical serial port the ISR could call a getInstance() style function to access the class which could then contain the buffers as member but I'm not sure if this is really better and one would need to look at the generated code. I guess it does raise the opportunity of keeping the Tx/Rx buffers "private" rather than making them global. I'll leave someone who actually understands C++ to contemplate that one.

Anyway the bottom line is that ISR aren't (in fact can't be) within the class so you have the question of what "data" needs to get into/out of the ISR() and how it is passed.

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

Hi Cliff,
Yeah I get the issues here, I suppose I need a file that has the ISR's in (and possibly extern "C" scopes) that shares some (volatile) variables with the class. Yeah I'll make the class a singleton because I dont want two or more versions of the same interrupt. Im just not sure how "the gurus" would do this.
Once I crack the ISR issue, i can deal with the timers and interrupt driven serial the same way...

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

Quote:

I suppose I need a file that has the ISR's in (and possibly extern "C" scopes) that shares some (volatile) variables with the class.

I pointed out Arduino's HardwareSerial.cpp to show that this is not the case. Perhaps you missed that point? ISR() will happily live in a .cpp along with the supporting C++ code. defines ISR() as follows:

#ifdef __cplusplus
#  define ISR(vector, ...)            \
    extern "C" void vector (void) __attribute__ ((signal,__INTR_ATTRS)) __VA_ARGS__; \
    void vector (void)
#else
#  define ISR(vector, ...)            \
    void vector (void) __attribute__ ((signal,__INTR_ATTRS)) __VA_ARGS__; \
    void vector (void)
#endif

Note this already recognises C++ and introduces the extern "C" to prevent the __vectorN() name getting mangled.

Quote:

Im just not sure how "the gurus" would do this.

Me neither. We see an increasing number of threads here from people using C++ for micros (no doubt as a result of current educational practices that concentrate on OO) but it is very very rare that actual C++ code is ever shown. So I'm as intrigued as you to know what the "standard" solution for data sharing with ISR()s is.

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

OK yeah, so as long as interrupts is iimportanted, I can stick ISR's anywehre. good. I might be able to work the "global" variables out...
I feel like I must have improved since starting all this if I have finally asked a question you are waiting to hear the answer to - not the case with fatfs hey... ;)

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

Quote:

I don't do macros, though.

LOL!

No, one might of course be generally detestable, but will not sink to those depths.. :D

Thank you, Sid! While I relaize you where probaly dead-serious, you still gave me the laugh of the day. A "tufer" as good as any!

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

For small micros, I think something like a multi-UART "driver" is better done in C than C++. Just code it so the which-UART code gets values at run-time, not compile time. Not hard. No C++ struggles, esp. with pointers and flags and so on that a class instance needs to share with an ISR - and the ISR also has to be one-code, multiple UARTs.

MACROs don't help.. that leaves it as a compile-time thing rather than run-time.

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

I have done something similar to the following several times and it works well,

class Bar;

static inline foo( Bar& _bar, uint8_t _x ) {
	_bar.x_ = _x;
}
	
class Bar
{
	private:
		friend foo( Bar&, uint8_t );
		uint8_t x_;		
}

static Bar bar;

ISR(...){
	uint8_t x = PINB;
	foo( bar, x );
}

The class, Bar, is the functional class. It contains whatever data might be important to the class. This may include things like data, or it may include pointers to uC registers. Either way, the data is ( unless there is a good reason to do otherwise ) private. Then, there is a function that implements any ISR functionality that might be needed. In this case foo. The function is declared as static inline, takes a reference to the class, and has parameters for any data generated by the ISR. This processing function is declared a 'friend' of the main class ( Bar ), so that it has access to all of the private data. This structure allows for the ISR functionality to be nicely encapsulated and, because the operational function is static inline, it gets rid of the function call overhead and ( typically ) any data accesses through the 'this' pointer as well. I've found this to be as well optimized as separate global variables and it is much easier to expand upon.

This also plays nicely with templates, though things can, doubtless, get more complicated at that point ( I've never liked the way friend functions and templates work together ). It has not real overhead performance or memory wise and it reduces code duplication. The ISR is not, however, associated with the class in any particular way so there is only very loose coupling there ( which may not be a bad thing ).

Martin Jay McKee

As with most things in engineering, the answer is an unabashed, "It depends."

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

mckeemj wrote:
I have done something similar to the following several times and it works well,

class Bar;

static inline foo( Bar& _bar, uint8_t _x ) {
	_bar.x_ = _x;
}
	
class Bar
{
	private:
		friend foo( Bar&, uint8_t );
		uint8_t x_;		
}

static Bar bar;

ISR(...){
	uint8_t x = PINB;
	foo( bar, x );
}


It is possible to modify that so that it compiles, but I don't see the point of making such a construct. x_ is private, but foo() is public - and just like a public member can be changed anywhere foo() can be called anywhere.

You could try something like this:

extern "C" void TIMER1_OVF_vect();

class Bar
{
    private:
        friend void TIMER1_OVF_vect();

        volatile uint8_t x;
};

static Bar bar;

ISR(TIMER1_OVF_vect)
{
    ++bar.x;
}

I just "invented" that, but it seems to work in the simulator.

Sid

Life... is a state of mind

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

someone have mercy and explain in simple terms what C++ "friend" does and if it's portable.

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

In C++ your classes can protect member variables (and member functions) by declaring them protected or private.

A friend declaration circumvents that, and allows one specific function (or class) full access to all the members of the class that contains the friend declaration.

Specifically, in the code I posted the ISR is allowed to access any member of the Bar class, while other parts of the program are only allowed to access public members. (In the code posted, there are no useful public members).

friend is a standard C++ thing so it is portable.

I generally don't use friend declarations, as I don't like the implications.

But in this particular case, it may actually be a good idea - what makes this different is that there doesn't seem to be any straightforward way to make the ISR a member of the class.

Sid

Life... is a state of mind

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

It may be a little bit easier to make sense out of it if you consider this slightly modified version:

extern "C" void TIMER1_OVF_vect();

class Bar
{
    friend void TIMER1_OVF_vect();

    public:
        uint8_t getX() { return x; }

    private:
        volatile uint8_t x;
};

static Bar bar;

ISR(TIMER1_OVF_vect)
{
    ++bar.x;
}

int main()
{
    ...
    uint8_t x = bar.getX();
    ...
}

Here, the only code that can change bar.x is the ISR, while the only thing main() is allowed to do is read it by calling the getter function.

The point would be that you are protected from doing some mistakes - you're not allowed to assign something to bar.x in main.

Most likely, you get this added protection at no performance cost, too. The generated code will be just as efficient as C code.

Sid

Life... is a state of mind

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

ChaunceyGardiner wrote:
I generally don't use friend declarations, as I don't like the implications.

But in this particular case, it may actually be a good idea - what makes this different is that there doesn't seem to be any straightforward way to make the ISR a member of the class.

In this case, the friend declaration is probably tidier,
but one can readliy make the ISR a member of the class.
Make it a static member and use asm("the right name") to give it the right name.
You would need another set of macros.

Iluvatar is the better part of Valar.

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

I apologize for the errors I introduced in the quick example I typed up -- thinking and rushing don't typically work well together.

I also tend to avoid friend ( for the implications as well ). I came to the structure in the search for a more efficient way to process class data in an ISR. In many cases, accessing member data ( even when it is constant ) leads the compiler ( at least when I last did this ) to accesses through the this pointer -- sub-optimal code. Doing the work through a friend function in this manner allowed the optimizer to remove pointer access.

I do agree it is a bit ugly. However, in some cases, I prefer it to directly defining the interrupt handler as a friend, because it allows the "optimized" operations to be defined completely separately from any ISR. The fact that the free functions are public scope is, for me, a non-issue. They are used to control access to data, not prevent it. For instance, the class may implement a buffer. In that case, I do not care ( in many cases ) if it is possible to buffer data from multiple places, but the way in which the buffering is done, is important -- a ring buffer may need to keep track of head and tail, full status, etc. The function protects against "stupid" access to the data. These functions are then usable to produce a final ISR that contains no class specific code, only calls to related functions. Of course, in other cases the ISR functionality may be much more closely related to the class in which case making it a friend could certainly be preferable.

Martin Jay McKee

As with most things in engineering, the answer is an unabashed, "It depends."