SRResources.h

documentation for the following objects:

object library
SRTimertimers; time as an event
SRLogconsole logging and time and date functions including RTC support
SRLEDLED blinker and sequencer
SRButtonsimple momentary switch support
SRCRCCyclical Redundancy Check methods
SRPRNGpseudo-random number generators
SRLoopTallyloop() characterizing code
SRMessagerobust inter-processor messaging scheme

the library source is available from my github repository.

SRTimer

SRTimer is the core component of my fastCode strategy. SRTimer turns changes in time (via the millis() function) into simple repeating timed events. the SRTimer object supports any number of individual timers, each of which can be set to trigger (create an event) from 1..65535 milliseconds or 1..65535 deciseconds (655.35 seconds). set once by setTimer(), timers repeat indefinitely. they can be set to a new value at any time. timers can be reset (begin time interval anew) or triggered immediately.

#include <SRResources.h> SRTimer T; // instantiate the object const int TIMER1 = 0; const int TIMER2 = 1; const int TIMER3 = 2; const int NUMTIMERS = 3; void setup () { ... T.begin (NUMTIMERS); // allocates RAM for timers T.setTimer (TIMER2, 667); // timer 2 fires every 2/3rds of a second T.setTimer (TIMER1, 6000); // timer 1 fires every 6 seconds } void loop () { ... if (T.timer (TIMER2)) Serial.println ("timer 2 fires often"); if (T.timer (TIMER1)) Serial.println ("timer 1 fires not very often") }

RAM for timers is allocated by begin(). this allows determining the number of timers and therefore RAM used at run time.

void setTimer (int t, unsigned n); causes timer t to trigger an event at millis() plus time_interval mS in the future and every time_interval mS thereafter.
void setDeciTimer (int t, unsigned n); causes timer t to trigger an event at millis() plus time_interval deciseconds (1 decisecond == 100 mS) in the future and every time_interval deciseconds thereafter.
bool timer (t); returns true when timer t fires.
unsigned long getTimer (t); returns the set interval for timer t.
unsigned long untilTimer (t); returns the number of milliseconds until timer t fires.
void trigTimer (t); causes timer t to immediately fire (eg. next call to timer())
void resetTimer (t); causes the countdown for timer t to start from the current time.

timers provide one millisecond resolution when used in an event loop strategy (see the main library page for details). timers fire when millis() reaches or exceeds the set time interval; code within loop() that blocks will defer timer firing for the amount of time blocked.

SRLog

SRLog initial purpose, and still the most useful and basic, provides a clean and consistent way to print time-stamped "debug" and runtime messages. however it has expanded to include time and date functions, including hardware RTC chip support and support for fastCode/defnite/Method packed time and date communication via SRMessage.

#include <SRResources.h> SRLog L; // instantiate void setup () { ... L.begin (); // } void loop () { ... L.log ("variable is now", variable); L.log ((F("variable is now"), variable); // F() macro flash-text wrapper ... }

the most commonly used method(s) all accept one or two arguments:

void log (t); // fixed text only void log (t, arg); // text and argument

t is text literal, const text, or flash text with the F() macro. the optional arg is (intended to be) every valid C simple data type; int, unsigned, float, string, char array, ...

all log() invokations produce similar output; a time stamp, the fixed text, and optional arg. the format of the time stamp depends on the time source; see below.

int foo = 12345; L.log ("foo is", foo);

will display:

18:40:23 foo is 12345

time and date support

the point of SRLog is to provide easy debug message with a time stamp. unless otherwise specified the time source is millis(). the other two options are built-in support for the DS1388 RTC chip or an external time source delivered in packed format, likely via the SRMessage scheme described elsewhere. the packed time format has semantics that allow time and date to arrive via single uint16's.

there are a number of methods to define and control the time source.

time source selection

L.begin(); // use only one of the following. L.RTCDS1388 (true); // initialize and enable the DS1388 chip L.useExtTime (true); // use external time source

if no time source is specified (neither method above called) then millis() is the source, and minutes and seconds are derived from it. when millis() is the source the time is 1/1/1980 00:00 at reset and increments from there. the date will never change.

if RTCDS1388() is invoked (and umm the chip is actually present) then time stamp data is derived from this hardware source. if the chip is unresponsive then 1/1/1980 00:00:00 is used and the date will not change.

time source status

these methods can be used to determine the time source being used.

bool usingRTC(); // returns true if the RTC hardware is the source bool usingExtTime(); // returns true if external time is the source

get and set time methods

time and date are passed (in or out) via the _time structure below. one must be declared in userland and passed by reference.

struct _time { uint8_t sec; uint8_t min; uint8_t hour; uint8_t mday; uint8_t mon; int16_t year; }; struct _time t; // allocate our time struct gtod (&t); // fetch time and date, store in struct stod (&t); // set the time and date from the data in the struct setTime (int h, int m, int sec); // set time fields only setDate (int year, int mon, int day); // set date fields only

a number of methods return time and date formatted as text strings, either in a specified buffer or in a hidden buffer within SRLog so that they can be easily copied or displayed. for those that place the string in your userland buffer, the size must accommodate the largest possible string. the example below is correct.

char buff [12]; // accommodates "2017/dec/31" or "23:59:59" including nul. L.timeStr (buff); // formatted time string in buff: "23:59:29" etc L.dateStr (buff); // formatted date string in buff: "2017/dec/31" etc

these return a pointer to a static text buffer (character array). do not write it. the contents of the buffer persist until the next call to one of these methods.

Serial.print (L.timeStr()); // as above, eg. "23:59:59" Serial.print (L.dateStr()); // as above, eg. "2017/dec/31" Serial.print (L.dateStrShort()); // compact date: "dec/31" no year

external time

external time is used when the source of time and date is external to SRLog, and probably external to the box running the code, as in the Roadster multiprocessor system, where time and date are passed in packed, uint16 format. external time basically disables all internal time sources; the time and date will remain unchanged unless an external source sets them via one of the methods described above (stod(), settime(), or setDate or one of the methods below. external time means that the date and time data stored within SRLog remain unchanged unless explicitly set.

external time is designed to support a single time source for a multi-processor system. it is assumed that SRMessage is used as inter-processor communications, but anything will work if the semantics of the packed system are maintained.

there are three methods explicitly for setting external time:

see FIXME: external time master note

packed time and date format

/* time and date packed. dd day 1..31 5 bits mm month 1..12 4 bits yy year 00.99 7 bits --- 16 bits 15 0 dddddmmmmyyyyyyy ...delivered as payload to MSGDATE. hh hour 0..23 5 bits mm minute 0..59 6 bits ss second 0..59 6 bits --- 17 bits time doesn't fit in 16 bits (86400 enumerable seconds on a day) so a semantic trick is played: 15 0 10000hhhhhmmmmmm hour and minute, implied second=0 0000000000ssssss second in current minute ... delivered as payload to MSGTIME. for once-per-second time, ssssss is sent every second, updating the second, but only when second is not zero; once per minute hhhhhmmmmmm is sent which implicitly resets the second to zero. */

SRLED

this is a succinct system for visual system status using a single LED. there are two major modes of operation, blink and sequence. as are most of the SR libraries, the heart of the LED system is a state machine that must be invoked within loop() continuously. the state machine has extremely low latency (tens of microseconds).

both "modes" below are actually interoperable; see the method descriptions for details.

BLINK

the blink functions cause the LED to turn on and blink in a proscribed manner; on time, off time, and number of on/off blink cycles specified.

SEQUENCE

the sequence functions create a visual signalling space, where a discernable number of sequential blinks is used to signal status or events. the idea is that an interval is defined, typically one or two seconds and is split into a number of "time slots".

typical usage is to want to indicate various operating states of some remote and/or embedded machine that has no explicit information display. with an interval of 2.5 seconds divided into 8 slots, "normal" is indicated by a blink sequence of 1; in other words, the LED blinks one time in 2.5 seconds, within 1/8th of the interval. different operating states idle, busy, ready, error, waiting, etc) can be indicated by 2, 3, 4, 5, etc sequential blinks. given 8 time slots, three blinks followed by a pause (that is the remaining 5 blink times) is easily seen.

(in detail, each time slot is split between LED on and off times, with OFF being 3/5ths of the time slot and ON the rest.)

#include <SRResources.h> SRLED L; // instantiate void setup () { ... L.begin (13); // blink the built-in LED on pin 13 L.seqSetup (2000, 8); // 2 second loop, carved into 8 time slots L.seq (3); // blink 3 times per sequence } void loop () { ... L.LED(); // runs the LED state machine if (someEvent) L. blink (1000); // long blink If Something Happens }

SRLED conforms to my event loop strategy. the LED state machine takes microseconds to run, the load being effectively zero. the above example blinks the LED 3 times in two seconds. a schematic event (someEvent) in this example will cause the LED to remain on for one second, then return to the blink sequence. the LED() object manages all of the state changes.

void blink (n)causes the LED to blink on for n milliseconds, once.
void blink (n, m)causes the LED to blink on for n milliseconds, m times.
void blink (n, o, m)causes the LED to blink on for n milliseconds, off for o milliseconds, doing so m times.
void seqSetup (t, n)define a blink sequence whose length ist milliseconds, allocated into n time slots.
void seq (n)if a sequence has been defined with seqSetup(), causes the LED to blink on n times during the set interval.
void on()turns the LED on.
void off()turns the LED off.

SRButton

this provides simple access to any two-state digital sensor or electromechanical switch (momentary pushbutton, toggle, etc) attached between an input pin and ground. no pullup resistor is needed. debounce is automatic and built-in.

the most common use of a momentary switch is the initial depression event, and method pressed() provides that; however button() returns the current state of the switch for other uses.

SRButton is initialized by specifying the digital input pin, and optionally, the switch scan rate. if scan rate is not specified then 20 milliseconds is used, which works with nearly any switch.

the following example code will light the Arduino on-board LED for one second each time that a momentary switch attached to pin 8 is initially depressed.

#include <SRResources.h> SRLED L; // the LED SRButton PB; // the switch void setup () { PB.begin (8); // switch attached to pin 8 and ground L.begin (13); // the built-in LED on pin 13 } void loop () { if (PB.pressed()) { // if switch is pressed L.blink (1000); // LED on for one second } }
void begin (n)says that the switch is on pin n.
void begin (n, m)says that the switch is on pin n and to check the switch's physical state every m milliseconds.
bool pressed()returns true when the switch is first depressed, only.
int button()this returns the current state of the switch encoded as an integer. returns are:
 
0released
1just pressed
2just released
1is held depressed
(in fact, pressed() internally returns (button() == 1)).
void polarity (p)specifies switch polarity; use 1 for switch-press-connects-to-ground (default, aka negative logic) or 0 for switch-press-connects-to-5V (positive logic).

SRMessage.h

the SR Message system is a generalized system for sending, receiving, and parsing text-based messages. messages consist of a single command letter (A..Za..z) preceded by a decimal numeric argument in the range 0.65535. messages fit through all channels (Arduino IDE Serial Monitor, USB, radio, EEPROM) and are readable and typeable by humans.

message decomposition is done internal to the object, with the actual dispatching of (command-letter, numeric-argument) handled by user code.

the programming model is straightforward. characters from the input source are passed to the input method, which returns true when a complete message has been received. at that point the decomposed message is brought out in two methods, ID() and arg().

since messages are simple digits:letter text strings, they can be generated with anything that can assemble character strings. however the provided message-building methods make this easy, and support Flock and Peep radio addressing as well.

instantiate a object with

SRMessage M;

instantiation of the object does not allocate RAM nor resources. that is done by begin():

M.begin();

here is a typical command dispatcher. the function must accept two arguments: a character (as int) and an unsigned integer.

void dispatcher (int c, unsigned nnn) { switch (c) { case 'A': SOMEVARIABLE= nnn; break; // store a value case 'B': someFunction (nnn); break; // pass a value to a function case 'Z': digitalWrite (13, nnn); break; // manipulate a digital pin case 65535: Serial.println ("checksum error"); break; // (if checksums sent, see below) default: break; // wasn't a message we know after all } }

MESSAGE SOURCES

a message source is any serial character stream (or block and pointer or index). there needs to be one SRMessage object per source, to allow simultaneous, asynchronous streams. the object provides:

bool rxChar (char c);

given a character from the input stream, rxChar() processes it (numeric argument or message letter, etc) and returns true when a complete message has been received.

when rxChar() returns true, the following two methods return the message ID code (letter), and unsigned integer numeric argument, respectively:

int ID (); unsigned arg();

please note that ID() returns the message ID only the first time it is called; a side effect of invoking ID() is to clear the message ID for the next iteration. therefore if you need to reference it more than once make a copy first.

SRMessage assumes Wiring style loop() non-blocking coding strategy. when characters are available they are passed to the rxChar() method of the object. rxChar() returns true when a complete command has been rendered. in this case ID() and arg() are valid and can be passed to your dispatcher.

for example, this example program accepts messages from the Arduino USB debug console and executes them. note that it does not block; the average "load" per loop() iteration is nil:

SRMessage M; void setup () { Serial.begin (9600); M.begin(); } void loop() { if (Serial.available()) { // if a character available, if (M.rxChar (Serial.read()) { // pass it to rxChar dispatch (M.ID(), M.arg()); // .. which returns true when message assembled } } } void dispatch (int c, unsigned nnn) { switch (c) { case 'A': SOMEVARIABLE= nnn; break; // store a value case 'B': someFunction (nnn); break; // pass a value to a function case 'Z': digitalWrite (13, nnn); break; // manipulate a digital pin case 65535: Serial.println ("checksum error"); break; // (if checksums in use, see below) default: Serial.println ("only A, B, Z recognized"); break; // wasn't a message we know after all } }

CREATING MESSAGES

messages can be formatted for output using sprintf() or itoa() and standard C string and character functions.

sprintf (buff, "%u%c", nnn, c);

the methods below are useful due to their small size (they don't drag in overly-general library functions.)

the following methods build messages in a buffer to be displayed, transmitted, Serial.print()'d to USB, written to EEPROM, etc. message strings are built up message by message, each additional message appended to the string. message strings so produced are normal null-character-terminated C text strings.

messageBegin (char * buffer, unsigned buffSize);

messageBegin() specifies where the message is to be built/stored, and the buffer's maximum length. it is recommended that message strings be brief, which is usually the case anyway. Flock radio packet size is 32 characters maximum.

messageAdd (char c, unsigned nnn);

messageAdd() appends the message payload nnn and command letter c. if nnn is not zero it is ignored (a command letter without payload implies payload 0).

messageChar (char c);

messageChar() appends character c to the message. the most common use for it is to append a space character to increase human readability of the message string (at the expense of RAM to store it all).

MESSAGE-BUILDING EXAMPLES

char buff[32]; // typical use. M.messageBegin (buff, sizeof (buff)); // build messages here (results in "" in buff) M.messageAdd ('A', someVar); // command A, payload from memory M.messageAdd ('D', 0); // command D, zero payload... M.messageAdd ('Z', 1); Serial.println (buff); // will print out, literally: "1234A0D1Z" // adds a space for human readability, otherwise the same as above. M.messageBegin (buff, sizeof (buff)); M.messageAdd ('A', 1234); M.messageChar (' '); M.messageAdd ('D', 0); M.messageChar (' '); M.messageAdd ('Z', 1); M.messageChar (' '); Serial.println (buff); // will print out, literally: "1234A 0D 1Z"

SRCRC

this is a software implementation of the Dallas/Maxim Semiconductor iButton 8-bit CRC algorithm. more than adequate for most communication needs and computationally light. this was "borrowed from the net", it is not my code, i simply packaged it as an Arduino library.

from http://nongnu.org/avr-libc/user-manual/group__util__crc.html Optimized Dallas (now Maxim) iButton 8-bit CRC calculation. Polynomial: x^8 + x^5 + x^4 + 1 (0x8C) Initial value: 0x0 See http://www.maxim-ic.com/appnotes.cfm/appnote_number/27

instantiate with

SRCRC8 CRC;

and initialize it with

CRC.begin();

the object contains two methods

uint8_t crc8buff (uint8_t * p, unsigned count); uint8_t crc8 (uint8_t crc, uint8_t data);

the first form calculates the polynomial over the specified block of data using an initial seed of 0. the second form accomodates coding your own loop, passing the accumulated crc each time (initially 0).

SRPRNG

object SRPRNG contains three separate Marsaglia XORshift pseudo-random number generators, 8, 16 and 32 bits. these are well-known, high quality pseudorandom sequence generators. all Marsaglia XORshift PRNGs return the same sequence when given the same starting seed, a feature required for certain applications such as radio frequency-hop sequences.

all PRNGs have limitations and these are no exception. the 32-bit version is probably suitable for crypto purposes, but i have not (and will not) check this code. ymmv. please rtfm, or at least skim the wikipedia page. at least the 32-bit version is known to have one severe limitation of importance: it never returns 0, which is serious when the result is used to traverse a table; but taking the result (modulo anything < 2e31) solves that. but even the 32-bit PRNG here is vastly faster than the admittedly high-quality PRNG in the Arduino library.

instantiate with

SRPRNG RNG;

this instantiates the object but doesn't allocate resources for it. do that with

RNG.begin();

begin() also seeds the sequence with the 0'th permutation of the seed. refer to the code if you care what the initial seed value is. if you want to specify another seed, do so with the appropriate method below. do not use 0 as a seed value for any of these methods. the sequence returned by subsequent calls to each PRNG will be the same for a given seed.

RNG.xor32seed (unsigned long s); RNG.xor16seed (uint16_t s); RNG.xor8seed (uint8_t s);

subsequent calls to each PRNG will return the next number in the sequence. xor32 will never return a 0 as noted above. the period of xor8() is 256, so it does return 0 (necessary for table scrambling and such).

long unsigned foo= RNG.xor32 (); uint16_t bar= RNG.xor16 (); uint8_t blatz= RNG.xor8 ();

two additional methods are provided for the 32 and 16 bit versions that constrain the return value to be within bounds, as the stdlib random (min, max) does. this is the only form; the others (zero or one argument) are not available. this makes no sense for the 8-bit version, since a signed char is rather silly most of the time. these follow the "google" convention of min and max. bounds may be signed.

long foo= RNG.random32 (long minimum, long maximum); int RNG.random16 (int minimum, int maximum);

last, and least, notes on the 32-bit version, from the source file:

this code is courtesy WebDrake https://github.com/WebDrake/xorshift/blob/master/xorshift.c see also this dicussion http://www.javamex.com/tutorials/random_numbers/xorshift.shtml#.VNmzLy5gHLU i have added a seed function that multplies the original, example seed with a passed parameter. this allows both ends to begin a predictable sequence. Marsaglia PRNG never returns 0, a serious problem if the return value is traversing a table; but used modulo (N < 32) problem solved.

notes on the 8 and 16 bit versions, from the source file:

low-quality PRNGs are fine for non-crypto stuff like lighting LEDs and "randoming" table traversals etc. added seed functions. found at http://www.arklyffe.com/main/2010/08/29/xorshift-pseudorandom-number-generator/

SRLoopTally

SRLoop measures basic loop() statistics: loop() iterations per second, average loop() execution time, longest loop() execution time. it can optionally tally and display the total time spent in each loop per second.

the strategic goal is to help write code that does not block, that runs through loop() as fast as possible, in order to code to a state-machine, non-blocking, cooperative-tasking model so strongly implied (and desireable) in the Wiring setup()/loop() model.

this started out, like many things, as a test crock, but i found it to be invaluable, revealing a lot of how my code was running and giving insights into more time-efficient algorithms.

some of the original limitations still apply. it makes some simplistic assumptions: the cpu can't execute more than (unsigned) iterations per specified report time. that loop() execution is more or less 1..65535 per second, all of the above entirely reasonable for ATMEGA328, MEGA 2560 and it's ilk.

the first report is often nonsense, as it initializes after the first report.

that said, there are two ways to run loopTally. the first one provides only overall statistical insight into the main loop, and the second one adds to that a tally of time spent, per second, within each task-loop within the main loop. this requires the addition of a small function call after each LOOP.loop() to tell loopTally to tally up the time spent. an example follows below.

instantiate with

SRloopTally SRL;

there are two ways to begin loopTally, with and without the per loop tallying. the first (or only) argument is the number of seconds over which to calculate statistics. i usually use 10 seconds. up to 60 allowed. the second, optional, argument specifies the number of loop "spans" to tally time; the assumption is that you will have one tally slot per LOOP.loop() in your main loop. eg. five task loops, five tally slots.

SRL.begin (10); // 10-second report interval SRL.begin (10, 5); // 10 sec interval plus time tally 5 tasks

the background state machine is run by calling

unsigned x= SRL.tally();

continuously in loop(). it returns the longest iteration, in microseconds. somewhat useful for exceptionalizing crazy values ("if x > 100 SOMETHING BLOCKED").

at/after the specified report interval, tally() prints out one compact line:

#loop: 7582/s, 131uS avg, 2130uS max

which indicates 7582 iterations/second, 131 microsecond average loop() execution time, the longest single loop() iteration of 2.13mS. 'longest' is of low resolution because the goal is to identify grossly anomalous loops (eg. encountering some function or code that blocks for an extended period) but the average time will display milliseconds (mS) or microseconds (uS) as appropriate.

SRLoopTally per-task-loop tallying

it is now possible to accurately count the number of microseconds actually spent calling a task loop. when initialized with two arguments, the second arg enables this feature. that number of "time slots" are created (4 bytes each, plus slightly increased overhead).

the main loop is constructed in the usual way, with the addition of a call to tallyLoop() immediately following the code to be time-tallied. (metaphorically speaking, this is the "stop" press of the stopwatch. the "start" press is the top of the main loop, or the tallyLoop() in the task loop above it).

an example will hopefully illuminate:

void setup () { ... SRL.begin (10, 3); ... } void loop () { A.loop(); SRL.tallyLoop(); B.loop(); SRL.tallyLoop(); C.loop(); SRL.tallyLoop(); SRL.tally(); }

three tally slots are created by begin. A.loop() executes, and upon return, the first call to tallyLoop() accumulates the time, in microseconds, spent within it. B.loop() is then called, and the second call to tallyLoop() does the same in the second tally slot. C.loop() is the same then passes to SRL.tally(), which does it's thing and exits. the main loop() terminates normally, and is recalled as usual.

when SRL.tally() is called after the report invertal time has past (10 seconds in this example) to the report described above is appended a per-task-loop tally of the time spent in each task loop per second. for example:

#loop: 7582/s, 131uS avg, 2130uS max (201uS + 357uS + 1572uS)/sec

from the basic report you get a sense of the time "consumed" by all the tasks in your main loop, in aggregate. the extended report breaks this total down by task loop. in this example, it is C.loop() that is consuming the most time. this could be entirely reasonable for whatever the task it is. but by exercising code and hardware inputs etc you can watch time be consumed as a particular loop responds to events.




Website contents, unless otherwise specified, © 2023 by Tom Jennings is licensed under CC BY-SA 4.0