This is a real-time command language for low level I/O micros of mobile robots, and first with the UKMARSBOT Robot.
The commands are terse but intended to be entered either by human via a terminal or via machine, e.g. a Pi Zero. It's intention is to allow real-time command loops of low level operations, or high-level control for less experienced users.
NOTE: Originally this project was intended as a wall follower for the UKMARSBOT robot, but it only got as far as a hardware test command line via a simple command line interpreter before it was repurposed.
- Github project: https://github.com/robzed/ukmarsey
- Hardware Designed by UK Micromouse and Robotics Society http://ukmars.org
- Hardware details: https://github.com/ukmars/ukmarsbot
- Examples code sketches: https://github.com/ukmars/ukmarsbot-examples
NOTE: This project is related to the Pi Zero for UKMARSbot project https://github.com/robzed/pizero_for_ukmarsbot
This project is licensed under the MIT license.
Loads into Arduino via serial lead from the Arduino IDE. You can issue commands to get the robot to do things.
As well as directly commanding the robot over the serial port, it's designed to be controlled from a host CPU which can be anything but we've been using it with the Raspberry Pi.
There is some more information about development environments in Code Development.
Even though the UKMarsBot is a standard design, there are several options:
- The gearbox ratio
- The wiring of left and right motors
- The wiring of the encoders
- The wheel size.
These can be configured in the file 'robot_config.h'. See that file and make appropriate adjustments.
(If you are using low level control from a Raspberry Pi, you might be able to adjust for these in the Raspberry Pi. But if you are using any mid- or high-level control, you will definitely need§ to adjust this file!)
NOTICE: This document reflects version 1.2 of the interpreter.
Baud rate is 115200, 8N1 bit format (8-bits, no parity, and 1-stop bit).
Once connected you should get a prompt like on reset:
Hello from ukmarsey
No spaces, tabs or other control characters (below value 32) are allowed in commands, except for those mentioned below in 'Special Control Characters' below, and their use is undefined and may change in future versions of the interpreter. (Technically UTF-8 characters are ok, but none are currently used in commands).
All serial commands are case sensitive. Each command needs to have a LF (10, 0x0D) at the end of it.
Values are in decimal, but if the value is out of range then interpreter will either issue an error, ignore extra values or interpret this in an undefined way. This last two options are considered undefined operation - and future changes of the interpreter might change the behaviour. Examples are D11= (no value), D11=2 (out of range setting of an I/O port, might throw an error.), D11=100 (out of range, might ignore 00) or D1=-3 (unexpected minus, probably error).
Any commands that return values is done on a seperate line per command. The ends of these lines returned from the interpreter contain a CR LF (0x0D 0x0A).
- Control-X (0x18) - Soft-Reset - same as Control-C, but stops motors, and any active commands. (Same as 'x' command).
- Control-C (0x03) - Abort entry of line. NOTICE: Character subsequent to this character will be treated as the start of a new line.
- Line Feed (LF, 0x10) - Finish line entry and send to the interpreter.
- Carriage Return (CR, 0x13) - Ignored by interpreter.
- Backspace (0x08) - Removes one character from input buffer, assuming input buffer has any characters in. Generates "\x08 \x08" which should step back, erase, then step back. However on some serial terminal emulators this might need to be enabled (e.g. on CoolTerm this option is 'Handle BS and Del Characters').
Note: 'x' command stops the motors in the case of runaway, but requires a newline (LF/0x10) afterwards. If you are using a serial terminal emulator (as opposed to Arduino Serial Monitor) you can also use Control-X to stop a runaway.
Cmd | Action |
---|---|
^ | reset state - writes RST in reply. |
^^ | processor reset |
v | show version |
V | Verbose error code, 2=extra verbose (default), 1=verbose, 0=numeric - see 'Interpreter errors'. |
E | Serial echo of input 0=off, 1=on (default). Generally should be turned off for machines |
Examples:
V0 Verbose off (numeric error codes)
V1 Verbose on (text error codes)
V2 Like V1 except even successful commands reply with OK
E1 Echo input back (for humans with serial terminals)
E0 Don't echo input back (for machines, or for use with Arduino Serial monitor)
Cmd | Params | Action |
---|---|---|
T | Tv,w or Dv or T,w | Set speed (v) in mm/s and/or rotation (w) in degree/s |
NOTE: Using this command with 'eu' allows mid-level control of the Robot.
Examples:
T20 Move forward at 20 millimeters per second
T-20 Move backward at -20 millimeters per second
T,11 Rotate anticlockwise at 11 degrees per second
T,-45 Rotate clockwise at 45 degrees per second
T20,11 Move forward at 20 millimeters per second while rotating anticlockwise at
T0,0 Stop (both speed and rotation)
T Set speed to zero, leave rotation the same value
T, Set rotation to zero, leave speed the same value
Cmd | Params | Action |
---|---|---|
D | Dp or Dp=n | Set or read digital pin |
A | Aa or Ap=n | Read analogue pin, or write PWM pin |
M | Mm=p | Motor control, using PWM value. 1 = left motor, 0 or 2 = right motor. PWM value -255 to 255, with 0 as stop. |
N | Nm,n | Dual motor control specifying a battery voltage |
P | Pp=d | PinMode - Set up GPIO pins; p is pin, d is I(input) or O(output) or U(Pull Up Input) |
Regarding analogue readings, (A command), these are read off interrupts, so can be up to 2ms old.
M - PWM values are low level control of the motor input. 255 represents full voltage - whatever level that is.
N - Set the voltage to the motor. Voltage levels are gneerated via PWM, after scaling for the battery level. This gives a more consistent result than raw PWM values, because it doesn't result in variation due to battery level.
Examples:
Setting digital output pins requires no response. It is assumed to simply 'work'.
D6=1 set pin 3 to logic one
D13=0 set pin 13 to logic zero
Reading digital input pins returns a single character '0' or '1' for the pin state
D4 might return '0'
Reading an analogue pin returns an integer in the range 0..1023
A2 might return '76'
A0 read analogue pin 0
Write to a PWM output. NOTE: PWM use digital I/O numbering! Range 0..255.
A9=255 write PWM pin 3, with 255.
Output a motor PWM duty cycle from the range 0..255.
M1=45 returns nothing
M1=255 full PWM forwards left motor
M2=0 PWM = 0, effectively off
M2=-128 Half PWM backwards, right motor
M0=128 Right motor
N command also controls the motors, however it's referenced to battery which means we can avoid differnt speeds as the battery discharges - and instead specify voltages rather than PWM (which is in fact a proportion of the current battery voltage). The general format is 'Nf,g' where f and g are signed floats representing voltage levels, negative backwards, positive forward.
N3.5,-3.5 Left motor 3.5 volts, right motor backwards 3.5 volts
N0,0 Stop both motors
The advantage of this command is that it will go the same speed regardless of battery level, assuming you keep values to the range underneath the minimum battery voltage expected.
The Pin mode commands allows the user to configure pins.
P6=O Configure right LED on wall follower board to Output
P11=O Configure left LED on wall follower board to Output
You can how turn the left and right LEDs on and off:
D11=1
D11=0
D6=0
D6=1
To switch them back to inputs:
P6=I Stop driving the right LED
P11=I Stop driving the led LED
Reading an encoder counter might be more involved. It is the total so far and the range is int32 (+/- 2,147m even at 1000 counts per mm!). Result or parameter is signed.
Cmd | Action |
---|---|
C0 | Read right wheel counter, also possible to use C2 |
C1 | Read Left wheel counter. Might return the left wheel counter as '-3752901'. |
Cn=m | Set the Wheel count value, usually. e.g. C1=0 where n=1 and m=0. |
C | Alone with no parameters - prints both encoders as 'left,right' |
Cz | Same as C, but also zeros encoders immediately after reading |
Ch | Same as C, but values in Hex |
ChZ | Same as Ch, but also zeros encoders immediately after reading |
z | zero wheel encoder counters. No return. |
ea | print wheel current info (all) - Format 'encoder-sum,distance,encoder-difference,angle' |
e | Old command for 'ea' command, still supported for backward compatability |
er | print wheel current info (raw format) - Format 'encoder-sum,encoder-difference' |
eu | print wheel current info (unit format) - Format 'distance,angle' where distance is mm, angle is degrees |
es | print speed (mm/s) and rotation speed (degrees/s) |
r | print encoder setup - Format 'mm-per-count,degrees-per-count' |
NOTE: 'r' command allows decoding 'er' into distances/angles on the host, rather than taking time to print floating values.
Setting a counter using 'C' command is usually to zero but could be any legal value. Therefore a quick 'z' command is provided.
Examples: C 98514,98181 C -5075,129406
Cz
8487,-8186
Cz
0,0
Ch
5C65,FFFFA6D8
Chz
B507,FFFF50CE
Ch
21E4,FFFFDEBE
It's sometimes necessary, with Infra-red sensors, to read the IR sensor 'dark', turn on IR illumination, take another IR sensor reading ('light') then turn the IR illumination off.
These commands provide the ability to specify up to three output ports for IR illumination, and read up to size sensors.
By default this is set up to read A0, A1, A2, A3 as sensors and use D12 to turn on the IR illumination with a logic 1 , which will work for the standard line and wall followers of the UKMARSBot (although the wall follower doesn't use A3).
Cmd | Action |
---|---|
S | Read sensors - gives difference between dark and light - which is what you normal what you need |
Sr | Read sensors 'raw' format - dark values then light values seperated by commas. |
Sh | As per S, but in hex from 0-FF without commas. You can get more data samples per second in this format. |
* | Enable/Disable emitter LED Control. Used to save power. |
NOTE: Sh values are divided by 4 (lose bottom 2 bits), capped at 255 (FF). The bottom bits are generally noise anyway. Use this if the transfer time is more important than resolution.
Examples of output of 'S':
S
398,104,19,6
Examples of output of 'Sh':
Sh
FF691306
Sh
FF681307
Examples of output of 'Sr':
0,0,0,142,28,32,486,352 <- wall close
0,0,0,4,28,13,15,11 <- no wall
Examples of Emitter control:
*1
Sr
0,0,0,131,135,25,440,318
*0
Sr
0,0,0,1,0,0,0,1
Parameters select specific characteristics of high level commands. They are stored in EEPROM so are preserved on power off, especially for human users.
Cmd | Action |
---|---|
$n | Read parameter n |
$n=f | Write parameter n with value f. E.g. $0=1.1 |
$a | Read all floating parameters seperate lines per parameter. |
$b | Read all boolean (bit) parameters, as 32 seperate lines. |
$d | Default all parameters. |
NOTE: $a and $b will incur serial buffering and block until complete
Currently all parameters are floating point values.
A List of specific usage of each parameters is here.
Param | Action |
---|---|
0 | Undefined |
1 | Undefined |
2 | Undefined |
3 | Undefined |
4 | Undefined |
5 | Undefined |
6 | Undefined |
7 | Undefined |
8 | Undefined |
9 | Undefined |
10 | Undefined |
11 | Undefined |
12 | Undefined |
13 | Undefined |
14 | Undefined |
15 | Undefined |
There are also 32 boolean ('bit') parameters. The first 16 are listed here. Current all are undefined.
Param | Action |
---|---|
100 | Undefined |
101 | Undefined |
102 | Undefined |
103 | Undefined |
104 | Undefined |
105 | Undefined |
106 | Undefined |
107 | Undefined |
108 | Undefined |
109 | Undefined |
110 | Undefined |
111 | Undefined |
112 | Undefined |
113 | Undefined |
114 | Undefined |
115 | Undefined |
(to be added)
Cmd | Action |
---|---|
l | (lower case L) Led on/off, l0 = led off, l1 = led on. (Short version of D13=x) |
? | just prints 'OK' |
h | just prints 'OK' |
s | shows the state of the switches. Returns a single number. NOTE: the Button is '16', and overrides the 4 switches |
b | shows the voltage of the battery. Example return '7.421' |
bi | Shows the voltage of the battery in millivolts. Example: '7421' |
bh | Shows the voltage of the battery in millivolts in hex format |
m | motor tests (see below) |
x | Motor stop (no parameters, no return.) - and cancels any actions |
m = motor tests, runs for 2 seconds or until button is pressed. NOTE: These are for human testing and are NOT for machine control!
Command | Function | Action |
---|---|---|
m0 | 0 | Motors Off |
m1 | 1 | Forward 25% |
m2 | 2 | Forward 50% |
m3 | 3 | Forward 75% |
m4 | 4 | Reverse 25% |
m5 | 5 | Reverse 50% |
m6 | 6 | Reverse 75% |
m7 | 7 | Spin Left 25% |
m8 | 8 | Spin Left 50% |
m9 | 9 | Spin Right 25% |
ma | 10 | Spin Right 50% |
mb | 11 | Pivot Left 25% |
mc | 12 | Pivot right 25% |
md | 13 | Curve - Left 25%, Right 50% |
me | 14 | Curve - Left 50%, Right 25% |
mf | 15 | Curve - Left 75%, Right 50% |
Send Control-C (or Control-X) followed by ^ repeatedly with a 20ms gap until you receive a RST message. Then try ? and v looking at the responses. If they don't succeeed, then repeat the entire sequence.
This is under review.
Interpreter error text strings (verbose on) are not defined to be stable across versions. Numeric error codes (verbose off) will be stable across releases as far as possible.
NOTE: All error messages (both verbose text and numeric are preceeded by '@Error:' as the first characters on the line, except for verbose error for OK, which appears simply as 'OK' on the line.
OK was left for compatbility with older versions (and it sort of makes sense, since it's not strictly an error as such). This message is only generated by specific commands - generally commands are silent when they succeed. OK can be considered command output, although it will change if verbose is off to an Error style code ('@Error:0').
NOTE: This makes Error codes a special case of unsolicited return messages - see below.
enum
{
T_OK = 0,
T_OUT_OF_RANGE = 1,
T_READ_NOT_SUPPORTED = 2,
T_LINE_TOO_LONG = 3,
T_UNKNOWN_COMMAND = 4,
T_UNEXPECTED_TOKEN = 5
};
OK
@Error:0
@Error:1
@Error:5
@Error:Out of range
Apart from the start up message, any other messages that are not caused by a command have a @ before them and are on a seperate line.
For example:
Message | Cause |
---|---|
@Defaulting Params | Shown when there was a problem loading parameters on boot. |
NOTE: Interpreter Error codes also have this format ('@Error:') - see Interpreter Errors.
Other internal settings and hidden information
Setting | Cause |
---|---|
floating_decimal_places = 3 | Number of floating point decimal places printed. |
https://www.arduino.cc/reference/en/
http://www.micromouseonline.com/micromouse-book/
This interpreter was written by Rob Probin, Jan 2021, with help and code from Peter Harrison, with many suggestions and input from UK Micromouse and Robotics Society members.
Code and this README Copyright (c) 2021 Rob Probin, except where noted in code or documentation. All licensed via MIT License. See LICENSE file.
Based on ukmarsbot examples by UK Micromouse & Robotics Society & Peter Harrison https://github.com/ukmars/
Some code based on: https://github.com/micromouseonline/ukmarsbot-line-follower-basic
It bares some resemblance in some places to G-code (and implementations like grbl), but does not attempt to maintain any G-code compatability, since really G-code is targetted at CNC not really at self-moving small robots.
Since this runs on an ATMEG328P Arduino Nano, there is not much Flash or RAM, so things like help and extended error messages have been minimised.
We aim to have shortened the amount of bytes requiring to be transmitted in order to maximise the bandwidth across the serial link, while still allowing for easy human entry via a text terminal.
There are three subsystems running all the time currently on the system tick interrupt that runs every 2ms: battery reading, function switch reading and update sensors control. The analogue command (e.g. A0) uses these subsystems to read the ADCs (partially to avoid conflicts since there is only one ADC unit).
The Arduino Nano has a 64 byte input buffer and a 64 byte output buffer. Transmission to and from the Arduino needs to be carefully designed not to overrun these buffers.
If you overrun the 64 byte output buffer the print commands will start waiting inside the Arduino Nano (the commands will take longer to complete). The could cause several affects - once of which could potentially be commands stacking up in the input buffer. It only requires, for instance perhaps three 'Sr' commands to cause the output buffer to be filled.
If you overrun the 64 byte input buffer, then old characters will be dropped. This is bad for several reasons - one of which includes partial comamnds - which could cause an error or action you don't intend.
There are several techniques possible - for instance avoid sending a long stream of commands without any commanbds with a reply. Having a long stream of no-reply commands means the host program cannot track how far through the input buffer the interpreter has got, therefore estimate how many bytes are currently queued.
If you are changing the baud rate, care must be taken to choose a baud rate that both end can generate accurately. If the total error exceeds of both sides exceeds around 2 or 3% then you are likely to start getting byte errors. Ideally you want to be within 1%.
One such baud rate table for the AVR on the Arduino Nano includes: https://trolsoft.ru/en/uart-calc
Remeber to add on the error rate of the other side as well - whehter that be a Rasberry Pi, USB-Serial converter, or other serial port. Sometimes you can get lucky. If they are both, say +3% of the target, then the baud rates will match. But a -2.5% on one end, and a +2.5% on the other end gives 5% error, and this will cause problems. (Although errors rates up to 5% would theoretically work before it meets an edge, the reality of sampling mechanisms, slew rate and other factors means that realistic error rates are well under half of this.)
Sometimes these cannot be simply looked up from microcontroller or microprocessor data sheets - crystals tend to be accurate, but devices with resonators or internal RC oscillators tend to have large tolerance between devices themselves - and this needs to be taken into account.