rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
stm32_pwm.cpp
Go to the documentation of this file.
1/**
2 * @file stm32_common_pwm.cpp
3 * @brief Low level common STM32 code
4 *
5 * @date Mar 28, 2019
6 * @author Andrey Belomutskiy, (c) 2012-2020
7 */
8
9#include "pch.h"
10
11#define _2_MHZ 2'000'000
12
13#if HAL_USE_PWM
14
15namespace {
16struct stm32_pwm_config {
17 PWMDriver* const Driver;
18 const uint8_t Channel;
19 const uint8_t AlternateFunc;
20};
21
22class stm32_hardware_pwm : public hardware_pwm {
23public:
24 bool hasInit() const {
25 return m_driver != nullptr;
26 }
27
28 // 2MHz, 16-bit timer gets us a usable frequency range of 31hz to 10khz
29 static constexpr uint32_t c_timerFrequency = _2_MHZ;
30
31 void start(const char* msg, const stm32_pwm_config& config, float frequency, float duty) {
32 m_driver = config.Driver;
33 m_channel = config.Channel;
34
35 m_period = c_timerFrequency / frequency;
36
37 // These timers are only 16 bit - don't risk overflow
38 if (m_period > 0xFFF0) {
39 firmwareError(ObdCode::CUSTOM_OBD_LOW_FREQUENCY, "PWM Frequency too low %.1f hz on pin \"%s\"", frequency, msg);
40 return;
41 }
42
43 // If we have too few usable bits, we run out of resolution, so don't allow that either.
44 // 200 counts = 0.5% resolution
45 if (m_period < 200) {
46 firmwareError(ObdCode::CUSTOM_OBD_HIGH_FREQUENCY, "PWM Frequency too high %.1f hz on pin \"%s\"", frequency, msg);
47 return;
48 }
49
50 const PWMConfig pwmcfg = {
51 .frequency = c_timerFrequency,
52 .period = m_period,
53 .callback = nullptr,
54 .channels = {
55 {PWM_OUTPUT_ACTIVE_HIGH, nullptr},
56 {PWM_OUTPUT_ACTIVE_HIGH, nullptr},
57 {PWM_OUTPUT_ACTIVE_HIGH, nullptr},
58 {PWM_OUTPUT_ACTIVE_HIGH, nullptr}
59 },
60 .cr2 = 0,
61 .bdtr = 0,
62 .dier = 0,
63 };
64
65 // Start the timer running
66 pwmStart(m_driver, &pwmcfg);
67
68 // Set initial duty cycle
70 }
71
72 void setDuty(float duty) override {
73 if (!m_driver) {
74 criticalError("Attempted to set duty on null hard PWM device");
75 return;
76 }
77
78 pwm_lld_enable_channel(m_driver, m_channel, getHighTime(duty));
79 }
80
81private:
82 PWMDriver* m_driver = nullptr;
83 uint8_t m_channel = 0;
84 uint32_t m_period = 0;
85
86 pwmcnt_t getHighTime(float duty) const {
87 return m_period * duty;
88 }
89};
90}
91
92/**
93 * Could this be unified with getIcuParams() method?
94 */
95static expected<stm32_pwm_config> getConfigForPin(brain_pin_e pin) {
96 switch (pin) {
97#if STM32_PWM_USE_TIM1
98 case Gpio::A8: return stm32_pwm_config{&PWMD1, /*channel*/0, /*AF*/1};
99 case Gpio::A9: return stm32_pwm_config{&PWMD1, /*channel*/1, /*AF*/1};
100 case Gpio::A10: return stm32_pwm_config{&PWMD1, 2, 1};
101 case Gpio::A11: return stm32_pwm_config{&PWMD1, 3, 1};
102
103 case Gpio::E9: return stm32_pwm_config{&PWMD1, 0, 1};
104 case Gpio::E11: return stm32_pwm_config{&PWMD1, 1, 1};
105 case Gpio::E13: return stm32_pwm_config{&PWMD1, 2, 1};
106 case Gpio::E14: return stm32_pwm_config{&PWMD1, 3, 1};
107#endif
108#if STM32_PWM_USE_TIM2
109 case Gpio::A15: return stm32_pwm_config{&PWMD2, 0, 1};
110 case Gpio::B3: return stm32_pwm_config{&PWMD2, 1, 1};
111 case Gpio::B10: return stm32_pwm_config{&PWMD2, 2, 1};
112 case Gpio::B11: return stm32_pwm_config{&PWMD2, 3, 1};
113#endif
114#if STM32_PWM_USE_TIM3
115 case Gpio::B4: return stm32_pwm_config{&PWMD3, 0, 2};
116 case Gpio::B5: return stm32_pwm_config{&PWMD3, 1, 2};
117
118 case Gpio::C6: return stm32_pwm_config{&PWMD3, 0, 2};
119 case Gpio::C7: return stm32_pwm_config{&PWMD3, 1, 2};
120#endif
121#if STM32_PWM_USE_TIM4
122 case Gpio::B6: return stm32_pwm_config{&PWMD4, 0, 2};
123 case Gpio::B7: return stm32_pwm_config{&PWMD4, 1, 2};
124 case Gpio::B8: return stm32_pwm_config{&PWMD4, 2, 2};
125 case Gpio::B9: return stm32_pwm_config{&PWMD4, 3, 2};
126
127 case Gpio::D12: return stm32_pwm_config{&PWMD4, 0, 2};
128 case Gpio::D13: return stm32_pwm_config{&PWMD4, 1, 2};
129 case Gpio::D14: return stm32_pwm_config{&PWMD4, 2, 2};
130 case Gpio::D15: return stm32_pwm_config{&PWMD4, 3, 2};
131#endif
132#if STM32_PWM_USE_TIM5
133 case Gpio::A0: return stm32_pwm_config{&PWMD5, 0, 2};
134 case Gpio::A1: return stm32_pwm_config{&PWMD5, 1, 2};
135 case Gpio::A2: return stm32_pwm_config{&PWMD5, 2, 2};
136 case Gpio::A3: return stm32_pwm_config{&PWMD5, 3, 2};
137#endif
138#if STM32_PWM_USE_TIM8
139
140 /* TIM8 may be used for ADC trigger */
141#ifndef EFI_INTERNAL_FAST_ADC_PWM
142#if !STM32_PWM_USE_TIM3
143 // If TIM3 is not used, put these pins on TIM8 instead..
144 // See https://github.com/rusefi/rusefi/issues/639
145 // See https://github.com/rusefi/rusefi/pull/3032
146 case Gpio::C6: return stm32_pwm_config{&PWMD8, 0, 3};
147 case Gpio::C7: return stm32_pwm_config{&PWMD8, 1, 3};
148#endif
149
150 case Gpio::C8: return stm32_pwm_config{&PWMD8, 2, 3};
151 case Gpio::C9: return stm32_pwm_config{&PWMD8, 3, 3};
152#endif
153#endif
154
155#if STM32_PWM_USE_TIM9
156 case Gpio::E5: return stm32_pwm_config{&PWMD9, 0, 3};
157 case Gpio::E6: return stm32_pwm_config{&PWMD9, 1, 3};
158#endif
159 default: return unexpected;
160 }
161};
162
163static stm32_hardware_pwm hardPwms[5];
164
165stm32_hardware_pwm* getNextPwmDevice() {
166 for (size_t i = 0; i < efi::size(hardPwms); i++) {
167 if (!hardPwms[i].hasInit()) {
168 return &hardPwms[i];
169 }
170 }
171
172 criticalError("Run out of hardware PWM devices!");
173 return nullptr;
174}
175
176/*static*/ hardware_pwm* hardware_pwm::tryInitPin(const char* msg, brain_pin_e pin, float frequencyHz, float duty) {
177 // Hardware PWM can't do very slow PWM - the timer counter is only 16 bits, so at 2MHz counting, that's a minimum of 31hz.
178 if (frequencyHz < 50) {
179 return nullptr;
180 }
181
182 auto cfg = getConfigForPin(pin);
183
184 // This pin can't do hardware PWM
185 if (!cfg) {
186 return nullptr;
187 }
188
189 if (stm32_hardware_pwm* device = getNextPwmDevice()) {
190 device->start(msg, cfg.Value, frequencyHz, duty);
191
192 // Finally connect the timer to physical pin
193 efiSetPadMode(msg, pin, PAL_MODE_ALTERNATE(cfg.Value.AlternateFunc));
194
195 return device;
196 }
197
198 return nullptr;
199}
200#endif /* HAL_USE_PWM */
static const PWMConfig pwmcfg
void efiSetPadMode(const char *msg, brain_pin_e brainPin, iomode_t mode)
static constexpr persistent_config_s * config
void firmwareError(ObdCode code, const char *fmt,...)
PWMDriver PWMD2
PWMD2 driver identifier.
Definition hal_pwm_lld.c:57
void pwm_lld_enable_channel(PWMDriver *pwmp, pwmchannel_t channel, pwmcnt_t width)
Enables a PWM channel.
PWMDriver PWMD1
PWMD1 driver identifier.
Definition hal_pwm_lld.c:49
PWMDriver PWMD4
PWMD4 driver identifier.
Definition hal_pwm_lld.c:73
PWMDriver PWMD3
PWMD3 driver identifier.
Definition hal_pwm_lld.c:65
uint16_t pwmcnt_t
Type of a PWM counter.
static Lps25 device
Definition init_baro.cpp:4
static float frequency
Definition init_flex.cpp:21
@ CUSTOM_OBD_LOW_FREQUENCY
@ CUSTOM_OBD_HIGH_FREQUENCY
brain_pin_e pin
Definition stm32_adc.cpp:15
static expected< stm32_pwm_config > getConfigForPin(brain_pin_e pin)
Definition stm32_pwm.cpp:95
static stm32_hardware_pwm hardPwms[5]
stm32_hardware_pwm * getNextPwmDevice()
Type of a PWM driver configuration structure.
uint32_t frequency
Timer clock in Hz.
Structure representing a PWM driver.
static hardware_pwm * tryInitPin(const char *msg, brain_pin_e pin, float frequencyHz, float duty)
Definition mpu_util.cpp:257
virtual void setDuty(float duty)=0
static float duty