Mengubah Pin GPIO Default pada Channel Timer STM32
Pada artikel sebelum-sebelumnya kita selalu menggunakan pin PA0 yang dihubungkan dengan channel 1 pada Timer 2 STM32F103C8. Namun terkadang pin PA0 digunakan untuk keperluan lain (seperti input/output biasa, analog input, atau fungsi alternatif lain), sehingga pin tersebut tidak dapat kita hubungkan ke channel 1 Timer 2. Untuk mengatasi hal tersebut mikrokontroler STM32 menyediakan fitur remap pin untuk channel Timer.
Pengenalan Remap Pin GPIO Channel pada Timer STM32
Remap pin merupakan fitur mikrokontroler STM32 yang memungkinkan kita mengubah jalur pin GPIO ke jalur GPIO lain yang didukung pada periferal-preiferal STM32. Pada Timer STM32 ada dua tipe remap pin yang dapat digunakan:
- Partial Remap: Mengubah sebagian jalur pin GPIO channel pada Timer STM32.
- Full Remap: Mengubah seluruh jalur pin GPIO channel pada Timer STM32.
Ketika menggunakan remap pin channel Timer, hanya salah satu remap yang bisa dipilih. Sehingga kita tidak bisa menggunakan full remap, partial remap 1 dan partial remap 2 secara bersamaan.
Remap Pin Channel pada Timer 1 (TIM1) STM32F1 Series
Berikut merupakan remap pin channel pada Timer 1 mikrokontroler STM32F1 series yang dapat digunakan:
| Channel | Default Pin | Partial Remap | Full Remap |
|---|---|---|---|
| TIM1_CH1 | PA8 | - | PE9* |
| TIM1_CH2 | PA9 | - | PE11* |
| TIM1_CH3 | PA10 | - | PE13* |
| TIM1_CH4 | PA11 | - | PE14* |
| TIM1_CH1N | PB13 | - | PA7 |
| TIM1_CH2N | PB14 | - | PB0 |
| TIM1_CH3N | PB15 | - | PB1 |
| TIM1_BKIN | PB12 | - | PA6 |
Keterangan:
* : Pin tidak ada pada mikrokontroler STM32F103C8 (Blue Pill).
Peringatan: Port E tidak ada pada Timer mikrokontroler STM32F103C8 (Blue Pill), sehingga remap pin tersebut tidak dapat digunakan.
Remap Pin Channel pada Timer 2 (TIM2) STM32F1 Series
Berikut merupakan remap pin channel pada Timer 2 mikrokontroler STM32F1 series yang dapat digunakan:
| Channel | Default Pin | Partial Remap 1 | Partial Remap 2 | Full Remap |
|---|---|---|---|---|
| CH1 / ETR | PA0 | PA15 | - | PB15 |
| CH2 | PA1 | PB3 | - | PB3 |
| CH3 | PA2 | - | PB10 | PB10 |
| CH4 | PA3 | - | PB11 | PA11 |
Remap Pin Channel pada Timer 3 (TIM3) STM32F1 Series
Berikut merupakan remap pin channel pada Timer 3 mikrokontroler STM32F1 series yang dapat digunakan:
| Channel | Default Pin | Partial Remap | Full Remap |
|---|---|---|---|
| CH1 | PA6 | PB4 | PC6* |
| CH2 | PA7 | PB5 | PC7* |
| CH3 | PB0 | - | PC8* |
| CH4 | PB1 | - | PC9* |
Keterangan:
* : Pin tidak ada pada mikrokontroler STM32F103C8 (Blue Pill).
Peringatan: Mikrokontroler STM32F103C8 (Blue Pill) hanya memiliki pin pC13, PC14 , dan PC15. Sehingga pin full remap tersebut tidak dapat digunakan pada mikrokontroler STM32F103C8 (Blue Pill).
Remap Pin Channel pada Timer 4 (TIM4) STM32F1 Series
Berikut merupakan remap pin channel pada Timer 4 mikrokontroler STM32F1 series yang dapat digunakan:
| Channel | Default Pin | Partial Remap | Full Remap |
|---|---|---|---|
| CH1 | PB6 | - | PD12* |
| CH2 | PB7 | - | PD13* |
| CH3 | PB8 | - | PD14* |
| CH4 | PB9 | - | PD15* |
Keterangan:
* : Pin tidak ada pada mikrokontroler STM32F103C8 (Blue Pill).
Peringatan: Port D tidak ada pada Timer mikrokontroler STM32F103C8 (Blue Pill), sehingga remap pin tersebut tidak dapat digunakan.
Persiapan Hardware
Komponen-komponen Hardware yang kita digunakan pada artikel ini sama dengan komponen-komponen pada artikel sebelumnya: Mikrokontroler STM32F103C8, ST-Link USB Downloader/Debuger, Breadboard, Push Button, kabel jumper (male to male dan female to female). Penjelasan lebih detail mengenai fungsi komponen-komponen yang digunakan dapat dilihat pada halaman Setup STM32F103C8 dengan Rust dan halaman Menggunakan GPIO STM32F103C8 dengan Rust.
Implementasi Remap Pin GPIO Channel pada Timer STM32F103C8 (Blue Pill) dengan Rust
Sama seperti artikel sebelumnya, pada tutorial kali ini kita akan membaca durasi (lebar pulsa) push button ditekan dengan fitur cross-mapping. Jika sebelumnya kita menggunakan pin PA0 sebagai input, kali ini kita akan menggunakan pin PA15 sebagai input. Dengan menggunakan fitur partial remap1 pada Timer 2, maka channel 1 yang awalnya terhubung ke pin PA0 akan terhubung ke pin PA15, sedangkan channel 2 yang awalnya terhubung ke pin PA1 akan terhubung ke pin PB3. Berikut merupakan skematik rangkaian yang digunakan, silakan buat rangkaian skematiknya pada breadboard:

Pin PA15 kita konfigurasi menjadi input pull-down. Sehingga ketika push button ditekan tegangan pada pin PA15 akan naik dari 0V ke 3V3, sebaliknya ketika dilepas maka tegangan pada pin PA15 akan turun dari 3.3V ke 0V. Untuk penjelasan lebih mendetail mengenai prinsip kerja skematik rangkaian yang digunakan, silakan baca pada artikel input capture.
Memprogram Timer STM32F103C8 (Blue Pill) dengan Channel Remap Menggunakan Rust
Silakan buat project Rust baru sesuai dengan yang dijelaskan pada artikel ini. Selanjutnya Buka file Cargo.toml, kemudian definisikan binary executable baru dengan nama ’ timer-channel-remap ’ dengan menambahkan kode brikut:
1[[bin]]
2name = "timer-channel-remap"
3path = "src/main.rs"
4test = false
5bench = falseSelanjutnya buka file src/main.rs, lalu isi dengan kode berikut:
Rust
1#![no_std]
2#![no_main]
3
4use core::sync::atomic::{AtomicU32, Ordering};
5
6use defmt_rtt as _;
7use panic_probe as _;
8
9use cortex_m_rt::entry;
10use stm32f1xx_hal::{
11 flash::FlashExt,
12 pac::{self, interrupt},
13 prelude::*,
14 rcc::{Config, RccExt},
15 time::Hertz,
16 timer::{Event, Remap, Tim2PartialRemap1, Timer},
17};Memberitahu kompiler Rust untuk tidak menggunakan standard library dan tidak menjalankan program diatas sistem operasi.
Kemudian mendefinisikan library yang akan digunakan dalam project:
- library
atomicdigunakan untuk mendefinisikan variabel global agar dapat diakses dari bloklooputama dan dapat diakses dari fungsiinterrupt. - Library/crate
cortex_m_rtuntuk menentukan fungsi entry program akan mulai berjalan dan menangani proses startup program. defmt_rttberfungsi untuk mengirimkan data ke PC/laptop untuk logging menggunakan protokol real time transfer.- Library
stm32f1xx_halberfungsi agar kita dapat mengakses periferal mikrokontroler STM32F103C8 secara aman. panic_probedigunakan untuk menangani jika terjadi runtime error, dan akan otomatis mengirimkan log eror yang terjadi ke host PC/laptop.
Rust
1static NUMB_OVERFLOW: AtomicU32 = AtomicU32::new(0);Membuat variabel global dengan nama NUMB_OVERFLOW untuk menyimpan jumlah timer mengalami overflow. Disini kami menggunakan tipe variabel AtomicU32 agar variabel dapat diakses baik dari blok loop utama maupun dari blok fungsi interrupt (Interrupt Service Routine/ISR) secara aman.
pada fungsi main tambahkan kode berikut:
Rust
1defmt::println!("STM32F103C8 Timer Channel Pin Remap");Mengirim pesan ke PC/laptop untuk menandai sebagai program timer dengan channel remap.
Rust
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);Mengonfigurasi clock untuk menggunakan clock external sebesar 8 MHz, kemudian menggunakan clock System* sebesar 72 MHz, clock Advanced-High Bus Periferal sebesar 72 MHz, dan clock Advanced-Periferal Bus 1 sebesar 36 MHz. Sehingga frekuensi clock Timer 2 akan menjadi 72 MHz.
Rust
1let mut gpioa = dp.GPIOA.split(&mut clocks);
2let gpiob = dp.GPIOB.split(&mut clocks);Mengakses periferal GPIO port A dan GPIO port B.
Rust
1let mut afio = dp.AFIO.constrain(&mut clocks);Mengakses periferal Alternate Function Input Output yang akan kita gunakan untuk melakukan remap pada Timer 2.
Rust
1let (pa15, _pb3, _pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4);Menonaktifkan fitur JTAG sehingga kita bisa menggunakan pin PA15 sebagai input/output biasa atau fungsi alternatif lain.
Catatan: Secara default fitur peroferal JTAG diaktifkan untuk melakukan debugging. Sehingga jika kita ingin menggunakan pin yang dipakai oleh JTAG (pin PA15, PB3, dan PB4), maka kita harus menonaktifkan fitur JTAG terlebih dahulu.
Rust
1let pa15 = pa15.into_pull_down_input(&mut gpioa.crh);Mengonfigurasi pin PA15 sebagai input pull-down. Sehingga secara default pin PA15 akan terhubung ke GND (0V).
Rust
1Tim2PartialRemap1::remap(&mut afio.mapr);Melakukan remap pin GPIO dengan tipe remap partial 1 pada Timer 2. Sehingga channel 1 yang awalnya terhubung ke pin PA0 sekarang terhubung ke pin PA15, dan channel 2 yang awalnya terhubung ke pin PA1 sekarang terhubung ke pin PB3. Sedangkan channel 3 dan 4 tetap terhubung dengan pin default.
Rust
1let timer2 = Timer::new(dp.TIM2, &mut clocks);
2
3unsafe {
4 pac::NVIC::unmask(interrupt::TIM2);
5}Mengakses periferal Timer 2 dan mengaktifkan interrupt untuk Timer 2 pada NVIC (Nested Vectored Interrupt Controller).
Rust
1let mut counter = timer2.counter_hz();Melakukan setup Timer 2 sebagai counter
Rust
1counter.listen(Event::Update);Mengaktifkan interrupt untuk event update pada Timer 2 STM32F103C8.
Rust
1let timer_register = unsafe { &*pac::TIM2::ptr() };Mengakses pointer register Timer 2 STM32F103C8 secara langsung.
Rust
1timer_register.ccmr1_input().modify(|_, w| w.cc1s().ti1());
2timer_register.ccmr1_input().modify(|_, w| w.cc2s().ti1());Mengonfigurasi channel 1 dan channel 2 sebagai input capture. Kemudian channel 1 dan channel 2 akan dihubungkan ke pin yang sama yaitu pin PA15 (TI1) karena sudah di-remap dengan remap partial 1.
Rust
1timer_register
2 .ccmr1_input()
3 .modify(|_, w| w.ic1f().fck_int_n8());
4timer_register
5 .ccmr1_input()
6 .modify(|_, w| w.ic2f().fck_int_n8());Mengaktifkan filter digital untuk channel 1 dan channel 2 pada Timer 2 untuk mengurangi debounce.
Rust
1timer_register.ccer().modify(|_, w| w.cc1e().set_bit());
2timer_register.ccer().modify(|_, w| w.cc2e().set_bit());Mengaktifkan input capture channel 1 dan channel 2 pada Timer 2.
Rust
1timer_register.ccer().modify(|_, w| w.cc1p().clear_bit());
2timer_register.ccer().modify(|_, w| w.cc2p().set_bit());Mengonfigurasi agar channel 1 pada Timer 2 mendeteksi Rising edge. Sedangkan channel 2 pada Timer 2 dikonfigurasi untuk mendeteksi Falling edge.
Rust
1counter.start_raw(7199, 65535);Menjalankan Timer 2 dengan prescaler 7199 dan nilai register Auto-Reload sebesar 65535. Sehingga frekuensi timer menjadi 10 KHz
Rust
1let mut start_press_tick = 0u16;
2
3let mut is_pressed = false;Membuat variabel untuk menyimpan nilai tick saat push button mulai ditekan dan menyimpan status kondisi push button.
Rust
1loop {
2
3}Membuat blok loop untuk menjalankan program utama secara terus-menerus.
Di dalam blok loop kita isi dengan kode berikut:
Rust
1if counter.get_interrupt().contains(Event::C1) {
2 let captured_tick = timer_register.ccr1().read().ccr().bits() as u16;
3
4 let pin_is_high = pa15.is_high();
5
6 if pin_is_high && !is_pressed {
7 start_press_tick = captured_tick;
8 NUMB_OVERFLOW.store(0, Ordering::SeqCst);
9
10 is_pressed = true;
11 defmt::println!("--- Button Pressed ---");
12 defmt::println!("start: {}", start_press_tick);
13 }
14 counter.clear_interrupt(Event::C1);
15}Di dalam blok loop kita mengecek event capture/compare pada channel 1. Jika ada event capture/compare artinya terjadi rising edge pada pin PA15, maka ambil nilai pada register Capture/Compare channel 1 Timer 2. Selanjutnya lihat status dari pin PA15. Jika pin PA15 bernilai HIGH dan nilai variabel is_pressed false, maka update nilai variable start_press_tick dengan nilai tick yang sudah diambil, kemudian reset nilai variabel global NUMB_OVERFLOW ke 0, dan mengubah nilai variabe is_pressed ke true. Jangan lupa untuk membersihkan capture/compare interrupt flag channel 1 meskipun biasanya secara otomatis dibersihkan.
Rust
1if counter.get_interrupt().contains(Event::C2) {
2 let captured_tick = timer_register.ccr2().read().ccr().bits() as u16;
3
4 let pin_is_high = pa15.is_high();
5 if !pin_is_high && is_pressed {
6 let total_overflow = NUMB_OVERFLOW.load(Ordering::SeqCst);
7
8 let total_tick =
9 (total_overflow * 65536) + captured_tick as u32 - start_press_tick as u32;
10
11 defmt::println!(
12 "end: {} | total_overflow: {}",
13 captured_tick,
14 total_overflow
15 );
16 defmt::println!("press long: {} ms", total_tick as f32 / 10f32);
17 defmt::println!("----------------------");
18 is_pressed = false;
19 }
20
21counter.clear_interrupt(Event::C2);
22}mengecek event capture/compare pada channel 2. Jika ada event capture/compare artinya terjadi falling edge pada pin PA15, maka ambil nilai pada register Capture/Compare channel 2 Timer 2. Selanjutnya lihat status dari pin PA15. Jika pin PA15 bernilai LOW dan nilai variabel is_pressed true, maka hitung total tick selama push buttong ditekan. Selanjutnya menggunakan total tick untuk menghitung durasi push button ditekan, dan mengubah nilai is_pressed ke false. Jangan lupa untuk membersihkan capture/compare interrupt flag channel 2 meskipun biasanya secara otomatis dibersihkan.
Selanjutnya membuat fungsi (Interrupt Service Routine/ISR) yang akan dieksekusi CPU ketika terjadi interrupt pada Timer 2, berikut merupakan fungsi interrupt yang akan kita gunakan.
Rust
1#[interrupt]
2fn TIM2() {
3 let timer_register = unsafe { &*pac::TIM2::ptr() };
4
5 // check update interrupt flag
6 if timer_register.sr().read().uif().bit_is_set() {
7 // Increment overflow count
8 NUMB_OVERFLOW.fetch_add(1, Ordering::SeqCst);
9 // Clear update interrupt flag
10 timer_register.sr().modify(|_, w| w.uif().clear_bit());
11 }
12}Membuat fungsi interrupt (ISR) untuk Timer 2. Di dalam fungsi tersebut kita mengecek update interrupt flag. Jika interrupt disebabkan oleh event update maka nilai global variabel NUMB_OVERFLOW akan kita tambah 1, dan selanjutnya membersihkan bit update interrupt flag.
1#![no_std]
2#![no_main]
3
4use core::sync::atomic::{AtomicU32, Ordering};
5
6use defmt_rtt as _;
7use panic_probe as _;
8
9use cortex_m_rt::entry;
10use stm32f1xx_hal::{
11 flash::FlashExt,
12 pac::{self, interrupt},
13 prelude::*,
14 rcc::{Config, RccExt},
15 time::Hertz,
16 timer::{Event, Remap, Tim2PartialRemap1, Timer},
17};
18
19static NUMB_OVERFLOW: AtomicU32 = AtomicU32::new(0);
20
21#[entry]
22fn main() -> ! {
23 defmt::println!("STM32F103C8 Timer Channel Pin Remap");
24
25 let dp = pac::Peripherals::take().unwrap();
26
27 let mut flash = dp.FLASH.constrain();
28
29 let rcc = dp.RCC.constrain();
30
31 let clock_config = Config::default()
32 .use_hse(Hertz::MHz(8))
33 .sysclk(Hertz::MHz(72))
34 .hclk(Hertz::MHz(72))
35 .pclk1(Hertz::MHz(36));
36
37 let mut clocks = rcc.freeze(clock_config, &mut flash.acr);
38
39 let mut gpioa = dp.GPIOA.split(&mut clocks);
40 let gpiob = dp.GPIOB.split(&mut clocks);
41
42 let mut afio = dp.AFIO.constrain(&mut clocks);
43
44 let (pa15, _pb3, _pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4);
45
46 let pa15 = pa15.into_pull_down_input(&mut gpioa.crh);
47
48 // Use remap partial 1 for Timer 2
49 Tim2PartialRemap1::remap(&mut afio.mapr);
50
51 let timer2 = Timer::new(dp.TIM2, &mut clocks);
52
53 unsafe {
54 pac::NVIC::unmask(interrupt::TIM2);
55 }
56
57 let mut counter = timer2.counter_hz();
58
59 counter.listen(Event::Update);
60
61 let timer_register = unsafe { &*pac::TIM2::ptr() };
62
63 timer_register.ccmr1_input().modify(|_, w| w.cc1s().ti1());
64 timer_register.ccmr1_input().modify(|_, w| w.cc2s().ti1());
65
66 timer_register
67 .ccmr1_input()
68 .modify(|_, w| w.ic1f().fck_int_n8());
69 timer_register
70 .ccmr1_input()
71 .modify(|_, w| w.ic2f().fck_int_n8());
72
73 timer_register.ccer().modify(|_, w| w.cc1e().set_bit());
74 timer_register.ccer().modify(|_, w| w.cc2e().set_bit());
75
76 timer_register.ccer().modify(|_, w| w.cc1p().clear_bit());
77 timer_register.ccer().modify(|_, w| w.cc2p().set_bit());
78
79 counter.start_raw(7199, 65535);
80
81 let mut start_press_tick = 0u16;
82
83 let mut is_pressed = false;
84
85 loop {
86 if counter.get_interrupt().contains(Event::C1) {
87 let captured_tick = timer_register.ccr1().read().ccr().bits() as u16;
88
89 let pin_is_high = pa15.is_high();
90
91 if pin_is_high && !is_pressed {
92 start_press_tick = captured_tick;
93 NUMB_OVERFLOW.store(0, Ordering::SeqCst);
94
95 is_pressed = true;
96 defmt::println!("--- Button Pressed ---");
97 defmt::println!("start: {}", start_press_tick);
98 }
99 counter.clear_interrupt(Event::C1);
100 }
101
102 if counter.get_interrupt().contains(Event::C2) {
103 let captured_tick = timer_register.ccr2().read().ccr().bits() as u16;
104
105 let pin_is_high = pa15.is_high();
106 if !pin_is_high && is_pressed {
107 let total_overflow = NUMB_OVERFLOW.load(Ordering::SeqCst);
108
109 let total_tick =
110 (total_overflow * 65536) + captured_tick as u32 - start_press_tick as u32;
111
112 defmt::println!(
113 "end: {} | total_overflow: {}",
114 captured_tick,
115 total_overflow
116 );
117 defmt::println!("press long: {} ms", total_tick as f32 / 10f32);
118 defmt::println!("----------------------");
119 is_pressed = false;
120 }
121
122 counter.clear_interrupt(Event::C2);
123 }
124 }
125}
126
127#[interrupt]
128fn TIM2() {
129 let timer_register = unsafe { &*pac::TIM2::ptr() };
130
131 // check update interrupt flag
132 if timer_register.sr().read().uif().bit_is_set() {
133 // Increment overflow count
134 NUMB_OVERFLOW.fetch_add(1, Ordering::SeqCst);
135 // Clear update interrupt flag
136 timer_register.sr().modify(|_, w| w.uif().clear_bit());
137 }
138}Dengan menggunakan ST-Link USB Downloader dan Debuger hubungkan Board Blue Pill (mikrokontroler STM32F103C8) dengan PC/laptop seperti yang dijelaskan pada artikel ini. Setelah itu jalan kan program dengan perintah ‘cargo run --bin timer-channel-remap’ pada terminal VSCode. Berikut merupakan hasil ketika program dijalankan:
Dengan menggunakan fitur remap pin pada Timer kita bisa mengubah jalur pin GPIO channel ke pin lain yang didukung. Dalam tutorial ini kita mengubah jalur pin GPIO channel 1 Timer 2 dari PA0 ke PA15 dengan menggunakan partial remap 1.
Source Code
Source code project Rust 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.