Skip to content

Commit 00ced4d

Browse files
committed
lesson4 2/3 done
1 parent 30c553c commit 00ced4d

File tree

215 files changed

+78398
-482
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

215 files changed

+78398
-482
lines changed

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,12 @@ We'll get into details about this board in the next lesson.
3838

3939
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.
4040

41-
## How to use this guide
41+
### Get the files
4242

4343
Click "Download ZIP" on the top right corner to get lesson files, then just follow along on this webpage.
4444

4545
![Alt text](resources/images/dl.png)
4646

47-
If STM32CubeMX asks you what to do when opening `.ioc` files, always pick `Migrate`:
48-
49-
![Alt text](resources/images/mig.png)
50-
5147
## What now?
5248

5349
If you can't wait to get going, click a lesson below and get started!

lesson0_intro_blinkLED/README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
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!
1818

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.
2020

2121
## The Chip
2222

@@ -600,10 +600,18 @@ I mentioned before that Keil MDK has a 32KB code size limit unless you pay for a
600600

601601
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.
602602

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+
![Alt text](resources/mig.png)
606+
603607
## Next Steps
604608

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.
606610

607611
Next up we'll take a look at setting up UART and print "Hello World" over serial.
608612

609613
[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.
File renamed without changes.

lesson1_serial_helloworld/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,7 @@ As a reminder, you can get the millisecond reading from `HAL_GetTick()`, and [ta
132132
We'll take a look at reading GPIO pins and using external interrupts in the next lesson.
133133

134134
[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.

lesson2_external_interrupt/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,7 @@ Go back to SMT32CubeMX to add and configure a new pin, regenerate the code. You
169169
We'll utilize another interrupt, this time internal, in the next lesson to implement an efficient UART receiving algorithm.
170170

171171
[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.

lesson3_serial_recv_interrupt/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,3 +340,7 @@ For the homework, I suggest making some modifications to `linear_buf` files to i
340340
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.
341341

342342
[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.

lesson4_timers_and_pwm/README.md

Lines changed: 164 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Now we have a bunch of settings change:
6464

6565
![Alt text](resources/t17config.png)
6666

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.
6868

6969
### Timer prescaler
7070

@@ -84,9 +84,11 @@ As you can see, the input clock is divided by `prescaler + 1`. Not too bad.
8484

8585
### Timer counter period
8686

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`.
8888

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.
9092

9193
If interrupt is enabled, a `PeriodElapsed` interrupt raise when the rollover happens. This is the periodic timer interrupt that we were talking about earlier.
9294

@@ -96,14 +98,168 @@ It's easy to see that both `prescaler` and `counter period` affect the frequency
9698

9799
Generally, you use `prescaler` to get the clock speed in the ballpack, then use `counter period` to fine tune the frequency.
98100

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.
100110

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.
102112

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.
104114

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.
106118

107119
There is also a detailed formula on page 10 of the [timer overview](resources/timer_overview.pdf) if you want more information.
108120

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+
![Alt text](resources/numbers.png)
126+
127+
Make sure to enable the interrupt in the NVIC tab as well:
128+
129+
![Alt text](resources/int.png)
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+
![Alt text](resources/startit.png)
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+
![Alt text](resources/hw.png)
144+
145+
Compile and upload, and you'll see it's printing `hello world` every 100ms:
146+
147+
![Alt text](resources/shw.png)
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+
![Alt text](resources/pinout.png)
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+
![Alt text](resources/pa4before.png)
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+
![Alt text](resources/orange.png)
178+
179+
Then on the side bar, activate TIM14, then set channel 1 function to `PWM Generation CH1`:
180+
181+
![Alt text](resources/t14opt.png)
182+
183+
Go to the `configuration` page and click on the newly appeared `TIM14` button:
184+
185+
![Alt text](resources/t14b.png)
186+
187+
Now we're back at the counter settings, except this time there is an additional PWM1 section:
188+
189+
![Alt text](resources/t14set.png)
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+
![Alt text](resources/t14done.png)
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+
![Alt text](resources/newcode.png)
220+
221+
Compile and upload. Now LED should be much dimmer than before, almost unnoticeable compared to the power LED:
222+
223+
![Alt text](resources/photo.jpg)
224+
225+
And if you have a logic analyzer, take a look at the waveform:
226+
227+
![Alt text](resources/logic.png)
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+
![Alt text](resources/breath.gif)
264+
265+
You can find the [finished code here](sample_code_pwm)
2.72 MB
Loading
14.4 KB
Loading
8.59 KB
Loading
12.3 KB
Loading
23 KB
Loading
15.6 KB
Loading
8.48 KB
Loading
19.5 KB
Loading
401 KB
Loading
87.7 KB
Loading
22.4 KB
Loading
10.3 KB
Loading
4.96 KB
Loading
17.4 KB
Loading
11.9 KB
Loading
17 KB
Loading

0 commit comments

Comments
 (0)