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:

  1. Partial Remap: Mengubah sebagian jalur pin GPIO channel pada Timer STM32.
  2. 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:

ChannelDefault PinPartial RemapFull Remap
TIM1_CH1PA8-PE9*
TIM1_CH2PA9-PE11*
TIM1_CH3PA10-PE13*
TIM1_CH4PA11-PE14*
TIM1_CH1NPB13-PA7
TIM1_CH2NPB14-PB0
TIM1_CH3NPB15-PB1
TIM1_BKINPB12-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:

ChannelDefault PinPartial Remap 1Partial Remap 2Full Remap
CH1 / ETRPA0PA15-PB15
CH2PA1PB3-PB3
CH3PA2-PB10PB10
CH4PA3-PB11PA11

Remap Pin Channel pada Timer 3 (TIM3) STM32F1 Series

Berikut merupakan remap pin channel pada Timer 3 mikrokontroler STM32F1 series yang dapat digunakan:

ChannelDefault PinPartial RemapFull Remap
CH1PA6PB4PC6*
CH2PA7PB5PC7*
CH3PB0-PC8*
CH4PB1-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:

ChannelDefault PinPartial RemapFull Remap
CH1PB6-PD12*
CH2PB7-PD13*
CH3PB8-PD14*
CH4PB9-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:

Rangkaian push button dengan STM32F103C8 untuk input capture pin remap
Rangkaian push button dan STM32F103C8 sebagai input capture pada channel 1 Timer 2 dengan pin remap

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 = false

Selanjutnya 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 atomic digunakan untuk mendefinisikan variabel global agar dapat diakses dari blok loop utama dan dapat diakses dari fungsi interrupt.
  • 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 mikrokontroler STM32F103C8 secara aman.
  • panic_probe digunakan 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.

Tampilkan kode full: Timer STM32F103C8 dengan Channel Remap
  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.