1+ #ifndef KLIB_STM_STM32F4XX_SYSTEM_HPP
2+ #define KLIB_STM_STM32F4XX_SYSTEM_HPP
3+
4+ #include < klib/klib.hpp>
5+ #include < klib/io/core_clock.hpp>
6+
7+ namespace klib ::core::stm32f4xx::io::system {
8+ class flash {
9+ public:
10+ /* *
11+ * @brief Set the amount of waitstates used for accessing flash
12+ *
13+ * @details
14+ * 0 = for 0 to 30Mhz
15+ * 1 = for 30 to 60Mhz
16+ * 2 = for 60 to 90Mhz
17+ * 3 = for 90 to 120Mhz
18+ * 4 = for 120 to 150Mhz
19+ * 5 = for 150 to 180Mhz
20+ *
21+ * Note: for voltage ranges of 2.7V - 3.6V
22+ *
23+ * More info can be found in the datasheet at page 81
24+ *
25+ * @tparam WaitState
26+ * @tparam EnablePrefetch
27+ * @tparam EnableInstructionCache
28+ * @tparam EnableDataCache
29+ */
30+ template <uint8_t WaitState, bool EnablePrefetch, bool EnableInstructionCache, bool EnableDataCache>
31+ static void setup () {
32+ // check the wait state from the user
33+ static_assert (WaitState <= 15 , " Max wait cycles is 15" );
34+
35+ // clear the wait state bits and set the new wait state
36+ FLASH->ACR = (
37+ (FLASH->ACR & (~0x70F )) | WaitState | (EnablePrefetch << 8 ) |
38+ (EnableInstructionCache << 9 ) | (EnableDataCache << 10 )
39+ );
40+ }
41+ };
42+
43+ class crystal {
44+ public:
45+ /* *
46+ * @brief Available clock sources for the system clock
47+ *
48+ */
49+ enum class source {
50+ internal = 0 ,
51+ external = 1 ,
52+ };
53+
54+ /* *
55+ * @brief Returns if the requested crystal is enabled
56+ *
57+ * @tparam Source
58+ * @return true
59+ * @return false
60+ */
61+ template <source Source>
62+ static bool is_enabled () {
63+ // calculate the bit to check
64+ constexpr static uint32_t bit = (Source == source::internal) ? 0 : 16 ;
65+
66+ // return the status of the bit
67+ return RCC->CR & (0x1 << bit);
68+ }
69+
70+ /* *
71+ * @brief Returns if the requested crystal is stable
72+ *
73+ * @tparam Source
74+ * @return true
75+ * @return false
76+ */
77+ template <source Source>
78+ static bool is_stable () {
79+ // calculate the bit to check
80+ constexpr static uint32_t bit = (Source == source::internal) ? 1 : 17 ;
81+
82+ // return the status of the bit
83+ return RCC->CR & (0x1 << bit);
84+ }
85+
86+ /* *
87+ * @brief Enable or disable the requested crystal
88+ *
89+ * @tparam Source
90+ * @tparam Enable
91+ */
92+ template <source Source, bool Enable>
93+ static void enable () {
94+ // calculate the bit to check
95+ constexpr static uint32_t bit = (Source == source::internal) ? 0 : 16 ;
96+
97+ // enable the crystal by setting the requested source bit
98+ if constexpr (Enable) {
99+ RCC->CR |= (0x1 << bit);
100+ }
101+ else {
102+ RCC->CR &= ~(0x1 << bit);
103+ }
104+ }
105+ };
106+
107+ class clock {
108+ public:
109+ /* *
110+ * @brief Available plls on the chip
111+ *
112+ */
113+ enum class pll {
114+ main = 0 ,
115+ plli2s = 1 ,
116+ pllsai = 2
117+ };
118+
119+ /* *
120+ * @brief Available clock sources for the system clock
121+ *
122+ */
123+ enum class sysclock_source {
124+ internal = 0 ,
125+ external = 1 ,
126+ pll = 2 ,
127+ };
128+
129+ /* *
130+ * @brief Get the current sysclock source
131+ *
132+ * @return sysclock_source
133+ */
134+ static sysclock_source get_sysclock_source () {
135+ return static_cast <sysclock_source>((RCC->CFGR >> 2 ) & 0x3 );
136+ }
137+
138+ /* *
139+ * @brief Set the sysclock source
140+ *
141+ * @note the clock needs to be stable before switching to it
142+ *
143+ * @param source
144+ */
145+ template <sysclock_source Source>
146+ static void set_sysclock_source () {
147+ // set the new source
148+ RCC->CFGR = (RCC->CFGR & ~(0x3 )) | static_cast <uint32_t >(Source);
149+ }
150+
151+ template <pll Pll, bool Enable>
152+ static void enable () {
153+ // calculate the bit to set or clear
154+ constexpr static uint32_t bit = 24 + (static_cast <uint32_t >(Pll) * 2 );
155+
156+ if constexpr (Enable) {
157+ RCC->CR |= (0x1 << bit);
158+ }
159+ else {
160+ RCC->CR &= ~(0x1 << bit);
161+ }
162+ }
163+
164+ template <pll Pll>
165+ static bool is_locked () {
166+ // calculate the bit to check
167+ constexpr static uint32_t bit = 25 + (static_cast <uint32_t >(Pll) * 2 );
168+
169+ // return if the pll is locked by checking the ready flag
170+ return RCC->CR & (0x1 << bit);
171+ }
172+
173+ /* *
174+ * @brief Set the main pll to the specified frequency and source
175+ *
176+ * @tparam Source
177+ * @tparam Freq
178+ * @tparam Multiplier
179+ * @tparam PreDivider
180+ * @tparam Div
181+ * @tparam UsbSdRngDiv
182+ * @tparam DsiDiv
183+ */
184+ template <crystal::source Source, uint32_t Freq, uint16_t Multiplier, uint8_t PreDivider, uint8_t Div, uint8_t UsbSdRngDiv = 2 , uint8_t DsiDiv = 2 >
185+ static void set_main () {
186+ // check if the requested source is stable and enabled
187+ if (!crystal::is_enabled<Source>()) {
188+ crystal::enable<Source, true >();
189+
190+ // wait until the crystal is stable
191+ while (!crystal::is_stable<Source>()) {
192+ // do nothing
193+ }
194+ }
195+
196+ // check if we need to use the pll or not
197+ if constexpr (Multiplier > 1 ) {
198+ // we need to enable the pll. Check everything is valid before enabling it
199+ static_assert (Multiplier >= 50 && Multiplier <= 432 , " Invalid multiplier" );
200+ static_assert (PreDivider >= 2 && PreDivider <= 63 , " Invalid pre-divider" );
201+ static_assert (Div == 2 || Div == 4 || Div == 6 || Div == 8 , " Invalid divider" );
202+ static_assert (UsbSdRngDiv >= 2 && UsbSdRngDiv <= 7 , " Invalid USB/SD/RNG divider" );
203+ static_assert (DsiDiv >= 2 && DsiDiv <= 15 , " Invalid DSI divider" );
204+
205+ // calculate the vco input frequency
206+ constexpr static uint32_t vco_input_freq = Freq / PreDivider;
207+
208+ static_assert (
209+ vco_input_freq >= 1'000'000 && vco_input_freq <= 2'000'000 ,
210+ " Invalid vco input frequency. Freq / PreDivider must be between 1 and 2mHz to limit pll jitter"
211+ );
212+
213+ // calculate the vco frequency using the oscilator and multiplier
214+ //
215+ // Note: we use the formula (Freq * Multiplier) / PreDivider
216+ // instead of (Freq / PreDivider) * Multiplier to avoid overflow
217+ // when calculating the vco frequency
218+ constexpr static uint32_t partial_freq = (Freq * Multiplier) / PreDivider;
219+
220+ static_assert (
221+ (Multiplier == 1 ) || (partial_freq >= 100'000'000 && partial_freq <= 432'000'000 ),
222+ " Invalid vco output. PLL frequency needs to be between 100 and 432mhz"
223+ );
224+
225+ // calculate the final frequency
226+ constexpr static uint32_t freq = (partial_freq / Div);
227+
228+ // disconnect the main pll if it is connected.
229+ if (get_sysclock_source () == sysclock_source::pll) {
230+ // get the current source of the pll
231+ const auto source = static_cast <crystal::source>((RCC->PLLCFGR >> 22 ) & 0x1 );
232+
233+ // check where we need to switch back to before
234+ // reconfiguring the main PLL
235+ if (source == crystal::source::external) {
236+ set_sysclock_source<sysclock_source::external>();
237+ }
238+ else {
239+ set_sysclock_source<sysclock_source::internal>();
240+ }
241+ }
242+
243+ // disable the pll
244+ enable<pll::main, false >();
245+
246+ // wait until the pll is disabled
247+ while (is_locked<pll::main>()) {
248+ // do nothing
249+ }
250+
251+ // set the new configuration for the pll
252+ RCC->PLLCFGR = (
253+ (DsiDiv << 28 ) | (UsbSdRngDiv << 24 ) | (static_cast <uint32_t >(Source) << 22 ) |
254+ ((((Div / 2 ) - 1 ) << 16 ) | (Multiplier << 6 ) | PreDivider)
255+ );
256+
257+ // enable the pll
258+ enable<pll::main, true >();
259+
260+ // wait until the pll is locked
261+ while (!is_locked<pll::main>()) {
262+ // do nothing
263+ }
264+
265+ // switch to the pll
266+ set_sysclock_source<sysclock_source::pll>();
267+
268+ // notify klib what freqency we are running
269+ klib::io::clock::set (freq);
270+ }
271+ else {
272+ // we don't need to enable the pll, just switch to the new source
273+ if constexpr (Source == crystal::source::external) {
274+ set_sysclock_source<sysclock_source::external>();
275+ }
276+ else {
277+ static_assert (
278+ Freq == 16'000'000 , " Internal oscillator frequency is fixed at 16Mhz"
279+ );
280+
281+ set_sysclock_source<sysclock_source::internal>();
282+ }
283+
284+ klib::io::clock::set (Freq);
285+ }
286+
287+ // clear APB1 and APB2 dividers to 0 so they are not divided
288+ RCC->CFGR &= ~((0x7 << 10 ) | (0x7 << 13 ));
289+ }
290+ };
291+ }
292+
293+ #endif
0 commit comments