Happiness
Sunday, July 12, 2026
Saturday, July 4, 2026
STM32 Development Log 8: State flow diagram, July 4 saturday.
Active Magnetic Bearing Controller
Software Architecture and Function Reference
main.c. It documents the overall architecture, state machine, module interactions, and the principal functions. It is suitable as a Blogger article and can be extended as the project evolves.
1. Overview
The firmware implements a closed-loop Active Magnetic Bearing (AMB) controller on an STM32H753. The software controls five magnetic bearing axes using AD7606 ADCs, an AD5361 16-channel DAC, two SH1106 OLED displays, and a finite state machine for safe operation and parameter tuning.
2. Hardware Interfaces
| Peripheral | Purpose |
|---|---|
| SPI2 | AD7606 ADC acquisition |
| SPI6 | AD5361 DAC output |
| I2C2/I2C3 | Dual SH1106 OLED displays |
| TIM14 | 300 µs control loop scheduler |
| TIM13 | 250 ms heartbeat, menu scan and long-press detection |
| GPIO EXTI | Immediate state transitions |
3. Software Architecture
main()
|
------------------------
| |
Hardware Init Variable Init
| |
-----------+------------
|
SAFE_MAIN_MENU
|
+-----------+-----------+
| |
TIM13 Interrupt Main Loop
| |
| OLED Update
|
Menu Navigation
|
+-----> RUN_STATE
|
TIM14 Interrupt
|
Read ADC -> Control -> DAC
4. Finite State Machine
SAFE_MAIN_MENU
|
+--> Axis Menu
|
+--> Parameter Menu
|
+--> Parameter Edit
SAFE_MAIN_MENU
|
+--> Orbit
|
+--> Manual
|
+--> ADC Display
|
+--> PA Gain
Long SW4
|
V
RUN_STATE
SW1 during RUN
|
V
SAFE_MAIN_MENU
5. Main Program Flow
- HAL initialization
- Clock configuration
- Peripheral initialization
- SPI speed configuration
- Timer configuration
- OLED initialization
- DAC reset and initialization
- ADC reset
- Controller parameter initialization
- Enter infinite loop
6. Function Call Hierarchy
main()
├── initializeAxisControlValueDefaults()
├── write16valuesToDac5361_v2()
├── HAL_TIM_Base_Start_IT()
└── while()
├── displayRunState()
└── displaySafeState()
TIM13 ISR
├── readAdc1Spi3BrdSpi2StmCh8ToCh1()
├── tuneParameterFromPot1()
├── updateSafeMenuSelection()
├── initializeControllerStates()
├── controlUpdateCompute()
└── write16valuesToDac5361_v2()
TIM14 ISR
├── readAdc1Spi3BrdSpi2StmCh8ToCh1()
├── controlUpdateCompute()
└── write16valuesToDac5361_v2()
controlUpdateCompute()
└── updateOneAxis()
├── updateErrorHistory()
└── PD_Compute()
7. Major Functions
| Function | Description |
|---|---|
| initializeAxisControlValueDefaults() | Loads default controller gains and bias currents. |
| initializeControllerStates() | Synchronizes controller history before entering RUN. |
| processSwInp1interrPress() | Processes FSM transitions from SW1. |
| enterParameterEdit() | Loads parameter limits and current value. |
| updateSafeMenuSelection() | Maps POT1 voltage into menu indices. |
| controlUpdateCompute() | Updates all five AMB axes. |
| updateOneAxis() | Performs one axis position-control update. |
| PD_Compute() | Computes proportional-derivative controller output. |
| write16valuesToDac5361_v2() | Writes all 16 DAC channels then pulses LDAC. |
| readAdc1Spi3BrdSpi2StmCh8ToCh1() | Reads eight ADC channels from AD7606. |
| displayRunState() | Displays live controller status. |
| displaySafeState() | Dispatches menu drawing based on FSM state. |
| drawMainMenu() | Main safe-state menu. |
| drawAxisMenu() | Axis selection screen. |
| drawParameterMenu() | Displays parameter list and current values. |
| drawParameterEdit() | Interactive parameter tuning screen. |
| saveEditedParameter() | Stores edited parameter into the selected axis. |
8. Control Loop
Sensor mV | sensorGain | Position (mm) | Reference | Error | PD Controller | Bias Current | Current-to-Voltage Gain | DAC Voltage | Power Amplifier
9. Safety Features
- System always powers up in SAFE mode.
- RUN mode requires a continuous 3-second SW4 press.
- SW1 immediately exits RUN mode.
- DAC outputs are forced to ground in SAFE mode using the AD5361 CLEAR input.
- Controller histories are initialized before closed-loop operation.
10. Future Extensions
- Orbit control implementation.
- Manual actuator drive mode.
- PA gain calibration.
- Derivative filtering.
- Integral control.
- Persistent parameter storage in Flash.
- DMA-based ADC and DAC transfers.
Sunday, June 28, 2026
STM32 Development Log 7: TIM14 TIM13 priority setup
GPIO
| Button | Method |
|---|
| PF10 | EXTI |
| PC0 | EXTI |
| PC1 | EXTI |
| PA0 | Poll every 5–10 ms using some timer or leave. Cant interrupt using both PC1 and PA0 |
| PH2 | EXTI |
| PH3 | EXTI |
TIMER13 for LED
TIMER 14
Sunday, June 21, 2026
STM32 Development Log 6 : ADC sampling not working
ADC 1 and 2 pins for spi 3 (board) SPI2 in the MCU:
Base board side:
Controller side
Original SPI2 config from SPI-ADC-DAC demo:
PI1 PI2 as clock and MISO for SPI2
| ADC | IC | Channels |
|---|---|---|
| ADC1 | ADS7606 (U9) | AN0–AN7 |
| ADC2 | ADS7606 (U10) | AN8–AN15 |
All 8 channels on each ADS7606 are sampled simultaneously Low pass filter at the input line of each adc channel:
|
| Pin | Purpose |
|---|---|
| PI8 | ADC1 conversion trigger (CONVST) |
| PI11 | ADC1 BUSY signal |
| PI9 | ADC2 conversion trigger (CONVST) |
| PI10 | ADC2 BUSY signal |
| PI0 | ADC1 SPI chip select |
| PI3 | ADC2 SPI chip select |
STM32 Development Log: Timer settings
Always backup main.c before generating code.
HCLK = 200 MHz
- D2PPRE1 = /2, again multiplied by 2 for APB1 Timer Clock , APB1 Timer Clock alone is back to 200
- APB1 Peripheral Clock = 100 MHz, APB1 Peripheral Clock is not multiplied by 2, it is 100 only
- APB1 Timer Clock = 200 MHz
- Prescaler = 199, Counter Clock = 1 MHz, Auto Reload (Period) = 199
- 200e6/200 prescaler = 1e6, 1e6/200 prescaler = 5000 Hz
- control loop Period = 200 µs, update Frequency = 5000 Hz
Enable interrupts from timer 14
Clock configuration
Saturday, June 20, 2026
STM32 Development Log 5: Current consumption
When running the Switches_LED_Demo program,]
Current consumption increase by 100mA when SW3 is pressed.
- When only heart beat LED is running, 0.179 Ampere @12V
- SW1:With LD5 on 0.187 Ampere = 8 mA increase over case 1
- SW2:With LD4 on 0.195 Ampere = 8 mA increase over case 2
- SW3:With LD3 on 0.305 Ampere = 110 mA increase over case 3 Why? SW3 switches PI14 and PI15. PI14 is LD3. PI15 is J3 connector, pin 38, switching relay 1. However this cannot add 100mA to the current consumption.
After disable PI15 relay output in software, the current consumption is correct:
if(button3 == 1)
{
if(button3_state == 0)
{
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_14, GPIO_PIN_SET);
button3_state = 1;
// PI 15 is relay 1
// This code is for testing only
// HAL_GPIO_WritePin(GPIOI, GPIO_PIN_15, GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_14, GPIO_PIN_RESET);
button3_state = 0;
// PI 15 is relay 1
// This code is for testing only
// HAL_GPIO_WritePin(GPIOI, GPIO_PIN_15, GPIO_PIN_RESET);
}
button3 = 0 ;
}
Comments
- LD2 , triggered by SW4, PA0 , needs long press for the LED to switch states, it is not interrupt driven, it is polled.
- LD1, triggered by SW5, increases current consumption by only 3mA
- SW_LD10, triggered by SW6, increases current consumption by only 4mA, SW_LD10 is physically located on the compute module, not on the base board.
STM32 Development Log 4: Heart beat LED
System clock setting:
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 50;
RCC_OscInitStruct.PLL.PLLP = 2;
Timer duration set in tim.c
/* USER CODE END TIM13_Init 1 */
htim13.Instance = TIM13;
htim13.Init.Prescaler = 9999;
htim13.Init.CounterMode = TIM_COUNTERMODE_UP;
htim13.Init.Period = 9999;
htim13.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;
htim13.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
Interrupt Frequency =
TIM13 Clock / ((Prescaler + 1) × (Period + 1))
PF2 Toggle code in main.c:
if(htim->Instance == TIM13)
{
if(heart_beat == 0)
{
heart_beat = 1 ;
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_2, GPIO_PIN_SET);
}
else
{
heart_beat = 0 ;
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_2, GPIO_PIN_RESET);
}
}
