PAL_MOT-single
This act includes the following.
- Wireless packet transmission and reception
- Configuration via interactive mode - <STG_STD>
- State transition control using state machine - <SM_SIMPLE>
- Board operation using board behavior of <PAL_MOT> or
Explanation of the act
The flow is wake up → start acquiring acceleration sensor → wait for acceleration sensor FIFO interrupt → retrieve acceleration sensor data → wireless transmission → sleep.
Declaration section
Include
##include <TWELITE> // MWX library basics
##include <NWK_SIMPLE> // Network
##include <SM_SIMPLE> // State machine (state transition)
##include <STG_STD> // Interactive mode
/*** board selection (choose one) */
##define USE_PAL_MOT
//#define USE_CUE
// board dependend definitions.
##if defined(USE_PAL_MOT)
##define BRDN PAL_MOT
##define BRDC <PAL_MOT>
##elif defined(USE_CUE)
##define BRDN CUE
##define BRDC <CUE>
##endif
// include board support
##include BRDC
To support MOT PAL or TWELITE CUE, the include section is macro-based. Define either USE_PAL_MOT
or USE_CUE
.
If USE_PAL_MOT
is defined, the board behavior <PAL_MOT>
of the motion sensor pal is included.
State definition
enum class E_STATE : uint8_t {
INTERACTIVE = 255,
INIT = 0,
START_CAPTURE,
WAIT_CAPTURE,
REQUEST_TX,
WAIT_TX,
EXIT_NORMAL,
EXIT_FATAL
};
SM_SIMPLE<E_STATE> step;
To perform sequential processing in loop()
, states are defined, and the state machine step
is declared.
Sensor data storage
struct {
int32_t x_ave, y_ave, z_ave;
int32_t x_min, y_min, z_min;
int32_t x_max, y_max, z_max;
uint16_t n_seq;
uint8_t n_samples;
} sensor;
Data structure for storing sensor data.
setup()
/// load board and settings objects
auto&& brd = the_twelite.board.use BRDC (); // load board support
auto&& set = the_twelite.settings.use<STG_STD>(); // load save/load settings(interactive mode) support
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>(); // load network support
Registers board, settings, and network behavior objects.
Interactive mode
// settings: configure items
set << SETTINGS::appname("MOT");
set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
set << SETTINGS::lid_default(0x1); // set default LID
set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);
// if SET=LOW is detected, start with intaractive mode.
if (digitalRead(brd.PIN_SET) == PIN_STATE::LOW) {
set << SETTINGS::open_at_start();
brd.set_led(LED_TIMER::BLINK, 300); // slower blink
step.next(STATE::INTERACTIVE);
return;
}
// load settings
set.reload(); // load from EEPROM.
OPT_BITS = set.u32opt1(); // this value is not used in this example.
Initializes interactive mode.
First, configuration items are adjusted. Here, the menu title name SETTINGS::appname
, default application ID SETTINGS::appid_default
, default channel SETTINGS::ch_default
, default logical device ID SETTINGS::lid_default
, and hidden items .hide_items()
are set.
In this sample, if the SET pin is LOW at startup, it transitions to interactive mode. If the pin is confirmed LOW by digitalRead(brd.PIN_SET)
, SETTINGS::open_at_start()
is specified. This causes the interactive mode screen to be displayed promptly after exiting setup()
. Even when the screen is displayed, begin()
and loop()
are executed. In this sample, the state STATE::INTERACTIVE
is set so that no sleep or other actions are performed in loop()
.
Next, settings values are loaded. To read settings, .reload()
must always be executed. In this sample, option bit setting .u32opt1()
is read.
the_twelite
the_twelite << set;
the_twelite
is a class object that manages the system’s basic behavior. This object performs various initializations such as application ID and channel in setup()
.
Here, part of the interactive mode settings is applied.
If you want to change the settings applied by interactive mode to other settings, overwrite the settings subsequently.
the_twelite << set;// Interactive mode
the_twelite << twenet::channel(19); // overwrite channel to 19
NWK_SIMPLE
object
nwk << set;
Settings are also applied to the network behavior object. Logical device ID (LID) and retransmission settings of interactive mode are applied.
Other hardware initialization
brd.set_led(LED_TIMER::BLINK, 100);
LED blink settings and others are performed.
begin()
void begin() {
auto&& set = the_twelite.settings.use<STG_STD>();
if (!set.is_screen_opened()) {
// sleep immediately, waiting for the first capture.
sleepNow();
}
}
Called after setup()
finishes. Here, the initial sleep is executed. However, if the interactive mode screen is displayed, sleep is not performed.
wakeup()
void wakeup() {
Serial << crlf << "--- PAL_MOT(OneShot):"
<< FOURCHARS << " wake up ---" << crlf;
eState = E_STATE::INIT;
}
After waking up, the state variable eState
is set to the initial state INIT. Then loop()
is executed.
loop()
void loop() {
auto&& brd = the_twelite.board.use<PAL_MOT>();
do {
switch(step.state()) {
case STATE::INTERACTIVE:
break;
...
} while(step.b_more_loop());
}
The basic structure of loop()
uses the <SM_STATE>
state machine state
and control with switch … case statements. The initial state is STATE::INIT
or STATE::INTERACTIVE
.
STATE::INTERACTIVE
State when the interactive mode screen is displayed. Does nothing. Serial input/output is used by interactive mode on this screen.
STATE::INIT
Initial state INIT.
case STATE::INIT:
brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).
memset(&sensor, 0, sizeof(sensor)); // clear sensor data
step.next(STATE::START_CAPTURE);
break;
In state INIT, initialization (clearing the queue for storing results) and initialization of the data structure for storing results are performed. Transition to STATE::START_CAPTURE. After this transition, the while loop is executed again.
STATE::CAPTURE
case STATE::START_CAPTURE:
brd.sns_MC3630.begin(
// 400Hz, +/-4G range, get four samples and will average them.
SnsMC3630::Settings(
SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, N_SAMPLES));
step.set_timeout(100);
step.next(STATE::WAIT_CAPTURE);
break;
In state START_CAPTURE
, FIFO acquisition of the MC3630 sensor is started. Here, FIFO interrupt occurs when 4 samples are acquired at 400Hz.
Timeout for exception handling is set, and transition to the next state STATE::WAIT_CAPTURE
.
STATE::WAIT_CAPTURE
case STATE::WAIT_CAPTURE:
if (brd.sns_MC3630.available()) {
brd.sns_MC3630.end(); // stop now!
In state WAIT_CAPTURE
, it waits for FIFO interrupt. When interrupt occurs and data is stored in the queue for results, sns_MC3630.available()
becomes true
. sns_MC3630.end()
is called to stop processing.
sensor.n_samples = brd.sns_MC3630.get_que().size();
if (sensor.n_samples) sensor.n_seq = brd.sns_MC3630.get_que()[0].get_t();
...
The number of samples and the sequence number of the samples are obtained.
// get all samples and average them.
for (auto&& v: brd.sns_MC3630.get_que()) {
sensor.x_ave += v.x;
sensor.y_ave += v.y;
sensor.z_ave += v.z;
}
if (sensor.n_samples == N_SAMPLES) {
// if N_SAMPLES == 2^n, division is much faster.
sensor.x_ave /= N_SAMPLES;
sensor.y_ave /= N_SAMPLES;
sensor.z_ave /= N_SAMPLES;
}
...
All sample data is read and averaged.
// can also be:
// int32_t x_max = -999999, x_min = 999999;
// for (auto&& v: brd.sns_MC3630.get_que()) {
// if (v.x >= x_max) x_max = v.x;
// if (v.y <= x_min) x_min = v.x;
// ...
// }
auto&& x_minmax = std::minmax_element(
get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
get_axis_x_iter(brd.sns_MC3630.get_que().end()));
sensor.x_min = *x_minmax.first;
sensor.x_max = *x_minmax.second;
...
Here, the min and max of each axis are obtained using iterators corresponding to the acquired samples.
std::minmax_element
from the C++ Standard Template Library algorithm is introduced as an example, but you can also find max and min using a for loop as in the comment.
if (brd.sns_MC3630.available()) {
...
brd.sns_MC3630.get_que().clear(); // clean up the queue
step.next(STATE::REQUEST_TX); // next state
} else if (step.is_timeout()) {
Serial << crlf << "!!!FATAL: SENSOR CAPTURE TIMEOUT.";
step.next(STATE::EXIT_FATAL);
}
break;
.sns_MC3630.get_que().clear()
is called to clear the data in the queue. Without calling this, subsequent sample acquisition is not possible. Then it transitions to STATE::REQUEST_TX
.
.is_timeout()
checks for timeout. On timeout, it transitions to STATE::EXIT_FATAL
as an error.
STATE::REQUEST_TX
case STATE::REQUEST_TX:
if (TxReq()) {
step.set_timeout(100);
step.clear_flag();
step.next(STATE::WAIT_TX);
} else {
Serial << crlf << "!!!FATAL: TX REQUEST FAILS.";
step.next(STATE::EXIT_FATAL);
}
break;
In state REQUEST_TX
, the locally defined function TxReq()
is called to process the obtained sensor data and generate/send the transmission packet. Transmission requests may fail due to the transmission queue status, etc. If the transmission request succeeds, TxReq()
returns true, but transmission is not yet performed. Transmission completion calls the on_tx_comp()
callback.
Also, .clear_flag()
clears the flag used to indicate transmission completion. Timeout is also set simultaneously.
E_STATE::WAIT_TX
case STATE::WAIT_TX:
if (step.is_flag_ready()) {
step.next(STATE::EXIT_NORMAL);
}
if (step.is_timeout()) {
Serial << crlf << "!!!FATAL: TX TIMEOUT.";
step.next(STATE::EXIT_FATAL);
}
break;
In state STATE::WAIT_TX
, it waits for wireless packet transmission completion. The flag is set by the on_tx_comp()
callback function, and .is_flag_ready()
becomes true after being set.
E_STATE::EXIT_NORMAL
, E_STATE::EXIT_FATAL
case STATE::EXIT_NORMAL:
sleepNow();
break;
case STATE::EXIT_FATAL:
Serial << flush;
the_twelite.reset_system();
break;
When the series of operations is completed, it transitions to state STATE::EXIT_NORMAL
and calls the locally defined function sleepNow()
to execute sleep. If an error is detected, it transitions to state STATE::EXIT_FATAL
and performs a system reset.
MWX_APIRET TxReq()
MWX_APIRET TxReq() {
auto&& brd = the_twelite.board.use<PAL_MOT>();
MWX_APIRET ret = false;
// prepare tx packet
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
// set tx packet behavior
pkt << tx_addr(0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x1 send two times in total)
<< tx_packet_delay(0, 0, 2); // send packet w/ delay
// prepare packet (first)
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(FOURCHARS, 4) // just to see packet identification, you can design in any.
, uint16_t(sensor.n_seq)
, uint8_t(sensor.n_samples)
, uint16_t(sensor.x_ave)
, uint16_t(sensor.y_ave)
, uint16_t(sensor.z_ave)
, uint16_t(sensor.x_min)
, uint16_t(sensor.y_min)
, uint16_t(sensor.z_min)
, uint16_t(sensor.x_max)
, uint16_t(sensor.y_max)
, uint16_t(sensor.z_max)
);
// perform transmit
ret = pkt.transmit();
if (ret) {
Serial << "..txreq(" << int(ret.get_value()) << ')';
}
}
return ret;
}
Finally, packet generation and transmission request are performed. The packet includes sequence number, sample count, XYZ average values, XYZ minimum sample values, and XYZ maximum sample values.
sleepNow()
void sleepNow() {
Serial << crlf << "..sleeping now.." << crlf;
Serial.flush();
step.on_sleep(false); // reset state machine.
the_twelite.sleep(3000, false); // set longer sleep (PAL must wakeup less than 60sec.)
}
Sleep procedure.
- The serial port calls
Serial.flush()
before sleep to output everything. - The
<SM_SIMPLE>
state machine must performon_sleep()
.