STM32F405 Feather GPS Logger

Demonstrates UART/DMA Rx, FreeRTOS, specific sentence selection and SD Card logging.

Compiled with STMicroelectronics core using the Arduino IDE. Note, all references to “Serial” in the STM32 SD library SD.cpp file were commented out since this program is not using the builtin serial functionality. Program listing below:

// Adafruit STM32F405 Feather GPS Logger.
// UART DMA RX undetermined length data demonstration.
// build_opt.h: -DHAL_UART_MODULE_ENABLED -DHAL_UART_MODULE_ONLY
// USART: disabled, USB support: none.
//
// Adaptation of technique described here:
// https://stm32f4-discovery.net/2017/07/stm32-tutorial-efficiently-receive-uart-data-using-dma/
// https://community.st.com/s/global-search/efficiently%20use%20dma%20with%20uart%20rx%20stm32
//
// Comment out all reference to "Serial" inside STM32SD library file SD.cpp file.
//
// Use TeraTerm to connect to board at 9600 baud to monitor.
// USART1 RX is on SDA pin, PB7 (GPS).
// USART1 TX is on SCL pin, PB6 (PC).
// SD detect pin is on PB12 (D32).

#include <STM32FreeRTOS.h>
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"
#include "stm32f4xx_ll_dma.h"
#include "stm32f4xx_ll_usart.h"
#include "ctype.h"
#include <atomic>
// All reference to Serial commented out in STM32SD.h/SD.cpp files of STM32SD library.
#include <STM32SD.h>

#define BUFFER_SIZE 128

// UART and DMA handles.
UART_HandleTypeDef hUART1;
DMA_HandleTypeDef hDMA2;
 
// UART DMA data buffer.
uint8_t dmaBuffer[BUFFER_SIZE];
// GPS sentence buffer.
uint8_t sentenceBuffer[BUFFER_SIZE];

// Overkill? Because ARM Architecture Reference Manual says any aligned 32-bit memory access is atomic.
// https://developer.arm.com/documentation/ddi0403/eb/
std::atomic<uint32_t> count(0);
//uint32_t volatile __attribute__((aligned(4))) count{0};

// File object refers to gps text file.
File file;

void errorHandler(uint32_t d) 
{
  while(1) 
  {
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_1);
    HAL_Delay(d);  
  }
}

void UART_DMA_Init(void)
{
  // USART1 init.
  __HAL_RCC_USART1_CLK_ENABLE();
  hUART1.Instance = USART1;
  hUART1.Init.BaudRate = 9600;
  hUART1.Init.WordLength = UART_WORDLENGTH_8B;
  hUART1.Init.StopBits = UART_STOPBITS_1;
  hUART1.Init.Parity = UART_PARITY_NONE;
  hUART1.Init.Mode = UART_MODE_TX_RX;
  hUART1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  hUART1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&hUART1) != HAL_OK)
    errorHandler(1000);
  HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(USART1_IRQn);
  LL_USART_EnableIT_IDLE(USART1);

  // DMA2 USART1 RX init.
  __HAL_RCC_DMA2_CLK_ENABLE();
  hDMA2.Instance = DMA2_Stream2;
  hDMA2.Init.Channel = DMA_CHANNEL_4;
  hDMA2.Init.Direction = DMA_PERIPH_TO_MEMORY;
  hDMA2.Init.PeriphInc = DMA_PINC_DISABLE;
  hDMA2.Init.MemInc = DMA_MINC_ENABLE;
  hDMA2.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  hDMA2.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  hDMA2.Init.Mode = DMA_CIRCULAR;
  hDMA2.Init.Priority = DMA_PRIORITY_VERY_HIGH;
  hDMA2.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
  if (HAL_DMA_Init(&hDMA2) != HAL_OK)
      errorHandler(1000);
  __HAL_LINKDMA(&hUART1, hdmarx, hDMA2);

  LL_DMA_SetPeriphAddress(DMA2, LL_DMA_STREAM_2, (uint32_t)&USART1->DR);
  LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_2, (uint32_t)dmaBuffer);
  LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_2, (uint32_t)BUFFER_SIZE);

  LL_DMA_EnableIT_HT(DMA2, LL_DMA_STREAM_2);
  LL_DMA_EnableIT_TC(DMA2, LL_DMA_STREAM_2);
  
  HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
  LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_2);
  LL_USART_EnableDMAReq_RX(USART1);
}

void GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct;

  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();

  // USART1 GPIOs: PB6->TX, PB7->RX
  GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  // LED GPIO pin.
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_RESET);
  GPIO_InitStruct.Pin = GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  // Neopixel LED GPIO pin reset.
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET);
  GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_13;
  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}

// Print RMC sentences.
void printRMCSentence(const size_t len)
{
  // Null terminate gps sentence.
  sentenceBuffer[len] = 0;

  if (strstr((const char *)sentenceBuffer, "$GPRMC") != NULL)
  {
    for (uint8_t i=0; i<len; i++)
    {
      LL_USART_TransmitData8(USART1, sentenceBuffer[i]);
      while (!LL_USART_IsActiveFlag_TXE(USART1));
    }
  
    // cr/lf.
    LL_USART_TransmitData8(USART1, '\r');
    while (!LL_USART_IsActiveFlag_TXE(USART1));
    LL_USART_TransmitData8(USART1, '\n');
    while (!LL_USART_IsActiveFlag_TXE(USART1));
  
    // Wait until xfer complete.
    while (!LL_USART_IsActiveFlag_TC(USART1));

    // Open file, write data then close file.
    file = SD.open("gps.txt", FILE_WRITE);
    if (file)
    {
      file.print((const char *)sentenceBuffer);
      file.close();
    }
  }
}

void constructGPSSentence(const void* data, size_t len)
{
  static size_t index = 0;
  const uint8_t* d = (const uint8_t*)data;
  
  while (len--)
  {
    if (*d == '\n')
      printRMCSentence(index);

    if (*d == '$' || index >= BUFFER_SIZE)
      index = 0;

    // Eat cr/lf (0x0d/0x0a).
    if (*d == '\n' || *d == '\r')
      d++;
    else
      sentenceBuffer[index++] = *d++;
  }
}

// Extract data from circular buffer.
void checkData(void)
{
  static size_t prevPos;
  size_t curPos;

  // Calculate current position in buffer.
  curPos = BUFFER_SIZE - LL_DMA_GetDataLength(DMA2, LL_DMA_STREAM_2);

  // Check for change in received data.
  if (curPos != prevPos)
  {
    if (curPos > prevPos)
    {
      // Process data directly by subtracting pointers.
      constructGPSSentence(&dmaBuffer[prevPos], curPos - prevPos);
    }
    else
    {
      // Buffer overflow occurred, first, process data to the end of buffer.
      constructGPSSentence(&dmaBuffer[prevPos], BUFFER_SIZE - prevPos);
      // Check and continue with beginning of buffer.
      if (curPos)
        constructGPSSentence(&dmaBuffer[0], curPos);
    }
  }
  
  // Save current position.
  prevPos = curPos;
  
  // Check and manually update if at end of buffer.
  if (prevPos == BUFFER_SIZE)
    prevPos = 0;
}

// Rx dma stream interrupt check for HT/TC interrupts.
extern "C" void DMA2_Stream2_IRQHandler(void)
{
  if (LL_DMA_IsEnabledIT_HT(DMA2, LL_DMA_STREAM_2) && LL_DMA_IsActiveFlag_HT2(DMA2))
  {
    // Clear half-transfer complete flag.
    LL_DMA_ClearFlag_HT2(DMA2);
    // Flag data needs processing.
    std::atomic_fetch_add(&count, 1);
  }

  if (LL_DMA_IsEnabledIT_TC(DMA2, LL_DMA_STREAM_2) && LL_DMA_IsActiveFlag_TC2(DMA2))
  {
    // Clear half-transfer complete flag.
    LL_DMA_ClearFlag_TC2(DMA2);      
    // Flag data needs processing.
    std::atomic_fetch_add(&count, 1);
  }
}

// USART1 global interrupt check IDLE line.
extern "C" void USART1_IRQHandler(void)
{
  if (LL_USART_IsEnabledIT_IDLE(USART1) && LL_USART_IsActiveFlag_IDLE(USART1))
  {
    // Clear IDLE line flag.
    LL_USART_ClearFlag_IDLE(USART1);
    // Flag data needs processing.
    std::atomic_fetch_add(&count, 1);
  }
}

void checkTask(void *arg) 
{
  UNUSED(arg);

  UART_DMA_Init();
  __HAL_UART_FLUSH_DRREGISTER(&hUART1);
  HAL_UART_Transmit(&hUART1, (uint8_t *)"USART/DMA FreeRTOS GPS Demo\n\r", 29, 100);

  while (1)
  {
    if (std::atomic_load(&count))
    {
      std::atomic_fetch_sub(&count, 1);
      checkData();
    }
  }
}

void setup()
{
  GPIO_Init();

  if (!SD.begin())
    errorHandler(100);

  xTaskCreate(checkTask, NULL, configMINIMAL_STACK_SIZE, NULL, 1, NULL);
  vTaskStartScheduler();
}

void loop() { }

About Jim Eli

µC experimenter
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a comment