2 Star 4 Fork 1

Lee Lup Yuen 李立源 / visual-embedded-rust

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
README.old.md 48.24 KB
一键复制 编辑 原始数据 按行查看 历史
Lee Lup Yuen 李立源 提交于 2020-02-18 16:40 . Updated doc

visual-embedded-rust

  • Create and edit Embedded Rust programs visually by dragging and dropping blocks

  • Generates Embedded Rust firmware code for PineTime Smart Watch hosted on Apache Mynewt realtime operating system

Connect PineTime to Raspberry Pi

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!

2️⃣ Just above the battery we see 4 shiny rings. This is the 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.

🛈 What is “flash memory” / “flashing” / “firmware”? Read this

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.

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.

4️⃣ Connect the SWD Port and the 5V Pad (optional) to the Raspberry Pi with Solid-Core Wire (22 AWG) and Female-To-Female Jumper Cables

PineTime Raspberry Pi Wire Colour
SWDIO Header Pin 19 (MOSI) Yellow
SWDCLK Header Pin 23 (SCLK) Blue
3.3V 3.3V Red
GND GND Black
5V 5V Green (Optional)

5️⃣ We may use Raspberry Pi Zero, 1, 2, 3 or 4.

6️⃣ The PineTime touchscreen needs to be accessible during development, so I mounted PineTime on a $2 clear box cover from Daiso with Blu Tack and sticky tape.

Remove PineTime Flash Protection

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.

🛈 What is “flash protection”? Read this

1️⃣ Power on the Raspberry Pi. Open a command prompt and enter the following…

sudo raspi-config

Select Interfacing Options → SPI → Yes

Select Finish

At the command prompt, enter the following…

#  Remove folders ~/pinetime-rust-mynewt and ~/openocd-spi (if they exist)
rm -rf ~/pinetime-rust-mynewt
rm -rf ~/openocd-spi

# Download and extract "pinetime-rust-mynewt" folder containing our prebuilt firmware, source files and flashing scripts
sudo apt install -y wget p7zip-full
cd ~
wget https://github.com/lupyuen/pinetime-rust-mynewt/releases/download/v3.0.3/pinetime-rust-mynewt.7z
7z x pinetime-rust-mynewt.7z
rm pinetime-rust-mynewt.7z

# Install build tools for PineTime: VSCode, Rust, gcc, gdb, openocd-spi, newt
cd ~/pinetime-rust-mynewt
scripts/install-pi.sh

2️⃣ At the Welcome to Rust! prompt, press Enter to select the default option:

1) Proceed with installation (default)

If you see this error…

Cloning into 'openocd-spi/jimtcl'...
fatal: unable to access 'http://repo.or.cz/r/jimtcl.git/': Recv failure: Connection reset by peer
fatal: clone of 'http://repo.or.cz/r/jimtcl.git' into submodule path '/private/tmp/aa/openocd-spi/jimtcl' failed

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. Then copy the executable openocd-spi/src/openocd to pinetime-rust-mynewt/openocd/bin/openocd

3️⃣ When the installation has completed, enter the following at the command prompt…

# Remove flash protection from PineTime and erase demo firmware
cd ~/pinetime-rust-mynewt
scripts/nrf52-pi/flash-unprotect.sh

4️⃣ We should see Shut Down And Power Off Your Raspberry Pi

If you see Clock Speed and nothing else after that…

Info : BCM2835 SPI SWD driver
Info : SWD only mode enabled
Info : clock speed 31200 kHz

Then the connection to the SWD Port is probably loose, check the pins.

Also enter sudo raspi-config and confirm that the SPI port has been enabled.

If you see this instead…

openocd/bin/openocd: cannot execute binary file: Exec format error

Then install-pi.sh probably didn’t run correctly. To fix this, copy the openocd executable like this…

cp $HOME/openocd-spi/src/openocd $HOME/pinetime-rust-mynewt/openocd/bin/openocd

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…

# Remove flash protection from PineTime and erase demo firmware
cd ~/pinetime-rust-mynewt
scripts/nrf52-pi/flash-unprotect.sh

6️⃣ We should see Flash Is Already Unprotected

PineTime’s demo firmware has been erased and the flash protection has been removed.

🛈 What is OpenOCD? Why Raspberry Pi and not ROCK64 or Nvidia Jetson Nano? Read this

Edit The Visual Rust Application

We shall be using VSCode with the Visual Embedded Rust Extension to edit our Visual Rust application graphically.

🛈 What is VSCode? Is it related to Visual Studio? How is Microsoft involved? Read this

1️⃣ Launch VSCode by clicking the Raspberry Pi Menu (top left corner) → Programming → Code OSS Headmelted

In VSCode, click File → Open Folder

Under Home, select the folder pinetime-rust-mynewt and click OK

When prompted to open the workspace, click Open Workspace

When prompted to install Extension Recommendations, click Install All

Ignore the message Unable To Watch For File Changes. Close the message when it appears.

2️⃣ Install the Visual Embedded Rust Extension...

Click View → Extensions

Search for Visual Embedded Rust

Install the extension

3️⃣ Enable the Visual Rust application...

Browse to rust/app/Cargo.toml

Modify the file such that visual_app is uncommented and the other options are commented out...

default =  [          # Select the conditional compiled features
    # "display_app",  # Disable graphics display app
    # "ui_app",       # Disable druid UI app
    "visual_app",     # Enable Visual Rust app
    # "use_float",    # Disable floating-point for GPS geolocation
]

4️⃣ Edit the Visual Rust application...

Browse to rust/app/src/visual.rs

Click Visual Editor at top right

Click Visual Editor

Use the Visual Editor to edit the Visual Rust application

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.

Build And Flash The Visual Rust Application

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. Mynewt OS contains two components that we shall flash to PineTime…

Mynewt Bootloader: This is the C code that’s run whenever we power on PineTime. The Bootloader executes the Mynewt Application upon startup.

Mynewt Application: Contains a Rust application that controls the PineTime functions, and low-level system functions written in C.

The Bootloader and Application firmware image files may be found at these locations…

Mynewt Component Flash Memory Address Location of Firmware Image
Bootloader 0x0 ~/pinetime-rust-mynewt/bin/targets/nrf52_boot/app/apps/boot_stub/boot_stub.elf.bin
Application 0x8000 ~/pinetime-rust-mynewt/bin/targets/nrf52_my_sensor/app/apps/my_sensor_app/my_sensor_app.img

From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/hw/bsp/nrf52/bsp.yml

🛈 What is a Bootloader? Read this

1️⃣ At the lower left corner, there is a panel Task Runner. Click the panel to display the build and flash tasks.

2️⃣ In the Task Runner, click [1] Build Bootloader

When the Terminal Panel appears, right-click the Terminal tab, select Move Panel Right

After the building the Bootloader, we should see Done

Ignore the message There Are Task Errors

3️⃣ In the Task Runner, click [2] Build Application

After the building the Application, we should see Done

If you see the message Undefined Reference To Main, click [2] Build Application again and it should succeed.

4️⃣ In the Task Runner, click [3] Image Application

After the creating the Firmware Image, we should see Done

5️⃣ In the Task Runner, click [4] Flash Bootloader

After flashing the Bootloader Firmware to PineTime, we should see Done

Flashing Bootloader...
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000
Enabled ARM Semihosting to show debug output
** Programming Started **
Info : nRF52832-QFAA(build code: E1) 512kB Flash, 64kB RAM
Warn : Adding extra erase range, 0x00000b78 .. 0x00000fff
** Programming Finished **
** Verify Started **
** Verified OK **

Restarting...
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000, semihosting

**** Done!

From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-bootloader-pi.log

The Bootloader only needs to be flashed once.

6️⃣ In the Task Runner, click [5] Flash Application

After the flashing the Application Firmware to PineTime, we should see Done! Press Ctrl-C To Exit

Flashing Application...
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000
Enabled ARM Semihosting to show debug output
** Programming Started **
Info : nRF52832-QFAA(build code: E1) 512kB Flash, 64kB RAM
Warn : Adding extra erase range, 0x0003e820 .. 0x0003efff
** Programming Finished **
** Verify Started **
** Verified OK **

Restarting...
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x000000d8 msp: 0x20010000, semihosting
Enabled ARM Semihosting to show debug output

**** Done! Press Ctrl-C to exit...

From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-application-pi.log

7️⃣ Our Visual Rust application starts running on PineTime

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.

TODO: Debug The Visual Rust Application

TODO: Flash Bootloader and Application to PineTime

1️⃣ To flash Mynewt Bootloader to PineTime, enter the following at the command prompt…

# Flash Mynewt Bootloader to PineTime
cd ~/pinetime-rust-mynewt
scripts/nrf52-pi/flash-boot.sh

2️⃣ We should see Done

3️⃣ To flash Mynewt Application to PineTime, enter the following at the command prompt…

# Flash Rust+Mynewt Application to PineTime
cd ~/pinetime-rust-mynewt
scripts/nrf52-pi/flash-app.sh

4️⃣

5️⃣ The new PineTime firmware runs after the flashing has been completed. Here are the debugging messages produced by our Rust application…

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
TMP create temp_stub_0
NET hwid 4a f8 cf 95 6a be c1 f6 89 ba 12 1a 
NET standalone node 
Rust test display

From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/logs/load-application-pi.log

6️⃣ And we should see some text and graphics on the PineTime screen

7️⃣ Press Ctrl-C to stop the display of debugging messages.

We have flashed a simple Rust application located at pinetime-rust-mynewt/rust/app/src/display.rs that renders some graphics and text to the PineTime display

Here’s a good introduction to Rust programming and here’s a good overview of Rust

How do we modify this Rust application and rebuild the firmware? We have 3 options:

[Option 1] Build the firmware on a Windows computer, and copy to Pi for flashing

[Option 2] Build the firmware on a macOS computer, and copy to Pi for flashing

[Option 3] Build the firmware on a (powerful) Raspberry Pi (or PineBook Pro) and flash directly

TODO

Pi Version: https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v3.0.3

macOS Version: https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v3.0.2

Windows Version: https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v3.0.1

  1. Install rustup with support for nightly target thumbv7em-none-eabihf.

    Follow the instructions at https://rustup.rs/

    Then execute...

    rustup default nightly
    rustup update
    rustup target add thumbv7em-none-eabihf
  2. Install Arm toolchain gcc-arm-none-eabi and the newt build tool for Mynewt. Refer to this script...

    scripts/install-pi.sh

  3. Clone this repository...

    git clone --recursive https://github.com/lupyuen/pinetime-rust-mynewt
  4. repos folder should contain the Mynewt source code. If your repos folder is empty, install the Mynewt source code with the newt install command:

    cd pinetime-rust-mynewt
    newt install

    Ignore the error Error: Error updating "mcuboot"

  5. Build the bootloader...

    cd pinetime-rust-mynewt
    scripts/nrf52/build-boot.sh
  6. Build the application...

    scripts/build-app.sh

    If you see the error Undefined main, run scripts/build-app.sh again. It should fix the error.

  7. Create the application firmware image...

    scripts/nrf52/image-app.sh
  8. Flash the bootloader...

    scripts/nrf52-pi/flash-boot.sh
  9. Flash the application and run it...

    scripts/nrf52-pi/flash-app.sh
  10. You may need to edit the scripts to set the right path of OpenOCD.

    Also for Windows, the ST-Link interface for OpenOCD is stlink-v2.cfg instead of stlink.cfg.

  11. Check this article in case of problems...

    Build and Flash Rust+Mynewt Firmware for PineTime Smart Watch

Documentataion for Previous Version

The documentation below is being updated

OBSOLETE: Features

Watch the demo...

微博视频

YouTube Video

Read the articles...

  1. "Visual Embedded Rust Programming with Visual Studio Code"

  2. "Advanced Topics for Visual Embedded Rust Programming"

  3. "Rust Rocks NB-IoT! STM32 Blue Pill with Quectel BC95-G on Apache Mynewt"

  4. "Visual Programming with Embedded Rust? Yes we can with Apache Mynewt and Google Blockly!"

OBSOLETE: Document Contents

  1. Usage

  2. Build The Firmware

  3. Connect The Hardware

  4. Flash The Firmware To Blue Pill

  5. Run The Program

  6. Function 1: On Start

  7. Function 2: Start Sensor Listener

  8. Function 3: Handle Sensor Data

  9. Function 4: Send Sensor Data

  10. Rust Source Files

  11. Program Settings

  12. CoAP: Constrained Application Protocol

  13. Quectel NB-IoT AT Commands

  14. Configuring the CoAP Server at thethings.io

  15. Typeless Rust

  16. How Small Is Rust?

  17. Why Blue Pill? Power vs Price Compromise

  18. Why Apache Mynewt? Evolution of Rust on Bare Metal

  19. How Safe Is Rust? Safe Wrappers for Mynewt

  20. Inside The Visual Embedded Rust Extension for Visual Studio Code

  21. Building The Visual Embedded Rust Extension

  22. References

  23. Release Notes

OBSOLETE: Usage

  1. In Visual Studio Code, Click File → Open to open any folder

    Click File → Open

  2. In the Explorer → (Folder Name) pane at top left, create a new Rust source file, like lib.rs

    Create a new Rust source file

  3. Edit the Rust source file. Click Visual Editor at top right

    Click Visual Editor

  4. When prompted to populate the visual program into the Rust source file, click OK

    Click OK

  5. 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.

Sample Rust source file containing generated Rust code and XML blocks

Visual Embedded Rust editor with generated Rust code

OBSOLETE: Build The Firmware

To compile the generated Rust program into Blue Pill firmware...

  1. Click here to install Build Tools For Visual Studio 2019:
    https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019

  2. Click the Individual Components tab

    Select the following components:
    Windows 10 SDK (10.0.18362.0)
    C++ CMake Tools for Windows
    (This should be automatically selected) MSVC v142 — VS 2019 C++ x64/x86 Build Tools

    Components for Build Tools For Visual Studio 2019

  3. Install rustup according to the instructions here:
    https://rustup.rs

    Click the link provided to download rustup‑init.exe
    Launch the downloaded file rustup‑init.exe

    If you see the message Windows Defender SmartScreen prevented an unrecognised app from starting
    Click More Info
    Click Run Anyway

    At the Welcome to Rust! prompt, press Enter to select the default option:
    1) Proceed with installation (default)

  4. Open the Windows Command Prompt. Enter into the command prompt:

    rustup default nightly
    rustup update
    rustup target add thumbv7m-none-eabi
    rustc -V

    The reported version of rustc should be 1.38.0 or later:
    rustc 1.38.0-nightly (435236b88 2019–08–01)

  5. Download the stm32bluepill-mynewt-sensor.7z file attached below…
    https://github.com/lupyuen/stm32bluepill-mynewt-sensor/releases/tag/v7.0.3

    Expand the .7z file with 7zip…
    https://www.7-zip.org/download.html

  6. Install Arm Cross-Compiler and Linker for Windows from Arm Developer Website…
    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

    Select this option at the last install step:
    Add path to environment variable

  7. Download the ST-Link USB driver from ST-Link Driver Website (email registration required)…
    https://www.st.com/en/development-tools/stsw-link009.html

    Click Get Software
    Unzip the downloaded file. Double-click the driver installer:
    dpinst_amd64.exe

  8. Launch Visual Studio Code
    Install the extension “Cortex-Debug”…
    https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug

  9. Click File → Open Folder

    Select the downloaded folder stm32bluepill-mynewt-sensor

    When prompted to open the workspace, click Open Workspace

    Open Workspace

  10. Copy your Visual Program source file to stm32bluepill-mynewt-sensor/rust/app/src/lib.rs. Overwrite the existing file.

  11. Delete the files app_network.rs and app_sensor.rs in that folder

  12. If you have a Quectel NB-IoT module…

    Open the following file and configure the program settings:
    targets/bluepill_my_sensor/syscfg.yml
    Change the NB-IoT band setting NBIOT_BAND. Check with your NB-IoT operator for the band to use.

  13. Click Terminal → Run Task → [1] Build bluepill_boot

    This builds the bootloader, which starts the Apache Mynewt operating system upon startup. If it shows errors, compare with this build log.

  14. Click Terminal → Run Task → [2] Build bluepill_my_sensor

    This builds the firmware containing our Rust program. Compare with this build log.

    When our Rust program has been successfully compiled as Blue Pill ROM firmware, we should see this…

    Build Firmware

  15. Click Terminal → Run Task → [3] Image bluepill_my_sensor

    This creates the Blue Pill flash image from the firmware. Compare with this image log

    If any source files or configuration files are changed, rebuild the application by clicking
    Terminal → Run Task → [2] Build bluepill_my_sensor

OBSOLETE: Connect The Hardware


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 for stm32f103c8t6 development board

[2] ST-Link V2 USB Adapter: Under $2, search AliExpress 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)

I ordered mine from Taobao. The manual in Chinese is here.

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 for sponsoring the NB-IoT SIM that I used for this tutorial!

|

Hardware

Connect Blue Pill to Quectel BC95-G and ST-Link as follows…

Blue Pill Quectel BC95-G ST-Link V2 Wire Colour
PA2 (UART2 TX2) RXD (Pin 4) Green
PA3 (UART2 RX2) TXD (Pin 3) Blue
GND GND (Pin 1) Black
VCC (Pin 2) 5.0V (Pin 10) Yellow
3V3 3.3V (Pin 8) Red
DIO SWDIO (Pin 4) Orange
DCLK SWDCLK (Pin 2) Brown
GND GND (Pin 6) Black

Both yellow jumpers on Blue Pill should be set to the 0 position, as shown in the above photo.

SIM partially exposed to show the unusual orientation
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)

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

|

OBSOLETE: Flash The Firmware To Blue Pill

Blue Pill and ST-Link connected to USB port

  1. Check that the Blue Pill is connected to ST-Link…
    And that the ST-Link is connected to your computer’s USB port.
    Now let’s head back to Visual Studio Code…

  2. Click Terminal → Run Task → [4] Load bluepill_boot

    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.

  3. Click Terminal → Run Task → [5] Load bluepill_my_sensor

    This flashes the firmware (containing our Visual Program) to Blue Pill. If it shows errors, compare with this flash log.

OBSOLETE: Run The Program

  1. Click Debug → Start Debugging

  2. Click View → Output

    Select Adapter Output to see the Blue Pill log

    Select Adapter Output

  3. The debugger pauses at the line with LoopCopyDataInit

    Click Continue or press F5

    LoopCopyDataInit

  4. The debugger pauses next at the main() function.

    Click Continue or press F5

    main() function

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.

The Blue Pill log should look like this. The log is explained below in the "Quectel NB-IoT AT Commands" section.

微博视频

YouTube Video

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.

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.

Display of sensor data received from our Blue Pill
Display of sensor data received from our Blue Pill

OBSOLETE: Function 1: On Start

On Start

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…

  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

  2. SENSOR_POLL_TIME is the time interval (in milliseconds) for polling the sensor. We’ll set this to 10 seconds (or 10,000 milliseconds)

  3. 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.

  4. 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

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.

By Rust convention, constants are named in uppercase. Hence we name the constants as SENSOR_DEVICE instead of sensor_device

Next we call the function start_sensor_listener to begin polling the temperature sensor every 10 seconds. More about this in the next section.

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.

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.

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”.

How was the On Start function created? By dragging and dropping the blocks from the Blocks Bar at the left of the Visual Program. That’s how we create a Visual Program… By arranging the blocks to compose a program!

微博视频

YouTube Video

Visual Embedded Rust

OBSOLETE: Function 2: Start Sensor Listener

Start Sensor Listener

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…

  1. sensor_name: Name of the sensor to be polled. Set to SENSOR_DEVICE (i.e. temp_stm32_0)

  2. sensor_key: Name of the sensor data field to be sent to the server. Set to TEMP_SENSOR_KEY (i.e. t)

  3. sensor_type: Type of sensor data that will be sent to the server. Set to SENSOR_TYPE_AMBIENT_TEMPERATURE_RAW

  4. poll_time: Time interval (in milliseconds) for polling the sensor. Set to SENSOR_POLL_TIME (i.e. 10,000 milliseconds or 10 seconds)

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.

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.

What shall we do with the temperature value? We’ll define a Listener Function to transmit the data. But first…

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.

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.

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.

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.

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.

微博视频

YouTube Video

OBSOLETE: Function 3: Handle Sensor Data

Handle Sensor Data

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.

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

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.

OBSOLETE: Function 4: Send Sensor Data

Send Sensor Data

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.

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.

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.

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.

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.

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...

  1. device_id: The randomly-generated Device ID that uniquely identifies our Blue Pill. This field shall be transmitted with the field name device

  2. sensor_data: Contains the field name t and the sensor value, like 1715

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.

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.

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"

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… We’ll click this URL to verify that the server has received our sensor data.

OBSOLETE: Rust Source Files

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!()

|

OBSOLETE: Typeless Rust

To making coding easier for beginners, the extension generates Typeless Rust code like this...

#[infer_type]  //  Infer the missing types
fn start_sensor_listener(sensor_name: _, sensor_key: _, sensor_type: _, poll_time: _) ...
    //  Call Mynewt API
    sensor::set_poll_rate_ms(sensor_name, poll_time) ? ;

When the typeless code is compiled, the infer_type Procedural Macro infers the types by matching the variables against the Mynewt API...

//  Call Mynewt API
sensor::set_poll_rate_ms(sensor_name, poll_time) ? ;  
//  `sensor_name` inferred as type `&Strn`
//  `poll_time`   inferred as type `u32`

The macro then injects the inferred types into the typeless code...

fn start_sensor_listener(sensor_name: &Strn, sensor_key: &'static Strn,
                         sensor_type: sensor_type_t, poll_time: u32) ...

The inferred types are stored in infer.json. The enables the infer_type macro to infer new types based on types already inferred for other functions...

    "start_sensor_listener": [
        [ "sensor_name", "&Strn" ],
        [ "sensor_key",  "&'static Strn" ],
        [ "sensor_type", "sensor_type_t" ],
        [ "poll_time",   "u32" ]
    ],
    "send_sensor_data": [
        [ "sensor_data", "&SensorValue" ]
    ],
    "handle_sensor_data": [
        [ "sensor_data", "&SensorValue" ]
    ]

This diagram illustrates the Type Inference…

How the infer_type macro infers missing types
How the infer_type macro infers missing types

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.

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.

Green ticks at the top left mean that we have successfully inferred the types.

The recursive Rust code parsing was implemented with the excellent syn crate. The quote crate was used to emit the transformed Rust code.

微博视频

YouTube Video

How the infer_type macro infers missing types, animated in Visual Studio Code with the Visual Embedded Rust Extension
How the infer_type macro infers missing types, animated in Visual Studio Code with the Visual Embedded Rust Extension

More details in the article "Advanced Topics for Visual Embedded Rust Programming"

OBSOLETE: Inside The Visual Embedded Rust Extension for Visual Studio Code

The source code for the Visual Embedded Rust extension is located at github.com/lupyuen/visual-embedded-rust

The extension is published in the Visual Studio Marketplace here

The extension wraps the web-based visual code editor from Google Blockly into a VSCode WebView. Blockly uses XML to represent a visual program.

The extension is activated when we edit a Rust source file (*.rs). Here’s a sample Rust source file containing a Visual Program

There are two parts of the file…

  1. Rust Source Code: Which is autogenerated by the Blockly Code Generator from the Blockly XML

  2. 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

Logic Flow in the Visual Embedded Rust Extension
Logic Flow in the Visual Embedded Rust Extension

  1. Main logic for the VSCode Extension is in extension.ts

    The extension contains two asset folders:

    resources: Contains a visual program template that will be used to populate empty Rust source files

    media: Contains the Blockly JavaScript code that will be embedded in the WebView to render the visual editor and generate Rust source code…

    media/blockly-mynewt-rust contains the Blockly JavaScript code with a custom Rust Code Generator

    media/closure-library is the Google Closure Library needed by Blockly

    media/vscode contains JavaScript code that enables VSCode Message Passing in the WebView to implement save/load functions and modal prompts

  2. The extension creates a WebView that embeds the HTML and JavaScript code from Google Blockly.

    HTML code for the WebView is here

  3. The VSCode Extension and the WebView are running in separate JavaScript sandboxes.

    Hence we’ll be using VSCode Message Passing to communicate between the VSCode Extension and WebView, as we shall soon see…

  4. When the WebView loads, it notifies the VSCode Extension to fetch the contents of the Rust source file.

    The VSCode Extension responds by passing the contents of the active Rust source file to the WebView via Message Passing.

    The WebView extracts the Blockly XML embedded in the file contents (at the bottom). The WebView refreshes the Blockly workspace with the Blockly XML.

    If the active Rust source file is empty, the VSCode Extension populates the file with a template containing Blockly XML

  5. When the visual program is updated, the WebView sends the updated Blockly XML and the generated Rust code (via Message Passing) to the VSCode Extension.

    The extension updates the Rust document in VSCode with the Blockly XML and generated Rust Code.

  6. The custom-built Rust Code Generator for Blockly is here…

    github.com/lupyuen/blockly-mynewt-rust/blob/master/generators/rust.js

    github.com/lupyuen/blockly-mynewt-rust/tree/master/generators/rust

    The Rust Code Generator for Blockly is explained in this article

OBSOLETE: Building The Visual Embedded Rust Extension

To build the extension, two repositories need to be cloned into the media folder: blockly-mynewt-rust and closure-library:

cd media
git clone https://github.com/lupyuen/blockly-mynewt-rust
git clone https://github.com/google/closure-library

OBSOLETE: References

The following files may be useful for reference…

Read more about hosting Rust applications on Mynewt

OBSOLETE: Release Notes

For changelog refer to...

  1. github.com/lupyuen/visual-embedded-rust/commits/master

  2. github.com/lupyuen/blockly-mynewt-rust/commits/master

  3. github.com/lupyuen/stm32bluepill-mynewt-sensor/commits/rust-nbiot

TypeScript
1
https://gitee.com/lupyuen/visual-embedded-rust.git
git@gitee.com:lupyuen/visual-embedded-rust.git
lupyuen
visual-embedded-rust
visual-embedded-rust
master

搜索帮助