Library reference - ejbergdk/loconet-routectrl3 GitHub Wiki

Initial remarks

Most of the software blocks described here has two common functions, a xxx_init(void) and a xxx_update(void).
The init function is called once at startup, before interrupts are enabled, and is used to initialize local variables, set up peripherals etc.
The update function is called continuously from the mainloop, and is used to do a bit of work in the software block. The entire system relies on these calls to xxx_update doesn't take up too much time each. This way we can make a program that feels like it is multitasking, without using an actual operating system.
The xxx_init and xxx_update functions won't be described individually below.


collision_check.c / .h

Collision checker.
When a collision happens on the Loconet bus, this little utility makes a 250 ms long pulse on port D3. Connect a LED (and series resistor) to this port to get a visual indication of these collisions. If this LED flashes a lot, then there is a problem with Loconet.
This is in no way essential to the operation of the rest of the software. It can be removed if desired.


fb_handler.c / .h

Feedback handler.
Receiving and processing feedback reports from Loconet is done here. If you want to do something when a train enters or leaves a contact track, you should use the macros and functions in this header file.

FEEDBACK_OCC()

Feedback Occupied macro. Use this to register a callback function that you want to run when a certain feedback address is reporting occupied. The callback function includes a parameter with the address of the feedback that caused the callback.

Example:

#include "fb_handler.h"
#include "route.h"

// Function with the actions to do
static void lets_do_this(uint16_t num)
{
    // Set signal 56 to red
    route_send_sw(56, SW_R);
}

// Set up a monitor on feedback address 49 occupied, and call the function above when it happens
FEEDBACK_OCC(49, lets_do_this);

More than one macro may use the same callback function.
It is also possible to make more than one macro with the same address, but different callback functions. However, you can't control the order these callbacks will be run.

FEEDBACK_FREE()

Feedback Free macro. The same as FEEDBACK_OCC(), except it runs the callback when the feedback address reports the track is free, instead of occupied.

FEEDBACK_RANGE_OCC()

Feedback Range Occupied macro. The same as FEEDBACK_OCC(), but it takes both a start and an end address. All feedback addresses between the start and end addresses (both inclusive) will activate the callback function.

FEEDBACK_RANGE_FREE()

Feedback Range Free macro. The same as FEEDBACK_RANGE_OCC() but for free instead of occupied reports.

fb_handler_set_state()

Do not use! Sets the state of a feedback address. It is used internally by other parts of the controller.

fb_handler_get_state()

Gets the state of a feedback address. If you need to know if a certain feedback address is free or occupied, but you don't need to get all state changes with callbacks, use this function. It returns true if the address given is occupied, false if it is free.
There is one caveat. At power up, all feedback addresses are assumed to be free, until a feedback report says otherwise. Some feedback modules (if not all) does not automatically report occupied addresses at startup. Some modules can be asked to send its status with a certain switch address.


main.c

The main entry point.

main()

Main is the start of the entire program. It sets up the internal clock to 24 MHz, and, if configured, enables the external 32.768 kHz crystal. Then it initializes other parts of the software, enables interrupts and finally runs the mainloop.


mmi.c / .h

Monkey machine interface.
A small and very simple user interface, consisting of one pushbutton and one led (the ones already present on the Curiosity Nano board). The purpose of this is for the user to see and adjust the "operating level" of the controller. The "operating level" is just a number between 0 and 4 and can be used to whatever you like.
I use it to set the level of automation that I want my layout to operate at. Level 0 is fully manual, where I have to activate all routes by hand. Level 1 automates some simple routes where there are no alternatives. Level 2 automates most of my layout, except entering and exiting my main station. And so on up to level 4 where my entire layout is operated fully automatic, including driving into the main station, waiting for some time at the platform, and then continues out of the station. Depending on my mood on the day, I have an easy way to set how much I want to be involved in controlling the traffic.

This mmi probably should not have been included on the main branch in the first place. Its functionality is very specific to the way I use this controller. I may move it out of the main branch some sunny day.


route.c / .h

Route handling.
This implements the route functionality, that is the main feature of this whole project. A route is the process of moving a train from one specific spot to another specific spot on your layout, preferably without colliding with another train along the way. If all possible movements on the layout are defined as routes, then we can say which routes aren't allowed to be active at the same time, thereby preventing collisions.

Route states

A route has 4 different states that it goes through:

stateDiagram-v2
    [*] --> Free
    Free --> AwaitCstr: route_request()
    AwaitCstr --> AwaitExe: No constraints blocking
    AwaitCstr --> Free: route_cancel()
    AwaitExe --> Active: Call activate CB
    Active --> Free: route_free()<br />Call free CB
    Active --> Free: route_cancel()<br />Call cancel CB
Loading

When a route is in the Free state, it is not in use. All routes start in this state at power on.

Calling route_request() will transition the route to AwaitCstr (Await Constraint). Routes can have a constraint list, that should have all conflicting routes and feedbacks. The route will stay in this state until all constraints are resolved. Once that happens, the route will change state to AwaitExe.

AwaitExe will call the Activate callback and then go to the Active state.

The route will stay in Active state until either route_free() or route_cancel() is called, at which time either Free callback or Cancel callback is called, and the route goes back to Free.

ROUTE()

ROUTE(num, act, fre, can, ...)
Route creation macro. It takes at least 4 parameters:

  • num: Route number. A unique number between 0 and MAXROUTES-1. Every route must have its own number.
  • act: Activate callback function pointer. The function you want to run when the route is activated. Set to NULL if not used.
  • fre: Free callback function pointer. The function you want to run when the route is freed. Set to NULL if not used.
  • can: Cancel callback function pointer. The function you want to run when the route is cancelled. Set to NULL if not used.
  • ...: Optional list of constraints for this route.

CSTR_RT()

Route constraint.
Create a route constraint in the constraint list. Not needed. Numbers in the list without any CSTR_ wrapper, is automatically considered a route constraint.

CSTR_FB()

Feedback constraint.
Create a feedback constraint in the constraint list.

route_request()

Request a route to be activated. It won't activate until all constraints are resolved. You may call route_request() more than once without consequence, but the route will only activate once.

route_free()

Free an active route. If this function is called without the route being active, it does nothing.

route_cancel()

Cancel a route. If called when a route is waiting for constraints, the route is just set back to free. If the route is active, the cancel callback is called and the route is set to free.

route_forceactive()

Route is immediately marked as active, without calling the activate callback or checking constraints. Should only be used during initialization.

route_kill()

Route is immediately marked as free, without calling the free or cancel callback.

route_state()

Get current state for a route.

route_exists()

Check if a route definition exists for a given route number.

route_send_sw()

Send switch request. Switch requests and feedback reports (see below) are queued up and sent with an appropriate intervals.

route_send_sw_prio()

Send switch request before any other in the queue.

route_send_fb()

Send feedback report. Feedback reports and switch requests (see above) are queued up and sent with an appropriate intervals.

route_send_fb_prio()

Send feedback report before any other in the queue.


route_cmd.c

Debug command (route) for easy testing of routes without having the controller connected to a fully operational layout.


route_delay.c / .h

Create delays in route functions. Works similarly to timer.c, but uses route number instead of context pointer.

route_delay_cb

Function prototype for route delay callbacks. This is the format of the functions that you'll want to be delayed.

route_delay_add()

void route_delay_add(uint16_t timeout, route_delay_cb *cb, routenum_t num);
Register a function to be called at a later time. Use this to create delayed actions in route callbacks. It takes three parameters:

  • timeout: The time to wait before calling the callback function, in seconds.
  • *cb: Pointer to the callback function, that you want to run.
  • num: The route number that adds the delay. This number will be delivered to the callback function.

route_delay_cancel()

void route_delay_cancel(routenum_t num);
Cancel delayed functions belonging to a specific route.


route_queue.c / .h

Queueing functionality for switch and feedback commands. Used by route.c. Do not include or use directly from your program.


sw_handler.c / .h

Switch handler.
Receiving and processing switch requests from Loconet is done here. If you want to do something when someone/something sends a switch request, you should use the macros and functions in this header file.

SWITCH_REQ()

Switch Request macro. Use this to register a callback function that you want to run when a certain switch address is activated. The callback function includes two parameters with the address of the switch that caused the callback and the direction the switch.

Example:

#include "route.h"
#include "sw_handler.h"

// Function with the actions to do
static void example(uint16_t num, bool dir)
{
    // Set signal 120 to green if switch 42 is set to R
    if (dir == SW_R)
        route_send_sw(120, SW_G);
}

// Set up a monitor on switch address 42, and call the function above when it happens
SWITCH_REQ(42, example);

More than one macro may use the same callback function.
It is also possible to make more than one macro with the same address, but different callback functions. However, you can't control the order these callbacks will be run.

SWITCH_REQ_RANGE()

Switch Request Range macro. The same as SWITCH_REQ(), but it takes both a start and an end address. All switch addresses between the start and end addresses (both inclusive) will activate the callback function.

sw_handler_set_state()

Do not use! Sets the state of a switch address. It is used internally by other parts of the controller.

sw_handler_get_state()

Gets the state of a switch address. If you need to know the direction of a certain switch address, but you don't need to get all state changes with callbacks, use this function. It returns SW_R (false) or SW_G (true).
There is one caveat. At power up, all switch addresses are set to SW_R, until a switch request says otherwise.


switch_queue.c / .h

Small state machine to send switch requests with proper timing on Loconet. Used by route.c and route_queue.c. You may use it directly, but it is highly recommended to use the corresponding functions in route.h instead (route_send_sw and route_send_sw_prio).


term.c / .h

Debug terminal.
This takes care of the UART communication for the debug shell. Everything written to stdout (like printf) is buffered up here and sent out the serial port. Incoming serial data is read and put together to a command line, which is then sent to lib/avr-shell-cmd/cmd.c (a submodule) that'll try to locate and execute the command.


test_cmds.c

Various test commands.
A collection of test commands that didn't naturally belong anywhere else. Mostly they are leftovers from earlier software development. They can (and probably should) be deleted some day.

inCmd()

Debug command to send an OPC_INPUT_REP to Loconet.

swCmd()

Debug command to send an OPC_SW_REQ to Loconet.

timeCmd()

Debug command to measure the execution time of another debug command. Just put time in front of the command to measure. Execution time is given in ticks (see ticks.c).


ticks.c / .h

A simple set of timing-related functions. Provides the system tick count. That is how many ticks has passed since program startup. The tick counter is a 32-bit counter that counts up by one, 1024 times per second. The counter will wrap around to 0 after 48.5 days. The ticks system uses the inbuilt RTC peripheral, and thus doesn't use up a regular timer/counter.

ticks_t

The type of the ticks counter. In essence just an unsigned 32-bit int.

ticks_get()

Function that returns the current value of the ticks counter.

ticks_elapsed()

Function that takes one ticks_t parameter (usually a timestamp) and returns the time elapsed since that timestamp (also in ticks).

TICKS_FROM_MS() and TICKS_FROM_SEC()

Macros to convert milliseconds or seconds to ticks.


timer.c / .h

Simple timer implementation. Create timers that'll call functions at a later time.

timer_cb

Function prototype for timer callbacks. This is the format of the functions that you'll want the timer to call.
If you want a timer inside a route, have a look at route_delay.c / .h instead.

timer_add()

int8_t timer_add(ticks_t timeout, timer_cb *cb, void *ctx);
Add a timer that will call another function at a specific time in the future. It takes three parameters:

  • timeout: The time to wait before calling the callback function.
  • *cb: Pointer to the callback function, that you want to run.
  • *ctx: Context pointer. Will be delivered as a parameter to the callback function. Use as you see fit, or set to NULL if not used.

timer_add() will return 0 if timer is successfully created, -1 if not.
After timeout ticks, the function given in *cb is run once, after which the timer deletes itself. The function isn't called repeatedly every timeout ticks, though that feature would be easy to add if needed.
Example of using a timer to call a function after 20 seconds:

static void run_me_later(void *ctx)
{
    // Add code to run after 20 seconds here.
}

Somewhere else in the code, create the timer:
timer_add(TICKS_FROM_SEC(20), run_me_later, NULL);

timer_delete()

void timer_delete(void *ctx);
Deletes a timer (if it exists) before it times out. Only timer(s) with a matching context pointer is deleted.

⚠️ **GitHub.com Fallback** ⚠️