cancel
Showing results for 
Search instead for 
Did you mean: 

Unclear how to build a C++ class for a UART GPS receiver that uses HAL DMA library calls

magene
Senior II

I'm in a fair ways over my head here. I figured out how to build a STM32F413 program that interfaces with a uBlox GPS receiver over a UART serial port. Now I'm trying to build that code into a C++ class that gets instantiated in my main application. My main app is a fairly standard "Gang of Four" state machine and I'm trying to make all my methods non-blocking to the greatest extent reasonable. So, commands are sent to the GPS receiver using DMA and messages from the GPS receiver are also handled with DMA and the idle line detect IRQ since I don't always know how many bytes are in the message. Eventually there will be 7 or 8 different UART devices but I'm hoping to make them all look like this GPS device.

In order to make the HAL_UART_RXIdleCallback work I had to declare it static in the .hpp file. It all works until I try to call a method from another object (like my msgQ object) in the HAL_UART_RXIdleCallback method. Then I get the error "Invalid use of member msgQ in static member function".

I've tried a few work arounds but I can't get past the issue that HAL_UART_RXIdleCallback needs to be static as far as I can tell. Another issue is I'm just not knowledgeable enough yet to dump the HAL library and write my own low level drivers. I understand this is a pretty complicated question but if anyone has any suggestions, I'd be happy to hear them.

Here's the GPS class header file. The msgQ is instantiated at line 35 below.

#pragma once
 
#include <stm32f4xx_hal.h>
#include <string>
using namespace std;
 
#include "FIFOQueue.hpp"
 
static UART_HandleTypeDef hUART7 = UART_HandleTypeDef();
static DMA_HandleTypeDef hDMA_RX = DMA_HandleTypeDef();
static DMA_HandleTypeDef hDMA_TX = DMA_HandleTypeDef();
 
const uint32_t RXBufferLength = 512;
static uint8_t RXBuffer[RXBufferLength];
 
//TODO probably should make this inheritable by future uartUBloxGPS and i2cUBloxGPS
class ubloxGPS
{
public:
	ubloxGPS(string qid)
	{
		configUART();
		qID = qid;
	}
	static void HAL_UART_RXIdleCallback(UART_HandleTypeDef *huart);
	void getPosition();
	int32_t checkMsgQ(FIFOQueue::QRecord qRecord);
	
private:
	uint8_t GPSMessage[256];
	uint16_t GPSMessageIndex = 0;
	string qID;
 
	//TODO might want to add a cmdQ sometime in the future
	FIFOQueue msgQ {4, 256, "ubloxGPSMsgQ"};
	
	void configUART();
	void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
};

and here the implementation. I get the error at line 41 below

#include <stm32f4xx_hal.h>
 
#include <iostream>
#include <string.h>
using namespace std;
 
#include "ubloxGPS.hpp"
#include "FIFOQueue.hpp"
 
const uint8_t PUBX00cmd[] = "$PUBX,00*33\r\n";
void ubloxGPS::getPosition()
{
	HAL_UART_Transmit_DMA(&hUART7, (uint8_t *)PUBX00cmd, sizeof(PUBX00cmd));
	HAL_UART_Receive_DMA(&hUART7, RXBuffer, RXBufferLength);
}
 
//TODO Figure out why certain methods are extern "C" and why some aren't
extern "C" void UART7_IRQHandler()
{
	//cout << "hit LPUART1_IRQHandler" << endl;
 
	HAL_UART_IRQHandler(&hUART7);
 
	if (__HAL_UART_GET_FLAG(&hUART7, UART_FLAG_IDLE))
	{
		ubloxGPS::HAL_UART_RXIdleCallback(&hUART7);
		__HAL_UART_CLEAR_IDLEFLAG(&hUART7); 
	}
}
 
void ubloxGPS::HAL_UART_RXIdleCallback(UART_HandleTypeDef *huart)
{
	HAL_UART_DMAStop(&hUART7);
	
	cout << "hit HAL_UART_RXIdleCallback" << endl;
	cout << "RX GPS Message: " << RXBuffer << endl;
	
	memset(RXBuffer, 0, RXBufferLength);
	HAL_UART_Receive_DMA(&hUART7, RXBuffer, RXBufferLength);
	
	msgQ.enqueue()
}
 
extern "C" void DMA1_Stream1_IRQHandler()
{
	cout << "hit DMA1_Stream1_IRQHandler (TX)" << endl;
	HAL_DMA_IRQHandler(&hDMA_TX);
}
 
extern "C" void DMA1_Stream3_IRQHandler()
{
	cout << "hit DMA1_Stream3_IRQHandler (RX)" << endl;
	HAL_DMA_IRQHandler(&hDMA_RX);
}
 
void ubloxGPS::HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	cout << "Hit HAL_UART_RxCpltCallback" << endl;
}
 
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	cout << "UART TX Complete" << endl;
}
 
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)
{
	cout << "UART TX Half Complete" << endl;
}
 
 
void ubloxGPS::configUART()
{
	/**UART7 GPIO Configuration    
	PF6     ------> UART7_RX
	PF7     ------> UART7_TX 
	*/
	__USART7_CLK_ENABLE();
	__GPIOF_CLK_ENABLE();
    
	GPIO_InitTypeDef GPIO_InitStructure;
 
	GPIO_InitStructure.Pin = GPIO_PIN_7;
	GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStructure.Alternate = GPIO_AF8_UART7;
	GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
	GPIO_InitStructure.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(GPIOF, &GPIO_InitStructure);
    
	GPIO_InitStructure.Pin = GPIO_PIN_6;
	GPIO_InitStructure.Mode = GPIO_MODE_AF_OD;
	HAL_GPIO_Init(GPIOF, &GPIO_InitStructure);
 
	hUART7.Instance        = UART7;
	hUART7.Init.BaudRate   = 9600;
	hUART7.Init.WordLength = UART_WORDLENGTH_8B;
	hUART7.Init.StopBits   = UART_STOPBITS_1;
	hUART7.Init.Parity     = UART_PARITY_NONE;
	hUART7.Init.HwFlowCtl  = UART_HWCONTROL_NONE;
	hUART7.Init.Mode       = UART_MODE_TX_RX;
    
	if (HAL_UART_Init(&hUART7) != HAL_OK)
	{
		cout << "Error in UART_Init" << endl;
		while (1) {}
	}
	
	NVIC_EnableIRQ(UART7_IRQn);
	__HAL_UART_ENABLE_IT(&hUART7, UART_IT_IDLE);
		
	//Setup UART TX DMA
	__DMA1_CLK_ENABLE();
	hDMA_TX.Instance = DMA1_Stream1;
	hDMA_TX.Init.Channel = DMA_CHANNEL_5;
	hDMA_TX.Init.Direction = DMA_MEMORY_TO_PERIPH;
	hDMA_TX.Init.PeriphInc = DMA_PINC_DISABLE;
	hDMA_TX.Init.MemInc = DMA_MINC_ENABLE;
	//hDMA.Init.Mode = DMA_CIRCULAR;
    hDMA_TX.Init.Mode = DMA_NORMAL;
	//hDMA.Init.Priority = DMA_PRIORITY_VERY_HIGH;
	hDMA_TX.Init.Priority = DMA_PRIORITY_LOW;
	hDMA_TX.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
	hDMA_TX.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
 
	if (HAL_DMA_Init(&hDMA_TX) != HAL_OK)
		asm("bkpt 255");
    
	__HAL_LINKDMA(&hUART7, hdmatx, hDMA_TX);
	NVIC_EnableIRQ(DMA1_Stream1_IRQn);
 
	//Setup UART RX DMA
	hDMA_RX.Instance = DMA1_Stream3;
	hDMA_RX.Init.Channel = DMA_CHANNEL_5;
	hDMA_RX.Init.Direction = DMA_PERIPH_TO_MEMORY;
	hDMA_RX.Init.PeriphInc = DMA_PINC_DISABLE;
	hDMA_RX.Init.MemInc = DMA_MINC_ENABLE;
	//hDMA.Init.Mode = DMA_CIRCULAR;
    hDMA_RX.Init.Mode = DMA_NORMAL;
	//hDMA.Init.Priority = DMA_PRIORITY_VERY_HIGH;
	hDMA_RX.Init.Priority = DMA_PRIORITY_HIGH;
	hDMA_RX.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
	hDMA_RX.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
 
	if (HAL_DMA_Init(&hDMA_RX) != HAL_OK)
		asm("bkpt 255");
    
	__HAL_LINKDMA(&hUART7, hdmarx, hDMA_RX);
	NVIC_EnableIRQ(DMA1_Stream3_IRQn);
}

14 REPLIES 14
TDK
Guru

Static functions are not member functions. They don't operate with a "this" object and you can't use non-static members within them. That is the source of the error message.

The difficulty here is that HAL_UART_RxCpltCallback is a global function and you're trying to use it within a class callback. Different classes are going to end up at the same callback so you need a way to know which one is relevant. It looks like you only have one object, so in this case you can do it, but if you had multiple objects it would be more difficult.

You've also hardcoded a lot of things within void ubloxGPS::configUART(), so even if you tried to instantiate multiple objects they would all be using the same peripheral, so it really wouldn't work out.

There's not really a silver bullet here. Implementing it well is going to be hard and is going to require a solid understanding of classes. Since you want to use DMA to make it asynchronous, it's going to be even more difficult and if you were using blocking functions.

One possibility is to have members within ubloxGPS for the peripheral, pins, and DMA stream handles you want to use and use those to call the relevant HAL functions.

If you feel a post has answered your question, please click "Accept as Solution".
  1. //TODO Figure out why certain methods are extern "C" and why some aren't

So they bind properly with the C and Assembler symbols in things like the vector table. C++ mangles the name to add typing information that isn't present on the undecorated symbols.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
magene
Senior II

@TDK​ and @Community member​ First of all, many thanks for taking the time to read my code so carefully. Even if you don't have the "silver bullet", you've still given me some things to thing about. Most importantly, you made me realize that I should have asked the question more like this:

I'm trying to build an app that has to talk to 7 or 8 UART devices plus a couple of I2C devices and run one or two modestly real time control loops (0.01 to 0.1 second response time). The basic architecture is an object oriented "Gang of Four" state machine with non-blocking methods. How would an experienced embedded programmer architect this system?

Here's a little more background:

1) I'm moving the program from C# and the .Net Micro Framework (please don't ask how I got there, it's too painful to talk about). The C# program is about 12,000 lines long.

2) I'm not a wildly experienced embedded C/C++ programmer but I have come to realize the benefit of an object oriented approach. But maybe not everywhere?

3) This program runs on a oceanographic profiling float that runs autonomously at sea for a year or more (or so my calculations say). Bi-directional communication with the float is through the 2400 baud Iridium satellite network.

4) Our hope is that this float will be adopted by 10s to 100s of other oceanographic research groups with widely varying levels of engineering support. So we have to design a system this is as robust as is reasonable and still capable of being used. maintained and extended for new applications by a wide group or users some without a strong embedded software development staff.

Here's a few more detailed questions if you're willing.

1) Does it make sense to leave the things that are clearly suitable for OO programming (like the state machine) but implement the device IO using standard C programming? My current architecture does as little as possible in the device drivers, just send commands, receive the response and put it in a FIFO queue for processing by the main state machine.

2) Should I just bite the bullet and dump the HAL library? If so, do I use the STM low level drivers or CMSIS or roll my own or ???

3) Can you recommend an experienced consultant who can get me up the learning curve and pointed in the right direction?

Thanks again.

I'm a C/Assembler guy, my brother went the C++ route, that's not to say I can't read the code and be a chameleon, but that I approach object and instances with structures, and don't write a dozen functions to handle all conceivable types/usage.

My personal view is that the UART HAL implementation is too bulky and unhelpful. I deal with the HW and SW side FIFOs more directly. For NMEA stuff I might just build a line buffer in the interrupt, and then copy/queue that for processing elsewhere.

UART RX DMA is a bit messy, my approach is to use it in a circular mode, to act as a FIFO, which I then sweep in the processing thread. I can generally eschew the hardware interlocks by having a buffer and process strategy that's at least an order of magnitude fast than the in-flow of data.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Pavel A.
Evangelist III

> Our hope is that this float will be adopted by 10s to 100s of other oceanographic research groups with widely varying levels of engineering support. So we have to design a system this is as robust as is reasonable and still capable of being used. maintained and extended

This is quite a serious statement. You're going to make a framework and a prototype system that will be code-reviewed, so there should be some "architecture" and code needs to look nicely. The crucial question for the architecture is whether to use a RTOS.

A RTOS can help selling your framework, but it adds effort to make it work, and it consumes resources.

Do you have minimal working code for the GPS receiver and stuff that sits on I2C?

If not, IMHO start from this.

Make one receiver work well (this means, do what it should do). Study what they do normally, how they responds, what are their failure modes.

Make all your receivers work one by one, then combine them together (without a RTOS, say, poll them in a loop + interrupts)

During this phase of the project, better forget OOP, GoF, classes, patterns, all that crap - just get the things practically working, with minimal amount of glue.

Then you'll have information helpful to move along. Especially, how to factor out the low level code (aka "drivers").

-- pa

TDK
Guru

> I'm trying to build an app that has to talk to 7 or 8 UART devices plus a couple of I2C devices and run one or two modestly real time control loops (0.01 to 0.1 second response time). The basic architecture is an object oriented "Gang of Four" state machine with non-blocking methods. How would an experienced embedded programmer architect this system?

One possibility, if you were to adopt HAL, would be to use their routines and check for the state of each peripheral (busy, transmitting, receiving) by using the UART handle. If you're not using an RTOS, this is robust assuming no HAL bugs. This has the benefit of being easier to read and more compatible with examples in CubeMX.

You could also discard HAL and write your own stuff. This is probably going to be slower and take much longer if you're not already familiar with the architecture.

I would also not use an RTOS and just service the different devices within the main loop, regardless of the choice above. Using an RTOS can be nice but it adds complexity and can hide bugs.

> 2) I'm not a wildly experienced embedded C/C++ programmer but I have come to realize the benefit of an object oriented approach. But maybe not everywhere?

Certainly not everywhere, but different programmers will have different opinions, probably based around their experience. I've used C++ style code more than C and, surprise, strongly prefer it even for embedded platforms.

If you're not familiar with OO programming, but you are comfortable with C, then C is probably the best way to go.

> 4) Our hope is that this float will be adopted by 10s to 100s of other oceanographic research groups with widely varying levels of engineering support. So we have to design a system this is as robust as is reasonable and still capable of being used. maintained and extended for new applications by a wide group or users some without a strong embedded software development staff.

Probably going to be hard to achieve this, especially the "able to be extended" part, without a experienced/dedicated person developing it.

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

Again, thanks for all the help. You've all given me plenty to think about.

@Pavel A.​ I've had a mostly working float running a C#/.Net Micro program for a couple of years now. I wasn't a very experienced embedded programmer at the beginning of this project (I had to take over developing the software from the previous much more experienced programmer) and it's been a very painful process cleaning up my newbie mistakes in the C# program. I found that OOP (done right), the GoF state pattern, classes and all that stuff have been a huge help in fixing my C# mistakes. C# was a great choice for me as an inexperienced embedded programmer to get something working but it's been obvious for a while that C# is not the right language for the oceanographic community which is why I'm slowly moving into the C/C++ world.

We're pretty much on the same page with respect to your recommendations. I've been writing little programs for a while to make sure I understand how to do each of the parts. I have several very small C programs that talk to a few of my devices using the STM HAL library to make sure I knew how to handle device IO. Writing a C++ version of my C# GoF state machine was easy. At this point, I've got a lot of the basic functionality working and I'm trying to tie it all together, again with a very small version of what will eventually grow into the final C/C++ app. However based on my C# experience, I think it's really important that I get all the bits and pieces, the state machine, the basic class hierarchy, device IO, message passing, error handling, etc., all integrated from the get-go so adding all the additional states and devices is almost 100% cook book repetition. At this point in my project, the biggest hurdle is integrating the STM HAL library. My understanding is ST is working on a leaner, more efficient HAL and I've got my fingers crossed. If I can leap the HAL hurdle, I should be off and running. At least, that's what I'm hoping but I'm sure I'll be posting more questions along the way.

Cheers and thanks again.

magene
Senior II

@TDK​ My last post must have crossed yours in the ether. Again, we're pretty much on the same page. In the short term, I think I can use a non C++ but still HAL based approach to the device IO. I'm don't envision the need for a RTOS either. My OO GoF state machine and non-blocking architecture has been very effective and extensible, at least for me. At this point it is just me doing all the mechanical, electrical and software engineering along with deploying and recovering the float and processing the data. It's a lot of fun, but it's also quite the understatement to say I'm resource constrained. It's always a balancing act figuring out what corners to cut so I can make progress. In this case, I'm hoping I can make the HAL stuff work with the understanding I'll have to rewrite that, hopefully small, part of my code at some point in the future. Or maybe ST will come out with a more effective HAL as some point in the near future. It could happen, right?

> Or maybe ST will come out with a more effective HAL as some point in the near future. It could happen, right?

Right. We are allowed to dream.

-- pa