You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+1-5Lines changed: 1 addition & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -38,16 +38,12 @@ We'll get into details about this board in the next lesson.
38
38
39
39
You probably already have one if you've been playing with Arduinos before. If you don't, go on ebay and search [CP2102](https://www.ebay.com/sch/i.html?_from=R40&_nkw=cp2102). Of course other chips like CH340 or FTDI also works. It's just a matter of preference.
40
40
41
-
##How to use this guide
41
+
### Get the files
42
42
43
43
Click "Download ZIP" on the top right corner to get lesson files, then just follow along on this webpage.
44
44
45
45

46
46
47
-
If STM32CubeMX asks you what to do when opening `.ioc` files, always pick `Migrate`:
48
-
49
-

50
-
51
47
## What now?
52
48
53
49
If you can't wait to get going, click a lesson below and get started!
Copy file name to clipboardExpand all lines: lesson0_intro_blinkLED/README.md
+10-2Lines changed: 10 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -16,7 +16,7 @@
16
16
17
17
In this lesson we're going to take a detailed look at the chip and the dev board, learn how to hook it up to the programmer, install required softwares, set up the microcontroller, and finally write our very own "Blink" program!
18
18
19
-
That's quite a bit of work, and it's going to take a while. However, this is the most important lesson of them all since it walks you through the entire process in detail. Once you persevere, you can use it on any other STM32 variant you want.
19
+
Bit of warning before we start: This particular lesson is quite a bit of work, and it's going to take a while. However, this is the most important lesson of them all since it walks you through the entire process in detail. Once you persevere, you can use it on any other STM32 variant you want.
20
20
21
21
## The Chip
22
22
@@ -600,10 +600,18 @@ I mentioned before that Keil MDK has a 32KB code size limit unless you pay for a
600
600
601
601
Sometimes STM32CubeMX will update itself, and you need to run it in Administrator mode to finish the process. To do so right-click the icon and select `Run as Administrator` from the context menu.
602
602
603
+
And from time to time when you open older version `.ioc` files, STM32CubeMX will ask you what to do. Always pick `Migrate`:
604
+
605
+

606
+
603
607
## Next Steps
604
608
605
-
Now that we have everything set up, the subsequent lessons will be a significantly shorter than this one.
609
+
Now that we have everything set up, the subsequent lessons will be a significantly shorter.
606
610
607
611
Next up we'll take a look at setting up UART and print "Hello World" over serial.
608
612
609
613
[CLICK ME TO GO TO NEXT LESSON](../lesson1_serial_helloworld/README.md)
614
+
615
+
## Questions?
616
+
617
+
If you have any questions, feel free to [open an issue](https://github.com/dekuNukem/stm32_the_easy_way/issues) or email me at `dekunukem gmail com`. The former is preferable since it helps other people too.
Copy file name to clipboardExpand all lines: lesson1_serial_helloworld/README.md
+4Lines changed: 4 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -132,3 +132,7 @@ As a reminder, you can get the millisecond reading from `HAL_GetTick()`, and [ta
132
132
We'll take a look at reading GPIO pins and using external interrupts in the next lesson.
133
133
134
134
[CLICK ME TO GO TO NEXT LESSON](../lesson2_external_interrupt/README.md)
135
+
136
+
## Questions?
137
+
138
+
If you have any questions, feel free to [open an issue](https://github.com/dekuNukem/stm32_the_easy_way/issues) or email me at `dekunukem gmail com`. The former is preferable since it helps other people too.
Copy file name to clipboardExpand all lines: lesson2_external_interrupt/README.md
+4Lines changed: 4 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -169,3 +169,7 @@ Go back to SMT32CubeMX to add and configure a new pin, regenerate the code. You
169
169
We'll utilize another interrupt, this time internal, in the next lesson to implement an efficient UART receiving algorithm.
170
170
171
171
[CLICK ME TO GO TO NEXT LESSON](../lesson3_serial_recv_interrupt/README.md)
172
+
173
+
## Questions?
174
+
175
+
If you have any questions, feel free to [open an issue](https://github.com/dekuNukem/stm32_the_easy_way/issues) or email me at `dekunukem gmail com`. The former is preferable since it helps other people too.
Copy file name to clipboardExpand all lines: lesson3_serial_recv_interrupt/README.md
+4Lines changed: 4 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -340,3 +340,7 @@ For the homework, I suggest making some modifications to `linear_buf` files to i
340
340
In the next lesson we'll check out another common used peripheral: timers. We'll explore how to use timer interrupts as well as PWM outputs.
341
341
342
342
[CLICK ME TO GO TO NEXT LESSON](../lesson4_timers_and_pwm/README.md)
343
+
344
+
## Questions?
345
+
346
+
If you have any questions, feel free to [open an issue](https://github.com/dekuNukem/stm32_the_easy_way/issues) or email me at `dekunukem gmail com`. The former is preferable since it helps other people too.
Copy file name to clipboardExpand all lines: lesson4_timers_and_pwm/README.md
+164-8Lines changed: 164 additions & 8 deletions
Original file line number
Diff line number
Diff line change
@@ -64,7 +64,7 @@ Now we have a bunch of settings change:
64
64
65
65

66
66
67
-
Some maths is going to be involved, but nothing too heady. They are explained below.
67
+
The part we're interested in is `prescaler` and `counter period`, as they determine the timer interrupt frequency. Some maths is going to be involved, but hopefully nothing too heady.
68
68
69
69
### Timer prescaler
70
70
@@ -84,9 +84,11 @@ As you can see, the input clock is divided by `prescaler + 1`. Not too bad.
84
84
85
85
### Timer counter period
86
86
87
-
Another thing to keep in mind is the `counter period`.
87
+
Another thing to keep in mind while setting up timer interrupts is the `counter period`.
88
88
89
-
The counter in timers will count up from 0 to `counter period` at the clock speed obtained with the `prescaler` above. Once the counter reaches the `counter period`, it will reset to 0 and start all over again.
89
+
`counter period` is the upper limit of the timer counter, at which the counter will reset.
90
+
91
+
In other words, the counter will count up from 0 to `counter period`, at the clock speed obtained with the `prescaler` above. Once the counter reaches the `counter period`, it will reset to 0 and start all over again.
90
92
91
93
If interrupt is enabled, a `PeriodElapsed` interrupt raise when the rollover happens. This is the periodic timer interrupt that we were talking about earlier.
92
94
@@ -96,14 +98,168 @@ It's easy to see that both `prescaler` and `counter period` affect the frequency
96
98
97
99
Generally, you use `prescaler` to get the clock speed in the ballpack, then use `counter period` to fine tune the frequency.
98
100
99
-
For example we want our timer interrupt to happen every 100ms. To do this we want a 1KHz counter clock. Dividing input clock frequency by the desired output, we get a ratio of `48000000 / 1000 = 48000`. However since `prescaler` starts at 0, we need to subtract 1 from the ratio. Therefore our final `prescaler` is 47999, and this gives us a 1KHz clock to the timer counter.
101
+
For example we want our timer interrupt to happen every 100ms. To do this can use a 1KHz counter clock. Dividing input clock frequency by the desired output, we get a ratio of `48000000 / 1000 = 48000`. However since `prescaler` starts at 0, we need to subtract 1 from the ratio. Therefore our final `prescaler` is 47999, and this gives us a 1KHz clock to the timer counter.
102
+
103
+
Now that the counter ticks up every 1ms, we can set `counter period` to 100. As a result the counter counts up to 100 then resets, generating an interrupt every 100ms.
104
+
105
+
### Finding the right combination
106
+
107
+
It's easy to see that different combinations of `prescaler` and `counter period` can achieve the same result. For example we used `prescaler = 47999` and `counter_period = 100` for the 100ms interrupts above, however `prescaler = 23999` and `counter_period = 200`, or even `prescaler = 479` and `counter_period = 10000` works just as well. Again, it's up to you to decide what to use, with some nuances:
108
+
109
+
* Since `prescaler` and `counter period` are both unsigned 16-bit integers, neither of them can exceed 65535.
100
110
101
-
Now that we have a counter clock with a period of 1ms, we can set `counter period` to 100. As a result the counter counts up to 100 then resets, generating an interrupt every 100ms.
111
+
*`prescaler` balances the range and resolution of your timer interrupt. A small `prescaler` allows you to fine tune the accuracy of periodic interrupt, but also limits the range.
102
112
103
-
Here we have a combination of `prescaler = 47999` and `counter_period = 100` for an 10Hz interrupt. However it's easy to see that other combination works too, for example `prescaler = 23999` and `counter_period = 200`, or even `prescaler = 479` and `counter_period = 10000`.
113
+
For example, when `prescaler = 47`, counter clock is 1MHz. 1 counter tick is 1us, so you can adjust `counter_period` in 1us increments from 1us to 65535us = 65.53ms.
104
114
105
-
Again, it's up to you to decide what to use. Just keep in mind that neither of them can exceed 65535, and maybe don't make `counter period` too small or you'll lose the ability to fine tune the frequency.
115
+
While when `prescaler = 47999`, counter clock is 1KHz. 1 counter tick is 1ms and you can adjust `counter_period` in 1ms increments, giving you the range from 1ms to 65535ms.
116
+
117
+
Notice how in the first scenario you can fine tune the timer interrupt accuracy down to 1us, but the maximum period of timer interrupt is only 65 milliseconds. While in the second scenario the max period is 65 seconds, but can only be adjusted in 1ms increments.
106
118
107
119
There is also a detailed formula on page 10 of the [timer overview](resources/timer_overview.pdf) if you want more information.
108
120
109
-
### Try it out
121
+
### Try it out
122
+
123
+
Anyway, since that we have figured out the `prescaler` and `counter period`, we can type them in:
124
+
125
+

126
+
127
+
Make sure to enable the interrupt in the NVIC tab as well:
128
+
129
+

130
+
131
+
That's it! Regenerate the code and launch Keil MDK.
132
+
133
+
As usual, we look into [provided library files](sample_code/Drivers/STM32F0xx_HAL_Driver/) to see what we can use. Looking at the timer file [stm32f0xx_hal_tim.h](sample_code/Drivers/STM32F0xx_HAL_Driver/Inc/stm32f0xx_hal_tim.h), we can see a sea of library functions near the end, most of them for advanced features.
134
+
135
+
For this simple example, we just need `HAL_TIM_Base_Start_IT()` to start the timer interrupt. Put it before the main loop:
136
+
137
+

138
+
139
+
Then we need to write our interrupt callback function. The timer overflow interrupt in STM32 HAL is called `HAL_TIM_PeriodElapsedCallback()`. It has a `__weak` attribute, meaning if we write our own function with the same name and arguments, the compiler will use the new one instead.
140
+
141
+
So just copy this function's definition and put it anywhere you like in `main.c`, for this example it just prints `hello world`:
142
+
143
+

144
+
145
+
Compile and upload, and you'll see it's printing `hello world` every 100ms:
146
+
147
+

148
+
149
+
You can use timer interrupts like this to periodically update a screen, scan button matrix input, read a sensor, and much more.
150
+
151
+
Periodic timer interrupts like this happens at a consistent rate outside the main loop. They are more accurate than using `HAL_Delay()`, and more importantly they are not blocking, allowing CPU to do other tasks in the mean time.
152
+
153
+
You can find the [finished project here](sample_code).
154
+
155
+
## PWM Output
156
+
157
+
Another popular timer function is outputting PWM signals. Take a look at the [sparkfun tutorial](https://learn.sparkfun.com/tutorials/pulse-width-modulation) if you're not familiar with this topic.
158
+
159
+
Put simply, PWM works by switching a signal on and off very rapidly. The more time the signal stays on, the more power is delivered to the output. This is how [analogWrite()](https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/) function in Arduino works.
160
+
161
+
In STM32 we have much more options and control for PWM generation compared to Arduino, although we'll stick to the basics for this lesson by using it to dim the on-board LED on PA4.
162
+
163
+
### Timer configuration
164
+
165
+
We'll continue working on the project from the previous section. Go back to STM32Cube and switch to `pinout` page:
166
+
167
+

168
+
169
+
Just like Arduino, only certain pins on STM32 has PWM output. You can find out by left clicking a pin and see if it as `TIMXXCHY` function, where `XX` is timer number, and `Y` is channel number. Let's see what's on the on-board LED, `PA4`:
170
+
171
+

172
+
173
+
Bingo! `PA4` has `TIM14CH1`, meaning it's the output of timer 14 channel 1, and we can use it for PWM.
174
+
175
+
Click `TIM14CH1` to switch to that function:
176
+
177
+

178
+
179
+
Then on the side bar, activate TIM14, then set channel 1 function to `PWM Generation CH1`:
180
+
181
+

182
+
183
+
Go to the `configuration` page and click on the newly appeared `TIM14` button:
184
+
185
+

186
+
187
+
Now we're back at the counter settings, except this time there is an additional PWM1 section:
188
+
189
+

190
+
191
+
Just like last time, we have `prescaler` and `counter period`. Except in this case, instead of the interrupt frequency, they determine the PWM frequency.
192
+
193
+
Let's say we want the PWM frequency of our LED to be 1KHz. One option is to use `prescaler` of 47 and `counter period` of 1000.
194
+
195
+
The `prescaler` of 47 divides the 48MHz system clock down to 1MHz for the TIM14 counter, making it count up every 1us. And the `counter period` of 1000 makes the counter reset once the count has reached 1000. This way the counter resets every 1000uS, which is 1ms, which is a frequency of 1KHz.
196
+
197
+
The new parameter `pulse` determines the duty cycle of the PWM output, calculated as `pulse / counter_period`.
198
+
199
+
For example, if `counter period` is 1000 and you set `pulse` to 900, then the duty cycle is `900/1000 = 90%`, meaning the output stays high 90% of the time during a single PWM period. Obviously, `pulse` should be less or equal to `counter period`.
200
+
201
+
Anyway, let's type the numbers in and see what happens:
202
+
203
+

204
+
205
+
Generate the code, then go to Keil IDE.
206
+
207
+
### Try it out
208
+
209
+
To see PWM in action we need to make a few changes in the code. Before the main loop, call a few more initialization functions:
210
+
211
+
```
212
+
HAL_TIM_Base_Start(&htim14);
213
+
HAL_TIM_PWM_Init(&htim14);
214
+
HAL_TIM_PWM_Start(&htim14, TIM_CHANNEL_1);
215
+
```
216
+
217
+
Then delete everything in the main loop. It should look like this:
218
+
219
+

220
+
221
+
Compile and upload. Now LED should be much dimmer than before, almost unnoticeable compared to the power LED:
222
+
223
+

224
+
225
+
And if you have a logic analyzer, take a look at the waveform:
226
+
227
+

228
+
229
+
You'll see that the PWM signal has a period of 1ms, and stays high 90% of the time, exactly what we want.
230
+
231
+
Eagle eyed viewer might spot the LED is dim even though the duty cycle is high. Remember that the LED on PA4 is connected to 3.3V on the other end, so it lights up when PA4 is LOW.
232
+
233
+
### Changing duty cycle on the fly
234
+
235
+
Setting up duty cycle in STM32Cube is all well and good, but it would be more useful if we can change it while our program is running. Luckily you can manipulate peripheral registers directly in STM32 HAL. For adjusting duty cycle, simply write into `CCRx` register like this:
236
+
237
+
```
238
+
htim14.Instance->CCR1 = 900;
239
+
```
240
+
CCR1 is for channel 1, change it to CCR2, CCR3, CCR4 if you you're using other channels.
241
+
242
+
Low-level peripheral registers is another massive rabbit hole that I'm not going into. After all, abstracting them is what HAL library is for in the first place.
243
+
244
+
However if you're feeling adventurous, feel free to dive in the thousand-page [reference manual](../resources/datasheets/stm32f0_reference_manual.pdf) and find out the details about every single peripheral registers. Then you can find their `typedef`s in the beginning of [device header file](sample_code_pwm/Drivers/CMSIS/Device/ST/STM32F0xx/Include/stm32f030x6.h) and manipulate them directly.
245
+
246
+
Anyway, we can try it out by adding this in the main loop:
247
+
248
+
```
249
+
for (int i = 0; i < 1000; i+=3)
250
+
{
251
+
htim14.Instance->CCR1 = i;
252
+
HAL_Delay(1);
253
+
}
254
+
for (int i = 1000; i > 0; i-=3)
255
+
{
256
+
htim14.Instance->CCR1 = i;
257
+
HAL_Delay(1);
258
+
}
259
+
```
260
+
261
+
It ramps up the duty cycle, then back down again, resulting in a smooth "breathing" animation:
262
+
263
+

264
+
265
+
You can find the [finished code here](sample_code_pwm)
0 commit comments