STM32CubeMX and SDRAM

Sometimes, using CubeMX and the HAL, there is something missing. For SDRAM, it is the command sequence that need to be issued after initializing the FMC module. The SDRAM itself also needs some information on timing and refresh, so FMC and SDRAM getting friends.

Digging around the web again mostly shows the low-level solutions and hardly no solutions using CubeMX and HAL. Even the examples of the STM32F4 MCU package has only very irritating examples that could not have been generated by CubeMX. It seems to be again my turn. I already found a community post, that exactly points out the problem with it. The problem with the solution is, that it does not integrate safely into CubeMX generated code when simply copy pasted.

But first we look at the callback tree for initializing the SDRAM.

HAL callback structure for SDRAM

MX_FMC_Init()                 fmc.c
   HAL_SDRAM_Init()           stm32f4xx_hal_sdram.c
      HAL_SDRAM_MspInit()     fmc.c (stm32f4xx_hal_sdram.c)
         HAL_FMC_MspInit()    fmc.c
      FMC_SDRAM_Init()        stm32f4xx_ll_fmc.c
      FMC_SDRAM_TimingInit()  stm32f4xx_ll_fmc.c

And the bug of CubeMX is, not to provide

  • a callback or user section at the end of MX_FMC_Init() or
  • a user section at the end of fmc.c (or at least somewhere after SDRAM handler definition)
  • a callback in FMC_SDRAM_TimingInit()

Welp, without having any of these, we need to cheat a bit. The SDRAM handler is already declared external by CubeMX in fmc.h, which in turn is included in fmc.c itself. Honestly, I find this not very clean, because it exposes the internal data stuctures to the outer code. But this is only the opinion of a clean code enthusiast… Since we have neither a callback nor a top level user code section in fmc.c, following the declaration of the SDRAM handler, we go this dirty way. Just below the peripheral initialization, there is a user code section generated by CubeMX. There we add our init sequence function call.

MX_SDRAM1_InitSequence(SDRAM_REFRESH_COUNT, SDRAM_TIMEOUT);

The init sequence function, we will add in a user code section of fmc.c looks like this (it is for SDRAM part IS42S32800G-6BLI).

define SDRAM_MODEREG_BURST_LENGTH_1             ((uint16_t)0x0000)
define SDRAM_MODEREG_BURST_LENGTH_2             ((uint16_t)0x0001)
define SDRAM_MODEREG_BURST_LENGTH_4             ((uint16_t)0x0002)
define SDRAM_MODEREG_BURST_LENGTH_8             ((uint16_t)0x0004)
define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL      ((uint16_t)0x0000)
define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED     ((uint16_t)0x0008)
define SDRAM_MODEREG_CAS_LATENCY_2              ((uint16_t)0x0020)
define SDRAM_MODEREG_CAS_LATENCY_3              ((uint16_t)0x0030)
define SDRAM_MODEREG_OPERATING_MODE_STANDARD    ((uint16_t)0x0000)
define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE     ((uint16_t)0x0200)

void MX_SDRAM1_InitSequence(uint32_t RefreshCount, uint32_t timeout)
{
  __IO uint32_t tmpmrd = 0;
  static FMC_SDRAM_CommandTypeDef Command;

  /* Step 1: Configure a clock configuration enable command */
  Command.CommandMode            = FMC_SDRAM_CMD_CLK_ENABLE;
  Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber      = 1;
  Command.ModeRegisterDefinition = 0;
  /* Send the command */
  HAL_SDRAM_SendCommand(&hsdram1, &Command, timeout);

  /* Step 2: Insert 100 us minimum delay */
  // Inserted delay is equal to 1 ms due to systick time base unit (ms) 
  HAL_Delay(1);

  /* Step 3: Configure a PALL (precharge all) command */
  Command.CommandMode            = FMC_SDRAM_CMD_PALL;
  Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber      = 1;
  Command.ModeRegisterDefinition = 0;
  /* Send the command */
  HAL_SDRAM_SendCommand(&hsdram1, &Command, timeout);

  /* Step 4: Configure an Auto Refresh command */
  Command.CommandMode            = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
  Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber      = 8;
  Command.ModeRegisterDefinition = 0;
  /* Send the command */
  HAL_SDRAM_SendCommand(&hsdram1, &Command, timeout);

  /* Step 5: Program the external memory mode register */
  tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |\
    SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |\
    SDRAM_MODEREG_CAS_LATENCY_2 |\
    SDRAM_MODEREG_OPERATING_MODE_STANDARD |\
    SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
  Command.CommandMode            = FMC_SDRAM_CMD_LOAD_MODE;
  Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber      = 1;
  Command.ModeRegisterDefinition = tmpmrd;
  /* Send the command */
  HAL_SDRAM_SendCommand(&hsdram1, &Command, timeout);

  /* Step 6: Set the refresh rate counter */
  HAL_SDRAM_ProgramRefreshRate(&hsdram1, RefreshCount);
}

Just to make it complete, here are the settings for FMC for the specific part, taken from MX_FMC_Init(), generated by CubeMX:

hsdram1.Instance = FMC_SDRAM_DEVICE;

/* hsdram1.Init */ 
hsdram1.Init.SDBank             = FMC_SDRAM_BANK1; 
hsdram1.Init.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_8;
hsdram1.Init.RowBitsNumber      = FMC_SDRAM_ROW_BITS_NUM_12; 
hsdram1.Init.MemoryDataWidth    = FMC_SDRAM_MEM_BUS_WIDTH_32; 
hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; 
hsdram1.Init.CASLatency         = FMC_SDRAM_CAS_LATENCY_1; 
sdram1.Init.WriteProtection     = FMC_SDRAM_WRITE_PROTECTION_DISABLE; 
hsdram1.Init.SDClockPeriod      = FMC_SDRAM_CLOCK_PERIOD_2; 
hsdram1.Init.ReadBurst          = FMC_SDRAM_RBURST_DISABLE; 
hsdram1.Init.ReadPipeDelay      = FMC_SDRAM_RPIPE_DELAY_0; 

/* SdramTiming */
SdramTiming.LoadToActiveDelay    = 2;
SdramTiming.ExitSelfRefreshDelay = 7;
SdramTiming.SelfRefreshTime      = 4;
SdramTiming.RowCycleDelay        = 7;
SdramTiming.WriteRecoveryTime    = 3;
SdramTiming.RPDelay              = 2;
SdramTiming.RCDDelay             = 2;

Even if SDRAM_HandleTypeDef hsdram1; is declared below our init function, it can be used in the upper user code section of fmc.c, because it is declared external in fmc.h, which is included just ahead of the user code section. Again not very clean, but it works.

My Two Cents About HAL…

And for all of you crying that HAL is way to oversized for STM32 projects and everybody needs to know about all registers of the devices. Maybe you are right, but this is no excuse for writing bad code. In my opinion, HAL does a very good job at keeping cohesion high and coupling low. The main problems with it originate from CubeMX templates, that break some of the rules for good coding. Would it use more techniques like first class abstract data types and ensure, the extension points (user code sections, callbacks) are sufficient, it would guide more people to a well designed architecture. Currently, programmers are forced to break the boundaries too often. The same applies e.g. for my other post about UART continuous receive

But instead of coding low-level code that is badly evolvable (can not be extended, reused, adjusted or understand easily), I think it is better to find a clean solution to keep most of CubeMX’s code as is and use the extension points it provides. I believe it is of much higher value having projects all integrating with CubeMX nicely and therefore can be understood easily by other parties (e.g. a new team mate or the guy taking over your project), than reinventing the wheel over and over again using LL functions or direct register access. STM32 have enough space and speed to cope with a little overhead. And exactly here applies another rule of thumb from clean coding guidelines: Never try to speed optimize without profiling. This means, use a profiler to identify the critical pieces of your code and only if needed, invest time to optimize the critical parts.

STM32 UART Continuous Receive with Interrupt

My last post is quite some time ago, due to vacations and high workload. But now I encountered some problem within an embedded project, I want to share the solution with you. Continuously receive data using interrupts on UART is complicated (or even impossible) in HAL. Most approaches I found crawling the internet are using the LL library to achieve this and many discussions around HAL do not end in satisfaction. Some work around the problems with dirty approaches (e.g. changing the HAL code itself), other step back from interrupt and use a polling approach.

To be honest, the high levels of HAL do not offer such a solution. Instead, it offers functions to receive a special amount of data using a non-blocking interrupt approach, handling all the difficulties with tracking the state in the instance stucture (huartX) and entering a callback for the diverse states of the reception/transmission, e.g.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) or
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)

Using HAL_UART_Receive_IT (not recommended)

A nearby approach without touching HAL code itself is, to call HAL_UART_Receive_IT(&huart3, &rxbuf, 1) once after initalization and at the end of the RxCpltCallback, to retrigger the reception, but this leads to some undesired lock (possibly a HAL-Bug), when transmitting data using HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size), which could also not be the desired behaviour and the beginning of endless debug sessions and frustration.

Simply Enable the IRQ

The best solution in my opinion instead is really simple. Don’t use the high level receive functions at all for the continuous RX behaviour, since you do not want to receive a special amount of data but be called at each reception. So, configure the UART with interrupt in CubeMX and after it’s initalization, enable the interrupt itself, never calling the HAL_UART_Receive_IT or any other UART receive function (it will disable the IT after finishing).

In the section of the appropriate instance in void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle), add the following line of code:

__HAL_UART_ENABLE_IT(&huartX, UART_IT_RXNE);

In stm32xxx_it.c do:

void USART3_IRQHandler(void)  {    
    /* USER CODE BEGIN USART3_IRQn 0 */
    CallMyCodeHere();
    return;  // To avoid calling the handler at all 
             // (in case you want to save the time)
    /* USER CODE END USART3_IRQn 0 */
    HAL_UART_IRQHandler(&huart3);
    /* USER CODE BEGIN USART3_IRQn 1 */
    /* USER CODE END USART3_IRQn 1 */ 
}

The return statement will avoid calling the HAL IRQ handler. I did not try during transmit, but it seems not disturbing anything. If you plan to use the HAL_UART_Receive_IT functions in parallel, you could try to put your code below the handler. I did not test it, but there is a good chance that it works.

Since this approach only touches the user code functions, none of your code will be destroyed by code re-generation of CubeMX.

This is all you need… Happy UART processing 😉

If Timestamping is Needed

Simple Millisecond Timestamps

If you want to trigger on inactive time durations (some serial protocols use it as a synchronisation condition), save a timestamp (e.g. HAL_GetTick()) within the UART-RX-Interrupt and look at the difference to the previous one (subtract the duration of a byte to get the real inactive time).

High Resolution Timestamps

If sub-milli-second resolution is required, run a timer with a prescaler of desired resolution and take the counter value of the timer instead of the tick counter. (you can get it with __HAL_TIM_GET_COUNTER(&htimX)).

Hope this helps in your next project using UART 🙂