Skip to main content

A Simple STM32 Example Project

Most of the embedded projects share certain initial steps. You need to confirm the clock settings before doing anything, then you want to have debug connection via a UART channel since it is cheap  but still it can provide useful information for debugging.

Let us start with CubeMX. You select a device/board of your choice, set up the pinouts for one GPIO output and one UART port. Configure the clock if necessary then create a project.


Clock Checking using SysTick

The sanity of the clock setting can be done by checking the SysTick interval. All Cortex-M series core have SysTick timer by default, which should fire at 1msec interval while the MCU is active. In the STM32Cube, the SysTick is initialized by HAL_Init() call, which in turn calls SysTick_Config() in CMSIS.

Once the SysTick is initialized, it generates 1 msec interrupt and this interrupt is handled by SysTick_Handler()according to the Cube framework. Source file
    1 /**
    2 * @brief This function handles System tick timer.
    3 */
    4 void SysTick_Handler(void)
    5 {
    6   /* USER CODE BEGIN SysTick_IRQn 0 */
    7 
    8   /* USER CODE END SysTick_IRQn 0 */
    9   HAL_IncTick();
   10   HAL_SYSTICK_IRQHandler();
   11   /* USER CODE BEGIN SysTick_IRQn 1 */
   12 
   13   /* USER CODE END SysTick_IRQn 1 */
   14 }
The SysTick_Handler() then calls HAL_SYSTICK_IRQHandler() as shown above and finally it calls HAL_SYSTICK_Callback(), which is implemented as a weak function. So you can overwrite it in your main.c. In this function, you toggle the GPIO output. Source file
  312 /* USER CODE BEGIN 4 */
  313 void HAL_SYSTICK_Callback(void)
  314 {
  315     HAL_GPIO_TogglePin(TEST_LED_GPIO_Port, TEST_LED_Pin);
  316 }
  317 /* USER CODE END 4 */
By measuring the frequency of the waveform, you can check the frequency of the SysTick.

This SysTick output is useful throughout the development. You can monitor the state of the MCU for example whether the watchdog kicked in or brown-out reset occurred.

UART Debug Output

Another useful feature that you want to have is the UART debug output. Basically you can implement the same function as printf() whose output is redirected to UART port. Source file
  312 /* USER CODE BEGIN 4 */
  313 void UartPrintf(const char *format, ...)
  314 {
  315     char buffer[128];
  316     uint16_t size;
  317     va_list args;
  318 
  319     va_start(args, format);
  320     size = vsprintf(buffer, format, args);
  321     va_end(args);
  322 
  323     HAL_UART_Transmit(&huart1, (uint8_t*)buffer, size, 1000);
  324 }
  325 /* USER CODE END 4 */
To compile this code, you may need to include following header files. Source file
   54 /* USER CODE BEGIN Includes */
   55 #include <stdio.h>
   56 #include <stdarg.h>
   57 /* USER CODE END Includes */
Depending on the c library the toolchain was built on, actual feature of the format conversion may vary, for example floating point conversion may not supported in Cortex-M0. However you can use the same syntax as printf(). Source file
  117         /* USER CODE BEGIN 2 */
  118         UartPrintf("System Reset: %d\n\r", some_int);
  119         /* USER CODE END 2 */

Comments

Popular posts from this blog

STM32 USB MSC Device with FLASH memory

USB Mass Storage Class framework implements bulk-only-transfer (BOT) with SCSI protocol. USB packets from the host eventually converted SCSI transport commands by the middleware, in which data is exchanged (read / write) in the unit of logical block, typically 512 bytes. This SCSI commands works well with SD card system where a dedicated controller does the job of managing the actual memory elements. If you want to use a FLASH chip as a memory unit instead, you need to handle read / write operation directly. Fortunately, most of flash memory support 4KB block erase. This makes the 4096 bytes as a natural choice for the size of the logical block in the file usbd_storage_if.c. In this case, 8Mbit Flash memory was used. During initial enumeration, this information is registered to the host. The middleware maintains one logical block size of buffer and handles USB transaction where each payload is only 64 bytes. It then calls SCSI requests to store / retrieve data to / from physical

STM32 USB MSD with SD Card

Build a low level driver for SD card, then the glue logic for FatFs and USB MSD is pretty much the same as Flash memory case posed before. In case of SD card, sector size is 512 in most of the cases. Thus the memory requirement is much relaxed. You can even allocate a file buffer that is bigger than the sector size. FatFs site has a  dedicated page for MMC/SDC, on which you can find fairly detailed explanation about how to interface MMC/SDC via SPI bus. Implementation should be straightforward until you encounter with cheap SD cards that do not behave very well. In such cases, you either have to protect your code with redundancy or just stick with quality devices. If you choose SanDisk or Kingston brand, you will be safe. ADATA on the other hand, frequently generates timeout error at first try. Most of the SD card sockets have a pin to detect the presence of the card. This pin is usually connected to GND pin or some other pin. You can use this to generate interrupt whenever a ca