Mengubah (Toggle) Status Pin GPIO dengan Output Compare Timer Blue Pill

Pada artikel sebelumnya kita sudah membahas mengenai input capture, pada artikel ini kita akan membahas mengenai output compare. Output compare merupakan kebalikan dari input capture. Jika pada input capture nilai register counter (TIMx_CNT) di salin ke register capture/compare (TIMx_CCRx) ketika terjadi perubahan tegangan pada pin yang terhubung ke channel Timer. Pada output compare justru akan mengubah status pin (High/Low) ketika nilai register counter (TIMx_CNT) sama dengan nilai register capture/compare (TIMx_CCRx).

Pengenalan Output Compare pada Timer STM32

Output compare merupakan fitur Timer STM32 yang digunakan untuk mengubah status pin yang terhubung ke channel Timer. Status pin tersebut akan berubah ketika nilai register counter (TIMx_CNT) sama dengan nilai register capture/compare (TIMx_CCRx).

Dengan menggunakan output compare kita dapat mengubah status pin GPIO pada periode tertentu secara akurat tanpa perlu menggunakan CPU, sehingga bisa menghemat resource CPU.

Ada beberapa mode output compare yang dapat diatur melalu register capture/compare mode (CCMR1) pada 3 bit output compare mode (OCxM). Berikut merupakan jenis mode output compare pada Timer STM32:

  1. Toggle Mode (OCxM=0b011)

    Setiap kali nilai register counter sama dengan nilai register capture compare ( CNT = CCR1 ), status pin akan dibalik. Jika awalnya HIGH,menjadi menjadi LOW. Jika awalnya LOW, maka akan menjadi HIGH.

    Contoh penggunaan untuk membuat lampu berkedip setiap waktu tertentu secara presisi.

  2. PWM Mode 1 (OCxM=0b110)

    Pin channel akan HIGH selama nilai register counter lebih kecil dari nilai register capture compare ( CNT < CCR ), dan akan menjadi LOW saat nilai register counter lebih besar atau sama dengan dari nilai register capture compare ( CNT >= CCR ).

    Digunakan untuk kontrol presisi intensitas cahaya atau kecepatan motor.

  3. PWM Mode 2 (OCxM=0b111)

    Pin channel akan LOW selama nilai register counter lebih kecil dari nilai register capture compare ( CNT < CCR ), dan akan menjadi HIGH saat nilai register counter lebih besar atau sama dengan dari nilai register capture compare ( CNT >= CCR ).

    Digunakan untuk kontrol presisi intensitas cahaya atau kecepatan motor yang aktif LOW.

  4. Frozen (OCxM=0b000)

    Perbandingan antara CNT dan CCR1 tidak mengubah status pin channel. Digunakan untuk memicu interupsi tanpa mengubah status pada pin channel.

  5. Active on Match (OCxM=0b001) dan Inactive on Match (OCxM=0b010)

    Active: Pin channel menjadi High segera ketika nilai register counter sama dengan dengan nilai register capture compare ( CNT = CCR1 ). Setelah itu status pin tidak akan berubah meskipun lagi nilai register counter sama dengan dengan nilai register capture compare.

    Inactive: Pin channel menjadi LOW segera ketika nilai register counter sama dengan dengan nilai register capture compare ( CNT = CCR1 ). Setelah itu status pin tidak akan berubah meskipun lagi nilai register counter sama dengan dengan nilai register capture compare.

  6. Force Active (OCxM=0b101) dan Force Inactive (OCxM=0b100)

    Memaksa pin menjadi High atau Low secara instan tanpa memperdulikan nilai counter.

Persiapan Hardware

Berikut merupakan komponen-komponen yang digunakan pada tutorial kali ini:

  1. Mikrokontroler STM32F103C8 (Blue Pill)

    Mikrokontroler STM32F103C8 (Blue Pill) merupakan salah satu varian dari mikrokontroler STM32. Mikrokontroler ini akan kita program menggunakan bahasa pemrograma Rust untuk mengakses fitur output compare

    Blue Pill (STM32F103C8)
    Board sistem minimum STM32F103C8T6 (Blue Pill)
  2. ST-LINK USB Downloader Debuger

    ST-LINK USB Downloader Debuger berfungsi sebagai penghubung antara PC/laptop dengan STM32F103C8, sehingga kita bisa memprogram STM32F103C8 dari PC/laptop.

    ST-LINK USB Downloader Debuger
    ST-Link USB Downloader Debuger untuk memrogram board Blue Pill
  3. Breadboard

    Breadboard (Project Board) digunakan untuk membuat prototipe rangkaian elektronik tanpa perlu menggunakan solder.

    Breadboard (Project Board)
    Breadboard (Project Board)
  4. Light Emitting Diode (LED)

    LED merupakan jenis dioda yang dapat memancarkan cahaya ketika diberi tegangan forward bias. Pada tutorial ini kami menggunakan 2 buah LED 3mm: warna putih dan biru.

  5. Resistor

    Resistor digunakan sebagai pengaman untuk LED dari arus listrik berlebihan. Kami menggunakan resistor 0.5 Watt dengan nilai tahanan 220 Ohm.

  6. Jumper female to female

    Kabel jumper femal to female digunakan untuk menghubungkan Blue Pill ke ST-LINK USB Downloader Debuger. Sedangkan kabel jumper male to male digunakan untuk memebuat rangkaian di breadboard.

Output Compare dengan Timer STM32F103C8 (Blue Pill) menggunakan Rust

Pada tutorial kali ini kita akan memprogram STM32F103C8 untuk membuat LED berkedip setiap 2 detik menggunakan output compare Timer 2 channel 1 dan channel 2 tanpa remap. LED putih dihubungkan ke pin PA0 dan LED biru dihubungkan ke pin PA1. Kita akan membuat kedua LED tersebut menyala secara bergantian setiap 2 detik.

Rangkaian Skematik untuk Output Compare

Dengan menggunakan komponen-komponen diatas, silakan buat rangkaian pada breadboard sesuai dengan gambar skematik berikut:

Skematik LED dan Blue Pill untuk Output Compare
Skematik LED dan Blue Pill untuk Output Compare

Pada gambar skematik tersebut, ketika pin PA0 bernilai HIGH maka LED putih akan menyala dan ketika pin PA0 bernilai LOW maka LED putih akan mati. Begitu pula ketika pin PA1 bernilai HIGH maka LED biru akan menyala dan ketika pin PA1 bernilai LOW maka LED biru akan mati.

Pemrograman Timer STM32F103C8 (Blue Pill) sebagai Output Compare Menggunakan Rust

Pertama kita akan membuat project Rust untuk embedded system sesuai dengan yang dijelaskan pada artikel ini. Buka file Cargo.toml kemudian tambahkan kode berikut untuk mendefiniskan binary executable baru dengan nama 'timer-output-compare' :

Toml

1[[bin]]
2name = "timer-output-compare"
3path = "src/main.rs"
4test = false
5bench = false
Tampilkan kode full: Cargo.toml
 1[package]
 2name = "timer-output-compare"
 3version = "0.1.0"
 4edition = "2024"
 5
 6[[bin]]
 7name = "timer-output-compare"
 8path = "src/main.rs"
 9test = false
10bench = false
11
12[dependencies]
13cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
14cortex-m-rt = "0.7.5"
15stm32f1xx-hal = { version = "0.11.0", features = ["stm32f103", "medium"] }
16panic-probe = { version = "1.0", features = ["print-defmt"] }
17defmt = "1.0.1"
18defmt-rtt = "1.1.0"
19
20[profile.dev]
21opt-level = 's'
22codegen-units = 1
23
24[profile.release]
25opt-level = 'z'
26lto = true

Buka file src/main.rs, lalu isi dengan kode berikut:

Rust

 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::{self},
11    prelude::*,
12    rcc::{Config, RccExt},
13    time::Hertz,
14    timer::Timer,
15};

Mengonfigurasi Rust toolchain agar tidak menggunakan standard library dan program tidak dijalankan dalam sistem operasi (bare-metal).

Mendefinisikan semua library yang akan digunakan:

  • 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.

Di dalam fungsi main isi dengan kode berikut:

Rust

1defmt::println!("STM32F103C8 Timer Ouput Compare");

Mengirim pesan ke terminal PC/laptop untuk menandai sebagai program output compare.

Rust

1let dp = pac::Peripherals::take().unwrap();

Mengakses periferal device mikrokontroler STM32F103C8.

Rust

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

Mengonfigurasi clock dengan menggunakan clock external ( use_hse ) 8 MHz, kemudian mengatur clock system ( sysclk ) ke 72 MHz dan clock advance high performace bus ( hclk ) ke 72 MHz, dan clock peripheral 1 ( pclk1 ) ke 36 MHz. Karena prescalare Periferal 1 lebih besar dari 1 (7236=2)(\frac {72} {36}=2) , maka frekusnsi clock untuk timer 2 adalah 36×2=72Mhz{36}\times {2}=72 Mhz .

Rust

1let mut gpioa = dp.GPIOA.split(&mut clocks);
2
3let _channel_1 = gpioa.pa0.into_alternate_push_pull(&mut gpioa.crl);
4let _channel_2 = gpioa.pa1.into_alternate_push_pull(&mut gpioa.crl);

Mengakses periferal GPIO port A dan mengonfigurasi pin PA0 dan PA1 sebagai output alternate push-pull.

Rust

1let timer2 = Timer::new(dp.TIM2, &mut clocks);
2
3let mut counter = timer2.counter_hz();

Mengakses periferal Timer 2 (TIM2) dan mengonfigurasi timer sebagai counter.

Rust

1let timer2_register = unsafe { &*pac::TIM2::ptr() };

Mengakses pointer register Timer 2.

Rust

1// OC1M = 0b011 (Toggle mode for Channel)
2timer2_register
3    .ccmr1_output()
4    .modify(|_, w| unsafe { w.oc1m().bits(0b011) });
5timer2_register
6    .ccmr1_output()
7    .modify(|_, w| unsafe { w.oc2m().bits(0b011) });

Mengatur mode output compare channel 1 dan channel 2 ke mode toggle.

Rust

1// enable capture/compare
2timer2_register.ccer().modify(|_, w| w.cc1e().set_bit());
3timer2_register.ccer().modify(|_, w| w.cc2e().set_bit());

Mengaktifkan capture/compare channel 1 dan channel 2.

Rust

1// set polarity output
2// PA0 will be HIGH at first toggle
3timer2_register.ccer().modify(|_, w| w.cc1p().clear_bit());
4// PA1 will be LOW saat first toggle
5timer2_register.ccer().modify(|_, w| w.cc2p().set_bit());

Mengatur polarity dari capture/compare channel Timer 2. Sehingga channel 1 akan HIGH ketika toggle pertama, dan channel 2 akan LOW ketika toggle pertama.

Rust

1// mengeset ccr ke 0
2timer2_register.ccr1().write(|w| unsafe { w.bits(0) });
3timer2_register.ccr2().write(|w| unsafe { w.bits(0) });

Mengatur nilai capture/compare channel 1 ke 0. Sehingga ketika nilai register counter bernilai 0 maka status pin PA0 dan pin PA1 akan di-toggle.

Rust

1// Reset counter register start from 0 (optional)
2timer2_register.cnt().write(|w| unsafe { w.cnt().bits(0) });

Mengatur nilai register counter ke 0.

Rust

1counter.start_raw(7199, 20000);

Menjalankan counter dengan prescaler 7199 dan nilai auto reload register 20000.

Rust

1loop {
2    cortex_m::asm::nop();
3}

Pada blok loop CPU tidak melakukan operasi apapun.

Tampilkan kode full: src/main.rs
 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::{self},
11    prelude::*,
12    rcc::{Config, RccExt},
13    time::Hertz,
14    timer::Timer,
15};
16
17#[entry]
18fn main() -> ! {
19    defmt::println!("STM32F103C8 Timer Ouput Compare");
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_alternate_push_pull(&mut gpioa.crl);
38    let _channel_2 = gpioa.pa1.into_alternate_push_pull(&mut gpioa.crl);
39
40    let timer2 = Timer::new(dp.TIM2, &mut clocks);
41
42    let mut counter = timer2.counter_hz();
43
44    let timer2_register = unsafe { &*pac::TIM2::ptr() };
45
46    // OC1M = 0b011 (Toggle mode for Channel)
47    timer2_register
48        .ccmr1_output()
49        .modify(|_, w| unsafe { w.oc1m().bits(0b011) });
50    timer2_register
51        .ccmr1_output()
52        .modify(|_, w| unsafe { w.oc2m().bits(0b011) });
53
54    // enable capture/compare
55    timer2_register.ccer().modify(|_, w| w.cc1e().set_bit());
56    timer2_register.ccer().modify(|_, w| w.cc2e().set_bit());
57
58    // set polarity output
59    // PA0 will be HIGH at first toggle
60    timer2_register.ccer().modify(|_, w| w.cc1p().clear_bit());
61    // PA1 will be LOW saat first toggle
62    timer2_register.ccer().modify(|_, w| w.cc2p().set_bit());
63
64    // mengeset ccr ke 0
65    timer2_register.ccr1().write(|w| unsafe { w.bits(0) });
66    timer2_register.ccr2().write(|w| unsafe { w.bits(0) });
67
68    // Reset counter register start from 0 (optional)
69    timer2_register.cnt().write(|w| unsafe { w.cnt().bits(0) });
70
71    counter.start_raw(7199, 20000); // or use this
72
73    loop {
74        cortex_m::asm::nop();
75    }
76}

Jalankan program dengan perintah ‘cargo run --bin timer-output-compare’ pada terminal di dalam folder project.

Source Code

Source code yang digunakan pada artikel ini dapat diakses di repositori Github

Jika anda mengalami kendala dalam mengikuti tutorial ini jangan ragu untuk menghubungi kami melalui halaman kontak