Skip to content

ufnalski/absolute_encoder_amt222bv_spi_g431kb

Repository files navigation

Absolute encoder with SPI interface (STM32G431KB)

An STM32 HAL example of communicating with an absolute encoder over the SPI interface. A single-turn 14-bit encoder from CUI Devices is taken as an example. The relevant evaluation kit is AMT222B-V.

Single-turn absolute encoder in action

Tip

Yes, the encoder happens to be mechanically compatible with LEGO Technic/Mindstorms bricks - no need to 3D print to align the rotating part or to drill to bolt the housing.

CUI Devices encoder LEGO compatibility

The main motivation behind this submission is to encourage you to get familiar with delays inevitably accompanying any code execution. In the embedded world microcontrollers interact with physical processes through their peripherals connected to sensors and actuators. Time flows ever onward for the physical plant, energy gets converted, d/dt is relentless, and neglecting delays on the uC side can ruin your project.

Here our goal is to reproduce the following waveforms under the assumption of getting close to the required minimal time delays:

CUI Devices AMT22 timing waveform

Source: AMT22 Series Datasheet

We will fail miserably in doing that but hopefully we will learn a lot along the way. This does not mean that we will be unable to read the position - they are the minimal delays required by the sensor, not the maximal allowable ones. The latter are not specified - lucky we 😉

Blocking vs. non-blocking code

The library implements both approaches. The blocking one is taken from AMT22 Library. The non-blocking version is a small addition from my side. Both of them fail miserably in getting close to the minimal time delays accepted by the sensor. Other approaches should be explored if reaching higher position sampling frequencies is critical for your application. Turning into the LL (low layer) libraries instead of the HAL (hardware abstraction layer) ones should improve things. The DMA transfers triggered by a timer are also to be considered1.

Let's play with it to understand why 2.5 us spacing between bytes is hard (impossible?) to achieve in HAL

Logic analyzer as elapsed time measuring tool

HAL_GPIO_WritePin(LOGIC_ANALYZER_GPIO_Port, LOGIC_ANALYZER_Pin,	GPIO_PIN_SET);
// Code to measure
HAL_GPIO_WritePin(LOGIC_ANALYZER_GPIO_Port, LOGIC_ANALYZER_Pin,	GPIO_PIN_RESET);

It's convenient but not very useful for sub-microsecond measurements. There is a delay between calling HAL_GPIO_WritePin() and the physical pin reaction.

Debug Watch and Trace (DWT) module2

CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
// Code not to measure
uint32_t t1 = DWT->CYCCNT;
// Code to measure
uint32_t t2 = DWT->CYCCNT;
uint32_t diff21 = t2 - t1;

And now you can correct for the delay introduced by HAL_GPIO_WritePin():

uint32_t t1 = DWT->CYCCNT;
HAL_GPIO_WritePin(LOGIC_ANALYZER_GPIO_Port, LOGIC_ANALYZER_Pin,	GPIO_PIN_SET);
uint32_t t2 = DWT->CYCCNT;
HAL_GPIO_WritePin(LOGIC_ANALYZER_GPIO_Port, LOGIC_ANALYZER_Pin,	GPIO_PIN_RESET);
uint32_t t3 = DWT->CYCCNT;
uint32_t diff21 = t2 - t1;
uint32_t diff32 = t3 - t2;

Obviously, accessing DWT->CYCCNT and storing its value in a variable (memory) is not delayless. Nevertheless, HAL_GPIO_WritePin() is around an order of magnitude slower.

And now compare accessing global vs. local variables:

t1 = DWT->CYCCNT;
t2 = DWT->CYCCNT;

vs.

uint32_t t1 = DWT->CYCCNT;
uint32_t t2 = DWT->CYCCNT;

Surprised?

Be aware (or even beware) of playing too much with DWT->CYCCNT - it may be highly addictive 😎

Delay between invoking HAL_SPI_TransmitReceive() or HAL_SPI_TransmitReceive_IT() and the first SPI CLK edge

Now measure the delay between invoking HAL_SPI_TransmitReceive() or HAL_SPI_TransmitReceive_IT() and the first SPI CLK edge. Eye-opening?

Time needed to reach HAL_TIM_PeriodElapsedCallback() or HAL_SPI_TxRxCpltCallback()

And now measure the delay e.g. between the last CLK edge and reaching HAL_SPI_TxRxCpltCallback(). Eye-opening? See /Assets/Images/ for some of my results.

else if vs. switch() case

Switch is generally faster.

Debugging mode

Remember that pausing the core does not pause/freeze the peripherals by default. If you need to stop also a selected peripheral, you can use macros provided in the library. For example:

#ifdef FREEZE_TIM4
__HAL_DBGMCU_FREEZE_TIM4();
#else
__HAL_DBGMCU_UNFREEZE_TIM4();
#endif
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);

Caution

Freezing the core stops the execution of a control algorithm. Do it only if you are sure what you are doing regarding your particular plant connected in the feedback loop with the uC you are debugging.

The verdict and the hint

What is your verdict regarding the superiority of the non-blocking mode vs. the blocking one? Remember that the safest response is: It depends 😉 The verdict probably will depend on the number of bytes to be received. It may happen that the presented non-blocking mode with all the needed callbacks imposes such an overhead that the blocking part of the non-blocking code is comparable to the blocking solution if the baud rate is high enough.

Missing files?

Don't worry 🙂 Just hit Alt-K to generate /Drivers/CMCIS/ and /Drivers/STM32G4xx_HAL_Driver/ based on the .ioc file. After a couple of seconds your project will be ready for building.

Libraries

Hardware

Tools

  • DSLogic Plus or any other logic analyzer capable of operating faster than HCLK the MCU core is clocked by. "What the eye doesn't see, the heart doesn't grieve over" does not apply here.

Call for action

Create your own home laboratory/workshop/garage! Get inspired by ControllersTech, DroneBot Workshop, Andreas Spiess, GreatScott!, ElectroBOOM, Phil's Lab, atomic14, That Project, Paul McWhorter, and many other professional hobbyists sharing their awesome projects and tutorials! Shout-out/kudos to all of them!

Warning

Control in power electronics and drives - do try this at home ❗

190+ challenges to start from: Control Engineering for Hobbyists at the Warsaw University of Technology.

Stay tuned!

Footnotes

  1. Search: stm32 tim triggered dma

  2. Measuring Code Execution Time on ARM Cortex-M MCUs and STM32 - How to enable DWT Cycle counter

About

Absolute encoder with SPI interface using STM32 and HAL.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published