Menggunakan Timer pada Blue Pill (STM32F103C8) dengan Rust

Pada artikel sebelumnya kita sudah membahas menggunakan ADC mikrokontroler STM32F103C8 mulai dari pengenalan, menggunakan berbagai mode konversi ( single conversion, continuous conversion, scan mode, dan discontinuous conversion), hingga contoh programmnya menggunakan bahasa Rust. Sekarang kami akan membahas mengenai periferal Timer pada mikrokontroler STM32F103C8 (STM32 Blue Pill).

Sebenarnya pada artikel-artikel sebelumnya kita sudah menggunakan Timer , yaitu untuk membuat delay . Mikrokontroler STM32F103C8 memiliki beberapa jenis timer yaitu: Advanced-control Timer, General Purpose Timer, System Timer, dan Watchdog. Setiap jenis timer memiliki resolusi dan fungsi khusus masing-masing. Pada artikel-artikel sebelumnya kita membuat delay dengan menggunakan jenis System Timer sebagai counter.

Mengenal Timer pada Blue Pill (STM32F103C8)

Berikut merupakan penjelasan jenis-jenis periferal Timer pada mikrokontroler STM32F103C8:

  1. Advanced-control Timer: Merupakan timer dengan resolusi 16 bit yang berada di bus APB2 dengan frekuensi maksimal 72 MHz. Mikrokontroler STM32F103C8 hanya memiliki 1 advanced-control timer yaitu TIM1 dengan 4 channel standar, 3 channel complementary output, dan 1 channel break input. Timer ini bisa digunakan sebagai counter, input capture, PWM (Pulse Width Modulation), one pulse output, dan output compare. Kelebihan timer ini dibanding general pupose timer adalah dapat membuat complementary output dengan dead-time yang dapat di program, bisa sebagai break input, dan memiliki akurasi yang lebih tinggi.
  2. General Purpose Timer: Memiliki resolusi 16 bit yang berada di bus APB1 dengan frekuensi maksimal 36 MHz. Biasanya digunakan sebagai counter, input capture, PWM (Pulse Width Modulation), one pulse output, dan output compare. Mikrokontroler STM32F103C8 memiliki 3 general purpose timer yaitu TIM2, TIM3, dan TIM4 yang masing-masing memiliki 4 channel.
  3. System Timer: Merupakan timer pada CPU Cortex-M3 yang memiliki resolusi 24 bit. Timer ini tidak memiliki channel. Biasanya digunakan untuk membuat delay seperti yang kita lakukan pada artikel-artikel sebelumnya.
  4. Watchdog: Berfungsi untuk melakukan reset pada mikrokontroler STM32F103C8 jika terjadi hang. Dalam keadaan normal nilai counter watchdog akan selalu ter-reset, ketika CPU mengalami masalah atau hang nilai counter bisa mencapai nilai yang telah ditentukan sehingga watchdog akan otomatis mereset mikrokontroler. Pada mikrokontroler STM32F103C8 terdapat 2 watchdog yaitu WWDG (Window Watchdog) dan IWDG (Independent Watchdog).

Secara umum fungsi Timer adalah sebagai berikut:

  1. Counter: Timer akan menghitung setiap tick (jika menggunakan clock internal mode) atau setiap channel menerima perubahan tegangan (jika menggunakan clock eksternal mode) sampai nilai maksimal yang ditentukan. Mode counter antara lain: upcounting, downcounting, dan center-aligned mode (up/down counting).
  2. Input Capture: Timer akan menghitung setiap tick frekuensi timer (counter) dan jika ada perubahan tegangan pada channel maka nilai counter tersebut akan disimpan. Mode input capture antara lain: Mode Basic Input Capture, mode PWM (Pulse Width Modulation) Input, mode input capture dengan slave mode controller, dan input capture dengan prescaler. Kita bisa menggunakan fungsi ini untuk mengukur lebar input pulsa.
  3. Output Compare: Timer akan melakukan counter sesuai dengan frekuensi timer kemudian mengubah status channel output ketika nilai counter mencapai nilai tertentu. Mode output compare antara lain: Mode frozen, active on match, inactive on match, toggle on match, dan PWM.
  4. PWM (Pulse Width Modulation): Merupakan salah satu mode dari output compare. pada mode ini output lebar pulsa channel dapat diatur sesuai dengan yang kita tentukan. Terdapat 2 mode PWM pada STM32F103C8 yaitu PWM Mode 1 dan PWM Mode 2 dengan 2 alignment yaitu edge alignment dan center alignment. Bisa digunkakan untuk mengatur tingkat kecerahan LED dan kecepatan motor.
  5. One Pulse Output: Channel akan mengeluarkan pulsa sekali ketika nilai counter mencapai nilai tertentu, setelah itu timer akan berhenti.
  6. complementary output: Hanya ada pada jenis Advanced-control Timer (TIM1). Ketika diaktifkan channel complementary akan memiliki status yang berlawanan dengan channel normal. Jika pada channel normal berstatus HIGH maka channel complementary akan berstatus LOW dan sebaliknya.

Timer 1 (TIM1) pada Blue Pill (STM32F103C8)

Timer 1 (TIM1) merupakan advanced-control timer pada STM32F103C8 dengan resolusi 16 bit (0-65535) dan frekuensi maksimal 72 MHz. Timer 1 memiliki akurasi yang lebih tinggi dari pada timer general purpose. Selain memiliki channel standar dan channel complementary, Timer 1 juga memiliki channel untuk Break Input yang berfungsi untuk menerima triger external. Ketika channel Break Input menerima trigger maka semua PWM pada channel akan dihentikan (direset). Berikut merupakan channel-channel pada Timer 1 STM32F103C8 beserta pin dan remap yang dapat digunakan:

ChannelDefault PinPartial RemapFull Remap
TIM1_CH1PA8--
TIM1_CH2PA9--
TIM1_CH3PA10--
TIM1_CH4PA11--
TIM1_CH1NPB13PA7-
TIM1_CH2NPB14PB0-
TIM1_CH3NPB15PB1-
TIM1_BKINPB12PA6-

Pro Tip: Jika default pin pada channel sudah digunakan untuk kebutuhan yang lain maka kita dapat menggunakan fitur Remap untuk mengubah jalur channel dari Timer ke pin yang lain sesuai dengan yang ditunjukkan pada tabel.

Timer 2 (TIM2) pada Blue Pill (STM32F103C8)

Timer 2 (TIM2) merupakan bagian dari general purpose timer pada STM32F103C8 yang memiliki resolusi 16 bit (0-65535). Frekuensi maksimalnya adalah 72 MHz. Ketika APB1 memaki prescaler > 1 maka frekuensi clock Timer 2 akan otomatis dikalikan 2. Berikut merupakan channel-channel pada Timer 2 STM32F103C8 beserta pin dan remap yang dapat digunakan:

ChannelDefault PinPartial Remap 1Partial Remap 2Full Remap
CH1 / ETRPA0PA15-PB3
CH2PA1PB3-PB10
CH3PA2-PB10PB11
CH4PA3-PB11PA15

Catatan: Pada Timer STM32F103C8, Ketika menggunakan Remap channel hanya salah satu remap yang bisa dipilih. Sehingga kita tidak bisa menggunakan full remap, partial remap 1 dan partial remap 2 secara bersamaan.

Timer 3 (TIM3) pada Blue Pill (STM32F103C8)

Timer 3 (TIM3) merupakan bagian dari general purpose timer pada STM32F103C8 yang memiliki resolusi 16 bit (0-65535) dengan frekuensi maksimal 72 MHz. Ketika APB1 memaki prescaler > 1 maka frekuensi clock Timer 3 akan otomatis dikalikan 2. Berikut merupakan channel-channel pada Timer 3 STM32F103C8 beserta pin dan remap yang dapat digunakan:

ChannelDefault PinPartial RemapFull Remap
CH1PA6PB4-
CH2PA7PB5-
CH3PB0--
CH4PB1--

Timer 4 (TIM4) pada Blue Pill (STM32F103C8)

Timer 4 (TIM4) merupakan bagian dari general purpose timer pada STM32F103C8 yang memiliki resolusi 16 bit (0-65535) yang memiliki frekuensi maksimal 72 MHz. Ketika APB1 memaki prescaler > 1 maka frekuensi Timer 4 clock akan otomatis dikalikan 2. Berikut merupakan channel-channel pada Timer 4 STM32F103C8:

ChannelDefault PinPartial RemapFull Remap
CH1PB6--
CH2PB7--
CH3PB8--
CH4PB9--

Persiapan Hardware

Pada tutorial kali ini kita akan menggunakan beberapa hardware seperti: Mikrokontroler STM32F103C8 (Blue Pill), ST-Link USB Downloader Debuger, Breadboard, push button, dan beberapa kabel jumper (female to female dan male to male).

Mikrokontroler STM32F103C8 (Blue Pill)

Ini merupakan komponen utama yang dibutuhkan karena kita akan mengakses periferal Timer dari mikrokontroler STM32F103C8. Kami menggunakan Blue Pill, Anda juga dapat menggunakan modul board lain yang menggunakan mikrokontroler STM32F103C8.

Blue Pill (STM32F103C8)
Board sistem minimum Blue Pill

ST-LINK USB Downloader Debuger berfungsi untuk menghubungkan PC/laptop ke mikrokontroler STM32F103C8 sehingga kita bisa memprogram dan melakukan debugging pada mikrokontroler STM32F103C8 dari PC/laptop.

ST-LINK USB Downloader Debuger
ST-Link USB Downloader Debuger untuk memprogram mikrokontroler STM32F103C8

Breadboard

Breadboard merupakan komponen yang digunakan untuk memudahkan kita membuat prototipe rangkaian elektronik. Dengan menggunakan breadboard kita tidak perlu menyolder komponen, cukup dengan menggunakan kabel jumper male to male.

Breadboard (Project Board)
Breadboard untuk memudahkan membuat prototipe rangkaian elektronik

Push Button

Push button merupakan komponen yang akan menghubungkan kedua kakinya ketika ditekan. Push button akan kita gunakan sebagai input ke mikrokontroler STM32F103C8.

Push Button
Push Button sebagai input ke mikrokontroler STM32F103C8

Penjelasan lebih detail mengenai fungsi komponen-komponen yang digunakan dapat dilihat pada halaman setup STM32F103C8 dengan Rust.

Mengakses Timer pada Mikrokontroler STM32F103C8 Menggunakan Rust

Pada tutorial ini kita akan menggunakan periferal Timer 2 (TIM2) mikrokontroler STM32F103C8 sebagai counter . Untuk fungsi Timer yang lain akan kami bahas pada artikel-artikel berikutnya.

Timer 2 memiliki resolusi 16 bit artinya nilai maksimal ketika melakukan counter adalah 65535. Sebelum menggunakan counter kita harus lebih dahulu menentukan frekuensi timer yang akan digunakan untuk mencari nilai prescaler timer. Berikut merupakan rumus untuk menentukan prescaler:

psc=(frekuensiclockfrekuensitimer)1\boxed{psc=(\frac {frekuensi_{clock}} {frekuensi_{timer}})-1}

Keterangan:

  • frekuensiclockfrekuensi_{clock} : Frekuensi dari sistem utama mikrokontroler ke Timer.
  • frekuensitimerfrekuensi_{timer} : Frekuensi Timer yang kita tentukan.

Kita harus menentukan frekuensiclockfrekuensi_{clock} dan frekuensitimerfrekuensi_{timer} yang sesuai agar tidak melebihi kapasitas (65535). Nilai prescaler akan disimpan di regiter TIMx_PSC (Prescaler register). Pada datasheet STM32 RM0008 bagian timing diagram counter istilah CLK_PSC sama dengan frekuensiclockfrekuensi_{clock} .

Seperti yang dijelaskan diatas mode Timer sebagai counter ada 3 yaitu:

  1. Upcounting: Timer akan menghitung dari yang awalnya 0 sampai nilai maksimal yang ditentukan secara berulang. Pada mode ini nilai register TIMx_CNT (Counter register) yang awalnya 0 akan terus bertambah 1 sampai mencapai nilai register TIMx_ARR (Auto-reload register) yang ditentukan. Begitu nilai register TIMx_CNT lebih besar dari nilai TIMx_ARR (TIMx_ARR+1) maka nilai register TIMx_CNT akan diset kembali ke 0.
  2. Downcounting: Timer akan menghitung kebawah dari nilai maksimal yang ditentukan sampai ke nilai 0 secara berulang. Pada mode ini nilai register TIMx_CNT yang awalnya sama dengan nilai TIMx_ARR akan terus berkurang 1 sampai mencapai nilai 0. Begitu nilai register TIMx_CNT mencapai 0 maka nilai register TIMx_CNT akan diset kembali sesuai dengan nilai TIMx_ARR.
  3. Center-aligned mode (Up/Down counting): Timer awalnya akan menghitung dari nilai 0 sampai ke nilai maksimal yang ditentukan kemudian menghitung kembali sampai ke nilai 0 secara berulang. Pada mode ini nilai register TIMx_CNT yang awalnya 0 akan terus bertambah 1 sampai mencapai nilai register TIMx_ARR (Auto-reload register) yang ditentukan. Begitu nilai register TIMx_CNT lebih besar dari nilai TIMx_ARR (TIMx_ARR+1) maka timer akan melakukan downcounting hingga nilai register TIMx_CNT mencapai 0.

Sumber clock untuk merubah nilai register TIMx_CNT pada Timer sebagai counter ada 2 yaitu:

  1. Clock internal: Pada tipe ini, timer akan menghitung tick dari frekuensi timer. setiap 1 tick maka nilai register TIMx_CNT akan berubah 1 sesuai dengan modenya. Biasanya digunakan untuk membuat delay baik blocking maupun non-blocking. Karena menggunakan clock internal maka Timer tidak menggunakan channel.
  2. Clock external: Pada tipe ini, timer akan menghitung begitu ada perubahan tegangan pada channel yang ditentukan. Setiap ada perubahan level tegangan pada channel maka nilai pada register TIMx_CNT akan berubah 1 sesuai dengan modenya. Digunakan untuk menghitung jumlah kejadian (perubahan tegangan) yang terjadi pada channel.

Timer STM32F103C8 sebagai Counter dengan Clock Internal Menggunakan Rust

Pada tutorial ini kita akan membuat counter upcounting dengan memanfaatkan periferal Timer 2 . Setiap 1 detik kita akan mengecek nilai dari register TIMx_CNT. Kita akan menanfaatkan method yang disediakan oleh crate stm32f1xx_hal.

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

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

Selanjutnya buka file src/main.rs, tambahkan kode berikut untuk memberitahu compiler bahwa program ini tidak menggunakan standard library dan tidak berjalan diatas sistem operasi:

1#![no_std]
2#![no_main]

Mendefinisikan semua library yang digunakan:

 1use defmt_rtt as _;
 2use panic_probe as _;
 3
 4use cortex_m_rt::entry;
 5use stm32f1xx_hal::{
 6    flash::FlashExt,
 7    pac,
 8    prelude::*,
 9    rcc::{Config, RccExt},
10    time::Hertz,
11};

Library/crate cortex_m_rt untuk menentukan fungsi entry program akan mulai berjalan dan menangani proses startup program. defmt_rtt berfungsi untuk mengirimkan data ke PC/laptop untuk logging menggunakan protokol real time transfer. Library stm32f1xx_hal berfungsi agar kita dapat mengakses periferal mikrokontoler STM32F103C8 secara aman. panic_probe digunakan untuk menangani jika terjadi runtime error, dan akan otomatis mengirimkan log eror yang terjadi ke host PC/laptop.

Pada fungsi main tambahkan kode berikut agar kita dapat mengakses periferal STM32F103C8:

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

Mengirim pesan ke terminal PC/laptop untuk menandai program sebagai counter. Kemudian 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 Flash dan Reset & Clock Control. 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. Setalah itu melakukan konfiguras clock dengan menggunakan method freeze .

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

Mengakses periferal Timer 2 dan mengatur frekuensinya ke 1 KHz (1000 tick tiap 1 detik). Pada method counter akan otomatis menghitung prescaler yang digunakan. Karena Timer 2 memiliki resolusi 16 bit maka nilai maksimum prescaler adaah 65535.

Peringatan: Agar program tidak error karena prescaler melebihi kapasitasnya (65535), maka kita harus menentukan frekuensi timer dan frekuensi clock yang sesuai.

Disini kami menggunakan hclk=36 MHz dan pclk1=36 MHz sehingga frekuensi clock untuk timer adalah 36 MHz:

psc=(frekuensiclockfrekuensitimer)1=(360000001000)1=35999\begin {split} psc &=(\frac {frekuensi_{clock}} {frekuensi_{timer}})-1\\ &=(\frac {36000000} {1000})-1\\ &=35999 \end {split}

Sehingga prescaler timer=35999 yang masih dibawah batas aman kapasitas.

Skenario jika menggunakan hclk=72 MHz dan pclk1=36 MHz , maka frekuensi clock akan menjadi 72 MHz. Seperti yang disebutkan diatas karena prescaler APB1 > 1, maka frekuensi clock untuk timer akan menjadi 2 kali pclk1:

fclock=2×36=72MHzf_{clock}=2\times36=72MHz psc=(frekuensiclockfrekuensitimer)1=(720000001000)1=71999\begin {split} psc &=(\frac {frekuensi_{clock}} {frekuensi_{timer}})-1\\ &=(\frac {72000000} {1000})-1\\ &=71999 \end {split}

Sehingga prescaler timer=71999 , dimana hal ini akan menyebabkan error karena nilainya melebihi dari kapasitas (65535).

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

Memulai counter dengan timeout 3000 ms. Pada method ini juga akan otomatis mengeset nilai register TIMx_ARR dengan nilai ticks dari timeout. Karena disini Kita menggunakan frekuensi timer 1 KHz (1000 ticks tiap detik) maka untuk mecapai 3 detik (3000ms) akan butuh 3000 ticks. sehingga nilai register TIMx_ARR akan diisi 30001=29993000-1=2999 . Nilai ini masih aman karena berada dibawah batas maksimal (65535).

Pro Tip: Untuk menghitung nilai TIMx_ARR dari nilai timeout dapat menggunakan rumus berikut:

arr=(frektimer×timeoutdetik)1\boxed{arr=({frek_{timer}}\times{timeout_{detik}})-1}

atau

arr=(frekclockpsc+1×timeoutdetik)1\boxed{arr=({\frac {frek_{clock}} {{psc}+1}}\times{timeout_{detik}})-1}

Disini kita menggunakan frekuensi timer 1 KHz (1000 Hz) dan timeout 3000 ms (3 detik) sehingga:

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

Jika kita menggunakan timeout 80 detik, maka:

arr=(1000×80)1=800001=79999 \begin {split} arr &=({1000}\times{80})-1\\ &=80000-1\\ &=79999 \end {split}

Nilai TIMx_ARR melebihi kapasitas (65535) sehingga akan terjadi error runtime.

 1let mut last_update_time = counter.now();
 2
 3let interval = 1000.millis::<1, 1000>();
 4
 5loop {
 6    let now = counter.now();
 7
 8    if now.ticks().wrapping_sub(last_update_time.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 kurangi dengan nilai counter terakhir yang tersimpan. Jika hasilnya lebih besar atau sama dengan interval maka kirim pesan ke terminal PC/laptop dan update nilai counter terakhir dengan nilai sekarang. Dengan metode ini kita dapat membuat delay non blocking.

Tampilkan kode full: Timer 2 sebagai Counter Internal Clock
 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");
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    counter.start(3000.millis()).unwrap();
37
38    let mut last_update_time = counter.now();
39
40    let interval = 1000.millis::<1, 1000>();
41
42    loop {
43        let now = counter.now();
44
45        if now.ticks().wrapping_sub(last_update_time.ticks()) >= interval.ticks() {
46            defmt::println!("1000 ms passed | Current counter value: {}", now.ticks());
47            last_update_time = now;
48        }
49    }
50}

Jalankan program dengan perintah ‘cargo run --bin timer-counter-internal’ pada terminal VSCode. Berikut merupakah hasilnya:

Hasil Timer 2 sebagai counter
Hasil menggunakan Timer 2 (TIM2) sebagai counter

Pada hasil tersebut dapat kita lihat mikrokontroler akan mengirimkan pesan ke terminal PC/laptop setiap 1 detik ketika nilai counter 1000, 2000, dan 0. Karena saat counter mencapai nilai register TIMx_ARR+1 (2999+1=3000) maka nilai counter akan kembali diset ke 0.

Timer STM32F103C8 sebagai Counter dengan Clock External Menggunakan Rust

Pada tutorial ini timer tidak lagi menggunakan clock internal melainkan menggunakan clock external yang sekaligus sebagai trigger pada channel. Kali ini kita akan menggunakan counter untuk menghitung berapa kali push button ditekan. Kita akan menggunakan Timer 2 (Tim2) channel 1 (pin PA0). Channel 1 akan kita set untuk mendeteksi Rising edge, sehingga ketika tegangan berubah dari 0V ke 3.3V maka nilai counter akan bertambah 1. Untuk itu kita perlu mengeset pin PA0 sebagai input pulldown yang dihubungkan ke push button. Push button akan menyalurkan tegangan 3.3V ke pin PA0 ketika ditekan. Berikut merupakan gambar skematik rangkaian push button dan mikrokontroler STM32F103C8:

Rangkaian push button dengan STM32F103C8 sebagai input timer counter
Rangkaian push button dengan STM32F103C8 sebagai input raising edge Timer 2 (TIM2)

Karena crate stm32f1xx_hal tidak menyediakan method untuk menggunakan clock external maka kita akan mencobanya dengan mengakses register secara langsung. Silakan buat rangkaian pada breadboard sesuai dengan skematik tersebut sebelum lanjut ke bagian membuat program.

Pertama mari definisikan binary executable baru dengan nama timer-counter-external di project rust yang sudah kita buat sebelumnya:

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

Buat file src/counter_external.rs. lalu isi dengan kode berikut untuk memberitahu compiler bahwa kita tidak menggunakan standard libray dan program tidak berjalan di sistem operasi, serta untuk mendefinisikan semua library yang akan kita gunakan:

 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};

Pada fungsi main tambahkan kode berikut untuk mengakses periferal STM32F103C8:

 1defmt::println!("STM32F103C8 Timer as Counter External");
 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);

Ini merupakan kode yang sama dengan sebelumnya untuk mengakses periferal STM32F103C8 dan mengonfigurasi clock.

1let mut gpioa = dp.GPIOA.split(&mut clocks);
2let _pa0 = gpioa.pa0.into_pull_down_input(&mut gpioa.crl);

Mengonfigurasi pin PA0 (channel 1 TIM2) sebagai input pulldown. Sehingga secara default pin akan terhubung ke GND (dengan resistor internal), ketika tombol ditekan maka akan mendeteksi tegangan 3.3V (Raising edge).

1let timer2 = dp.TIM2;
2
3unsafe {
4    let rcc_ptr = pac::RCC::ptr();
5    (*rcc_ptr).apb1enr().modify(|_, w| w.tim2en().set_bit());
6}

Mengakses periferal Timer 2 dan mengaktifkan clock untuk Timer 2.

1timer2
2    .ccmr1_input()
3    .modify(|_, w| unsafe { w.bits(0xF << 4) });

Mengaktifkan filter digital pada channel 1 agar pembacaan stabil.

1timer2
2    .ccer()
3    .modify(|_, w| w.cc1p().clear_bit().cc1e().set_bit());

Dengan mengeset register bit cc1e maka channel 1 akan aktif sebagai capture input. Register cc1p berfungsi untuk menentukan polarity capture pada channel 1. ketika cc1p bernilai 0 makan akan mendeteksi Raising edge dan jika bernilai 1 maka akan mendeteksi Falling edge.

1timer2
2    .smcr()
3    .modify(|_, w| unsafe { w.sms().bits(0b111).ts().bits(0b101) });

Mengubah sumber clock timer ke external yang sekaligus sebagai trigger pada channel. Selain itu juga mengonfigurasi untuk menggunakan digital filter.

 1timer2.cr1().modify(|_, w| w.cen().set_bit());
 2
 3let mut last_count = 0;
 4
 5loop {
 6    let current_count = timer2.cnt().read().cnt().bits();
 7
 8    if current_count != last_count {
 9        defmt::println!("Counter value: {}", current_count);
10        last_count = current_count;
11    }
12
13}

Mengaktifkan timer dengan mengset bit cen ke 1. Kemudian membaca nilai counter pada register cnt . Jika nilai counter tidak sama dengan nilai sebelumnya maka kirimkan nilainya ke terminal PC/laptop.

Tampilkan kode full: Timer 2 Sebagai Counter External Clock
 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 External");
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 gpioa = dp.GPIOA.split(&mut clocks);
35
36    let _pa0 = gpioa.pa0.into_pull_down_input(&mut gpioa.crl);
37
38    let timer2 = dp.TIM2;
39
40    unsafe {
41        let rcc_ptr = pac::RCC::ptr();
42        (*rcc_ptr).apb1enr().modify(|_, w| w.tim2en().set_bit());
43    }
44
45    timer2
46        .ccmr1_input()
47        .modify(|_, w| unsafe { w.bits(0xF << 4) });
48
49    timer2
50        .ccer()
51        .modify(|_, w| w.cc1p().clear_bit().cc1e().set_bit());
52
53    timer2
54        .smcr()
55        .modify(|_, w| unsafe { w.sms().bits(0b111).ts().bits(0b101) });
56
57    timer2.cr1().modify(|_, w| w.cen().set_bit());
58
59    let mut last_count = 0;
60
61    loop {
62        let current_count = timer2.cnt().read().cnt().bits();
63
64        if current_count != last_count {
65            defmt::println!("Counter value: {}", current_count);
66            last_count = current_count;
67        }
68
69    }
70}

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

Ketika push button ditekan maka nilai counter akan bertambah 1.

Source Code

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

Jika anda mengalami kendala ketika mengikuti tutorial ini, ingin bertanya, atau menyampaikan kritik dan saran, silakan hubungi kami melalui halaman kontak.