= A Beginner's Complete Guide to Microcontroller Programming with Ruby : author hasumikin : place Bangkok, Thailand : date October 6th - 7th 2023 : allotted-time 30m : theme theme = Today's content (('tag:center')) (('tag:x-small:Part 1'))\n Preparation \n\n (('tag:x-small:Part 2'))\n Getting Started with Microcontroller \n\n (('tag:x-small:Part 3'))\n Exploring PicoRuby Further \n\n (('tag:x-small:Part 4'))\n PicoRuby Under the Hood == prop : hide-title true = self.inspect * Hitoshi HASUMI * hasumikin (('tag:small:(GitHub, ex-Twitter, Bluesky and Threads)')) * Creator of PicoRuby and PRK Firmware * Committer of CRuby's IRB and Reline * First prize of Fukuoka Ruby Award\n(2020 and 2022✌️) * A final nominee of Ruby Prize 2021 # image # src = images/hasumi.jpg # align = right # relative-height = 95 # relative_margin_top = 50 # relative_margin_left = 25 = chapter Part 1\n (('tag:xx-large:Preparation')) == prop : hide-title true = Setup (minimal) * Raspberry Pi Pico * Or other ((*RP2040*))-based controller * USB cable * Terminal emulator on laptop # image # src = images/rpi_pico.jpg # align = right # relative-height = 95 # relative_margin_top = 0 # relative_margin_left = 0 # 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}] = Raspberry Pi Pico * Raspberry Pi Pico: Microcontroller board * MCU: RP2040 * Cortex-Mzero+ (dual) * 264 KB RAM * 2 MB flash ROM * It generally runs without an OS (bere metal) * ref) Raspberry Pi: Single-board computer * It generally needs an OS like Raspberry Pi OS or Windows for Arm = Terminal emulator * Linux -> GTKTerm👍 * Windows -> Tera Term🙆 * macOS -> PuTTY (I'm not sure) * Traditional CUI/TUI tools may have CR/LF trouble * cu * screen * minicom = Let's begin 1/4 * Download the latest\n`R2P2-*.(zip|gz)`\nfrom GitHub\nthen unzip it into\n`R2P2-*.uf2` # image # align = right # src = images/download-r2p2.png # relative-height = 90 # relative_margin_left = 20 # relative_margin_top = 0 (('tag:x-small'))\n\n\n github.com/picoruby/R2P2/releases = Let's begin 2/4 * Connect Pi Pico and PC while\npressing the BOOTSEL button * You'll find "RPI-RP2" drive\nin file manager # image # align = right # src = images/boot_button_1.png # relative-height = 80 # relative_margin_left = 20 # relative_margin_top = -2 (('tag:right')) (('tag:xx-small'))\n\n\n\n\n\n\n\n\n\n\n https://www.raspberrypi.org/documentation/rp2040/getting-started = Let's begin 3/4 * Drag & drop `R2P2-*.uf2` into RPI-RP2 drive # image # align = bottom # src = images/install-uf2.png # relative-height = 90 # relative_margin_left = 0 # relative_margin_top = 1 = Let's begin 4/4 * Open a proper\nserial port on\nterminal emulator # image # align = right # src = images/teraterm-0.png # relative-height = 90 # 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 `pwd`, `cd`,\n`ls`, `mkdir` * It apparently has a\nfilesystem\n(written in Ruby!) # image # align = right # src = images/teraterm.png # 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 going to be 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\n (('tag:xx-large:Getting Started with Microcontroller')) == prop : 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> 3.times do irb* led.write 1 irb* sleep 1 irb* led.write 0 irb* sleep 1 irb* end (('tag:small')) (('tag:center')) GPIO25 internally connects to on-board LED\nthrough a resistor = GPIO --- Blinking LED by discrete parts * Parts list: * LED (RED) * Resistor (1kΩ) # image # align = right # src = images/rp2-resistor-led_bb.png # relative-height = 90 # relative_margin_left = 10 # relative_margin_top = 0 = GPIO --- Blinking LED by discrete parts irb> pin = GPIO.new(15, GPIO::OUT) # image # align = bottom # src = images/rp2-resistor-led_bb.png # 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) # image # align = right # src = images/rp2-resistor-led_schem.png # relative-height = 100 # relative_margin_left = 5 # relative_margin_top = 0 = 🏫Study time: Electromagnetism | 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 = ADC (Analog to Digital Converter) * ADC handles values from 0 to logic-level by converting an analog voltage to a digital value * e.g. RP2040's ADC has ((*12 bits*)) depth and accordingly takes a raw value from ((*0 (0 V)*)) to ((*4095 (3.3 V)*)) * Typical usage: * Temperature sensor🌡 * Joystick🕹 = ADC --- Temperature [Demo] irb> require 'adc' irb> adc = ADC.new(:temperature) irb> adc.read_raw irb> while true irb* voltage = adc.read_voltage irb* puts 27 - (voltage - 0.706) / 0.001721 irb* sleep 1 irb* end (('tag:center')) RP2040 has an in-chip temperature sensor that connects to an ADC channel = ADC --- Temperature by discrete parts * Parts list: * Resistor * Rref: 10kΩ * Thermistor * 10kΩ (at 25℃ = 298.15K) * B const: 3950 * T0: 298.15 (kelvin) # image # align = right # src = images/rp2-resistor-thermistor_bb.png # relative-height = 90 # relative_margin_left = 18 # relative_margin_top = 0 = ADC --- Temperature by discrete parts * Parts list: * Resistor * Rref: 10kΩ * Thermistor * 10kΩ (at 25℃ = 298.15K) * B const: 3950 * T0: 298.15 (kelvin) # image # align = right # src = images/rp2-resistor-thermistor_schem.png # relative-height = 90 # relative_margin_left = -10 # relative_margin_top = 0 = ADC --- Temperature by discrete parts irb> require 'adc' irb> Rref = 10000.0 irb> B = 3950.0 irb> T0 = 298.15 irb> def kelvin_temp(rth) irb* temp_inverse = 1 / B * Math.log(rth / Rref) + (1 / T0) irb* 1 / temp_inverse irb* end irb> rth = (3.3 / adc.read_voltage - 1) * Rref irb> puts "#{kelvin_temp(rth) - 273.15} C" => 28.1234 C = chapter Part 3\n (('tag:xx-large:Exploring PicoRuby Further')) == prop : hide-title true = PicoRuby applications * R2P2 * Unix-like shell system written in PicoRuby * You may want to say an Operating System in Ruby * 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 # enscript ruby # date puts Time.now.to_s # mkdir Dir.mkdir(ARGV[0]) = Write a Ruby script file [Demo] # enscript sh $> vim hello.rb (('tag:center'))Edit the file and save it. # enscript ruby puts "Hello World!" (('tag:center'))Then run it. # enscript sh $> ./hello.rb = Or just drag and drop [Demo] # image # align = bottom # src = images/drag-and-drop.png # relative-height = 90 # relative_margin_left = 0 # relative_margin_top = 3 = GPIO and ADC work together [Demo] # enscript ruby require 'adc' def calc_temp(volt) 27 - (volt - 0.706) / 0.001721 end adc = ADC.new(:temperature) led = GPIO.new(25, GPIO::OUT) while true temp = calc_temp(adc.read_voltage) puts "temp: #{temp} C" led.write(30 < temp ? 1 : 0) sleep 1 end = R2P2 [Demo] * `/home/app.rb` automatically runs on start up # enscript ruby # You can stop by Ctrl-C led = GPIO.new(25, GPIO::OUT) while true led.write 1 puts "Hello World!" sleep 1 led.write 0 sleep 1 end = BTW, \n\n R2P2 stands for \n\n (('tag:large:((*R*))uby on ((*R*))aspberry ((*P*))i ((*P*))ico')) # image # align = right # src = images/R2D2_mosaic.png # relative-height = 90 # relative_margin_left = 10 # relative_margin_top = 0 = [FYI] Serial communication protocols * SPI: High speed, full duplex. e.g. Acceleration sensor, Color display, etc. * I2C: Low speed, Addressing network with fewer wires. e.g. RTC, Temperature sensor and Charactor display, etc. * UART: Buffered asyncronous communication. e.g. Terminal emulator, Wireless module like BLE and LTE/5G, etc. = [FYI] I2C and UART * Example of I2C (RTC)\nand UART (USB serial) * Watch the demo video\nin README.md * It's also an example of\nhow to build your own app (('tag:center')) \n\n github.com/picoruby/rp2040-peripheral-demo # image # align = right # src = images/rp2040-peripheral-demo_bb-trim.png # relative-height = 80 # relative_margin_left = 45 # relative_margin_top = -5 = [FYI] I2C and UART * Parts list: * PCF8523 RTC module * FTDI USB to TTL Serial\nAdapter Cable (3.3V) # image # align = right # src = images/rp2040-peripheral-demo_bb-trim.png # relative-height = 90 # relative_margin_left = 33 # relative_margin_top = 0 = PRK Firmware - Corne (CRKBD) # image # src = images/crkbd_2.jpg # relative_height = 95 = PRK Firmware - Meishi2 (4-keys pad) # enscript ruby require "consumer_key" kbd = Keyboard.new kbd.init_pins( [ 6, 7 ], # row0, row1 [ 28, 27 ] # col0, col1 ) kbd.add_layer :default, %i[ ZERO_RAISE KC_1 KC_2 KC_3 ] kbd.define_mode_key :ZERO_RAISE, [ :KC_0, :raise, 200, 200 ] kbd.add_layer :raise, %i[ ZERO_RAISE KC_AUDIO_VOL_UP KC_AUDIO_VOL_DOWN KC_AUDIO_MUTE ] kbd.start! = chapter Part 4\n (('tag:xx-large:PicoRuby Under the Hood')) == prop : 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*))) * The VM code specifications are common to both * So the compilers are interchangeable = Small foot print # enscript sh $ valgrind \ --tool=massif \ --stacks=yes \ path/to/(mruby|picoruby) \ -e 'puts "Hello World!"' (('tag: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 running with mruby works * Big apps like R2P2 and PRK Firmware should be written in PicoRuby = PicoRuby ecosystem * Picogems * PRK Firmware is also a Picogem * Peripheral gems * picoruby-gpio * picoruby-adc * picoruby-i2c * picoruby-spi * picoruby-uart * Peripheral interface guide * https://github.com/mruby/microcontroller-peripheral-interface-guide # image # src = images/QR_github-com-peripheral-interface-guide.png # align = right # relative-height = 70 # relative_margin_top = 0 # relative_margin_left = 18 = 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. So, * You can port PicoRuby (Picogems) to other microcontrollers = Restrictions of PicoRuby * Minimum built-in classes and methods * Doesn't support some syntax like heredoc and numbered parameters * No meta-programming features * No strict distinction between instance methods and singleton methods * Some bugs (because I'm lazy🥺).\nSee github.com/picoruby/picoruby/issues = Conclusion * PicoRuby is a Ruby implementaiton targeting on one-chip microcontroller * Essential peripheral libraries: GPIO, ADC, I2C, SPI, and UART are ready * You can develop your microcontroller application step by step using the R2P2 and IRB * You need only R2P2 to write small applications * Future work: * Bluetooth for "Raspberry Pi Pico ((*W*))" (soon🤞) = RubyKaigi 2024 [AD] # image # src = images/okinawa.jpg # align = center # relative-height = 140 # relative_margin_top = -10 # relative_margin_left = 0 # draw1 = [text, RubyKaigi 2024, 0.10, 0.15, {color: white, size: 100, font_family: 'Montserrat', weight: bold}] # draw2 = [text, "[ad]", 0.60, 0.21, {color: white, size: 30, font_family: 'Montserrat', weight: normal}] # draw3 = [text, "In Okinawa🏝️ May 15th - 17th ", 0.10, 0.26, {color: white, size: 60, font_family: 'Montserrat', weight: normal}] # draw4 = [text, "1000+ attendees, tons of tech talks", 0.10, 0.33, {color: white, size: 60, font_family: 'Montserrat', weight: normal}] # draw5 = [text, "All Japanese talks come with simultaneous interpretation into English", 0.10, 0.40, {color: white, size: 35, font_family: 'Montserrat', weight: normal}] # draw6 = [text, "Various parties🍻", 0.10, 0.47, {color: white, size: 60, font_family: 'Montserrat', weight: normal}] # draw7 = [text, https://098free.com/photos/14262/, 0.68, 0.78, {color: white, size: 22, font_family: 'Montserrat', weight: normal}] == prop : hide-title true = That's all! Visit repos and stargaze🌟 (('tag:small')) \n\n github.com/picoruby/picoruby \n\n github.com/picoruby/R2P2 \n\n github.com/picoruby/prk_firmware \n\n github.com/picoruby/rp2040-peripheral-demo # image # src = images/QR_github-com-picoruby.png # align = right # relative-height = 65 # relative_margin_top = -10 # relative_margin_left = 18