This is the multi-page printable view of this section. Click here to print...

Return to the regular view of this page

As of 2025-07-24

PAL_MOT-single

Sample using motion sensor pal
    In this act, after waking up from sleep, a few samples of acceleration data are acquired and the data is sent.

    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.

    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.

    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 perform on_sleep().