STM32F103C8 + Rust: Timer sebagai Counter Downcounting dan Center Aligned

Pada artikel sebelumnya telah dibahas mengenai periferal Timer pada mikrokontroler STM32F103C8 (Blue Pill) dan contoh program Timer sebagai counter upcounting. Pada artikel ini akan dibahas cara menggunakan Timer STM32F103C8 sebagai counter downcounting dan center-aligned.

Seperti yang dijelaskan pada artikel tersebut, Timer sebagai counter akan menghitung tick dari frekuensi timer yang ditentukan. Jika kita mengatur timer dengan frekuensi 1000 Hz (1 KHz), maka Timer akan menghitung 1000 tick setiap detik.

Persiapan Hardware

Bahan-bahan yang digunakan dalam tutorial ini adalah: Mikrokontroler STM32F103C8 (Blue Pill), ST-Link USB Downloader Debuger untuk melakukan flash program ke mikrokontroler STM32F103C8, dan kabel jumper (female to female) untuk menghubungkan ST-Link ke mikrokontroler STM32F103C8.

Timer STM32F103C8 sebagai Counter Downcounting dengan Rust

Timer sebagai counter downcounting akan menghitung kebawah dari nilai maksimal yang ditentukan sampai ke nilai 0 secara berulang. Nilai pada register TIMx_CNT (counter register) yang awalnya sama dengan nilai TIMx_ARR (auto-reload register) akan terus berkurang 1 sampai mencapai nilai 0. Begitu nilai register TIMx_CNT mencapai 0 maka nilai register TIMx_CNT akan diset kembali ke nilai TIMx_ARR. Berikut merupakan timing diagram dari counter downcounting:

Timing diagram dari Timer STM32F103C8 sebagai Counter Downcounting
Timing diagram dari Timer STM32F103C8 sebagai Counter Downcounting

Pada gambar tersebut menggunakan prescaler 2 dan nilai register TIMx_ARR sebesar 5. Awalnya nilai register TIMx_CNT adalah 5 sama dengan register TIMx_ARR, kemudian berkurang 1 setiap tick frekuensi timer sampai 0. Ketika mencapai 0, maka nilai register TIMx_CNT akan diset kembali ke nilai TIMx_ARR.

Catatan: Jika mengaktifkan Update Interrupt Flag (UIF), maka nilai regiter UIF harus di reset secara manual melalui program yang dibuat setelah memanggil fungsi interrupt.

Pada tutorial ini kita akan menggunakan periferal Timer 2 (TIM2) untuk membuat counter downcounting. Perlu diingat, bahwa seperti yang dijelaskan di Artikel ini Timer 2 memiliki resolusi 16 bit.

Pemrograman Timer STM32F103C8 sebagai Counter Downcounting dengan Rust

Crate stm32f1xx_hal tidak menyediakan method untuk menggunakan timer sebagai counter downcounting, tetapi kita masih bisa mengunakannnya dengan mengakses register TIMx_CR1 bit DIR secara langsung.

Pertama mari buat project Rust baru sesuai dengan halaman ini. Buka file Cargo.toml lalu isi dengan kode berikut untuk mendefinisikan binary executable baru dengan nama timer-counter-down :

1
2[[bin]]
3name = "timer-counter-down"
4path = "src/main.rs"
5test = false
6bench = false

Selanjutnya buka file src/main.rs, lalu tambahkan kode berikut:

 1#![no_std]
 2#![no_main]
 3
 4use defmt_rtt as _;
 5use panic_probe as _;
 6
 7use cortex_m_rt::entry;
 8use stm32f1xx_hal::{
 9    flash::FlashExt,
10    pac,
11    prelude::*,
12    rcc::{Config, RccExt},
13    time::Hertz,
14};

Mengonfigurasi rust toolchain untuk tidak menggunakan standard library dan tidak menjalankan program diatas sistem operasi. Selanjutnya mendefinisikan library (crate) yang digunakan:

  • cortex_m_rt: Menangani startup program dan menentukan fungsi entry program mulai dijalankan.
  • defmt_rtt: Mengirimkan data ke PC/laptop menggunakan protokol real time transfer.
  • panic_probe: Menangani runtime error, dan akan otomatis mengirimkan log eror yang terjadi ke host PC/laptop
  • stm32f1xx_hal: Menyediakan API agar dapat mengakses periferal mikrokontroler STM32F103C8 secara aman.

Di dalam fungsi main tambahkan kode berikut untuk mengakses periferal STM32F103C8:

1defmt::println!("STM32F103C8 Timer as Counter Downcounting");
2
3let dp = pac::Peripherals::take().unwrap();

Mengirim pesan ke PC/laptop untuk menandai program sebagain counter downcounting. Selanjutnya mengakses periferal STM32F103C8.

 1let mut flash = dp.FLASH.constrain();
 2let rcc = dp.RCC.constrain();
 3
 4let clock_config = Config::default()
 5    .use_hse(Hertz::MHz(8))
 6    .sysclk(Hertz::MHz(72))
 7    .hclk(Hertz::MHz(36))
 8    .pclk1(Hertz::MHz(36));
 9
10let mut clocks = rcc.freeze(clock_config, &mut flash.acr);

Mengakses periferal Reset & Clock Control (RCC) dan flash STM32F103C8. Membuat konfigurasi clock dengan menggunakan High Speed External Clock ( use_hse ) 8 MHz, system clock ( systclk ) 72 MHz, Advanced-High performance Bus ( hclk ) ke 36 MHz, dan periferal 1/ APB1 ( pclk1 ) ke 36 MHz. Selanjutnya mengaplikasikan konfigurasi ke periferal RCC dan flash.

1let mut counter = dp.TIM2.counter::<1_000>(&mut clocks);

Membuat timer menggunakan Timer 2 (TIM2) dengan frekuensi 1000 Hz (1 KHz). Pada method counter akan otomatis menghitung prescaler yang digunakan.

Karena kita mengonfigurasi clock high bus ( hclk ) ke 36 MHz dan clock APB1 ( pclk1 ) ke 36 MHZ maka frekuensi clock Timer 2 adalah 36 MHz, sehingga nilai prescalernya:

psc=(frekclockfrektimer)1=(36 MHz1000 Hz)1=(36000000 Hz1000 Hz)1=35999\begin {split} psc &=(\frac {frek_{clock}} {frek_{timer}})-1\\ &=(\frac {36\ MHz} {1000\ Hz})-1\\ &=(\frac {36000000\ Hz} {1000\ Hz})-1\\ &=35999 \end {split}

Sehingga register TIM2_PSC akan bernilai 35999.

1unsafe {
2    (*pac::TIM2::ptr()).cr1().modify(|_, w| w.dir().down());
3}

Mengonfigurasi Timer 2 untuk melakukan downcounting. Kita mengakses pointer periferal Timer 2 secara langsung dan mengubah bit dir register CR1 ke nilai 1 dengan memanggil method down .

Pro Tip: Ketika bit dir register CR1 bernilai 1 maka Timer akan melakukan downcounting. Jika bernilai 0 maka Timer akan melakukan upcounting.

1counter.start(3000.millis()).unwrap();

Memulai proses downcounting dengan timeout 3000 ms (3 detik). Method start juga akan otomatis mengeset nilai register TIM2_ARR dengan nilai ticks dari timeout.

Karena kita menggunakan frekuensi timer 1000 Hz dan timeout 3000 ms maka:

arr=(1000×3000 ms)1arr=(1000×3 s)1=30001=2999\begin {split} arr &=({1000}\times{3000\ ms})-1\\ arr &=({1000}\times{3\ s})-1\\ &=3000-1\\ &=2999 \end {split}

Sehingga register TIM2_ARR akan berisikan nilai 2999.

 1let mut last_update_time = counter.now();
 2
 3let interval = 1000.millis::<1, 1000>();
 4
 5loop {
 6    let now = counter.now();
 7
 8    if last_update_time.ticks().wrapping_sub(now.ticks()) >= interval.ticks() {
 9        defmt::println!("1000 ms passed | Current counter value: {}", now.ticks());
10        last_update_time = now;
11    }
12}

Membuat variabel untuk menyimpan nilai counter terakhir dan nilai interval mengirimkan pesan ke terminal PC/laptop. Disini kami menggunakan interval 1 detik (1000 ms). Di dalam blok loop program utama kita akan dijalankan. Pertama ambil nilai counter sekarang. kemudian nilai counter terakhir yang tersimpan dikurangi dengan nilai counter sekarang. Jika hasilnya lebih besar atau sama dengan interval maka kirim pesan ke terminal PC/laptop dan update nilai counter terakhir dengan nilai sekarang.

Tampilkan kode full: Timer sebagai Counter Downcounting
 1#![no_std]
 2#![no_main]
 3
 4use defmt_rtt as _;
 5use panic_probe as _;
 6
 7use cortex_m_rt::entry;
 8use stm32f1xx_hal::{
 9    flash::FlashExt,
10    pac,
11    prelude::*,
12    rcc::{Config, RccExt},
13    time::Hertz,
14};
15
16#[entry]
17fn main() -> ! {
18    defmt::println!("STM32F103C8 Timer as Counter Downcounting");
19
20    let dp = pac::Peripherals::take().unwrap();
21
22    let mut flash = dp.FLASH.constrain();
23
24    let rcc = dp.RCC.constrain();
25
26    let clock_config = Config::default()
27        .use_hse(Hertz::MHz(8))
28        .sysclk(Hertz::MHz(72))
29        .hclk(Hertz::MHz(36))
30        .pclk1(Hertz::MHz(36));
31
32    let mut clocks = rcc.freeze(clock_config, &mut flash.acr);
33
34    let mut counter = dp.TIM2.counter::<1_000>(&mut clocks);
35
36    unsafe {
37        (*pac::TIM2::ptr()).cr1().modify(|_, w| w.dir().down());
38    }
39    counter.start(3000.millis()).unwrap();
40
41    let mut last_update_time = counter.now();
42
43    let interval = 1000.millis::<1, 1000>();
44
45    loop {
46        let now = counter.now();
47
48        if last_update_time.ticks().wrapping_sub(now.ticks()) >= interval.ticks() {
49            defmt::println!("1000 ms passed | Current counter value: {}", now.ticks());
50            last_update_time = now;
51        }
52    }
53}

Hubungkan mikrokontroler STM32F103C8 denga PC/laptop menggunakan ST-Link USB Downloader/Debugger, lalu jalankan program dengan perintah cargo run --bin timer-counter-down pada terminal. Berikut merupakan hasilnya:

Timer STM32F103C8 sebagai Counter Downcounting dengan Rust
Timer STM32F103C8 sebagai Counter Downcounting dengan Rust

Pada hasil tersebut dapat dilihat bahwa nilai awal dari register TIM2_CNT adalah 2999 yang kemudian terus berkurang 1 setiap tick frekuensi timer sampai mencapai 0. Karena kita menggunakan interval 1000 ms maka setiap interval nilai counter yang dikirimakan ke PC/laptop akan berkurang 1000. Ketika mencapai 0, nilai register TIM2_CNT akan diset kembali ke nilai TIM2_ARR.

Timer STM32F103C8 sebagai Counter Center-Aligned dengan Rust

Timer sebagai counter center-aligned akan menghitung mulai dari nilai 0 ke nilai maksimum (upcounting) yang ditentukan, setelah mencapai nilai maksimum counter lanjut menghitung ke bawah sampai nilai 0 (downcounting). Saat timer mulai berjalan, nilai register TIMx_CNT (Counter Register) yang awalnya bernilai 0 bertambah 1 setiap tick Timer sampai sama dengan nilai TIMx_ARR (Auto Reload Register), setelah mencapai nilai TIMx_ARR nilai counter akan berkurang 1 setiap tick timer. Berikut merupakan timing diagram dari counter center-aligned:

Timing diagram dari Timer STM32F103C8 sebagai Counter Center-Aligned
Timing diagram dari Timer STM32F103C8 sebagai Counter Center-Aligned

Pada gambar tersebut timer menggunakan pescaler 2, sehingga tiap 2 tick pada clock timer sama dengan 1 tick pada timer. Pada diagram tersebut juga menggunakan Nilai TIMx_ARR 5. TIMx_CNT awalnya 0 bertambah 1 setiap tick frekuensi timer hingga nilainya sama dengan TIMx_ARR. Setelah nilai TIMx_CNT sama dengan TIMx_ARR nilainya akan bekurang 1 setiap tick frekuensi timer hingga mencapai nilai 0. Proses ini akan berulang secara terus menerus.

Pada tutorial ini kita masih akan menggunakan Timer 2 dan mengakses langsung registernya, karena crate stm32f1xx_hal tidak menyediakan method untuk menggunakan counter center-aligned.

Pemrograman Timer STM32F103C8 sebagai Counter Center-Aligned dengan Rust

Pertama mari definisikan binary executable baru dengan nama timer-counter-center di Cargo.toml.

1[[bin]]
2name = "timer-counter-center"
3path = "src/counter_center_aligned.rs"
4test = false
5bench = false

Buat file src/counter_center_aligned.rs lalu tambahkan kode berikut:

 1#![no_std]
 2#![no_main]
 3
 4use defmt_rtt as _;
 5use panic_probe as _;
 6
 7use cortex_m_rt::entry;
 8use stm32f1xx_hal::{
 9    flash::FlashExt,
10    pac,
11    prelude::*,
12    rcc::{Config, RccExt},
13    time::Hertz,
14};

Merupakan kode yang sama dengan program sebelumnya, untuk mengonfigurasi compiler Rust dan mendefinisikan library yang digunakan.

 1defmt::println!("STM32F103C8 Timer as Counter Center Aligned");
 2
 3let dp = pac::Peripherals::take().unwrap();
 4
 5let mut flash = dp.FLASH.constrain();
 6
 7let rcc = dp.RCC.constrain();
 8
 9let clock_config = Config::default()
10    .use_hse(Hertz::MHz(8))
11    .sysclk(Hertz::MHz(72))
12    .hclk(Hertz::MHz(36))
13    .pclk1(Hertz::MHz(36));
14
15let mut clocks = rcc.freeze(clock_config, &mut flash.acr);

Sama seperti program sebelumnya, digunakan untuk mengakses semua periferal STM32F103C8 dan mengonfigurasi clock.

1let mut counter = dp.TIM2.counter::<1_000>(&mut clocks);
2
3unsafe {
4    (*pac::TIM2::ptr())
5        .cr1()
6        .modify(|_, w| w.cms().center_aligned1());
7}

Mengakses periferal Timer2 (TIM2), sekaligus mengonfigurasi frekuensinya sebesar 1000 Hz (1 KHz). Selanjutnya mengakses pointer Timer 2 dan mengatur register CR1 bit CMS (Center Mode register) ke nilai 0x01 (center_aligned1). Karena kita menggunakan frekuensi clock timer 36 Mhz dan mengatur frekuensi timer sebesar 1000 Hz, maka nilai register TIM2_PSC akan menjadi 35999. Perhitungannya sama seperti pada bagian counter downcounting.

Catatan: CMS digunakan untuk mengatur mode counter center-aligned. Pada saat bernilai 0x00, center-aligned tidak aktif. Berdasarkan pada nilai CMS ada 3 mode counter center-aligned yaitu:

  • 0x01 (center_aligned1): Update Interrupt Flag akan di set 1 ketika kondisi menghitung ke bawah (downcounting).
  • 0x10 (center_aligned2): Update Interrupt Flag akan di set 1 ketika kondisi menghitung naik (upcounting).
  • 0x11 (center_aligned3): Update Interrupt Flag akan di set 1 ketika kondisi menghitung naik dan turun (up/down counting).
1counter.start(3000.millis()).unwrap();

Memulai proses counter dengan timeout 3000 ms. Karena kita mengatur frekuensi timer menjadi 1000 Hz, maka nilai register TIM2_ARR akan menjadi 2999. Perhitungannya sama seperti pada bagian counter downcounting.

 1let mut last_update_time = counter.now();
 2
 3let interval = 500.millis::<1, 1000>();
 4
 5loop {
 6    let now = counter.now();
 7
 8    let delta = last_update_time.ticks().abs_diff(now.ticks());
 9
10    if delta >= interval.ticks() {
11        match now.ticks() > last_update_time.ticks() {
12            true => defmt::println!("Upcounting"),
13            false => defmt::println!("Downcounting"),
14        }
15        defmt::println!(
16            "{} ms passed | Current counter value: {}",
17            interval.to_millis(),
18            now.ticks()
19        );
20        last_update_time = now;
21    }
22}

Membuat variabel untuk menyimpan nilai counter terakhir dan interval mengecek nilai counter. Kemudian di dalam blok loop kita mencari selisih nilai counter tersimpan terakhir dengan nilai counter sekarang secara absolut. Jika selisihnya sama atau lebih besar dari nilai interval, maka bandingkan nilai counter sekarang dengan nilai counter tersimpan terakhir. Jika nilai counter sekarang lebih besar dari yang tersimpan terakhir maka merupakan proses upcounting, sebaliknya merupakan proses downcounting. Selanjutnya kirimkan nilai counter sekarang ke terminal PC/laptop dan update nilai counter terakhir dengan nilai sekarang.

Tampilkan kode full: Timer sebagai Counter Center-Aligned
 1#![no_std]
 2#![no_main]
 3
 4use defmt_rtt as _;
 5use panic_probe as _;
 6
 7use cortex_m_rt::entry;
 8use stm32f1xx_hal::{
 9    flash::FlashExt,
10    pac,
11    prelude::*,
12    rcc::{Config, RccExt},
13    time::Hertz,
14};
15
16#[entry]
17fn main() -> ! {
18    defmt::println!("STM32F103C8 Timer as Counter Center Aligned");
19
20    let dp = pac::Peripherals::take().unwrap();
21
22    let mut flash = dp.FLASH.constrain();
23
24    let rcc = dp.RCC.constrain();
25
26    let clock_config = Config::default()
27        .use_hse(Hertz::MHz(8))
28        .sysclk(Hertz::MHz(72))
29        .hclk(Hertz::MHz(36))
30        .pclk1(Hertz::MHz(36));
31
32    let mut clocks = rcc.freeze(clock_config, &mut flash.acr);
33
34    let mut counter = dp.TIM2.counter::<1_000>(&mut clocks);
35
36    unsafe {
37        (*pac::TIM2::ptr())
38            .cr1()
39            .modify(|_, w| w.cms().center_aligned1());
40    }
41
42    counter.start(3000.millis()).unwrap();
43
44    let mut last_update_time = counter.now();
45
46    let interval = 500.millis::<1, 1000>();
47
48    loop {
49        let now = counter.now();
50
51        let delta = last_update_time.ticks().abs_diff(now.ticks());
52
53        if delta >= interval.ticks() {
54            match now.ticks() > last_update_time.ticks() {
55                true => defmt::println!("Upcounting"),
56                false => defmt::println!("Downcounting"),
57            }
58            defmt::println!(
59                "{} ms passed | Current counter value: {}",
60                interval.to_millis(),
61                now.ticks()
62            );
63            last_update_time = now;
64        }
65    }
66}

Jalankan program dengan perintah cargo run --bin timer-counter-center pada terminal. Berikut merupakan hasilnya:

Hasil Timer STM32F103C8 sebagai Counter Center Aligned Menggunakan Rust
Hasil Timer STM32F103C8 sebagai Counter Center-Aligned Menggunakan Rust

Pada hasil tersebut, terlihat bahwa counter menghitung dari 0 ke nilai TIM2_ARR (2999). Kemudian setelah mencapai nilai TIM2_ARR, counter menghitung kebawah sampai 0. Proses tersebut terus berulang.

Permasalahan & Source Code

Crate stm32f1xx_hal tidak menyediakan method untuk menggunakan counter dengan countdown dan center-aligned, tetapi kita masih bisa melakukannya dengan mengakses register timer secara langsung untuk mengatur bit DIR dan CMS.

Jika anda mengalami kendala, atau ingin menyampaikan kritik dan saran silakan hubungi kami melalui halaman kontak.

Source code program yang digunakan pada tutorial ini dapat diakses pada repositori Github.