Skip to content

Commit b1ae311

Browse files
author
Richard Unger
committed
add a stm32 LPTIM encoder
1 parent c66b9db commit b1ae311

File tree

4 files changed

+201
-0
lines changed

4 files changed

+201
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include "Arduino.h"
2+
3+
#ifdef HAL_LPTIM_MODULE_ENABLED
4+
5+
6+
// TODO which models does this apply to? the entire series, or the line?
7+
#if defined(STM32G431KB) || defined(STM32G431CB)
8+
WEAK const PinMap PinMap_LPTIM[] = {
9+
{PB_5, LPTIM1, STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF11_LPTIM1, 0, 0)}, // LPTIM1_CH1
10+
{PB_7, LPTIM1, STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF11_LPTIM1, 1, 0)}, // LPTIM1_CH2
11+
{NC, NP, 0}
12+
};
13+
WEAK const PinMap PinMap_LPTIMETR[] = {
14+
{PB_6, LPTIM1, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF11_LPTIM1)}, // LPTIM1_ETR
15+
{NC, NP, 0}
16+
};
17+
#endif
18+
19+
20+
#if defined(STM32G473QE) || defined(STM32G431VB)
21+
WEAK const PinMap PinMap_LPTIM[] = {
22+
{PB_5, LPTIM1, STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF11_LPTIM1, 0, 0)}, // LPTIM1_CH1
23+
{PB_7, LPTIM1, STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF11_LPTIM1, 1, 0)}, // LPTIM1_CH2
24+
{PC_0, LPTIM1, STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF1_LPTIM1, 0, 0)}, // LPTIM1_CH1
25+
{PC_2, LPTIM1, STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF1_LPTIM1, 1, 0)}, // LPTIM1_CH2
26+
{NC, NP, 0}
27+
};
28+
WEAK const PinMap PinMap_LPTIMETR[] = {
29+
{PB_6, LPTIM1, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF11_LPTIM1)}, // LPTIM1_ETR
30+
{PC_3, LPTIM1, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF1_LPTIM1)}, // LPTIM1_ETR
31+
{NC, NP, 0}
32+
};
33+
#endif
34+
35+
36+
37+
38+
#endif // HAL_LPTIM_MODULE_ENABLED
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
# STM32 Low Power Encoder driver
3+
4+
This is a driver for the encoder mode of STM32 MCU LPTIM timers.
5+
6+
:warning: not yet tested
7+
8+
The LPTIM timers are low power timers available on some STM32 MCUs. Some LPTIM timers have an encoder mode (typically LPTIM1 and LPTIM2). Using this encoder class has zero MCU overhead as the hardware handles all the encoder counting. There is also no need for interrupt handlers, interrupts are not used. However, you must be sure to call `encoder.update()` frequently (at least 2x per revolution) to correctly keep track of the full rotations.
9+
10+
In theory the LPTIM timers can keep counting when the MCU is in sleep mode, but in practice tracking the full rotations would be difficult to achieve.
11+
12+
## Compatibility
13+
14+
Unfortunately, Arduino framework (stm32duino) provides no support for low power timers, and there is no "PinMap" to identify which pins are LPTIM outputs.
15+
16+
If you want to use this driver, your MCU has to be part of the custom `PinMap_LPTIM.c` file used by this driver. Currently, we've added the following MCUs:
17+
18+
- STM32G431KB
19+
- STM32G431CB
20+
- STM32G473QE
21+
- STM32G431VB
22+
23+
Adding MCUs is not hard if you look at the format [in the file](./PinMap_LPTIM.c), and pull requests for other MCUs would be gladly accepted.
24+
25+
## Usage
26+
27+
Like all our encoders, you must specify the PPR (pulses per revolution). This number should come from your encoder's datasheet. Note: internally, the encoder will use a 4x higher CPR (counts per revolution) than the PPR value you provide.
28+
29+
You may need to enable the LPTIM in stm32duino:
30+
31+
```
32+
-DHAL_LPTIM_MODULE_ENABLED
33+
```
34+
35+
Its easy to use:
36+
37+
```c++
38+
STM32LowPowerEncoder encoder = STM32LowPowerEncoder(4096, PB5, PB7); // 4096 is the PPR
39+
40+
vpid setup() {
41+
...
42+
encoder.init();
43+
motor.linkSensor(&encoder);
44+
...
45+
}
46+
```
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
2+
3+
#include "./STM32LowPowerEncoder.h"
4+
5+
#if defined(_STM32_DEF_) && defined(HAL_LPTIM_MODULE_ENABLED)
6+
7+
8+
extern const PinMap PinMap_LPTIM[];
9+
extern const PinMap PinMap_LPTIMETR[];
10+
11+
12+
STM32LowPowerEncoder::STM32LowPowerEncoder(unsigned int ppr, int pinA, int pinB, int pinI){
13+
cpr = ppr * 4; // 4x for quadrature
14+
_pinA = digitalPinToPinName(pinA);
15+
_pinB = digitalPinToPinName(pinB);
16+
_pinI = digitalPinToPinName(pinI);
17+
};
18+
19+
void STM32LowPowerEncoder::init(){
20+
_encoder_handle.Instance = (LPTIM_TypeDef*)pinmap_peripheral(_pinA, PinMap_LPTIM);
21+
if (!IS_LPTIM_ENCODER_INSTANCE(_encoder_handle.Instance)) {
22+
initialized = false;
23+
return;
24+
}
25+
LPTIM_TypeDef* inst = (LPTIM_TypeDef*)pinmap_peripheral(_pinA, PinMap_LPTIM);
26+
if (inst!=_encoder_handle.Instance) { // check the other pins are on the same timer
27+
initialized = false;
28+
return;
29+
}
30+
if (_pinI!=NC) {
31+
inst = (LPTIM_TypeDef*)pinmap_peripheral(_pinI, PinMap_LPTIMETR);
32+
if (inst!=_encoder_handle.Instance) {
33+
initialized = false;
34+
return;
35+
}
36+
}
37+
_encoder_handle.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC;
38+
_encoder_handle.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1;
39+
_encoder_handle.Init.UltraLowPowerClock.Polarity = LPTIM_CLOCKPOLARITY_RISING_FALLING;
40+
_encoder_handle.Init.UltraLowPowerClock.SampleTime = LPTIM_CLOCKSAMPLETIME_DIRECTTRANSITION;
41+
if (_pinI==NC)
42+
_encoder_handle.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
43+
else {
44+
_encoder_handle.Init.Trigger.Source = LPTIM_TRIGSOURCE_0;
45+
_encoder_handle.Init.Trigger.ActiveEdge = LPTIM_ACTIVEEDGE_RISING;
46+
_encoder_handle.Init.Trigger.SampleTime = LPTIM_TRIGSAMPLETIME_DIRECTTRANSITION;
47+
}
48+
_encoder_handle.Init.OutputPolarity = LPTIM_OUTPUTPOLARITY_HIGH;
49+
_encoder_handle.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
50+
_encoder_handle.Init.CounterSource = LPTIM_COUNTERSOURCE_EXTERNAL;
51+
_encoder_handle.Init.Input1Source = LPTIM_INPUT1SOURCE_GPIO;
52+
_encoder_handle.Init.Input2Source = LPTIM_INPUT2SOURCE_GPIO;
53+
if (HAL_LPTIM_Init(&_encoder_handle) != HAL_OK)
54+
initialized = false;
55+
else {
56+
if (HAL_LPTIM_Encoder_Start(&_encoder_handle, cpr) != HAL_OK)
57+
initialized = false;
58+
else {
59+
pinmap_pinout(_pinA, PinMap_LPTIM);
60+
pinmap_pinout(_pinB, PinMap_LPTIM);
61+
if (_pinI != NC)
62+
pinmap_pinout(_pinI, PinMap_LPTIMETR);
63+
initialized = true;
64+
}
65+
}
66+
};
67+
68+
69+
int STM32LowPowerEncoder::needsSearch(){
70+
return false;
71+
};
72+
73+
74+
int STM32LowPowerEncoder::hasIndex(){
75+
return 0; // TODO: implement index
76+
};
77+
78+
79+
80+
float STM32LowPowerEncoder::getSensorAngle(){
81+
return _2PI * (_encoder_handle.Instance->CNT&0xFFFFUL) / (float)cpr;
82+
};
83+
84+
#endif
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
#include "Arduino.h"
3+
4+
5+
#if defined(_STM32_DEF_) && defined(HAL_LPTIM_MODULE_ENABLED)
6+
7+
#include "common/base_classes/Sensor.h"
8+
#include "common/foc_utils.h"
9+
10+
class STM32LowPowerEncoder : public Sensor {
11+
public:
12+
/**
13+
Encoder class constructor
14+
@param ppr impulses per rotation (cpr=ppr*4)
15+
*/
16+
explicit STM32LowPowerEncoder(unsigned int ppr, int pinA, int pinB, int pinI=-1);
17+
18+
void init() override;
19+
int needsSearch() override;
20+
int hasIndex(); // !< function returning 1 if encoder has index pin and 0 if not.
21+
22+
bool initialized = false;
23+
uint32_t cpr; //!< encoder cpr number
24+
PinName _pinA, _pinB, _pinI;
25+
26+
protected:
27+
float getSensorAngle() override;
28+
29+
LPTIM_HandleTypeDef _encoder_handle;
30+
31+
};
32+
33+
#endif

0 commit comments

Comments
 (0)