# A Beginner's Complete Guide to Microcontroller Programming with Ruby author : hasumikin content-source : RubyConf Africa 2024 place : Nairobi, Kenya date : 27 July 2024 allotted-time : 30m theme : theme # Today's content {:.center} {::tag name="x-small"}Part 1{:/tag} Preparation\\n {:.center} {::tag name="x-small"}Part 2{:/tag} Getting Started with Microcontroller\\n {:.center} {::tag name="x-small"}Part 3{:/tag} Exploring PicoRuby Further\\n {:.center} {::tag name="x-small"}Part 4{:/tag} PicoRuby Under the Hood\\n # self.inspect - Hitoshi HASUMI - @hasumikin {::tag name="small"}(GitHub and Twitter){:/tag} - ANDPAD: construction tech👷🏽‍♂️ - Creator of PicoRuby - Contributor to CRuby and mruby - Member of IRB maintainer team ![](images/hasumi.jpg){: align="right" relative-height="65" relative_margin_top="10" relative_margin_left="20" } # The former IRB maintainer ![](images/itoyanagi.jpg){: align="center" relative-width="100" relative_margin_top="5" relative_margin_left="0" draw0="[rectangle, false, 0.55, 0.01, 0.18, 0.6, {color: red, line_width: 8\}]" draw1="[text, Itoyanagi-san, 0.48, -0.14, {color: red, size: 50, font_family: 'Courier Prime', weight: bold\}]" } {:.center} {::tag name="x-small"}RubyConf Africa 2019{:/tag} # chapter Part 1 {::tag name="xx-large"}Preparation{:/tag} ## Properties hide-title : true # Setup (minimal) - Raspberry Pi Pico - Or other *RP2040)-based controller - USB cable - Terminal emulator on laptop # Raspberry Pi *Pico* - Raspberry Pi Pico: Microcontroller board - MCU: RP2040 - Cortex-Mzero+ (dual) - 264 KB RAM - 2 MB flash ROM - Generally runs without an OS - Raspberry Pi: Single-board computer - Generally needs an OS like Raspberry Pi OS\\nor Windows for Arm ![](images/rpi_pico.jpg){: align="right" relative-height="95" relative_margin_top="0" relative_margin_left="20" draw0="[rectangle, false, 0.3, 0.41, 0.37, 0.2, {color: red, line_width: 8\}]" draw1="[text, RP2040, 0.12, 0.61, {color: red, size: 70, font_family: 'Courier Prime', weight: bold\}]" } # Terminal emulator (recommendation) - Linux -> GTKTerm - Windows -> Tera Term - macOS -> PuTTY ![](images/Terminal-dec-vt100.jpg){: align="right" relative-height="62" relative_margin_top="0" relative_margin_left="20" } {::tag name="large"}\\n\\n\\n{:/tag} {:.right} {::tag name="xx-small"}Creative Commons Attribution-ShareAlike 3.0 https://commons.wikimedia.org/w/index.php?curid=6693684{:/tag} # Let's begin 1/4 - Download the latest\\n`R2P2-*.uf2`\\nfrom GitHub ![](images/download-r2p2.png){: align="right" relative-height="78" relative_margin_left="19" relative_margin_top="-5" } \\n\\n\\n\\n\\n {:.right} {::tag name="xx-small"}https://github.com/picoruby/R2P2/release{:/tag} # Let's begin 1/4 BTW, R2P2 stands for {::tag name="large"}*R*uby on *R*aspberry *P*i *P*ico{:/tag} ![](images/R2D2_mosaic.png){: align="right" relative-height="90" relative_margin_left="10" relative_margin_top="0" } # Let's begin 2/4 - Connect Pi Pico and PC while\\npressing the BOOTSEL button - You'll find "RPI-RP2" drive in file manager ![](images/boot_button_1.png){: align="right" relative-height="78" relative_margin_left="20" relative_margin_top="-1" } \\n\\n\\n\\n\\n {:.right} {::tag name="xx-small"}https://www.raspberrypi.org/documentation/rp2040/getting-started{:/tag} # Let's begin 3/4 - Drag & drop `R2P2-*.uf2` into RPI-RP2 drive ![](images/install-uf2.png){: align="bottom" relative-height="90" relative_margin_left="0" relative_margin_top="1" } # Let's begin 4/4 - Open a proper\\nserial port on\\nterminal emulator ![](images/teraterm-0.png){: align="right" relative-height="80" relative_margin_left="22" relative_margin_top="0" } # R2P2 Shell should start [Demo] - Unix-like shell running on Raspberry Pi Pico - You can use some\\ncommands like `cd`,\\n`ls`, `mkdir`, and *`irb`* ![](images/teraterm.png){: align="right" relative-height="75" relative_margin_left="28" relative_margin_top="5" } # PicoIRB [Demo] - PicoRuby's IRB is running within the R2P2 shell on Raspberry Pi Pico - Your Ruby snippet is compiled into mruby VM code and executed *on the fly* - It means PicoRuby contains an mruby compiler which can run on a one-chip microcontroller (will be mentioned later) # chapter Part 2 {::tag name="xx-large"}Getting Started with Microcontroller{:/tag} ## Properties hide-title : true # GPIO (General Purpose Input/Output) - Fundamental digital I/O - Variety of uses: - Input: Detects on-off state of switch and button - Output: Makes a voltage - You can even implement a communication protocol by controlling GPIO in milli/micro sec # GPIO --- Blinking LED [Demo] irb> led = GPIO.new(25, GPIO::OUT) irb> 5.times do irb* led.write 1 irb* sleep 1 irb* led.write 0 irb* sleep 1 irb* end {:.center} {::tag name="small"}GPIO25 internally connects to\\non-board LED through a resistor{:/tag} # GPIO --- Blinking LED by discrete parts - Parts list: - LED (RED) - Resistor (1kΩ) ![](images/rp2-resistor-led_bb.png){: align="right" relative-height="90" relative_margin_left="10" relative_margin_top="0" } # GPIO --- Blinking LED by discrete parts irb> pin = GPIO.new(15, GPIO::OUT) ![](images/rp2-resistor-led_bb.png){: align="bottom" relative-height="100" relative_margin_left="0" relative_margin_top="0" } # GPIO --- Blinking LED by discrete parts GPIO15 ===> 1kΩ ===> LED ===> GND <----- 1.5V -----><--- 1.8V ----> <------------ 3.3V -------------> - RP2040's logic level: 3.3V - LED voltage drop: *1.8V*\\n(according to LED's datasheet) - Current: (3.3V - 1.8V) / 1kΩ = *1.5mA*\\n(calculated by Ohm's Law) ![](images/rp2-resistor-led_schem.png){: align="right" relative-height="100" relative_margin_left="15" relative_margin_top="0" } # 🏫Study time: Physics - Ohm's Law - V = I - R ⇔ I = V / R ⇔ R = V / I - Kirchhoff's Circuit Laws - Current law: The algebraic sum of currents in a network of conductors meeting at a point is zero - Voltage law: The directed sum of the potential differences (voltages) around any closed loop is zero # Peripherals for serial communication - I²C: To communicate between integrated circuits with support for multiple devices connected to the same bus - SPI: To facilitate high-speed communication between microcontrollers and peripheral devices - UART: To establish asynchronous serial communication between devices # I²C --- Inter-Integrated Circuit irb> require 'i2c' irb> i2c = I2C.new(unit: :RP2040_I2C1, sda_pin: 26, scl_pin: 27) irb> [0x38, 0x39, 0x14, 0x70, 0x56, 0x6c].each { |i| i2c.write(0x3e, 0, i); sleep_ms 1 } irb> [0x38, 0x0c, 0x01].each { |i| i2c.write(0x3e, 0, i); sleep_ms 1 } irb> "Hello,".bytes.each { |c| i2c.write(0x3e, 0x40, c); sleep_ms 1 } irb> i2c.write(0x3e, 0, 0x80|0x40) irb> "Nairobi!".bytes.each { |c| i2c.write(0x3e, 0x40, c); sleep_ms 1 } ![](images/hello_nairobi.jpg){: relative_height="100" relative_margin_top="0" } # LCD wraps I²C # /lib/lcd.rb in R2P2 drive require 'i2c' class LCD ADDRESS = 0x3e # 0x7c == (0x3e << 1) + 0 (R/W) def initialize(i2c:) @i2c = i2c reset end def reset [0x38, 0x39, 0x14, 0x70, 0x56, 0x6c].each { |i| @i2c.write(ADDRESS, 0, i) } sleep_ms 200 [0x38, 0x0c, 0x01].each { |i| @i2c.write(ADDRESS, 0, i) } end def putc(c) @i2c.write(ADDRESS, 0x40, c) sleep_ms 1 end def print(line) line.bytes.each { |c| putc c } end # ... # See https://github.com/picoruby/picoruby/tree/master/mrbgems # /picoruby-ble/example/broadcaster-observer {: lang="ruby"} ## properties fill-color : #f8f8f8 # LCD wraps I²C irb> require 'lcd' irb> lcd = LCD.new(i2c: I2C.new(unit: :RP2040_I2C1, sda_pin: 26, scl_pin: 27)) irb> lcd.print "Hello," irb> lcd.break_line irb> lcd.print "Nairobi!" ![](images/hello_nairobi.jpg){: relative_height="100" relative_margin_top="0" } # SPI --- Serial Peripheral Interface irb> require 'spi' irb> spi = SPI.new(unit: :RP2040_SPI0, cipo_pin: 16, cs_pin: 17, sck_pin: 18, copi_pin: 19) irb> spi.select irb> spi.write(255,255,255,255) # Reset irb> spi.write(0x54) # Start continuous mode irb> data = spi.read(2).bytes irb> temp = data[0] << 8 | data[1] irb> temp / 128.0 # Convert to Celsius => 19.5621 # THERMO wraps SPI #/lib/thermo.rb in R2P2 drive require 'spi' class THERMO def initialize(unit:, sck_pin:, cipo_pin:, copi_pin:, cs_pin:) @spi = SPI.new(unit: unit, frequency: 500_000, mode: 0, cs_pin: cs_pin, sck_pin: sck_pin, cipo_pin: cipo_pin, copi_pin: copi_pin ) @spi.select @spi.write 0xFF, 0xFF, 0xFF, 0xFF # Reset @spi.write 0x54 # Start continuous mode sleep_ms 240 end def read data = @spi.read(2).bytes temp = (data[0] << 8 | data[1]) >> 3 # If it minus? temp -= 0x2000 if 0 < temp & 0b1_0000_0000_0000 temp / 16.0 # Convert to Celsius end end # See https://github.com/picoruby/picoruby/tree/master/mrbgems # /picoruby-ble/example/broadcaster-observer {: lang="ruby"} # THERMO wraps SPI irb> require 'thermo' irb> thermo = THERMO.new(unit: :RP2040_SPI0, cipo_pin: 16, cs_pin: 17, sck_pin: 18, copi_pin: 19) irb> thermo.read => 19.5621 # LCD and THERMO ![](images/lcd_and_thermo.png){: relative_height="100" relative_margin_top="0" } # LCD and THERMO irb> require 'lcd' irb> lcd = LCD.new(i2c: I2C.new(unit: :RP2040_I2C1, sda_pin: 26, scl_pin: 27)) irb> require 'thermo' irb> thermo = THERMO.new(unit: :RP2040_SPI0, cipo_pin: 16, cs_pin: 17, sck_pin: 18, copi_pin: 19) irb> lcd.print sprintf("%5.2f \xdfC", thermo.read) ![](images/lcd-and-thermo.jpg){: relative_height="100" relative_margin_top="0" } # chapter Part 3 {::tag name="xx-large"}Exploring PicoRuby Further{:/tag} ## Properties hide-title : true # PicoRuby killer applications - R2P2 - Microcontroller application framework - Unix-like shell system and IRB written in PicoRuby - PRK Firmware - Keyboard firmware framework for DIY keyboard - You can write your keymap and keyboard's behavior with Ruby # R2P2 (again) - IRB - Multiple-line editor - Built-in commands and executables (all written in Ruby) - You can write your own external command # Executables in R2P2, for example, # date puts Time.now.to_s {: lang="ruby"} # mkdir Dir.mkdir(ARGV[0]) {: lang="ruby"} # Write a Ruby script file [Demo] $> vim hello.rb {:.center} Edit the file and save it. puts "Hello World!" {: lang="ruby"} {:.center} Then run it. $> ./hello.rb # Make a standalone IoT device # `/home/app.rb` automatically runs require 'lcd' require 'thermo' led = GPIO.new(25, GPIO::OUT) lcd = LCD.new(i2c: I2C.new(unit: :RP2040_I2C1, sda_pin: 26, scl_pin: 27)) thermo = THERMO.new(unit: :RP2040_SPI0, cipo_pin: 16, cs_pin: 17, sck_pin: 18, copi_pin: 19) # Stop infinite loop by Ctrl-C while true temp = thermo.read lcd.clear sleep 0.1 lcd.print sprintf("%5.2f \xdfC", temp) sleep 1 led.write(30 < temp ? 1 : 0) end {: lang="ruby"} # Make a standalone IoT device ![](images/drag-and-drop.png){: align="bottom" relative-height="90" relative_margin_left="0" relative_margin_top="3" } # chapter Part 4 {::tag name="xx-large"}PicoRuby Under the Hood{:/tag} ## Properties hide-title : true # mruby and PicoRuby - mruby - General purpose embedded Ruby implementation written by Matz - PicoRuby (PicoRuby compiler + mruby/c VM) - Another implementation of murby targeting on one-chip microcontroller (*smaller foot print*) - Based on the mruby's VM code standard # Small foot print $ valgrind \ --tool=massif \ --stacks=yes \ path/to/(mruby|picoruby) \ -e 'puts "Hello World!"' {:.center} `massif.out.[pid]` file will be created. Then, $ ms_print massif.out.1234 | less # Small foot print -------------------------------------------------------------------------------- Command: mruby -e 'puts "Hello World!"' Massif arguments: --stacks=yes ms_print arguments: massif.out.18391 -------------------------------------------------------------------------------- KB 133.5^ # | # | # | # | # | # | # | # | @ :@:::@:#: | @:@@@::::@:::@:#:: | ::@::::::@::::::::@:@@@::::@:::@:#:: | @:::::::::@@::::@:::@::::::@:::::: :@:@@@::::@:::@:#:: | @@@:::@:::::::::@ :: :@:::@::::::@:::::: :@:@@@::::@:::@:#:: | @ :: @:::::::::@ :: :@:::@::::::@:::::: :@:@@@::::@:::@:#:: | @ :: @:::::::::@ :: :@:::@::::::@:::::: :@:@@@::::@:::@:#:: | @ :: @:::::::::@ :: :@:::@::::::@:::::: :@:@@@::::@:::@:#:: | @ :: @:::::::::@ :: :@:::@::::::@:::::: :@:@@@::::@:::@:#:: | @ :: @:::::::::@ :: :@:::@::::::@:::::: :@:@@@::::@:::@:#:: | @ :: @:::::::::@ :: :@:::@::::::@:::::: :@:@@@::::@:::@:#:: | @ @ :: @:::::::::@ :: :@:::@::::::@:::::: :@:@@@::::@:::@:#:: 0 +----------------------------------------------------------------------->Mi 0 1.281 Note: Measured in 64 bit Ubuntu # Small foot print -------------------------------------------------------------------------------- Command: picoruby -e 'puts "Hello World!"' Massif arguments: --stacks=yes ms_print arguments: massif.out.21752 -------------------------------------------------------------------------------- KB 9.820^ # | @:#::: | @:#::::: | @:#::::: | @:#::::: | @ @:#::::: | @ @:#::::: | @ : @:#::::: | @ ::: @:#::::: | @ : ::: @:#::::: | @ ::@::::@:#:::::@ | @ ::@::::@:#:::::@ | @: :::::@::::@:#:::::@ | @::: :: :::@::::@:#:::::@ | @:: ::: :::@::::@:#:::::@ | @:: : ::: :::@::::@:#:::::@ | @:: : ::: :::@::::@:#:::::@ | :@:: : : :: :@:@: : :@@: ::@::::: ::: :::@::::@:#:::::@ | :@:: :::::::::::::::@:@:@:::::@ ::: @:: : :::: :::@::::@:#:::::@ |::::::@::@:: ::: ::: :::::@:@:@:: ::@ : : @:: : :::: :::@::::@:#:::::@ 0 +----------------------------------------------------------------------->ki 0 324.5 Note: Measured in 64 bit Ubuntu # Small foot print - RAM consumption of `puts "Hello World!"` - mruby: 133.5 KB (on 64 bit) - PicoRuby: 9.82 KB (on 64 bit) - RP2040 (32 bit) has 264 KB RAM - Only small applications written in mruby should work - R2P2 and PRK Firmware should be written in PicoRuby # PRK Firmware: DIY keyboard firmware ![](images/crkbd_2.jpg){: relative_height="95" } # PRK Firmware on Meishi2 (4-keys macro pad) ![](images/meishi2.jpg){: relative_height="95" } # PRK Firmware on Meishi2 (4-keys macro pad) require "consumer_key" kbd = Keyboard.new kbd.init_pins( [ 6, 7 ], # row0, row1 [ 28, 27 ] # col0, col1 ) kbd.add_layer :default, %i[ RAISE KC_2 KC_A KC_4 ] kbd.add_layer :raise, %i[ RAISE KC_AUDIO_VOL_UP KC_AUDIO_VOL_DOWN KC_AUDIO_MUTE ] kbd.define_mode_key :RAISE, [ :KC_SPACE, :raise, 200, 200 ] kbd.start! {: lang="ruby"} # PicoRuby ecosystem - Picogems - PRK Firmware is also a Picogem - Peripheral gems - picoruby-gpio, picoruby-adc, picoruby-i2c,\\npicoruby-spi, picoruby-uart, picoruby-pwm - Peripheral interface guide - https://github.com/mruby/\\nmicrocontroller-peripheral-interface-guide ![](images/QR_github-com-peripheral-interface-guide.png){: align="right" relative_height="70" relative_margin_top="0" relative_margin_left="22" } # PicoRuby ecosystem - Build system forked from mruby - You can build your application in a similar way to mruby - You can also write your gem and host it on your GitHub - RP2040 is the only target as of now though, - Carefully designed to keep portability # Conclusion - PicoRuby is a Ruby implementaiton targeting on one-chip microcontroller - You can prototype your microcontroller application step-by-step using R2P2 and IRB - You don't need any compiler or linker - Educational and fun to learn about microcontroller programming # chapter {::tag name="xx-large"}Stargaze at{:/tag} ![](images/QR_picoruby.png){: relative_height="70" } github.com/picoruby/picoruby ## Properties hide-title : true