|
| 1 | +# Open Bot Brain |
| 2 | +The WARDuino VM has support for [specialised hardware](https://www.openbotbrain.org/) that can be used to control Lego Mindstorms components. |
| 3 | + |
| 4 | +## Prerequisites |
| 5 | +To use WARDuino on this hardware, the Zephyr platform should be used. To get started you will first need to install Zephyr by following the [official installation guide](https://docs.zephyrproject.org/latest/develop/getting_started/index.html). |
| 6 | + |
| 7 | +By default Zephyr will use the [STM32CubeProgrammer](https://www.st.com/en/development-tools/stm32cubeprog.html) to flash the board, which you will also need to install. |
| 8 | + |
| 9 | +## Cloning WARDuino |
| 10 | +To get started with WARDuino for Open Bot Brain you will first need to clone it using the following command: |
| 11 | +```bash |
| 12 | +git clone --recursive [email protected]:TOPLLab/WARDuino.git |
| 13 | +``` |
| 14 | + |
| 15 | +## Adding your program |
| 16 | +Before building WARDuino you will need to specify the program which the VM will execute when starting. To achieve this, you will need to compile your WebAssembly program and place the resulting executable in the `platforms/Zephyr` directory with the name `upload.wasm`. |
| 17 | + |
| 18 | +### Example |
| 19 | +The following program written in [AssemblyScript](https://www.assemblyscript.org/) will make one of the built-in LEDs on the board blink. |
| 20 | +```ts:line-numbers [AS] |
| 21 | +@external("env", "chip_delay") |
| 22 | +declare function delay(value: i32): void; |
| 23 | +@external("env", "chip_pin_mode") |
| 24 | +declare function pinMode(pin: i32, mode: i32): void; |
| 25 | +@external("env", "chip_digital_write") |
| 26 | +declare function digitalWrite(pin: i32, value: i32): void; |
| 27 | +
|
| 28 | +enum PinVoltage { |
| 29 | + LOW = 0, |
| 30 | + HIGH = 1, |
| 31 | +} |
| 32 | +
|
| 33 | +enum PinMode { |
| 34 | + INPUT = 0, |
| 35 | + OUTPUT = 2 |
| 36 | +} |
| 37 | +
|
| 38 | +enum Pin { |
| 39 | + powerSupply = 60, |
| 40 | + led1 = 45, |
| 41 | +} |
| 42 | +
|
| 43 | +function setup(): void { |
| 44 | + // Configure and enable power supply pin (active low). |
| 45 | + pinMode(Pin.powerSupply, PinMode.OUTPUT); |
| 46 | + delay(500); // Wait for 500ms to avoid current spike. |
| 47 | + digitalWrite(Pin.powerSupply, PinVoltage.LOW); |
| 48 | +
|
| 49 | + // Configure LED. |
| 50 | + pinMode(Pin.led1, PinMode.OUTPUT); |
| 51 | +} |
| 52 | +
|
| 53 | +export function main(): void { |
| 54 | + setup(); |
| 55 | +
|
| 56 | + while (true) { |
| 57 | + digitalWrite(Pin.led1, PinVoltage.HIGH); |
| 58 | + delay(500); |
| 59 | + digitalWrite(Pin.led1, PinVoltage.LOW); |
| 60 | + delay(500); |
| 61 | + } |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +This program can be compiled using the following command if you have the [AssemblyScript](https://www.assemblyscript.org/) compiler globally installed on your system: |
| 66 | +```bash |
| 67 | +asc blink.ts -o upload.wasm |
| 68 | +``` |
| 69 | + |
| 70 | +With the program compiled and placed in the `platforms/Zephyr` directory you should now have the following directory structure: |
| 71 | + |
| 72 | +```tree |
| 73 | +. |
| 74 | +├── app.overlay |
| 75 | +├── boards |
| 76 | +│ ├── ... |
| 77 | +├── CMakeLists.txt |
| 78 | +├── Kconfig |
| 79 | +├── main.cpp |
| 80 | +├── prj.conf |
| 81 | +└── upload.wasm // [!code ++] |
| 82 | +``` |
| 83 | + |
| 84 | +## Building |
| 85 | +To build WARDuino for Open Bot Brain, run the following commands starting from the root directory of the repository: |
| 86 | +```bash |
| 87 | +cd platforms/Zephyr |
| 88 | +source ~/zephyrproject/zephyr/zephyr-env.sh |
| 89 | +source ~/zephyrproject/.venv/bin/activate |
| 90 | +west build -b stm32l496g_disco |
| 91 | +``` |
| 92 | + |
| 93 | +## Flashing |
| 94 | +To flash WARDuino to the board, the following command can be used. By default this will use the [STM32CubeProgrammer](https://www.st.com/en/development-tools/stm32cubeprog.html). You can also use a different runner by using the `--runner` flag, more info can be found in the [Zephyr documentation](https://docs.zephyrproject.org/latest/boards/st/nucleo_l496zg/doc/index.html#flashing). |
| 95 | +```bash |
| 96 | +west flash |
| 97 | +``` |
| 98 | + |
| 99 | +Once flashed, you should start seeing one of the LEDs blink. |
| 100 | + |
| 101 | +## Board information |
| 102 | +The Open Bot Brain has various built-in LEDs and buttons, in the following table we list some of the pin numbers for use in your programs. |
| 103 | +|Description|Pin number| |
| 104 | +|:--|---:| |
| 105 | +|Power supply|60| |
| 106 | +|Motor AB Driver sleep|46| |
| 107 | +|Motor CD Driver sleep|78| |
| 108 | +|Led 1|45| |
| 109 | +|Led 2|56| |
| 110 | +|Led 3|39| |
| 111 | +|Button SW1 |21| |
| 112 | +|Button SW5 |20| |
| 113 | +|Button SW2 |10| |
| 114 | + |
| 115 | +## Lego Mindstorms Primitives |
| 116 | +Aside from the built-in LEDs and buttons, Open Bot Brain also has 4 input and 4 output ports that can be used to control Lego Mindstorms components. |
| 117 | + |
| 118 | +### Outputs |
| 119 | +In terms of outputs we support both the NXT and EV3 (medium and large) motors. These can be controlled using the following primitives: |
| 120 | + |
| 121 | +| Primitive | Description | |
| 122 | +|----------------------------|-------------------------------------------| |
| 123 | +| `drive_motor(port, speed)` | Makes the motor spin at a constant speed. | |
| 124 | +| `drive_motor_ms(port, speed, time)` | Makes the motor spin for x ms. | |
| 125 | +| `drive_motor_degrees(port, speed, angle)` | Makes the motor rotate x degrees. | |
| 126 | +| `stop_motor(port)` | Actively slows down the motor. | |
| 127 | + |
| 128 | +### Inputs |
| 129 | +In terms of inputs we supports UART based sensors such as the EV3 Color and Gyro sensors. These can be controlled using the following primitives: |
| 130 | + |
| 131 | +| Primitive | Description | |
| 132 | +|----------------------------|-------------------------------------------| |
| 133 | +| `setup_uart_sensor(port, mode)` | Configure the UART sensor and set its mode. | |
| 134 | +| `read_uart_sensor(port)` | Read the latest value from the UART sensor. | |
| 135 | + |
| 136 | + |
| 137 | +### Example usage |
| 138 | +To illustrate how some of these primitives could be used we show a simple line follower example program. This robot has two wheels and drives left or right depending on if it sees a dark or a light color with its color sensor. By doing so the robot will follow the edge of a dark line on the floor. |
| 139 | + |
| 140 | + |
| 141 | + |
| 142 | +Since a lot of this code is similar to the blink example, we highlighted the main changes. In particular we now import three extra primitives and disable the sleep mode on the motor drivers. Using this setup a simple line follower can be written in just a few lines of code as shown in the `main` function. |
| 143 | + |
| 144 | +```ts:line-numbers [AS] |
| 145 | +@external("env", "chip_delay") |
| 146 | +declare function delay(value: i32): void; |
| 147 | +@external("env", "chip_pin_mode") |
| 148 | +declare function pinMode(pin: i32, mode: i32): void; |
| 149 | +@external("env", "chip_digital_write") |
| 150 | +declare function digitalWrite(pin: i32, value: i32): void; |
| 151 | +@external("env", "drive_motor") // [!code focus:6] |
| 152 | +declare function driveMotor(port: i32, speed: i32): void; |
| 153 | +@external("env", "setup_uart_sensor") |
| 154 | +declare function setupUARTSensor(port: i32, mode: i32): void; |
| 155 | +@external("env", "read_uart_sensor") |
| 156 | +declare function readUARTSensor(port: i32): i32; |
| 157 | +
|
| 158 | +enum PinVoltage { |
| 159 | + LOW = 0, |
| 160 | + HIGH = 1, |
| 161 | +} |
| 162 | +
|
| 163 | +enum PinMode { |
| 164 | + INPUT = 0, |
| 165 | + OUTPUT = 2 |
| 166 | +} |
| 167 | +
|
| 168 | +enum Pin { |
| 169 | + powerSupply = 60, |
| 170 | + motorABDriverSleep = 46, |
| 171 | + motorCDDriverSleep = 78, |
| 172 | +} |
| 173 | +
|
| 174 | +enum InputPort { |
| 175 | + portA = 0, |
| 176 | + portB, |
| 177 | + portC, |
| 178 | + portD |
| 179 | +} |
| 180 | +
|
| 181 | +enum OutputPort { |
| 182 | + port1 = 0, |
| 183 | + port2, |
| 184 | + port3, |
| 185 | + port4 |
| 186 | +} |
| 187 | +
|
| 188 | +enum ColorSensorMode { |
| 189 | + reflectivity = 0, |
| 190 | + ambient, |
| 191 | + colour |
| 192 | +} |
| 193 | +
|
| 194 | +function setupBoard(): void { |
| 195 | + // Configure and enable power supply pin (active low). |
| 196 | + pinMode(Pin.powerSupply, PinMode.OUTPUT); |
| 197 | + delay(500); // Wait for 500ms to avoid current spike. |
| 198 | + digitalWrite(Pin.powerSupply, PinVoltage.LOW); |
| 199 | +
|
| 200 | + // Disable sleep on motor AB driver. // [!code focus:7] |
| 201 | + pinMode(Pin.motorABDriverSleep, PinMode.OUTPUT) |
| 202 | + digitalWrite(Pin.motorABDriverSleep, PinVoltage.HIGH) |
| 203 | +
|
| 204 | + // Disable sleep on motor CD driver. |
| 205 | + pinMode(Pin.motorCDDriverSleep, PinMode.OUTPUT) |
| 206 | + digitalWrite(Pin.motorCDDriverSleep, PinVoltage.HIGH) |
| 207 | +} |
| 208 | +
|
| 209 | +const threshold = 5; |
| 210 | +
|
| 211 | +export function main(): void { // [!code focus:19] |
| 212 | + setupBoard(); |
| 213 | +
|
| 214 | + // Setup color sensor. |
| 215 | + setupUARTSensor(InputPort.portA, ColorSensorMode.reflectivity); |
| 216 | +
|
| 217 | + while (true) { |
| 218 | + if (readUARTSensor(InputPort.portA) < threshold) { |
| 219 | + // Drive right. |
| 220 | + driveMotor(OutputPort.port1, 5000); |
| 221 | + driveMotor(OutputPort.port2, 0); |
| 222 | + } |
| 223 | + else { |
| 224 | + // Drive left. |
| 225 | + driveMotor(OutputPort.port1, 0); |
| 226 | + driveMotor(OutputPort.port2, 5000); |
| 227 | + } |
| 228 | + } |
| 229 | +} |
| 230 | +``` |
0 commit comments