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:
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.
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.
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.
Frozen (
OCxM=0b000)Perbandingan antara CNT dan CCR1 tidak mengubah status pin channel. Digunakan untuk memicu interupsi tanpa mengubah status pada pin channel.
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.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:
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

Board sistem minimum STM32F103C8T6 (Blue Pill) 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 untuk memrogram board Blue Pill Breadboard
Breadboard (Project Board) digunakan untuk membuat prototipe rangkaian elektronik tanpa perlu menggunakan solder.

Breadboard (Project Board) 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.
Resistor
Resistor digunakan sebagai pengaman untuk LED dari arus listrik berlebihan. Kami menggunakan resistor 0.5 Watt dengan nilai tahanan 220 Ohm.
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:

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 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 = trueBuka 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_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.
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
, maka frekusnsi clock untuk timer 2 adalah
.
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.
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