Hello Main Loop Example
This introductory example covers getting set up, interacting with the Amiga, and using auto-control mode to drive your Amiga from a computer using the farm-ng microcontroller kit.
This example enables driving the Amiga by entering simple fwd / rev / left / right keyboard commands the serial port, which the app sends over the CAN bus.
- farm-ng microcontroller kit (w/ USB-C cable)
MainLoop class is used throughout the application layer of
the farm-ng firmware.
MainLoop contains generic functionality we use on our pendant,
dashboard, and auxiliary components for constant looping,
receiving of CAN messages, sending of regular status updates
Heartbeats, and more.
MainLoop takes an
AppClass in the constructor, and the
AppClass is expected to contain a method called
iter that is
called every in every iteration (also called
iter) of the
_register_message_handlers() method is an important feature
This method adds parsing directly into the MainLoop so the App
only receives the desired CAN messages.
Because messages sent on the CAN bus are seen by all other
it is important to efficiently filter out irrelevant messages
on the resource constrained microcontrollers.
Try to add an additional message parser for one of the other messages on the CAN bus. For instance, if you have a pendant connected to your CAN bus you could add something like:
from farm_ng.utils.packet import PENDANT_NODE_ID, PendantState
DASHBOARD_NODE_ID] = self._handle_amiga_tpdo1
self.main_loop.command_handlers[CanOpenObject.TPDO1 | PENDANT_NODE_ID] = self._handle_pendant_state
def _handle_pendant_state(self, message):
pendant_state = PendantState.from_can_data(message.data)
All messages on the bus can be found by using the
cansniffer example app.
You can compare the detected CAN ID's to those in
CanOpenObject. But keep in mind, node id is added
to the function code for the full CAN Id, as you'll see below in
CanOpenObject / DASHBOARD_NODE_ID.
TickRepeater class is a useful utility that we recommend
taking advantage of throughout your custom implementations.
We use "repeaters" to limit the frequency of certain actions, by
only performing the action once the
period of the repeater has
past, when compared to the last time the action was performed.
check() method returns
False until the checkpoint has
True once the checkpoint is past.
True is returned, the repeater is updated to the next
checkpoint, so you really only need the
check() method in most
TickRepeater is what we call a "catch-up" repeater, in
which the the next checkpoint is the
ticks_period_ms (period in ms) added to the last checkpoint
(rather than the next checkpoint being the
added to the time of last execution).
As you can infer, there's no reason to use one of these catch-up
repeaters if the
check() will be called less frequently than
ticks_period_ms used in the constructor.
which wraps every
2^29 ms (~6.2 days).
Our logic handles a single wrap, but we do not detect two wraps
as we use this in periods more on the
100 ms timescale.
If you are creating a long duration application,
just make sure your period is less than 6 days and that the check
is called at least that often.
for more details about
Wrapper for CAN packet used for auto mode controls of the Amiga.
AmigaRpdo1 object with a requested
AmigaControlState, speed, and angular rate.
Then pack this into a
and send this message over the bus.
This is a request for a specific
angular rate, and linear velocity sent to the dashboard.
The dashboard, operating as the vehicle control unit (VCU), has
built-in logic to prevent unsafe speeds, accelerations, control
state transitions, etc.
Wrapper for CAN packet used for sending state of the Amiga,
Unpack the message to see the current
and angular rate of the robot.
There is a convenient util function
from_can_data that unpacks
the message directly into an
Control state of the Amiga.
CanOpenObject / DASHBOARD_NODE_ID
We mostly follow the CANopen standards. A recommended reading is the CSS Electronics CANopen tutorial.
Some of the third-party, auxiliary components we have integrated into the system do not allow for strict adherence to the CANopen standards. For our core system, we adhere closely to the standards.
In this standard, messages are passed using function codes based
on their use.
Each component has a node ID identifier used to identify either
the intended recipient or the source component of each message
sent on the CAN bus.
In the current example, we send requested commands to the Amiga
RPDO1 channel, and receive responses streamed from the
Amiga on the
These are differentiated from pendant or motor controller RPDO/
TPDO command sets by sending them with the dashboard node ID.
code.py (or main.py) is the default name for the executable Python file on microcontrollers flashed with CircuitPython. You'll see we stick to the code.py convention with our files.
Here we create
HelloMainLoopApp as a simple example of the
AppClass you can create.
HelloMainLoopApp constructor, we create a
that will stream the automatic control command to the dashboard
every 50 ms (at a 20hz rate).
iter() call, we:
- Check for control keys entered into the serial console
<space bar>for toggling auto mode, &
d[fwd / left / rev / right] for adjusting velocities].
- Parse through all received CAN messages, sorting only for the
AmigaTpdo1responses coming from the dashboard.
- Send the most up-to-date auto control commands, based on serial
console entries, in an
Connect your microcontroller as in the following diagram:
2. Load the code
amiga-dev-kit/circuitpy/, drop the
code.py file and
lib/ folder directly into the root of the mounted
CIRCUITPY drive, as seen below.
This assumes you have already cloned the amiga-dev-kit repo.
git clone firstname.lastname@example.org:farm-ng/amiga-dev-kit.git
3. Open the Serial Console
Mu is the recommended serial console program by adafruit on their CircuitPython serial console page. Mu has a built in plotter for tuples printed to the serial console (print statements in the python code on your microcontroller).
We've found that Mu can be a little unstable and freezes occasionally, so we'd recommend checking out their links for the "advanced" serial console:
You should see an output of the current state of the robot, similar to the screenshot below, and you should see the values update as the robot drives around.
4. Enable AUTO
On the dashboard
Navigate to the Auto mode tab on your dashboard, and click the
[AUTO CONTROL] button.
[AUTO READY] icon should turn yellow,
indicating the dashboard is ready for a component to take
In the serial console
Hit the space bar in your serial console to request auto
control, and you should see the
[AUTO READY] turn green,
indicating the dashboard is in
Auto Control mode.
NOTE: The space bar may not register every press, so use the dash indicators!
5. Drive the robot
In the serial console, increase / decrease the robot forward /
reverse speed with the
s keys, and increase / decrease
the robot angular rate with the
6. Release AUTO
Hit the space bar in the serial console to release auto
control and return to the
[AUTO READY] state. Or hit the E-Stop
on your Amiga!