Skip to content

A project for practice and a work sample to demonstrate embedded software skills.

Notifications You must be signed in to change notification settings

ncaccamo/STM32_DCMotor_Demonstration

Repository files navigation

STM32_DCMotor_Demonstration

Picture of hardware setup

Closeup of display while running

This project is intended to practice and demonstrate embedded software skills on a modern ARM microcontroller.

Main objectives:

  1. Start a blank project and configure the microcontroller through registers utilizing basic drivers and headers provided by the manufacturer (CMSIS).
  2. Drive a DC motor using PWM.
  3. Control the motor speed using a rotary encoder. Change the motor direction with the encoder's pushbutton.
  4. Measure the motor speed using a quadrature encoder on the motor.
  5. Measure the motor's temperature with a temperature sensor.
  6. Show the motor speed and temperature on a small display. Utilize third party API to drive the display.

Software design description: All configuration is done by manual register manipulation, without STM HAL, to keep the application fast and small. The application is interrupt driven. A timer with interrupt periodically polls for changes in the rotary encoder, sets the motor PWM duty, and calls a function from the motor module to measure the motor's speed using another timer in encoder mode. A timer is dedicated to generating the PWM signal to control the motor. An external interrupt is generated by the rotary encoders pushbutton to change the motor direction, but the direction change is only allowed if the motor is stopped to prevent hardware damage. Another periodic timer interrupt is dedicated to drawing the display and calling for temperature measurement. The display module utilizes the u8g2 library and interfaces with a basic SPI driver. The temperature measurement utilizes an ADC and conversion equations to yield a result in degrees Celsius. Drawing of the display and measuring temperature are done periodically through a timer interrupt, but the routines are called in main when the ISR sets a flag. This is because these routines are likely too time intensive to be done within the ISR, so this approach was deemed more appropriate. Efforts have been made to encapsulate data to the relevant modules and provide interfaces for other modules to retrieve necessary data.

The modules can be divided into an MCU peripheral configuration layer and a hardware application layer. The hardware application modules could be adapted to another MCU configuration with some macro changes.

Peripheral configuration modules:

  • adc.c: configures ADC
  • clockconfig.c: sets system clock PLL and starts peripheral clocks
  • gpio.c: sets all GPIO modes and parameters
  • interrupt.c: contains all ISRs
  • spi.c: implements driver functions to start SPI transfer, send a byte of data, and end SPI transfer
  • timer.c: configures all timers

Hardware application modules:

  • display.c: calls u8g2 library functions to draw the display, and queries other modules for the needed data
  • encoder.c: implements a driver to measure rotary encoder movement
  • motor.c: implements functions for setting direction, setting duty cycle, measuring speed, and calculating a rolling average of speed. Maintains data structure with status of the motor and interface functions for access to data.
  • temperature.c: implements function to take ADC measurement and convert to temperature. Also provides interface function to access the latest temperature measurement.

u8g2 Library: https://github.com/olikraus/u8g2

Components:

  • STM32 Nucleo-G431RB development board
  • KY-040 rotary encoder
  • 12V DC motor with encoder
  • L298N motor driver board and 12V DC power supply
  • SPI OLED Display, 128x64 resolution (SSD1306 controller)
  • TMP36 temperature sensor

Hardware connections:

Nucleo Board Pin Label MCU Signal Hardware Interface
PC10 SPI3_SCK Display D0
PC11 Output Display CS
PC12 SPI3_MOSI Display D1
PD2 Output Display DC
PA15 Output Display RES
PA11 Timer Input Motor Encoder A
PA12 Timer Input Motor Encoder B
PC7 Output Motor Driver In1
PA9 Output Motor Driver In2
PB6 Timer Output Motor Driver PWM
PB13 Input Rotary Encoder CLK
PB14 Input Rotary Encoder DT
PB15 Input (Interrupt) Rotary Encoder SW
PA1 ADC Input Temp Sensor Output

Issues encountered, learnings, notes:

  • GPIO clock has to be enabled before the configuration registers can be written.
  • Clearing GPIOA MODER register disabled the JTAG. Learned that JTAG is using some pins on GPIO Port A so those pin configurations were left at reset value.
  • Writing clock registers required that flash latency was set to 4 wait states according to ST example code and the manual.
  • Printing to console did not work so had to add a write function using CMSIS drivers, found someone else's solution online.
  • Learned that for clearing pending interrupt flags, it makes the most sense to write the bit directly instead of | or & with a mask. In STM32, these registers are cleared by writing a 0 or 1 depending, and so the remaining bits are unaffected anyway. Example: rc_w0 bit is cleared by writing zero so it can be cleared by TIM4->SR = ~TIM_SR_UIF.
  • The rotary encoder seems noisy so I researched some encoder examples online and adapted a state machine that accounts for when the encoder moves and stops. This will replace using a timer in encoder mode.
  • For reference, to calculate the update frequency of a timer: TIM_CLK / (PSC + 1) / (ARR + 1) / (RSR + 1), the terms being prescaler (PSC), auto-reload register (ARR), and repetition counter (RCR). The result is in hertz and must be converted to seconds (1/hz).
  • For reference, the frequency of PWM is calculated by TIM_CLK / (PSC + 1) / (ARR + 1), because it is based on a timer. The result is in hertz. Duty cycle is calculated by CCRx/ARRx, where CCRx is the reload value of the capture/compare register.
  • The initial timer interrupt frequency of 200ms (5hz) was much too slow, and there were many errors in detecting the encoder movement, such as missed pulses and failure to switch the direction flag. Polling at 30hz is working better.
  • I found the SPI hardware NSS (chip select) automatic control to be confusing and decided to use a GPIO as CS and manually change its state for data transfer.
  • The screen drawing function seems to take too long to be safely put in an ISR directly - infinite loop occurs. Setting a flag in the ISR to update the screen in the main loop seems to be stable.
  • The STM32 ADC is quite complicated and requires a lot of setup. Parts initially missed were that the clock source has to be selected in the RCC registers and the power control clock also needs to be enabled.

Future considerations:

  • The motor control could be refined for better startup from zero speed. Running the motor at 100% duty for a short time to get it started is one possible solution.
  • Temperature measurements have not been verified as accurate but seem reasonable.
  • Basic verification of function parameters is done in some functions, but error handling could be implemented throughout.
  • The ADC measurement is taken through a blocking method which does not appear to impact the timing of the rest of the system, but could be changed to use an interrupt for signalling the completed measurement.
  • The SPI transfer uses a blocking method which does not appear to impact the timing of the rest of the system, but could be changed to use interrupts or DMA for transfer.
  • The RES line of the display is highly suseptible to noise and prevents the display from operating normally. It is configured to be a software controlled signal now, but a workaround is to connect it to a steady voltage (3.3V or 5V) to ensure that it stays high.

About

A project for practice and a work sample to demonstrate embedded software skills.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages