rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
osc_detector.cpp
Go to the documentation of this file.
1/**
2 * @file osc_detector.cpp
3 * @brief This logic automatically detects the speed of the
4 * oscillator or crystal connected to HSE.
5 * @date 12 July 2021
6 *
7 * It works by first using the reasonably-precise HSI oscillator (16MHz) to measure LSI (nominally 32khz, but wide tolerance).
8 * Then, it switches the system clock source to HSE, and repeats the same measurement. The inaccurate LSI will not drift
9 * significantly in the short period of time between these two measurements, so use it as a transfer standard to compare the speed
10 * of HSI and HSE. The ratio between the measured speed of LSI when running on HSE vs. HSI will give the ratio of speeds of HSE
11 * and HSI themselves. Since we know the value of HSI (16mhz), we can compute the speed of HSE.
12 *
13 * Lastly, the PLL is reconfigured to use the correct input divider such that the input frequency is 1MHz
14 * (PLLM is set to N for an N-MHz HSE crystal).
15 */
16
17#ifdef __cplusplus
18#include "pch.h"
19#endif
20
21#if ENABLE_AUTO_DETECT_HSE
22
25
26#ifdef STM32H7XX
27#define TIMER TIM17
28#else // not H7
29#define TIMER TIM11
30#endif
31
32static uint32_t getOneCapture() {
33 // wait for input capture
34 while ((TIMER->SR & TIM_SR_CC1IF) == 0);
35
36 // Return captured count
37 return TIMER->CCR1;
38}
39
40static uint32_t getTimerCounts(size_t count) {
41 // Burn one count
43
44 uint32_t firstCapture = getOneCapture();
45 uint32_t lastCapture = 0;
46
47 for (size_t i = 0; i < count; i++)
48 {
49 lastCapture = getOneCapture();
50 }
51
52 return lastCapture - firstCapture;
53}
54
55#ifdef __cplusplus
56// These clocks must all be enabled for this to work
57static_assert(STM32_HSI_ENABLED);
58static_assert(STM32_HSE_ENABLED);
59#endif
60
61#ifdef STM32H7XX
62static const float rtcpreDivider = 63;
63
64static void enableTimer() {
65 RCC->APB2ENR |= RCC_APB2ENR_TIM17EN;
66}
67
68static void disableTimer() {
69 RCC->APB2ENR &= RCC_APB2ENR_TIM17EN;
70}
71
72static void reprogramPll(uint8_t roundedHseMhz) {
73 // Switch system clock to HSI to configure PLL (SW = 0)
74 RCC->CFGR &= ~RCC_CFGR_SW;
75
76 // Stop all 3 PLLs
77 RCC->CR &= ~(RCC_CR_PLL1ON | RCC_CR_PLL2ON | RCC_CR_PLL3ON);
78
79 // H7 is configured for 2MHz input to PLL
80 auto pllm = roundedHseMhz / 2;
81
82 // Set PLLM for all 3 PLLs to the new value, and select HSE as the clock source
83 RCC->PLLCKSELR =
84 pllm << RCC_PLLCKSELR_DIVM1_Pos |
85 pllm << RCC_PLLCKSELR_DIVM2_Pos |
86 pllm << RCC_PLLCKSELR_DIVM3_Pos |
87 RCC_PLLCKSELR_PLLSRC_HSE;
88
89 // Enable PLLs
90 RCC->CR |= RCC_CR_PLL1ON | RCC_CR_PLL2ON | RCC_CR_PLL3ON;
91
92 // Wait for PLLs to lock
93 auto readyMask = RCC_CR_PLL1RDY | RCC_CR_PLL2RDY | RCC_CR_PLL3RDY;
94 while ((RCC->CR & readyMask) != readyMask) ;
95
96 // Switch system clock source back to PLL
97 RCC->CFGR |= RCC_CFGR_SW_PLL1;
98}
99#else // not STM32H7
100
101static const float rtcpreDivider = 31;
102
103#ifdef __cplusplus
104// This only works if you're using the PLL as the configured clock source!
105static_assert(STM32_SW == RCC_CFGR_SW_PLL);
106#endif
107
108static void enableTimer() {
109 RCC->APB2ENR |= RCC_APB2ENR_TIM11EN;
110}
111
112static void disableTimer() {
113 RCC->APB2ENR &= ~RCC_APB2ENR_TIM11EN;
114}
115
116static void reprogramPll(uint8_t roundedHseMhz) {
117 // Switch back to HSI to configure PLL
118 // clear SW to use HSI
119 RCC->CFGR &= ~RCC_CFGR_SW;
120
121 // Stop the PLL
122 RCC->CR &= ~RCC_CR_PLLON;
123
124 // Mask out the old PLLM and PLLSRC
125 RCC->PLLCFGR &= ~(RCC_PLLCFGR_PLLM_Msk | RCC_PLLCFGR_PLLSRC_Msk);
126
127 // Stick in the new PLLM value
128 RCC->PLLCFGR |= (roundedHseMhz << RCC_PLLCFGR_PLLM_Pos) & RCC_PLLCFGR_PLLM_Msk;
129 // Set PLLSRC to HSE
130 RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE;
131
132 // Reenable PLL, wait for lock
133 RCC->CR |= RCC_CR_PLLON;
134 while (!(RCC->CR & RCC_CR_PLLRDY));
135
136 // Switch clock source back to PLL
137 RCC->CFGR &= ~RCC_CFGR_SW;
138 RCC->CFGR |= RCC_CFGR_SW_PLL;
139 while ((RCC->CFGR & RCC_CFGR_SWS) != (RCC_CFGR_SW_PLL << RCC_CFGR_SWS_Pos));
140}
141#endif
142
143#ifdef __cplusplus
144// __late_init runs after bss/zero initialization, but before static constructors and main
145extern "C" void __late_init() {
146#else
147void OscDetector() {
148#endif
149 // Set RTCPRE to /31 - just set all the bits
150 RCC->CFGR |= RCC_CFGR_RTCPRE_Msk;
151
152 // Turn on timer
153 enableTimer();
154
155 // Remap to connect HSERTC to CH1
156#ifdef STM32H7XX
157 // TI1SEL = 2, HSE_1MHz
158 TIMER->TISEL = TIM_TISEL_TI1SEL_1;
159#elif defined(STM32F4XX)
160 TIMER->OR = TIM_OR_TI1_RMP_1;
161#else
162 // the definition has a different name on F7 for whatever reason
163 TIMER->OR = TIM11_OR_TI1_RMP_1;
164#endif
165
166 // Enable capture on channel 1
167 TIMER->CCMR1 = TIM_CCMR1_CC1S_0;
168 TIMER->CCER = TIM_CCER_CC1E;
169
170 // Start timer
171 TIMER->CR1 |= TIM_CR1_CEN;
172
173 // Measure HSE against SYSCLK
174 uint32_t hseCounts = getTimerCounts(10);
175
176 // Turn off timer now that we're done with it
177 disableTimer();
178
179 float hseFrequencyHz = 10 * rtcpreDivider * STM32_TIMCLK2 / hseCounts;
180 hseFrequencyMhz = hseFrequencyHz / 1e6;
181 autoDetectedRoundedMhz = ((int)hseFrequencyHz + (1e6 / 2)) / 1e6;
182
184}
185
186#endif // defined ENABLE_AUTO_DETECT_HSE
static void reprogramPll(uint8_t roundedHseMhz)
static void enableTimer()
void __late_init()
static const float rtcpreDivider
static uint32_t getOneCapture()
static uint32_t getTimerCounts(size_t count)
float hseFrequencyMhz
uint8_t autoDetectedRoundedMhz
static void disableTimer()
uint16_t count
Definition tunerstudio.h:1