cancel
Showing results for 
Search instead for 
Did you mean: 

Request For Comment on this way of combining C++ and interrupts on STM32 microcontrollers

WilkoL
Senior

I'm trying to teach myself C++ on STM32. Unfortunately the books on C++ I have and most websites I found do not mention interrupt handling. The websites where it was explained (embedded.com, stackexchange.com, etc) did it in a way I simply do not (yet?) understand.

So I tried to do it in my own way with a ringbuffer and usart, and I was hoping to get some critisism on it, here goes:

File stm32f0xx_it.cpp contains

void USART1_IRQHandler(void)
{
	USART_Callback_Handler();
}

File main.cpp contains (only the relevant parts)

RINGBUFFER ring_buff;
 
 
int main(void)
{
	char buffer[32];
	uint8_t teller = 0;
 
	while (1)
	{
		sprintf(buffer, "%d\t", teller++);
		ring_buff.put_str(buffer);
 
		LL_mDelay(499);
	}
}
 
void USART_Callback_Handler()
{
	ring_buff.USART_IRQ_Callback_Handler();
}

File ringbuffer.hpp contains (also only the parts I think are relevant)

class RINGBUFFER
{
private:
	volatile uint8_t tx_head;
	volatile uint8_t tx_tail;
	volatile uint8_t rx_head;
	volatile uint8_t rx_tail;
 
	volatile char tx_buff[TXBUFF_SIZE];
	volatile char rx_buff[RXBUFF_SIZE];
public:
	void put_ch(char data);
	void put_str(char *s);
	void USART_IRQ_Callback_Handler(void);
 
	RINGBUFFER()
	{
		uint16_t i;
 
		tx_head = 0;
		tx_tail = 0;
		rx_head = 0;
		rx_tail = 0;
 
		for (i = 0; i < TXBUFF_SIZE; i++) tx_buff[i] = '\0';
		for (i = 0; i < RXBUFF_SIZE; i++) rx_buff[i] = '\0';
	}
};

and the file ringbuffer.cpp contains (relevant parts only)

void RINGBUFFER::put_ch(char data)
{
	uint8_t tx_tmp;
 
	tx_tmp = (tx_head + 1) & TXBUFF_MASK;
	while(tx_tmp == tx_tail);				//blocking wait for free space in tx buffer
 
	tx_buff[tx_tmp] = data;
	tx_head = tx_tmp;
	LL_USART_EnableIT_TXE(USART1);             //enable TXE interrupt
}
 
 
void RINGBUFFER::put_str(char * data)
{
	while (*data)
	{
		put_ch(*(data++));
	}
}
 
 
void RINGBUFFER::USART_IRQ_Callback_Handler(void)
{
	uint8_t tx_tmp;
 
	if (LL_USART_IsActiveFlag_TXE(USART1)) 
	{
		if (tx_head == tx_tail)
		{
			while (LL_USART_IsActiveFlag_TC(USART1) != 1);		//wait until tx complete
			LL_USART_DisableIT_TXE(USART1);  				//disable TXE interrupt
			LL_USART_ClearFlag_TC(USART1);
		}
		else
		{
			tx_tmp = (tx_tail + 1) & TXBUFF_MASK;
			tx_tail = tx_tmp;
			LL_USART_TransmitData8(USART1, tx_buff[tx_tmp]);	//tx data
		}
	}
}

And it does work, The "real" USART interrupt routine in stm32f0xx_it.cpp_calls the USART callback function in main.c.

In main.cpp the instance ring_buff exists so it can call the actual code in the CLASS (RINGBUFFER::USART_IRQ_Callback_Handler(void)) to handle the transfer of data from the ringbuffer to the USART.

But it feels a bit clunky to have this "inbetween" function in main.c Any comments? Positive and negative, both welcome.

Wilko

5 REPLIES 5
Pavel A.
Evangelist III

Haven't your C++ or CS course teach about dependency injection?

Just think of this as of dependency injection.

-- pa

TDK
Guru

> But it feels a bit clunky to have this "inbetween" function in main.c Any comments?

So you want the interrupt to call ring_buff.USART_IRQ_Callback_Handler directly? Given that you can have multiple RINGBUFFER objects, how would that work exactly?

There is likely a way to hotwire it but it's probably going to hurt readability more than help it. Interrupts are single-point entry and I don't see a way around that.

If you feel a post has answered your question, please click "Accept as Solution".

So you are saying that this way isn't so bad after all... I still think it is odd that one has to jump through hoops to do something simple as handling an interrupt in C++, while it comes so naturally in C.

Well, no. As I said, I'm teaching myself C++ and I haven't seen anything about "dependency injection". But I found some things about it on the web that I'm going to study today. Thanks.

There's more going on under the hood here. Calling a member function means the object (the "this" pointer) needs to be placed on the stack before that call. Interrupt handlers don't take any arguments, so there's no mechanism for this to happen.
Also, since you can have multiple (or zero) objects of that type, how is it supposed to know which one, if any, to call?
So yes, the way you're doing it seems fine to me. You could have a more elaborate interrupt registration mechanism at the cost of code complexity, but I don't think that is a good idea.
If you feel a post has answered your question, please click "Accept as Solution".