Rust + STM32 Blue Pill: Panduan Lengkap Input-Output untuk Pemula
Sebelum Anda memulai tutorial ini, pastikan anda sudah membaca artikel sebelumnya tentang Setup STM32F103C8 dengan Rust di Linux. Karena struktur project Rust dan hardware yang digunakan pada tutorial ini sama dengan pada artikel tersebut. Untuk memudahkan pemahaman anda terhadap kode Rust yang disajikan pada artikel ini, kami akan memberikan penjelasan berupa komentar fungsi dari kode Rust di setiap barisnya.
STM32F103C8 dilengkapi dengan periferal GPIO (General Purpose Input Output) yang dapat kita program sesuai dengan tujuan kita. Setiap pin GPIO dapat dikonfigurasi sebagai input, output, atau fungsi periferal alternatif. Pada tutorial ini kita akan fokus pada memprogram GPIO STM32F103C8 sebagai input dan output, untuk fungsi periferal alternatif (PWM, UART, SPI, dan lain-lain) akan kita bahas secara mendetail pada artikel berikutnya.
1. Persiapan Hardware
Selain menggunakan hardware di artikel sebelumnya, kita juga akan menggunakan beberapa hardware tambahan seperti Breadboard, Push Button dan beberapa kabel jumper male to male untuk membuat rangakaian di breadboard.
1.1 Breadboard
Breadboard (Project Board) merupakan papan yang digunakan untuk membuat prototipe rangkaian elektronik. Dengan menggunakan Breadborad prototipe rangkaian elektronik dapat dibuat lebih cepat dan rapi tanpa menggunakan solder.

1.2 Push Button
Push Button akan kita gunakan sebagai input ke pin GPIO STM32F103C8. Kedua kaki pada Push Button akan saling terhubung ketika Push Button ditekan, sehingga memungkinkan arus listrik mengalir dia antara kedua kakinya.

2. Memprogram GPIO STM32F103C8 sebagai Output dengan Rust
Pin GPIO sebagai output berfungsi sebagai jalur keluar sinyal listrik dari mikrokontroler untuk mengontrol perangkat lain (seperti relay, transistor, LED kecil, dan lain-lain). Pada STM32F103C8 tegangan pin output adalah 3.3V dengan arus rekomendasi setiap pin adalah 8 mA, kecuali pin PC13, PC14, PC15 yang hanya mampu menyuplai 3 mA. Sehingga jika kita ingin mengontrol perangkat dengan arus yang besar dan tegangan yang lebih tinggi kita harus menggunakan perangkat tambahan seperti relay dan transistor.
2.1 Konfigurasi Pin GPIO STM32F103C8 sebagai Output
Pin GPIO STM32F103C8 ketika digunakan sebagai output dapat dikonfigurasi menjadi 2 mode:
- Output Push-Pull: Ketika pin diatur ke logika HIGH maka pin akan terhubung ke VCC sehingga dapat mengeluarkan arus listrik (Source). Sedangkan ketika pin diatur ke logika LOW, pin akan terhubung ke GND sehingga menyedot arus listrik ke GND (Sink).
- Output Open Drain: Ketika pin diatur ke logika HIGH, pin tidak akan terhubung ke VCC atau pun GND (floating). Sedangkan ketika pin diatur ke logika LOW, pin akan terhubung GND dan akan menyedot arus listrik ke GND.
2.2 Memprogram Pin GPIO STM32F103C8 sebagai Output
Pada tutorial ini kita akan mencoba mengontrol on dan off builtin LED pada board STM32F103C8 Blue Pill. Builtin LED pada board Blue Pill terhubung ke pin PC13 dan active-low, artinya LED akan menyala ketika pin diatur ke logika LOW.

Pertama kita akan membuat project Rust baru sesuai dengan artikel sebelumnya. Pada project Rust tersebut buka file src/main.rs lalu ganti dengan kode berikut:
1#![no_std]
2#![no_main]
3
4use defmt_rtt as _;
5use panic_probe as _;
6
7use cortex_m::delay::Delay;
8use cortex_m_rt::entry;
9use stm32f1xx_hal::{
10 flash::FlashExt,
11 gpio::GpioExt,
12 pac,
13 rcc::{Config, RccExt},
14 time::Hertz,
15};
16
17#[entry]
18fn main() -> ! {
19 defmt::println!("STM32F103C8 GPIO as Output");
20
21 // Mengakses periferal STM32F103C8
22 let dp = pac::Peripherals::take().unwrap();
23
24 // Mengakses core peripherals (cortex-M3) STM32F103C8
25 let cp = pac::CorePeripherals::take().unwrap();
26
27 // Mengakses periferal FLASH yang akan digunakan untuk menyesuaikan
28 // 'wait states' saat konfigurasi Clock.
29 let mut flash = dp.FLASH.constrain();
30
31 // Mengakses periferal Reset & Control Clock (RCC)
32 // agar dapat dikonfigurasi
33 let rcc = dp.RCC.constrain();
34
35 // membuat konfigurasi Reset & Control clock (RCC)
36 let clock_config = Config::default()
37 // menggunakan clock external 8MHZ
38 .use_hse(Hertz::MHz(8))
39 //mengatur frekuensi CPU ke 72MHZ
40 .sysclk(Hertz::MHz(72))
41 //mengatur frekuensi BUS (jalur akses SRAM dan FLASH) ke 72MHZ
42 .hclk(Hertz::MHz(72));
43
44 // mengaplikasikan konfigurasi clock ke rcc dan
45 // menyesuaikan 'wait states' memori flash
46 let mut clocks = rcc.freeze(clock_config, &mut flash.acr);
47
48 // membuat instans delay dengan menggunakan Timer System.
49 let mut delay = Delay::new(cp.SYST, clocks.clocks.hclk().to_Hz());
50
51 // Mengakses periferal GPIO Port C
52 let mut gpioc = dp.GPIOC.split(&mut clocks);
53
54 // Mengatur GPIOC pin 13 (PC13) sebagai output open-drain untuk
55 // menyalakan dan mematikan LED. (crl: pin 0-7; crh: pin 8-15)
56 let mut led = gpioc.pc13.into_open_drain_output(&mut gpioc.crh);
57
58 // Tunggu 5 detik dan lihat kondisi LED on atau off?
59 defmt::println!("Default logic value of pin output:");
60 defmt::println!("\tlow: {}", led.is_set_low());
61 defmt::println!("\thigh: {}", led.is_set_high());
62 delay.delay_ms(5000);
63
64 // disini program akan dijalankan secara berulang terus menerus
65 loop {
66 // mengirim pesan ke PC/laptop
67 defmt::println!("LED off");
68 // atur pin led (PC13) ke logika HIGH
69 led.set_high();
70 // Delay selama 2 detik
71 delay.delay_ms(2000);
72
73 // mengirim pesan ke PC/laptop
74 defmt::println!("LED on");
75 // atur pin led (PC13) ke logika LOW
76 led.set_low();
77 // Delay selama 2 detik
78 delay.delay_ms(2000);
79 }
80}Jalankan program dengan perintah ‘cargo run --bin gpio-output’ di terminal VSCode. Berikut merupakan hasil dari program yang kita buat:
Ketika pin PC13 diatur ke HIGH maka pin akan bersifat floating (tidak terhubung ke VCC atau GND) sehingga tidak ada arus listrik yang mengalir dan LED tidak menyala. Sebaliknya ketika pin PC13 diatur ke LOW maka pin akan terhubung ke GND menyebabkan adanya aliran listrik ke GND dan LED menyala.
Pro Tip: Untuk menghentikan proses debug pada terminal PC/laptop bisa dengan menekan tombol keyboard CTRL+C secara bersamaan.
Pada hasil tersebut juga dapat kita lihat bahwa secara default pin output akan berlogika LOW, sehingga pada saat mikrokontroler baru dinyalakan LED akan menyala. Hal ini akan sangat berbahaya jika kita ingin mengontrol perangkat yang active-low, karena perangkat akan langsung menyala ketika mikrokontroler baru dinyalakan. Untuk mengatasi hal tersebut kita bisa mengganti kode program pada baris 56 dengan kode berikut:
1let mut led = gpioc
2 .pc13
3 // pin sebagai output open-drain dengan state HIGH
4 .into_open_drain_output_with_state(&mut gpioc.crh, PinState::High);Dengan menggunakan kode tersebut pin PC13 ketika diatur sebagai output akan langsung HIGH sehingga LED tidak akan menyala.
Sebenarnya kita juga bisa menggunakan mode output push-pull, dengan cara mengganti lagi kode program pada baris 56 dengan kode berikut:
1let mut led = gpioc
2 .pc13
3 // pin sebagai output push-pull dengan state HIGH
4 .into_push_pull_output_with_state(&mut gpioc.crh, PinState::High);Ketika pin PC13 diatur ke HIGH maka pin akan terhubung ke VCC sehingga tidak ada beda potensial antara anoda dan katoda LED, sehingga tidak ada arus listrik yang mengalir dan LED tidak menyala. Sebaliknya ketika pin PC13 diatur ke LOW maka pin akan terhubung ke GND menyebabkan adanya aliran listrik ke GND dan LED menyala.
Peringatan: Ketika menggunakan mode push-pull untuk mengontrol perangkat yang menggunakan VCC>3.3V maka akan memunculkan beda potensial, sehingga perangkat mungkin akan tetap menyala meskipun pin sudah diatur ke HIGH.
3. Memprogram GPIO STM32F103C8 sebagai Input dengan Rust
Pin GPIO sebagai input berfungsi untuk membaca level tegangan dari perangkat luar. Toleransi tegangan yang dapat diterima pin STM32F103C8 adalah maksimal 3.6V, tetapi ada beberapa pin yang bisa menerima tegangan hingga 5V yang lebih detail dapat dilihat pada datasheet STM32F103C8.
3.1 Konfigurasi Pin GPIO STM32F103C8 sebagai Input
Pin GPIO STM32F103C8 ketika digunakan sebagai input dapat dikonfigurasi menjadi 4 mode:
- Input Floating: Pin tidak terhubung ke resistor pull-up atau pun resistor pull down. Untuk menggunakan mode ini perangkat luar harus memiliki pengkondisian tegangan sendiri ke pin, jika tidak nilainya akan noise (berubah-ubah)
- Input Pull-Up: Pin terhubung ke resistor internal pull-up (terhubung ke VCC) sehingga nilai defaultnya HIGH, untuk merubah nilainya pin harus dihubungkan ke GND.
- Input Pull-Down: Pin terhubung ke resistor internal pull-down (terhubung ke GND) sehingga nilai defaultnya LOW, untuk merubah nilainya pin harus diberi tegangan 3.3V atau bisa 5V untuk pin yang toleran.
- Input Analog: Pin dihubungkan ke periferal ADC untuk membaca nilai digital dari tegangan yang diberikan. ADC STM32F103C8 memiliki resolusi 12 bit (0-4095): ketika tegangan input 0V maka akan menghasilkan nilai digital 0, sedangkan ketika tegangan input 3.3V maka akan menghasilkan nilai digital 4095.
Peringatan: Ketika pin GPIO STM32F103C8 yang toleran 5V diubah ke mode Input Analog, pin tersebut hanya akan mampu menerima tegangan maksimal 3.3V, jika diberikan tegangan 5V maka akan berpotensi merusak pin tersebut dan pembacaan ADC menjadi tidak akurat.
3.2 Memprogram Pin GPIO STM32F103C8 sebagai Input Pull-Up
Kita akan menggunakan push button sebagai input ke pin GPIO PB0 STM32F103C8. Pertama mari kita rangkai STM32F103C8 dan push button di breadboard sesuai dengan gambar skematik berikut:

Pada rangkaian tersebut ketika push button ditekan maka pin PB0 akan terhubung ke GND sehingga bernilai LOW, jadi pada program kita akan mengatur pin PB0 ke Input Pull-Up agar dapat mendeteksi perubahan ketika push button ditekan.
Selanjutnya pada project rust yang baru kita buat tadi buka file Cargo.toml, lalu tambahkan kode berikut untuk menambahkan output binary baru:
1[[bin]]
2name = "gpio-input-pullup"
3path = "src/input_pullup.rs"
4test = false
5bench = falsePro Tip: Dengan menambahkan [[bin]] , kita dapat memiliki banyak file executable dalam project Cargo (Rust) yang sama. Ini sangat berguna untuk mengatur percobaan yang berbeda atau tutorial, seperti satu file untuk ‘LED Blink’ dan yang lainnya untuk ‘Push Button Input’, tanpa membuat project terpisah.
Buat file src/input_pullup.rs lalu isi dengan kode berikut:
1#![no_std]
2#![no_main]
3
4use defmt_rtt as _;
5use panic_probe as _;
6
7use cortex_m::delay::Delay;
8use cortex_m_rt::entry;
9use stm32f1xx_hal::{
10 flash::FlashExt,
11 gpio::GpioExt,
12 pac,
13 rcc::{Config, RccExt},
14 time::Hertz,
15};
16
17#[entry]
18fn main() -> ! {
19 defmt::println!("STM32F103C8 Acess GPIO as Input Pull Up");
20
21 // Mengakses periferal STM32F103C8
22 let dp = pac::Peripherals::take().unwrap();
23
24 // Mengakses core peripherals (cortex-M3) STM32F103C8
25 let cp = pac::CorePeripherals::take().unwrap();
26
27 // Mengakses periferal FLASH yang akan digunakan untuk
28 // menyesuaikan 'wait states' saat konfigurasi Clock.
29 let mut flash = dp.FLASH.constrain();
30
31 // Mengakses periferal Reset & Control Clock agar dapat dikonfigurasi
32 let rcc = dp.RCC.constrain();
33
34 // membuat konfigurasi Reset & Control clock (RCC)
35 let clock_config = Config::default()
36 // menggunakan clock external 8MHZ
37 .use_hse(Hertz::MHz(8))
38 //mengatur frekuensi CPU ke 72MHZ
39 .sysclk(Hertz::MHz(72))
40 //mengatur frekuensi BUS (jalur akses SRAM dan FLASH) ke 72MHZ
41 .hclk(Hertz::MHz(72));
42
43 // mengaplikasikan konfigurasi clock ke rcc dan
44 // menyesuaikan 'wait states' memori flash
45 let mut clocks = rcc.freeze(clock_config, &mut flash.acr);
46
47 // membuat instans delay dengan menggunakan Timer System.
48 let mut delay = Delay::new(cp.SYST, clocks.clocks.hclk().to_Hz());
49
50 // Mengakses periferal GPIO Port B
51 let mut gpiob = dp.GPIOB.split(&mut clocks);
52
53 // mengatur pin PB0 sebagai input pull-up
54 let button_pullup = gpiob.pb0.into_pull_up_input(&mut gpiob.crl);
55
56 // tunggu 50 ms
57 delay.delay_ms(50);
58
59 loop {
60 // Cek jika tombol ditekan maka PB0 akan low
61 if button_pullup.is_low() {
62 // kirim pesan ke PC/laptop
63 defmt::println!("button_pullup is pressed");
64 // delay 500ms
65 delay.delay_ms(500);
66 }
67 }
68}Jalankan program dengan perintah ‘cargo run --bin gpio-input-pullup’ di terminal VSCode dan coba tekan push button. Berikut merupakan hasil dari program yang kita buat:
3.3 Memprogram Pin GPIO STM32F103C8 sebagai Input Pull-Down
Kita juga dapat menggunakan pin PB0 sebagai Input Pull-Down, namun rangkaian push button perlu dirubah yang awalnya dihubungkan ke GND sekarang dihubungkan ke VCC atau 3.3V seperti gambar skematik berikut:

Buka file Cargo.toml lalu tambahkan kode berikut:
1[[bin]]
2name = "gpio-input-pulldown"
3path = "src/input_pulldown.rs"
4test = false
5bench = falseKetika push button ditekan makan pin PB0 akan bernilai HIGH, sebaliknya ketika tidak ditekan akan bernilai LOW. Silakan buat file src/input_pulldown.rs dan isi dengan kode berikut:
1#![no_std]
2#![no_main]
3
4use defmt_rtt as _;
5use panic_probe as _;
6
7use cortex_m::delay::Delay;
8use cortex_m_rt::entry;
9use stm32f1xx_hal::{
10 flash::FlashExt,
11 gpio::GpioExt,
12 pac,
13 rcc::{Config, RccExt},
14 time::Hertz,
15};
16
17#[entry]
18fn main() -> ! {
19 defmt::println!("STM32F103C8 Acess GPIO as Input Pull Down");
20
21 // Mengakses periferal STM32F103C8
22 let dp = pac::Peripherals::take().unwrap();
23
24 // Mengakses core peripherals (cortex-M3) STM32F103C8
25 let cp = pac::CorePeripherals::take().unwrap();
26
27 // Mengakses periferal FLASH yang akan digunakan untuk
28 // menyesuaikan 'wait states' saat konfigurasi Clock.
29 let mut flash = dp.FLASH.constrain();
30
31 // Mengakses periferal Reset & Control Clock agar dapat dikonfigurasi
32 let rcc = dp.RCC.constrain();
33
34 // membuat konfigurasi Reset & Control clock (RCC)
35 let clock_config = Config::default()
36 // menggunakan clock external 8MHZ
37 .use_hse(Hertz::MHz(8))
38 //mengatur frekuensi CPU ke 72MHZ
39 .sysclk(Hertz::MHz(72))
40 //mengatur frekuensi BUS (jalur akses SRAM dan FLASH) ke 72MHZ
41 .hclk(Hertz::MHz(72));
42
43 // mengaplikasikan konfigurasi clock ke rcc dan
44 // menyesuaikan 'wait states' memori flash
45 let mut clocks = rcc.freeze(clock_config, &mut flash.acr);
46
47 // membuat instans delay dengan menggunakan Timer System.
48 let mut delay = Delay::new(cp.SYST, clocks.clocks.hclk().to_Hz());
49
50 // Mengakses periferal GPIO Port B
51 let mut gpiob = dp.GPIOB.split(&mut clocks);
52
53 // mengatur pin PB0 sebagai input pull-down
54 let button_pulldown = gpiob.pb0.into_pull_down_input(&mut gpiob.crl);
55
56 // tunggu 50 ms
57 delay.delay_ms(50);
58
59 loop {
60 // Cek jika tombol ditekan maka PB0 akan high
61 if button_pulldown.is_high() {
62 // kirim pesan ke PC/laptop
63 defmt::println!("button_pulldown is pressed");
64 // delay 500ms
65 delay.delay_ms(500);
66 }
67 }
68}Silakan anda coba menjalankan programnya dengan perintah ‘cargo run --bin gpio-input-pulldown’ , lalu coba tekan push button dan lihat hasilnya sendiri.
Permasalahan yang Ditemui
Berikut merupakan masalah yang ditemui pada saat membuat tutorial ini:
- Ghost Triggering: Pada konfigurasi pin GPIO STM32F103C8 sebagai input pull-up kadang-kadang saat pertama kali program dijalankan push button seperti ditekan, padahal tidak ada yang menekan push button. Untuk mengatasi hal ini tambahkan delay sekitar 50ms setelah mengonfigurasi pin atau sebelum loop utama.
Jika Anda menemui masalah lain atau memiliki pertanyaan, kritik, dan saran silakan hubungi kami melalui halaman kontak.
Untuk memudahkan pemahaman anda, source code yang digunakan pada tutorial ini dapat dilihat di repository Github.