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
MBany.1
Associate

Hello there

I wanna ask, how you managed to use a string type in the code?

in ny code I have included the library and tried to use it but it gives an error

magene
Senior II

@MBany.1​ I'm not sure what IDE you're using but with VisualGDB, I have to rename the source file that needs C++ functionality with a .cpp extension. I also have to make sure a C++ compiler is selected in the C/C++ property page of the project properties. If you're using VisualGDP it's one of the choices in Project-> yourProjectName Properties->Configuration Properties->C/C++->Advanced->Language Standard for C++ files. I also start the project with the VisualGDB project wizard and one of the first choices in the wizard is which C or C++ compiler I want to use so the compiler is usually selected when I check the project properties.

Hope that helps

James Murray
Senior

What baud rates are you using? Do you really need the DMAs?

I wrote a GPS decoder for 38400 baud just using regular interrupts on each received byte. As Clive suggests, I just read directly from the hardware without the HAL complexity. I am calling the HAL code to setup the port.

My background is more low-level, so I generally find it easier to understand the hardware instead of fighting the HAL.

James

magene
Senior II

@James Murray​ Good question, I'll try to make a long story short but no promises. I'm rewritting a working C# app that I wrote several years ago when for a variety of reasons that are too painful to menion, I had to take over writing all the embedded code for an oceanographic profiling float with a suite of sensors on it. The float has to talk to 7 RS232 devices, 2 I2C devices and a handful of other gizmos. Baud rates are everywhere from 9600 to 115K. In addition, the float runs a PID control loop that keeps the float profiling at a constant velocity or maintains a constant depth and it needs to run with something like a few precent timing jitter. For a variety of reasons that make sense to me (but maybe nobody else), I'm using a modestly object oriented, non-blocking, state machine architecture based on several other architectures I've read about that I'm actually pretty happy with.

Some of the devices have message sizes up to several kilobytes, hence the DMA. Some of the devices have message sizes of only a few 10s of bytes but they do it several times per second. In almost all cases, I don't know the exact number of bytes to be transfered. The good news is I was able to get my DMA driver working with idle line detect in a reasonablly straight forward way after a fair amount of HAL induced pain. And although my original post talked about using DMA, I have a virtually identical driver that uses interrupts without DMA. Unfortunately since I dont' know the exact number of bytes to be transferred ahead of time, my current interrupt driver fires the IRQ for each byte. If I can get the idle line detect IRQ running with the interrupt only approach, that would be much better and is something I'll work on in the future. I expect to use a mix of the DMA and non-DMA drivers and try to figure out the pros and cons of each approach.

The main stumbling block is HAL, not DMA or C++. I can't undertand why something designed to make it easy for developers to build embedded systems is so complicated and unwieldy. For example, my current task is integrating the FATFs file system. I can get the STM provided examples working on the STMF413 Discovey board but now I need to port it over to the STM Nulceo413ZH board and the SDIO pins are different on the 2 boards. Just finding where the pins are mapped in the STM HAL example has taken several hours and I still don't think I"ve identified all the places in the HAL code that I need to modify. the STM HAL is making my already tenuous grip on sanity even more slippery. I would love to take the time to learn enough to drop HAL entirely, but that seems like a huge investment in time.

BTW, does anyone have any experience with Tilen Majerle's libraries (https://stm32f4-discovery.net/) that they'd like to share?

James Murray
Senior

I have the FATFS working in my project, while I've written a DOS previously, using the canned code made life an awful lot easier and it does work, but you will need to ensure other tasks are called from interrupts as the FATfs and SD code do a lot of blocking.

There's lots of useful stuff in the HAL, but also many horrible things, like the HAL_LOCK macro that includes a return, I also found busy waits in many pieces of code.

e.g. the ethernet startup code waits for a number of seconds for the port!! I re-wrote that as a non-blocking state machine to avoid stalling startup.

James