Mengukur Lebar Pulsa dengan STM32 Timer Input Capture menggunakan Rust
Pada artikel sebelumnya kita sudah membahas mengenai Timer STM32F103C8 dan menggunakan Timer STM32F103C8 sebagai counter. Kali ini kita akan mencoba menggunakan channel pada Timer STM32F103C8 sebagai input capture. Input capture biasanya digunakan untuk membaca nilai rpm pada tachometer, membaca sinyal remote kontrol, mengukur lebar pulsa input, dan lain-lain.
Apa itu Input Capture?
Input capture merupakan salah satu fungsi dari Timer STM32F103C8 untuk menyimpan nilai counter Timer saat terjadi perubahan tegangan pada pin input (channel Timer). Ketika terjadi perubahan tegangan pada channel Timer, maka secara otomatis akan menyalin nilai pada register counter (TIMx_CNT) ke register TIMx_CCR (Capture/Compare Register). Berikut merupakan perubahan tegangan channel Timer yang dapat dideteksi oleh fitur input capture:
- Rising edge: Input capture hanya menyalin nilai register
TIMx_CNTke registerTIMx_CCRketika terjadi perubahan tegangan dari 0V ke 3.3V (LOW ke HIGH). - Falling edge: Input capture hanya menyalin nilai register
TIMx_CNTke register TIMx_CCR ketika terjadi perubahan tegangan dari 3.3V ke 0V (HIGH ke LOW). - Both edges (Rising/Falling edge): Input capture menyalin nilai register
TIMx_CNTke registerTIMx_CCRketika terjadi perubahan tegangan dari 0V ke 3.3V (LOW ke HIGH) atau 3.3V ke 0V (HIGH ke LOW).
Dengan menggunakan selisih nilai register TIMx_CCR ketika terjadi perubahan tegangan maka kita bisa mengetahui durasi dari perubahan tegangan pertama sampai perubahan tegangan kedua (lebar pulsa). Berikut merupakan persamaan untuk mencari durasi (lebar pulsa) 2 peristiwa pada channel Timer:
Keterangan:
- : Periode antar 2 peristiwa pada channel Timer (lebar pulsa).
-
: Selisih nilai register
TIMx_CCRketika terjadi perubahan tegangan. - : Frekuensi Timer yang digunakan.
-
: Nilai register Capture/Compare (
TIMx_CCR).
Persiapan Hardware
Komponen yang digunakan pada tutorial kali ini antara lain: 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):
Merupakan komponen utama yang dibutuhkan. Kita akan mengakses periferal Timer dari mikrokontroler STM32F103C8.

Board sistem minimum Blue Pill ST-LINK USB Downloader Debuger:
ST-LINK USB Downloader Debuger digunakan untuk memprogram dan melakukan debugging pada mikrokontroler STM32F103C8. Komponen ini yang akan menghubungkan PC/laptop ke mikrokontroler STM32F103C8.

ST-Link USB Downloader Debuger untuk memprogram mikrokontroler STM32F103C8 Breadboard
Breadboard digunakan untuk membuat prototipe rangkaian elektronik. Dengan menggunakan breadboard kita tidak perlu menyolder komponen, cukup dengan menggunakan kabel jumper male to male.

Breadboard untuk memudahkan membuat prototipe rangkaian elektronik Push Button
Push button digunakan sebagai input ke channel Timer mikrokontroler STM32F103C8. Ketika push button ditekan maka kedua kakinya akan terhubung.

Push Button sebagai input ke mikrokontroler STM32F103C8 Kabel Jumper
Kabel jumper digunakan untuk menghubungkan komponen-komponen di breadboard, selain itu juga digunakan untuk menghubungkan Blue Pill dengan ST-Link USB Downloader Debuger.
Penjelasan lebih detail mengenai fungsi komponen-komponen yang digunakan dapat dilihat pada halaman Setup STM32F103C8 dengan Rust dan halaman Menggunakan GPIO STM32F103C8 dengan Rust.
Timer STM32F103C8 sebagai Input Capture dengan Rust
Pada artikel ini, kita akan menggunakan timer sebagai input capture untuk hal sederhana yaitu mengukur berapa lama push button ditekan. Meksipun sederhana, ini merupakan konsep dasar yang nanti akan digunakan ketika kita ingin membaca sensor seperti tachometer, sensor jarak ultrasonic (HC-SR04), dan lain-lain.
Kita akan menggunakan Timer 2 dengan channel 1 (pin PA0) sebagai input capture. Push button digunakan untuk memberikan pulsa input ke channel 1 Timer 2. Ketika push button ditekan maka terjadi perubahan tegangan pada pin PA0, perubahan tegangan inilah yang akan dideteksi oleh input capture.
Pada tutorial ini kita akan menggunakan channel 1 Timer 2 dengan mode rising edge dan falling edge secara bergantian, karena Timer 2 tidak mendukung mode both edges.
Rangkaian skematik STM32F103C8 untuk Input Capture
Salah satu kaki push button dihubungakan ke channel 1 Timer 2 (pin PA0) mikrokontroler STM32F103C8, kaki satunya lagi kita hubungkan ke tegangan 3.3V. Pin PA0 (channel 1 Timer 2) juga akan kita konfigurasi sebagai input pull-down. Berikut merupakan skematik rangkaian yang digunakan, silakan buat rangkaiannya di bredboard:

Timer 2 (TIM2) channel 1Ketika push button ditekan maka pin PA0 (channel 1 Timer 2) akan terhubung ke tegangan 3.3V, sehingga input capture mendeteksi perubahan tegangan (Rising edge). Karena pin PA0 dikonfigurasi sebagai input pull-down maka setelah push button dilepas, pin PA0 akan terhubung ke GND, sehingga input capture mendeteksi perubahan tegangan (Falling edge). Dengan menggunakan selisih nilai register TIMx_CCR saat Rising edge dan Falling edge maka kita akan dapat mengetahui berapa lama push button ditekan.
Memprogram Timer STM32F103C8 sebagai Input Capture dengan Rust
Pertama mari buat project Rust baru sesuai dengan halaman ini. Buka file Cargo.toml lalu tambahkan kode berikut untuk mendefinisikan binary executable baru dengan nama timer-input-capture :
1[[bin]]
2name = "timer-input-capture"
3path = "src/main.rs"
4test = false
5bench = falseSelanjutnya buka file src/main.rs, tambahkan kode berikut untuk memberitahu compiler bahwa program ini tidak menggunakan standard library:
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 timer::Timer,
15};Memeberitahu kompiler untuk tidak menggunakan standard library dan program tidak berjalan diatas sistem operasi.
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.
1defmt::println!("STM32F103C8 Timer as Input Capture");Mengirimkan pesan ke PC/laptop sebagai penanda program input capture.
1let dp = pac::Peripherals::take().unwrap();
2
3let mut flash = dp.FLASH.constrain();
4
5let rcc = dp.RCC.constrain();
6
7let clock_config = Config::default()
8 .use_hse(Hertz::MHz(8))
9 .sysclk(Hertz::MHz(72))
10 .hclk(Hertz::MHz(72))
11 .pclk1(Hertz::MHz(36));
12
13let mut clocks = rcc.freeze(clock_config, &mut flash.acr);Mengakses periferal flash dan RCC (Reset Clock & Control). Kemudian mengonfigurasi dengan menggunakan clock external 8 MHz, system clock ke 72 MHz, clock advance high bus ke 72 MHz, dan clock periferal 1 ke 36 MHz. Sehingga prescaler clock untuk periferal 1 adalah 2.
Seperti yang dijelaskan pada artikel sebelumnya ketika prescaler clock periferal 1 lebih besar dari 1 maka frekuensi clock untuk Timer General Purpose akan menjadi 2 kali clock peroferal 1. Sehingga frekuensi clock Timer General Purpose akan menjadi .
1let mut gpioa = dp.GPIOA.split(&mut clocks);
2
3let channel_1 = gpioa.pa0.into_pull_down_input(&mut gpioa.crl);Mengakses periferal GPIO dan menggunakan pin PA0 (channel 1 Timer 2) sebagai input dengan konfigurasi input pull-down.
1let timer2 = Timer::new(dp.TIM2, &mut clocks);
2
3let mut counter = timer2.counter_hz();Menggunakan periferal Timer 2. Dengan method counter_hz mengaktifkan counter Timer 2.
1let timer_register = unsafe { &*pac::TIM2::ptr() };Mengakses pointer register Timer 2 secara langsung.
1timer_register.ccmr1_input().modify(|_, w| w.cc1s().ti1());Mengaktifkan channel 1 (pin PA0) sebagai input ke Timer 2 (tanpa remap pin).
1timer_register
2 .ccmr1_input()
3 .modify(|_, w| w.ic1f().fck_int_n8());Mengaktifkan filter digital ke channel 1 Timer 2 untuk mengurangi debounce/noise.
1timer_register.ccer().modify(|_, w| w.cc1e().set_bit());Mengaktifkan input capture pada channel 1 Timer 2.
1timer_register.ccer().modify(|_, w| w.cc1p().clear_bit());Mengatur channel 1 Timer 2 agar mendeteksi rising edge.
Catatan: Ketika bit
CCxPbernilai 0 maka channel tersebut akan mendeteksi Rising Edge, sedangkan ketika bernilai 1 maka akan mendeteksi Falling Edge.
1// frekuensi 10KHz dan Arr maks
2counter.start_raw(7199, 65535);Memulai counter dengan konfigurasi prescaler timer (TIMX_PSC) 7199 dan nilai register ARR (TIMX_ARR) ke nilai maksimal yaitu 65535.
Berbeda dengan artikel sebelumnya dimana kita menentukan frekuensi timer secara langsung dan membiarkan stm32f10xx_hal yang menghitung prescaler timer. Pada tutorial kali ini kita yang menentukan Prescaler timer dan mengeset nilai register auto reload ke nilai maksimal agar dapat mengukur waktu lebih lama.
Dengan menggunakan prescaler 7199 maka frekuensi timer akan menjadi 10 kHz (10000 Hz), artinya timer dapat mendeteksi dengan ketelitian . Berikut merupakan perhitungan frekuensi timer tersebut:
1let mut start_press_tick = 0u16;
2
3let mut is_pressed = false;Membuat variabel untuk menyimpan nilai tick ketika push button mulai ditekan dan variabel untuk menyimpan status apakah push button sudah ditekan atau belum.
1loop {
2 if timer_register.sr().read().cc1if().bit_is_set() {
3
4 }
5}Di dalam blok loop kita mengecek apakah terjadi perubahan tegangan pada pin PA0 (channel 1 Timer 2). Ketika bit CCxP di set 0 (mendeteksi rising edge) maka bit CCx1F (Interrupt Flag) akan bernilai 1 jika terjadi perubahan tegangan dari GND ke 3.3V (LOW ke HIGH). Sedangkan jika bit CCxP di set 1 (mendeteksi falling edge) maka bit CCx1F akan bernilai 1 jika terjadi perubahan tegangan dari 3.3V ke GND (HIGH ke LOW). Ketika perubahan tegangan tersebut nilai register TIMx_CNT akan otomatis disalin ke register TIMx_CCR.
1let captured_tick = timer_register.ccr1().read().ccr().bits();
2
3let pin_is_high = channel_1.is_high();
4
5if pin_is_high && !is_pressed {
6 // Push button mulai ditekan
7 start_press_tick = captured_tick;
8 timer_register.ccer().modify(|_, w| w.cc1p().set_bit());
9
10 is_pressed = true;
11 defmt::println!("--- Button Pressed ---");
12 defmt::println!("start: {}", start_press_tick);
13} else if !pin_is_high && is_pressed {
14 // Push button dilepas
15 let delta = captured_tick.wrapping_sub(start_press_tick);
16 timer_register.ccer().modify(|_, w| w.cc1p().clear_bit());
17
18 defmt::println!("end: {} | delta: {}", captured_tick, delta);
19 defmt::println!("press long: {} ms", delta as f32 / 10f32);
20 defmt::println!("----------------------");
21 is_pressed = false;
22}
23
24timer_register.sr().modify(|_, w| w.cc1if().clear_bit());Jika terjadi perubahan tegangan pada pin PA0 (channel 1 Timer 2) maka ambil nilai dari register TIMx_CCR dan cek status pin PA0. Jika status pin PA0 berhilai HIGH dan is_pressed bernilai false maka artinya push button baru mulai di tekan. Jika sebaliknya maka push button selesai ditekan.
Ketika push button baru mulai ditekan (rising edge) perbarui nilai variabel start_press_tick dengan nilai register TIMx_CCR sekarang, kemudian ubah mode deteksi input capture menjadi falling edge dengan mengeset bit CCxP ke 1. Selain itu juga mengubah status variabel is_pressed menjadi true.
Ketika push button selesai ditekan (falling edge) maka cari selisih nilai register TIMx_CCR saat Rising edge dan Falling edge dengan menggunakan method wrapping_sub. Kemudian mengatur kemabali mode deteksi input capture menjadi rising edge dengan mengeset bit CCxP ke 0. Menghitung lama push button ditekan sesuai dengan rumus diatas dan mengeset variabel is_pressed menjadi false.
Kemudian jangan lupa mereset nilai register CCxIF. Sebenarnya register ini akan otomatis direset ketika nilai TIMx_CCR dibaca. Tapi kita mereset manual agar lebih pasti.
Perhitungan durasi tombol ditekan:
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 timer::Timer,
15};
16
17#[entry]
18fn main() -> ! {
19 defmt::println!("STM32F103C8 Timer as Input Capture");
20
21 let dp = pac::Peripherals::take().unwrap();
22
23 let mut flash = dp.FLASH.constrain();
24
25 let rcc = dp.RCC.constrain();
26
27 let clock_config = Config::default()
28 .use_hse(Hertz::MHz(8))
29 .sysclk(Hertz::MHz(72))
30 .hclk(Hertz::MHz(72))
31 .pclk1(Hertz::MHz(36));
32
33 let mut clocks = rcc.freeze(clock_config, &mut flash.acr);
34
35 let mut gpioa = dp.GPIOA.split(&mut clocks);
36
37 let channel_1 = gpioa.pa0.into_pull_down_input(&mut gpioa.crl);
38
39 let timer2 = Timer::new(dp.TIM2, &mut clocks);
40
41 let mut counter = timer2.counter_hz();
42
43 let timer_register = unsafe { &*pac::TIM2::ptr() };
44
45 // 00 output
46 // 01 input
47 // 10 Input, (Cross-mapping channel 2).
48 timer_register.ccmr1_input().modify(|_, w| w.cc1s().ti1());
49
50 timer_register
51 .ccmr1_input()
52 .modify(|_, w| w.ic1f().fck_int_n8());
53
54 timer_register.ccer().modify(|_, w| w.cc1e().set_bit());
55
56 timer_register.ccer().modify(|_, w| w.cc1p().clear_bit());
57
58 counter.start_raw(7199, 65535);
59
60 let mut start_press_tick = 0u16;
61
62 let mut is_pressed = false;
63
64 loop {
65 if timer_register.sr().read().cc1if().bit_is_set() {
66 let captured_tick = timer_register.ccr1().read().ccr().bits();
67
68 let pin_is_high = channel_1.is_high();
69
70 if pin_is_high && !is_pressed {
71 start_press_tick = captured_tick;
72 timer_register.ccer().modify(|_, w| w.cc1p().set_bit());
73
74 is_pressed = true;
75 defmt::println!("--- Button Pressed ---");
76 defmt::println!("start: {}", start_press_tick);
77 } else if !pin_is_high && is_pressed {
78 let delta = captured_tick.wrapping_sub(start_press_tick);
79 timer_register.ccer().modify(|_, w| w.cc1p().clear_bit());
80
81 defmt::println!("end: {} | delta: {}", captured_tick, delta);
82 defmt::println!("press long: {} ms", delta as f32 / 10f32);
83 defmt::println!("----------------------");
84 is_pressed = false;
85 }
86
87 timer_register.sr().modify(|_, w| w.cc1if().clear_bit());
88 }
89 }
90}Dengan menggunakan ST-Link USB Downloader Debuger hubungkan mikrokontroler STM32F103C8 ke PC/laptop. Kemudian jalankan program dengan perintah ‘cargo run --bin timer-input-capture’ pada terminal. Berikut merupakan hasil dari program ketika dijalankan:
Ketika push button mulai ditekan di terminal akan muncul nilai tick saat mulai ditekan, kemudian saat dilepas nilai tick, selisih dan durasi push button ditekan akan muncul di terminal.
Peringatan: Karena timer di set ke frekuensi 10 KHz (mampu mengukur dengan ketelitian 0.1 ms) dan ARR disetting ke maksimal (65535), maka lama maksimal durasi pengukuran makimal adalah . Jika tombol ditekan malebihi waktu tersebut maka akan direset kembali ke 0 dan menyebabkan pengukuran tidak akurat. Hal ini karena nilai CNT direset ke 0 kembali ketika mencapai nilai dan menghitung kembali sampai nilai ARR.
Permasalahan dan Source Code
Masalah dalam menggunakan Input Capture:
- Dalam membuat tutorial ini awalnya kami ingin menggunakan mode both edge. Tetapi karena Timer 2 STM32F103C8 tidak mendukung mode both edges, maka kami mengakalinya dengan mengubah mode dari rising edge ke falling edge ketika tombol mulai ditekan dan mengubah kembali ke rising edge ketika tombol selesai ditekan.
- Durasi maksimal pengukuran adalah . Untuk mengatasi hal ini, solusinya akan kami bahas pada artikel selanjutnya.
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.