509 |
510 |
511 |
512 | `;
513 | */
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | UPDATE: This code in this article has been archived in the [pre-lvgl branch of pinetime-rust-mynewt](https://github.com/lupyuen/pinetime-rust-mynewt/tree/pre-lvgl). The [pinetime-rust-mynewt firmware](https://github.com/lupyuen/pinetime-rust-mynewt) has been revamped to support [Rust Watch Faces on LVGL](https://lupyuen.github.io/pinetime-rust-mynewt/articles/watchface). [Check out the updates](https://lupyuen.github.io/pinetime-rust-mynewt/articles/watchface)
2 |
3 | # visual-embedded-rust
4 |
5 | - Create and edit Embedded Rust programs visually by dragging and dropping blocks
6 |
7 | - Generates Embedded Rust firmware code for [__PineTime Smart Watch__](https://wiki.pine64.org/index.php/PineTime) hosted on [__Apache Mynewt__](https://mynewt.apache.org/) realtime operating system, with [__druid UI Framework__](https://github.com/lupyuen/druid-embedded)
8 |
9 | - Hardware Required: PineTime Smart Watch and Raspberry Pi (preferably Pi 4 with Raspbian)
10 |
11 | 
12 |
13 | 
14 |
15 | # Connect PineTime to Raspberry Pi
16 |
17 | 1️⃣ Carefully pry open the PineTime casing. Use tweezers to pivot the shiny battery gently to the side. Be careful not to break the red and black wires that connect the battery to the watch!
18 |
19 | 2️⃣ Just above the battery we see 4 shiny rings. This is the __[Serial Wire Debug](https://en.wikipedia.org/wiki/JTAG#Serial_Wire_Debug) (SWD)__ Port for PineTime. We’ll use this port to flash our firmware to PineTime. The 4 pins (from left to right) are SWDIO (Data I/O), SWDCLK (Clock), 3.3V, GND.
20 |
21 | 🛈 [_What is “flash memory” / “flashing” / “firmware”? Read this_](https://gist.github.com/lupyuen/41fffaddade277d27c48697bca21d837)
22 |
23 | 
24 |
25 | In the above photo, the SWD pins from left to right are…
26 |
27 | 1. __SWDIO__ (Yellow)
28 |
29 | 1. __SWDCLK__ (Blue)
30 |
31 | 1. __3.3V__ (Red)
32 |
33 | 1. __GND__ (Black)
34 |
35 | The exposed copper wire at the top centre of the photo is the Bluetooth antenna. Bend it upwards so that it doesn’t come into contact with anything.
36 |
37 | 
38 |
39 | 3️⃣ At lower right we see a pad marked 5V. We’ll connect this pad to Raspberry Pi to charge the battery. If charging of the battery is not needed during development, we may leave5V disconnected.
40 |
41 | 4️⃣ Connect the SWD Port and the 5V Pad (optional) to the Raspberry Pi with [__Solid-Core Wire (22 AWG)__](https://www.adafruit.com/product/288) and [__Female-To-Female Jumper Cables__](https://www.adafruit.com/product/1951)…
42 |
43 | | PineTime | Raspberry Pi | Wire Colour |
44 | | :--- | :--- | :--- |
45 | | `SWDIO` | `Header Pin 19 (MOSI)` | Yellow |
46 | | `SWDCLK` | `Header Pin 23 (SCLK)` | Blue |
47 | | `3.3V` | `3.3V` | Red |
48 | | `GND` | `GND` | Black |
49 | | `5V` | `5V` | Green (Optional) |
50 |
51 | 
52 |
53 | _Based on https://pinout.xyz/_
54 |
55 | 5️⃣ We may use Raspberry Pi Zero, 1, 2, 3 or 4.
56 |
57 | 
58 |
59 | 6️⃣ The PineTime touchscreen needs to be accessible during development, so I mounted PineTime on a [$2 clear box cover from Daiso](https://www.daisojapan.com/p-30955-clear-box-28-x-47-x-19-x-in-12pks.aspx) with Blu Tack and sticky tape.
60 |
61 | 
62 |
63 | # Remove PineTime Flash Protection
64 |
65 | PineTime is shipped with preloaded demo firmware. We need to erase the demo firmware and unprotect PineTime’s flash memory so that we may flash our own firmware.
66 |
67 | 🛈 [_What is “flash protection”? Read this_](https://gist.github.com/lupyuen/3ee440542853e1e637582c4efa1b240a)
68 |
69 | 1️⃣ Power on the Raspberry Pi. Open a command prompt and enter the following…
70 |
71 | ```bash
72 | sudo raspi-config
73 | ```
74 |
75 | Select `Interfacing Options → SPI → Yes`
76 |
77 | Select `Finish`
78 |
79 | At the command prompt, enter the following…
80 |
81 | ```bash
82 | # Remove folders ~/pinetime-rust-mynewt and ~/openocd-spi (if they exist)
83 | rm -rf ~/pinetime-rust-mynewt
84 | rm -rf ~/openocd-spi
85 |
86 | # Download and extract "pinetime-rust-mynewt" folder containing our prebuilt firmware, source files and flashing scripts
87 | sudo apt install -y wget p7zip-full
88 | cd ~
89 | wget https://github.com/lupyuen/pinetime-rust-mynewt/releases/download/v3.0.3/pinetime-rust-mynewt.7z
90 | 7z x pinetime-rust-mynewt.7z
91 | rm pinetime-rust-mynewt.7z
92 |
93 | # Install build tools for PineTime: VSCode, Rust, gcc, gdb, openocd-spi, newt
94 | cd ~/pinetime-rust-mynewt
95 | scripts/install-pi.sh
96 |
97 | # Latest nightly-2020-04-20 fails with asm error, so we use nightly-2020-02-16
98 | source $HOME/.cargo/env
99 | rustup default nightly-2020-02-16
100 | rustup update
101 | rustup target add thumbv7em-none-eabihf
102 | ```
103 |
104 | 2️⃣ At the `Welcome to Rust!` prompt, press Enter to select the default option:
105 |
106 | `1) Proceed with installation (default)`
107 |
108 | If you see this error…
109 |
110 | ```
111 | Cloning into 'openocd-spi/jimtcl'...
112 | fatal: unable to access 'http://repo.or.cz/r/jimtcl.git/': Recv failure: Connection reset by peer
113 | fatal: clone of 'http://repo.or.cz/r/jimtcl.git' into submodule path '/private/tmp/aa/openocd-spi/jimtcl' failed
114 | ```
115 |
116 | It means that the sub-repository for one of the dependencies jimtcl is temporarily down. You may download the pre-built `openocd-spi` binaries [from this link](https://github.com/lupyuen/pinetime-rust-mynewt/releases/download/openocd-spi2/openocd-spi.7z). Then copy the executable openocd-spi/src/openocd to pinetime-rust-mynewt/openocd/bin/openocd
117 |
118 | 3️⃣ When the installation has completed, enter the following at the command prompt…
119 |
120 | ```bash
121 | # Remove flash protection from PineTime and erase demo firmware
122 | cd ~/pinetime-rust-mynewt
123 | scripts/nrf52-pi/flash-unprotect.sh
124 | ```
125 |
126 | 4️⃣ We should see `Shut Down And Power Off Your Raspberry Pi`…
127 |
128 | 
129 |
130 | If we see `Clock Speed` and nothing else after that…
131 |
132 | ```
133 | Info : BCM2835 SPI SWD driver
134 | Info : SWD only mode enabled
135 | Info : clock speed 31200 kHz
136 | Info : SWD DPIDR 0x2ba01477
137 | ```
138 |
139 | Then the connection to the SWD Port is probably loose, please check the pins.
140 |
141 | If we don't see this `DPIDR` number, or if we see a different `DPIDR` number...
142 |
143 | ```
144 | SWD DPIDR 0x2ba01477
145 | ```
146 |
147 | Then the connection to the SWD Port is most likely loose, please check the pins.
148 |
149 | Also enter `sudo raspi-config` and confirm that the SPI port has been enabled.
150 |
151 | If we see this instead…
152 |
153 | ```
154 | openocd/bin/openocd: cannot execute binary file: Exec format error
155 | ```
156 |
157 | Then `install-pi.sh` probably didn’t run correctly. To fix this, copy the `openocd` executable like this…
158 |
159 | ```bash
160 | cp $HOME/openocd-spi/src/openocd $HOME/pinetime-rust-mynewt/openocd/bin/openocd
161 | ```
162 |
163 | 5️⃣ Shut down and power off your Raspberry Pi. Wait 30 seconds for the red and green LEDs on your Pi to turn off. Power on your Pi. Enter the same commands at a command prompt…
164 |
165 | ```bash
166 | # Remove flash protection from PineTime and erase demo firmware
167 | cd ~/pinetime-rust-mynewt
168 | scripts/nrf52-pi/flash-unprotect.sh
169 | ```
170 |
171 | 6️⃣ We should see `Flash Is Already Unprotected`…
172 |
173 | PineTime’s demo firmware has been erased and the flash protection has been removed.
174 |
175 | 🛈 [_What is OpenOCD? Why Raspberry Pi and not ROCK64 or Nvidia Jetson Nano? Read this_](https://gist.github.com/lupyuen/18e66c3e81e11050a10d1192c5b84bb0)
176 |
177 | # Edit The Visual Rust Application
178 |
179 | We shall be using VSCode with the Visual Embedded Rust Extension to edit our Visual Rust application graphically.
180 |
181 | 🛈 [_What is VSCode? Is it related to Visual Studio? How is Microsoft involved? Read this_](https://gist.github.com/lupyuen/08e383845d68d3337747e8eb59d0f624)
182 |
183 | 1️⃣ Launch VSCode by clicking the Raspberry Pi Menu (top left corner) → Programming → Code OSS Headmelted
184 |
185 | In VSCode, click `File → Open Folder`
186 |
187 | Under `Home`, select the folder `pinetime-rust-mynewt` and click OK
188 |
189 | When prompted to open the workspace, click Open Workspace
190 |
191 | When prompted to install Extension Recommendations, click `Install All`
192 |
193 | Ignore the message `Unable To Watch For File Changes`. Close the message when it appears.
194 |
195 | 2️⃣ Install the `Visual Embedded Rust` Extension...
196 |
197 | Click `View → Extensions`
198 |
199 | Search for `Visual Embedded Rust`
200 |
201 | Install the extension
202 |
203 | 3️⃣ Enable the Visual Rust application...
204 |
205 | Browse to `rust/app/Cargo.toml`
206 |
207 | Modify the file such that `visual_app` is uncommented and the other options are commented out...
208 |
209 | ```yaml
210 | default = [ # Select the conditional compiled features
211 | # "display_app", # Disable graphics display app
212 | # "ui_app", # Disable druid UI app
213 | "visual_app", # Enable Visual Rust app
214 | # "use_float", # Disable floating-point for GPS geolocation
215 | ]
216 | ```
217 |
218 | 4️⃣ Edit the Visual Rust application...
219 |
220 | Browse to [`rust/app/src/visual.rs`](https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/visual.rs)
221 |
222 | Click `Visual Editor` at top right
223 |
224 | 
225 |
226 | Use the Visual Editor to edit the Visual Rust application
227 |
228 | 
229 |
230 | 5️⃣ After editing, save the [`visual.rs`](https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/visual.rs) source file to save the visual program. Don't edit the Rust source file manually, always use the Visual Editor.
231 |
232 | [_Rust Source Code generated from Visual Rust application_](https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/visual.rs)
233 |
234 | The Visual Rust application shows a button that increments a counter...
235 |
236 | 
237 |
238 | Let's look at the blocks in the Visual Rust application...
239 |
240 | # On Start
241 |
242 | 
243 |
244 | Upon starting the Watch App, we initialise the `count` variable to 0.
245 |
246 | This block generates the following Rust code...
247 |
248 | ```rust
249 | /// Application State
250 | #[infer_type] // Infer the missing types
251 | #[derive(Clone, Data, Default)]
252 | struct State {
253 | count: _,
254 | }
255 |
256 | /// Will be run upon startup to launch the app
257 | #[infer_type] // Infer the missing types
258 | pub fn on_start() -> MynewtResult<()> {
259 | console::print("on_start\n");
260 | // Build a new window
261 | let main_window = WindowDesc::new(ui_builder);
262 | // Create application state
263 | let mut state = State::default();
264 | state.count = 0;
265 |
266 | // Launch the window with the application state
267 | AppLauncher::with_window(main_window)
268 | .use_simple_logger()
269 | .launch(state)
270 | .expect("launch failed");
271 | // Return success to `main()` function
272 | Ok(())
273 | }
274 | ```
275 |
276 | # Create App
277 |
278 | 
279 |
280 | We create a Watch App with two Widgets...
281 |
282 | 1. A Label named `my_label` surrounded by padding of 5 pixels
283 |
284 | 1. A Button named `my_button` with the title `Press Me`, surrounded by padding of 5 pixels
285 |
286 | This block generates the following Rust code...
287 |
288 | ```rust
289 | /// Build the UI for the window
290 | #[infer_type] // Infer the missing types
291 | fn ui_builder() -> impl Widget { // `State` is the Application State
292 | console::print("Rust UI builder\n"); console::flush();
293 | // Create a line of text
294 | // Call `on_my_label_show` to get label text
295 | let my_label_text = LocalizedString::new("hello-counter")
296 | .with_arg("count", on_my_label_show);
297 | // Create a label widget `my_label`
298 | let my_label = Label::new(my_label_text);
299 | // Create a button widget `my_button`
300 | // Call `on_my_button_press` when pressed
301 | let my_button = Button::new("Press Me", on_my_button_press);
302 |
303 | // Create a column
304 | let mut col = Column::new();
305 | // Add the label widget to the column, centered with padding
306 | col.add_child(
307 | Align::centered(
308 | Padding::new(5.0,
309 | my_label
310 | )
311 | ),
312 | 1.0
313 | );
314 | // Add the button widget to the column, with padding
315 | col.add_child(
316 | Padding::new(5.0,
317 | my_button
318 | ),
319 | 1.0
320 | );
321 | // Return the column containing the widgets
322 | col
323 | } // ;
324 | ```
325 |
326 | # On Label Show
327 |
328 | 
329 |
330 | This block is called to generate the text that will be shown on the label `my_label`.
331 |
332 | We return the variable `count` for display on the label.
333 |
334 | This block generates the following Rust code...
335 |
336 | ```rust
337 | /// Callback function that will be called to create the formatted text for the label `my_label`
338 | #[infer_type] // Infer the missing types
339 | fn on_my_label_show(state: _, env: _) -> ArgValue {
340 | console::print("on_my_label_show\n");
341 | state.count.into()
342 | }
343 | ```
344 |
345 | # On Button Press
346 |
347 | 
348 |
349 | This block is called to when the button `my_button` is pressed.
350 |
351 | We increment the variable `count` by 1.
352 |
353 | This block generates the following Rust code...
354 |
355 | ```rust
356 | /// Callback function that will be called when the button `my_button` is pressed
357 | #[infer_type] // Infer the missing types
358 | fn on_my_button_press(ctx: _, state: _, env: _) {
359 | console::print("on_my_button_press\n");
360 | state.count = state.count + 1;
361 | }
362 | ```
363 |
364 | # Build And Flash The Firmware
365 |
366 | We’ll be flashing the PineTime firmware that’s based on open-source [__Apache Mynewt embedded operating system__](https://mynewt.apache.org/). Mynewt OS contains two components that we shall flash to PineTime…
367 |
368 | __Mynewt Bootloader__: This is the C code that’s run whenever we power on PineTime. The Bootloader executes the Mynewt Application upon startup.
369 |
370 | __Mynewt Application__: Contains a Rust application that controls the PineTime functions, and low-level system functions written in C.
371 |
372 | The Bootloader and Application firmware image files may be found at these locations…
373 |
374 | | Mynewt Component | Flash Memory Address | Location of Firmware Image |
375 | | :--- | :--- | :--- |
376 | | Bootloader | `0x0` | `~/pinetime-rust-mynewt/bin/targets/nrf52_boot/app/apps/boot_stub/boot_stub.elf.bin` |
377 | | Application | `0x8000` | `~/pinetime-rust-mynewt/bin/targets/nrf52_my_sensor/app/apps/my_sensor_app/my_sensor_app.img` |
378 |
379 | _From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/hw/bsp/nrf52/bsp.yml_
380 |
381 | 🛈 [_What is a Bootloader? Read this_](https://gist.github.com/lupyuen/93ba71e0ae5e746e7a68e4513e0a54d8)
382 |
383 | 1️⃣ At the lower left corner, there is a panel `Task Runner`. Click the panel to display the build and flash tasks.
384 |
385 | 2️⃣ In the Task Runner, click `[1] Build Bootloader`
386 |
387 | When the Terminal Panel appears, right-click the `Terminal` tab, select `Move Panel Right`
388 |
389 | After the building the Bootloader, we should see `Done`
390 |
391 | Ignore the message `There Are Task Errors`
392 |
393 | The Bootloader only needs to be built once.
394 |
395 | 3️⃣ In the Task Runner, click `[2] Build Application`
396 |
397 | After the building the Application, we should see `Done`
398 |
399 | If you see the message `Undefined Reference To Main`, click `[2] Build Application` again and it should succeed.
400 |
401 | The Application needs to be rebuilt whenever a source file has been changed.
402 |
403 | 4️⃣ In the Task Runner, click `[3] Image Application`
404 |
405 | After the creating the Firmware Image, we should see `Done`
406 |
407 | 5️⃣ In the Task Runner, click `[4] Flash Bootloader`
408 |
409 | After flashing the Bootloader Firmware to PineTime, we should see `Done`
410 |
411 |
412 | ```
413 | Flashing Bootloader...
414 | target halted due to debug-request, current mode: Thread
415 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000
416 | Enabled ARM Semihosting to show debug output
417 | ** Programming Started **
418 | Info : nRF52832-QFAA(build code: E1) 512kB Flash, 64kB RAM
419 | Warn : Adding extra erase range, 0x00000b78 .. 0x00000fff
420 | ** Programming Finished **
421 | ** Verify Started **
422 | ** Verified OK **
423 |
424 | Restarting...
425 | target halted due to debug-request, current mode: Thread
426 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000, semihosting
427 |
428 | **** Done!
429 | ```
430 |
431 | _From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-bootloader-pi.log_
432 |
433 | The Bootloader only needs to be flashed once.
434 |
435 | 6️⃣ In the Task Runner, click `[5] Flash Application`
436 |
437 | After the flashing the Application Firmware to PineTime, we should see `Done! Press Ctrl-C To Exit`…
438 |
439 | ```
440 | Flashing Application...
441 | target halted due to debug-request, current mode: Thread
442 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000
443 | Enabled ARM Semihosting to show debug output
444 | ** Programming Started **
445 | Info : nRF52832-QFAA(build code: E1) 512kB Flash, 64kB RAM
446 | Warn : Adding extra erase range, 0x0003e820 .. 0x0003efff
447 | ** Programming Finished **
448 | ** Verify Started **
449 | ** Verified OK **
450 |
451 | Restarting...
452 | target halted due to debug-request, current mode: Thread
453 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000, semihosting
454 | Enabled ARM Semihosting to show debug output
455 |
456 | **** Done! Press Ctrl-C to exit...
457 | ```
458 |
459 | _From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-application-pi.log_
460 |
461 | 7️⃣ Our Visual Rust application starts running on PineTime
462 |
463 | 
464 |
465 | 8️⃣ Click the Trash icon 🗑 near the top right to terminate the application. If we click the Close icon ❌ instead of the Trash icon, the next flash or debug command will fail.
466 |
467 | 
468 |
469 | # PineTime Updater
470 |
471 | Alternatively, flash the following two files to PineTime with [__PineTime Updater__](https://github.com/lupyuen/pinetime-updater)...
472 |
473 | 1. __MCUBoot Bootloader__
474 |
475 | File: `bin/targets/nrf52_boot/app/boot/mynewt/mynewt.elf`
476 |
477 | Address: `0x0`
478 |
479 | 1. __Rust+Mynewt Firmware__
480 |
481 | File: `bin/targets/nrf52_my_sensor/app/apps/my_sensor_app/my_sensor_app.elf`
482 |
483 | Address: `0x8000`
484 |
485 | # Debug The Firmware
486 |
487 | 1️⃣ Build the application: In the Task Runner, click `[2] Build Application`
488 |
489 | 2️⃣ Click `Debug → Start Debugging` or press `F5`
490 |
491 | This starts the VSCode Debugger and automatically flashes our updated firmware to PineTime.
492 |
493 | 3️⃣ Click `View → Output`
494 |
495 | In the Output Panel, select `Adapter Output`
496 |
497 | The debugging messages will be displayed here.
498 |
499 | 4️⃣ The program has paused at first line of code in our firmware, the Reset Handler.
500 |
501 | In the Debug Toolbar, click `Continue` or press `F5`
502 |
503 | 
504 |
505 | 🛈 [_What’s a Reset Handler? Read this_](https://gist.github.com/lupyuen/b0b7782f21330e292ea65b9c875bd9a7)
506 |
507 | 5️⃣ The debugger now pauses at the first line of the main function that’s defined in rust/app/src/lib.rs
508 |
509 | This is the first line of Rust code in our Rust Application, which will call test_display in a while.
510 |
511 | In the Debug Toolbar, click Continue or press F5
512 |
513 | 🛈 [_What’s a main function? Read this_](https://gist.github.com/lupyuen/5360769a2d92ec50d988cce92622abff)
514 |
515 | # Edit, Build and Debug the Visual Rust Application on Windows
516 |
517 | 
518 |
519 | To edit, build and debug the Visual Rust Application on Windows, follow these steps...
520 |
521 | ## _[Windows]_ Connect PineTime to ST-Link
522 |
523 | If we’re doing serious development with PineTime, I recommend getting an [ST-Link v2 USB dongle](https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180924134644&SearchText=st-link+v2&switch_new_app=y) ($2) that connects PineTime directly to our Windows, macOS or Linux computer.
524 |
525 | ST-Link allows us to flash PineTime directly from our computer, and it even supports firmware debugging (setting breakpoints, checking values of variables at runtime, …)
526 |
527 | Here’s how we connect PineTime to ST-Link…
528 |
529 | 
530 |
531 | | PineTime | ST-Link | Wire Colour |
532 | | :--- | :--- | :--- |
533 | | `SWDIO` | `SWDIO` | Yellow |
534 | | `SWDCLK` | `SWDCLK` | Blue |
535 | | `3.3V` | `3.3V` | Red |
536 | | `GND` | `GND` | Black |
537 | | `5V` | `5V` | Green (Optional) |
538 |
539 | Before connecting ST-Link to our Windows computer, the ST-Link USB driver should be installed...
540 |
541 | Download the ST-Link USB driver from ST-Link Driver Website (email registration required)…
542 |
543 | https://www.st.com/en/development-tools/stsw-link009.html
544 |
545 | Click `Get Software`
546 |
547 | Unzip the downloaded file. Double-click the driver installer:
548 | `dpinst_amd64.exe`
549 |
550 | ## _[Windows]_ Remove PineTime Flash Protection
551 |
552 | This must be done with a Raspberry Pi, not on Windows, because ST-Link is a High-Level Adapter that doesn't implement all flash commands. Follow the instructions above for Raspberry Pi.
553 |
554 | ## _[Windows]_ Install PineTime Build Tools
555 |
556 | 1️⃣ Download the pinetime-rust-mynewt.7z file attached below…
557 |
558 | https://github.com/lupyuen/pinetime-rust-mynewt/releases/download/v3.0.1/pinetime-rust-mynewt.7z
559 |
560 | Expand the `.7z` file with 7zip…
561 |
562 | https://www.7-zip.org/download.html
563 |
564 | 2️⃣ Click here to install Build Tools For Visual Studio 2019:
565 |
566 | https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019
567 |
568 | 
569 |
570 | Click the `Individual Components` tab
571 |
572 | Select the following components:
573 |
574 | 1. `Windows 10 SDK (10.0.18362.0)`
575 |
576 | 1. `C++ CMake Tools for Windows`
577 |
578 | 1. (This should be automatically selected) `MSVC v142 — VS 2019 C++ x64/x86 Build Tools`
579 |
580 | 3️⃣ Install `rustup` according to the instructions here:
581 | `rustup.rs`
582 |
583 | Click the link provided to download `rustup‑init.exe`
584 |
585 | Launch the downloaded file `rustup‑init.exe`
586 |
587 | If you see the message `Windows Defender SmartScreen prevented an unrecognised app from starting`…
588 |
589 | Click `More Info`
590 |
591 | Click `Run Anyway`
592 |
593 | At the `Welcome to Rust!` prompt, press Enter to select the default option:
594 |
595 | `1) Proceed with installation (default)`
596 |
597 | 4️⃣ Open the Command Prompt and enter…
598 |
599 | ```cmd
600 | :: Install Rust build tools for Arm Cortex
601 | rustup default nightly
602 | rustup update
603 | rustup target add thumbv7em-none-eabihf
604 | ```
605 |
606 | 5️⃣ Install [GNU Arm Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads) for Windows from Arm Developer Website…
607 |
608 | https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2019q3/RC1.1/gcc-arm-none-eabi-8-2019-q3-update-win32-sha1.exe?revision=fcadabed-d946-49dc-8f78-0732d2f43773?product=GNU%20Arm%20Embedded%20Toolchain,32-bit,,Windows,8-2019-q3-update
609 |
610 | Select this option at the last install step:
611 | `"Add path to environment variable"`
612 |
613 | 6️⃣ Install VSCode…
614 |
615 | https://code.visualstudio.com/
616 |
617 | ## _[Windows]_ Edit The Visual Rust Application
618 |
619 | 1️⃣ Launch VSCode
620 |
621 | Click `File → Open Folder`
622 |
623 | Select the downloaded folder `pinetime-rust-mynewt` and click OK
624 |
625 | When prompted to open the workspace, click Open Workspace
626 |
627 | When prompted to install Extension Recommendations, click `Install All`
628 |
629 | 2️⃣ Install the `Visual Embedded Rust` Extension...
630 |
631 | Click `View → Extensions`
632 |
633 | Search for `Visual Embedded Rust`
634 |
635 | Install the extension
636 |
637 | 3️⃣ Enable the Visual Rust application...
638 |
639 | Browse to `rust/app/Cargo.toml`
640 |
641 | Modify the file such that `visual_app` is uncommented and the other options are commented out...
642 |
643 | ```yaml
644 | default = [ # Select the conditional compiled features
645 | # "display_app", # Disable graphics display app
646 | # "ui_app", # Disable druid UI app
647 | "visual_app", # Enable Visual Rust app
648 | # "use_float", # Disable floating-point for GPS geolocation
649 | ]
650 | ```
651 |
652 | 4️⃣ Edit the Visual Rust application...
653 |
654 | Browse to [`rust/app/src/visual.rs`](https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/visual.rs)
655 |
656 | Click `Visual Editor` at top right
657 |
658 | 
659 |
660 | Use the Visual Editor to edit the Visual Rust application
661 |
662 | 
663 |
664 | 5️⃣ After editing, save the [`visual.rs`](https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/visual.rs) source file to save the visual program. Don't edit the Rust source file manually, always use the Visual Editor.
665 |
666 | ## _[Windows]_ Build And Flash The Firmware
667 |
668 | 1️⃣ At the lower left corner, there is a panel `Task Runner`. Click the panel to display the build and flash tasks.
669 |
670 | 2️⃣ In the Task Runner, click `[1] Build Bootloader`
671 |
672 | When the Terminal Panel appears, right-click the `Terminal` tab, select `Move Panel Right`
673 |
674 | After the building the Bootloader, we should see `Done`
675 |
676 | Ignore the message `There Are Task Errors`
677 |
678 | The Bootloader only needs to be built once.
679 |
680 | 3️⃣ In the Task Runner, click `[2] Build Application`
681 |
682 | After the building the Application, we should see `Done`
683 |
684 | If you see the message `Undefined Reference To Main`, click `[2] Build Application` again and it should succeed.
685 |
686 | The Application needs to be rebuilt whenever a source file has been changed.
687 |
688 | __Note:__ When we run `Build Application`, the build script will overwrite the default `.vscode/launch.json` (meant for Raspberry Pi) with the correct version `.vscode/launch-nrf52.json` (meant for ST-Link on Windows and macOS)
689 |
690 | 4️⃣ In the Task Runner, click `[3] Image Application`
691 |
692 | After the creating the Firmware Image, we should see `Done`
693 |
694 | 5️⃣ In the Task Runner, click `[4] Flash Bootloader`
695 |
696 | After flashing the Bootloader Firmware to PineTime, we should see `Done`
697 |
698 |
699 | ```
700 | Flashing Bootloader...
701 | target halted due to debug-request, current mode: Thread
702 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000
703 | Enabled ARM Semihosting to show debug output
704 | ** Programming Started **
705 | Info : nRF52832-QFAA(build code: E1) 512kB Flash, 64kB RAM
706 | Warn : Adding extra erase range, 0x00000b78 .. 0x00000fff
707 | ** Programming Finished **
708 | ** Verify Started **
709 | ** Verified OK **
710 |
711 | Restarting...
712 | target halted due to debug-request, current mode: Thread
713 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000, semihosting
714 |
715 | **** Done!
716 | ```
717 |
718 | _From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-bootloader-pi.log_
719 |
720 | The Bootloader only needs to be flashed once.
721 |
722 | 6️⃣ In the Task Runner, click `[5] Flash Application`
723 |
724 | After the flashing the Application Firmware to PineTime, we should see `Done! Press Ctrl-C To Exit`…
725 |
726 | ```
727 | Flashing Application...
728 | target halted due to debug-request, current mode: Thread
729 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000
730 | Enabled ARM Semihosting to show debug output
731 | ** Programming Started **
732 | Info : nRF52832-QFAA(build code: E1) 512kB Flash, 64kB RAM
733 | Warn : Adding extra erase range, 0x0003e820 .. 0x0003efff
734 | ** Programming Finished **
735 | ** Verify Started **
736 | ** Verified OK **
737 |
738 | Restarting...
739 | target halted due to debug-request, current mode: Thread
740 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000, semihosting
741 | Enabled ARM Semihosting to show debug output
742 |
743 | **** Done! Press Ctrl-C to exit...
744 | ```
745 |
746 | _From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-application-pi.log_
747 |
748 | 7️⃣ Our Visual Rust application starts running on PineTime
749 |
750 | 
751 |
752 | 8️⃣ Click the Trash icon 🗑 near the top right to terminate the application. If we click the Close icon ❌ instead of the Trash icon, the next flash or debug command will fail.
753 |
754 | 
755 |
756 | ## _[Windows]_ Debug The Firmware
757 |
758 | 1️⃣ Build the application: In the Task Runner, click `[2] Build Application`
759 |
760 | The build script will also overwrite the default `.vscode/launch.json` (meant for Raspberry Pi) with the correct version `.vscode/launch-nrf52.json` (meant for ST-Link on Windows and macOS)
761 |
762 | 2️⃣ Click `Debug → Start Debugging` or press `F5`
763 |
764 | This starts the VSCode Debugger and automatically flashes our updated firmware to PineTime.
765 |
766 | 3️⃣ Click `View → Output`
767 |
768 | In the Output Panel, select `Adapter Output`
769 |
770 | The debugging messages will be displayed here.
771 |
772 | 4️⃣ The program has paused at first line of code in our firmware, the Reset Handler.
773 |
774 | In the Debug Toolbar, click `Continue` or press `F5`
775 |
776 | 
777 |
778 | 🛈 [_What’s a Reset Handler? Read this_](https://gist.github.com/lupyuen/b0b7782f21330e292ea65b9c875bd9a7)
779 |
780 | 5️⃣ The debugger now pauses at the first line of the main function that’s defined in rust/app/src/lib.rs
781 |
782 | This is the first line of Rust code in our Rust Application, which will call test_display in a while.
783 |
784 | In the Debug Toolbar, click Continue or press F5
785 |
786 | 🛈 [_What’s a main function? Read this_](https://gist.github.com/lupyuen/5360769a2d92ec50d988cce92622abff)
787 |
788 | # TODO: Edit, Build and Debug the Visual Rust Application on macOS
789 |
--------------------------------------------------------------------------------
/README.old.md:
--------------------------------------------------------------------------------
1 | # visual-embedded-rust
2 |
3 | - Create and edit Embedded Rust programs visually by dragging and dropping blocks
4 |
5 | - Generates Embedded Rust firmware code for PineTime Smart Watch hosted on [Apache Mynewt](https://mynewt.apache.org/) realtime operating system
6 |
7 | # Connect PineTime to Raspberry Pi
8 |
9 | 1️⃣ Carefully pry open the PineTime casing. Use tweezers to pivot the shiny battery gently to the side. Be careful not to break the red and black wires that connect the battery to the watch!
10 |
11 | 2️⃣ Just above the battery we see 4 shiny rings. This is the __[Serial Wire Debug](https://en.wikipedia.org/wiki/JTAG#Serial_Wire_Debug) (SWD)__ Port for PineTime. We’ll use this port to flash our firmware to PineTime. The 4 pins (from left to right) are SWDIO (Data I/O), SWDCLK (Clock), 3.3V, GND.
12 |
13 | 🛈 [_What is “flash memory” / “flashing” / “firmware”? Read this_](https://gist.github.com/lupyuen/41fffaddade277d27c48697bca21d837)
14 |
15 | The exposed copper wire at the top centre of the photo is the Bluetooth antenna. Bend it upwards so that it doesn’t come into contact with anything.
16 |
17 | 3️⃣ At lower right we see a pad marked 5V. We’ll connect this pad to Raspberry Pi to charge the battery. If charging of the battery is not needed during development, we may leave5V disconnected.
18 |
19 | 4️⃣ Connect the SWD Port and the 5V Pad (optional) to the Raspberry Pi with [__Solid-Core Wire (22 AWG)__](https://www.adafruit.com/product/288) and [__Female-To-Female Jumper Cables__](https://www.adafruit.com/product/1951)…
20 |
21 | | PineTime | Raspberry Pi | Wire Colour |
22 | | :--- | :--- | :--- |
23 | | `SWDIO` | `Header Pin 19 (MOSI)` | Yellow |
24 | | `SWDCLK` | `Header Pin 23 (SCLK)` | Blue |
25 | | `3.3V` | `3.3V` | Red |
26 | | `GND` | `GND` | Black |
27 | | `5V` | `5V` | Green (Optional) |
28 |
29 | 5️⃣ We may use Raspberry Pi Zero, 1, 2, 3 or 4.
30 |
31 | 6️⃣ The PineTime touchscreen needs to be accessible during development, so I mounted PineTime on a [$2 clear box cover from Daiso](https://www.daisojapan.com/p-30955-clear-box-28-x-47-x-19-x-in-12pks.aspx) with Blu Tack and sticky tape.
32 |
33 | # Remove PineTime Flash Protection
34 |
35 | PineTime is shipped with preloaded demo firmware. We need to erase the demo firmware and unprotect PineTime’s flash memory so that we may flash our own firmware.
36 |
37 | 🛈 [_What is “flash protection”? Read this_](https://gist.github.com/lupyuen/3ee440542853e1e637582c4efa1b240a)
38 |
39 | 1️⃣ Power on the Raspberry Pi. Open a command prompt and enter the following…
40 |
41 | ```bash
42 | sudo raspi-config
43 | ```
44 |
45 | Select `Interfacing Options → SPI → Yes`
46 |
47 | Select `Finish`
48 |
49 | At the command prompt, enter the following…
50 |
51 | ```bash
52 | # Remove folders ~/pinetime-rust-mynewt and ~/openocd-spi (if they exist)
53 | rm -rf ~/pinetime-rust-mynewt
54 | rm -rf ~/openocd-spi
55 |
56 | # Download and extract "pinetime-rust-mynewt" folder containing our prebuilt firmware, source files and flashing scripts
57 | sudo apt install -y wget p7zip-full
58 | cd ~
59 | wget https://github.com/lupyuen/pinetime-rust-mynewt/releases/download/v3.0.3/pinetime-rust-mynewt.7z
60 | 7z x pinetime-rust-mynewt.7z
61 | rm pinetime-rust-mynewt.7z
62 |
63 | # Install build tools for PineTime: VSCode, Rust, gcc, gdb, openocd-spi, newt
64 | cd ~/pinetime-rust-mynewt
65 | scripts/install-pi.sh
66 | ```
67 |
68 | 2️⃣ At the `Welcome to Rust!` prompt, press Enter to select the default option:
69 |
70 | `1) Proceed with installation (default)`
71 |
72 | If you see this error…
73 |
74 | ```
75 | Cloning into 'openocd-spi/jimtcl'...
76 | fatal: unable to access 'http://repo.or.cz/r/jimtcl.git/': Recv failure: Connection reset by peer
77 | fatal: clone of 'http://repo.or.cz/r/jimtcl.git' into submodule path '/private/tmp/aa/openocd-spi/jimtcl' failed
78 | ```
79 |
80 | It means that the sub-repository for one of the dependencies jimtcl is temporarily down. You may download the pre-built `openocd-spi` binaries [from this link](https://github.com/lupyuen/pinetime-rust-mynewt/releases/download/openocd-spi2/openocd-spi.7z). Then copy the executable openocd-spi/src/openocd to pinetime-rust-mynewt/openocd/bin/openocd
81 |
82 | 3️⃣ When the installation has completed, enter the following at the command prompt…
83 |
84 | ```bash
85 | # Remove flash protection from PineTime and erase demo firmware
86 | cd ~/pinetime-rust-mynewt
87 | scripts/nrf52-pi/flash-unprotect.sh
88 | ```
89 |
90 | 4️⃣ We should see `Shut Down And Power Off Your Raspberry Pi`…
91 |
92 | If you see `Clock Speed` and nothing else after that…
93 |
94 | ```
95 | Info : BCM2835 SPI SWD driver
96 | Info : SWD only mode enabled
97 | Info : clock speed 31200 kHz
98 | ```
99 |
100 | Then the connection to the SWD Port is probably loose, check the pins.
101 |
102 | Also enter `sudo raspi-config` and confirm that the SPI port has been enabled.
103 |
104 | If you see this instead…
105 |
106 | ```
107 | openocd/bin/openocd: cannot execute binary file: Exec format error
108 | ```
109 |
110 | Then `install-pi.sh` probably didn’t run correctly. To fix this, copy the `openocd` executable like this…
111 |
112 | ```bash
113 | cp $HOME/openocd-spi/src/openocd $HOME/pinetime-rust-mynewt/openocd/bin/openocd
114 | ```
115 |
116 | 5️⃣ Shut down and power off your Raspberry Pi. Wait 30 seconds for the red and green LEDs on your Pi to turn off. Power on your Pi. Enter the same commands at a command prompt…
117 |
118 | ```bash
119 | # Remove flash protection from PineTime and erase demo firmware
120 | cd ~/pinetime-rust-mynewt
121 | scripts/nrf52-pi/flash-unprotect.sh
122 | ```
123 |
124 | 6️⃣ We should see `Flash Is Already Unprotected`…
125 |
126 | PineTime’s demo firmware has been erased and the flash protection has been removed.
127 |
128 | 🛈 [_What is OpenOCD? Why Raspberry Pi and not ROCK64 or Nvidia Jetson Nano? Read this_](https://gist.github.com/lupyuen/18e66c3e81e11050a10d1192c5b84bb0)
129 |
130 | # Edit The Visual Rust Application
131 |
132 | We shall be using VSCode with the Visual Embedded Rust Extension to edit our Visual Rust application graphically.
133 |
134 | 🛈 [_What is VSCode? Is it related to Visual Studio? How is Microsoft involved? Read this_](https://gist.github.com/lupyuen/08e383845d68d3337747e8eb59d0f624)
135 |
136 | 1️⃣ Launch VSCode by clicking the Raspberry Pi Menu (top left corner) → Programming → Code OSS Headmelted
137 |
138 | In VSCode, click `File → Open Folder`
139 |
140 | Under `Home`, select the folder `pinetime-rust-mynewt` and click OK
141 |
142 | When prompted to open the workspace, click Open Workspace
143 |
144 | When prompted to install Extension Recommendations, click `Install All`
145 |
146 | Ignore the message `Unable To Watch For File Changes`. Close the message when it appears.
147 |
148 | 2️⃣ Install the `Visual Embedded Rust` Extension...
149 |
150 | Click `View → Extensions`
151 |
152 | Search for `Visual Embedded Rust`
153 |
154 | Install the extension
155 |
156 | 3️⃣ Enable the Visual Rust application...
157 |
158 | Browse to `rust/app/Cargo.toml`
159 |
160 | Modify the file such that `visual_app` is uncommented and the other options are commented out...
161 |
162 | ```yaml
163 | default = [ # Select the conditional compiled features
164 | # "display_app", # Disable graphics display app
165 | # "ui_app", # Disable druid UI app
166 | "visual_app", # Enable Visual Rust app
167 | # "use_float", # Disable floating-point for GPS geolocation
168 | ]
169 | ```
170 |
171 | 4️⃣ Edit the Visual Rust application...
172 |
173 | Browse to `rust/app/src/visual.rs`
174 |
175 | Click `Visual Editor` at top right
176 |
177 | 
178 |
179 | Use the Visual Editor to edit the Visual Rust application
180 |
181 | 5️⃣ After editing, save the `visual.rs` source file to save the visual program. Don't edit the Rust source file manually, always use the Visual Editor.
182 |
183 | # Build And Flash The Visual Rust Application
184 |
185 | We’re now ready to flash our own firmware to PineTime! We’ll be flashing the PineTime firmware that’s based on open-source [__Apache Mynewt embedded operating system__](https://mynewt.apache.org/). Mynewt OS contains two components that we shall flash to PineTime…
186 |
187 | __Mynewt Bootloader__: This is the C code that’s run whenever we power on PineTime. The Bootloader executes the Mynewt Application upon startup.
188 |
189 | __Mynewt Application__: Contains a Rust application that controls the PineTime functions, and low-level system functions written in C.
190 |
191 | The Bootloader and Application firmware image files may be found at these locations…
192 |
193 | | Mynewt Component | Flash Memory Address | Location of Firmware Image |
194 | | :--- | :--- | :--- |
195 | | Bootloader | `0x0` | `~/pinetime-rust-mynewt/bin/targets/nrf52_boot/app/apps/boot_stub/boot_stub.elf.bin` |
196 | | Application | `0x8000` | `~/pinetime-rust-mynewt/bin/targets/nrf52_my_sensor/app/apps/my_sensor_app/my_sensor_app.img` |
197 |
198 | _From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/hw/bsp/nrf52/bsp.yml_
199 |
200 | 🛈 [_What is a Bootloader? Read this_](https://gist.github.com/lupyuen/93ba71e0ae5e746e7a68e4513e0a54d8)
201 |
202 | 1️⃣ At the lower left corner, there is a panel `Task Runner`. Click the panel to display the build and flash tasks.
203 |
204 | 2️⃣ In the Task Runner, click `[1] Build Bootloader`
205 |
206 | When the Terminal Panel appears, right-click the `Terminal` tab, select `Move Panel Right`
207 |
208 | After the building the Bootloader, we should see `Done`
209 |
210 | Ignore the message `There Are Task Errors`
211 |
212 | 3️⃣ In the Task Runner, click `[2] Build Application`
213 |
214 | After the building the Application, we should see `Done`
215 |
216 | If you see the message `Undefined Reference To Main`, click `[2] Build Application` again and it should succeed.
217 |
218 | 4️⃣ In the Task Runner, click `[3] Image Application`
219 |
220 | After the creating the Firmware Image, we should see `Done`
221 |
222 | 5️⃣ In the Task Runner, click `[4] Flash Bootloader`
223 |
224 | After flashing the Bootloader Firmware to PineTime, we should see `Done`
225 |
226 |
227 | ```
228 | Flashing Bootloader...
229 | target halted due to debug-request, current mode: Thread
230 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000
231 | Enabled ARM Semihosting to show debug output
232 | ** Programming Started **
233 | Info : nRF52832-QFAA(build code: E1) 512kB Flash, 64kB RAM
234 | Warn : Adding extra erase range, 0x00000b78 .. 0x00000fff
235 | ** Programming Finished **
236 | ** Verify Started **
237 | ** Verified OK **
238 |
239 | Restarting...
240 | target halted due to debug-request, current mode: Thread
241 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000, semihosting
242 |
243 | **** Done!
244 | ```
245 |
246 | _From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-bootloader-pi.log_
247 |
248 | The Bootloader only needs to be flashed once.
249 |
250 | 6️⃣ In the Task Runner, click `[5] Flash Application`
251 |
252 | After the flashing the Application Firmware to PineTime, we should see `Done! Press Ctrl-C To Exit`…
253 |
254 | ```
255 | Flashing Application...
256 | target halted due to debug-request, current mode: Thread
257 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000
258 | Enabled ARM Semihosting to show debug output
259 | ** Programming Started **
260 | Info : nRF52832-QFAA(build code: E1) 512kB Flash, 64kB RAM
261 | Warn : Adding extra erase range, 0x0003e820 .. 0x0003efff
262 | ** Programming Finished **
263 | ** Verify Started **
264 | ** Verified OK **
265 |
266 | Restarting...
267 | target halted due to debug-request, current mode: Thread
268 | xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000, semihosting
269 | Enabled ARM Semihosting to show debug output
270 |
271 | **** Done! Press Ctrl-C to exit...
272 | ```
273 |
274 | _From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-application-pi.log_
275 |
276 | 7️⃣ Our Visual Rust application starts running on PineTime
277 |
278 | 8️⃣ Click the Trash icon 🗑 near the top right to terminate the application. If we click the Close icon ❌ instead of the Trash icon, the next flash or debug command will fail.
279 |
280 | # TODO: Debug The Visual Rust Application
281 |
282 | # TODO: Flash Bootloader and Application to PineTime
283 |
284 |
285 | 1️⃣ To flash Mynewt Bootloader to PineTime, enter the following at the command prompt…
286 |
287 | ```bash
288 | # Flash Mynewt Bootloader to PineTime
289 | cd ~/pinetime-rust-mynewt
290 | scripts/nrf52-pi/flash-boot.sh
291 | ```
292 |
293 | 2️⃣ We should see `Done`…
294 |
295 |
296 | 3️⃣ To flash Mynewt Application to PineTime, enter the following at the command prompt…
297 |
298 | ```bash
299 | # Flash Rust+Mynewt Application to PineTime
300 | cd ~/pinetime-rust-mynewt
301 | scripts/nrf52-pi/flash-app.sh
302 | ```
303 |
304 | 4️⃣
305 |
306 | 5️⃣ The new PineTime firmware runs after the flashing has been completed. Here are the debugging messages produced by our Rust application…
307 |
308 | ```
309 | Info : Listening on port 6666 for tcl connections
310 | Info : Listening on port 4444 for telnet connections
311 | TMP create temp_stub_0
312 | NET hwid 4a f8 cf 95 6a be c1 f6 89 ba 12 1a
313 | NET standalone node
314 | Rust test display
315 | ```
316 |
317 | _From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-application-pi.log_
318 |
319 | 6️⃣ And we should see some text and graphics on the PineTime screen
320 |
321 | 7️⃣ Press `Ctrl-C` to stop the display of debugging messages.
322 |
323 | We have flashed a simple Rust application located at [`pinetime-rust-mynewt/rust/app/src/display.rs`](https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/display.rs) that renders some graphics and text to the PineTime display
324 |
325 | [Here’s a good introduction to Rust programming](https://doc.rust-lang.org/book/title-page.html) and [here’s a good overview of Rust](https://doc.rust-lang.org/rust-by-example/)
326 |
327 | How do we modify this Rust application and rebuild the firmware? We have 3 options:
328 |
329 | [Option 1] Build the firmware on a Windows computer, and copy to Pi for flashing
330 |
331 | [Option 2] Build the firmware on a macOS computer, and copy to Pi for flashing
332 |
333 | [Option 3] Build the firmware on a (powerful) Raspberry Pi (or PineBook Pro) and flash directly
334 |
335 |
336 |
337 |
338 | # TODO
339 |
340 | Pi Version: https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v3.0.3
341 |
342 | macOS Version: https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v3.0.2
343 |
344 | Windows Version: https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v3.0.1
345 |
346 | 1. Install `rustup` with support for nightly target `thumbv7em-none-eabihf`.
347 |
348 | Follow the instructions at https://rustup.rs/
349 |
350 | Then execute...
351 |
352 | ```bash
353 | rustup default nightly
354 | rustup update
355 | rustup target add thumbv7em-none-eabihf
356 | ```
357 |
358 | 1. Install Arm toolchain `gcc-arm-none-eabi` and the `newt` build tool for Mynewt. Refer to this script...
359 |
360 | [`scripts/install-pi.sh`](scripts/install-pi.sh)
361 |
362 | 1. Clone this repository...
363 |
364 | ```bash
365 | git clone --recursive https://github.com/lupyuen/pinetime-rust-mynewt
366 | ```
367 |
368 | 1. [`repos`](repos) folder should contain the Mynewt source code. If your `repos` folder is empty, install the Mynewt source code with the `newt install` command:
369 |
370 | ```bash
371 | cd pinetime-rust-mynewt
372 | newt install
373 | ```
374 |
375 | Ignore the error `Error: Error updating "mcuboot"`
376 |
377 | 1. Build the bootloader...
378 |
379 | ```bash
380 | cd pinetime-rust-mynewt
381 | scripts/nrf52/build-boot.sh
382 | ```
383 |
384 | 1. Build the application...
385 |
386 | ```bash
387 | scripts/build-app.sh
388 | ```
389 |
390 | If you see the error `Undefined main`, run `scripts/build-app.sh` again. It should fix the error.
391 |
392 | 1. Create the application firmware image...
393 |
394 | ```bash
395 | scripts/nrf52/image-app.sh
396 | ```
397 |
398 | 1. Flash the bootloader...
399 |
400 | ```bash
401 | scripts/nrf52-pi/flash-boot.sh
402 | ```
403 |
404 | 1. Flash the application and run it...
405 |
406 | ```bash
407 | scripts/nrf52-pi/flash-app.sh
408 | ```
409 |
410 | 1. You may need to edit the scripts to set the right path of OpenOCD.
411 |
412 | Also for Windows, the ST-Link interface for OpenOCD is `stlink-v2.cfg` instead of `stlink.cfg`.
413 |
414 | 1. Check this article in case of problems...
415 |
416 | [_Build and Flash Rust+Mynewt Firmware for PineTime Smart Watch_](https://medium.com/@ly.lee/build-and-flash-rust-mynewt-firmware-for-pinetime-smart-watch-5e14259c55?source=friends_link&sk=150b2a73b84144e5ef25b985e65aebe9)
417 |
418 |
419 | # Documentataion for Previous Version
420 |
421 | The documentation below is being updated
422 |
423 | ## OBSOLETE: Features
424 |
425 | - Generates Embedded Rust code for [STM32 Blue Pill](https://medium.com/swlh/super-blue-pill-like-stm32-blue-pill-but-better-6d341d9347da?source=friends_link&sk=956087171b9b9efcc484ea60b9c78c16) with [Apache Mynewt](https://mynewt.apache.org/) realtime operating system
426 |
427 | Watch the demo...
428 |
429 | [微博视频](https://weibo.com/7285313566/I2MWZ1CnK)
430 |
431 | [YouTube Video](https://youtu.be/ytGa-7q6sqY)
432 |
433 | Read the articles...
434 |
435 | 1. [_"Visual Embedded Rust Programming with Visual Studio Code"_](https://medium.com/@ly.lee/visual-embedded-rust-programming-with-visual-studio-code-1bc1262e398c?source=friends_link&sk=222de63e45993aacd0db5a2e4b1f33c7)
436 |
437 | 1. [_"Advanced Topics for Visual Embedded Rust Programming"_](https://medium.com/@ly.lee/advanced-topics-for-visual-embedded-rust-programming-ebf1627fe397?source=friends_link&sk=01f0ae0e1b82efa9fd6b8e5616c736af)
438 |
439 | 1. [_"Rust Rocks NB-IoT! STM32 Blue Pill with Quectel BC95-G on Apache Mynewt"_](https://medium.com/@ly.lee/rust-rocks-nb-iot-stm32-blue-pill-with-quectel-bc95-g-on-apache-mynewt-ef62a7e28f7e?source=friends_link&sk=aaa21371f68a07c543066b6b89a760f0)
440 |
441 | 1. [_"Visual Programming with Embedded Rust? Yes we can with Apache Mynewt and Google Blockly!"_](https://medium.com/@ly.lee/visual-programming-with-embedded-rust-yes-we-can-with-apache-mynewt-and-google-blockly-8b67ef7412d7?source=friends_link&sk=353fb92b6f20ebf885ff5c9be44fd6f2)
442 |
443 | ## OBSOLETE: Document Contents
444 |
445 | 1. Usage
446 |
447 | 1. Build The Firmware
448 |
449 | 1. Connect The Hardware
450 |
451 | 1. Flash The Firmware To Blue Pill
452 |
453 | 1. Run The Program
454 |
455 | 1. Function 1: On Start
456 |
457 | 1. Function 2: Start Sensor Listener
458 |
459 | 1. Function 3: Handle Sensor Data
460 |
461 | 1. Function 4: Send Sensor Data
462 |
463 | 1. Rust Source Files
464 |
465 | 1. Program Settings
466 |
467 | 1. CoAP: Constrained Application Protocol
468 |
469 | 1. Quectel NB-IoT AT Commands
470 |
471 | 1. Configuring the CoAP Server at thethings.io
472 |
473 | 1. Typeless Rust
474 |
475 | 1. How Small Is Rust?
476 |
477 | 1. Why Blue Pill? Power vs Price Compromise
478 |
479 | 1. Why Apache Mynewt? Evolution of Rust on Bare Metal
480 |
481 | 1. How Safe Is Rust? Safe Wrappers for Mynewt
482 |
483 | 1. Inside The Visual Embedded Rust Extension for Visual Studio Code
484 |
485 | 1. Building The Visual Embedded Rust Extension
486 |
487 | 1. References
488 |
489 | 1. Release Notes
490 |
491 | ## OBSOLETE: Usage
492 |
493 | 1. In Visual Studio Code, Click `File → Open` to open any folder
494 |
495 | 
496 |
497 | 1. In the `Explorer → (Folder Name)` pane at top left, create a new Rust source file, like `lib.rs`
498 |
499 | 
500 |
501 | 1. Edit the Rust source file. Click `Visual Editor` at top right
502 |
503 | 
504 |
505 | 1. When prompted to populate the visual program into the Rust source file, click `OK`
506 |
507 | 
508 |
509 | 1. Click the Rust source file to see the generated Rust code. Save the file to save the visual program. Don't edit the Rust source file manually, always use the visual editor.
510 |
511 | [Sample Rust source file containing generated Rust code and XML blocks](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/rust/visual/src/lib.rs)
512 |
513 | 
514 |
515 | ## OBSOLETE: Build The Firmware
516 |
517 | To compile the generated Rust program into Blue Pill firmware...
518 |
519 | 1. Click here to install `Build Tools For Visual Studio 2019`:
520 | https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019
521 |
522 | 1. Click the `Individual Components` tab
523 |
524 | Select the following components:
525 | `Windows 10 SDK (10.0.18362.0)`
526 | `C++ CMake Tools for Windows`
527 | (This should be automatically selected) `MSVC v142 — VS 2019 C++ x64/x86 Build Tools`
528 |
529 | 
530 |
531 | 1. Install rustup according to the instructions here:
532 | https://rustup.rs
533 |
534 | Click the link provided to download `rustup‑init.exe`
535 | Launch the downloaded file `rustup‑init.exe`
536 |
537 | If you see the message `Windows Defender SmartScreen prevented an unrecognised app from starting`…
538 | Click `More Info`
539 | Click `Run Anyway`
540 |
541 | At the `Welcome to Rust!` prompt, press Enter to select the default option:
542 | `1) Proceed with installation (default)`
543 |
544 | 1. Open the Windows Command Prompt. Enter into the command prompt:
545 |
546 | ```
547 | rustup default nightly
548 | rustup update
549 | rustup target add thumbv7m-none-eabi
550 | rustc -V
551 | ```
552 |
553 | The reported version of rustc should be 1.38.0 or later:
554 | `rustc 1.38.0-nightly (435236b88 2019–08–01)`
555 |
556 | 1. Download the `stm32bluepill-mynewt-sensor.7z` file attached below…
557 | https://github.com/lupyuen/stm32bluepill-mynewt-sensor/releases/tag/v7.0.3
558 |
559 | Expand the `.7z` file with 7zip…
560 | https://www.7-zip.org/download.html
561 |
562 | 1. Install Arm Cross-Compiler and Linker for Windows from Arm Developer Website…
563 | https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2019q3/RC1.1/gcc-arm-none-eabi-8-2019-q3-update-win32-sha1.exe?revision=fcadabed-d946-49dc-8f78-0732d2f43773?product=GNU%20Arm%20Embedded%20Toolchain,32-bit,,Windows,8-2019-q3-update
564 |
565 | Select this option at the last install step:
566 | `Add path to environment variable`
567 |
568 | 1. Download the ST-Link USB driver from ST-Link Driver Website (email registration required)…
569 | https://www.st.com/en/development-tools/stsw-link009.html
570 |
571 | Click `Get Software`
572 | Unzip the downloaded file. Double-click the driver installer:
573 | `dpinst_amd64.exe`
574 |
575 | 1. Launch Visual Studio Code
576 | Install the extension “Cortex-Debug”…
577 | https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug
578 |
579 | 1. Click `File → Open Folder`
580 |
581 | Select the downloaded folder `stm32bluepill-mynewt-sensor`
582 |
583 | When prompted to open the workspace, click `Open Workspace`
584 |
585 | 
586 |
587 | 1. Copy your Visual Program source file to `stm32bluepill-mynewt-sensor/rust/app/src/lib.rs`. Overwrite the existing file.
588 |
589 | 
590 |
591 | 1. Delete the files `app_network.rs` and `app_sensor.rs` in that folder
592 |
593 | 1. If you have a Quectel NB-IoT module…
594 |
595 | Open the following file and configure the program settings:
596 | `targets/bluepill_my_sensor/syscfg.yml`
597 | Change the NB-IoT band setting `NBIOT_BAND`. Check with your NB-IoT operator for the band to use.
598 |
599 | 1. Click `Terminal → Run Task → [1] Build bluepill_boot`
600 |
601 | This builds the bootloader, which starts the Apache Mynewt operating system upon startup. If it shows errors, [compare with this build log](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/build-bootloader.log).
602 |
603 | 1. Click `Terminal → Run Task → [2] Build bluepill_my_sensor`
604 |
605 | This builds the firmware containing our Rust program. [Compare with this build log](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/build-application.log).
606 |
607 | When our Rust program has been successfully compiled as Blue Pill ROM firmware, we should see this…
608 |
609 | 
610 |
611 | 1. Click `Terminal → Run Task → [3] Image bluepill_my_sensor`
612 |
613 | This creates the Blue Pill flash image from the firmware. [Compare with this image log](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/image.log)
614 |
615 | If any source files or configuration files are changed, rebuild the application by clicking
616 | `Terminal → Run Task → [2] Build bluepill_my_sensor`
617 |
618 | ## OBSOLETE: Connect The Hardware
619 |
620 | | | |
621 | |:- |:- |
622 | |  _From top to bottom: STM32 Blue Pill, ST-Link V2, Quectel BC95-G breakout board with antenna, NB-IoT SIM_ | We’ll need the following hardware…
[1] __STM32 Blue Pill:__ Under $2, search [AliExpress](https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180924131057&SearchText=stm32f103c8t6+development+board&switch_new_app=y) for `stm32f103c8t6 development board`
[2] __ST-Link V2 USB Adapter:__ Under $2, search [AliExpress](https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180924134644&SearchText=st-link+v2&switch_new_app=y) for `st-link v2`
__Optional:__ To transmit data to the NB-IoT network, we’ll also need…
[3] __Quectel BC95-G Global NB-IoT Module__ ([breakout board with antenna](https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20190725022150&SearchText=bc95-g+nb101&switch_new_app=y))
I ordered mine [from Taobao](https://item.taobao.com/item.htm?id=577310122904). [The manual in Chinese is here](http://rs.iotxx.com/uploads/doc/%E8%B0%B7%E9%9B%A8NB10x%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E%E4%B9%A6-V1.3.pdf).
BC95-G works in all NB-IoT frequency bands worldwide. If you’re buying a different NB-IoT module, check that it supports your local NB-IoT Frequency Band. (For example: In Singapore I’m using NB-IoT Frequency Band 8 with StarHub)
[4] __NB-IoT SIM__ from your local NB-IoT network operator
Many thanks to [StarHub](https://www.starhub.com/) for sponsoring the NB-IoT SIM that I used for this tutorial!
623 | |
624 |
625 | 
626 |
627 | Connect Blue Pill to Quectel BC95-G and ST-Link as follows…
628 |
629 | | Blue Pill | Quectel BC95-G | ST-Link V2 | Wire Colour |
630 | | :--- | :--- | :--- | :--- |
631 | | `PA2 (UART2 TX2)` | `RXD (Pin 4)` | | Green |
632 | | `PA3 (UART2 RX2)` | `TXD (Pin 3)` | | Blue |
633 | | `GND` | `GND (Pin 1)` | | Black |
634 | | | `VCC (Pin 2)` | `5.0V (Pin 10)` | Yellow |
635 | | `3V3` | | `3.3V (Pin 8)` | Red |
636 | | `DIO` | | `SWDIO (Pin 4)` | Orange |
637 | | `DCLK` | | `SWDCLK (Pin 2)` | Brown |
638 | | `GND` | | `GND (Pin 6)` | Black |
639 |
640 | Both yellow jumpers on Blue Pill should be set to the 0 position, as shown in the above photo.
641 |
642 | | | |
643 | | :- | :- |
644 | |  _SIM partially exposed to show the unusual orientation_ | Note that we are powering the Quectel module with __5V from ST-Link instead of 3.3V from Blue Pill__. That’s because the module requires more power than Blue Pill can provide. (How did I find out? Because the module kept restarting when I powered it from Blue Pill.)
__Check the documentation for your Quectel breakout board to confirm that it supports 5V__. ([Mine does](http://rs.iotxx.com/uploads/doc/%E8%B0%B7%E9%9B%A8NB10x%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E%E4%B9%A6-V1.3.pdf))
__Insert the NB-IoT SIM__ according to the orientation shown in the photo. (Yes the SIM notch faces outward, not inward).
_Remember: Always connect the antenna before powering up the NB-IoT module!_
__If you’re using Windows:__ Make sure that the ST-Link Driver has been installed before connecting ST-Link to your computer
645 | |
646 |
647 | ## OBSOLETE: Flash The Firmware To Blue Pill
648 |
649 | 
650 |
651 | 1. Check that the Blue Pill is connected to ST-Link…
652 | And that the ST-Link is connected to your computer’s USB port.
653 | Now let’s head back to Visual Studio Code…
654 |
655 | 1. Click `Terminal → Run Task → [4] Load bluepill_boot`
656 |
657 | This flashes the bootloader to Blue Pill, to start the Apache Mynewt operating system upon startup. If it shows errors, [compare with this flash log](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/load-bootloader.log).
658 |
659 | 1. Click `Terminal → Run Task → [5] Load bluepill_my_sensor`
660 |
661 | This flashes the firmware (containing our Visual Program) to Blue Pill. If it shows errors, [compare with this flash log](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/load-application.log).
662 |
663 | ## OBSOLETE: Run The Program
664 |
665 | 1. Click `Debug → Start Debugging`
666 |
667 | 1. Click `View → Output`
668 |
669 | Select `Adapter Output` to see the Blue Pill log
670 |
671 | 
672 |
673 | 1. The debugger pauses at the line with `LoopCopyDataInit`
674 |
675 | Click `Continue` or press `F5`
676 |
677 | 
678 |
679 | 1. The debugger pauses next at the `main()` function.
680 |
681 | Click `Continue` or press `F5`
682 |
683 | 
684 |
685 | Our Blue Pill should now poll its internal temperature sensor every 10 seconds. It should also transmit the temperature data to the CoAP server hosted at thethings.io.
686 |
687 | [The Blue Pill log should look like this](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/visual.log). The log is explained below in the _"Quectel NB-IoT AT Commands"_ section.
688 |
689 | [微博视频](https://weibo.com/7285313566/I2MZOeP0F)
690 |
691 | [YouTube Video](https://youtu.be/PL4Yj3IS5ck)
692 |
693 | Upon clicking the URL `https://blue-pill-geolocate.appspot.com/?device=5cfca8c…` that’s shown in the Blue Pill log, we’ll see a web page that displays the temperature received by the server at thethings.io.
694 |
695 | The server has converted the raw temperature into degrees Celsius. We convert the temperature at the server to conserve RAM and ROM on Blue Pill.
696 |
697 | 
698 | _Display of sensor data received from our Blue Pill_
699 |
700 | ## OBSOLETE: Function 1: On Start
701 |
702 | 
703 |
704 | `On Start` marks the start of the program. Here we define some constants — values used by the program that won’t change as the program runs…
705 |
706 | 1. `SENSOR_DEVICE` is the name of the sensor that the program will poll (check periodically). We’ll be polling Blue Pill’s Internal Temperature Sensor, which is named `temp_stm32_0`
707 |
708 | 1. `SENSOR_POLL_TIME` is the time interval (in milliseconds) for polling the sensor. We’ll set this to 10 seconds (or 10,000 milliseconds)
709 |
710 | 1. `TEMP_SENSOR_KEY` is the name of the sensor data field that our program will send to the server. We’ll call it `t` to tell the server we’re sending a temperature value.
711 |
712 | 1. `TEMP_SENSOR_TYPE` is the type of sensor data that our program will send: Raw ambient temperature in whole numbers (integers from 0 to 4095), hence `SENSOR_TYPE_AMBIENT_TEMPERATURE_RAW`
713 |
714 | Why do we send the temperature in raw form instead of the usual decimal (floating-point) form like 28.9 degrees Celsius? That’s because Blue Pill has very limited RAM and ROM. Sending the raw temperature without conversion will save us from reserving RAM and ROM that would be needed for the floating-point conversion. We’ll let the server convert instead.
715 |
716 | By Rust convention, constants are named in uppercase. Hence we name the constants as `SENSOR_DEVICE` instead of sensor_device
717 |
718 | 
719 |
720 | Next we call the function `start_sensor_listener` to begin polling the temperature sensor every 10 seconds. More about this in the next section.
721 |
722 | 
723 |
724 | Finally we call `start_server_transport`, which is a system function defined in the `sensor_network` library. This function starts a background task to establish a connection to the NB-IoT network. For this tutorial, we’ll be transmitting sensor data over the NB-IoT network, which is available worldwide.
725 |
726 | It may take a few seconds to complete, but the function executes in the background so it won’t hold up other tasks, like polling the temperature sensor.
727 |
728 | Take note of the Rust convention… `sensor_network::start_server_transport` refers to the function `start_server_transport` that’s found inside the Rust Library `sensor_network`. Rust Libraries are also known as “Crates”.
729 |
730 | How was the `On Start` function created?
731 | By dragging and dropping the blocks from the Blocks Bar at the left of the Visual Program.
732 | That’s how we create a Visual Program… By arranging the blocks to compose a program!
733 |
734 | [微博视频](https://weibo.com/7285313566/I2MOamxS9)
735 |
736 | [YouTube Video](https://youtu.be/Qw1N-01PAy8)
737 |
738 | 
739 |
740 | ## OBSOLETE: Function 2: Start Sensor Listener
741 |
742 | 
743 |
744 | `To start_sensor_listener With ...` is the way that we define functions in the Visual Program. Here we define `start_sensor_listener` as a function that accepts 4 parameters (or inputs), whose values we have seen from the previous section…
745 |
746 | 1. `sensor_name`: Name of the sensor to be polled. Set to `SENSOR_DEVICE` (i.e. `temp_stm32_0`)
747 |
748 | 1. `sensor_key`: Name of the sensor data field to be sent to the server. Set to `TEMP_SENSOR_KEY` (i.e. `t`)
749 |
750 | 1. `sensor_type`: Type of sensor data that will be sent to the server. Set to `SENSOR_TYPE_AMBIENT_TEMPERATURE_RAW`
751 |
752 | 1. `poll_time`: Time interval (in milliseconds) for polling the sensor. Set to `SENSOR_POLL_TIME` (i.e. 10,000 milliseconds or 10 seconds)
753 |
754 | 
755 |
756 | Next we call the system function `set_poll_rate_ms`, defined in the `sensor` library. The `sensor` library comes from the Apache Mynewt operating system, which manages all sensors on Blue Pill.
757 |
758 | By calling the function `set_poll_rate_ms` with `sensor_name` set to `temp_stm32_0` and `poll_time` set to `10000` (milliseconds), we are asking the system to poll the temperature sensor every 10 seconds. And the system will happily fetch the temperature value on our behalf every 10 seconds.
759 |
760 | What shall we do with the temperature value? We’ll define a Listener Function to transmit the data. But first…
761 |
762 | 
763 |
764 | We call function `mgr_find_next_bydevname` (also from the `sensor` library) to fetch the sensor driver from the system and store it in the variable `sensor_driver`. By passing the `sensor_name` as `temp_stm32_0`, the function returns the driver responsible for managing the temperature sensor. The driver will be used for setting the Listener Function in a while.
765 |
766 | 
767 |
768 | Before that, we check the sensor driver was actually found. If we had misspelt the name of the sensor, the sensor driver would not be found and it would be set to `null`, a special Rust value that means “nothing”. Hence we check to ensure that `sensor_driver` is not `null`.
769 |
770 | 
771 |
772 | We create a sensor listener (stored as `listener`) by calling the system function `new_sensor_listener`, passing in the `sensor_key` (set to `t`) and the `sensor_type` (raw ambient temperature). func is the name of the Listener Function that will be called after reading the sensor data: `handle_sensor_data`. Which we’ll cover in the next section.
773 |
774 | 
775 |
776 | To register the Listener Function in the system, we call the system function `register_listener`, passing in the `sensor_driver` and the sensor listener that we have just created.
777 |
778 | After that, the operating system will automatically read the temperature sensor every 10 seconds and call our function `handle_sensor_data` with the temperature value.
779 |
780 | [微博视频](https://weibo.com/7285313566/I2MWZ1CnK)
781 |
782 | [YouTube Video](https://youtu.be/ytGa-7q6sqY)
783 |
784 | ## OBSOLETE: Function 3: Handle Sensor Data
785 |
786 | 
787 |
788 | How shall we handle the temperature data that has been read? `handle_sensor_data` passes the sensor data to another function `send_sensor_data` that transmits the sensor data to the server. More about `send_sensor_data` in a while.
789 |
790 | The function `handle_sensor_data` doesn’t seem to do much… why did we design the program this way? It’s meant for future expansion — when we need more complicated logic for handling sensor data, we’ll put the logic into `handle_sensor_data`
791 |
792 | `handle_sensor_data` could be extended to handle multiple sensors, aggregating the sensor data before transmitting. Or it could check for certain conditions and decide whether it should transmit the data. This program structure gives us the most room to expand for the future.
793 |
794 | ## OBSOLETE: Function 4: Send Sensor Data
795 |
796 | 
797 |
798 | The final function in our program, `send_sensor_data`, is called by `handle_sensor_data` to transmit sensor data. The parameter `sensor_data` contains the field name `t` and the sensor value, like `1715`. Remember that this is a raw temperature value. The server will convert the raw value to degrees Celsius later.
799 |
800 | 
801 |
802 | We call `get_device_id` from the `sensor_network` library to fetch the Device ID from the system. This is a long string of random letters and digits like `a8b2c7d8e9b2...` Each time we restart Blue Pill we’ll get a different Device ID. We’ll use this Device ID later to identify our Blue Pill uniquely and check whether the server has received the temperature sensor data from our Blue Pill.
803 |
804 | 
805 |
806 | Next we call `init_server_post` (also from `sensor_network` library) to prepare a sensor data message that will be sent to the server. Because Blue Pill has limited RAM, this function will ensure that only one task is allowed to compose messages at any time. The other tasks will have to wait for their turn.
807 |
808 | 
809 |
810 | `init_server_post` returns a true/false result (known as a boolean) that indicates whether the NB-IoT network connection has been established. This stored in the variable `network_ready`.
811 |
812 | Only when `network_ready` is true, which means that the device has connected to the NB-IoT network, then we proceed to compose a CoAP Message.
813 |
814 | 
815 |
816 | What’s a CoAP Message? It’s a standard format for transmitting sensor data over NB-IoT. Here we are transmitting two data values in the CoAP Message...
817 |
818 | 1. `device_id`: The randomly-generated Device ID that uniquely identifies our Blue Pill. This field shall be transmitted with the field name device
819 |
820 | 1. `sensor_data`: Contains the field name `t` and the sensor value, like `1715`
821 |
822 | 
823 |
824 | The CoAP Message is transmitted only when function `do_server_post` is called. Again this transmission takes place in a background task, so it won’t hold up our program from polling the sensor.
825 |
826 | Notice that `_payload` is named differently… it begins with an underscore `_`. By Rust convention, variables that are set but not read should be named with an underscore `_` as the first character. Because the Rust Compiler will warn us about unused variables.
827 |
828 | This effectively tells the Rust Compiler: _“Yes I’m setting the variable `_payload` and I’m not using the value… Please don’t warn me that I may have misspelt the name `_payload`"_
829 |
830 | 
831 |
832 | At the end of the function, we display a URL in the Blue Pill log that contains the Device ID. The URL looks like this: https://blue-pill-geolocate.appspot.com/?device=5cfca8c…
833 | We’ll click this URL to verify that the server has received our sensor data.
834 |
835 | ## OBSOLETE: Rust Source Files
836 |
837 | | | |
838 | |:- |:- |
839 | |  | The Rust source files are located in the `rust` folder…
`rust/app`: Rust application that polls the internal temperature sensor and transmits the sensor data over NB-IoT
If you’re using Visual Embedded Rust...
Overwrite the file `src/lib.rs` by your Visual Program source file
Delete `app_network.rs` and `app_sensor.rs` in the src folder.
Rebuild the application by clicking
`Terminal → Run Task → [2] Build bluepill_my_sensor`
`rust/visual`: Sample Visual Embedded Rust program
`rust/mynewt`: Rust Safe Wrappers for Mynewt OS and libraries
`rust/macros`: Rust Procedural Macros for generating Safe Wrappers, inferring types and other utility macros like `strn!()`
840 | |
841 |
842 | ## OBSOLETE: Typeless Rust
843 |
844 | To making coding easier for beginners, the extension generates [Typeless Rust code like this](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/rust/visual/src/lib.rs)...
845 |
846 | ```rust
847 | #[infer_type] // Infer the missing types
848 | fn start_sensor_listener(sensor_name: _, sensor_key: _, sensor_type: _, poll_time: _) ...
849 | // Call Mynewt API
850 | sensor::set_poll_rate_ms(sensor_name, poll_time) ? ;
851 | ```
852 |
853 | When the typeless code is compiled, the [`infer_type` Procedural Macro](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/rust/macros/src/infer_type.rs) infers the types by matching the variables against the Mynewt API...
854 |
855 | ```rust
856 | // Call Mynewt API
857 | sensor::set_poll_rate_ms(sensor_name, poll_time) ? ;
858 | // `sensor_name` inferred as type `&Strn`
859 | // `poll_time` inferred as type `u32`
860 | ```
861 |
862 | The macro then injects the inferred types into the typeless code...
863 |
864 | ```rust
865 | fn start_sensor_listener(sensor_name: &Strn, sensor_key: &'static Strn,
866 | sensor_type: sensor_type_t, poll_time: u32) ...
867 | ```
868 |
869 | The inferred types are stored in [`infer.json`](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/infer.json). The enables the `infer_type` macro to infer new types based on types already inferred for other functions...
870 |
871 | ```json
872 | "start_sensor_listener": [
873 | [ "sensor_name", "&Strn" ],
874 | [ "sensor_key", "&'static Strn" ],
875 | [ "sensor_type", "sensor_type_t" ],
876 | [ "poll_time", "u32" ]
877 | ],
878 | "send_sensor_data": [
879 | [ "sensor_data", "&SensorValue" ]
880 | ],
881 | "handle_sensor_data": [
882 | [ "sensor_data", "&SensorValue" ]
883 | ]
884 | ```
885 |
886 | This diagram illustrates the Type Inference…
887 |
888 | 
889 | _How the infer_type macro infers missing types_
890 |
891 | Here’s an animation (done with Visual Studio Code) that explains how the types were inferred by the `infer_type` macro. At top left are the types to be inferred. At bottom left are the known type signatures from the Mynewt API.
892 |
893 | The `infer_type` macro scans the Typeless Rust program recursively, hence we see the roving red highlight. When the macro finds a match with the Mynewt API, the code flashes green.
894 |
895 | Green ticks at the top left mean that we have successfully inferred the types.
896 |
897 | The recursive Rust code parsing was implemented with the excellent `syn` crate. The `quote` crate was used to emit the transformed Rust code.
898 |
899 | [微博视频](https://weibo.com/7285313566/I2N12aA4W)
900 |
901 | [YouTube Video](https://youtu.be/1SCLlwK5KwE)
902 |
903 | 
904 | _How the infer_type macro infers missing types, animated in Visual Studio Code with the Visual Embedded Rust Extension_
905 |
906 | More details in the article [_"Advanced Topics for Visual Embedded Rust Programming"_](https://medium.com/@ly.lee/advanced-topics-for-visual-embedded-rust-programming-ebf1627fe397?source=friends_link&sk=01f0ae0e1b82efa9fd6b8e5616c736af)
907 |
908 | ## OBSOLETE: Inside The Visual Embedded Rust Extension for Visual Studio Code
909 |
910 | The source code for the Visual Embedded Rust extension is located at github.com/lupyuen/visual-embedded-rust
911 |
912 | The extension is published in the [Visual Studio Marketplace here](https://marketplace.visualstudio.com/items?itemName=LeeLupYuen.visual-embedded-rust&ssr=false#overview)
913 |
914 | The extension wraps the web-based visual code editor from [Google Blockly](https://developers.google.com/blockly/guides/overview) into a [VSCode WebView](https://code.visualstudio.com/api/extension-guides/webview). Blockly uses XML to represent a visual program.
915 |
916 | The extension is activated when we [edit a Rust source file](https://github.com/lupyuen/visual-embedded-rust/blob/master/package.json#L41-L49) (`*.rs`). [Here’s a sample Rust source file containing a Visual Program](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/rust/visual/src/lib.rs)
917 |
918 | There are two parts of the file…
919 |
920 | 1. __Rust Source Code:__ Which is autogenerated by the Blockly Code Generator from the Blockly XML
921 |
922 | 1. __Blockly XML:__ The XML representation of the visual program. It’s located at the bottom of the source file, marked by `BEGIN BLOCKS … END BLOCKS`
923 |
924 | 
925 | _Logic Flow in the Visual Embedded Rust Extension_
926 |
927 | 1. Main logic for the VSCode Extension is in [extension.ts](https://github.com/lupyuen/visual-embedded-rust/blob/master/src/extension.ts)
928 |
929 | The extension contains two asset folders:
930 |
931 | [`resources`](https://github.com/lupyuen/visual-embedded-rust/tree/master/resources): Contains a [visual program template](https://github.com/lupyuen/visual-embedded-rust/blob/master/resources/template.rs) that will be used to populate empty Rust source files
932 |
933 | [`media`](https://github.com/lupyuen/visual-embedded-rust/tree/master/media): Contains the Blockly JavaScript code that will be embedded in the WebView to render the visual editor and generate Rust source code…
934 |
935 | [`media/blockly-mynewt-rust`](https://github.com/lupyuen/blockly-mynewt-rust) contains the Blockly JavaScript code with a custom Rust Code Generator
936 |
937 | [`media/closure-library`](https://github.com/google/closure-library) is the Google Closure Library needed by Blockly
938 |
939 | [`media/vscode`](https://github.com/lupyuen/visual-embedded-rust/tree/master/media/vscode) contains JavaScript code that enables VSCode Message Passing in the WebView to implement save/load functions and modal prompts
940 |
941 | 1. The extension creates a [WebView that embeds the HTML and JavaScript code](https://github.com/lupyuen/visual-embedded-rust/blob/master/src/extension.ts#L88-L144) from [Google Blockly](https://developers.google.com/blockly/guides/overview).
942 |
943 | [HTML code for the WebView is here](https://github.com/lupyuen/visual-embedded-rust/blob/master/src/web.ts)
944 |
945 | 1. The VSCode Extension and the WebView are running in [separate JavaScript sandboxes](https://code.visualstudio.com/api/extension-guides/webview#scripts-and-message-passing).
946 |
947 | Hence we’ll be using VSCode Message Passing to communicate between the VSCode Extension and WebView, as we shall soon see…
948 |
949 | 1. [When the WebView loads](https://github.com/lupyuen/visual-embedded-rust/blob/master/media/vscode/storage.js#L59-L71), it notifies the VSCode Extension to fetch the contents of the Rust source file.
950 |
951 | The VSCode Extension responds by [passing the contents of the active Rust source file to the WebView](https://github.com/lupyuen/visual-embedded-rust/blob/master/src/extension.ts#L168-L186) via Message Passing.
952 |
953 | The WebView [extracts the Blockly XML](https://github.com/lupyuen/visual-embedded-rust/blob/master/media/vscode/message.js#L40-L60) embedded in the file contents ([at the bottom](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/rust/visual/src/lib.rs#L159)). The WebView refreshes the Blockly workspace with the Blockly XML.
954 |
955 | If the active Rust source file is empty, the VSCode Extension [populates the file](https://github.com/lupyuen/visual-embedded-rust/blob/master/src/extension.ts#L155-L202) with a [template containing Blockly XML](https://github.com/lupyuen/visual-embedded-rust/blob/master/resources/template.rs)
956 |
957 | 1. When the [visual program is updated](https://github.com/lupyuen/visual-embedded-rust/blob/master/media/vscode/storage.js#L194-L207), the WebView sends the [updated Blockly XML and the generated Rust code](https://github.com/lupyuen/visual-embedded-rust/blob/master/media/vscode/message.js#L79-L89) (via [Message Passing](https://github.com/lupyuen/visual-embedded-rust/blob/master/media/vscode/storage.js#L187-L192)) to the VSCode Extension.
958 |
959 | The extension [updates the Rust document](https://github.com/lupyuen/visual-embedded-rust/blob/master/src/extension.ts#L203-L223) in VSCode with the Blockly XML and generated Rust Code.
960 |
961 | 1. The custom-built Rust Code Generator for Blockly is here…
962 |
963 | github.com/lupyuen/blockly-mynewt-rust/blob/master/generators/rust.js
964 |
965 | github.com/lupyuen/blockly-mynewt-rust/tree/master/generators/rust
966 |
967 | The Rust Code Generator for Blockly is [explained in this article](https://medium.com/@ly.lee/visual-programming-with-embedded-rust-yes-we-can-with-apache-mynewt-and-google-blockly-8b67ef7412d7)
968 |
969 | ## OBSOLETE: Building The Visual Embedded Rust Extension
970 |
971 | To build the extension, two repositories need to be cloned into the media folder: [`blockly-mynewt-rust`](https://github.com/lupyuen/blockly-mynewt-rust) and [`closure-library`](https://github.com/google/closure-library):
972 |
973 | ```bash
974 | cd media
975 | git clone https://github.com/lupyuen/blockly-mynewt-rust
976 | git clone https://github.com/google/closure-library
977 | ```
978 |
979 | ## OBSOLETE: References
980 |
981 | The following files may be useful for reference…
982 |
983 | - [Disassembly of the Rust Application build](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/libapp-demangle.S)
984 |
985 | - [Disassembly of the Rust Crates](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/rustlib-demangle.S)
986 |
987 | - [Disassembly of the entire firmware](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/my_sensor_app.elf.lst)
988 |
989 | - [Memory map of the firmware](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/rust-nbiot/logs/my_sensor_app.elf.map)
990 |
991 | [Read more about hosting Rust applications on Mynewt](https://medium.com/@ly.lee/hosting-embedded-rust-apps-on-apache-mynewt-with-stm32-blue-pill-c86b119fe5f?source=friends_link&sk=f58f4cf6c608fded4b354063e474a93b)
992 |
993 | ## OBSOLETE: Release Notes
994 |
995 | For changelog refer to...
996 |
997 | 1. [`github.com/lupyuen/visual-embedded-rust/commits/master`](https://github.com/lupyuen/visual-embedded-rust/commits/master)
998 |
999 | 1. [`github.com/lupyuen/blockly-mynewt-rust/commits/master`](https://github.com/lupyuen/blockly-mynewt-rust/commits/master)
1000 |
1001 | 1. [`github.com/lupyuen/stm32bluepill-mynewt-sensor/commits/rust-nbiot`](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/commits/rust-nbiot)
1002 |
--------------------------------------------------------------------------------