This repository contains a Zephyr example application. The main purpose of this repository is to serve as a reference on how to structure Zephyr-based applications. Some of the features demonstrated in this example are:
- Basic Zephyr application skeleton
- Zephyr workspace applications
- Zephyr modules
- West T2 topology
- Custom boards
- Custom devicetree bindings
- Out-of-tree drivers
- Out-of-tree libraries
- Example CI configuration (using GitHub Actions)
- Custom west extension
- Custom Zephyr runner
- Doxygen and Sphinx documentation boilerplate
This repository is versioned together with the Zephyr main tree. This
means that every time that Zephyr is tagged, this repository is tagged as well
with the same version number, and the manifest entry for zephyr
will point to the corresponding Zephyr tag. For example, the example-application
v2.6.0 will point to Zephyr v2.6.0. Note that the main branch always
points to the development branch of Zephyr, also main.
Same Sky (formerly CUI Devices) AMT21 series absolute encoders on a half-duplex
RS485 bus. Both the 12-bit and 14-bit resolutions are supported, as are the
single-turn and multi-turn variants and both the 2 Mbps and the adjustable data
rate options. The generic interface lives in include/drivers/encoder.h and the
driver-specific diagnostics in include/drivers/encoder/amt21.h.
One bus node owns the UART and polls every encoder child node in turn, so each encoder appears as its own device. Readings are taken from a cached snapshot and never block on the bus, which makes them usable from a control loop.
The UART must be able to drive the transceiver in hardware, for example through
the de-enable property of an STM32 UART. Toggling driver enable from software
adds latency around the turnaround window and is not viable at 2 Mbps.
Keep de-deassert-time small. It is measured in sixteenths of a bit time, and a
large value holds the transceiver enabled past the end of the outgoing stop bit,
straight into the window where the encoder starts replying. That collision
corrupts both frames.
On boards whose transceiver has its receiver permanently enabled, the
transmitted command byte comes back on the receive line. Declare tx-echo on the
bus node so the driver discards it; without the property the driver works it out
from the first transaction.
At 2 Mbps the encoder starts replying about 3 us after the command byte. That is
far too soon to arm a receiver in software, so the driver enables reception once
at init and never disables it during normal operation. Arming the receiver per
transaction is the single most likely way to lose the first byte of every
response. For the same reason the driver always answers UART_RX_BUF_REQUEST:
failing to hand over the next buffer stops reception permanently.
The encoder also needs a gap between consecutive commands, which
inter-command-delay-us provides. Sending commands back to back without it is
the other common cause of dropped responses.
Beyond that, a failed transaction is retried up to max-retries times, and an
encoder is only declared offline after offline-threshold consecutive failures.
Until then the last good reading is kept and reported as stale, so a single
dropped response does not disturb a control loop. Reception is resynchronised
after a truncated or overlong response, and restarted if the UART reports it
stopped. A scan that overruns poll-interval-us causes the next scan to be
skipped rather than queued, and a failing encoder never holds up the others.
Three levels, so a production build carries none of the cost:
| Level | Configuration | Cost per encoder |
|---|---|---|
Aggregate count in struct encoder_feedback.error_count |
always available | 4 bytes |
Per-cause counters through amt21_get_stats() and amt21_bus_get_stats() |
CONFIG_ENCODER_AMT21_STATS |
~60 bytes, plus 24 per bus |
Recent failures with the raw bytes received, through amt21_get_error_log() |
CONFIG_ENCODER_AMT21_ERROR_LOG_SIZE |
16 bytes per entry |
The counters are 32-bit and wrap around, so compare successive readings rather than treating them as totals. Statistics are exposed as driver-specific functions rather than through the encoder class, because the ways a transaction can fail are a property of the RS485 protocol and not of encoders in general. Counters are split between per-encoder and per-bus scopes: a UART overrun disturbs every transaction on the bus, so attributing it to one encoder would be misleading.
CONFIG_ENCODER_AMT21_SHELL adds an amt21 command for use on hardware:
amt21 list
amt21 read <dev>
amt21 stats <dev>
amt21 errlog <dev>
amt21 bus-stats <bus>
amt21 zero <dev>
amt21 reset <dev>
amt21 errlog is usually the fastest way to work out why responses are being
dropped, because it keeps the bytes that actually arrived. debug.conf enables
the statistics and the error log.
- Polling faster than the internal update rate of the encoder gains nothing: that rate is 100 us for 14-bit devices and 25 us for 12-bit devices.
- Node addresses must be multiples of four. The low two bits carry the command, which is why up to 64 encoders can share one bus.
- Only single-turn devices can store a zero point;
encoder_set_zero()returns-ENOTSUPon a multi-turn device. Both that command andencoder_reset()make the encoder restart, so it stops answering for about 200 ms. - The turns counter is not retained across a power cycle.
Before getting started, make sure you have a proper Zephyr development environment. Follow the official Zephyr Getting Started Guide.
The first step is to initialize the workspace folder (my-workspace) where
the example-application and all Zephyr modules will be cloned. Run the following
command:
# initialize my-workspace for the example-application (main branch)
west init -m https://github.com/zephyrproject-rtos/example-application --mr main my-workspace
# update Zephyr modules
cd my-workspace
west updateTo build the application, run the following command:
cd example-application
west build -b $BOARD appwhere $BOARD is the target board.
Note that Zephyr sample boards may be used if an appropriate overlay is
provided (see app/boards).
A sample debug configuration is also provided. To apply it, run the following command:
west build -b $BOARD app -- -DEXTRA_CONF_FILE=debug.confOnce you have built the application, run the following command to flash it:
west flashTo execute Twister integration tests, run the following command:
west twister -T tests --integrationA minimal documentation setup is provided for Doxygen and Sphinx. To build the
documentation first change to the doc folder:
cd docBefore continuing, check if you have Doxygen installed. It is recommended to use the same Doxygen version used in CI. To install Sphinx, make sure you have a Python installation in place and run:
pip install -r requirements.txtAPI documentation (Doxygen) can be built using the following command:
doxygenThe output will be stored in the _build_doxygen folder. Similarly, the
Sphinx documentation (HTML) can be built using the following command:
make htmlThe output will be stored in the _build_sphinx folder. You may check for
other output formats other than HTML by running make help.