このページのみ全てのページ
GitBook提供
134ページのPDFを生成できませんでした、100で生成が停止しました。
さらに50ページで拡張
1 / 100

latest_en

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Classes

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Functions

Functions

System Functions

System functions (time, random numbers)

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

The MWX Library

Mono Wireless C++ Library for TWELITE.

Please refer to the Handling of Materials section. If you have any questions or concerns, please contact our support desk.

This page contains information that is still under development. The contents of this page may not yet be reflected in the public source code.

The MWX library is intended to simplify the code notation of the TWELITE radio module. a program created with MWX is called an act. There are two types of acts: descriptions by loops and descriptions by events (called BEHAVIOR).

This page introduces the main features of ACT.

  • Description by loop (setup(), loop()). Suitable for writing small-scale functions.

#include <TWELITE>
const uint8_t PIN_LED = 5;

void setup() {
  pinMode(PIN_LED, OUTPUT);
}

void loop() {
  if (TickTimer.available()) {
    uint32 t_now = millis();
    
    // blink LED every 1024ms
    digitalWrite(PIN_LED, (t_now >> 10) & 1 ? HIGH : LOW);
  }
}	
  • Event-driven application description. It is possible to define various event and interrupt handlers, as well as a state machine within a class that is suitable for describing complex behavior of the application, and write code with good visibility. This description is called BEHAVIOR.

// myApp.hpp
...
class myApp : MWX_APPDEFS_CRTP(myApp) {
...
  void loop() {
    // main loop
  }
  
  void receive(mwx::packet_rx& rx) {
    // on receive
  }
};

// myApp.cpp
...
MWX_DIO_EVENT(12, uint32_t arg) {
		// on event from DIO12
}
  • Simplify peripheral procedures. Defines class objects to handle commonly used UART, I2C, SPI, ADC, DIO, timer, and pulse counter.

void loop() {
  while(Serial.available() { 
    auto x = Serial.read(); ... } // serial message
  if (Analogue.available() {
    auto x = Analogue.read(...); } // adc values
  if (Buttons.available() { 
    Buttons.read(...); } // DIO changes
  if (the_twelite.receiver.available()) { 
    auto&& rx = the_twelite.receiver.read(); } // on rx packet
}
  • A simple relay network is defined. This relay network is implemented in the same way as the TWELITE standard application, and is characterized by the fact that individual addresses are managed by 8-bit logical IDs, and that wireless packets can be sent to the network immediately after power-on because there is no communication to build the network.

#include <TWELITE>
#include <NWK_SIMPLE>

void setup() {
  ...
  auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
	nwksmpl << NWK_SIMPLE::logical_id(0xFE) 
	           // set Logical ID. (0xFE means a child device with no ID)
	        << NWK_SIMPLE::repeat_max(3);
	           // can repeat a packet up to three times.
}

void loop() {
  ...
  vTransmit();
  ...
}

void vTransmit() {
  if (auto&& pkt =
    the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet(); 
  pkt << tx_addr(0x00)  // to parent 
	  	<< tx_retry(0x3); // set retry
	
	pack_bytes(pkt.get_payload() // prepare payload data
	    , uint8_t(0x01)
	    , uint16_t(analogRead(PIN_ANALOGUE::A1))
	    , uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)));
	
	pkt.transmit(); // transmit!
}

Although it does not allow for packet intercommunication with TWELITE standard applications, it does allow for more flexibility in the following areas

  • The logical ID is the same in that 0 is the parent device, but since 0x01..0xEF can be used as the child device address, the number of identifications can be 200 or more.

  • As a rule, the maximum number of times a packet can be relayed has been set to 64 times. (* If a packet returns after a certain period of time due to a detour, the duplicate packet management table will be cleared and the packet may have to be relayed again even if it has already been relayed. (Be careful when setting a large number of relays.

  • Board definition for PAL and MONOSTICK. Easy handling of sensors etc. on the board.

#include <TWELITE>
#include <PAL_AMB> // include the board support of PAL_AMB

void setup() {
	auto&& brd = the_twelite.board.use<PAL_AMB>(); // use PAL AMB
	uint8_t u8dip = brd.get_DIP_SW();   // check DIP s/w status
	brd.set_led(LED_TIMER::BLINK, 100); // LED switchs on/off every 100ms
	...
	
	// start capture of sensors
	brd.sns_SHTC3.begin();
}

void loop() {
	if (TickTime.available()) { // check every ms
		auto&& brd = the_twelite.board.use<PAL_AMB>();

		if (brd.sns_LTR308ALS.available()) {
		  Serial << brd.sns_SHTC3..get_temp();
		} else {
		  // notify sensor that 1ms passed.
			brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
		}
	}
}

License

License

Warranty and License

The Mono Wireless Software License Agreement (MW-SLA) applies to anything in this package that is not specifically described in the license.

This document is also handled under MW-SLA as a part of this library package part.

This software is not officially supported by Mono Wireless Ltd. Please note that we may not be able to respond to your inquiries. Please understand beforehand.

In response to reports of defects, Mono-Wireless, Inc. does not promise to fix or improve the problem.

There are also cases where the software may not work depending on the customer's environment, such as the package installed.

/* Copyright (C) 2019-2022 Mono Wireless Inc. All Rights Reserved.
   Released under MW-SLA-*J,*E (MONO WIRELESS SOFTWARE LICENSE
   AGREEMENT) */

Terms

The following is a supplement to the terminology used in this document.

The explanation of terms may not be in accordance with the definitions provided in standards and other documents.

General terms

SDK (TWELITE SDK, MWSDK)

Software Development Environment

The SDK for software development of TWELITE wireless microcontrollers is called TWELITE SDK (or MWSDK).

IEEE802.15.4

This is the radio standard used by the TWELITE radio module; as long as you use the MWX library, you do not need to be aware of the details of the radio standard.

packet

The smallest unit of communication in wireless communications.

The maximum amount varies depending on the communication method and settings during communication, but in the MWX library standard communication <NWK_SIMPLE>, the amount of data a user can send in one packet is 90 bytes.

payload (of a packet)

It refers to the body of data contained in a wireless packet.

node

It refers to a radio station in a wireless network.

MWX library specific terms

ACT

A program created using this library. This refers to its source code or the program that runs.

BEHAVIOR

A program in the form of an event, among other ACTs. The source code or the program that runs it.

BEHAVIORs are described by a single class definition, which describes callback functions from TWENET, events, and interrupt processing, all in one place. There are three types of behaviors available in the MWX library:

  • Application BEHAVIOR: A class defined by the user that describes the application in an event-driven manner.

  • Board BEHAVIOR: A class to simplify the use of the functionality of the board that implements the TWELITE radio module.

  • Network BEHAVIOR: A class for simplifying procedures in wireless networks.

Behavior names are enclosed in < >. For example, the behavior name for a simple relay network is <NWK_SIMPLE>.

Class objects

In the description of this library, objects that are declared globally from the beginning in the library are referred to as class objects: Serial, Wire, etc. These class objects can be used without any procedure or by performing the start procedure.

Class objects that consume relatively large amounts of memory allocate memory along with the initialization parameters during the initialization procedure (.setup() or .begin() method).

C++ terms

This is a general term, and assumes knowledge of the C language.

C++

The C++ language.

The MWX library is written in C++ and C.

C++11

One of the versions of the C++ standard, meaning C++ as of 2011, which was standardized by ISO in 2011. It has been greatly enhanced since its predecessor, C++03. Newer versions such as C++14 and C++17 are available.

The MWX library is implemented using features and syntax added in C++11, and MWSDK compiler support is limited to C++11.

Class

A collection of procedures that focus on some data in a single place. A structure contains the procedures for handling that structure. It actually develops into a much deeper topic, but please refer to technical books.

In C++, the keywords struct and class are essentially the same thing, and a class is a class regardless of which keyword it is declared with.

struct myhello {
  int _i;
  void say_hello() { printf("hello %d\n", _i); }
};

If the above class definition was also done in C, for example, it would look like the following:

typedef struct _c_myhello {
  int _i;
  void (*pf_say_hello)(struct _c_myhello *);
} c_myhello;

void say_hello(c_myhello*p) { p->pf_say_hello(); }
void init_c_my_hello(c_myhello*p) {
  p->pf_say_hello = say_hello;
}

Wrapper class

It is a class that includes existing C libraries and their internal structures, and adds C++ specific functionality to make it easier to use. In some cases, the description "wrapped ~structure" is used in the explanation.

The MWX library is a combination of wrapper classes from TWENET's C library and a set of newly implemented classes.

Method, Member function

A function that is defined in a class and is associated with the class.

struct myhello {
  int _i;
  void say_hello() { printf("hello %d\n", _i); } //Method
};

Object (instance)

A class is materialized (allocated memory).

void func() {
    myhello obj_hello; // obj_hello is an object of myhello class
    obj_hello._i = 10;
    obj_hello.say_hello();
}

In this explanation, object and instance are treated as having the same meaning.

Constructor

Initialization procedure at the time of object creation.

struct myhello {
  int _i;
  void say_hello() { printf("hello %d\n", _i); }
  
  myhello(int i = 0) : _i(i) {} // constructor
};

void my_main() {
  myhello helo(10); // Here, the constructor is called and set to _i=10
}

Destructor

This is the procedure when the object is destroyed, paired with the constructor.struct myhello {

  int _i;
  void say_hello() { printf("hello! %d\n", _i); }
  
  myhello(int i = 0) : _i(i) {} // constructor
  ~myhello() {
    printf("good bye! %d\n", _i);
  } // destructor
};

abstract class

In C++, polymorphism is achieved by virtual classes. Specifically, a class that defines a pure virtual function specified by the virtual keyword.

struct Base {
  virtual void say_hello() = 0;
};

struct DeriveEng : public Base {
  void say_hello() { printf("Hello!"); }
};

struct DeriveJpn : public Base {
  void say_hello() { printf("Kontiwa!"); }
};

The MWX library does not use virtual functions due to compiler limitations and performance reasons. We use a differnet approach to achieve polymorphism.

Scope

In the C/C++ language, think of it as a scope enclosed in { }. The objects created in this scope are destroyed when they leave the scope. The destructor is called at this time.

The following is an explicitly scoped version of helo2, which is discarded after line 8 and the destructor is called.

void my_main() {
  myhello helo1(1);
  helo1.say_hello();
  
  {
    myhello helo2(2);
    helo2.say_hello();
  }
}

// hello! 1
// hello! 2
// good bye! 2
// good bye! 1

MThe MWX library uses the following notation. Here, the validity period of an object declared within the conditional expression of an if statement (older C languages such as C89 do not allow declarations in such a place) is within {} of the if statement.

struct myhello {
  int _i;
  void say_hello() { printf("hello! %d\n", _i); }
  operator bool() { return true; } // Operators for judgment in if()
  
  myhello(int i = 0) : _i(i) {} // constructor
  ~myhello() { printf("good bye! %d\n", _i); } // constructor
};

// Function to create a myhello object (generator)
myhello gen_greeting() { return my_hello(); }

void my_main() {
  if (myhello x = gen_greeting()) {
    // The object x in myhello is valid during the if statement
    x.say_hello();
  }
  // object x is destroyed when exiting the if minute
}

For example, a two-wire serial bus is a procedure where there is a start and end procedure and the bus is manipulated by an object only during that time. After the object is created, if the bus is properly connected, the true clause of the if statement is executed and the bus is written or read by the created object. When the bus read/write operation is completed, the if statement is exited and the destructor is called to terminate the bus usage.

const uint8_t DEV_ADDR = 0x70;
if (auto&& wrt = Wire.get_writer(DEV_ADDR)) { //バスの初期化、接続判定
	wrt(SHTC3_TRIG_H); // 書き出し
	wrt(SHTC3_TRIG_L);
} // バスの利用終了手続き

Namespace

The namespace are actively used in C++ to avoid duplication of definition names. To access a definition in a namespace, use::.

namespace MY_NAME { // 名Namespace declaration
  const uint8_t MYVAL1 = 0x00;
}

...
void my_main() {
  uint8_t i = MY_NAME::MYVAL1; // reference of MY_NAME
}

Template

Think of a template as an extension of a C macro.

template <typename T, int N>
class myary {
  T _buf[N];
public:
  myary() : _buf{} {}
  T operator [] (int i) { return _buf[i % N]; }
};

myary<int, 10> a1; // Array of type int with 10 elements
myary<char, 128> a2; // Array of char type with 128 elements

This example defines a simple array, where T and N are template parameters, where T is the type name and N is a number, and defines an array class of type T with N elements.

nullptr

In C++11, NULL pointers are now written as nullptr.

reference type

In C++, reference types are available. This is similar to access by pointer, but with the restriction that it must refer to an object.

For functions with pass-by-reference parameters like the one below, the value of i can be rewritten in incr().

void incr(int& lhs, int rhs) { lhs += rhs; }

void my_main() {
  int i = 10; j = 20;
  incr(i, j);
}

In the example of the template explanation, the return type of operator[] is changed to T&. By doing so, it becomes possible to perform assignment operations directly on the data inside the array, such as a[0]=1.

template <typename T, int N>
class myary {
  T _buf[N];
public:
  myary() : _buf{} {}
  T& operator [] (int i) { return _buf[i % N]; }
};

myary<int, 10> a1;
void my_main() {
  a1[0] = 1;
  a1[1] = 2;
}

As a rule, the programming interface of the MWX library does not use pointer types, but reference types.

type inference

C++11 introduces the auto keyword for type inference. This allows the compiler to infer the type of an object from its initialization description, thus omitting the need to specify the specific type name. This is effective in cases where class names using template are very long.

In most of the explanations, auto&&, which is called universal reference, is used. Universal references can be written here without being aware of passing references.

auto&& p = std::make_pair("HELLO", 5);
       // const char* と int のペア std::pair

container

A class for storing multiple objects of a specific data type such as arrays is called a container. An array class such as myary mentioned in the template example is also called a container.

The MWX library provides an array class smplbuf and a FIFO queue class smplque.

Iterator, .begin(), .end()

A pointer in C can be thought of as a way to access a contiguous set of memory elements in a continuous manner from the beginning to the end. The simplest implementation of a FIFO queue is a ring buffer, but there is no memory continuity. Even such a data structure can be described in the same way as a pointer using an iterator.

The methods .begin() and .end() are used to get the iterator. The iterator that points to the beginning of the container is obtained with .begin(). The iterator that points to the next to the end is obtained with .end(). The reason for using the next to the end instead of the end is for the clarity of the loop description in the for and while statements, and to handle the case where the number of elements stored in the container is zero.

my_queue que; // my_queue is a class of queue

auto&& p = que.begin();
auto&& e = que.end();

while(p != e) {
  some_process(*p);
  ++p;
}

In the above, some_process() is applied to each element of the que using the iterator p. p is incremented by the ++ operator as an iterator that points to the next element. Even if the container has a data structure that cannot be described by a pointer, it can be processed in the same way as using a pointer.

Since .end() indicates the next to the end, the end decision of the while statement is as simple as (p ! = e), which is concise. If there is no element in the queue, .begin() returns the same iterator as .end(). If there are no elements in the queue, .begin() will return the same iterator as .end(), which is the next iterator after the iterator for the unstored elements.

For a contiguous container in memory, its iterator will usually be a normal pointer. It is not expected to be a large overhead during its operation.

C++ standard library

The C++ standard library includes the STL (Standard Template Library), which is part of the MWX library.

Due to the limitations of the C/C++ compiler for TWELITE, only a few features are available.

Algorithm

In C, for example, the process of finding the maximum or minimum value is written separately depending on the type. In C++, you can use templates, iterators, etc. to describe such operations independently of their types. This is called an algorithm.

// Return the iterator with the maximum value with any iterator as a parameter.
template <class Iter>
Iter find_max(Iter b, Iter e) {
  Iter m = b; ++b;
  while(b != e) {
    if (*b > *m) { m = b; }
    ++b;
  }
  return m;
}

For example, the algorithm to find the maximum value as shown above. This algorithm is type-independent. (It is called generic programming.)

#include <algorithm>

auto&& minmax = std::minmax_element( // Algorithm for obtaining the maximum minimum
  que.begin(), que.end());

auto&& min_val = *minmax.first;
auto&& max_val = *minmax.second;

Here we specify an iterator for que and apply the algorithm std::minmax_elenet to obtain its maximum and minimum. std::minmax_elemet is defined in the C++ standard library. Its return value is std::pair, which combines any two values. The algorithm calculates the maximum and minimum values if the elements indicated by the iterator can be compared with each other using operators such as <,>,==. The return type is also derived from the iterator type.

Install and Build

Install and Build

In order to write an application using the MWX library (called ACT in this document) and run it, you need to set up a development environment.

  • Enviromnents for TWELITE STAGE SDK

  • Installing TWELITE STAGE SDK

  • (Optional) Installing Visual Stdio code

  • Building ACT

  • Create a new project

  • Makefile

  • Use part of MWX code at another platoform(e.g. stdio environment)

Environment (OS, etc.)

DESC

In order to create a development environment, you need to install the software and agree to the license agreement. In addition, you may need to configure security settings on your PC or workstation.

  • Although we take great care when distributing the software, we ask that you also check for viruses.

  • Please check with the administrator of your environment regarding your security approach and operations (e.g., whether or not to install external applications).

In addition, the installation and operation of the development environment may require the intervention and configuration of the operating system (e.g., running an application whose developer is unknown; many of the development environments and tools introduced here do not have a built-in mechanism to prove the origin of the application) Please refer to the general information on how to configure.

To write an application using the MWX library, you will need the following:

  • MWSDK(Software Development Environment)

  • An editor for development (Microsoft's VisualStudio Code will be introduced)

Windows10,11

Compiler toolchains, etc., are relatively less dependent on the environment, so they can be expected to work in many environments, but we recommend the Windows 10 version that is currently supported. If your system does not work due to differences in the operating environment, please prepare a separate environment by referring to the environment we have confirmed.

The following is the version we are using for development.

  • Windows11 21H2 (Visual Studio 2019)

  • FTDI driver must be running (to run MONOSTICK, TWELITE R)

Linux

Compiler toolchains, for example, are relatively less dependent on the environment, so they can be expected to work in many environments, but we recommend distributions that are currently supported. If your system does not work due to differences in the operating environment, please prepare a separate environment by referring to the environment that we have confirmed.

The following is the version we are using for development.

  • Ubuntu 18.04 LTS 64bit

  • Ubuntu 20.04 LTS 64bit

32bit systems are not supported.

macOS

Compiler toolchains, for example, are relatively less dependent on the environment, so they can be expected to work in many environments, but we recommend distributions that are currently supported. If your system does not work due to differences in the operating environment, please prepare a separate environment by referring to the environment that we have confirmed.

The following is the version we are using for development.

  • macOS 10.14 (Mojave, Intel)

  • macOS 12.4 (Monterey, Apple Silicon)

About Visual Studio Code and other development environments

For information on how to run and use the development environment, please refer to the information of its developer or community.

The use of Visual Studio Code (VSCode) is recommended for efficient code writing.

The MWX library requires more PC resources to interpret the code on VSCode, etc., since there are more header files to read compared to C language development.

Differences by build environment

The build results for Linux/macOS are different from the results for Windows 10. As far as we know, there is no difference in the normal operation, but the binary size tends to be a few percent larger, especially since the LTO of gcc is disabled.

If you have any doubts about the operation, be sure to run the build on Windows 10 and confirm that it reproduces before contacting us.

Creating a new project

Creating a new project

To create a new project, copy the folder of an already existing sample act with a different name and edit the file name.

The destination folder does not have to be a folder under the MWSDK. However, the folder name must not contain any whitespace characters or Japanese names.

The file structure of the project is as follows (we'll use PingPong as an example)

Act_samples
  +-PingPong
    +-PingPong.cpp   : ACT file
    +-build          : build dir
    +-.vscode        : setting fils for VSCode    

Copy this PingPong folder to another location (but without Japanese characters or spaces in the folder name).

SomeDir
  +-AlphaBravo
    +-PingPong.cpp -> AplhaBravo.cpp (Note: Changed file name)
    +-build          : build dir
    +-.vscode        : setting files for VSCode

The only thing you need to edit is the file name PingPong.cpp. Change it to AlphaBravo.cpp, the same as the folder name.

Run build\build-BLUE.cmd and if a BIN file is generated, it' is done (Windows 10).

On Linux/macOS, run make TWELITE=BLUE to see if the build succeeds.

Edit Build Definition

To add files to be built, edit build/Makefile. The .c .cpp files directly under the project will be added automatically, but other files will need to be edited.

See Makefile Description for how to edit the file.。

Configuration for VSCode

If you use VSCode, edit the definition under .vscode as necessary.

Most of the examples included in the TWELITE STAGE SDK are as follows

  • The source code for the TWELITE STAGE SDK library cites ${env:MWSDK_TWENET_LIBSRC}/include/** ${env:MWSDK_TWENET_LIBSRC}/src/**. This environment variable MWSDK_TWENET_LIBSRC is automatically set when the project is opened in VSCode from the TWELITE STAGE app.

  • For the build task, no additional options such as -D are set by default.

The design policy

This section describes the specifications, limitations, notes in this document, and design memos for the C++ language used in the MWX library.

This page is intended for use in situations where you need to refer to the library resource code to understand how the library works or to modify it. It assumes a higher level of knowledge of the C++ language compared to using the library.

Design Policy

  • The application loop description is intended to be similar to the commonly used API system, but the implementation should be tailored to the characteristics of TWELITE.

  • TWENET is an event-driven code description, and it should be classed so that it can be handled. The above classifications will encapsulate the behavior of the application.

  • Event-driven and loop descriptions should be able to coexist.

  • Simplify procedures by classifying typical peripherals. Make them accessible by loop descriptions whenever possible.

  • Simplify the procedures for using the boards we sell, such as MONOSTICK/PAL, by creating classes. (For example, to automate the use of an external watchdog timer.

  • Application classes and board classes should be made available through a unified procedure, introducing the idea of polymorphism. (For example, to load application classes with several behaviors at startup, and to avoid defining the connection code of the TWENET C library each time).

  • There are no restrictions on the use of C++ functionality. For example, it provides a means to simplify typical procedures such as packet construction and decomposition, which are complicated in handling wireless packets.

  • The operator -> should be avoided as much as possible, and the API should be based on reference types in principle.

As we are working on the implementation in a limited time, the design does not cover all the details, but if you have any questions about the design or implementation, please contact our support.

About the C++ Compiler

Version

gcc version 4.7.4

C++ standard

C++11 (For compiler support status, please refer to the general information.)

  • https://gcc.gnu.org/gcc-4.7/cxx0x_status.html

  • https://cpprefjp.github.io/implementation-status.html

C++ limitations

※ This is a description of what we know.

  • You can allocate memory with the new and new[] operators, but you cannot destroy the allocated memory; most C++ libraries that allocate memory dynamically are virtually unusable. It is used for objects that are created only once and not destroyed after that.

  • The constructor of the global object is not called. Note: If necessary, you can initialize the constructor call by using the initialization function (setup()) as shown in (new ((void*)&obj_global) class_foo();).

  • Exception cannot be used.

  • Unable to use virtual function.

Design Memo

This section contains information that will help you understand the code when referring to the MWX library code.

Current implementation

Due to the limited time available for implementation, some of the details may not be sufficiently developed. For example, const is not fully taken into account in many classes.

namespace

We have the following policy for namespaces.

  • In principle, definitions are placed in a common namespace mwx.

  • We want to be able to use namespaces without identifiers, but we want to require identifiers for some definitions.

  • Class names should be relatively long, and those used by users should be defined as aliases.

Classes, Functions, and constants are defined within the namespace of mwx names (more precisely, mwx::L1 enclosed in inline namespace L1), with a few exceptions. inline namespace is specified so that definitions that require the specification of mwx:: can coexist with those that do not. The reason why inline namespace is specified is to allow definitions that require the specification of mwx:: to coexist with those that do not.

Most of the definitions do not require namespace names to be specified by using namespace. These specifications are made in using_mwx_def.hpp in the library.

// at some header file.
namespace mwx {
  inline namespace L1 {
    class foobar {
      // class definition...
    };
  }
}

// at using_mwx_def.hpp
using namespace mwx::L1; // Definitions in mwx::L1 can be accessed without mwx::.
                         // But mwx::L2 needs mwx::.

Exceptionally, relatively short names can be specified as mwx::crlf, mwx::flush. These are placed in the inline namespace mwx::L2; using namespace mwx::L2; will allow them to be used without specifying the namespace name.

Also, some class names have a using specification.

The std::make_pair used in the MWX library is specified using.

CRTP(Curiously recursive template patterns.)

Since virtual functions (virtual) and run-time type information (RTTI) are not available, and even if they were available, they would be difficult to perform, CRTP (Curiously recurring template pattern) is used as an alternative design method. CRTP is a template pattern for calling methods of a child class from the parent class from which it is inherited.

The following example shows how to implement an interface called interface() in a Derived class that inherits from Base, and calls the Derived::print() method from Base.

template <class T>
class Base {
public:
  void intrface() {
    T* derived = static_cast<T*>(this);
    derived->prt();
  }
};

class Derived : public class Base<Derived> {
  void prt() {
     // print message here!
     my_print("foo");
  }
}

The following are the main classes used in the MWX library.

  • Basic parts of event processingmwx::appdefs_crtp

  • state machine public mwx::processev_crtp

  • stream mwx::stream

Virtualization with CRTP

In the CRTP class, the class from which it inherits is different for each instance. For this reason, it is not possible to cast it to a parent class and treat it as a member of the same family, nor is it possible to use advanced polymorphism such as virtual functions or RTTI (runtime type information).

The following is an example of implementing the above CRTP example with a virtual function: CRTP cannot manage instances together in the same array as in Base* b[2].

class Base {
	virtual void prt() = 0;
public:
	void intrface() { prt(); }
};

class Derived1 : public Base {
	void prt() { my_print("foo"); }
};

class Derived2 : public Base {
	void prt() { my_print("bar"); }
};

Derived1 d1;
Derived2 d2;
Base* b[2] = { &d1, &d2 };

void tst() {
	for (auto&& x : b) { x->intrface(); }
}

The MWX library solves this problem by defining a dedicated class for storing class instances of CRTP and defining a similar interface to this class. An example code is given below.

class VBase {
public:
	void* p_inst;
	void (*pf_intrface)(void* p);

public:
	void intrface() {
		if (p_inst != nullptr) {
			pf_intrface(p_inst);
		}
	}
};

template <class T>
class Base {
	friend class VBase;
	static void s_intrface(void* p) {
		T* derived = static_cast<T*>(p);
		derived->intrface();
	}
public:
	void intrface() {
		T* derived = static_cast<T*>(this);
		derived->prt();
	}
};

class Derived1 : public Base<Derived1> {
	friend class Base<Derived1>;
	void prt() { my_print("foo"); }
};

class Derived2 : public Base<Derived2> {
	friend class Base<Derived2>;
	void prt() { my_print("bar"); }
};

Derived1 d1;
Derived2 d2;

VBase b[2];

void tst() {
	b[0] = d1;
	b[1] = d2;

	for (auto&& x : b) {
		x.intrface();
	}
}

The VBase class member variable p_inst stores a pointer to an object of type Base , and pf_intrface is a member function pointer to Base::s_intrface. Base::s_intrface invokes the T::intrface method by being passed an object instance of itself as an argument and static_casting it to the T type.

Storage in VBase is implemented here by overloading the = operator (see below for source examples).

In the above example, when making a call to b[0].intrface(), Base::s_intrface() will be called with reference to the VBase::pf_intrface function pointer. In addition, a call to Derived1::intrface() will be made. This part is expected to be expanded inline by the compiler.

It is also possible to perform a conversion from the VBase type to the original Derived1 or Derived2 through a forced cast, but there is no way to directly know the type of the pointer stored in void*. Although there is no completely safe way to do this, a unique ID (TYPE_ID) is provided for each class as shown below, and the ID is checked when the cast is executed (get() method). If the get() method is called with a different type, an error message will be displayed.

BIf a pointer is stored as a Base type, it may not be correctly converted to a T type (e.g., when T has multiple inheritance), so a static_assert is used to determine at compile time that the pointer is derived from a Base type by using is_base_of in <type_trails>.

#include <type_trails>

class Derived1 : public Base<Derived1> {
public:
   static const uint8_t TYPE_ID = 1;
}

class Derived1 : public Base<Derived1> {
public:
   static const uint8_t TYPE_ID = 2;
}

class VBase {
  uint8_t type_id;
public:
	
	template <class T>
	void operator = (T& t) {
		static_assert(std::is_base_of<Base<T>, T>::value == true,
						"is not base of Base<T>.");

		type_id = T::TYPE_ID;
		p_inst = &t;
		pf_intrface = T::s_intrface;
	}
	
  template <class T>
  T& get() {
    static_assert(std::is_base_of<Base<T>, T>::value == true,
					  "is not base of Base<T>.");
			
		if(T::TYPE_ID == type_id) {
			return *reinterpret_cast<T*>(p_inst);
		} else {
			// panic code here!
		}
  }
}

Derived1 d1;
Derived2 d2;

VBase b[2];

void tst() {
	b[0] = d1;
	b[1] = d2;
	
  Derived1 e1 = b[0].get<Derived1>(); // OK
  Derived2 e2 = b[1].get<Derived2>(); // OK
  
  Derived2 e3 = b[1].get<Derived1>(); // PANIC!
}

new, new[] operator

The microcontroller in the TWELITE module does not have enough memory nor does it have advanced memory management. However, the area from the end of the microcontroller's memory map to the stack area is available as a heap area, which can be allocated as needed. An overview of the memory map is shown in the figure below, where APP is the RAM area allocated by the application code, HEAP is the heap area, and STACK is the stack area.

|====APP====:==HEAP==..   :==STACK==|
0                                  32KB

Even if it is not possible to delete, the new operator may be useful in some situations. For this reason, the new and new[] operators are defined as follows: pvHear_Alloc() is a function for allocating memory provided by the semiconductor library, and the same is true for u32HeapStart and u32HeapEnd. 0xdeadbeef is a dummy address. Please do not point out that it is strange that beef is dead.

void* operator new(size_t size) noexcept {
    if (u32HeapStart + size > u32HeapEnd) {
        return (void*)0xdeadbeef;
    } else {
        void *blk = pvHeap_Alloc(NULL, size, 0);   
        return blk;
    }
}
void* operator new[](size_t size) noexcept {
    return operator new(size); }
void operator delete(void* ptr) noexcept {}
void operator delete[](void* ptr) noexcept {}

Since exceptions cannot be used, there is no way to deal with failures. Also, if you continue to allocate without being aware of the memory capacity, there is a possibility of interference with the stack area.

The memory allocated by the system (e.g. MAC layer) is about 2.5KB.

Container class

The MWX library does not use the container classes provided by the standard library, considering the small resources of the microcontroller and the lack of dynamic memory allocation, but defines two simple container classes. The container classes have defined iterators and begin() and end() methods, so you can use some of the range for statements and STL algorithms.

smplbuf<int16_t, alloc_local<int16_t, 16>> buf;
buf.push_back(-1); // push_back() は末尾に追加
buf.push_back(2);
...
buf.push_back(10);

//範囲for文
for(auto&& x : buf) { Serial << int(x) << ',' }
//アルゴリズム std::minmax
auto&& minmax = std::minmax_element(buf.begin(), buf.end());
Serial << "Min=" << int(*minmax.first)
       << ",Max=" << int(*minmax.second);
Class name
remark

smplbuf

It is an array class that manages the maximum area (capacity) and the usable area (size) whose size can be specified within the maximum area. This class also implements the stream interface, so data can be written using the << operator.

smplque

The FIFO queue is implemented. The size of the queue is determined by template parameters. There is also a template argument to manipulate the queue using interrupt inhibition.

About memory in container classes

In the container class, the memory allocation method is specified as a parameter of the template argument.

Class name
Remark

alloc_attach

Specify the buffer memory that has already been allocated. This is used when you want to manage the memory area allocated for the C library, or when you want to process the same buffer area as a fragmented area.

alloc_static

Allocate as a static array in the class. The size is determined in advance or used as an area for temporary use.

alloc_heap

Allocate to the heap area. Once allocated to the system heap, it cannot be discarded, but it is suitable for use in initialization to allocate an area according to application settings.

variable parameter

In the MWX library, variable number arguments are used for operations on byte sequences, bit sequences, and printf equivalent operations. The example below shows the process of setting 1 to the specified bit position.

// packing bits with given arguments, which specifies bit position.
//   pack_bits(5, 0, 1) -> (b100011) bit0,1,5 are set.

// The first function of recursive extraction
template <typename Head>
constexpr uint32_t pack_bits(Head head) { return  1UL << head; }

// Extract head and transfer the rest of the parameters to pack_bits 
//by recursive call
template <typename Head, typename... Tail>
constexpr uint32_t pack_bits(Head head, Tail&&... tail) {
  return (1UL << head) | pack_bits(std::forward<Tail>(tail)...);
}

// コンパAfter ILL, the following two will have the same result.
constexpr uint32_t b1 = pack_bits(1, 4, 0, 8);
// b1 and b2 are the same! 
const uint32_t b2 = (1UL << 1)|(1UL << 4)|(1UL << 0)|(1UL << 8);

In this process, the parameter pack of template (typename... part of template) to perform recursive processing to expand the arguments. In the above example, since constexpr is specified, the calculation is done at compile time and the result is equivalent to macro definition or const value specification such as b2. It can also behave as a function that dynamically calculates variables as arguments.

In the following example, the expand_bytes function is used to store a value in a local variable from the received packet data string. In the case of using a parameter pack, since the type of each argument can be known, it is possible to store values of different sizes and types from the byte string of the received packet, as shown below.

auto&& rx = the_twelite.receiver.read(); // received packet

// Variable that stores the contents of the packet after expansion
// The payload of a packet is a sequence of bytes, arranged as follows.
//   [B0][B1][B2][B3][B4][B5][B6][B7][B8][B9][Ba][Bb]
//   <message       ><adc*  ><vcc*  ><timestamp*    >
//   * Numerical types are big-endian.
uint8_t msg[MSG_LEN];
uint16_t adcval, volt;
uint32_t timestamp;

// expand packet payload
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
		, msg       // 4bytes of msg
		, adcval    // 2bytes, A1 value [0..1023]
	  , volt      // 2bytes, Module VCC[mV]
	  , timestamp // 4bytes of timestamp
);

Iterator

An iterator is an abstraction of a pointer, which has the effect of making it possible to access data structures as if they were pointers, even if the data structures are not memory-contiguous, for example.

In the C++ STL, the combination of an iterator indicating the beginning of the container obtained by the begin() method and an iterator indicating the "next" end of the container obtained by the end() method is often used.

The reason why we use end() for the "next" at the end of the container is because we expect the following description, and the MWX library follows this in its container implementation.

smplque<uint8_t, alloc_local<uint8_t, 5> > que;
que.push('a'); que.push('b'); que.pop(); que.push('c'); ...

auto&& p = que.begin();
auto&& e = que.end();

while(p != e) { // p advanced to e = all elements processed
  Serial << *p;
  ++p; // イThe prefix operator is used for incrementing the terrator.
     // In this case, writing p++ will result in a copy of the iterator 
       // in the code, although it is likely to be optimized by the compiler.
}

Conform the iterator to the standard library specification, so that range for statements can be used, and algorithms from the standard library can be used.

#include <algorithm>
#include <cctype>

// Character conversion using lambda expressions
std::for_each(que.begin(), que.end(), 
  [](uint8_t& x) { x = std::toupper(x); });

// range-for statement
for (uint8_t x : que) {
  Serial << x;
}

(The MWX library has not been tested for compatibility with the C++ standard library. Please check the operation before use.)

The following example shows the use of an iterator for a FIFO queue that cannot be accessed continuously with a normal pointer, and also an iterator that extracts only a specific member of the FIFO queue structure (the X axis in the example).

//A queue with 5 elements, whose elements are the 4-axis structures of XYZT
smplque<axis_xyzt, alloc_local<axis_xyzt, 5> > que;

// Input data for testing.
que.push(axis_xyzt(1, 2, 3, 4));
que.push(axis_xyzt(5, 2, 3, 4));
...

// Access using iterators as structures
for (auto&& e : v) { Serial << int(e.x) << ','; }

// Extract the X axis in the queue.
auto&& vx = get_axis_x(que);
// Access with X-axis iterator
for (auto&& e : vx) { Serial << int(e) << ','; }

// Since it is an iterator of int16_t elements, 
//the STL algorithm (max-min) can be used.
auto&& minmax = std::minmax_element(vx.begin(), vx.end());

The following is an excerpt of the implementation of an iterator for the smplque class. In this iterator, the queue object is managed by its entity and its index. The part of the queue that is discontiguous in memory (ring buffer structure where the next to the tail must point to the beginning) is solved by smplque::operator []. If the addresses of the objects match and the indices match, the iterators point to the same thing.

This implementation part also includes the typedefs required by , allowing more STL algorithms to be applied.

class iter_smplque {
	typedef smplque<T, alloc, INTCTL> BODY;

private:
	uint16_t _pos; // index
	BODY* _body;   // point to original object

public: // for <iterator>
	typedef iter_smplque self_type;
	typedef T value_type;
	typedef T& reference;
	typedef T* pointer;
	typedef std::forward_iterator_tag iterator_category;
	typedef int difference_type;

public: // pick some methods
	inline reference operator *() {
		return (*_body)[_pos];
	}
	
	inline self_type& operator ++() {
		_pos++;
		return *this;
	}
};

構造体を格納したコンテナ中の、特定構造体メンバーだけアクセスするイテレータは少々煩雑です。構造体のメンバーにアクセスするメンバー関数を予め定義しておきます。このメンバー関数をパラメータ(R& (T::*get)())としたテンプレートを定義します。Iterはコンテナクラスのイテレータ型です。

Iterators that access only specific structure members in the container that contains the structure are a bit complicated. Define a member function to access the structure members in advance. Next, define a template with this member function as a parameter (R& (T::*get)()). "Iter" is the iterator type of the container class.

struct axis_xyzt {
    int16_t x, y, z;
    uint16_t t;
    int16_t& get_x() { return x; }
    int16_t& get_y() { return y; }
    int16_t& get_z() { return z; }
};

template <class Iter, typename T, typename R, R& (T::*get)()>
class _iter_axis_xyzt {
    Iter _p;
    
public:
    inline self_type& operator ++() {
        _p++;
        return *this; }

    inline reference operator *() {
        return (*_p.*get)(); }
};

template <class Ixyz, class Cnt>
class _axis_xyzt_iter_gen {
    Cnt& _c;
    
public:
    _axis_xyzt_iter_gen(Cnt& c) : _c(c) {}
    Ixyz begin() { return Ixyz(_c.begin()); }
    Ixyz end() { return Ixyz(_c.end()); }
};

// It's long, so shorten it with using
template <typename T, int16_t& (axis_xyzt::*get)()>
using _axis_xyzt_axis_ret = _axis_xyzt_iter_gen<
    _iter_axis_xyzt<typename T::iterator, axis_xyzt, int16_t, get>, T>;

// Generator to extract X axis
template <typename T>
_axis_xyzt_axis_ret<T, &axis_xyzt::get_x>
get_axis_x(T& c) {
    return _axis_xyzt_axis_ret<T, &axis_xyzt::get_x>(c);
}

The operator * that accesses the value calls the member function described above. The *_p is the axis_xyzt structure, and (*_p.*get)() calls _p->get_x() if &axis_xyzt::get_x is specified in T::*get.

The _axis_xyzt_iter_gen class implements only begin(), end() and generates the above iterators. Now you can use range for statements and algorithms.

This class name is very long and difficult to write in the source code. We will prepare a generator function to generate this class. In the example below, it is get_axis_x() in the last line. By using this generator function, the description becomes as simple as auto&& vx = get_axis_x(que); as shown in the beginning.

This iterator for extracting only the axes can also be used with the smplbuf class of array type as well.

Implementing interrupt, event, and state handlers

In order to describe the application behavior by user-defined classes, typical handlers need to be defined as mandatory methods, but it is complicated to define all the other numerous interrupt handlers, event handlers, and state machine state handlers. Ideally, only those defined by the user should be defined, and only that code should be executed.

class my_app_def {
public: // Define required methods
	void network_event(twe::packet_ev_nwk& pEvNwk) {}
	void receive(twe::packet_rx& rx) {}
	void transmit_complete(twe::packet_ev_tx& pEvTx) {}
	void loop() {}
	void on_sleep(uint32_t& val) {}
	void warmboot(uint32_t& val) {}
	void wakeup(uint32_t& val) {}
	
public: // It is cumbersome to make these descriptions mandatory.
  // DIO interrupt handler: There are 20 types.
  // DIO event handler: There are 20 types.
  // Timer interrupt handler: There are five types
  // Timer event handlers: there are 5 types
  // ...
}

In the MWX library, a large number of DIO interrupt handlers (on TWELITE hardware, a single interrupt, but for ease of use, a handler is assigned to each DIO) are defined as empty handlers using templates, and user-defined member functions are defined by specializing the templates.

// hpp file
class my_app_def : class app_defs<my_app_def>, ... {
  // Empty handler
  template<int N> void int_dio_handler(uint32_t arg, uint8_t& handled) { ; }

  ...   
  // Implement only number 12.
  
public:
  // Callback function called from TWENET
  uint8 cbTweNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap);
};

// cpp file
template <>
void my_app_def::int_dio_handler<12>(uint32_t arg, uint8_t& handled) {
  digitalWrite(5, LOW);
  handled = true;
  return;
}

void cbTweNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap) {
  uint8_t b_handled = FALSE;
  switch(u32DeviceId) {
  	case E_AHI_DEVICE_SYSCTRL:
      if (u32ItemBitmap & (1UL << 0)){int_dio_handler<0>(0, b_handled);}
      if (u32ItemBitmap & (1UL << 1)){int_dio_handler<1>(1, b_handled);}
      ...
      if (u32ItemBitmap & (1UL << 12)){int_dio_handler<12>(12, b_handled);}
      ...
      if (u32ItemBitmap & (1UL << 19)){int_dio_handler<19>(19, b_handled);}
    break;
  }
}

The actual user-described code has been simplified by macroizing and including header files, but the above includes the code necessary for the explanation.

The my_app_def::cbTweNet_u8HwInt() is called from the interrupt handler from TWENET. in the cpp file, only int_dio_handler<12> is instantiated with the specialization described in it. file is instantiated from a template in the hpp file. The rest are instantiated from templates in the hpp file.

  	case E_AHI_DEVICE_SYSCTRL:
      if (u32ItemBitmap & (1UL << 0)){;}
      if (u32ItemBitmap & (1UL << 1)){;}
      ...
      if (u32ItemBitmap & (1UL << 12)){
          int_dio_handler<12>(12, b_handled);}
      ...
      if (u32ItemBitmap & (1UL << 19)){;}
      break;
      
    // ↓ ↓ ↓
    
    // 結局、このように最適化されることが期待できる。
   	case E_AHI_DEVICE_SYSCTRL:
      if (u32ItemBitmap & (1UL << 12)){
        // int_dio_handler<12> もinline展開
        digitalWrite(5, LOW);
        handled = true;
      }
      break;
    

Eventually, we can expect that the compiler optimization will determine that codes other than number 12 are meaningless and disappear from the code (however, we do not guarantee that they will be optimized as described above).

In other words, in user code, if you want to define the behavior at interrupt 12, just write int_dio_handler<12> (Note: to enable DIO interrupt, you need to call attachInterrupt()). Handlers that are not registered are expected to be low-cost calls due to compile-time optimization.

One technique to enable this when the user defines a function, and call another function if not defined, is to resolve it at link time. Specify __attribute__((wake)) as shown below. If the wakeup() function is defined in the user code, the user code will be linked to the function, and if it is not defined, the function with empty content will be linked.

// mwx_appcore.cpp
void wakeup() __attribute__((weak));
void wakeup() { }

In the implementation of the above handler, it is necessary to generate weak member variables explicitly, and it is difficult to optimize by inlining, so it is not used. However, weak functions are defined to receive some callbacks from TWENET, such as wakeup().

Stream class

The stream class is mainly used for input/output of UART (serial port), and MWX library mainly defines procedures for output. But some of them are also defined for input.

This section describes the implementation required by the derived class.

template <class D>
class stream {
protected:
	void* pvOutputContext; // TWE_tsFILE*
public:
  inline D* get_Derived() { return static_cast<D*>(this); }
	inline D& operator << (char c) {
		get_Derived()->write(c);
		return *get_Derived();
	}
};

class serial_jen : public mwx::stream<serial_jen> {
public:
 	inline size_t write(int n) {
		return (int)SERIAL_bTxChar(_serdef._u8Port, n);
	}
};

The above is an implementation of the write() method that writes a single character. The stream<serial_jen> of the parent class accesses the serial_jen::write() method using the get_Drived() method to perform casting.

Define methods such as write(), read(), flush(), and available() as needed.

For formatting output, we use Marco Paland's printf library, which needs to be implemented for use with the MWX library. In the following example, the derived class serial_jen needs to define the vOutput() method for 1-byte output, and save the auxiliary information for output in the parent class pvOutputContext since vOutput() is a static method. The other is to save the auxiliary information in the pvOutputContext of the parent class since vOutput() is a static method.

template <class D>
class stream {
protected:
	void* pvOutputContext; // TWE_tsFILE*
public:
	inline tfcOutput get_pfcOutout() { return get_Derived()->vOutput; }
	
	inline D& operator << (int i) {
		(size_t)fctprintf(get_pfcOutout(), pvOutputContext, "%d", i);
		return *get_Derived();
	}
};

class serial_jen : public mwx::stream<serial_jen> {
	using SUPER = mwx::stream<serial_jen>;
	TWE_tsFILE* _psSer; // Low-level structure for serial output
public:
  void begin() {
    SUPER::pvOutputContext = (void*)_psSer;
  }
  
	static void vOutput(char out, void* vp) {
		TWE_tsFILE* fp = (TWE_tsFILE*)vp;
		fp->fp_putc(out, fp);
	}
};

By get_pfcOutput(), the vOutput() function defined in the derived class is specified, and pvOutputContext is passed as its parameter. In the above example, when the << operator is called with int type, serial_jen::vOutput() and TWE_tsFILE* which is already set for UART are passed to the fctprintf() function.

Worker object for Wire, SPI

In the Wire class, it is necessary to manage the communication from start to end when sending and receiving with a 2-wire device. This section describes the contents of the description of using the worker object.

if (auto&& wrt = Wire.get_writer(SHTC3_ADDRESS)) {
	Serial << "{I2C SHTC3 connected.";
	wrt << SHTC3_TRIG_H;
	wrt << SHTC3_TRIG_L;
	Serial << " end}";
}

This is an excerpt of the periph_twowire::writer class, which inherits from mwx::stream<writer> to implement the stream interface, and implements the write() and vOutput() methods to use the steam interface. To use the steam interface, the write() and `vOutput() methods are implemented.

The constructor calls the method to start communication for 2-wire serial and the destructor calls the method to end communication. Also, the operator bool() operator returns true if the communication of the 2-wire serial device is successfully started.

class periph_twowire {
public:
	class writer : public mwx::stream<writer> {
		friend class mwx::stream<writer>;
		periph_twowire& _wire;
	
	public:
		writer(periph_twowire& ref, uint8_t devid) : _wire(ref) {
	  	_wire.beginTransmission(devid); // Start communication with constructor
		}
	
		~writer() {
			_wire.endTransmission(); // Communication terminated by destructor
		}
	
		operator bool() {
			return (_wire._mode == periph_twowire::MODE_TX);
		}
	
	private: // stream interface
		inline size_t write(int n) {
			return _wire.write(val);
		}
	
		// for upper class use
		static void vOutput(char out, void* vp) {
			periph_twowire* p_wire = (periph_twowire*)vp;
			if (p_wire != nullptr) {
				p_wire->write(uint8_t(out));
			}
		}
	};
	
public:
	writer get_writer(uint8_t address) {
		return writer(*this, address);
	}
};
class periphe_twowire Wire; // global instance

// ユーザコード
if (auto&& wrt = Wire.get_writer(SHTC3_ADDRESS)) {
	Serial << "{I2C SHTC3 connected.";
	wrt << SHTC3_TRIG_H;
	wrt << SHTC3_TRIG_L;
	Serial << " end}";
}

The get_writer() method creates an object wrt. Due to the Return Value Optimization (RVO) of the C++ compiler, the writer is created directly in the wrt, so no copy is made and the bus running in the constructor is not initialized multiple times. However, RVO is not guaranteed by the C++ specification, and just in case, the MWX library defines copy, delete assignment operators, and move constructors (although it is unlikely that move constructors will be evaluated).

The wrt in the if clause is first initialized by the constructor and starts communication at the same time. If there is no error at the start of communication, the bool operator at the time of conditional judgment returns true, and the processing in the scope of the if clause takes place. If there is no error at the start of communication, the bool operator at the conditional judgment returns true, and the processing in the if clause scope is performed. When the scope is exited, the destructor terminates the 2-wire serial bus. If there is no communication partner, false will be returned and the wrt object will be destroyed.

It overrides the definition of operator << (int), which is specific to Wire and SPI. The default behavior of the stream is to convert numeric values to strings and output them, but Wire and SPI rarely write numeric strings to the bus, and on the contrary, we often want to input literals of numeric type such as configuration values. We will change this behavior.

			writer& operator << (int v) {
				_wire.write(uint8_t(v & 0xFF));
				return *this;
			}

In this example, the int type values are truncated to 8 bits and the values are output.

Building ACT

Building ACT

The application program written in the MWX library is called ACT. The first step is to build and write it.

  • About the build folder structure

  • About the build script

  • About building with Visual Studio Code (Described as VSCode)

This page describes several methods of building, all of which ultimately involve running the make command. Please refer to the Makefile description for details.

Depending on the OS environment, security warnings may appear when running each executable program (e.g. make, build toolchain like gcc). It is necessary to configure the settings to suppress the warning. (Please consult with yourself or your system administrator to determine if you wish to run the program with the warning suppressed.)

About the build folder structure

Open the folder MWSDK_ROOT (e.g. C:\MWSDK) where you have installed the MWSDK. It has the following structure

MWSDK_ROOT
  |
  +-ChipLib      : Semiconductor library
  +-License      : Software License Agreement
  +-MkFiles      : makefile
  +-Tools        : A set of tools such as compilers
  +-TWENET       : TWENET/MWX library
  +-Act_samples  : Sample codes of ACT
  ...

Tool act files such as compilers are stored under Act_samples. (Some of them are omitted below)

Act_samples
  |
  +-BRD_APPTWELITE    : ACT for boards with the same configuration as App_TweLite
  +-PAL_AMB           : ACT for environmental sense PAL
  +-PAL_MAG           : ACT for open-close PAL
  +-PAL_MOT           : ACT for motion PAL
  ..
  +-Parent-MONOSTICK  : ACT for parent device (for MONOSTICK)
  +-PingPong          : ACT for transmit and receive like Ping Pong
  +-PulseCounter      : ACT using a pulse counter
  +-act0              : Scratching ACT

These acts are simple examples to help you write your own MWX library, but many of them have the following features

  • Obtaining sensor values

  • After obtaining the sensor value, send a wireless packet to the master

  • Sleeps for a period of time (or waits for an interrupt) after transmission is complete

The Parent-MONOSTICK act is used to receive and display packets. This act for the parent is output in ASCII format. (:00112233AABBCC.... .FF[CR][LF], and the middle part is a hexadecimal byte expressed by two ASCII characters. The trailing ? is also a two-character byte, but it becomes a checksum byte called LRC. Reference: ASCII format)

When trying to get it to work in practice, try the following combinations:

Parents
Child
Remark

The parent device is started with the M1 pin low (GND level). In normal mode (always running), you can see it works like App_TweLite.

The system works with two children. When one of them sends a ping packet, the other sends a pong packet back.

Other

You can check the transmission of packets of the act for the child machine.

Now let's take a look inside the PingPong folder from within ACT.

You can also build other acts in Act_samples. In this case, the folder and file names should be read differently.

Act_samples
  +-PingPong
    +-PingPong.cpp   : ACT code
    +-build          : build folder
    +-.vscode        : setting files for VSCode

You must have a .cpp file with the same name as the folder directly under the folder.

If it is a small act, you can write it in this .cpp file. If you have a larger act, you can build it in multiple files by referring to the Makefile description.

The ACT file PingPong.cpp is located directly under the PingPong folder. If you change the name of the folder, make sure to rename the .cpp file to the same name as the folder.

Next, open the build folder.

Act_samples
  +-PingPong
    +-build
      +-Makefile        : makefile
      +-build-BLUE.cmd  : build script for TWELITE BLUE
      +-build-RED.cmd   : build script for TWELITE RED
      +-build-clean.cmd : clean up obj_* files

It contains the scripts and Makefiles needed for the build.

Build by running make TWELITE={BLUE or RED} in the folder containing this Makefile. Building with VSCode is the same, calling make internally.

Building with TWELITE STAGE App. (Normal)

The TWELITE STAGE app can be used to build, write, and run. This section describes the TWELITE STAGE application from startup to build.

0. TWELITE Connection

Connect MONOSTICK or TWELITE R to your USB port.

TWELITE is a sensitive electronic component and should be handled with care. Typical precautions are listed below.

Especially when TWELITE R is used, the electronic board is often in direct contact with the outside without a case, which may cause unintended shorts, noise, or other problems that prevent the USB device from operating properly.

In this case, closing the application and unplugging and plugging in the USB device will usually restore it. In the worst case, the USB device may be damaged or the PC may be corrupted.

Also, handle electronic circuit boards with care.

  • Circuit error.

    • Check the circuit again before turning on the power.

    • Be careful not to reverse-insert batteries or over-voltage.

  • Static electricity

    • Even a voltage that is not human-sensitive can cause a semiconductor failure. Even simple measures such as touching metal parts before working, wristbands, and special mats can have a reasonable effect.

  • Short-circuits caused by touching metal objects, etc.

    • Make sure that there are no metal objects near the electronic board. If clips or other objects are scattered around, this may cause a short circuit, or even a dangerous situation in which the battery heats up due to a large discharge.

1. Launch the TWELITE STAGE application

Launch the executable TWELITE_Stage.{extension} located in the {TWELITE SDK installation} folder. (reference: TWELITE STAGE Application Manual - Usage)。

Below is an example of the screen while the TWELITE STAGE application is running. The main screen on the left and the command prompt screen are shown, but the main screen is usually used. The command prompt screen is not normally used, but it displays various information and input data from the TWELITE microcontroller serial port.

The main operations on the main screen are as follows

  • Left mouse click (selection)

  • Double right mouse click (return to previous screen)

  • Quickly press ESC twice, or ESC once on some screens (return to previous screen)

  • Hold down the Alt(Cmd) key (help screen)

  • Normal keyboard input (follow the screen)

(Reference: TWELITE STAGE App Manual - Key and Mouse Operations)

2. Serial port selection

This is the first screen that appears when you start the TWELITE STAGE application. If TWELITE R or MONOSTICK is connected in advance, it will be listed on this screen. Select the TWELITE you wish to operate on this screen. It is also possible to select the TWELITE without selecting it on this screen.

(Reference: TWELITE STAGE App Manual)

3. Main Menu

After exiting the serial port selection screen, the main menu appears. Select the "Application Rewrite" menu for build and write.

(Reference: TWELITE STAGE App Manual)

4. Wrt Firmware (Application programming) menu

Before selecting the application programming menu, please check the TWELITE connection and serial port selection. The serial port selection status can be checked on the help screen that appears by holding down the Alt(Cmd) key.

There are several categories of projects that can be referenced from the TWELITE STAGE application. HELP on the right side displays related information in a browser. Foldr displays the folder where the project is located.

If TWELITE is already connected, the TWELITE model is determined when the menu is selected. (Inside the TWELITE STAGE application, the build is performed according to the TWELITE model that has been determined.)

If an error occurs here, return to the main menu from this screen and reselect the menu. If necessary, deselect the serial port by pressing Alt(Cmd) + 0 on the TWELITE STAGE application and check the various connections, including the USB connection. Some USB connection errors may not be resolved until you reboot your computer.

(Reference: TWELITE STAGE App Manual)

4. Project Selection

Here, select "Act Build & Wrt" from the " Wrt Firmware" menu.

Project names, such as sample acts, are listed. The HELP on the right side displays the related information in a browser. The foldr displays the folder where the project is located.

Reference: TWELITE STAGE App Manual-Act Build & Wrt)

5. Build & Wrt(Programming)

Here, select BRD_APPTWELITE in the project selection screen shown earlier.

Once selected, writing will be performed as shown in the following screen example. If an error is displayed, follow the on-screen instructions or return to the previous screen and try again.

(Reference: TWELITE STAGE App Manual-build screen)

6. Go to Interactive settings mode

When the programming is successfully completed, it will continue to Interactive settings mode (settings screen). However, the screen will not be displayed unless the firmware supports Interactive settings mode.

In Interactive settings mode, you can configure various settings, including TWELITE's wireless CHANNEL.

(Reference: TWELITE STAGE App Manual-Interactive settings mode)

7. Terminal screen

Return to the root menu and select Viewer > Terminal.

This is a very simple terminal where you can check messages from TWELITE and input data into TWELITE.

The screen displays a message when a wireless transmission is made approximately every second. You can also transition to the Interactive settings mode screen by typing + + +.

(Reference: TWELITE STAGE App Manual-Terminal)

About building with VSCode (Optional)

VSCode is a powerful editor for source editing, but it is also possible to build firmware for TWELITE microcontrollers on VSCode.

VSCode is started from the TWELITE STAGE application from the project listing in Build&Write menu. (Note: The settings is required at TWELITE STAGE App [Setting Menu] > Wrt Firmware > Open a folder with VSCode. For simplicity of configuration, the executable TWELITE_Stage_VSCode.{extension} is available for Windows, Linux, and macOS.)

Set "Open folder with code (VSCode)" to 1 in STAGE settings.

Press [VSCode] on the right end of the list of builds.

If VSCode is already launched, the necessary settings by setting the system environment variables may not be reflected. In this case, exit VSCode and start VSCode again from the TWELITE STAGE application.

Because of the use of system environment variables to reflect information, in principle, simultaneous operation of multiple TWELITE STAGES that reference different library folders may cause problems. If you open Terminal on VSCode and the environment variable MWSDK_ROOT is properly set, you can expect the build to be successful.

Build with VSCode

Firstly, open a workspace from TWELITE STAGE app that you want build. The attached workspace in TWELITE STAGE SDK has build task definitions for TWELITE microcontroller.

In the example below, a workspace is opened with an example screen of the English interface.

Open a workspace and select [Terminal>Run Task...].

Select the type of TWELITE radio module (BLUE/RED) and the act name to build. In the example below we have selected Build for TWELITE BLUE PingPong (for TWELITE BLUE/PingPong act). The build will start immediately after selection.

The progress of the build is shown in the TERMINAL section at the bottom of the screen.

If the build is successful, you will see a message about the creation of the .elf file, along with its size information (text data bss dec hex filename), as shown in the highlighted part of the screenshot above.

Also, a BIN file (in the above example, PingPong_BLUE_???.bin) file should be created under the build folder. Please check it.

The build definition adds a definition to convert folder names (e.g. /c/User/...) that do not conform to the Windows 10 file system (e.g. C:/User/...).

The conversion is not complete, but you can extract the filename and line number of the error from the compilation message.

The execution command in .vscode/tasks.json is sh -c "make ... | sed -E -e s#..." to rewrite the string of the drive name equivalent in the output message.

...
"windows": {
    "command": "sh",
    "args": [
        "-c", "make TWELITE=BLUE 2>&1 | sed -E -e s#\\(/mnt\\)?/\\([a-zA-Z]\\)/#\\\\\\2:/#g"
    ],

If the build does not work, check the error messages first; it is often easy to identify the cause of an error from a message on a line containing the string error.。

To be sure, clean (remove intermediate files in the objs_??? folder) and rerun the build to be sure. (All operations, including make clean, will fail if there are intermediate files left over from builds in other environments).

Build on command line (Linux/macOS)

Additional information about building in the command line environment.

A working knowledge of the command line (e.g. bash) is required.

Depending on the OS environment, security warnings may appear when running each executable program. It is necessary to configure the settings to suppress the warning. (Please consult with yourself or your system administrator to determine if you wish to run the program with the warning suppressed.)

Linux, macOS

To build by command line, run make in a window where bash (or some other shell) is running. Make sure that the environment variable MWSDK_ROOT is set correctly beforehand. For example, if you install to /work/MWSTAGE/MWSDK, set ~/.profile as follows.

MWSDK_ROOT=/work/MWSTAGE/MWSDK
export MWSDK_ROOT

Run make from the command line (bash). If make is not available, you need to install a package. (The following is an example for Ubuntu Linux)

$ make

Command 'make' not found, but can be installed with:

sudo apt install make
sudo apt install make-guile

$ sudo apt install make
...
  • On Linux environments, install the make or build-essential package.

  • In the macOS environment, install Command Line Tools in Xcode.

Windows

On Windows, run {MWSTAGE SDK install}/MWSDK/WIN_BASH.cmd. Environment variables and make utility are already set.

Building

A build should look like this:

$ cd $MWSDK_ROOT
$ cd Act_samples/PingPong/build
$ pwd
/mnt/c/MWSDK/Act_samples/PingPong/build

$ ls
... View file list

$ rm -rfv objs_*
... Delete intermediate files just in case

$ make TWELITE=BLUE
... Normal build for BLUE

$ make -j8 TWELITE=BLUE
... Parallel build for BLUE (8 processes simultaneously)

Example commands

See the Makefile description for more details.

Example
Remark

make TWELITE=BLUE

build for TWELITE BLUE

make TWELITE=RED

build for TWELITE RED

make cleanall

Delete intermediate files

About intermediate files

When the build is done, the objs_??? folder is created and an intermediate file is created in it. This file is dependent on the environment in which it was compiled, so if any files from other environments are left, make will fail.

Deleting the objs_??? folder'' may resolve the make error.

Installing the TWELITE SDK

Installing the TWELITE SDK

Download the TWELITE STAGE SDK distribution archive (e.g. ZIP) and extract it to an appropriate folder.

The folder names of each level of the destination folder must not contain anything other than one-byte numbers 0..9, one-byte alphabets a..zA..Z, and some symbols -_.. In short, it must not contain spaces, Kanji characters, Hiragana characters, etc.

See Installing the TWELITE STAGE SDK for more information.

This is the end of the normal installation process. Refer to the following "Set Environmental variables" and below as necessary.

Set Environmental variables

If you want to build with other than TWELITE STAGE application, please set the environment variables (e.g. MWSDK_ROOT).

If you are using the TWELITE STAGE application, you do not need to set these environment variables. Inside the TWELITE STAGE application, those environment variables will be set appropriately.

MWSDK_ROOT, MWSDK_ROOT_WINNAME(for Windows10) need to be set.

Windows10,11

In this example, the name of the extracted folder is C:\MWSTAGE. If you have installed the software in a different folder, please change the name.

Run C:\MWSTAGE\Tools\SET_ENV.CMD to set the following environment variables:

  • MWSDK_ROOT

  • MWSDK_ROOT_WINNAME

For example, the following variables are set:

MWSDK_ROOT=C:/MWSTAGE/MWSDK/
MW_ROOT_WINNAME=C:\MWSTAGE\MWSDK\

To uninstall the TWELITE STAGE SDK from the installed PC, please do the following:

  • Run UNSET_ENV.cmd. This will unset the environment variables.

  • Delete the MWSTAGE folder.

Linux

Set the development environment and shell to reflect the MWX_ROOT environment variable.

There are several ways to do this, but add the following setting to the .profile of your home folder (if the file does not exist, please create a new one). You can even build VSCode with this setting.

MWSDK_ROOT=/foo/bar/MWSTAGE/MWSDK/ export MWSDK_ROOT

To add it without using the editor, enter the command as follows. The $ is a prompt and will be displayed differently depending on your environment. The /foo/bar/MSWSDK part should be rewritten according to the installed folder.

$ cd $HOME
$ echo MWSDK_ROOT=/foo/bar/MWSTAGE/MWSDK>>.profile
$ echo export MWSDK_ROOT>>.profile

macOS

Set the development environment and shell to reflect the MWX_ROOT environment variable.

There are several ways to do this, but add the following settings to .profile in your home folder (create a new file if you don't have one). You can even build VSCode with this setting.

MWSDK_ROOT=/foo/bar/MWSTAGE/MWSDK/ export MWSDK_ROOT

To add it without using the editor, enter the command as follows. The $ is a prompt and will be displayed differently depending on your environment. The /foo/bar/MSWSDK part should be rewritten according to the installed folder.

$ cd $HOME
$ echo MWSDK_ROOT=/foo/bar/MWSTAGE/MWSDK>>.profile
$ echo export MWSDK_ROOT>>.profile

Use LaunchD to apply MWSDK_ROOT to the entire environment.

Some settings in VSCode refer to environment variables, but they are not required for the build.

Installing VSCode

Installing VSCode

VisualStudio Code (VSCode) is recommended to make Act (source code) writing easier. The attached Act contains a file that has been configured to ensure that the code is interpreted properly in VSCode.

VSCode reads source files and header files and interprets the source code, thus providing function definition information and function and method name completion to help you write source code. The MWX library requires more header files to be loaded than the C library. In comparison to C development, the MWX library requires more header files to be loaded and the editor may be slower in some environments.

The project settings of VSCode requires some information, such as library source code location, to analyse source codes. These inforamtion are passed by TWELITE STAGE app as environmental variable when launching VSCode. Therefore, application instance of VSCode should not be present when launching from TWELITE STAGE app, otherwise VSCode will not refer to these environmental values.

Installing VSCode

Please note that this support does not cover questions about how to install or use VSCode. Please refer to the information available in the public domain.

Depending on your environment, you may need to configure security settings or other settings in order to install the software. Please check with your system administrator whether installation is possible, and refer to the distributor or general information for instructions.

VSCode allows you to do the following:

  • Editing the source code

  • The IntelliSense based on source code interpretation (*not all definitions can be guaranteed to be interpreted correctly)

VSCode can be downloaded from the link below.

Special note for each OS

The code command must be enabled to invoke VSCode from TWELITE STAGE.

The following information is from code.visualstudio.com

  • macOS - PATH must be set so that the code command can be executed.

  • Windows

  • Linux

Installing plug-ins

To enable Visual Studio Code to interpret C/C++ language descriptions, install a plugin.

  • C/C++ for Visual Studio Code

Notes

The MWX library examples include a .vscode definition. This definition uses the MWSDK_ROOT environment variable to identify the source code of the library (under {MWSDK_ROOT}/TWENET/current).

When launching VSCode from TWELITE STAGE, those settings such as MWSDK_ROOT will be setup automatically. In some cases, such as when you have already started VSCode, the settings may not be reflected.

VSCode's interpretation of source code is not always the same as the compiler's interpretation. Also, depending on how the source code is edited, the interpretation may be more incomplete.

The following is an example folder after extracting the TWELITE STAGE SDK archive. (Windows, c:\work\MWSTAGE...)

BRD_APPTWELITE
BRD_APPTWELITE
PingPong
PingPong
Parent-MONOSTICK

Slp_Wk_and_Tx

Slp_Wk_and_Tx is a template source code for an application which, after a regular wake-up, does something (e.g. acquires sensor data) and sends the result as a wireless packet.

In the form of setup() and loop(), conditional branches which are difficult to read in loop() tend to occur. In this Act, we use SM_SIMPLE state machine in loop() and simple state transition by switch syntax to improve the visibility of the code.

This act includes

  • The control structure of a typical intermittent operation (sleep -> wake -> measure -> radio transmission -> sleep)

  • Generation and transmission procedures for outgoing packets and waiting for completion

ACT features.

  • After starting up, the system goes through an initialization process and then goes to sleep.

    1. setup()Initialise

    2. begin() Run sleep

  • After waking up from sleep, the state variables are initialized and the actions are performed in the following order

    1. wakeup() wakes up from sleep, performs each initialization

    2. loop()/INIT->WORK_JOB state: does some processing (in this Act, the counter is updated every TickCount for 1ms and moves to TX state after a count determined by a random number)

    3. loop()/TX state: Make a request to send

    4. loop()/WAIT_TX state: Waiting for transmission completion

    5. loop()/EXIT_NORMAL state: Run sleep (back to 1.)

  • loop()/EXIT_FATAL state :Resetting the module if an error occurs

Description of the ACT

Declarations

Includes

#include <TWELITE>
#include <NWK_SIMPLE>
#include <SM_SIMPLE>

#include "Common.h"

To send packets, <NWK_SIMPLE> is included. Also, basic definitions such as application ID are in "Common.h".

State definition.

In order to describe the sequential processing in loop(), this sample uses the concept of a state machine (state transition). It uses <SM_SIMPLE>, which summarizes the processing of very simple state transitions.

An enum STATE is defined in Common.h for the following states.

enum class STATE {
    INIT = 0,    // INIT STATE
    WORK_JOB,    // do some job (e.g sensor capture)
    TX,          // reuest transmit
    WAIT_TX,     // wait its completion
    EXIT_NORMAL, // normal exiting.
    EXIT_FATAL   // has a fatal error (will do system reset)
};

Declares an SM_SIMPLE state machine (state transition) using the STATE state enumeration.

SM_SIMPLE<STATE> step;

The step declared here contains functions for managing state, timeouts and waiting for processing.

Sensor data.

In this sample we do not process the sensor data, but we prepare dummy data for explanation.

struct {
	uint16_t dummy_work_ct_now;
	uint16_t dummy_work_ct_max;  // counter for dummy work job. 
} sensor;

setup()

void setup() {
	/*** SETUP section */
	step.setup(); // init state machine
	
	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle(false);  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk	<< NWK_SIMPLE::logical_id(DEVICE_ID); // set Logical ID. 

	/*** BEGIN section */
	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- Sleep an Tx Act ---" << crlf;
}

Initializes variables and class objects.

  • Initialization of the step state machine

  • Initialization of the_twelite class object

  • Register and initialize the network <NWK_SIMPLE> (register DEVICE_ID).

This is followed by the initiation of class objects and hardware.

the_twelite.begin(); // start twelite!

This is the procedure to start the_twelite, it didn't appear in act0..4, but you should call it if you set up the_twelite or register various behaviors.

begin()

void begin() {
	Serial << "..begin (run once at boot)" << crlf;
	SleepNow();
}

Called only once, immediately after setup(). The SleepNow() function is called to perform the first sleep procedure.

wakeup()

void wakeup() {
 memset(&sensor, 0, sizeof(sensor));
	Serial << crlf << int(millis()) << ":wake up!" << crlf;
}

Called immediately after waking up. Here, it initializes the sensor data area and outputs a message on waking.

loop()

void loop() {
	do {
		switch(step.state()) {
		case STATE::INIT:
			sensor.dummy_work_ct_now = 0;
			sensor.dummy_work_ct_max = random(10,1000);
			step.next(STATE::WORK_JOB);
		break;

		...
		}
	} while (step.b_more_loop());
}

The above code is a simplified version of the actual code.

This control structure uses the SM_SIMPLE state machine. It is a loop with do..while() syntax. Inside the loop is a _switch case _ clause, which splits the process according to the state obtained by .state(). State transitions call .next() to rewrite internal variables in the state machine to the new state values.

step.b_more_loop() is set to true if there is a state transition by .next(). The purpose of this is to execute the code of the next state (case clause) without escaping loop() when a state transition occurs.

Below is a description of each state.

STATE::INIT

sensor.dummy_work_ct_now = 0;
sensor.dummy_work_ct_max = random(10,1000);

step.next(STATE::WORK_JOB);

Initialises the sensor values of the dummies. One is determined randomly by an add counter and one by a counter stop value.

STATE::WORK_JOB

if (TickTimer.available()) {
	Serial << '.';
	sensor.dummy_work_ct_now++;
	if (sensor.dummy_work_ct_now >= sensor.dummy_work_ct_max) {
		Serial << crlf;
		step.next(STATE::TX);
	}
}

In the WORK_JOB state, we work on a timer every 1ms; every Tick timer becomes TickTimer.available(); every Tick timer adds a counter and when it reaches dummy_work_ct_max, we move to the next state STATE::TX .

STATE::TX

if (Transmit()) {
	Serial << int(millis()) << ":tx request success!" << crlf;
	step.set_timeout(100);
	step.clear_flag();
	step.next(STATE::WAIT_TX);
} else {
	// normall it should not be here.
	Serial << int(millis()) << "!FATAL: tx request failed." << crlf;
	step.next(STATE::EXIT_FATAL);
}

Call the Transmit() function to request packet transmission. If the request succeeds, the function transits to STATE::WAIT_TXEVENT and waits for the completion of transmission. Here, we use the timeout and flag functions of the SM_SIMPLE state machine to wait for completion (it is simple to determine the value of a variable by changing its value in the waiting loop).

We don't usually expect a single request to fail, but if it does, it will go to the STATE::EXIT_FATAL exception handling state.

You should not sleep at this point, as the packet has not yet been sent. In most cases, you should wait for transmission to complete before continuing.

The Transmit() function returns a MWX_APIRET object, which holds a bool type success or failure and a value of up to 31 bits. It can be evaluated as a bool type, so an if statement will return true if the send request succeeds, or false if it fails.

STATE::WAIT_TX

if (step.is_flag_ready()) {
	Serial << int(millis()) << ":tx completed!" << crlf;
	step.next(STATE::EXIT_NORMAL);
} else if (step.is_timeout()) {
	Serial << int(millis()) << "!FATAL: tx timeout." << crlf;
	step.next(STATE::EXIT_FATAL);
}

Waiting for completion of transmission is judged by setting the flag of the state machine function with on_tx_comp() described later. The timeout is judged by the elapsed time since .set_timeout() was done by calling .is_timeout().

Normally you will get a completion notice whether the transmission succeeds or fails, but it will time out and go to STATE::EXIT_FATAL for exception handling.

STATE::EXIT_NORMAL

SleepNow();

Call SleepNow() to start the sleep process.

STATE::EXIT_FATAL

Serial << crlf << "!FATAL: RESET THE SYSTEM.";
delay(1000); // wait a while.
the_twelite.reset_system();

As a critical error, a system reset is performed.

SleepNow()

void SleepNow() {
	uint16_t u16dur = SLEEP_DUR;
	u16dur = random(SLEEP_DUR - SLEEP_DUR_TERMOR, SLEEP_DUR + SLEEP_DUR_TERMOR);

	Serial << int(millis()) << ":sleeping for " << int(u16dur) << "ms" << crlf;
	Serial.flush();

	step.on_sleep(); // reset status of statemachine to INIT state.

	the_twelite.sleep(u16dur, false);
}

Periodic sleep is performed. The sleep time is set to a constant time blur using the random() function. This is because when the transmission cycles of several devices are synchronized, the failure rate may increase significantly.

Before sleeping, the state of the SM_SIMPLE state machine is set by calling .on_sleep().

Transmit()

MWX_APIRET vTransmit() {
	Serial << int(millis()) << ":vTransmit()" << crlf;

	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 (0x3 send four times in total)
			<< tx_packet_delay(0,0,2); // send packet w/ delay (send first packet with randomized delay from 0 to 0ms, repeat every 2ms)

		// prepare packet payload
		pack_bytes(pkt.get_payload() // set payload data objects.
			, make_pair(FOURCC, 4) // string should be paired with length explicitly.
			, uint32_t(millis()) // put timestamp here.
			, uint16_t(sensor.dummy_work_ct_now) // put dummy sensor information.
		);
		
		// do transmit 
		//return nwksmpl.transmit(pkt);
		return pkt.transmit(); 
	}

	return MWX_APIRET(false, 0);
}

This function requests to send a wireless packet to the parent device with ID=0x00. The data to be stored is a four-character identifier (FOURCC) commonly used in the Act sample, plus the system time [ms] and a dummy sensor value (sensor.dummy_work_ct_now).

The first step is to get an object that contains the transmitted packets. This object can then be manipulated to set the data and conditions for transmission.

if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {

In the mwx library, the if statement is used to get an object and the bool decision of the object is true. Here, a board object is retrieved by the_twelite.network.use<NWK_SIMPLE>(), and a packet object is retrieved by .prepare_tx_packet() of the board object. Failure to retrieve the packet object is not normally expected, but when it does occur, it is when the transmit queue is full and the transmit request cannot be accepted. Since this example is for a single transmission only, the error is limited to an unexpected serious problem.

pkt << tx_addr(0x00) // Destination
		<< tx_retry(0x1) // Number of resends
		<< tx_packet_delay(0,0,2); // Transmission delay

For the resulting pkt object, set the conditions for transmission (destination, retransmission, etc.) using the << operator. tx_addr specifies the destination of the packet. The tx_addr specifies the destination of the packet, the tx_retry specifies the number of retransmissions, and the tx_packet_delay specifies the transmission delay.

pack_bytes(pkt.get_payload() // set payload data objects.
	, make_pair(FOURCC, 4) // string should be paired with length explicitly.
	, uint32_t(millis()) // put timestamp here.
	, uint16_t(sensor.dummy_work_ct_now) // put dummy sensor information.
);	

The payload of a packet is an array of smblbuf<uint8_t> derivatives obtained by pkt.get_payload(). You can set the value of this array directly, but here we use pack_bytes() to set the value.

The maximum length of the payload is 91 bytes in the above example, see NWK_SIMPLE packet structure and maximum length for more information.

This function can be specified by a variable number of arguments. The first parameter is an array object obtained from .get_payload().

  • make_pair(FOURCC,4) : make_pair is from the C++ standard library and creates a std::pair object. It means to write out 4 bytes from the beginning for string type.(This is done to explicitly specify the number of bytes to be written, as the topic of including or excluding the end of an array of string types is confusing)

  • If uint32_t type data is specified, 4 bytes of data are written in big-endian order.

  • The same applies to data of type uint16_t.

It is also possible to write data using a pointer of type uint8_t.

auto&& pay = pkt.get_payload(); // get buffer object.

// the following code will write data directly to internal buffer of `pay' object.
uint8_t *p = pay.begin(); // get the pointer of buffer head.

S_OCTET(p, FOURCC[0]); // store byte at pointer `p' and increment the pointer.
S_OCTET(p, FOURCC[1]);
S_OCTET(p, FOURCC[2]);
S_OCTET(p, FOURCC[3]);

S_DWORD(p, millis()); // store uint32_t data.
S_WORD(p, sensor.dummy_work_ct_now); // store uint16_t data.

pay.redim(p - pay.begin());

The array object obtained from .get_payload() is an array of size 0 with no data stored in it, but it is expanded by writing data to it (actually, writing data to an internal fixed-length buffer and updating the data size in internal management). The final size is the data size of the payload.

Here we use .begin() to get a pointer to uint8_t*, write data using this pointer, and set the last written size with .redim().

functions (macros) such as S_OCTET(), S_WORD(), and S_DWORD() are used to write data, for example, S_OCTET(p, 'H') is the same as *p = 'H'; p++;.

The last .redim() is a procedure to change the size of an array without initializing the buffer. Calling .resize() clears everything to zero.

Finally, .transmit() is called to request sending. The return value is of the type MWX_APIRET. After the request, the actual transmission takes place, which may take several to several tens of milliseconds to complete, depending on the transmission parameters and the size of the transmission. On completion, on_tx_comp() is called.

return pkt.transmit(); 

``MWX_APIRET is a wrapped class of uint32_t type, which uses MSB as a failure success flag and 31 bits as data. It is a return type of pkt.transmit(), and success or failure of the request (cast to bool type) and the ID are stored in the data part (.get_value()).

on_tx_comp()

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

This is a system event that is called when the transmission is complete. Here, it is set to complete by .set_flag().

Other platforms

Other platforms

Build definitions are provided so that some features (serparser, pktparser, Serial object for console) can be built on other platforms. Only the necessary files are cut out.

The build definitions are stored in the {mwx library storage}/stdio folder. See README.md (link is on GitHub) for build instructions.

  • Must be able to compile in C++11.

  • Ability to use C++11 standard library headers (utility, algorithm, functional, iterator, etc.)

  • new/delete/virtual are not used.

  • Memory allocation by new may be used in exceptional cases.

    • In serparser/pktparser, alloc_heap which uses new operator is processed by delete.

      • (Reference) However, the mwx library has been designed on the assumption that delete is not taken into account.

Build definition Makefile

Build definition Makefile

The Makefile is stored in build/Makefile and is pre-defined to build the act by running the make command.

MWSDK 2020-04 automatically detects the .cpp file in the project folder, so there is usually no need to modify the Makefile.

If the source file is to be stored in a subfolder, it will need to be edited.

MWSDK 2019-12 requires you to edit the Makefile if you have more than one .cpp file.

After copying the project folder from another environment, make sure to delete the build/objs_??? folder. If any intermediate files from the other environment remain, make will fail.

(MWSDK 2020-04) You can avoid errors by adding USE_APPDEPS=0 to clean and then running make again.

$ make USE_APPDEPS=0 TWELITE=BLUE clean ... $ make TWELITE=BLUE

``

Parameters for make.

TWELITE=

Specify the build target as BLUE or RED; for TWELITE BLUE, use make TWELITE=BLUE.

all

Run the build. Usually, you can omit this and use make TWELITE=BLUE.

clean

Remove intermediate files from the build. Do this as make TWELITE=BLUE clean.

cleanall

Remove all intermediate files. Do this as make cleanall, the same as removing all of the objs_??? folder in the build folder.

USE_APPDEPS=0 or 1

When set to 1 (the default), the build file is determined based on file dependencies. For example, if there is a change in a header file, the associated source file will be recompiled.

If set to 0, the makefile will not error if there are inconsistent intermediate files left.

Makefile definition

Depending on the size of the act, and when defining behaviours, the source files are usually split and built separately.

One of the build files is {project folder name.cpp}.

If you want to define other files, edit the build/Makefile in your project folder.

The above is an example Makefile with sample PAL_AMB-bhv.

VERSION_???

Specify the version number. This will be reflected in the build result file name.

During compilation, it is passed as a definition like -DVERSION_MAIN=0 -DVERSION_SUB=1 -DVERSION_VAR=0.

Adding source files

(MWSDK 2020-04) If you do not place files in subfolders, you no longer need to specify additions. All .c .cpp files in the project file will be added.

When you add a source file, you need APPSRC_CXX and APP_COMMON_SRC_DIR_ADD?.

If you place source files in a subfolder, you must specify the folder APP_COMMON_SRC_DIR_ADD?.

Append the name of the source file to `APPSRC_CXX'. This file name must not contain a folder name. Anything in a subfolder should also be specified without a folder (i.e. if the same filename is in a subfolder, the build will fail)

Next, specify the search path if the source files are stored in a location other than the project folder. You can set up to four.

The folder specification is relative to the Makefile.

Compile and linker options

A number of other options can be passed to the compiler linker.

act0 .. 4

revision history

Minor corrections will not be listed in this revision history, but only in the revision on GitHub. Please cite corrections as necessary.

Updating

After the release of the TWELITE STAGE distribution package, fixes and additions are stored in the GitHub repository. Please replace the location of the distribution package if necessary.

Updating MWX library code

  1. Click on the link for each release to clone the Git file or download the source code in zip format.

  2. Replace the contents of the following folders.

Update before major release

Updated information prior to major releases may be posted on the above link.

0.2.0 - 2022-03-01

  • changed Wire object that reserves memory in heap area.

  • changed function name from G_OCTET() to G_BYTE()](api-reference/funcs/utility/byte-array-utils.md to avoid name conflict in utils.h.

  • changed an order of vAHI_DioInterruptEnable() in the attachIntDio() for code efficiency.

  • added secondary network behavior the_twelite.network2 to support universal receiver (receive packets from NWK_LAYERED, NWK_SIMPLE or networkless packets in the same executable code.)

  • introduced MWX_Set_Usder_App_Ver() function to set application version during MWX intitialization, mainly for interactive mode.

    • added new[] operators for EASTL

  • pre-compled most of source codes in MWX to quicker compile.

  • fixed: DIO events were being passed on to unrelated ports.

0.1.9 - 2021-12-15

主な改定内容

  • Added an internal procedure to allow output using Serial class objects in Interactive settings mode. (Serial._force_Serial_out_during_intaractive_mode())

0.1.8 - 2021-09-09

Main revisions

  • Serial1port and alternate port were not properly defined.

  • Enabled to change the baud rate of (Serial UART0).

    • If you don't define a callback function, you can use the previous procedure.

  • Wrong definition ID for interactive mode setting<STG_STD> and some default values.

  • Added support for changing the default values of channel and logical device IDs in addition to AppID in interactive mode settings<STG_STD>.

  • added support for setting the_twelite and <NWK_SIMPLE> objects in interactive mode <STG_STD> object for some settings.

  • added support for setting the default value of the number of retransmissions in <NWK_SIMPLE>.

  • Serial(UART0) input and output from the application is not performed while the interactive mode screen<STG_STD> is displayed.

  • added CUE::PIN_SET, PAL???"":PIN_SET (Since it is unnatural to use PIN_BTN for CUEs without buttons)

  • Move namespace of random() to mwx::.

  • MONOSTICK watchdog setting is now done in 32ms units.

  • When sleep was performed using BRD_TWELITE, the pins were not initialized correctly upon recovery.

Sorry. The following has not been translated.

0.1.7 - 2020-12-03

Major Revisions.

  • Added method to receive other packets (without network usage) that are not in NWK_SIMPLE format when using NWK_SIMPLE. Add NWK_SIMPLE::receive_nwkless_pkt() to initialize NWK_SIMPLE. When using this packet information, use only the TWENET C library layer structure by .get_psRxDataApp() and the data array obtained by .get_payload(). Information obtained from other methods of the incoming packet (auto&& rx = the_twelite.receiver.read()) is undefined.

  • Refine get_stream_helper() code and read/write position API.

  • Fixed bugs in smplbuf::get_stream_helper().

0.1.6 - 2020-10-09

Major revisions

  • Modified so that div100(), which calculates the quotient and remainder, can be output to Serial, etc.

  • Changed implementation of smplbuf<> array class. The inheritance from mwx::stream is removed to reduce memory consumption, and a helper class and an inheritance class are defined separately.

  • Added mwx_printf() mwx_snprintf()` function.

  • added the_twelite.stop_watchdog() and the_twelite.restart_watchdog() functions.

  • mwx::stream maintenance: operator bool() is obsolete. Disable timeout when .set_timeout(0xff) is set to 0xff in the read timeout setting. Add definition of << operator.

  • Add scale functions between 8bit and 0..1000 with no division.

  • Added division by 10,100,1000 (quotient and remainder at the same time) div10(), div100(), div1000(). Restricted the value range and composed mainly of multiplication and bit shift.

  • Added corresponding methods for encrypted packets.

    • packet_rx::is_secure_pkt() : determine if received packet is encrypted or not.

    • STG_STD::u8encmode() : Obtain encryption setting in interactive mode.

    • STG_STD::pu8enckeystr() : Obtain encryption key byte sequence in interactive mode.

  • Serial1: Default port is DIO11(TxD), DIO9(RxD) because they are usually assigned to I2C, though DIO14,15 overlap with I2C in the semiconductor specification.

  • The calculation of the baud rate is omitted for the main baud rates.

  • Serial: The proxy functions for Serial: available() and read() are now held only by void*, and the specification memory is reduced by 8 bytes.

  • Add typedef boolean.

  • Network: added support for encryption.

    • The first parameter is the encryption key, and the second parameter is true, so that plaintext packets are also received. packets are also received.

  • Added sensor support for SHT3x and BME280

  • Sensor: added a mechanism to exchange configuration parameters and status in legacy code (C library wrapper class).

  • Sensors: I2C address can be specified for SHT3x and BME280.

  • Configuration: added hide_items(). Unnecessary items can be deleted.

  • Added `H/W UTIL menu' to display DI status, I2C probe, and PAL EEPROM contents.

  • Configuration: Add encryption related menus.

  • I2C related fixes (to improve compatibility with code implemented using TwoWire class)

    • Processing of requestFrom(false) did not work correctly because there was no code to send the NO_STOP message when processing requestFrom(false).

    • Added TwoWire class name alias.

    • Modified not to initialize multiply in begin() process.

    • Added setClock() method (but it is a dummy function and does nothing).

    • Added WIRE_CONF::WIRE_? KHZ added. Added the main configuration values for the bus clock.

0.1.5 - 2020-08-05

Download in bulk

Major revisions

0.1.4 - 2020-07-29 (MWSDK2020_07_UNOFFICIAL)

Download in bulk

Major revisions.

  • Addition of delayMilliseconds() function

  • Addition of digitalReadBitmap() function

  • Improved accuracy of delay().

  • Fixed the problem that Serial1 instance was not defined

  • Fixed problem with Analogue interrupt handler not being called

0.1.3 - 2020-05-29

Support for MWSDK202020_05

  • Duplicate checker duplicate_checker was not removed as expected due to incomplete initialization, etc.

  • The implementation of format() was made less machine-dependent. If 64-bit arguments are included, the number of arguments is limited.

The fix assumes MWSDK2020_05.

Updates are recommended for this fix.

0.1.2 - 2020-04-24

Support for MWSDK2020_04

  • Fixed initialization problem with Timer0..4

  • Changed internal processing of mwx::format()

  • Added experimental code to support interactive mode

This fix assumes MWSDK2020_04.

We recommend an update to this fix.

0.1.1 - 2020-02-28

Fixed problems with handling of relay flags in packets

We recommend an update to this correction.

0.1.0 - 2019-12-23

First release (included in SDL Dec 2019)

PingPong

Send a PING wireless packet from one of the two serially connected TWELITEs and receive a PONG wireless packet back from the other.

This ACT includes.

  • Sending a prompt response from the receipt of a wireless packet

  • Transmission with direct address of the peer

how to use act

Required TWELITE

Two of any of the following.

Explanation of ACT

Include

Declaration section

  • Sample act common declarations

  • Prototype declarations for longer processes (sending and receiving), since they are made into functions

  • Variables for holding data in the application

セットアップ setup()

The general flow of the program is the initial setup of each section and the start of each section.

the_twelite

This object is the core class object for manipulating TWENET.

the_twelite に設定を反映するには << を用います。

Use << to reflect the setting in the_twelite.

  • TWENET::appid(APP_ID) to specify the Application ID.

  • TWENET::channel(CHANNEL) to specify the channel.

  • TWENET::rx_when_idle() Specifies that the receive circuit is open.

The <<, >> operator is originally a bit shift operator, but it is used differently from its meaning.

Next, register the network.

The first line is written in the same way as the board registration, specifying <> as <NWK_SIMPLE>.

The second line specifies <NWK_SIMPLE>, specifying 0xFE (WK_SIMPLE is a Child Node with an unset ID).

The third line specifies the maximum number of relays. This explanation does not touch on relaying, but packets are relayed when operating with multiple units.

Execute the_twelite.begin() at the end of the setup() function.

Analogue

Class object that handles ADCs (analog-to-digital converters).

Initialization Analogue.setup(). The parameter true specifies to wait in place until the ADC circuit is stable.

To start the ADC, call Analogue.begin(). The parameter is a bitmap corresponding to the pin to be ADC'd.

The pack_bits() function is used to specify the bitmap. It is a function with variable number of arguments, each argument specifies a bit position to be set to 1. For example, pack_bits(1,3,5) returns the binary value 101010. This function has the constexpr specification, so if the parameters are constants only, they are expanded to constants.

The parameters are specified as PIN_ANALOGUE::A1 (ADC0) and PIN_ANALOGUE::VCC (module supply voltage).

The second parameter is specified as 50, and the ADC operation is started by default with TickTimer, which is set to

Except for the first time, the ADC is started in an interrupt handler.

Buttons

Detects changes in DIO (digital input) values; Buttons only detect a change in value after the same value has been detected a certain number of times in order to reduce the effects of mechanical button chattering.

Initialization is done with Buttons.setup(). The parameter 5 is the number of detections required to determine the value, but it is the maximum value that can be set. Internally, the internal memory is allocated based on this number.

The start is done with Buttons.begin() The first parameter is the DIO to be detected. The second parameter is the number of detections required to determine the state. The third parameter is the detection interval. Since 10 is specified, the HIGH and LOW states are determined when the same value is detected five times in a row every 10 ms.

The detection of the DIO state in Buttons is done by an event handler. Event handlers are called in the application loop after an interrupt has occurred, so there is a delay compared to interrupt handlers.

Serial

Serial objects can be used without initialization or initiation procedures.

Outputs a string to the serial port. mwx::crlf is a newline character.

loop()

Loop function are called as callback functions from the main loop of the TWENET library. The basic description here is to wait for the object to be used to become available and then process it. This section describes the use of some objects used in ACT.

The main loop of the TWENET library processes incoming packets and interrupt information stored in the FIFO queue in advance as events, after which loop() is called. After exiting loop(), the CPU enters DOZE mode and waits until a new interrupt occurs with low current consumption.

Therefore, code that assumes the CPU is always running will not work well.

Serial

While Serial.available() is true, there is input from the serial port. The data is stored in the internal FIFO queue, so there is some leeway, but it should be read out promptly. To read data, call Serial.read().

Here, the vTransmit() function is called to send a PING packet in response to a 't' key input.

Buttons

It becomes available at the timing when a change in DIO (digital IO) input is detected, and is read by Buttons.read().

The first parameter is a bitmap of the HIGH/LOW of the current DIO, ordered from bit0 to DIO0,1,2,... . and so on, starting from bit 0. For example, for DIO12, HIGH / LOW can be determined by evaluating btn_state & (1UL << 12). If the bit is set to 1, it is HIGH.

When the IO state is determined for the first time, MSB (bit31) is set to 1. The initial determination process is also performed when the device returns from sleep.

The vTransmit() is called at the timing when the button is released except for the initial confirmation. To make the timing of the press (! (btn_state && (1UL << PIN_BTN))) to invert the condition logically.

transmit()

This function requests TWENET to send a wireless packet. At the end of this function, the wireless packet is not yet processed. The actual transmission will be completed in a few ms or later, depending on the transmission parameters. This section describes typical transmission request methods.

Getting Network and Packet Objects

Get a network object with the_twelite.network.use<NWK_SIMPLE>(). Use that object to get a pkt object by .prepare_tx_packet().

Here it is declared in the conditional expression of the if statement. The declared pkt object is valid until the end of the if clause. pkt object gives a response of type bool, which here is true if there is a free space in TWENET's send request queue and the send request is accepted, or false if there is no space.

Settings for sending packets

Packets are configured using the << operator as in the_twelite initialization setup.

  • Specify the destination address in the tx_addr() parameter. If it is 0x00, it means that you are the Child Node and broadcast to the Parent Node, and if it is 0xFE, it means that you are the Parent Node and broadcast to any Child Node.

  • The tx_retry() parameter specifies the number of retransmissions. In the example 3 means that the number of retransmissions is 3, i.e., the packet is sent 4 times in total. Sending only one wireless packet may fail a few percent of the time even under good conditions.

  • tx_packet_delay() Sets the transmission delay. The first parameter is the minimum wait time to start sending and the second is the maximum wait time. The third is the retransmission interval. The third is the retransmission interval, meaning that a retransmission is performed every 20 ms after the first packet is sent.

Data Payload in a Packet

Payload means a loaded item, but in wireless packets it is often used to mean "the main body of data to be sent". In addition to the main body of data, the data in a wireless packet also contains some auxiliary information, such as address information.

For correct transmission and reception, please be aware of the data order of the data payload. In this example, the data order is as follows. Construct the data payload according to this data order.

The data payload can contain 90 bytes (actually a few more bytes).

Every byte in an IEEE802.15.4 wireless packet is precious. There is a limit to the amount of data that can be sent in a single packet. If a packet is split, the cost of the split packet is high because it must take into account transmission failures. Also, sending one extra byte consumes energy equivalent to approximately 16 µs x current during transmission, which can be significant, especially for battery-powered applications. {endhint %}

Let's actually build the data structure of the above data payload. The data payload can be referenced as a container of type simplbuf<uint8_t> via pkt.get_payload(). In this container, we build the data based on the above specification.

It can be written as above, but the MWX library provides an auxiliary function pack_bytes() for data payload construction.

The first parameter of pack_bytes specifies the container. In this case, it is pkt.get_payload().

The parameters after that are variable arguments, specifying as many values of the corresponding type in pack_bytes as needed. The pack_bytes internally calls the .push_back() method to append the specified value at the end.

The third line, make_pair(), is a standard library function to generate std::pair. This is specified to avoid confusion of string types (specifically, whether or not to include null characters when storing payloads). The first parameter of make_pair() is the string type (char*, uint8_t*, uint8_t[], etc.) The second parameter is the number of bytes to store in the payload.

Lines 4, 5, and 6 store values of numeric types (uint8_t, uint16_t, uint32_t). Numeric types such as signed, or even the same numeric type such as char are cast to the three types listed on the left and submitted.

analogRead() and analogRead_mv() get the result of ADC. The former is the ADC value (0..1023) and the latter is the voltage[mv](0..2470). The supply voltage of the module is read internally from the value of the voltage divider resistor, so we use adalogRead_mv() to perform that conversion.

This completes the packet preparation. Now all that remains is to make a request for transmission.

Packets are sent using the pkt.transmit() method of the pkt object.

Although not used in this ACT, the return value contains information about the success or failure of the request and the number corresponding to the request. Use this return value if the process waits until the transmission is complete.

on_rx_packet()

This is the process when there is an incoming packet.

First, the data of the incoming packet is passed as parameter rx. From rx, the address information and data payload of the wireless packet is accessed.

In the next line, the received packet data refers to the source address (32-bit long address and 8-bit logical address) and other information.

In <NWK_SIMPLE>, two types of addresses are always exchanged: an 8-bit logical ID and a 32-bit long address. When specifying the destination, either the long address or the logical address is specified. When receiving, both addresses are included.

The MWX library provides a function expand_bytes() as a counterpart to pack_bytes() used in transmit().

Lines 1 through 3 specify variables to store data.

The first parameter specifies the first iterator of the container (a uint8_t* pointer), which can be retrieved by the .begin() method. The second parameter is the next iterator after the end of the container and can be retrieved with the .end() method.

The third and subsequent parameters enumerate variables. The payloads are read and stored in the order in which they are listed.

This ACT omits error checking, such as if the packet length is wrong. If you want to make the check strict, judge by the return value of expand_bytes().

The return value of expand_bytes() is uint8_t*, but returns nullptr (null pointer) in case of access beyond the end.

The process sends a PONG message if the identifier of the 4-byte string read in msg is "PING".

It then displays information on packets that have arrived.

The format() is used because numeric formatting output is required. helper class that allows the same syntax as printf() for >> operators, but limits the number of arguments to a maximum of 8 (for 32-bit parameters). (A compile error will occur if the limit is exceeded. Note that Serial.printfmt() has no limit on the number of arguments.)

The mwx::crlf specifies a newline character (CR LF), and mwx::flush waits for completion of output. (mxw::flush may be written as Serial.flush())

Designation
Remarks

The acts starting with act0 are the ones introduced in , and although they are simple, we recommend you try them out first.

Name
Remark

Other updates to the MWSDK may be required. Please refer to the release description at the time of the update; see for information on updating the MWSDK.

The source code of the library is available on GitHub (). To replace the source code of the library, please follow the steps below.

Library Name
Dependent version

added (At this time, only Parent Node reception is supported.)

added to describe placement new simply.

added support of

Library Name
Dependent version

Added board support for TWELITE ARIA and sensor definition

Library Name
Dependent version

Added event callbacks to notify received packets () and completed transmission ().

Library Name
Dependent Version

Added board behavior () for TWELITE CUE.

Add EEPROM class object. ()

Samples ()

Added pktparser class ()

Added sample ()

sample serparser/pktparser so that it can be built on other platforms ()

Library Name
Dependent Version

Added NOTICE PAL / PCA9632 support (Description , sample )

Library Name
Dependent Version

()

Added

Channel Manager. Implement

Library Name
Dependent Version

()

Input from serial port -

Digital (button) input -

Analogue input -

connected to UART with products/TWE-Lite-DIP/index.html), etc.

Include <TWELITE> in all ACTs. Here, the simple network should be included.

##############################################################################
# Copyright (C) 2019 Mono Wireless Inc. All Rights Reserved.
# Released under MW-SLA-*J,*E (MONO WIRELESS SOFTWARE LICENSE
# AGREEMENT). 
##############################################################################
# USER PROJECT BUILD DEFINITION.
##############################################################################

#####################################################################
### set TWELITE model
TWELITE ?= BLUE
#TWELITE = RED

#####################################################################
### set application version (MUST SET THIS.)
VERSION_MAIN = 0
VERSION_SUB  = 1
VERSION_VAR  = 0

#####################################################################
### set an additional source file
###   the default file name is dirname.

### for C++ files compiled with g++ (must have .cpp suffix)
APPSRC_CXX += myAppBhvParent.cpp
APPSRC_CXX += myAppBhvParent-handlers.cpp
APPSRC_CXX += myAppBhvChild.cpp
APPSRC_CXX += myAppBhvChild-handlers.cpp

### for C files compiled with gcc (must have .c suffix)
#APPSRC += my_c_file.c

### Additional Src/Include Path
# if set, find source files from given dirs.
#
APP_COMMON_SRC_DIR_ADD1 = ../Parent
APP_COMMON_SRC_DIR_ADD2 = ../Child
#APP_COMMON_SRC_DIR_ADD3 = 
#APP_COMMON_SRC_DIR_ADD4 =

#####################################################################
### set misc option for compiler

### C++ flags passed to g++
# e.g. CXXFLAGS += -DMY_DEFS
#CXXFLAGS +=

### C++/C flags passed to g++/gcc
# e.g. CFLAGS += -DMY_DEFS
#CFLAGS +=

### include opts
# e.g. INCFLAGS += -I../my_common_src/
#INCFLAGS +=

### optimize flag (default is -Os, normally no need to change)
#OPTFLAG=-O2

#####################################################################
### must include mwx.mk (the makefile body part.)
MWSDK_PATH?=$(realpath $(MWSDK_ROOT))
include $(MWSDK_PATH)/MkFiles/mwx.mk
#####################################################################
### set application version (MUST SET THIS.)
VERSION_MAIN = 0
VERSION_SUB  = 1
VERSION_VAR  = 0
APPSRC_CXX += myAppBhvParent.cpp
APPSRC_CXX += myAppBhvParent-handlers.cpp
APPSRC_CXX += myAppBhvChild.cpp
APPSRC_CXX += myAppBhvChild-handlers.cpp
APP_COMMON_SRC_DIR_ADD1 = ../Parent
APP_COMMON_SRC_DIR_ADD2 = ../Child

CXXFLAGS

Specify compilation options for C++ source files.

CFLAGS

Specify compile options for C/C++ source files.

INCFLAGS

Specify the include file for the header file.

OPTFLAGS

Define this if you have a special reason for wanting to apply a compile option other than -Os.

LDFLAGS

Specify linker options. (This is not mentioned in the comments of the Makefile above, but can be specified)

act0

Templates with no processing description

act1

L-tica (flashing LED)

act2

L-tica with timer

act3

L-tica with two timers

act4

LED lighting using a button (switch)

https://code.visualstudio.comcode.visualstudio.com
.../MWSTAGE/              --- TWELITE STAGE distribution dir
        .../MWSDK         --- MWSDK dir
              .../TWENET/current/src/mwx <-- Replace this dir.

mwx

0.2.0

twesettings

0.2.6

TWENET C

1.3.5

mwx

0.1.9

twesettings

0.2.6

TWENET C

1.3.5

mwx

0.1.8

twesettings

0.2.6

TWENET C

1.3.5

mwx

0.1.7

twesettings

0.2.6

TWENET C

1.3.4

mwx

0.1.6

twesettings

0.2.5

TWENET C

1.3.4

auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(0xFE) // set Logical ID. (0xFE means a child device with no ID)
    << NWK_SIMPLE::secure_pkt((const uint8_t*)"0123456789ABCDEF");
    ;

mwx

0.1.5

twesettings

0.2.5

TWENET C

1.3.4

mwx

0.1.4

twesettings

0.2.4

TWENET C

1.3.3

// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
// application ID
const uint32_t APP_ID = 0x1234abcd;

// channel
const uint8_t CHANNEL = 13;

// DIO pins
const uint8_t PIN_BTN = 12;

/*** function prototype */
void vTransmit(const char* msg, uint32_t addr);

/*** application defs */
// packet message
const int MSG_LEN = 4;
const char MSG_PING[] = "PING";
const char MSG_PONG[] = "PONG";
void setup() {
	/*** SETUP section */
	Buttons.setup(5); // init button manager with 5 history table.
	Analogue.setup(true, 50); // setup analogue read (check every 50ms)

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
	nwksmpl << NWK_SIMPLE::logical_id(0xFE) // set Logical ID. (0xFE means a child device with no ID)
	        << NWK_SIMPLE::repeat_max(3);   // can repeat a packet up to three times. (being kind of a router)

	/*** BEGIN section */
	Buttons.begin(pack_bits(PIN_BTN), 5, 10); // check every 10ms, a change is reported by 5 consequent values.
	Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC)); // _start continuous adc capture.

	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- PingPong sample (press 't' to transmit) ---" << mwx::crlf;
}
	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)
// The following statements are not available in the MWX library
#include <iostream>
std::cout << "hello world" << std::endl;
auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(0xFE);
        << NWK_SIMPLE::repeat_max(3);
the_twelite.begin(); // start twelite!
Analogue.setup(true);
Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC), 50); 
Buttons.setup(5);
Buttons.begin(pack_bits(PIN_BTN),
					5, 		// history count
					10);  	// tick delta
Serial << "--- PingPong sample (press 't' to transmit) ---" << mwx::crlf;
void loop() {
	  // read from serial
		while(Serial.available())  {
				int c = Serial.read();
				Serial << mwx::crlf << char(c) << ':';
				switch(c) {
				    case 't':
				    	  vTransmit(MSG_PING, 0xFF);
				        break;
				    default:
							  break;
				}
		}


	// Button press
	if (Buttons.available()) {
		uint32_t btn_state, change_mask;
		Buttons.read(btn_state, change_mask);

		// Serial << fmt("<BTN %b:%b>", btn_state, change_mask);
		if (!(change_mask & 0x80000000) && (btn_state && (1UL << PIN_BTN))) {
			// PIN_BTN pressed
			vTransmit(MSG_PING, 0xFF);
		}
	}
}
		while(Serial.available())  {
				int c = Serial.read();
				Serial << mwx::crlf << char(c) << ':';
				switch(c) {
				    case 't':
				    	  vTransmit(MSG_PING, 0xFF);
				        break;
				    default:
							  break;
				}
		}
	if (Buttons.available()) {
		uint32_t btn_state, change_mask;
		Buttons.read(btn_state, change_mask);
// Serial << fmt("<BTN %b:%b>", btn_state, change_mask);
if (!(change_mask & 0x80000000) && (btn_state && (1UL << PIN_BTN))) {
	// PIN_BTN pressed
	vTransmit(MSG_PING, 0xFF);
void vTransmit(const char* msg, uint32_t addr) {
	Serial << "vTransmit()" << mwx::crlf;

	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
		// set tx packet behavior
		pkt << tx_addr(addr)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
			<< tx_retry(0x3) // set retry (0x3 send four times in total)
			<< tx_packet_delay(100,200,20); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)

		// prepare packet payload
		pack_bytes(pkt.get_payload() // set payload data objects.
			, make_pair(msg, MSG_LEN) // string should be paired with length explicitly.
			, uint16_t(analogRead(PIN_ANALOGUE::A1)) // possible numerical values types are uint8_t, uint16_t, uint32_t. (do not put other types)
			, uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)) // A1 and VCC values (note: alalog read is valid after the first (Analogue.available() == true).)
			, uint32_t(millis()) // put timestamp here.
		);
	
		// do transmit 
		pkt.transmit();
	}
}
	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
		pkt << tx_addr(addr)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
			<< tx_retry(0x3) // set retry (0x3 send four times in total)
			<< tx_packet_delay(100,200,20); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
# Index of first byte: Data type : Number of bytes : Contents

00: uint8_t[4] : 4 : four-character identifier
08: uint16_t   : 2 : ADC value of AI1 (0..1023)
06: uint16_t   : 2 : Voltage value of Vcc (2000..3600)
10: uint32_t   : 4 : millis() system time
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
	, make_pair(msg, MSG_LEN) // string should be paired with length explicitly.
	, uint16_t(analogRead(PIN_ANALOGUE::A1)) // possible numerical values types are uint8_t, uint16_t, uint32_t. (do not put other types)
	, uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)) // A1 and VCC values (note: alalog read is valid after the first (Analogue.available() == true).)
	, uint32_t(millis()) // put timestamp here.
);
pkt.transmit();
void on_rx_packet(packet_rx& rx, bool_t &handled) {
		uint8_t msg[MSG_LEN];
		uint16_t adcval, volt;
		uint32_t timestamp;

		// expand packet payload (shall match with sent packet data structure, see pack_bytes())
		expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
					, msg       // 4bytes of msg
											//   also can be -> std::make_pair(&msg[0], MSG_LEN)
					, adcval    // 2bytes, A1 value [0..1023]
				  , volt      // 2bytes, Module VCC[mV]
					, timestamp // 4bytes of timestamp
        );
		
		// if PING packet, respond pong!
    if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
				// transmit a PONG packet with specifying the address.
        vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
    }

		// display the packet
		Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
                    , rx.get_psRxDataApp()->u32SrcAddr
                    , rx.get_lqi()
                    , rx.get_length()
					, rx.get_psRxDataApp()->u8Seq
                    )
				<< format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
					, msg
					, adcval
					, volt
					, timestamp
					)
               << mwx::crlf
			   << mwx::flush;
	}
while (the_twelite.receiver.available()) {
		auto&& rx = the_twelite.receiver.read();
Serial << format("..receive(%08x/%d) : ",
   rx.get_addr_src_long(), rx.get_addr_src_lid());
uint8_t msg[MSG_LEN];
uint16_t adcval, volt;
uint32_t timestamp;

// expand packet payload (shall match with sent packet data structure, see pack_bytes())
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
		, msg       // 4bytes of msg
								//   also can be -> std::make_pair(&msg[0], MSG_LEN)
		, adcval    // 2bytes, A1 value [0..1023]
	  , volt      // 2bytes, Module VCC[mV]
		, timestamp // 4bytes of timestamp
    );
if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
    vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
}
		Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
                    , rx.get_psRxDataApp()->u32SrcAddr
                    , rx.get_lqi()
                    , rx.get_length()
										, rx.get_psRxDataApp()->u8Seq
                    )
           << format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
                    , msg
                    , adcval
                    , volt
                    , timestamp
                    )
         << mwx::crlf
			   << mwx::flush;
Starting an act - Opening act
here
https://github.com/monowireless/mwx
https://github.com/monowireless/mwx/wiki
NWK_LAYERED
mwx::pnew()
EASTL
BRD_ARIA
SHT4x
on_rx_packet()
on_tx_comp()
https://mwx.twelite.info/v/v0.1.7/boards/cue
https://mwx.twelite.info/v/v0.1.7/api-reference/predefined_objs/eeprom
https://github.com/monowireless/Act_samples/tree/master/Unit_EEPROM
https://mwx.twelite.info/v/v0.1.7/api-reference/classes/pktparser
https://github.com/monowireless/Act_samples/tree/master/Unit_PktParser
https://github.com/monowireless/mwx/tree/master/stdio
https://mwx.twelite.info/v/latest/boards/pal/pal_notice
https://github.com/monowireless/Act_samples/tree/master/Unit_using_PAL_NOTICE
MWSDK2020_08_UNOFFICIAL
README.md
settings behavior (interactive mode function)
chmgr
MWSDK2020_07_UNOFFICIAL
README.md
https://github.com/monowireless/mwx/releases/tag/0.1.3
https://github.com/monowireless/mwx/releases/tag/0.1.2
https://github.com/monowireless/mwx/releases/tag/0.1.1
https://github.com/monowireless/mwx/releases/tag/0.1.0
Serial
Buttons
Analogue
MONOSTICK BLUE or RED
TWELITE DIP
TWELITE R
<NWK_SIMPLE>

Scratch

Template code.

This act includes

  • Sending radio packets ('t' key)

  • Sleep ('s' key)

  • Input from serial port - Serial

  • Digital (button) input - Buttons

setup()

void setup() {
	/*** SETUP section */
	tx_busy = false;

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk	<< NWK_SIMPLE::logical_id(0xFE); // set Logical ID. (0xFE means a child device with no ID)

	/*** BEGIN section */
	Buttons.begin(pack_bits(PIN_BTN), 5, 10); // check every 10ms, a change is reported by 5 consequent values.

	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- Scratch act ---" << mwx::crlf;
}

Set the_twelite to set the application ID APP_ID, the radio channel CHANNEL and the receive presence.

It also generates nwk and specifies the child machine address 0xFE. This address means that this is an unnamed child machine that has not been addressed by a child machine.

The addresses that can be set are 0x00: parent unit, 0x01~0xEF: child unit, 0xFE: unspecified child unit address range.

The address to be specified as the transmission destination is 0x00 for the parent machine, 0x01~0xEF for the specified parent machine address, 0xFE for any child machine address, and 0xFF for any address including the parent machine.

It also initializes the Buttons object. This is a chatter-suppressing algorithm by successive references, which determines HI or LOW of the target port (PIN_BTN only) if the value is the same 5 times in 10ms. pack_bits(N1, N2, ...) pack_bits(N1, N2, ...)' does 1UL<<N1 | 1UL << N2 | ... to make a bitmap.

the_twelite.begin(); // start twelite!

This is the procedure to start the_twelite, it didn't appear in act0..4, but you should call it if you set up the_twelite or register various behaviors.

begin()

void begin() {
	Serial << "..begin (run once at boot)" << mwx::crlf;
}

Called only once after setup() on startup. Only displays a message.

loop()

Input detection of buttons (switches).

if (Buttons.available()) {
	uint32_t bm, cm;
	Buttons.read(bm, cm);

	if (cm & 0x80000000) {
		// the first capture.
	}

	Serial << int(millis()) << ":BTN" << format("%b") << mwx::crlf;
}

The state is determined by continuous reference to Buttons. When the button state changes, it is output serially.

Input from serial.

while(Serial.available()) {
  int c = Serial.read();

	Serial << '[' << char(c) << ']';

  switch(c) {
  case 'p': ... // display millis()
  case 't': ... // transmit radio packet (vTransmit)
        if (!tx_busy) {
					tx_busy = Transmit();
					if (tx_busy) {
						Serial  << int(millis()) << ":tx request success! (" 
										<< int(tx_busy.get_value()) << ')' << mwx::crlf;
 					} else {
						Serial << int(millis()) << ":tx request failed" << mwx::crlf;;
					}
				}
  case 's': ... // sleeping
				Serial << int(millis()) << ":sleeping for " << 5000 << "ms" << mwx::crlf << mwx::flush;
				the_twelite.sleep(5000);
				break;
  }
}

If Serial.available() is true, the input from the serial port is stored. It reads one character from the serial and processes it according to the input character.

Enter t for wireless transmission

When 't' is input, sending is done. In this sample, the tx_busy flag is used to prevent continuous input.

Send requests are stored in a queue up to a certain number of packets, so it is possible to stack requests in the range of the queue (3 packets).

The following is an example of what happens when you don't check if(!tx_busy) and type 'tttt' continuously: the queue fills up at the fourth request and the request fails. The pkt object obtained by .prepare_tx_packet() in Transmit() will be false.

The timing of the transmissions is randomised, so the completion of transmissions is not in the order in which they are requested.

--- Scratch act ---
..begin (run once at boot)
[t]11591:Transmit()
11592:tx request success! (1)
[t]11593:Transmit()
11593:tx request success! (2)
[t]11594:Transmit()
11595:tx request success! (3)
[t]11595:Transmit()
TX QUEUE is FULL
11596:tx request failed
11654:tx completed!(id=2, stat=1)
11719:tx completed!(id=3, stat=1)
11745:tx completed!(id=1, stat=1)

Type s to sleep.

the_twelite.sleep(5000);

The system will sleep for 5000ms=5 seconds. After recovery, wakeup() is executed.

wakeup()

void wakeup() {
	Serial << int(millis()) << ":wake up!" << mwx::crlf;
}

First to be called on sleep wake up. Display of message only.

Transmit()

MWX_APIRET Transmit() {
	Serial << int(millis()) << ":Transmit()" << mwx::crlf;

	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
		// set tx packet behavior
		pkt << tx_addr(0xFF)  // Broadcast communications
			<< tx_retry(0x1)    // 1 resend
			<< tx_packet_delay(100,200,20); // Transmission delay between 100-200 ms, retransmission interval 20 ms

		// Specification of the data to be sent (to be determined for each application)
		pack_bytes(pkt.get_payload()
			, make_pair("SCRT", 4) // 4-character identifier
			, uint32_t(millis())   // Timestamp
		);
		
		// Make a request to send
		return pkt.transmit(); 
	} else {
		// Failed at .prepare_tx_packet() (send queue is full)
		Serial << "TX QUEUE is FULL" << mwx::crlf;
	  return MWX_APIRET(false, 0);
	}
}

This is the minimum procedure for making a transmission request.

When you leave this function, the request has not yet been executed. You need to wait a while. In this example we have set a delay of 100-200ms for the start of the transmission, so the transmission will not start until 100ms at the earliest.

on_tx_comp()

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	Serial 	<< int(millis()) << ":tx completed!"
			<< format("(id=%d, stat=%d)", ev.u8CbId, ev.bStatus) << mwx::crlf;
	tx_busy = false; // clear tx busy flag.
}

Called on completion of a transmission; ev contains the transmission ID and completion status.

on_rx_packet()

void on_rx_packet(packet_rx& rx, bool_t &handled) {
	Serial << format("rx from %08x/%d", 
					rx.get_addr_src_long(), rx.get_addr_src_lid()) << mwx::crlf;
}

When a packet is received, the sender's address information is displayed.

PAL_AMB-usenap

The sample in PAL_AMB is slightly modified so that the waiting time (approx. 50 ms) during sensor data acquisition is replaced by a sleep wait.

Please see the explanation of the ACT in PAL_AMB before the explanation of this ACT.

Explanation of ACT

begin()

The begin() function exits the setup() function (after which TWENET is initialized) and is called just before the first loop().

void begin() {
	sleepNow(); // the first time is just sleeping.
}

The first sleep is performed after setup() ends. Although sensor data acquisition is started during setup(), this result is not evaluated and is not necessarily necessary, since it means that the sensor is run once in advance.

wakeup()

Procedures after waking up. The following process is performed.

  • If the sensor data acquisition start has not yet been performed, the sensor data acquisition is performed and a short sleep is entered.

  • Since the start of sensor data acquisition was performed immediately before, the data is checked and sent wirelessly.

void wakeup() {
	if (!b_senser_started) {
		// delete/make shorter this message if power requirement is harder.	
		Serial	<< mwx::crlf
				<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
				<< mwx::crlf
				<< "..start sensor capture again."
				<< mwx::crlf;

		startSensorCapture();
		b_senser_started = true;

		napNow(); // short period sleep.
	} else {
		Serial << "..wake up from short nap.." << mwx::crlf;

		auto&& brd = the_twelite.board.use<PAL_AMB>();

		b_senser_started = false;

		// tell sensors waking up.
		brd.sns_LTR308ALS.process_ev(E_EVENT_START_UP);
		brd.sns_SHTC3.process_ev(E_EVENT_START_UP);
	}
}

The above branch is controlled by the global variable b_sensor_started. If !b_sensor_started, it starts acquiring a sensor (startSensorCapture()) and goes into a short sleep by napNow(). The time is 100ms.

After returning from sleep by napNow(), the clause b_sensor_started==true is executed. Here, the E_EVENT_START_UP event is notified to the two sensors. This event means that enough time has passed for the sensors to finish acquiring. Based on this notification, sns_LTR308ALS and sns_SHTC3 are made available. After this, it will go to loop() and wireless packets will be sent.

The event that notifies the sensor is used to determine if the required time wait is over. Whether or not the actual time has elapsed depends on whether or not the correct time was set with napNow(). If the wake-up time is short, it is expected that the time elapsed is not enough to meet the required time elapsed and that subsequent processing will result in errors such as sensor data not being available.

napNow()

Perform a very short sleep.

void napNow() {
	uint32_t u32ct = 100;
	Serial << "..nap " << int(u32ct) << "ms." << mwx::crlf;
	the_twelite.sleep(u32ct, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
}

If the second parameter of sleep is set to true, the next wake-up time is adjusted based on the previous sleep wake-up time. This is useful if you always want to wake up every 5 seconds.

If the third parameter is set to true, the sleep mode is set without memory retention. After waking up, wakup() will not be called and the process will be the same as power-on.

The fourth specifies the use of the second wake-up timer. Here, the first is used for normal sleep and the second is used for short sleep. There is no strong reason to use the second timer in this ACT, but if, for example, the user wants to wake up every 5 seconds as described above, using the first timer for a short sleep would reset the counter value, which would complicate the elapsed time correction calculation, so the second timer is used.

Setting too short a sleep time does not balance the energy cost of reinitializing the system after returning from sleep. As a rule of thumb, the minimum time should be 30-50 ms.

PAL_AMB-bhv

BEHAVIOR description sample. For details, please refer to here for details.

About the MWX library

The MWX library is designed to make the programming of TWELITE modules easier and more extensible. Based on the TWENET C library that has been used in the MWSDK, the MWX library has been developed as a library for the application development layer.

   Act (USER APPs)... 
+-----------------------+
| MWX C++ LIB           |
+---------------+       |
| TWENET C LIB  |       |
+------------+----------+
| MAC LAYER  | AHI APIs |
+-----------------------+
| TWELITE HARDWARE      |
+-----------------------+

The name of the MWX library is Mono Wireless C++ Library for TWELITE, where MW comes from MonoWireless, and C++ -> CXX -> double X -> WX, where MW and WX are combined to form MWX.

The code written using this library is called "ACT".

Notation, etc.

This section describes the notation used in this explanation.

auto&&

This is called a universal reference, and is often used in standard libraries. In our library, auto&& is used in most cases

The "auto" is a keyword used to declare local variables (automatic variables) in the C language, but in this case it means to declare them by type inference. auto is useful in situations where the C++ template syntax often results in very cumbersome type names, and at the same time can be implemented without explicitly stating the type name.

The following example uses std::minmax_element, a standard library algorithm for finding the maximum-minimum value for a type v, and declares the resulting return value by auto. In this case, the type deduced by auto will be std::pair<int, int>.

#include <algorithm>
int v[] = { 1, 3, -1, 5, 10 };
auto&& result = std::minmax_element(std::begin(v), std::end(v));
Serial << "min=" << int(result.first)
       << ",max=" << int(result.second);

For the strict meaning of && in auto &&, you will need to refer to technical books, etc. Here, please think of it as "you can declare the return value without worrying about whether it is a reference type (similar to pointer passing in C) or a value.

about namespace

The namespace, inline namespace, and using are used to redefine names and so on. Some of them are abbreviated in the explanation.

Restrictions (TWENET)

The MWX library has not been developed to support all the functions of the underlying libraries and functions (functions in the TWNET C library, microcontroller peripheral functions provided by semiconductor vendors, and IEEE802.15.4 functions).

Restrictions (Using C++)

The MWX library is written in the C++ language, and the act description is also written in C++. However, not all functions are available even in the C++ language. Please pay particular attention to the following points.

  • You can allocate memory with the new and new[] operators, but you cannot destroy the allocated memory, and most C++ libraries that allocate memory dynamically are virtually unusable.

  • The constructor of the global object is not called. Note: If necessary, initialization can be done including a call to the constructor by initializing it as in the initialization function ((void*)&obj_global) class_foo();).

  • exception cannot be used.

  • Unable to use virtual functions.

  • Due to the above limitations, only some of the C++ standard libraries such as STL can be used.

※ This is a description of what we know.

We have not conducted a comprehensive verification of the standard library as to whether it can be used or not, nor what might be available. If you find any inconvenience in operation, please try to implement it in another way.

about the library source code

The source code can be found at the followings:

  • {MWSDK install dir}/TWENET/current/src/mwx

  • https://github.com/monowireless/mwx

API

Act/behavior Programming Interface

The MWX library API may be subject to specification changes in the future for the purpose of improvement.

Parent_MONOSTICK

Parent application (for MONOSTICK)

This act uses MONOSTICK as a parent device. It outputs the data payload of packets from the child machine to the serial port. It can display packets in many samples of sample acts.

This act includes

  • Receiving wireless packets

  • Data interpretation of received packets

  • Interactive mode settings - <STG_STD>

  • Conversion of byte strings to ASCII format - serparser

Act Features

  • Receives packets from the child of the sample act and outputs them to the serial port.

How to use Act

TWELITE and wiring required

Role
Items

Parent Device

Child Device

Sample Act Child Setup (e.g. Slp_Wk_and_Tx, PAL_AMB, PAL_MAG, PAL_MOT???, etc...)

Please check the following default settings at first.

  • Application ID: 0x1234abcd

  • Channel: 13

Act Explanation

Declaration part

includes

// use twelite mwx c++ template library
#include <TWELITE>
#include <MONOSTICK>
#include <NWK_SIMPLE>
#include <STG_STD>

Include board behavior for <MONOSTICK>. This board support includes LED control and watchdog support.

  • <NWK_SIMPLE> Loads the definition of a simple relay net

  • <STG_STD> Loads the interactive mode definition

Other

// application ID
const uint32_t DEFAULT_APP_ID = 0x1234abcd;
// channel
const uint8_t DEFAULT_CHANNEL = 13;
// option bits
uint32_t OPT_BITS = 0;

/*** function prototype */
bool analyze_payload(packet_rx& rx);

Declaration of default values, function prototypes, etc.

setup()

auto&& brd = the_twelite.board.use<MONOSTICK>();
auto&& set = the_twelite.settings.use<STG_STD>();
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

In setup(), first load the <MONOSTICK> board behavior, the <STG_STD> interactive mode behavior, and the <NWK_SIMPLE> behavior using use<>. This procedure is always done in setup().

set << SETTINGS::appname("PARENT"); // Title in the settings screen
set << SETTINGS::appid_default(DEFAULT_APP_ID); // Application ID Default
set << SETTINGS::ch_default(DEFAULT_CHANNEL); // channel default
set << SETTINGS::lid_default(0x00); //LID Default
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);
set.reload(); // Read settings from non-volatile memory
OPT_BITS = set.u32opt1(); // Example of reading (option bits)

The interactive mode is then set up and the settings are read out. The <STG_STD> interactive mode provides standard items, but allows for some customization for each act you create.

  • appname→ Act name that appears in the title line of the configuration screen

  • appid_default→ Default Application ID

  • ch_default→ Default channel

  • lid_default→ Default value of device ID (LID)

  • .hide_items()→ Item Hide Settings

Always call .reload() before reading the configuration values. Each set value has its own method for reading, such as .u32opt1().

the_twelite
	<< set                    // Reflects interactive mode settings
	<< TWENET::rx_when_idle() // Set RF to receive when idle.
	;

// Register Network
nwk << set;		            // Reflects interactive mode settings
nwk << NWK_SIMPLE::logical_id(0x00) // Only the LID is reconfigured.
	;

Some settings can be directly reflected using <STG_STD> objects. In addition, if you want to rewrite a specific value due to a DIP switch setting, for example, you can rewrite the value separately after it is reflected. In the above example, the application ID, channel, radio output, etc. are set in the_twelite object, the LID and the retransmission count are set in the nwkobject, and then the LID is set to 0 again.

brd.set_led_red(LED_TIMER::ON_RX, 200); // RED (on receiving)
brd.set_led_yellow(LED_TIMER::BLINK, 500); // YELLOW (blinking)

Procedures for controlling LED lighting are available in the <MONOSTICK> board behaviour.

The first line sets the red LED to switch on for 200 ms after receiving a radio packet. The first parameter LED_TIMER::ON_RX means when a radio packet is received; the second specifies the lighting time in ms.

The second line specifies the blinking of the LEDs: the first parameter LED_TIMER::BLINK specifies the blinking, the second parameter is the blinking on/off switching time: every 500ms the LEDs are switched on and off (i.e. Repeat blinking with a 1 s cycle).

the_twelite.begin(); // start twelite!

Procedure for starting the_twelite, which did not appear in act0..4, but must be called if you have configured the_twelite or registered various behaviours.

loop()

There is no processing during loop() in this sample.

void loop() {
}

on_rx_packet()

Callback function called when a packet is received. In this example, some output is produced for the received packet data.

void on_rx_packet(packet_rx& rx, bool_t &handled) {  
	Serial << ".. coming packet (" << int(millis()&0xffff) << ')' << mwx::crlf;

  ...
  
	// packet analyze
	analyze_payload(rx);
}

analyze_payload()

The analyze_payload() called at the end of the function contains code to interpret some sample act packets. Refer to the code in correspondence with the packet generation part in the sample act.

bool b_handled = false;

uint8_t fourchars[4]{};
auto&& np = expand_bytes(
	    rx.get_payload().begin(), rx.get_payload().end()
		, fourchars
    );
    
if (np == nullptr) return;

// display fourchars at first
Serial
	<< fourchars 
	<< format("(ID=%d/LQ=%d)", rx.get_addr_src_lid(), rx.get_lqi())
	<< "-> ";
	

This function first reads the four-character identification data into the fourchars[4] array.

Reading is done using the expand_bytes() function.The first and second parameters of this function follow the C++ standard library's practice of giving the first pointer .begin() and the next .end() of the payload section of the incoming packet. The following parameters are variable arguments, giving the data variables to be read. The return value is nullptr in case of an error, otherwise the next interpretation pointer. If interpreted to the end, .end() is returned. The parameter here is uint8_t fourchars[4].

This description only supports the uint8_t[N] type, where the array length N is specified; when using the uint8*, char*, char[] types, etc., the specification must be made using make_pair(char*,int).

char fourchars[5]{}; // Allocate 5 bytes including the terminating character `\0`.
auto&& np = expand_bytes(
	    rx.get_payload().begin(), rx.get_payload().end()
		, make_pair((char *)fourchars, 4)
	);

Processing is then carried out for the 4-byte header. Here, the packets of the sample act Slp_Wk_and_Tx are interpreted and the contents are displayed.

// Slp_Wk_and_Tx
if (!b_handled && !strncmp(fourchars, "TXSP", 4)) {
	b_handled = true;
	uint32_t tick_ms;
	uint16_t u16work_ct;

	np = expand_bytes(np, rx.get_payload().end()
		, tick_ms
		, u16work_ct
	);

	if (np != nullptr) {
		Serial << format("Tick=%d WkCt=%d", tick_ms, u16work_ct);
	} else {
		Serial << ".. error ..";
	}
}

Set b_handled to true so that the other interpreters' decisions are skipped.

"TXSP" packets contain the values of a system timer count of type uint32_t and a dummy counter of type uint16_t. Each variable is declared and read using the expand_bytes() function. The difference from the above is that the first parameter is np as the first pointer to read. The tick_ms and u16work_ct are given as parameters and the value stored in the payload is read as a big-endian format byte sequence.

If the readout is successful, the contents are output and the process is complete.

Define and output your own ASCII format

It is structured by ASCII format in a user-defined order.

smplbuf_u8<128> buf;
mwx::pack_bytes(buf
	, uint8_t(rx.get_addr_src_lid()		// Logical ID of the sender
	, uint8_t(0xCC)											   // 0xCC
	, uint8_t(rx.get_psRxDataApp()->u8Seq)	// Sequence number of the packet
	, uint32_t(rx.get_addr_src_long())	// Serial number of the sender.
	, uint32_t(rx.get_addr_dst())		// destination address
	, uint8_t(rx.get_lqi())			// LQI:Link Quality Indicator
	, uint16_t(rx.get_length())		// Number of following bytes
	, rx.get_payload() 			// data payload
);

serparser_attach pout;
pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());

Serial << "FMT PACKET -> ";
pout >> Serial;
Serial << mwx::flush;

The first line declares a buffer as a local object to store the data sequence before conversion to ASCII format.

The second line uses pack_bytes() to store the data sequence into the buf mentioned earlier. See comments in the source code for the data structure. The pack_bytes() parameter can also be a container of the form smplbuf_u8 (smplbuf<uint8_t, ALLOC>).

The sequence number of the packet is automatically set by <NWK_SIMPLE> and is assigned in the order of the transmitted packets. This value is used for packet duplication detection.

The LQI (Link Quality Indicator) is a value corresponding to the signal strength at the time of reception; the higher the value, the stronger the field strength. However, there is no strict relationship defined between this value and the physical quantity, and even if the LQI is higher relative to the noise in the environment, more noise will also reduce the success rate of communication.

Lines 13, 14 and 17 are the declaration, configuration and output of the serial parser.

Dump output including NWK_SIMPLE header.

The first output (which is prevented from being executed by if(0)) displays all data including the control data of <NWK_SIMPLE>. There are 11 bytes of control data. Normally, the control information is not directly referenced.

serparser_attach pout;
pout.begin(PARSER::ASCII, rx.get_psRxDataApp()->auData, 
    rx.get_psRxDataApp()->u8Len, rx.get_psRxDataApp()->u8Len);

Serial << "RAW PACKET -> ";
pout >> Serial;
Serial << mwx::flush;

// Reference: packet structure of the control unit.
// uint8_t  : 0x01 fixws
// uint8_t  : LID of sender
// uint32_t : Long address (Serial Number) of sender
// uint32_t : Destination address
// uint8_t  : Number of repeating

The first line declares the serial parser for output as a local object. It does not have an internal buffer, but diverts an external buffer and uses the output function of the parser to output the byte sequence in the buffer in ASCII format

The second line sets the buffer for the serial parser. It specifies an already existing data array, i.e. the payload part of the incoming packet. serparser_attach pout declares the serial parser using an already existing buffer. The first parameter of pout.begin() specifies the corresponding format of the parser as PARSER::ASCII, i.e. ASCII format; the second specifies the first address of the buffer; the third specifies the The length of valid data in the buffer and the fourth specifies the maximum length of the buffer. The fourth parameter has the same value as the third, as it is for output and is not used for format interpretation.

Output to the serial port in line 6 using the >> operator.

The Serial << mwx::flush in line 7 is a specification to wait until the output of data that has not been output here is finished. (Serial.flush() is the same process.)

BRD_I2C_TEMPHUMID

This is an Act sample using an I2C sensor device to measure temperature and humidity and send them periodically.

In this sample, using I2C sensors on our product (AMBIENT SENSE PAL or TWELITE ARIA BLUE / RED). However you can also use generic I2C devices by editing receiving/sending procedures. Connect a generic I2C device according to the following diagram.

This Act includes following features:

  • Sending / Receiving wireless packets

  • Configuration with "interactive settings mode" - <STG_STD>

  • Building a state machine with "SM_SIMPLE" - <SM_SIMPLE>

Features

  • Send / Receive I2C commands

  • Sleep periodically for running with a button cell.

Usage

Required TWELITE modules

Type
Example

Parent

Children

Act Explained

Header files

#include <TWELITE>
#include <NWK_SIMPLE>// Network support
#include <STG_STD>   // Interactive settings mode
#include <SM_SIMPLE> // Simple state machines

Header files are <NWK_SIMPLE> (Simple network support), <STG_STD> (Interactive settings mode) and <SM_SIMPLE> (Simple state machines).

Sensor drivers

For your reference, this Act is using SHTC3 (TWELITE AMB PAL) or SHT40 (TWELITE ARIA). You can switch them by editing #ifdef and they have same function interfaces for code portability. Both codes are similar because they were designed by the same manufacturer. (Please #define either USE_SHTC3 or USE_SHT40.)

/*** sensor select, define either of USE_SHTC3 or USE_SHT40  */
// use SHTC3 (TWELITE PAL)
#define USE_SHTC3
// use SHT40 (TWELITE ARIA)
#undef USE_SHT40

Below using the SHTC3.

#if defined(USE_SHTC3)
// for SHTC3
struct SHTC3 {
	uint8_t I2C_ADDR;
	uint8_t CONV_TIME;

    bool setup() { ... }
	bool begin() { ... }
	int get_convtime() { return CONV_TIME; }
	bool read(int16_t &i16Temp, int16_t &i16Humd) { ... }
} sensor_device;

The struct (class) SHTC3 has I2C-sensor-realted procedures. The member variable I2C_ADDR is the I2C address and the CONV_TIME is the waiting time for getting values. The instance created from the SHTC3 is sensor_device.

Methods

Name
Description

setup()

Initialize the sensor instead of the constructor (uncalled due to the limitation of the MWX Library).

begin()

Begin getting values from the sensor. After starting, it is necessary to wait a certain amount of time until the appropriate sensor values are obtained.

get_convtime()

Get the waiting time to acquire the sensor value.

read(int&, int&)

Read the sensor value.

Each procedures are shown below.

setup()

bool setup() {
	// here, initialize some member vars instead of constructor.
	I2C_ADDR = 0x70;
	CONV_TIME = 10; // wait time [ms]
	return true;
}

Set the I2C address and the sensor value acquisition wait time (10 ms for the above) in the member variables.

In principle, these values are fixed values, so there is no need to set them. Valid examples of how to treat them as variables: Managing the conversion time required when operating a sensor with higher precision or selecting an I2C sub-address by setting them.

begin()

bool begin() {
	// send start trigger command
	if (auto&& wrt = Wire.get_writer(I2C_ADDR)) {
		wrt << 0x60; // SHTC3_TRIG_H
		wrt << 0x9C; // SHTC3_TRIG_L
	} else {
		return false;
	}
	return true;
}

Write commands to operate the sensor.

In the MWX Libary, you can choose two different ways to control the I2C line from the Wire class object. This act is using Helper functions.

Wire.get_writer(I2C_ADDR) opens the I2C device and returns a object for reading / writing data. If the device is not available, the object wrt is evaluated as false.

The line wrt << 0x60; is writing a data to the device with the stream operator <<. The operator is often used for writing data in uint8_t.

get_convtime()

int get_convtime() {
	return CONV_TIME;
}

Return the value of CONV_TIME.

read()

bool read(int16_t &i16Temp, int16_t &i16Humd) {
	// read result
	uint16_t u16temp, u16humd;
	uint8_t u8temp_csum, u8humd_csum;
	if (auto&& rdr = Wire.get_reader(I2C_ADDR, 6)) {
		rdr >> u16temp;      // read two bytes (MSB first)
		rdr >> u8temp_csum;  // check sum (crc8)
		rdr >> u16humd;      // read two bytes (MSB first)
		rdr >> u8humd_csum;  // check sum (crc8)
	} else {
		return false;
	}

	// check CRC and save the values
	if (   (CRC8_u8CalcU16(u16temp, 0xff) == u8temp_csum)
		&& (CRC8_u8CalcU16(u16humd, 0xff) == u8humd_csum))
	{
		i16Temp = (int16_t)(-4500 + ((17500 * int32_t(u16temp)) >> 16));
		i16Humd = (int16_t)((int32_t(u16humd) * 10000) >> 16);
	} else {
		return false;
	}

	return true;
}

Get values from the sensor device.

SHTC3 reads the sensor value after waiting a few ms after starting the sensor readout by begin(). The sequence of sensor values is as follows.

Byte
Description

0

Temperature Value (MSB side)

1

Temperature Value (LSB side)

2

CRC8 value of the byte 0 and 1

3

Humidity Value (MSB side)

4

Humidity Value (LSB side)

5

CRC8 value of the byte 3 and 4

In SHTC3, the order of data also changes depending on the parameters given at the start of sensor acquisition, but if you start with the 0x609C command written by the above begin(), temperature data comes first.

In begin(), the data was written out, but here the data is read in. To read the data, similarly, Wire.get_reader() creates a helper object rdr. If there are no errors, rdr returns true in the if clause. The second parameter 6 given to get_reader(I2C_ADDR, 6) is the number of bytes to read. When this number of bytes have been read, the procedure terminates reading the I2C bus. (Some devices may work even if these procedures are omitted, but usually you should give an appropriate value.)

Read is done with the stream operator >>. There are several other read methods. For details, see Helper functions for details. When using the stream operator, enter values into pre-declared variables of type uint8_t, uint16_t, or uint32_t. rdr >> u16temp reads from the 2-byte I2C bus for variables of type uint16_t and stores them in big-endian format (first byte is upper byte).

Finally, 100 times the temperature [°C] and 100 times the humidity [%] are calculated and stored in i16Temp and i16Humd.Refer to the I2C device datasheet for the calculation formula.

setup()

Called once when the TWELITE is started. This function performs various initializations.

void setup() {
	/*** SETUP section */
	...
}

State machine with SM_SIMPLE

// application state defs
enum class STATE : uint8_t {
	INTERACTIVE = 255,
	INIT = 0,
	SENSOR,
	TX,
	TX_WAIT_COMP,
	GO_SLEEP
};

// simple state machine.
SM_SIMPLE<STATE> step;

void setup() {
	...
	/// init vars or objects
	step.setup(); // initialize state machine
	...
}

The state machine (state transition machine) is used to simplify the description in the loop() statement that is called each time. Of course, you do not have to use SM_SMPLE in this example to describe your application.

SM_SIMPLE is implemented in a very short code that allows simple management of transitions to states, timeouts, and flags. The states are defined in advance as enumerations. In the above example, it is enum class STATE. The entity of the state machine is declared as SM_SMPLE<STATE> step with the predefined enum STATE as a parameter.

Set BEHAVIOR

void setup() {
	...
	/// load board and settings objects
	auto&& set = the_twelite.settings.use<STG_STD>(); // load save/load settings(interactive settings mode) support
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>(); // load network support
	...
}

BEHAVIOR is a collection of functions used in a program. It describes how to behave when various events occur.

In this example, we use two kinds of BEHAVIOR: interactive settings mode screen <STG_STD> and simple relay network <NWK_SMPLE>.

Interactive settings mode STG_STD

	...
	/// configure settings
	// configure settings
	set << SETTINGS::appname(FOURCHARS);
	set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
	set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
	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);

In order to make the interactive settings mode configuration items suitable for the application you are describing, initialize STG_STG. /settings/stg_std.md) to set the initial settings.

  • SETTINGS::appname : application name (string). It will be displayed on the first line on the interactive settings mode screen. Use a minimum string since there is not enough room for the number of characters on the screen.

  • SETTINGS::appid_default : Default application ID. Execute this if you want your own application to have its own defined application ID.

  • SETTINGS::ch_default : channel default. Execute if you want your own application to have its own default channel.

Then set.hide_items() removes unnecessary settings items on the screen in the default interactive settings mode. If you do not mind displaying them all, you do not need to make this call.

	// if SET(DIO12)=LOW is detected, start with intaractive mode.
	if (digitalRead(PIN_DIGITAL::DIO12) == PIN_STATE::LOW) {
		set << SETTINGS::open_at_start();
		step.next(STATE::INTERACTIVE);
		return;
	}

This description starts in interactive settings mode when the DIO12 pin is LOW (GND level) and the device is powered up or reset. Read the pin status with digitalRead() and reflect SETTINGS::open_at_start().

Set the state of the state machine to STATE::INTERACTIVE, since it would be inconvenient if normal application processing is performed during interactive settings mode. In this state, no input or other processing is performed and the application stays in the same state.

	// load values
	set.reload(); // load from EEPROM.
	OPT_BITS = set.u32opt1(); // this value is not used in this example.

	// LID is configured DIP or settings.
	LID = set.u8devid(); // 2nd is setting.
	if (LID == 0) LID = 0xFE; // if still 0, set 0xFE (anonymous child)

Finally, load the data in interactive settings mode. By calling set.reload(), data written to the EEPROM is read. If no setting is made and there is no information in the EEPROM, it can be read as the specified value.

Here, option bits set.u32opt1() and 8-bit logical ID set.u8devid() are read. child machine).

	/// configure system basics
	the_twelite << set; // apply settings (from interactive settings mode)
	nwk << set; // apply settings (from interactive settings mode)
	nwk << NWK_SIMPLE::logical_id(LID); // set LID again (LID can also be configured by DIP-SW.)
	...

Finally, the_twelite and nwk reflect (some of) the configuration information. This reflects information essential for wireless communication, such as application ID and channel. There is no explicit read code for these settings in the above, but set.reload() reads the settings to the specified values if they are not set, or to the set values if they are.

Initialize peripherals

	/*** BEGIN section */
	Wire.begin(); // start two wire serial bus.

Initialization for the I2C device.

Start MWX

	// let the TWELITE begin!
	the_twelite.begin();

	/*** INIT message */
	Serial << "--- TEMP&HUMID:" << FOURCHARS << " ---" << mwx::crlf;
	Serial	<< format("-- app:x%08x/ch:%d/lid:%d"
					, the_twelite.get_appid()
					, the_twelite.get_channel()
					, nwk.get_config().u8Lid
				)
			<< mwx::crlf;
	Serial 	<< format("-- pw:%d/retry:%d/opt:x%08x"
					, the_twelite.get_tx_power()
					, nwk.get_config().u8RetryDefault
					, OPT_BITS
			)
			<< mwx::crlf;

the_twelite.begin() is a procedure that declares the completion of the initialization of the MWX library. Without this procedure, the MWX library will not work properly.

Messages at startup, etc. are also displayed here.

loop()

void loop() {
	do {
		switch (step.state()) {
		 // states
		case STATE::INIT:
		...
		break;
		...
		}
	while(step.b_more_loop());
}

The loop() is controlled by the SM_SIMPLE state machinestep for control. The purpose is to concisely express the sequence of steps from sleep recovery, sensor value acquisition, wireless packet transmission, waiting for transmission to complete, and sleep.

The control structure of the above do while statement is described below. The state is determined by step.state(). The conditional expression of while is step.b_more_loop(). This is because there are cases where you want to process continuously without leaving loop() when a state transitions from one state to another. In other words, if you transition to another state and leave the switch clause, the case clause of the next state will be called. Be careful with this behavior.

case STATE::INTERACTIVE:

It is not convenient for the main loop to operate during interactive settings mode, so it is fixed in this state.

case STATE::INIT:

// start sensor capture
sensor_device.begin();
step.set_timeout(sensor_device.get_convtime()); // set timeout
step.next(STATE::SENSOR);

Starts acquiring sensor data. The set_timeout() statement waits for the sensor to acquire data.

For a sensor that waits for a very long time, it is possible to extend the battery life by writing a process to sleep once here, but this example is omitted because of the complexity of the structure. If necessary, you can use Example of waiting for sleep.

case STATE::SENSOR:

if (step.is_timeout()) {
	// the sensor data should be ready (wait some)
	sensor_device.read(sensor.i16temp, sensor.i16humid);

	Serial << "..finish sensor capture." << mwx::crlf
		<< "     : temp=" << div100(sensor.i16temp) << 'C' << mwx::crlf
		<< "       humd=" << div100(sensor.i16humid) << '%' << mwx::crlf
		;
	Serial.flush();

	step.next(STATE::TX);
}

Get the sensor value by sensor_device.read() and store the value in the sensor structure.

First, a timeout check is performed by step.is_timeout(). The starting point of timeout is step.set_timeout(). If the timeout does not occur, the if clause is not executed and loop() is left as is. The next hardware event (often a system timer, TickTimer, which generates an interrupt every 1 ms. /api-reference/predefined_objs/ticktimer.md), which generates an interrupt every 1ms), the TWELITE microcontroller will be in DOZE mode, where the CPU is in low-power standby.

As a wireless sensor, there is no need to output the result to the serial port of TWELITE on the sensor side, but the acquired value is displayed on the serial port to facilitate operation check. Here, Serial.flush() is executed to wait for output, which is a description assuming that the serial port output does not finish before TWELITE goes to sleep. This process also causes battery drain, so either do not do Serial.flush() or do not output to the serial port.

The div100() is a low-cost function to divide by 100.

case STATE::TX:

step.next(STATE::GO_SLEEP); // set default next state (for error handling.)

// get new packet instance.
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
	...
}

Describes a communication procedure. In this state, there is no wait processing, etc., and the program transitions to the next state immediately after processing is executed. The reason why step.next(STATE::GO_SLEEP) is written in advance is to avoid writing the same description in all places, since errors are detected in multiple places.

In if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()), the process is to create an object for the packet to be sent and execute the if clause if the object is successfully created.

// 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

First, configure the transmission settings. The destination tx_addr(0x00) is set to the parent machine 0x00, the number of retransmissions tx_retry(0x1) is set to 1, and the packet delay setting tx_packet_delay(0, 0, 2) is set to 0 delay for the first transmission and 2 ms for the retransmission interval. The packet delay is set to 0 for the first transmission and 2ms for the retransmission interval.

pack_bytes(pkt.get_payload()
	, make_pair(FOURCHARS, 4)
	, uint16_t(sensor.i16temp)
	, uint16_t(sensor.i16humid)
	);

The identifier FOURCHARS and the sensor data are stored in the payload part of the packet. Of the values obtained, the temperature value is int16_t , but is cast to uint16_t because the data structure of the outgoing packet is to be stored unsigned.

// do transmit
MWX_APIRET ret = pkt.transmit();

if (ret) {
	step.clear_flag(); // waiting for flag is set.
	step.set_timeout(100); // set timeout
	step.next(STATE::TX_WAIT_COMP);
}

Call pkt.transmit() to request a transmission. The MWX library will process the request at the appropriate time.

If the send request is successful, ret will be true. Initialize the flags to determine completion step.clear_flag(), set a timeout step.set_timeout(100) to handle unexpected errors such as failed transmissions, and set the next state to STATE::TX_WAIT_COMP (see STATE::GO_SLEEP is overridden).

case STATE::TX_WAIT_COMP:

Here, it waits for the completion of transmission. Judgment of timeout (in case of error) or transmission completion event is made.

if (step.is_timeout()) { // maybe fatal error.
	the_twelite.reset_system();
}
if (step.is_flag_ready()) { // when tx is performed
	Serial << "..transmit complete." << mwx::crlf;
	Serial.flush();
	step.next(STATE::GO_SLEEP);
}

STATE::GO_SLEEP:

Processes the sleepNow() function. By calling this function, the TWELITE wireless microcontroller goes to sleep.

on_tx_comp()

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

This is a system event called when transmission is completed. Here .set_flag() is called to set the flag of step.

sleepNow()

Procedures for going to sleep.

void sleepNow() {
	step.on_sleep(false); // reset state machine.

	// randomize sleep duration.
	uint32_t u32ct = 1750 + random(0,500);

	// output message
	Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf;
	Serial.flush(); // wait until all message printed.

	// do sleep.
	the_twelite.sleep(u32ct);
}

Initialize the state of the state machine by .on_sleep(false) before sleep. The parameter false' starts from STATE::INIT(=0)` after returning from sleep.

Here, the time to wake up is set between 1750ms and 2250ms by a random number. This avoids continuous collisions with packets from other devices transmitting at a similar period.

If the cycles are exactly the same, packets from each other will collide and communication will be difficult. Usually, the timer cycles shift with each other over time, so that communication is restored after a short period of time, and then collisions occur again after another period of time.

Lines 8 and 9, this example goes to sleep waiting for output from the serial port. Usually, we want to minimize energy consumption, so we minimize (or eliminate) output from the serial port before sleep.

Line 12, to enter sleep, call the_twelite.sleep(). In this call, the pre-sleep procedures of the hardware on the board are performed.

The sleep time is specified in ms as a parameter.

TWELITE PAL must always wake up once within 60 seconds and reset the watchdog timer. The sleep time must be specified not to exceed 60000.

wakeup()

When the program wakes up from sleep, wakeup() is called. After that, loop() is called each time. Before wakeup(), each peripheral such as UART and devices on the board are woken up. For example, it restarts the LED lighting control.

void wakeup() {
	Serial	<< mwx::crlf
			<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
			<< mwx::crlf
			<< "..start sensor capture again."
			<< mwx::crlf;
	...
}

PAL_AMB

Environmental Sensor Pal AMBIENT SENSE PAL is used to acquire sensor values.

This ACT includes

  • Sending and receiving wireless packets

  • Configuring settings via Interactive settings mode - <STG_STD>

  • State transition control by state machine - <SM_SIMPLE>

  • <PAL_AMB>Board operation with board behavior

ACT FEATURES.

  • Uses the environmental sensor PAL AMPIENT SENSE PAL to acquire sensor values.

  • Use the sleep function to operate with coin cell batteries.

how to use act

Required TWELITE

Role
Example

Parent Node

Child Node

Explanation of ACT

Include

#include <TWELITE>
#include <NWK_SIMPLE>// network support
#include <PAL_AMB> // PAL_AMB
#include <STG_STD> // Interactive settings mode
#include <SM_SIMPLE> // Simple State Machine

Environment sensor pal <PAL_AMB>) include board behavior.

setup()

void setup() {
	/*** SETUP section */
	step.setup(); // State machine initialization
		
	// Load PAL_AMB board behavior
	auto&& brd = the_twelite.board.use<PAL_AMB>();
	
	// Load Interactive settings mode
	auto&& set = the_twelite.settings.use<STG_STD>();
	set << SETTINGS::appname(FOURCHARS);
	set << SETTINGS::appid_default(APP_ID); // set default appID
	set.hide_items(E_STGSTD_SETID::POWER_N_RETRY, 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);

	// Activate Interactive settings mode when SET pin is detected
	if (digitalRead(brd.PIN_BTN) == PIN_STATE::LOW) {
		set << SETTINGS::open_at_start();
		step.next(STATE::INTERACTIVE);
		return;
	}

	// Read data from Interactive settings mode
	set.reload();
	APP_ID = set.u32appid();
	CHANNEL = set.u8ch();
	OPT_BITS = set.u32opt1();

	// Determine LID from DIP switches and Interactive settings mode
	LID = (brd.get_DIPSW_BM() & 0x07); // 1st priority is DIP SW
	if (LID == 0) LID = set.u8devid(); // 2nd is setting.
	if (LID == 0) LID = 0xFE; // if still 0, set 0xFE (anonymous child)
	
	// LED initialization
	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

	// the twelite main object.
	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL); // set channel (pysical channel)

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk << NWK_SIMPLE::logical_id(u8ID); // set Logical ID. (0xFE means a child device with no ID)

	/*** BEGIN section */
	Wire.begin(); // start two wire serial bus.
	Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC)); // _start continuous adc capture.

	the_twelite.begin(); // start twelite!

	startSensorCapture(); // start sensor capture!

	/*** INIT message */
	Serial << "--- PAL_AMB:" << FOURCHARS << " ---" << mwx::crlf;
}

The first step is to initialize variables, etc. Here we are initializing the state machine step.

First, the board support <PAL_AMB> is registered. The sensors and DIOs are initialized when the board support is initialized. The reason for doing this first is that it is common to check the status of the board's DIP SW, etc., and then configure network settings, etc.

auto&& brd = the_twelite.board.use<PAL_AMB>();

The next step is to initialize and read out the Interactive settings mode.

// Load Interactive settings mode
auto&& set = the_twelite.settings.use<STG_STD>();
set << SETTINGS::appname(FOURCHARS);
set << SETTINGS::appid_default(APP_ID); // set default appID
set.hide_items(E_STGSTD_SETID::POWER_N_RETRY, 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 pin is detected, activate Interactive settings mode
if (digitalRead(brd.PIN_BTN) == PIN_STATE::LOW) {
	set << SETTINGS::open_at_start();
	step.next(STATE::INTERACTIVE);
	return;
}

// Read data from Interactive settings mode
set.reload();
APP_ID = set.u32appid();
CHANNEL = set.u8ch();
OPT_BITS = set.u32opt1();

// Determine LID from DIP switches and Interactive settings mode
LID = (brd.get_DIPSW_BM() & 0x07); // 1st priority is DIP SW
if (LID == 0) LID = set.u8devid(); // 2nd is setting.
if (LID == 0) LID = 0xFE; // if still 0, set 0xFE (anonymous child)

Here you can get the set object, reflect the application name, reflect the default Application ID, and delete unnecessary items in the settings menu.

Next, the state of the SET pin is read. Since this sample operates intermittently in sleep mode, Interactive settings mode transition by +++ input is not possible. Instead, the sample transitions to Interactive settings mode with the SET pin = LO state at startup. In this case, SETTINGS::open_at_start() is specified, which means that the interactive mode screen will be displayed as soon as setup() is finished.

Finally, .reload() is executed to read the set values from the EEPROM. The configuration values are copied to each variable.

Next, configure the LED settings. (In an application that sleeps and wakes for a short period of time, this is almost the same as setting the LED to turn on during wake-up.)

	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

Since this ACT exclusively transmits wireless packets, the TWENET configuration does not include a specification (TWENET::rx_when_idle()) to open the receive circuit during operation.

	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL); // set channel (pysical channel)

The sensors on the board use the I2C bus, so start using the bus.

Wire.begin(); // start two wire serial bus.

Starts the acquisition of a sensor on the board. See the description of startSensorCapture().

startSensorCapture();

loop()

void loop() {
	auto&& brd = the_twelite.board.use<PAL_AMB>();

	do {
		switch (step.state()) {
		 // Behavior of each state
		case STATE::INIT:
		...
		break;
		...
		}
	while(step.b_more_loop());
}	

The loop() is controlled by the SMSMSIMPLE_SIMPLE state machinestep for control. This is to concisely represent the sequence of events from sleep recovery, sensor value acquisition, wireless packet transmission, waiting for transmission to complete, and sleep. In the combat of the loop, a brd object is acquired.

case STATE::INTERACTIVE:

It is not convenient to have the main loop running during Interactive settings mode, so it is fixed in this state.

case STATE::INIT:

brd.sns_SHTC3.begin();
brd.sns_LTR308ALS.begin();

step.next(STATE::SENSOR);

Start sensor data acquisition.

case STATE::SENSOR:

	if (!brd.sns_LTR308ALS.available()) {
		brd.sns_LTR308ALS.process_ev(E_EVENT_TICK_TIMER);
	}

	if (!brd.sns_SHTC3.available()) {
		brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
	}

The sensors on the board can be accessed with the name .sns_LTR308ALS or .sns_SHTC3, and operations are performed on this object. Wait for the sensor to complete. If the sensor has not yet been acquired (.available() is false), a time elapsed event (.process_ev(E_EVENT_TICK_TIMER)) is sent to the sensor.

When the above two sensors become available, the sensor value is retrieved and a transition is made to STATE TOK_TX.

// now sensor data is ready.
if (brd.sns_LTR308ALS.available() && brd.sns_SHTC3.available()) {
	sensor.u32luminance = brd.sns_LTR308ALS.get_luminance();
	sensor.i16temp = brd.sns_SHTC3.get_temp_cent();
	sensor.i16humid = brd.sns_SHTC3.get_humid_per_dmil();

	Serial << "..finish sensor capture." << mwx::crlf
		<< "  LTR308ALS: lumi=" << int(sensor.u32luminance) << mwx::crlf
		<< "  SHTC3    : temp=" << div100(sensor.i16temp) << 'C' << mwx::crlf
		<< "             humd=" << div100(sensor.i16humid) << '%' << mwx::crlf
		;
	Serial.flush();

	step.next(STATE::TX);
}

The illuminance sensor can be obtained with .get_luminance() : uint32_t.

The temperature and humidity sensor can be obtained as follows.

  • .get_temp_cent() : int16_t : temperature with 1℃ as 100 (2560 for 25.6℃)

  • .get_temp() : float : float value (25.6 for 25.6 ℃)

  • .get_humid_dmil() : int16_t : humidity at 1% as 100 (5680 for 56.8%)

  • .get_temp() : float : float value (56.8 for 56.8%)

case STATE::TX:

The transmission procedure is the same as in the other ACT samples. Here, the settings are set to minimize one retransmission and retransmission delay.

	pkt << tx_addr(0x00)  // Parent Node 0x00
		<< tx_retry(0x1)    // 1 retry
		<< tx_packet_delay(0, 0, 2); // minimul delay

The identifier FOURCHARS and the sensor data are stored in the payload part of the packet. Of the values obtained, the temperature value is int16_t, but is cast to uint16_t because the data structure of the outgoing packet is to be stored unsigned.

pack_bytes(pkt.get_payload() 
	, make_pair(FOURCHARS, 4)  
	, uint32_t(sensor.u32luminance)
	, uint16_t(sensor.i16temp)
	, uint16_t(sensor.i16humid)
);

Requests transmission. If the send request succeeds, prepare the send completion city. Specify .clear_flag() to wait for the completion event and set_timeout(100) for timeout in case of emergency. The unit of 100 in the parameter is milliseconds [ms].

// do transmit
MWX_APIRET ret = pkt.transmit();

if (ret) {
	step.clear_flag(); // waiting for flag is set.
	step.set_timeout(100); // set timeout
	step.next(STATE::TX_WAIT_COMP);
}

case STATE::TX_WAIT_COMP:

This section determines timeouts and transmission completion events.

if (step.is_timeout()) { // maybe fatal error.
	the_twelite.reset_system();
}
if (step.is_flag_ready()) { // when tx is performed
	Serial << "..transmit complete." << mwx::crlf;
	Serial.flush();
	step.next(STATE::GO_SLEEP);
}

STATE::GO_SLEEP:

Processes sleepNow().

on_tx_comp()

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

This is a system event called when transmission is complete. Here, .set_flag() is used to indicate completion.

sleepNow()

This section includes a collection of procedures for going to sleep.

void sleepNow() {
	step.on_sleep(false); // reset state machine.

	// randomize sleep duration.
	uint32_t u32ct = 1750 + random(0,500);

	// output message
	Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf;
	Serial.flush(); // wait until all message printed.
	
	// do sleep.
	the_twelite.sleep(u32ct);
}

Initialize the state of the state machine by .on_sleep(false) before sleep. The parameter false' starts from STATE::INIT(=0)` after returning from sleep.

Here, the time to wake up is set between 1750ms and 2250ms by a random number. This avoids continuous collisions with packets from other devices transmitting at a similar period.

If the cycles are exactly the same, packets from each other will collide and communication will be difficult. Usually, the timer cycles shift with each other over time, so that communication is restored after a short period of time, and then collisions occur again after another period of time.

Lines 8 and 9, this example goes to sleep waiting for output from the serial port. Usually, we want to minimize energy consumption, so we minimize (or eliminate) output from the serial port before sleep.

Line 12, to enter sleep, call the_twelite.sleep(). In this call, the pre-sleep procedures of the hardware on the board are performed. For example, LEDs are turned off.

The sleep time is specified in ms as a parameter.

TWELITE PAL must always wake up once within 60 seconds to reset the watchdog timer. The sleep time must be specified not to exceed 60000.

wakeup()

When the program wakes up from sleep, wakeup() is called. After that, loop() is called each time. Before wakeup(), each peripheral such as UART and devices on the board are woken up. For example, it restarts the LED lighting control.

void wakeup() {
	Serial	<< mwx::crlf
			<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
			<< mwx::crlf
			<< "..start sensor capture again."
			<< mwx::crlf;

Applications

Reduction of energy consumption

ACT PAL_AMB-UseNap can operate with lower energy consumption by sleeping while waiting for sensor data acquisition.

WirelessUART

WirelessUART performs serial communication.

ACT Features.

  • Communicate between two UART-connected TWELITEs in ASCII format.

how to use act

Required TWELITE

Two of the following devices serially connected to a PC.

Explanation of ACT

setup()

Interactive settings mode is initialized. This sample provides two or more devices that have different logical device IDs (LIDs) from each other.

loop()

When data input from the serial is received, one byte is input to the serial parser. When the ASCII format is accepted to the end, SerialParser.parse() returns true.

The SerialParser can access the internal buffer with smplbuf. In the example above, the first byte of the buffer is taken as the destination address, and the second byte to the end is passed to the transmit() function.

on_rx_packet()

When a packet is received, a buffer smplbuf_u8<128> buf containing the payload followed by the source as the first byte is created and output serially from the serial parser serparser_attach pout for output.

command for testing

Test data must be entered into the terminal using the paste function. This is because there is a timeout for input.

Note: To paste in TWE Programmer or TeraTerm, use Alt+V.

CR LF is required at the end of the input.

At first try a series ending in X where CR LF can be omitted. If no terminating string is entered, the series will be ignored.

example

Send 00112233 to any Child Node.

example

Send AABBCC00112233 to Child Node #3.

example

Sent to any Parent Node or Child Node (0xFF) and to the Parent Node (0x00).

BRD_APPTWELITE

IO communication (basic function of standard application App_Twelite)

This is a sample using board support <BRD_APPTWELITE>, which assumes the same wiring as required by App_TweLite.

This ACT includes:

  • Transmission and reception of wireless packets

This sample cannot communicate with App_TweLite.

functions of ACT

  • Read M1 to determine the parent or child unit.

  • Reads the value of DI1-DI4; the Buttons class reduces the effect of chattering, so that changes are notified only when the values are the same consecutively. Communication is performed when there is a change.

  • Reads the value of AI1-AI4.

  • Sends the values of DI1-4, AI1-4, and VCC to a child unit if it is the parent unit, or to the parent unit if it is a child unit, every DI change or every 1 second.

  • Set to DO1-4 or PWM1-4 depending on the value of the received packet.

How to use ACT

Required TWELITE and wiring example

Explanation of ACT

include part

It also includes <STG_STD> to add Interactive settings mode.

declaration part

  • sample-act common declaration

  • Its prototype declarations (send and receive), since the longer process is functionalized

  • Variables for holding data in the application

setup()

The general flow of the program is the initial setup of each section and the start of each section.

Registration of various BEHAVIOR objects

Register BEHAVIOR objects to determine system behavior. It can be an Interactive settings mode, board support, or a network description of wireless packets.

It will not work unless registered within setup().

Configure the Interactive settings mode

Initialize the Interactive settings mode. First, a set object is obtained. Then, the following process is performed.

  • Set the application name to "BRD_APPTWELITE" (used in the menu)

  • Rewrite default Application ID and CHANNEL values

  • Delete unnecessary items

  • reads configuration values saved by set.reload().

  • Copy the values of OPT_BITS and LID into the variables

Reflection of the read Interactive settings mode settings is described below.

Below is an example screen. + + + and + three times with a pause of 0.2 to 1 second to bring up the Interactive settings mode screen.

Option Bits are not used in this sampler, although they are displayed in the menu.

the_twelite

This object behaves as the core of TWENET.

Register a board (this ACT registers <BRD_APPTWELITE>). Specify the name of the board you want to register with <> after use as follows.

The return value obtained by universal reference (auto&&) is a board object of reference type. This object contains board-specific operations and definitions. The following example uses the board object to check the status of the M1 pin: if the M1 pin is LO, set LID = 0, i.e., the Parent Node address.

Initial configuration is required to make the the_twelite work. Application ID and wireless CHANNEL settings are mandatory.

Use << to apply the setting to the_twelite.

  • TWENET::rx_when_idle() Specification to open the receive circuit.

The <<, >> operator is originally a bit shift operator, but it is used differently from its meaning. Within the MWX library, the above settings and serial port input/output are used in the library, following the C++ standard library's use of input/output.

Next, register the network.

The first line is written in the same way as for board registration, where <> is <NWK_SIMPLE>.

The second and third lines are settings for <NWK_SIMPLE>. The first one reflects the value of the settings in Interactive settings mode. The items to be reflected are LID and the number of retransmissions. In this application, LID is set again in the third line because LID=0 may be set depending on the state of the M1 pin.

Analogue

Class object that handles ADCs (analog-to-digital converters).

The Initialization is performed by Analogue.setup(). The parameter true specifies to wait in place until the ADC circuit is stable; the second parameter specifies to start the ADC synchronously with Timer0.

To start the ADC, call Analogue.begin(). The parameter is a bitmap corresponding to the pin of the ADC target.

The pack_bits() function is used to specify a bitmap. It is a function with variable number of arguments, each argument specifies a bit position to be set to 1. For example, pack_bits(1,3,5) returns the binary value 101010. This function has the constexpr specification, so if the parameters are constants only, they are expanded to constants.

Parameters and BRD_APPTWELITE:: specified as PIN_AI1..4 are defined, corresponding to AI1..AI4 used in App_Twelite. AI1=ADC1, AI2=DIO0, AI3=ADC2, AI4=DIO2 and so on are assigned. PIN_ANALOGUE. PIN_ANALOGUE:: defines the list of pins available for ADC.

Except for the first time, the ADC is started in the interrupt handler.

Buttons

Detects changes in DIO (digital input) values; Buttons only detect a change in value after the same value has been detected a certain number of times in order to reduce the effects of chattering (sliding) on mechanical buttons.

Initialization is done with Buttons.setup(). The parameter 5 is the number of detections required to determine the value, but it is the maximum value that can be set. Internally, the internal memory is allocated based on this number.

Start is done with Buttons.begin() The first parameter is the DIO to be detected. The second parameter is the number of detections required to determine the state. Since 4 is specified, the HIGH and LOW states are determined when the same value is detected five times in a row every 4ms.

Detection of the DIO state in Buttons is done by an event handler. Event handlers are called in the application loop after an interrupt occurs, so there is a delay compared to interrupt handlers.

Timer0

Since App_Twelite uses a timer origin to control the application, the same timer interrupt/event should be run in this ACT. Of course, you can also use the system's TickTimer, which runs every 1 ms.

The first parameter in the above example specifies a timer frequency of 32 Hz; setting the second parameter to `true' enables software interrupts.

After calling Timer0.begin(), the timer starts running.

Start of the operation of the_twelite

Execute the_twelite.begin() at the end of the setup() function.

Serial

Serial objects can be used without initialization or initiation procedures.

This sample displays some system configuration values as a startup message. The Serial object is given a string of type const char*, a string of type int (no other integer types), a format() that behaves almost identically to printf, and a crlf that outputs newline characters to the << operator.

The namespace mwx:: may be omitted in the sample. In the above, it is described as mwx::crlf, but may be described as crlf. mwx::namespace is designed to be partially optional.

ループ loop()

Loop functions are called as callback functions from the main loop of the TWENET library. The basic description here is to wait for the object to be used to become available and then process it. This section describes the use of some of the objects used in ACT.

The main loop of the TWENET library processes incoming packets and interrupt information stored in the FIFO queue in advance as events, after which loop() is called. After exiting loop(), the CPU enters DOZE mode and waits until a new interrupt occurs with low current consumption.

Therefore, code that assumes the CPU is always running will not work well.

Buttons

It becomes available at the timing when a change in DIO (digital IO) input is detected, and is read by Buttons.read().

The first parameter is a bitmap of the HIGH/LOW of the current DIO, ordered from bit0 to DIO0,1,2,... in order from bit0. For example, for DIO12, HIGH / LOW can be determined by evaluating bp & (1UL << 12). The bit that is set to 1 is HIGH.

When the IO state is determined for the first time, MSB (bit31) is set to 1. The first time the IO status is determined is also performed when the device returns from sleep mode.

Next, the values are extracted from the bitmap and stored in u8DI_BM. Here, the collect_bits() function provided by the MWX library is used.

collect_bits() takes an integer argument of bit positions similar to pack_bits() above. It is a function with a variable number of arguments and arranges the parameters as many as necessary. In the above process, bit0 is stored in u8DI_BM as the value of DI1, bit1 as the value of DI2, bit2 as the value of DI3, and bit3 as the value of DI4.

In App_Twelite, since wireless transmission is performed when there is a change from DI1 to DI4, the transmission process is performed starting from Buttons.available(). The details of the transmit() process are described below.

Analogue

It becomes available at loop() immediately after the analog-to-digital conversion of the ADC is completed. Until the next ADC starts, data can be read back as if it was acquired immediately before.

Use the Analogue.read() or Analogue.read_raw() method to read ADC values. read() is the value converted to mV, read_raw() is the ADC value of 0..1023. The parameter is the pin number of ADC, which is defined in PIN_ANALOGUE:: or BRD_APPTWELITE::.

ADC values that are executed cyclically may read more recent values prior to the AVAILABLE notification, depending on the timing.

Since this ACT processes at a relatively slow cycle of 32 Hz, it is not a problem if the processing is done immediately after the AVAILABLE decision, but if the conversion cycle is short, or if you are doing a relatively long process in loop(), be careful.

In Analogue, you can specify a callback function that will be called from within the interrupt handler after the conversion is complete. For example, this callback function will store the value in the FIFO queue, and the application loop will perform asynchronous processing such as sequential reading of the queue value.

Timer0

The Timer0 runs at 32 Hz. It becomes available at loop() immediately after a timer interrupt occurs. In other words, it processes 32 times per second. Here, the transmission is processed when it reaches exactly 1 second.

AppTwelite performs a periodic transmission about every second. When Timer0 becomes available, it increments u16ct. Based on this counter value, transmit() is called to send a radio packet when the count is completed 32 times.

The value judgment of u8DI_BM and au16AI[] is whether or not it is just after initialization. If the values of DI1..DI4 and AI1..AI4 are not yet stored, nothing is done.

transmit()

This function requests TWENET to send a radio packet. At the end of this function, the wireless packet is not yet processed. The actual transmission will be completed in a few ms or later, depending on the transmission parameters. This section describes typical transmission request methods.

function prototype

MWX_APIRET is a class that handles return values with data members of type uint32_t, where MSB (bit31) is success/failure and the rest are used as return values.

Obtaining network and packet objects

Get a network object with the_twelite.network.use<NWK_SIMPLE>(). Use that object to get a pkt object with .prepare_tx_packet().

Here it is declared in a conditional expression in an if statement. The declared pkt object is valid until the end of the if clause. pkt object gives a response of type bool, which here is true if the TWENET send request queue is free and the send request is accepted, or false if there is no free queue.

Display suppression during Interactive settings mode screen display

Suppresses screen output when Interactive settings mode screen is displayed.

Packet transmission settings

Packets are configured using the << operator as in the initialization settings of the_twelite.

  • Specify the destination address in the tx_addr() parameter. If it is 0x00, it means that you are the Child Node and broadcast to the Parent Node, and if it is 0xFE, it means that you are the Parent Node and broadcast to any Child Node.

  • The tx_retry() parameter specifies the number of retransmissions. The 1 in the example sends one retry, i.e., a total of two packets. Sending only one wireless packet will fail a few percent of the time even under good conditions.

  • tx_packet_delay() Sets the transmission delay. The first parameter is the minimum wait time to start sending, the second is the maximum wait time, in this case approximately from 0 to 50 ms after issuing a request to send. The third is the retransmission interval. The third is the retransmission interval, meaning that a retransmission is performed every 10 ms after the first packet is sent.

Data payload of the packet

Payload means a loaded item, but in wireless packets it is often used to mean "the main body of data to be sent". In addition to the main body of data, the data in a wireless packet also contains some auxiliary information, such as address information.

Be aware of the data order of the data payload for correct transmission and reception. In this example, the data order is as follows. Construct the data payload according to this data order.

The data payload can contain 90 bytes (actually several more bytes).

Every byte of an IEEE802.15.4 wireless packet is precious. There is a limit to the amount of data that can be sent in a single packet. If a packet is split, the cost of the split packet is high, since it is necessary to take into account transmission failures. Also, sending one extra byte consumes energy equivalent to approximately 16 µs x the current used during transmission, which is especially significant for battery-powered applications.

The above example is a compromise to some extent for the sake of explanation. To save money, the 00: identifier should be a simple 1 byte, and the Vcc voltage value may be rounded to 8 bits. Also, each AI1..AI4 value is 10 bits, using 6 bytes for a total of 40 bits = 5 bytes.

Let's actually construct the data structure of the above data payload. The data payload can be referenced as a container of type simplbuf<uint8_t> by pkt.get_payload(). In this container, we build the data based on the above specification.

It can be written as above, but the MWX library provides an auxiliary function pack_bytes() for data payload construction.

The first parameter of pack_bytes specifies the container. In this case, it is pkt.get_payload().

The parameter after that is a variable number of arguments, pack_bytes, specifying the required number of values of the corresponding type. The pack_bytes internally calls the .push_back() method to append the specified value at the end.

The third line, make_pair(), is a standard library function to generate std::pair. This is to avoid confusion of string types (specifically, including or excluding null characters when storing payloads). The first parameter of make_pair() is the string type (char*, uint8_t*, uint8_t[], etc.) The second parameter is the number of bytes to store in the payload.

The fourth line is of type uint8_t and writes a bitmap of DI1..DI4.

Lines 7-9 write the values of the au16AI array in sequence. The values are of type uint16_t and are 2 bytes, but are written in big-endian order.

The for statement in line 7 is a range for statement introduced in C++. This syntax can be used for arrays of known size or container classes that can be accessed by begin(), end() iterators. au16AI types can also be determined at compile time, so the type specification is also omitted with auto&& (see Universal Reference).

If rewritten as a normal for statement, it would look like this

This completes the packet preparation. Now all that remains is to make a request for transmission.

Packets are sent using the pkt.transmit() method of the pkt object. It returns a return value of type MWX_APIRET, which is not used in this ACT.

The return value contains information on the success or failure of the request and the number corresponding to the request. To perform a process that waits until the completion of transmission, use this return value.

on_rx_packet()

When a wireless packet is received, on_rx_packet() is called as a receive event.

The procedure with the_twelite.receiver stores incoming packets in an internal queue (up to 2 packets) before processing them, but on_rx_packet() is called directly from a callback from the TWENET library, so it is more resistant to overflow. However, loop() is called directly from a callback from the TWENET library, and is less likely to cause overflow. However, if the processing is stopped for a long time in a loop() statement, it may cause the same kind of overflow.

Here, the values of DI1...DI4 and AI1...AI4 communicated by the other party are set to its own DO1...DO4 and PWM1...PWM4.

First, the data rx of the received packet is passed as a parameter. From rx the address information and data payload of the wireless packet is accessed. The parameter handled is not normally used.

Received packet data is referenced to the source address (32-bit long address and 8-bit logical address) and other information. Output is suppressed when the Interactive settings mode screen is displayed.

In <NWK_SIMPLE>, two types of addresses are always exchanged: an 8-bit logical ID and a 32-bit long address. When specifying the destination, either the long address or the logical address is specified. When receiving, both addresses are included.

The MWX library provides a function expand_bytes() as a counterpart to pack_bytes() used in transmit().

The first line declares an array of type char for data storage. The size of the array is 5 bytes because the null character is included at the end for convenience in character output, etc. The trailing {} specifies initialization. The trailing {} specifies initialization, and although it is sufficient to set the fifth byte to 0, here the entire array is initialized in the default way, that is, to 0.

The 4-byte string is extracted by expand_bytes() in the second line. The reason for not specifying the container type in the parameters is that we need to know the read position to read this continuation. the first parameter specifies the first iterator of the container (uint8_t* pointer), which can be obtained by the .begin(). The second parameter is an iterator that points to the next to the end of the container and can be retrieved with the .end() method. The second parameter is used to avoid reading beyond the end of the container.

The third variable to read is specified, again by make_pair, which is a pair of string array and size.

The ACT of expand_bytes() omits error checking, for example, if the packet length is incorrect. If you want to make the check strict, judge by the return value of expand_bytes().

The return value of expand_bytes() is uint8_t*, but returns nullptr (null pointer) for accesses beyond the end.

If the identifier in the 4-byte string read is different from the identifier specified in this ACT, this packet is not processed.

In TWENET, if the Application ID and the physical radio CHANNEL match, any application can receive the packet, even if it is of a different type. For the purpose of avoiding unintended reception of packets created by other applications, it is recommended to check such identifiers and the structure of data payloads to prevent coincidental matches.

The requirements for packet structure on the simple network <NWK_SIMPLE> must also be satisfied, so mixed packet reception will not occur unless other applications that do not use the simple network define packets with the same structure (which seems very rare).

The next step is to get the data part: store the values of DI1..DI4 and AI1..AI4 in separate variables.

The first parameter is the return value np of the previous expand_bytes(). The second parameter is the same.

The third and subsequent parameters are variables of the matching type alongside the data payload, in the same order as the sender's data structure. Once this process is complete, the specified variable will contain the value read from the payload.

Output to serial port for confirmation. Output is suppressed when the Interactive settings mode screen is displayed.

We use format() because we need to format output of numbers. helper class that allows the same syntax as printf() for the >> operator, but limits the number of arguments to four. (Serial.printfmt() has no limit on the number of arguments.)

The first line "DI:%04b" prints a 4-digit bitmap of DI1..DI4, like "DI:0010" The third line "/%04d" prints the values of Vcc/AI1..AI4 as integers, like "/3280/0010/0512/1023/" The fifth line mwx::crlf prints a newline string.

Now that the necessary data has been extracted, all that remains is to change the values of DO1..DO4 and PWM1..PWM4 on the board.

digitalWrite() changes the value of the digital output, the first parameter is the pin number and the second is HIGH (Vcc level) or LOW (GND level).

Timer?.change_duty() changes the duty ratio of the PWM output. Specify a duty ratio of 0..1024 for the parameter. Note that the maximum value is not 1023 (the maximum value is 1024, a power of 2, due to the high cost of the division performed in the library). Setting the value to 0 will result in an output equivalent to the GND level, and setting it to 1024 will result in an output equivalent to the Vcc level.

Rcv_Univsl

Rcv_Unvsl (Universal Reciever)

By running NWK_LAYERED on twe_twelite.network and NWK_SIMPLE on twe_twelite.network2 in the MWX library, packets of different types, including packets of layered tree net (TWELITE PAL, ARIA, etc) You can receive and interpret various types of packets.

However, wireless packets must be on the same CHANNEL and have the same Application ID.

main.cpp

setup(), loop(), and the callback function on_rx_packet() for incoming packets.

setup()

These objects are declared in pkt_handler.cpp and initialized by pnew() in setup(). It mainly interprets the payload (data) of the packet.

Two network objects are created. Be sure to NWK_LAYERED to the_twelite.network.

loop()

In this sample, the important part of the loop() process is the .refresh() process, which is performed about every second. Only g_pkt_apptwelite().refresh() does the duplicate checker timeout. Other objects do nothing.

on_rx_packet()

This is the most important part of this sample code. auto type = rx.get_network_type(); determines the packet type.

  • mwx::NETWORK::LAYERED : NWK_LAYERED layer tree net ket

  • mwx::NETWORK::SIMPLE : NWK_SIMPLE packets

  • mwx::NETWORK::NONE : no network (e.g. App_Twelite)

  • other : error or unsupported packets

In the case of mwx::NETWORK::NONE, the MWX library does not handle duplicate checkers for the same packet that may be sent in multiple retransmissions. It is necessary to describe the handling of these. In this sample, dup_checker.hpp and dup_checker.cpp are provided.

Interpretation of the packet refers to the packet_rx& object that wraps tsRxDataApp*. The packet_rx class itself has no special functionality, it only defines a means of accessing some information obtained from tsRxDataApp* using get_psRxDataApp().

pkt_common.hpp

It is defined for the purpose of unifying the interface of the packet interpretation part.

  • analyze() : interpret the payload of a packet.

  • display() : Display packet information.

  • refresh() : describe the process every second.

  • self() : cast to the derived class D.

In addition, the packet interpretation class (example pkt_handler_apptwelite above) contains a member object pkt. The actual packet interpretation part is done in pkt_???.cpp.

pkt_???.hpp, pkt_???.cpp

A packet interpretation part analyze() for each packet type and a data structure data are defined. The member data is a structure but inherits the common structure of PktDataCommon. This common part is used to concisely describe the code for serial output of the packet's data.

pkt_pal

Corresponds to PAL-related packets; the PAL packet structure has a complex data structure. The implementation here is based on the EASTL container.

  • _vect_pal_sensors : pool of _pal_sensor objects. This object is a dedicated class for use with instusive map.

  • _map_pal_sensors : intrusive map structure for efficient retrieval of sensor data.

Allocate an entry in _vect_pal_sensors for each of the multiple data in a packet as they are added and store the value. Once all the data in a packet has been interpreted, a _map_pal_sensors is constructed with the sensor type as the key.

dup_checker

Implement a duplicate checker. The behavior of the checker can be customized by template arguments.

template argument

  • MODE : If MODE_REJECT_SAME_SEQ is specified, packets with the same sequence number are excluded. This is used when the packet order is reordering. The MODE_REJECT_OLDER_SEQ adopts a more recent number.

  • TIMEOUT_ms : The interval at which to initialize the duplicate database. If 1000 is specified, data after 1 second will be erased. Packets that were excluded immediately before will be adopted again when the duplicate database is initialized.

  • N_ENTRIES : The maximum number of elements to be allocated in the data structure.

  • N_BUCKET_HASH : Maximum number of hash values. Specifies a prime number. It is determined based on the type of wireless node being received.

container

  • _mmap_entries : It is an intrusive hash multi-map structure. The search key is the serial number of the wireless node.

  • _vect_pool : Allocates a fixed number (N_ENTRIES) of elements used in the map structure.

  • _ring_vecant_idx : Keeps track of _vect_pool elements not used in _mmap_entries by array index number. It is a ring buffer structure, which takes one value from the ring buffer when adding elements and returns a value to the ring buffer when removing elements.

duplicate check

To retrieve data from the multimap structure, call .equal_range(). The resulting r is an iterator that enumerates elements with the same serial number.

Each element (_dup_checker_entry) has a timestamp and sequence number. Duplicates are checked according to these values.

PAL_MOT-fifo

The ACT of the ACT includes the following.

  • Sending and receiving wireless packets

ACT Features

  • Uses the motion sensor PAL MOTION SENSE PAL to continuously measure the acceleration of the accelerometer and transmit it wirelessly.

  • Use the sleep function, to operate on coin cell batteries.

how to use act

Required TWELITE

Explanation of ACT

Include

setup()

First, register the board BEHAVIOR <PAL_MOT>. When the board BEHAVIOR is initialized, the sensors and DIOs are initialized. The reason for doing this first is that it is common to check the status of the board's DIP SW, etc., and then configure the network settings, etc.

Here, 3 bits of the 4-bit DIP SW on the board are read and set as the Child Node ID. 0 means no ID (0xFE).

Set the LED settings. Here the ON/OFF blinking is set to blink every 10ms (in an application that sleeps and has a short wake-up time, this is almost equivalent to setting the LED to turn on during wake-up).

Accelerometer initialization

Starts accelerometer measurement. The accelerometer settings (SnsMC3630::Settings) specify the measurement frequency and measurement range. Here we use 14HZ measurement (SnsMC3630::MODE_LP_14HZ) with ±4G range (SnsMC3630::RANGE_PLUS_MINUS_4G).

Once started, the accelerometer takes 14 measurements per second and the values are stored in a FIFO queue inside the sensor. The sensor is notified when 28 measurements have been taken.

begin()

The begin() function exits the setup() function (after which TWENET is initialized) and is called just before the first loop().

Call sleepNow() after setup() ends to perform the first sleep.

sleepNow()

Before going to sleep, set up an interrupt for the accelerometer's DIO pin, which is generated when the FIFO queue reaches a certain number. The second parameter is PIN_MODE::WAKE_FALLING. This is a setting to wake up when the pin state changes from HIGH to LOW.

The third line executes sleep with the_twelite.sleep(). The parameter 60000 is the wake-up setting required to reset the watchdog on the TWELITE PAL board. If not reset, a hard reset will be performed after 60 seconds.

wakeup()

When the accelerometer wakes up from sleep due to a FIFO interrupt from the accelerometer, wakeup() is called. After that, loop() is called each time. Before wakeup(), each peripheral such as UART and devices on the board are woken up (e.g. resetting the watchdog timer). For example, it restarts the LED lighting control.

Here we are initializing variables to be used in loop().

loop()

Here, the acceleration information stored in the FIFO queue in the accelerometer is retrieved and packet transmission is performed based on this information. After the packet transmission is completed, sleep is executed again.

The b_transmit variable controls the behavior within loop(). After a successful transmission request, this value is set to 1, and the program waits for the packet transmission to complete.

First check to see if the sensor is available. Since it is after an interrupted wake-up, it is not normal for it not to be AVAILABLE, and it will go straight to sleep.

It is not used in the wireless transmission packet, but we will check the information on the acceleration taken out.

The measurement results of the accelerometer are stored in a FIFO queue obtained by brd.sns_MC3630.get_que().

The structure axis_xyzt that stores the measurement results of the accelerometer contains the information of three axes (x, y, z) and the sequence number t. The number of samples stored is the size of the queue.

The number of samples stored can be checked by reading the queue size (brd.sns_MC3630.get_que().size()). Normally 28 samples, but it may go a little further due to processing delays, etc. The first sample is taken from front()'. The first sample can be obtained by front(). Its sequential number is front().t`.

Here we will take the average of the samples before taking them out of the queue. Each element of the queue can be accessed with a for statement (for (auto&& v: brd.sns_MC3630.get_que()) { ... }), where v.x, v.y, v.z in the for statement is each element. Here, the sum of each element is calculated, and after the for statement, the average is calculated by dividing by the number of elements.

Next, a packet is generated and a request for transmission is made, but because the data volume is large, the packet is divided into two and transmitted twice. Therefore, the sending process is performed twice in the for statement.

The number of samples to be included in the packet to be sent and the sample first sequence number are stored in the first part of the packet payload.

Finally, the acceleration data is stored. While earlier we only referenced each element of the queue to calculate the average value, here we read one sample at a time from the queue and store it in the payload of the packet.

Use .front() to read the head of the data queue from the accelerometer. After reading, .pop() is used to release the top of the queue.

The data acquired from the accelerometer is in milli-G units, where 1G is 1000. Since the range is set to ±4G, the data is divided by 2 to be stored within a 12-bit range. To save the number of data, the first 4 bytes store the upper 8 bits of the X, Y and Z axes, and the next 1 byte stores the lower 4 bits of the Z axis, generating a total of 5 bytes.

The transmission ID is stored in the txid[] array to wait for two transmissions.

Then, if b_transmit is true during loop(), a completion check is performed, and if completed, sleepNow() puts the program to sleep.

The completion of transmission is confirmed by the_twelite.tx_status.is_complete() The .txid[] is the ID value returned on transmission.

Unit_???

An ACT (Act) beginning with _Unit_ is used to describe a very single function or to check the operation of a function.

Definition.

Act/behavior Programming Interface

For definitions commonly loaded in the library, cite the definition content.

mwx_common.h

PulseCounter

The pulse counter counts the number of rising or falling pulses of a signal without intervening microcontroller. It can be used to count irregular pulses and send wireless packets when the count reaches a certain number of times.

Function of ACT

  • Counts the pulses connected to DIO8 on the Child Node side and transmits them wirelessly after a certain period of time or when a certain number of counts are detected.

  • The Child Node side operates while sleeping.

How to use ACT

Required TWELITE

Explanation of ACT

setup()

Initializes the pulse counter.

begin()

Starts the pulse counter operation and performs the first sleep. The first parameter of PulseCounter.begin() is the count number 100 to generate a wake-up interrupt, and the second is the falling detection PIN_INT_MODE::FALLING.

wakeup()

Checks PulseCounter.available() when waking up. available, that is, true, indicates that the count is greater than or equal to the specified count. If it is false, it resleeps.

If the count is more than the specified number, loop() processes the transmission and waits for the completion of the transmission.

loop()

Reads the pulse count value. The counter is reset after the readout.

Sample ACTs

Sample Acts

To help you understand how the ACT works, we have prepared some samples.

The samples can be found in Act_samples in the folder where you installed the MWSDK.

Introduction to sample ACTs

Several samples are provided to help you understand how the act works.

Short ACT with no wireless communication and only microcontroller functions

Example of ACT description with I2C sensor

This is an example of a wireless sensor implementation that connects an I2C sensor and sends wireless packets while performing a brief operation by sleeping. Since this is a relatively simple and typical structure, it is recommended that you refer to it after reviewing act0 through act4.

Basic ACT for wireless communication

These are samples of sending or receiving wireless packets, each implemented from a slightly different perspective.

ACT on Parent Node side

Refer to this when implementing your own Receiving Parent Node application.

ACT to add Interactive settings mode

The explanation of the ACT using Interactive settings mode describes the general flow of the program. There are no major differences between the explanations for any of the samples. (Here we quote BRD_I2C_TEMPHUMID)

ACT to operate sensors and other devices

This sample obtains sensor information from built-in peripherals and external sensor devices.

ACT to use TWELITE PAL

Although a standard PAL application is written into TWELITE PAL, it is possible to write a description using ACT without using the PAL application. The MWX library provides standard procedures for MOTION SENSE PAL use.

This is a sample for various PAL boards. This sample acquires sensor values, transmits these valuse, and sleeps on the PAL board.

The following is an example of an advanced application, which is a little more complicated to describe than the above ACT.

ACT, which introduced the stand-alone function

Get the latest version.

We have put the complete source on Github for the purpose of checking the latest code and the revision history between MWSDK versions. See the links below for more information.

Common description

The following items are common settings in the Act sample and are explained below.

The following settings are common to all sample acts.

  • Application ID 0x1234abcd

  • Channel 13

Both the application ID and the channel are mechanisms to ensure that the network does not mix with other networks.

Systems with different Application IDs will not mix, even if they are on the same channel. However, if a system with a different Application ID is transmitting frequently, this will cause interference and will have an effect.

The channel determines the frequency used for communication; in principle, 16 channels are available on the TWELITE radio module, and it is not possible to communicate with different channels except in very exceptional cases, which would not be the case in a normal system.

As a common specification of sample acts, the payload (data part) of the packet is prefixed with a 4-byte string (APP_FOURCHAR[]). This is for explanatory purposes, although one byte is sufficient for species specific identifiers. The inclusion of such system-specific identifiers and data structures is another way to prevent confusion.

Run the Act on the

- + -

ACT in action.

+

connected to UART with products/TWE-Lite-DIP/index.html) etc.

Packets addressed to the Parent Node can also be received by .

Initialize .

Interactive settings mode -

Digital (button) input -

analog input -

Role
Example

Include <TWELITE> in all ACTs. Here is a simple network and board support <BRD_APPTWELITE> should be included.

set reflects some of the settings (Application ID, radio channel, etc.) read from Interactive settings mode. The items to be reflected are described in .

is used to acquire sensor values.

Interactive settings mode configuration -

State transition control by state machine -

or Board operation with board BEHAVIOR

Role
Example

Board BEHAVIOR is included.

Name
Content

is an example of an ACT.

Role
Example

is a very simple example, without any radio functions, to give you an idea of the basic structure of Act.

Typical elements for implementing wireless sensors in TWELITE (, , , intermittent operation with sleep, etc.).

is a simple code that receives 1 byte commands from the UART, sends them and so on.

uses a state machine and intermittent operation with sleep, repeating the process of sleep recovery, radio transmission and sleep.

is a sample of sending packets from one side to the other and receiving packets back by the other side. It contains basic procedures for sending and receiving.

interprets the UART input into ASCII format using and sends it.

Note: can also be used to receive wireless packets for the ACTs included in this sample.

exclusively receives and outputs the reception result to the serial port. The wireless packets in this sample, which are addressed to the parent device (0x00) or to the child device broadcast (0xFE), can be received. It also includes a procedure to add the interactive mode to Act.

is an example code for universal packets receiver (e.g. TWENET layered tree network, App_Twelite, Act, ...). It alse uses EASTL library for container or some algorithm.

executes I2C sensor device read/write commands and wirelessly transmits measurements obtained from I2C sensors. It also uses the Interactive settings mode to Act.

Setting provides a higher degree of customisation of the interactive mode .

executes I2C sensor device read/write commands and wirelessly transmits measurements obtained from I2C sensors. It also uses the Interactive settings mode to Act.

provides bidirectional communication using digital input, analogue input, digital output and analogue output. It also contains the procedure for adding the interactive mode to Act.

uses a pulse counter function to count the number of pulses detected on the input port, even during sleep, and to transmit this data wirelessly.

is a behavioural example: in PAL_AMB the temperature and humidity sensors are called by the code inside the library, but this sample also includes its own procedure for accessing the temperature and humidity sensors via I2C bus.

is a sample that aims to save more power by allowing the TWELITE microcontroller to sleep briefly during the sensor's operating time, which can take several tens of milliseconds.

is a behavioural example: in PAL_AMB the temperature and humidity sensors are called by the code inside the library, but this sample also includes its own procedure for accessing the temperature and humidity sensors via I2C bus.

for continuous acquisition and wireless transmission of samples without interruption, using the accelerometer's FIFO and FIFO interrupts.

Acts with names starting with are intended to introduce features and APIs.

void setup() {
	auto&& set = the_twelite.settings.use<STG_STD>();
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

	/*** INTERACTIE MODE */
	// settings: configure items
	set << SETTINGS::appname("WirelessUART");
	set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
	set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
	set << SETTINGS::lid_default(DEFAULT_LID); // 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);
	set.reload(); // load from EEPROM.
	
	/*** SETUP section */
	// the twelite main class
	the_twelite
		<< set                      // from iteractive mode (APPID/CH/POWER)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	nwk	<< set;						// from interactive mode (LID/REPEAT)

	/*** BEGIN section */
	SerialParser.begin(PARSER::ASCII, 128); // Initialize the serial parser
	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- WirelessUart (id=" << int(nwk.get_config().u8Lid) << ") ---" << mwx::crlf;
}
SerialParser.begin(PARSER::ASCII, 128); 
while(Serial.available())  {
	if (SerialParser.parse(Serial.read())) {
		Serial << ".." << SerialParser;
		const uint8_t* b = SerialParser.get_buf().begin();
		uint8_t addr = *b; ++b; // the first byte is destination address.
		transmit(addr, b, SerialParser.get_buf().end());
	}
}
void on_rx_packet(packet_rx& rx, bool_t &handled) {
	// check the packet header.
	const uint8_t* p = rx.get_payload().begin();
	if (rx.get_length() > 4 && !strncmp((const char*)p, (const char*)FOURCHARS, 4)) {
		Serial << format("..rx from %08x/%d", rx.get_addr_src_long(), rx.get_addr_src_lid()) << mwx::crlf;

		smplbuf_u8<128> buf;
		mwx::pack_bytes(buf			
				, uint8_t(rx.get_addr_src_lid())            // src addr (LID)
				, make_pair(p+4, rx.get_payload().end()) );	// data body

		serparser_attach pout;
		pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());
		Serial << pout;
	}
}
:FE00112233X

:FE001122339C
:03AABBCC00112233X

:03AABBCC0011223366
:FF00112233X

:00112233X

Parent Node

TWELITE DIP At a minimum, wire M1=GND, DI1:button, DO1:LED.

Child Node

TWELITE DIP At a minimum, wire M1=open, DI1:button, DO1:LED.

// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
#include <BRD_APPTWELITE>
#include <STG_STD>
/*** Config part */
// application ID
const uint32_t DEFAULT_APP_ID = 0x1234abcd;
// channel
const uint8_t DEFAULT_CHANNEL = 13;
// option bits
uint32_t OPT_BITS = 0;
// logical id
uint8_t LID = 0;

/*** function prototype */
MWX_APIRET transmit();
void receive();

/*** application defs */
const char APP_FOURCHAR[] = "BAT1";

// sensor values
uint16_t au16AI[5];
uint8_t u8DI_BM;
void setup() {
	/*** SETUP section */	
	// init vars
	for(auto&& x : au16AI) x = 0xFFFF;
	u8DI_BM = 0xFF;

	// load board and settings
	auto&& set = the_twelite.settings.use<STG_STD>();
	auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

	// settings: configure items
	set << SETTINGS::appname("BRD_APPTWELITE");
	set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
	set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
	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);
	set.reload(); // load from EEPROM.
	OPT_BITS = set.u32opt1(); // this value is not used in this example.
	LID = set.u8devid(); // logical ID

	// the twelite main class
	the_twelite
		<< set                      // apply settings (appid, ch, power)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	if (brd.get_M1()) { LID = 0; }

	// Register Network
	nwk << set // apply settings (LID and retry)
			;

	// if M1 pin is set, force parent device (LID=0)
	nwk << NWK_SIMPLE::logical_id(LID); // write logical id again.
	
	/*** BEGIN section */
	// start ADC capture
	Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0); // setup analogue read (check every 16ms)
	Analogue.begin(pack_bits(
						BRD_APPTWELITE::PIN_AI1,
						BRD_APPTWELITE::PIN_AI2,
						BRD_APPTWELITE::PIN_AI3,
						BRD_APPTWELITE::PIN_AI4,
				   		PIN_ANALOGUE::VCC)); // _start continuous adc capture.

	// Timer setup
	Timer0.begin(32, true); // 32hz timer

	// start button check
	Buttons.setup(5); // init button manager with 5 history table.
	Buttons.begin(pack_bits(
						BRD_APPTWELITE::PIN_DI1,
						BRD_APPTWELITE::PIN_DI2,
						BRD_APPTWELITE::PIN_DI3,
						BRD_APPTWELITE::PIN_DI4),
					5, 		// history count
					4);  	// tick delta (change is detected by 5*4=20ms consequtive same values)	


	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial 	<< "--- BRD_APPTWELITE ---" << mwx::crlf;
	Serial	<< format("-- app:x%08x/ch:%d/lid:%d"
					, the_twelite.get_appid()
					, the_twelite.get_channel()
					, nwk.get_config().u8Lid
				)
			<< mwx::crlf;
	Serial 	<< format("-- pw:%d/retry:%d/opt:x%08x"
					, the_twelite.get_tx_power()
					, nwk.get_config().u8RetryDefault
					, OPT_BITS
			)
			<< mwx::crlf;
}
	auto&& set = the_twelite.settings.use<STG_STD>();
	auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
// Initialize Interactive settings mode
auto&& set = the_twelite.settings.use<STG_STD>();

set << SETTINGS::appname("BRD_APPTWELITE");
set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
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);
set.reload(); // load from EEPROM.
OPT_BITS = set.u32opt1(); // this value is not used in this example.
LID = set.u8devid(); // logical ID;
[CONFIG/BRD_APPTWELITE:0/SID=8XXYYYYY]
a: (0x1234ABCD) Application ID [HEX:32bit]
i: (        13) Device ID [1-100,etc]
c: (        13) Channel [11-26]
x: (      0x03) RF Power/Retry [HEX:8bit]
o: (0x00000000) Option Bits [HEX:32bit]

 [ESC]:Back [!]:Reset System [M]:Extr Menu    
auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
	if (brd.get_M1()) { LID = 0; }
	// the twelite main class
	the_twelite
		<< set
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)
// The following statements are not available in the MWX library
#include <iostream>
std::cout << "hello world" << std::endl;
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << set;
nwk << NWK_SIMPLE::logical_id(LID);
Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0);
	Analogue.begin(pack_bits(
						BRD_APPTWELITE::PIN_AI1,
						BRD_APPTWELITE::PIN_AI2,
						BRD_APPTWELITE::PIN_AI3,
						BRD_APPTWELITE::PIN_AI4,
				   	PIN_ANALOGUE::VCC));
Buttons.setup(5);
Buttons.begin(pack_bits(
						BRD_APPTWELITE::PIN_DI1,
						BRD_APPTWELITE::PIN_DI2,
						BRD_APPTWELITE::PIN_DI3,
						BRD_APPTWELITE::PIN_DI4),
					5, 		// history count
					4);  	// tick delta
Timer0.begin(32, true); // 32hz timer
the_twelite.begin(); // start twelite!
	Serial 	<< "--- BRD_APPTWELITE ---" << mwx::crlf;
	Serial	<< format("-- app:x%08x/ch:%d/lid:%d"
					, the_twelite.get_appid()
					, the_twelite.get_channel()
					, nwk.get_config().u8Lid
				)
			<< mwx::crlf;
	Serial 	<< format("-- pw:%d/retry:%d/opt:x%08x"
					, the_twelite.get_tx_power()
					, nwk.get_config().u8RetryDefault
					, OPT_BITS
			)
			<< mwx::crlf;
/*** loop procedure (called every event) */
void loop() {
	if (Buttons.available()) {
		uint32_t bp, bc;
		Buttons.read(bp, bc);

		u8DI_BM = uint8_t(collect_bits(bp, 
							BRD_APPTWELITE::PIN_DI4,   // bit3
							BRD_APPTWELITE::PIN_DI3,   // bit2
							BRD_APPTWELITE::PIN_DI2,   // bit1
							BRD_APPTWELITE::PIN_DI1)); // bit0

		transmit();
	}

	if (Analogue.available()) {
		au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC);
		au16AI[1] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI1);
		au16AI[2] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI2);
		au16AI[3] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI3);
		au16AI[4] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI4);
	}

	if (Timer0.available()) {
		static uint8_t u16ct;
		u16ct++;

		if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
			if ((u16ct % 32) == 0) { // every 32ticks of Timer0
				transmit();
			}
		}
	}
}
	if (Buttons.available()) {
		uint32_t bp, bc;
		Buttons.read(bp, bc);
u8DI_BM = uint8_t(collect_bits(bp, 
		BRD_APPTWELITE::PIN_DI4,   // bit3
		BRD_APPTWELITE::PIN_DI3,   // bit2
		BRD_APPTWELITE::PIN_DI2,   // bit1
		BRD_APPTWELITE::PIN_DI1)); // bit0

/* collect_bits performs the following operations
u8DI_BM = 0;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI1)) u8DI_BM |= 1;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI2)) u8DI_BM |= 2;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI3)) u8DI_BM |= 4;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI4)) u8DI_BM |= 8;
*/
transmit();
if (Analogue.available()) {
	au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC);
	au16AI[1] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI1);
	au16AI[2] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI2);
	au16AI[3] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI3);
	au16AI[4] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI4);
}
if (Timer0.available()) {
	static uint8_t u16ct;
	u16ct++;

	if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
		if ((u16ct % 32) == 0) { // every 32ticks of Timer0
			transmit();
		}
	}
}
MWX_APIRET transmit() {
	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
	  auto&& set = the_twelite.settings.use<STG_STD>();
		if (!set.is_screen_opened()) {
			Serial << "..DI=" << format("%04b ", u8DI_BM);
			Serial << format("ADC=%04d/%04d/%04d/%04d ", au16AI[1], au16AI[2], au16AI[3], au16AI[4]);
			Serial << "Vcc=" << format("%04d ", au16AI[0]);
			Serial << " --> transmit" << mwx::crlf;
		}

		// set tx packet behavior
		pkt << tx_addr(u8devid == 0 ? 0xFE : 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,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)

		// prepare packet payload
		pack_bytes(pkt.get_payload() // set payload data objects.
			, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
			, uint8_t(u8DI_BM)
		);

		for (auto&& x : au16AI) {
			pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
		}
		
		// do transmit 
		return pkt.transmit();
	}
	return MWX_APIRET(false, 0);
}
MWX_APIRET transmit()
	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
auto&& set = the_twelite.settings.use<STG_STD>();
if (!set.is_screen_opened()) {
    //Not during Interactive settings mode screen!
}
pkt << tx_addr(u8devid == 0 ? 0xFE : 0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
		<< tx_retry(0x1) // set retry (0x3 send four times in total)
		<< tx_packet_delay(0,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
# Index of first byte: Data type : Number of bytes : Contents

00: uint8_t[4] : 4 : four-character identifier
04: uint8_t    : 1 : Bitmap of DI1..4
06: uint16_t   : 2 : Voltage value of Vcc
08: uint16_t   : 2 : ADC value of AD1 (0..1023)
10: uint16_t   : 2 : ADC value of AD2 (0..1023)
12: uint16_t   : 2 : ADC value of AD3 (0..1023)
14: uint16_t   : 2 : ADC value of AD4 (0..1023)
auto&& payl = pkt.get_payload();
payl.reserve(16); // Resize to 16 bytes
payl[00] = APP_FOURCHAR[0];
payl[01] = APP_FOURCHAR[1];
...
payl[08] = (au16AI[0] & 0xFF00) >> 8; //Vcc
payl[09] = (au16AI[0] & 0xFF);
...
payl[14] = (au16AI[4] & 0xFF00) >> 8; // AI4
payl[15] = (au16AI[4] & 0xFF);
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
	, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
	, uint8_t(u8DI_BM)
);

for (auto&& x : au16AI) {
	pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
}
for(int i = 0; i < sizeof(au16AI)/sizeof(uint16_t)); i++) {
  pack_bytes(pkt.get_payload(), au16AI[i]);
}
return pkt.transmit();
void on_rx_packet(packet_rx& rx, bool_t &handled) {	
	auto&& set = the_twelite.settings.use<STG_STD>();

	Serial << format("..receive(%08x/%d) : ", rx.get_addr_src_long(), rx.get_addr_src_lid());

  // expand the packet payload
	char fourchars[5]{};
	auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
		, make_pair((uint8_t*)fourchars, 4)  // 4bytes of msg
    );

	// check header
	if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }

	// read rest of payload
	uint8_t u8DI_BM_remote = 0xff;
	uint16_t au16AI_remote[5];
	expand_bytes(np, rx.get_payload().end()
		, u8DI_BM_remote
		, au16AI_remote[0]
		, au16AI_remote[1]
		, au16AI_remote[2]
		, au16AI_remote[3]
		, au16AI_remote[4]
	);

	Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
	for (auto&& x : au16AI_remote) {
		Serial << format("/%04d", x);
	}
	Serial << mwx::crlf;

	// set local DO
	digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
	digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
	digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
	digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);

	// set local PWM : duty is set 0..1024, so 1023 is set 1024.
	Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
	Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
	Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
	Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);
}
void on_rx_packet(packet_rx& rx, bool_t &handled)
if (!set.is_screen_opened()) {
   Serial << format("..receive(%08x/%d) : ",
      rx.get_addr_src_long(), rx.get_addr_src_lid());
}
char fourchars[5]{};
auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
	, make_pair((uint8_t*)fourchars, 4)  // 4bytes of msg
  );
if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }
	// read rest of payload
	uint8_t u8DI_BM_remote = 0xff;
	uint16_t au16AI_remote[5];
	expand_bytes(np, rx.get_payload().end()
		, u8DI_BM_remote
		, au16AI_remote[0]
		, au16AI_remote[1]
		, au16AI_remote[2]
		, au16AI_remote[3]
		, au16AI_remote[4]
	);
auto&& set = the_twelite.settings.use<STG_STD>();
...
Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
for (auto&& x : au16AI_remote) {
	Serial << format("/%04d", x);
}
Serial << mwx::crlf;
// set local DO
digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);

// set local PWM : duty is set 0..1024, so 1023 is set 1024.
Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);
	mwx::pnew(g_pkt_pal);
	mwx::pnew(g_pkt_apptwelite);
	mwx::pnew(g_pkt_actsamples);
	mwx::pnew(g_pkt_unknown);
	auto&& nwk_ly = the_twelite.network.use<NWK_LAYERED>();
	auto&& nwk_sm = the_twelite.network2.use<NWK_SIMPLE>();
	if (TickTimer.available()) {
		static unsigned t;
		if (!(++t & 0x3FF)) {
			g_pkt_pal.refresh();
			g_pkt_apptwelite.refresh();
			g_pkt_actsamples.refresh();
			g_pkt_unknown.refresh();
		}
	}
void on_rx_packet(packet_rx& rx, bool_t &handled) {
	auto type = rx.get_network_type();
	bool b_handled = false;

	// PAL
	if (!b_handled
		&& type == mwx::NETWORK::LAYERED
		&& g_pkt_pal.analyze(rx, b_handled)
	) {
		g_pkt_pal.display(rx);
	}

	// Act samples
	if (!b_handled
		&& type == mwx::NETWORK::SIMPLE
		&& g_pkt_actsamples.analyze(rx, b_handled)
	) {
		g_pkt_actsamples.display(rx);
	}

	// Standard application (e.g. App_Twelite)
	if (!b_handled
		&& type == mwx::NETWORK::NONE
		&& g_pkt_apptwelite.analyze(rx, b_handled)
	) {
		g_pkt_apptwelite.display(rx);
	}

	// unknown
	if (!b_handled) {
		g_pkt_unknown.analyze(rx, b_handled);
		g_pkt_unknown.display(rx);
	}
}
template <class D>
struct pkt_handler {
	D& self() { return static_cast<D&>(*this); }
	bool analyze(packet_rx& rx, bool &b_handled) {
		return self().pkt.analyze(rx, b_handled);
	}
	void display(packet_rx& rx) {
		Serial
			<< crlf
			<< format("!PKT_%s(%03d-%08x/S=%d/L=%03d/V=%04d)"
					, self().get_label_packet_type()
					, self().pkt.data.u8addr_src
					, self().pkt.data.u32addr_src
					, rx.get_psRxDataApp()->u8Seq
					, rx.get_lqi()
					, self().pkt.data.u16volt
					);

		self().disp_detail(rx);
	}
	void refresh() {
		self()._refresh();
	}
};

// packet analyzer for App_Twelite
class pkt_handler_apptwelite : public pkt_handler<pkt_handler_apptwelite> {
	friend class pkt_handler<pkt_handler_apptwelite>;
	pkt_apptwelite pkt;
	void disp_detail(packet_rx& rx);
	const char* get_label_packet_type() { return "AppTwelite"; }
	void _refresh() { pkt.refresh(); }
public:
	pkt_handler_apptwelite() : pkt() {}
};
	bool check_dup(uint32_t u32ser, uint16_t u16val, uint32_t u32_timestamp) {
		// find entry by key:u32ser.
		auto r = _mmap_entries.equal_range(u32ser);

        ...
    }

Parent Node

MONOSTICK BLUE or RED Act Parent_MONOSTICK to work.

Child Node

BLUE PAL or RED PAL + Motion Sensor PAL MOTION SENSE PAL

#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_>
void setup() {
	/*** SETUP section */
	// board
	auto&& brd = the_twelite.board.use<PAL_MOT>();
	brd.set_led(LED_TIMER::BLINK, 100);

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)     
		<< TWENET::channel(CHANNEL);

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk	<< NWK_SIMPLE::logical_id(0xFE); 
	
	/*** BEGIN section */
	the_twelite.begin(); // start twelite!
	brd.sns_MC3630.begin(SnsMC3630::Settings(
		SnsMC3630::MODE_LP_14HZ, SnsMC3630::RANGE_PLUS_MINUS_4G));

	/*** INIT message */
	Serial << "--- PAL_MOT(Cont):" << FOURCHARS 
				 << " ---" << mwx::crlf;
}
auto&& brd = the_twelite.board.use<PAL_MOT>();

u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE
	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)
	brd.sns_MC3630.begin(SnsMC3630::Settings(
		SnsMC3630::MODE_LP_14HZ, SnsMC3630::RANGE_PLUS_MINUS_4G));
void begin() {
	sleepNow(); // the first time is just sleeping.
}
void sleepNow() {
	pinMode(PAL_MOT::PIN_SNS_INT, WAKE_FALLING);
	the_twelite.sleep(60000, false);
}
void wakeup() {
	Serial << "--- PAL_MOT(Cont):" << FOURCHARS
	       << " wake up ---" << mwx::crlf;

	b_transmit = false;
	txid[0] = 0xFFFF;
	txid[1] = 0xFFFF;
}
void loop() {
	auto&& brd = the_twelite.board.use<PAL_MOT>();

	if (!b_transmit) {
		if (!brd.sns_MC3630.available()) {
			Serial << "..sensor is not available." 
					<< mwx::crlf << mwx::flush;
			sleepNow();
		}

		// send a packet
		Serial << "..finish sensor capture." << mwx::crlf
			<< "  seq=" << int(brd.sns_MC3630.get_que().back().t) 
			<< "/ct=" << int(brd.sns_MC3630.get_que().size());

		// calc average in the queue.
		{
			int32_t x = 0, y = 0, z = 0;
			for (auto&& v: brd.sns_MC3630.get_que()) {
				x += v.x;
				y += v.y;
				z += v.z;
			}
			x /= brd.sns_MC3630.get_que().size();
			y /= brd.sns_MC3630.get_que().size();
			z /= brd.sns_MC3630.get_que().size();

			Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;
		}

		for (int ip = 0; ip < 2; ip++) {
			if(auto&& pkt = 
				the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet())
				
				// set tx packet behavior
				pkt << tx_addr(0x00)
					<< tx_retry(0x1)
					<< tx_packet_delay(0, 0, 2);
			
				// prepare packet (first)
				uint8_t siz = (brd.sns_MC3630.get_que().size() >= MAX_SAMP_IN_PKT)
									? MAX_SAMP_IN_PKT : brd.sns_MC3630.get_que().size();
				uint16_t seq = brd.sns_MC3630.get_que().front().t;
			
				pack_bytes(pkt.get_payload()
					, make_pair(FOURCHARS, 4)
					, seq 
					, siz
				);

				// store sensor data (36bits into 5byts, alas 4bits are not used...)
				for (int i = 0; i < siz; i++) {
					auto&& v = brd.sns_MC3630.get_que().front();
					uint32_t v1;

					v1  = ((uint16_t(v.x/2) & 4095) << 20)  // X:12bits
						| ((uint16_t(v.y/2) & 4095) <<  8)  // Y:12bits
						| ((uint16_t(v.z/2) & 4095) >>  4); // Z:8bits from MSB
					uint8_t v2 = (uint16_t(v.z/2) & 255);   // Z:4bits from LSB
					pack_bytes(pkt.get_payload(), v1, v2); // add into pacekt entry.
					brd.sns_MC3630.get_que().pop(); // pop an entry from queue.
				}

				// perform transmit
				MWX_APIRET ret = pkt.transmit();

				if (ret) {
					Serial << "..txreq(" << int(ret.get_value()) << ')';
					txid[ip] = ret.get_value() & 0xFF;
				} else {
					sleepNow();
				}
			}
		}

		// finished tx request
		b_transmit = true;
	} else {
		if(		the_twelite.tx_status.is_complete(txid[0])
			 && the_twelite.tx_status.is_complete(txid[1]) ) {

			sleepNow();
		}
	}
}
	if (!b_transmit) {
if (!brd.sns_MC3630.available()) {
	Serial << "..sensor is not available." 
			<< mwx::crlf << mwx::flush;
	sleepNow();
}
Serial << "..finish sensor capture." << mwx::crlf
	<< "  seq=" << int(brd.sns_MC3630.get_que().front().t) 
	<< "/ct=" << int(brd.sns_MC3630.get_que().size());

// calc average in the queue.
{
	int32_t x = 0, y = 0, z = 0;
	for (auto&& v: brd.sns_MC3630.get_que()) {
		x += v.x;
		y += v.y;
		z += v.z;
	}
	x /= brd.sns_MC3630.get_que().size();
	y /= brd.sns_MC3630.get_que().size();
	z /= brd.sns_MC3630.get_que().size();

	Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;
}
		for (int ip = 0; ip < 2; ip++) {
// prepare packet (first)
uint8_t siz = (brd.sns_MC3630.get_que().size() >= MAX_SAMP_IN_PKT)
? MAX_SAMP_IN_PKT : brd.sns_MC3630.get_que().size();
uint16_t seq = brd.sns_MC3630.get_que().front().t;

pack_bytes(pkt.get_payload()
	, make_pair(FOURCHARS, 4)
	, seq 
	, siz
);
for (int i = 0; i < siz; i++) {
	auto&& v = brd.sns_MC3630.get_que().front();
	uint32_t v1;

	v1  = ((uint16_t(v.x/2) & 4095) << 20)  // X:12bits
		| ((uint16_t(v.y/2) & 4095) <<  8)  // Y:12bits
		| ((uint16_t(v.z/2) & 4095) >>  4); // Z:8bits from MSB
	uint8_t v2 = (uint16_t(v.z/2) & 255);   // Z:4bits from LSB
	pack_bytes(pkt.get_payload(), v1, v2); // add into pacekt entry.
	brd.sns_MC3630.get_que().pop(); // pop an entry from queue.
}
MWX_APIRET ret = pkt.transmit();

if (ret) {
	Serial << "..txreq(" << int(ret.get_value()) << ')';
	txid[ip] = ret.get_value() & 0xFF;
} else {
	sleepNow();
}
} else {
	if(		the_twelite.tx_status.is_complete(txid[0])
		 && the_twelite.tx_status.is_complete(txid[1]) ) {

		sleepNow();
	}
}

Unit_ADC

This sample runs the ADC, reading and displaying about every second while continuously running the ADC every 100ms. Sleep by [s] key.

Unit_I2Cprobe

Scan the I2C bus for responding device numbers (some devices may not respond to this procedure).

Unit_delayMicoroseconds

delayMicroseconds(), and compare with the count of 16Mhz TickTimer.

Unit_brd_CUE

Check the operation of the acceleration sensor, magnetic sensor, and LED of TWELITE CUE. Input [a],[s],[l] keys from the terminal.

Unit_brd_PAL_NOTICE

Try Notice Pal (NOTICE PAL) LED lighting. All lights flash at startup, then operate via serial input. - r,g,b,w : Toggle each color lighting mode - R,G,B,W : change the brightness of each color (disabled when lights off or all lights on) - c: change the cycle (when blinking) - C: change the duty when blinking (when blinking)

Unit_div100

Find the divisor and quotient of 10,100,1000 div10(),div100(),div1000(). -9999999 to 999999 and compare the elapsed time with the usual division by /,%.

Unit_div_format

div10(),div100(),div1000() results in string output.

Unit_UART1

UART1 (Serial1) is a sample of using UART0 (Serial) outputs input from UART1 to UART1 and input from UART1 to UART0.

Unit_Pkt_Parser

Sample use of the packet information parser <a href="../It can interpret the output of App_Wings. * You may want to connect two TWELITE radio modules serially and use one as App_Wings and the other to interpret its output. If you want to connect the other to a non-TWELITE radio module, you can use "Other Platforms".

Uint_EEPROM

EEPROM read/write test code.

Unit_Cue_MagBuz

This program uses the TWELITE CUE magnet sensor and SET pin (connected to a piezoelectric buzzer) to sound a buzzer when the magnet is released.

Unit_doint-bhv

Example of BEHAVIOR description for handling IO interrupts.

Unit_EASTL

A collection of small codes using the EASTL library.

#include <cstdint> // for type name
typedef char char_t;
typedef uint8_t byte;
typedef uint8_t boolean;

#ifndef NULL
#define NULL nullptr
#endif

Parent Node

MONOSTICK BLUE or RED ACT Parent_MONOSTICK in action.。

Child Node

1. TWELITE DIP 2. BLUE PAL or RED PAL +environmental sensor pal AMBIENT SENSE PAL

// Pulse Counter setup
PulseCounter.setup();
void begin() {
	// start the pulse counter capturing
	PulseCounter.begin(
		  100 // 100 count to wakeup
		, PIN_INT_MODE::FALLING // falling edge
		);

	sleepNow();
}
void wakeup() {
	Serial	<< mwx::crlf
			<< "--- Pulse Counter:" << FOURCHARS << " wake up ---"
			<< mwx::crlf;

	if (!PulseCounter.available()) {
		Serial << "..pulse counter does not reach the reference value." << mwx::crlf;
		sleepNow();
	}
}
uint16_t u16ct = PulseCounter.read();
const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const char APP_FOURCHAR[] = "BAT1";
MONOSTICK BLUE or RED
Parent_MONOSTICK
MONOSTICK BLUE or RED
BLUE PAL / RED PAL
AMBIENT SENSE PAL
TWELITE ARIA BLUE / RED
MONOSTICK BLUE or RED
Parent_MONOSTICK
BLUE PAL or RED PAL
Environmental Sensor Pal AMBIENT SENSE PAL
MONOSTICK BLUE or RED
TWELITE DIP
TWELITE R
Parent_MONOSTICK
serial parser
<STG_STD>
Buttons
Analogue
<NWK_SIMPLE>
<STG_STD>
MOTION SENSE PAL
<STG_STD>
<SM_SIMPLE>
<PAL_MOT>
<CUE>
<PAL_MOT>
pulse counter
act0..4
BRD_I2C_TEMPHUMID
simple relay net <NWK_SIMPLE>
Interactive settings mode <STG_STD>
I2C sensor handling Wire
Scratch
Slp_Wk_and_Tx
PingPong
WirelessUART
serparser
App_Wings
Parent_MONOSTICK
<STG_STD>
Rcv_Univsl
BRD_I2C_TEMPHUMID
<STG_STD>
<STG_STD>
BRD_I2C_TEMPHUMID
<STG_STD>
BRD_APPTWELITE
<STG_STD>
PulseCounter
PAL_AMB_behavior
PAL_AMB
PAL_MOT-single
PAL_MAG
PAL_AMB_usenap
PAL_AMB_behavior
PAL_MOT_fifo
Unit
https://github.com/monowireless/Act_samples

EEPROM

EEPROM

TWELITE Reads and writes to the built-in EEPROM of the wireless microcontroller.

The built-in EEPROM has 3480 bytes available from address 0x000 to 0xEFF.

The first part is Settings (Interactive settings mode), so it is recommended to use the second half of the address when used together. The amount of space consumed by the settings (Interactive settings mode) depends on its implementation. Even with minimal settings, up to 256 bytes are used from the beginning, so use of the later addresses is recommended.

Methods

read()

uint8_t read(uint16_t address)

Read the data corresponding to address from EEPROM.

No error detection.

write()

void write(uint16_t address, uint8_t value)

Write value from EEPROM to address.

No error detection.

update()

void update(uint16_t address, uint8_t value)

This function is used when you want to reduce the number of rewrites in consideration of the rewrite life of EEPROM.

get_stream_helper()

auto&& get_stream_helper()
// The return type is abbreviated as auto&& due to its length.

Obtain a helper object to read and write using mwx::stream described below.

mwx::streamインタフェースを用いた入出力

stream_helper helper object mwx::stream operators and methods. Using mwx::stream, you can read and write integer types such as uint16_t and uint32_t types, read and write fixed-length array types such as uint8_t, and format them using format() objects.

auto&& strm = EEPROM.get_stream_helper();
// Helper object type names are resolved by auto&& due to their length.

Interfaces defined in mwx::stream, such as the << operator, can be used on this object.

strm.seek(1024); // Move to 1024th byte

strm << format("%08x", 12345678); // Record 12345678 as 8 characters in hexadecimal
strm << uint32_t(0x12ab34cd);     // Record 4 bytes of 0x12ab34cd
uint8_t msg_hello[16] = "HELLO WORLD!";
strm << msg_hello;                // Record "HELLO WORLD!" byte sequence (unterminated)

// result
// 0400: 30 30 62 63 36 31 34 65 12 ab 34 cd 48 45 4c 4c
//        0  0  b  c  6  1  4  e  0x12ab34cd  H  E  L  L
// 0410: 4f 20 57 4f 52 4c 44 21 00 00 00 00 ff ff ff ff
//        O SP  W  O  R  L  D  ! 

.seek() is used to move the EEPROM address to 1024.

The above writes an 8-byte string (00bc614e), a 4-byte integer (0x12ab34cd), a 16-byte byte string (HELLO WORLD!.... ), and a 1-byte terminating character.

strm.seek(1024);

uint8_t msg1[8];
strm >> msg1;
Serial << crlf << "MSG1=" << msg1;
// MSG1=00bc614e

uint32_t var1;
strm >> var1;
Serial << crlf << "VAR1=" << format("%08x", var1);
// VAR1=12ab34cd

uint8_t msg2[16]; // "HELLO WORLD!"の文字数
strm >> msg2;
Serial << crlf << "MSG2=" << msg2;
// MSG2=HELLO WORLD!

Move the EEPROM address to 1024 using .seek().

Reads the data sequence written out earlier. In order, 8-byte characters, 4-byte integers, and 16-byte strings are read out using the >> operator.

class object

Class Objects

Class objects are predefined objects in the MWX library, such as the_twelite, which handles TWENET, and objects for using peripherals.

Each object must be initialized by calling the .setup() or .begin() method. (Only Serial objects that use UART0 do not require initialization.)

Analogue

ADC (mwx::periph_analogue.hpp)

Analogue performs ADC and acquires values. It can continuously acquire multiple channels at a time, and can do so sequentially according to a timer or other cycle.

Constant.

Pin Definition.

constant
classification
Pin name in standard apps

uint8_t PIN_ANALOGUE::A1 = 0

ADC1 pin

AI1

uint8_t PIN_ANALOGUE::A2 = 1

ADC2 pin

AI3

uint8_t PIN_ANALOGUE::A3 = 2

uint8_t PIN_ANALOGUE::D0 = 2

ADC3 pin (DIO0) *1

AI2

uint8_t PIN_ANALOGUE::A4 = 3

uint8_t PIN_ANALOGUE::D1 = 3

ADC4 pin (DIO1) *1

AI4

uint8_t PIN_ANALOGUE::VCC = 4

Vcc Supply voltage

In the standard application (App_Twelite), the pin names ADC2/ADC3 in the semiconductor data sheet are AI3/AI2 to match the TWELITE DIP. Please note this.

*1 ADC2/ADC3 pins for both digital and analog are subject to usage procedures and restrictions.

Assume no pull-up on the pin to be used before the ADC starts. If this is not done, the pull-up voltage will always be observed in the ADC.

pinMode(PIN_DIGITAL::DIO0, PIN_MODE::INPUT); 
pinMode(PIN_DIGITAL::DIO1, PIN_MODE::INPUT);

In a normal circuit configuration, current leakage occurs during sleep. It cannot be circumvented by software description alone.

To avoid current leakage during sleep, the GND of the analog circuit section should be disconnected with a FET switch or the like and left floating during sleep. Also, before sleep, set the pin to input and pull-up state.

method.

setup()

void setup(
        bool bWaitInit = false,
        uint8_t kick_ev = E_AHI_DEVICE_TICK_TIMER,
        void (*fp_on_finish)() = nullptr) 

Initializes ADCs. setup() starts the regulator inside the semiconductor, specifies the timer device for periodic execution, and specifies the callback function to be called when all ADCs on the specified channel have completed.

parameter
explanation

bWaitInit

If true is specified, waits for initialization of the regulator inside the semiconductor.

kick_ev

Specify the timer device to be specified for cyclic execution. The following five types of devices can be specified, and AD is started in the interrupt handler except for the first time.

E_AHI_DEVICE_TICK_TIMER (TickTimer)

E_AHI_DEVICE_TIMER0 .. 4 (Timer0 .. 4)

fp_on_finish

This callback function is called from within the interrupt handler after all ADCs on the specified ports have finished, and is used when ADC measurement values are to be stored separately in a FIFO queue, etc.

begin()

void begin(uint8_t bmPorts, uint8_t capt_tick = 1)

The first parameter specifies the port where the ADC is to be made. The port specification is a bitmap with the bits set corresponding to the port numbers mentioned in the pin definition.For example, if you want to get the values of two pins PIN_ANALOGUE::A2 and PIN_ANALOGUE::VCC, specify (1 <<PIN_ANALOGUE::A1 | 1<<PIN_ANALOGUE::VCC ). You can also use pack_bits and write pack_bits(PIN_ANALOGUE::A1,PIN_ANALOGUE::VCC).

After calling begin(), the first ADC processing starts immediately, and the processing of the next pin starts from its end interrupt. When all processing is finished (if specified), the callback function is called. It waits until the next timer interrupt occurs before starting a new ADC process.

The second parameter specifies the number of timer interrupts before AC starts. For example, TickTimer is called every 1 ms, but if you specify 16 for the parameter, it will be processed every 16 ms.

void begin()

Starts ADC processing with default ADC pins (PIN_ANALOGUE::A1, PIN_ANALOGUE::A2). end() resumes the interrupted ADC processing.

end()

void end()

ADC processing is terminated and the regulator inside the semiconductor is stopped.

available()

inline bool available()

The value of the ADC is set to true after the acquisition. After confirmation by this function, it is false until the next ADC completion.

read(), read_raw()

inline int16_t read(uint8_t s)
inline int16_t read_raw(uint8_t s)

Reads ADC values. The parameter specifies the ADC pin to be read. read() returns the read value converted to mV and read_raw() returns the ADC value (0..1023).

It is recommended to read Vcc with read(). This is because a special conversion formula must be applied to convert from read_raw() values to mV.

After ADC completion (available), if the value is read after a delay until near the timing when the next ADC process is executed, the next ADC value may be returned, because the ADC process is executed by an interrupt handler and the value is updated even during the loop() process.

ADC interrupt handler.

The ADC interrupt handler is set to periph_analogue::ADC_handler() when setup() is called.

If a different handler is specified by the semiconductor peripheral library, it will not work properly.

Sleep behavior

If the ADC is in cyclic execution state by begin(), ADC processing is resumed after returning from sleep.

Buttons

Digital input management class (mwx::periph_buttons)

Detects changes in digital inputs. This class detects changes when the same detected value is obtained multiple times. This is useful for reducing the effect of chattering on mechanical buttons.

Methods

setup()

void setup(uint8_t max_history);

The parameter max_history is the maximum number of references that can be set with begin(). Memory is allocated and initialized here.

begin()

void begin(uint32_t bmPortMask,
				   uint8_t u8HistoryCount,
				   uint16_t tick_delta);

Starts the Buttons operation, the first parameter bmPortMask specifies the bitmap of digital inputs to be monitored. bit 0 corresponds to DIO 0, ... , bit N corresponds to DIO N. More than one can be specified: the second u8HistoryCount is the number of times required to confirm the value; the third tick_delta specifies the interval in ms between value checks; the fourth u8HistoryCount is the number of times to confirm the value.

It will take u8HistoryCount*tick_delta[ms] to confirm the value. For example, if u8HistoryCount=5 and tick_delta=4, it will take at least 20ms to confirm the state.

The confirmation is done in the TickTimer event handler. Since it is not an interrupt handler, it is affected by delays in processing, etc., but it is sufficient to suppress chattering of mechanical buttons, etc.

end()

void end()

Terminates the Buttons operation.

available()

inline bool available()

Returns true when a change is detected. It is cleared when read() is executed.

read()

bool read(uint32_t& u32port, uint32_t& u32changed)

Called when u32port becomes available. u32portis the bitmap of the current input DIO andu32changed` is the bitmap of the DIO where the change was detected.

Returns false if Buttons is not working.

About operation

Initial value determination

When Buttons starts to operate, the input status of DIO is not yet determined. When the value is determined, it becomes available. At this time, the MSB (bit31) of the bitmap read by read() is set to 1.

Since this function requires the operation to be determined, it cannot be used to monitor ports whose input values change constantly.

Sleep

If Buttons is in operation before Sleep, it will resume after it is restored. After resumption, initial determination is performed.

the_twelite

The core class object of TWENET (mwx::twenet)

The the_twelite object summarizes the procedures for using TWENET and includes procedures for operating the wireless microcontroller, such as basic wireless settings, sleep, and other procedures.

summary.

the_twelite is set up in the setup() function, which also calls the_twelite.begin() to start the process. It is not possible to set up the_twelite except in setup().

void setup() {
  ...
 	the_twelite
		<< TWENET::appid(APP_ID)    
		<< TWENET::channel(CHANNEL) 
		<< TWENET::rx_when_idle();  
  ...
  the_twelite.begin();
}

In the above example, the application ID is set, the communication channel is set, and the receiving circuit is set.

Various procedures are included.

// Get a serial number
uint32_t u32hwser = the_twelite.get_hw_serial();

// Set channel to 11
the_twelite.change_channel(11);

// Sleep for 1 second.
the_twelite.sleep(1000);

// Perform a reset
the_twelite.reset_system();

It is also possible to register classes that handle wireless networks, classes that summarize board support, and classes that perform event-driven processing described by the user. By registering these classes, dedicated functions can be easily used. These classes are referred to as "behaviors" in this explanation.

void setup() {
	/*** SETUP section */
	// use PAL_AMB board support.
	auto&& brd = the_twelite.board.use<PAL_AMB>();
	
	...
	
	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk << NWK_SIMPLE::logical_id(u8ID);

In the above example, two types of behaviors are registered: environmental sensor pal behavior <PAL_AMB> and simple relay network <NWK_SIMPLE>. By registering these, hardware such as sensors on the environmental sensor pal can be easily handled. In addition, the complicated handling of wireless packets can be implicitly provided with functions such as relay processing and automatic discarding of duplicate packet arrivals.

Methods

The MWX library defines other methods in addition to those introduced here.

Act descriptions include those that are not directly related to the act description, those that are set up but do not function effectively, and those that are used internally.

<<operator (setting)

The << operator is used to initialize the object the_twelite.

The following configuration class objects are used as input, and default values are applied if no configuration is made.

TWENET::appid(uint32_t id)

Set the parameter id to the specified application ID. This is a required specification.

Reading the settings is done with uint32_t the_twelite.get_appid().

TWENET::channel(uint8_t ch)

Set the specified channel number (11..26) in parameter ch.

Reading the settings is done with uint8_t the_twelite.get_channel().

TWENET::tx_power(uint8_t pw)

Parameter pw is set to (0. 3) for the radio output. The default is (3: no output attenuation).

Reading the settings is done with uint8_t the_twelite.get_tx_power().

TWENET::rx_when_idle(uint8_t bEnable)

If the parameter bEnable is 1, the receiver circuit is always activated and can receive radio packets from others. The default is 0, which is dedicated to transmission only.

Reading the settings is done with uint8_t the_twelite.get_rx_when_idle().

TWENET::chmgr(uint8_t ch1 = 18, uint8_t ch2 = 0, uint8_t ch3 = 0)

Enables the channel manager. If multiple channels are specified, sending/receiving is performed on multiple channels; if 0 is specified for ch2 and ch3, the specification is disabled.

STG_STD

Reflects the setting values in interactive mode.

auto&& set = the_twelite.settings.use<STG_STD>();
...
set.reload();       // Load settings
the_twelite << set; // Reflects interactive mode settings

The items that will be reflected are as follows

  • app_id

  • channel

  • tx_power

  • Number of retransmissions when MAC ack is used

There are other settings in the MWX library code that are irrelevant to the library's functionality at this time or that may cause conflicts if set.

begin()

void begin()

It is executed after preconfiguration (see << operator) and registration of the behavior. It is usually placed at the end of the setup() function.

  • the_twelite finished setting up

  • Behavior initialization

TWENET initialization is also performed after the setup() function exits. Since many processes are to be executed after setup() exits, do not do anything other than initialization here.

例

void setup() {
	// use PAL_AMB board support.
	auto&& brd = the_twelite.board.use<PAL_AMB>();
	
	// settings
 	the_twelite
		<< TWENET::appid(APP_ID)    
		<< TWENET::channel(CHANNEL) 
		<< TWENET::rx_when_idle();  
	
	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk << NWK_SIMPLE::logical_id(u8ID);
	
	// somo others
	
	// begin the TWENET!
	the_twelite.begin();
}

change_channel()

inline bool change_channel(uint8_t u8Channel)

Changes channel settings. On failure, the channel is not changed and false is returned.

get_channel_phys()

uint8_t get_channel_phys()

Obtains the currently set channel number (11..26) from the MAC layer API.

get_hw_serial()

inline uint32_t get_hw_serial()

Get the module serial number.

sleep()

inline void sleep(
        uint32_t u32Periodms, 
        bool_t bPeriodic = true, 
        bool_t bRamOff = false, 
        uint8_t u8Device = TWENET::SLEEP_WAKETIMER_PRIMARY)

Put the module to sleep.

parameter
explanation

u32Periodms

Sleep duration[ms]

bPeriodic

Recalculate the next wake-up time based on the previous wake-up time. ※It may be from the current timing for reasons such as the next wake-up timing is imminent.

bRamoff

Set to true to sleep without retaining RAM (must be reinitialized from setup() instead of wakeup() after wakeup)

u8Device

Designation of a wake-up timer to be used for sleep. Specify

TWENET::SLEEP_WAKETIMER_PRIMARYorTWENET::SLEEP_WAKETIMER_SECONDARY.

Before sleep, the on_sleep() method of the built-in object or behavior is called to perform the pre-sleep procedure. After returning from sleep, the opposite procedure is performed by the on_wakeup() method.

is_wokeup_by_dio()

bool is_wokeup_by_dio(uint8_t port)

Returns true if the return factor from sleep is the specified digital pin.

is_wokeup_by_wktimer()

bool is_wokeup_by_wktimer()

Returns true if the wake-up timer is the wake-up factor for returning from sleep.

reset_system()

inline void reset_system()

Resets the system. After reset, the process starts from setup().

stop_watchdog()

inline void stop_watchdog()

Stops the watchdog timer. Stop the timer if you are going to wait for polling for a long time.

The watchdog timer is restarted each time in the main loop inside the library. It takes about 4 seconds for the timer to run out and reset.

restart_watchdog()

inline void restart_watchdog()

Restart the watchdog timer.

Behavior.

Three behaviors can be registered with the_twelite, and the following class objects are defined to store them.

  • network : A behavior that implements a network. Normally, <NWK_SIMPLE> is registered.

  • network2 : BEHAVIOR that implements networking. Use this behavior if you want another network BEHAVIOR to process packets which were not accepted by the first network due to the data structure of the payload or other reasons. (Reference: Using NWK_LAYERED and NWK_SIMPLE together)

  • board: Behaviors for board support. Procedures for using each device on the board are added.

  • app: Behaviors describing user applications. Behaviors can be written in terms of interrupts, events, and state transitions using state machines. It is also easy to define multiple application descriptions and select an application with completely different behavior at startup.

  • settings: Behavior for performing configuration (interactive mode). Registers <STG_STD>.

use<B>()

// example
auto&& brd = the_twelite.board.use<PAL_AMB>();

Register behavior . Registration is done within setup(). The return value is a reference to the object corresponding to the registered behavior.

After registration, the object is retrieved in the same way as at the time of registration.

If an incorrect behavior is specified, a panic operation (infinite loop) occurs and program operation stops.

It is not intended to declare objects of the behavior as global variables. Please use use<B>() for each use.

void loop() {
  auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
}

However, it is possible to define a pointer to an object in a global variable and write as follows (The MWX library has a policy of minimizing the use of pointer types and using reference types, so the following description is not recommended.)

NWK_SIMPLE *pNwk = nullptr;

setup() {
  auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	pNwk = &nwk;
}

void transmit() {
  if (auto&& pkt = pNwk->prepare_tx_packet()) {
    ...
  }
}

class object.

the_twelite defines the three class objects board, network, and app mentioned above, but also the following

tx_status

Notification of transmission completion status.

The description of the event-driven behavior is managed by the transmit_complete() callback.

is_complete()

bool is_complete(uint8_t cbid)

Returns true when the packet with the specified ID has completed transmission.

is_success()

bool is_success(uint8_t cbid)

Returns true when the packet with the specified ID has completed transmission and has been successfully sent.

receiver

Retrieve incoming packets.

In the event-driven behavior description, it is obtained in the receive() callback.

The received packet data obtained by the read() method is designed to be overwritten when subsequent packets are received and processed. If you read the data immediately after available() and do some short processing, this will not be a problem, but as a general rule, read the data, copy the data needed for application use, and finish loop() promptly. For example, a long delay() during loop() will cause incoming packets to be dropped.

available()

bool available()

Returns true if there is an incoming packet that has not yet been read.

read()

packet_rx& read()

Read packets.

PAL_MOT-single

This ACT acquires several samples of acceleration data after sleep recovery and sends that data.

The ACT of the "Act" includes the following

  • Sending and receiving wireless packets

  • Configuration through Interactive settings mode - <STG_STD>

  • State transition control by state machine - <SM_SIMPLE>

  • <PAL_MOT> or <CUE> board operation with board BEHAVIOR

Explanation of ACT

Wake up → start acquiring accelerometer data → wait for accelerometer FIFO interrupt → retrieve accelerometer data → wireless transmission → sleep.

The accelerometer stops adding data to the FIFO queue when the FIFO queue is full.

宣言部

インクルード

#include <TWELITE> // MWX library basic
#include <NWK_SIMPLE> // network
#include <SM_SIMPLE> // State machine (state transition)
#include <STG_STD> // Interactive settings 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 part is a macro. Define either USE_PAL_MOT or USE_CUE.

If USE_PAL_MOT is defined, the board BEHAVIOR <PAL_MOT> is included.

state-defined

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;

Define states for sequential processing during loop() and also state machinestep 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, configuration, and network behavior objects.

Interactive settings 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.

Initialize the Interactive settings mode.

First, adjust the configuration items. Here, we set the title name SETTINGS::appname to be displayed in menu items, the default value of Application ID SETTINGS::appid_default, the default value of CHANNEL SETTINGS::ch_default, the default value of logical device ID default SETTINGS::lid_default, and .hide_items() for hidden items.

This sample transitions to Interactive settings mode when the SET pin is LO at startup. If digitalRead(brd.PIN_SET) confirms that the pin is LO, specify SETTINGS::open_at_start(). This specification causes the Interactive settings mode screen to appear immediately after exiting SETUP(). When the screen is displayed, begin() and loop() are executed. In this sample, the state STATE::INTERACTIVE is set so that no action such as sleep is performed during loop() and nothing is done.

Next, the configuration values are read. To read the configuration values, be sure to execute .reload(). In this sample, the option bit setting .u32opt1() is read.

the_twelite

the_twelite << set;

the_twelite is a class object that manages the basic behavior of the system. This object performs various initializations such as Application ID and CHANNEL within setup().

Here some of the settings for Interactive settings mode is reflected.

If you wish to change an item reflected in the Interactive settings mode settings to another setting, continue with the setting you wish to override.

the_twelite << set;// Interactive settings mode
the_twelite << twenet::channel(19); // set ch overwrite to 19

NWK_SIMPLE Object

nwk << set;

Settings are also made for the network behavior object. The logical device ID (LID) and retransmission settings in Interactive settings mode are reflected.

Other hardware initialization, etc.

brd.set_led(LED_TIMER::BLINK, 100);

LED blink settings and other settings.

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() is finished. Here, the first sleep is performed. However, if the screen in Interactive settings mode is displayed, it does not sleep.

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. After this, 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() is <SM_STATE> state machine state with switch ... . case clause. The initial state is STATE::INIT or STATE::INTERACTIVE.

STATE::INTERACTIVE

This is the state of the Interactive settings mode screen when it is displayed. Nothing is done. Interactive settings mode is used for Serial input and output in this screen.

STATE::INIT

INIT in its initial state.

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 is performed. transition to STATE::START_CAPTURE. After setting 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 the state START_CAPTURE, the FIFO acquisition of the MC3630 sensor is started. Here, the FIFO interrupt is set to occur when 4 samples are acquired at 400 Hz.

Set timeout for exception handling 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 the state WAIT_CAPTURE, it waits for a FIFO interrupt. When an interrupt occurs and data is stored in the result storage queue, sns_MC3630.available() becomes true. Call sns_MC3630.end() to terminate the process.

sensor.n_samples = brd.sns_MC3630.get_que().size();
if (sensor.n_samples) sensor.n_seq = brd.sns_MC3630.get_que()[0].get_t();
...

Get the number of samples and sample sequence numbers.

// 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;
}
...

Reads and averages all sample data.

// 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 maximum and minimum are obtained for the acquired samples using the iterator corresponding to each axis.

The std::mimmax_element is introduced as an example of using the algorithm of the C++ Standard Template Library, but you can also find the maximum and minimum in the for loop as shown in the comments.

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;

Call .sns_MC3630.get_que().clear() to clear the data in the queue. If this is not called, subsequent sample acquisitions will not be possible. After that, it transits to the STATE::REQUEST_TX state.

. is_timeout() checks for timeout. If timeout occurs, it transits 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 and send a transmission packet. The transmission request may fail due to the status of the transmission queue or other factors. If the transmission request succeeds, TxReq()returns as true, but no transmission is performed yet. Theon_tx_comp()` callback is called to complete the transmission.

Also, .clear_flag() clears the flag to indicate that transmission is complete. At the same time, a timeout is set.

E_SATE::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 the completion of wireless packet transmission. The flag is set by the on_tx_comp() callback function, and .is_flag_ready() becomes true after it is set.

E_SATE::EXIT_NORMAL, FATAL

case STATE::EXIT_NORMAL:
	sleepNow();
break;

case STATE::EXIT_FATAL:
	Serial << flush;
	the_twelite.reset_system();
break;

When a series of operations is completed, it transits to the state STATE::EXIT_NORMAL and calls the locally defined function sleepNow() to execute sleep. When an error is detected, it transits 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;
}

The last step is to generate a packet and request that it be sent. The packet should include the continuation number, the number of samples, the average value of XYZ, the minimum sample value of XYZ, and the maximum sample value of XYZ.

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.

  • Serial ports should call Serial.flush() to output all before sleep.

  • The <SM_SIMPLE> state machine must do on_sleep().

PAL_MAG

OPEN-CLOSE SENSE PAL is used to acquire sensor values.

This ACT includes

  • Sending and receiving wireless packets

  • Configuring settings via Interactive settings mode - <STG_STD>

  • State transition control by state machine - <SM_SIMPLE>

  • <PAL_MAG> or <CUE> board manipulation via board behavior

ACT functions

  • Uses the OPEN-CLOSE SENSE PAL to interrupt and wake up when the magnetic sensor is detected and transmit wirelessly.

  • Uses the sleep function to operate on coin cell batteries.

How to use ACT

Required TWELITE

Role
Example

Parent Node

Child Node

Explanation of ACT

Include

#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_MAG>

Board BEHAVIOR for open/close sensor pal <PAL_MAG> is included.

setup()

void setup() {
	/*** SETUP section */
	// use PAL_AMB board support.
	auto&& brd = the_twelite.board.use<PAL_MAG>();
	// now it can read DIP sw status.
	u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
	if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE

	// LED setup (use periph_led_timer, which will re-start on wakeup() automatically)
	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

	// the twelite main object.
	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL); // set channel (pysical channel)

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk << NWK_SIMPLE::logical_id(u8ID); // set Logical ID. (0xFE means a child device with no ID)

	/*** BEGIN section */
	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- PAL_MAG:" << FOURCHARS << " ---" << mwx::crlf;
}

First, register the board behavior <PAL_MAG>. When the board behavior is initialized, the sensors and DIOs are initialized. The reason for doing this first is that it is common to check the status of the board's DIP SW, etc., and then configure the network settings and so on.

auto&& brd = the_twelite.board.use<PAL_MAG>();

u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE

Here, 3 bits of the 4-bit DIP SW on the board are read and set as the ID of the Child Node; if 0, the Child Node without ID (0xFE) is assumed.

Set the LED settings. Here the ON/OFF blinking is set to blink every 10ms (in an application that sleeps and has a short wake-up time, this is almost equivalent to setting the LED to turn on during wake-up).

	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

begin()

The begin() function exits the setup() function (after which TWENET is initialized) and is called just before the first loop().

void begin() {
	sleepNow(); // the first time is just sleeping.
}

Call sleepNow() after setup() ends to perform the first sleep.

sleepNow()

void sleepNow() {
	uint32_t u32ct = 60000;
	
	pinMode(PAL_MAG::PIN_SNS_OUT1, PIN_MODE::WAKE_FALLING);
	pinMode(PAL_MAG::PIN_SNS_OUT2, PIN_MODE::WAKE_FALLING);

	the_twelite.sleep(u32ct);
}

Before going to sleep, set the interrupt for the DIO pin of the magnetic sensor. Use pinMode(), the second parameter is PIN_MODE::WAKE_FALLING. This is a setting to wake up when the pin state changes from HIGH to LOW.

In line 7, the_twelite.sleep() executes sleep. The parameter 60000 is the wake-up setting required to reset the watchdog on the TWELITE PAL board. If not reset, a hard reset will be performed after 60 seconds.

wakeup()

When the program wakes up from sleep, wakeup() is called. After that, loop() is called each time. Before wakeup(), each peripheral such as UART and devices on the board are woken up (e.g. resetting the watchdog timer). For example, it restarts the LED lighting control.

void wakeup() {
	if (the_twelite.is_wokeup_by_wktimer()) {
		sleepNow();
	}
}

Here, in the case of wake-up from the wake-up timer (the_twelite.is_wokeup_by_wktimer()), sleep is performed again. This is a wake-up only for the purpose of resetting the watchdog timer described above.

In the case of wakeup upon detection of the magnetic sensor, the loop() process will continue.

loop()

Here, the DIO of the detected magnetic sensor is checked, packets are sent, and sleep is performed again after packet transmission is complete.

void loop() {
	if (!b_transmit) {
		if (auto&& pkt = 
      the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet())

			uint8_t b_north = 
			  the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_NORTH);
			uint8_t b_south = 
			  the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_SOUTH);
	
			Serial << "..sensor north=" << int(b_north) 
			       << " south=" << int(b_south) << mwx::crlf;
	
			// set tx packet behavior
			pkt << tx_addr(0x00)
				<< tx_retry(0x1)
				<< tx_packet_delay(0, 0, 2);
	
			// prepare packet payload
			pack_bytes(pkt.get_payload()
				, make_pair(FOURCHARS, 4) 
				, b_north
				, b_south
			);
	
			// do transmit
			MWX_APIRET ret = pkt.transmit();
	
			if (ret) {
				u8txid = ret.get_value() & 0xFF;
				b_transmit = true;
			}
			else {
				// fail to request
				sleepNow();
			}
		} else {
		  sleepNow();
		}
	} else { 
		if (the_twelite.tx_status.is_complete(u8txid)) {		
			b_transmit = 0;
			sleepNow();
		}
	}
}

The b_transmit variable controls the behavior within loop(). After a successful transmission request, this value is set to 1, and the program waits for the packet transmission to complete.

	if (!b_transmit) {

Check the detection DIO pins of the magnetic sensor. There are two types of detection pins: N-pole detection and S-pole detection. If you simply want to know that a magnet is approaching, you need to know that one of the pins has been detected.

uint8_t b_north = 
  the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_NORTH);
uint8_t b_south = 
  the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_SOUTH);

Use the_twelite.is_wokeup_by_dio() to check the pin of the wakeup factor. The parameter is the pin number. The reason why the return value is stored in uint8_t is to store it in the payload of the packet.

After setting communication conditions and storing data in the payload, transmission is performed.

// do transmit
MWX_APIRET ret = pkt.transmit();

Then, if b_transmit is true during loop(), a completion check is performed, and if completed, sleepNow() puts the program to sleep.

if (the_twelite.tx_status.is_complete(u8txid)) {		
	b_transmit = 0;
	sleepNow();
}

The completion of transmission is confirmed by the_twelite.tx_status.is_complete(u8txid). The u8txid is the ID value returned on transmission.

SPI (using member functions)

SPI access (using member functions)

After initializing the hardware by the begin() method, the bus can be read and written by beginTransaction(). Executing beginTransaction() selects the SPI select pin. Read/write is done with the transfer() function; SPI reads and writes at the same time.

beginTransaction()

Starts the use of the bus; sets the SPI select pin.

If called with the settings parameter given, the bus is set.

endTransaction()

Terminates the use of the bus; releases the SPI select pin.

transfer(), transfer16(), transfer32()

Reads and writes the bus. transfer() transfers 8 bits, transfer16() transfers 16 bits, and transfer32() transfers 32 bits.

Serial

UART0 port of TWELITE (mwx::serial_jen)

  • The Serial object is initialized at system startup with UART0, 115200 bps, and the initialization process is performed in the library. On the user code, it is available from setup().

  • The Serial1 object is provided in the library, but no initialization process is performed; to enable UART1, perform the necessary initialization procedures Serial1.setup(), Serial1.begin().

Output may become unstable during setup(), wakeup() just after startup or flush process just before sleep.

setup()

Initialize objects.

  • Allocate memory for FIFO buffers for TX/RX

  • Allocating memory for TWE_tsFILE structure

Serial(UART0) will automatically call setup() in the library. There is no need for a user call.

Also, the buffer size of Serial (UART0) is determined at compile time. You can change it by the macro MWX_SER_TX_BUFF (768 if not specified) or MWX_SER_RX_BUFF (256 if not specified).

begin()

Initialize hardware.

The Serial (UART0) has an automatic begin() call in the library. No user call is required.

The last two digits of the specified baud rate are rounded to zero. Also, due to hardware limitations, there may be an error from the specified baud rate.

When specifying 9600, 38400, or 115200, the baud rate is calculated without division. See constexpr uint16_t _serial_get_hect_baud(uint32_t baud) for details of processing.

end()

(Not implemented) Stop using hardware.

get_tsFile()

Get a structure in the TWE_tsFILE* format used in the C library.

In Serial (UART), the _sSerial structure is defined.

TickTimer

system timer (mwx::periph_ticktimer)

TickTimer is used for internal control of TWENET and is implicitly executed. The period of the timer is 1ms. Only the available() method is defined in loop() for the purpose of describing processing every 1ms by the TickTimer event.

Note that it is not always available in 1ms increments.

There are cases in which events are skipped due to a large delay caused by factors such as the contents of the user program description or the system's internal interrupt processing.

Methods

available()

It is set after the TickTimer interrupt occurs and becomes true in the loop() immediately following it. It is cleared after loop() ends.

SPI (using helper class)

SPI access (using helper class)

The helper class version is a more abstract implementation. Creating a transceiver object for reading and writing is the start of using the bus, and destroying the object is the end of using the bus.

By creating the object in the decision expression of the if statement, the validity period of the object is limited to the scope of the if clause, and the object is destroyed when it exits the if clause, at which point the bus usage termination procedure is performed.

In addition, read/write objects implement the mwx::stream interface, allowing the use of the << operator, etc.

  • The start and end of bus usage is aligned with the validity period of the object to improve the visibility of the source code and to prevent omissions in the termination procedure, etc.

  • Unify read/write procedures with the mwx::stream interface

read/write

Reading process and its termination procedure in scope if() { ... } to do the reading with a helper class.

In the above, the x object created by the get_rwer() method is used to read and write one byte at a time. 1.

  1. create x object in if(...) Generate the x object in the get_rwer() method. At the same time, set the select pin of the SPI bus. (The type is resolved with the universal reference auto&& by type inference. 2.)

  2. the generated x object has a operator bool () defined, which is used to evaluate the decision expression, which is always true for the SPI bus.

  3. x object defines uint8_t transfer(uint8_t) method, which is called to perform 8bit read/write transfer to SPI. 4. 4.if() { ... } Destructor of x is called at the end of the scope to release the select pin of the SPI bus.

get_rwer()

Obtains the worker object used to read/write the SPI bus.

worker object

transfer() transfer16() transfer32()

Each transfers 8-bit, 16-bit, and 32-bit data, and returns the read result with the same data width as the written data width.

<< operator

The int and uint8_t types perform 8-bit transfer.

Types uint16_t and uint32_t perform 16-bit and 32-bit transfers, respectively.

The transfer result is stored in an internal FIFO queue of up to 16 bytes and read by the >> operator. Since the buffer is not large, it is assumed to be read out after each transfer.

>> operator

Specify a variable with the same data width as the previous transfer.

If the result of the read is not needed, use the null_stream() object; it skips reading by the data byte specified by i. If the result of the read is not needed, use the null_stream() object.

Timer0 .. 4

Timers, PWM (mwx::periph_timer)

The timer has two functions: to generate a software interrupt at a specified period, and to output a PWM at a specified period. 5 timers in total are available in the TWELITE radio module, from 0...4.

The name of the built-in object is Timer0..4, but will be referred to as TimerX on this page.

Methods

setup()

Initializes the timer. This call allocates the necessary memory space.

begin()

Starts a timer; the first parameter is the period of the timer in Hz; setting the second parameter to true enables software interrupts; setting the third parameter to true true` enables PWM output.

You can change the frequency with change_hz(). The change_hz() allows more detailed specification than the begin() specification.

The duty ratio of PWM output can be changed with change_duty().

end()

Stops the timer operation.

available()

It becomes true at loop() immediately after a timer interrupt occurs, and becomes false when loop() ends.

change_duty()

Set the duty ratio. the first parameter specifies the duty ratio (a small value makes the average of the waveform closer to the GND level, a large value makes it closer to the Vcc level). the second parameter specifies the maximum duty ratio value of the duty ratio.

It is recommended that duty_max be one of 1024,4096,16384.

The internal calculation of the count value involves division, but only for these three values is bit shifting used, while for other values, a computationally expensive division process is performed.

change_hz()

Sets the frequency of the timer; the second parameter is an integer with three decimal places for the frequency. For example, to set the frequency to 10.4 Hz, specify hz=10, mil=400.

SerialParser

format input parser for serial input (mwx::serial_parser)

This built-in class is defined as a built-in object, intended to be used for format input via serial port.

It is defined as a mwx::serial_parser<mwx::alloc_heap<uint8_t>> type that allocates buffer space for internal use from the heap at initialization (begin()).

PulseCounter

PulseCounter (mwx::periph_pulse_counter)

The pulse counter is a circuit that reads and counts pulses even when the microcontroller CPU is not running. There are two systems of pulse counters: PC0 is assigned to PulseCounter0 and PC1 to PulseCounter1.

PC0 is assigned to PulseCounter0 and PC1 is assigned to PulseCounter1.

method

begin()

Initializes the object and starts counting. the first parameter refct is the count number on which interrupts and available decisions are based. When this number is exceeded, it is reported to the application. You can also set refct to 0. In this case, it is not a sleep wake-up factor.

The second parameter edge specifies whether the interrupt is rising (PIN_INT_MODE::RISING) or falling (PIN_INT_MODE::FALLING).

The third debounce takes the values 0, 1, 2, or 3. 1, 2, or 3 settings require the same consecutive value to detect a change in value to reduce the effect of noise.

end()

Discontinue detection.

available()

If the specified count (refct in begin()) is 0, true is returned when the count exceeds 1.

If the specified count (refct in begin()) is 1 or more, true is returned when the number of detections exceeds the specified count.

read()

Reads the count value. Resets the count value to 0 after reading.

Act in action.

+

Implement and input/output with UART0 of TWELITE.

Parameter
Description
Parameters
Description

To describe the processing of the interrupt handler, specify it in the definition.

For details, see class for details.

setting
number of consecutive samples
maximum detection frequency
void beginTransaction()
void beginTransaction(SPISettings settings)
void endTransaction()
inline uint8_t transfer(uint8_t data)
inline uint16_t transfer16(uint16_t data)
inline uint32_t transfer32(uint32_t data)
void setup(uint16_t buf_tx, uint16_t buf_rx)

buf_tx

FIFO buffer size for TX

buf_rx

FIFO buffer size for RX

void begin(unsigned long speed = 115200, uint8_t config = 0x06)

speed

Specifies the baud rate of the UART.

config

When the serial_jen::E_CONF::PORT_ALT bit is specified, UART1 is initialized with DIO14,15. If not specified, initializes UART1 with DIO11(TxD),9(RxD).

TWE_tsFILE* get_tsFile();
void loop() {
  if (TickTimer.available()) {
    if ((millis() & 0x3FF) == 0) { // This may not be processed (could be skipped)
      Serial << '*';
    }
  }
}
inline bool available()
uint8_t c;
if (auto&& trs = SPI.get_rwer()) { // Object creation and device communication determination
   // Within this scope (wave brackets) is the validity period of trs.
   trs << 0x00; // Writes 0x00 with mwx::stream interface
   trs >> c;    // Store the read data in c.
} 
// wrt is discarded at the point where the if clause is exited, and the use of the bus is terminated
inline uint8_t _spi_single_op(uint8_t cmd, uint8_t arg) {
    uint8_t d0, d1;
    if (auto&& x = SPI.get_rwer()) {
        d0 = x.transfer(cmd); (void)d0;
        d1 = x.transfer(arg);
        // (x << (cmd)) >> d0;
        // (x << (arg)) >> d1;
    }

    return d1;
}
periph_spi::transceiver get_rwer()
uint8_t transfer(uint8_t val)
uint16_t transfer16(uint16_t val)
uint32_t transfer32(uint32_t val)
operator << (int c)
operator << (uint8_t c)
operator << (uint16_t c) 
operator << (uint32_t c)
operator >> (uint8_t& c)
operator >> (uint16_t& c)
operator >> (uint32_t& c)

null_stream(size_t i = 1)
operator >> (null_stream&& p)
void setup()
void begin(uint16_t u16Hz, bool b_sw_int = true, bool b_pwm_out = false)
void end()
inline bool available()
void change_duty(uint16_t duty, uint16_t duty_max = 1024)
void change_hz(uint16_t hz, uint16_t mil = 0) 
void begin(uint16_t refct = 0, 
           E_PIN_INT_MODE edge = PIN_INT_MODE::FALLING,
           uint8_t debounce = 0)

0

-

100Khz

1

2

3.7Khz

2

4

2.2Khz

3

8

1.2Khz

void end()
inline bool available()
uint16_t read()
MONOSTICK BLUE or RED
Parent_MONOSTICK
BLUE PAL or RED PAL
OPEN-CLOSE SENSE PAL
mwx::stream
application behavior
serparser

SPI

Reads and writes the SPI bus (as Controller).

Reads and writes the SPI bus (as Controller).

Constants

Constant
Meaning

const uint8_t SPI_CONF::MSBFIRST

MSB as the first bit

const uint8_t SPI_CONF::LSBFIRST

make LSB the first bit

const uint8_t SPI_CONF::SPI_MODE0

set SPI MODE 0

const uint8_t SPI_CONF::SPI_MODE1

set to SPI MODE 1

const uint8_t SPI_CONF::SPI_MODE2

set to SPI MODE 2

const uint8_t SPI_CONF::SPI_MODE3

set to SPI MODE 3

Initialization and termination

The procedure for using the SPI bus depends on the begin() method.

Reads and writes the SPI bus (MASTER).

begin()

void begin(uint8_t slave_select, SPISettings settings)
SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode)

Initialize hardware.

This process is also required after sleep recovery.

Parameters
Description

slave_select

Specify the SPI slave select pin to be used. 0 : DIO19, 1 : DIO0 (DIO 19 is reserved), 2 : DIO1 (DIO 0,19 is reserved)

settings

Specifies the SPI bus setting. The clock[hz] specifies the SPI bus frequency. A divisor closer to the specified frequency will be selected: 16Mhz or 16Mhz divided by an even number. bitOrder specifies SPI_CONF::MSBFIRST or SPI_CONF::LSBFIRST. dataMode specifies SPI_CONF::SPIMODE0..3.

Example

void setup() {
  ...
  SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
  ...
}

void wakeip() {
  ...
  SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
  ...
}

end()

void end()

Terminate the use of SPI hardware.

Reading and Writing

There are two types of read/write procedures. Select and use one of them.

  • Member function version (input/output using the following member functions) beginTransaction(), endTransaction(), transfer(), transfer16(), transfer32()

  • Helper class version (stream function available) transceiver

Wire

Read/write two-wire serial (I2C) master (mwx::periph_wire)

Reads and writes two-wire serial (I2C) master.

Alias Definition

using TwoWire = mwx::periph_twowire<MWX_TWOWIRE_RCVBUFF>;

The mwx::periph_wire<MWX_TWOWIRE_RCVBUFF> can be referred as TwoWire.

Type Definitions

The following definition types describe the types of arguments and return values.

typedef uint8_t size_type;
typedef uint8_t value_type;

hint style="warning %}

Some APIs make calls where the STOP bit is not strictly handled.

write(), writer::operator() () has several arguments defined in addition to the ones described here.

  • fixed array type uint8_t cmds[]={11,12}; ... Wire.write(cmds);

  • initializer_list<> 型 Wire.write({11,12})

Initialization and termination

Wire Instance Creation

Instance creation and necessary initialization is done in the library. In user code, it is made available by calling Wire.begin().

When using the requestFrom() method, you can specify the size of the FIFO queue for temporary data storage. At compile time, compile the macro MWX_TWOWIRE_BUFF with the required number of bytes. The default is 32 bytes.

Example: -DMWX_TWOWIRE_BUFF=16

begin()

void begin(
    const size_type u8mode = WIRE_100KHZ,
    bool b_portalt = false)

Initialize hardware.

Performing any Wire operation without initialization will cause the TWELITE radio module to hang.

When waking from sleep, if the module was operating just before sleep, it will return to the previous state.

Parameters
Description

u8mode

Specifies the bus frequency. Default is 100Khz (WIRE_CONF::WIRE_100KHZ) The frequency is specified by WIRE_CONF::WIRE_? KHZ and ? KHZ can be 50,66,80,100,133,160,200,266,320,400.

b_portalt

Change hardware pin assignments.

Example

void setup() {
    ...
    Wire.begin();
    ...
}

void wakeup() {
    ...
    Wire.begin();
    ...
}

Reading and Writing

There are two types of read/write procedures. Select and use one of them.

  • Member function (input/output using the following member functions) requestFrom(), beginTransmission(), endTransmission(), write()

  • Helper class (stream function available) reader, writer

Others

Probe(Determine presence of device)

bool probe(uint8_t address)

Checks if the device specified by address is responding. If the device exists, true is returned.

setClock()

void setClock(uint32_t speed)

The procedure is originally intended to change the bus frequency, but no action is taken.

Wire (using helper class)

accessing Wire (using helper class)

The helper class version is a more abstract implementation. Creating objects reader, writer for reading and writing is the start of using the bus, and destroying the objects is the end of using the bus.

By creating the object in the decision expression of the if statement, the validity period of the object is limited to the scope of the if clause, and the object is destroyed when it exits the if clause, at which point the bus usage termination procedure is performed.

if (auto&& wrt = Wire.get_writer(...)) { // Object creation and device communication determination
   // Within this scope (wave brackets) is the validity period of wrt.
   wrt << 0x00; // Writes 0x00 with mwx::stream interface
} 
// wrt is discarded at the point where the if clause is exited, and the use of the bus is terminated

Also, read/write objects implement the mwx::stream interface, so you can use the << operator, etc.

  • Aligning the start and end of bus usage with the validity period of objects improves the visibility of source code and prevents omissions of termination procedures.

  • Unification of read/write procedures by mwx::stream interface

loading

Loading process and its termination procedure in scope if() { ... } in scope.

const uint8_t DEV_ADDR = 0x70;
uint8_t au8data[6];
if (auto&& rdr = Wire.get_reader(DEV_ADDR, 6)) {
		for (auto&& c: au8data) {
			c = rdr();
		}
}

// same above
uint16_t u16temp, u16humd;
uint8_t u8temp_csum, u8humd_csum;
if (auto&& rdr = Wire.get_reader(SHTC3_ADDRESS, 6)) {
		rdr >> u16temp;
		rdr >> u8temp_csum;
		rdr >> u16humd;
		rdr >> u8humd_csum;
}

The above reads one byte at a time using the rdr object generated by the get_readr() method. The parameter of the method is the two-line serial ID to be read. 1.

  1. in if(...) Create a rdrobject in. (The type is resolved with the universal reference auto&& by type inference. 2.) The generated rdr object defines an operator bool (), which is used to evaluate the decision expression. If communication is possible with the specified ID, true is returned. 3. The rdr object defines an int operator () (void) operator, which is called to read one byte of data from the 2-wire serial bus. If the read fails, -1 is returned, and if it succeeds, the read byte value is returned. 4.

  2. if() { ... } The destructor of rdr is called at the end of the scope to STOP the two-wire serial bus.

get_reader(addr, read_count=0)

periphe_wire::reader
get_reader(uint8_t addr, uint8_t read_count = 0)

I2C 読み出しに用いるワーカーオブジェクトを取得します。

Parameters
Description

addr

I2C address for reading

read_count

Number of bytes to read (specifying this value will issue a STOP bit on the last transfer); specifying 0 will result in no STOP bit (some devices may work)

write (writer)

Reading with a helper class to perform the writing process and its termination procedure in scope if() { ... } in scope.

const uint8_t DEV_ADDR = 0x70;
if (auto&& wrt = Wire.get_writer(DEV_ADDR)) {
	wrt(SHTC3_TRIG_H);
	wrt(SHTC3_TRIG_L);
}

// same above
if (auto&& wrt = Wire.get_writer(DEV_ADDR)) {
	wrt << SHTC3_TRIG_H; // int type is handled as uint8_t
	wrt << SHTC3_TRIG_L;
}

In the above, the wrt object generated by the get_writer() method is used to write one byte at a time. The method parameter is the two-line serial ID to be read.

  1. if(...) Generate a wrtobject in. (The type name is resolved with auto' because it is long. 2.) The generated wrtobject defines anoperator bool (), which is used to evaluate the decision expression. If communication is possible with the specified ID, trueis returned. 3. Thewrtobject defines anint operator () (void)operator, which is called to write one byte of data to the 2-wire serial bus. If it fails,-1` is returned, and if it succeeds, the written byte value is returned. 4.

  2. if() { ... } The destructor of wrt is called at the end of the scope to STOP the two-wire serial bus.

get_writer()

periph_wire::writer
get_writer(uint8_t addr)

Obtains the worker object used for I2C export.

Parameters
Description

addr

I2C address for writing out

Worker object (writer)

<< operator

operator << (int c)
operator << (uint8_t c)
operator << (uint16_t c) 
operator << (uint32_t c)

The int and uint8_t types transfer 8 bits.

() operator

operator() (uint8_t val)
operator() (int val)

Write out 1 byte.

worker object (reader)

>> operator

operator >> (uint8_t& c)
operator >> (uint16_t& c)
operator >> (uint32_t& c)
operator >> (uint8_t(&c)[N]) // Fixed array of N bytes

Reads only the size of each data type.

() operator

int operator() (bool b_stop = false)

//Example
uint8_t dat[6];
if (auto&& rdr = Wire.get_reader(0x70)) {
  for(uint8_t& x : dat) {
    x = rdr();
  }
}

Reads 1 byte. Returns -1 if there is an error, returns the byte value read if normal.

If b_stop is set to true, the STOP bit is issued on that read.

Example

The following example shows a measurement example of the temperature/humidity sensor SHTC3 of the environmental sensor PAL.

Wire.begin();
// reset (may not necessary...)
if (auto&& wrt = Wire.get_writer(0x70)) {
	wrt << 0x80; // SHTC3_SOFT_RST_H
	wrt << 0x05; // SHTC3_SOFT_RST_L
}

delay(5); // wait some

// start read
if (auto&& wrt = Wire.get_writer(0x70)) {
	wrt << 0x60; // SHTC3_TRIG_H
	wrt << 0x9C; // SHTC3_TRIG_L
}

delay(10); // wait some

// read result
uint16_t u16temp, u16humd;
uint8_t u8temp_csum, u8humd_csum;
if (auto&& rdr = Wire.get_reader(0x70, 6)) {
	rdr >> u16temp;
	rdr >> u8temp_csum;
	rdr >> u16humd;
	rdr >> u8humd_csum;
}

// checksum 0x31, init=0xFF
if (CRC8_u8CalcU16(u16temp, 0xff) != u8temp_csum) {
	Serial << format("{SHTC3 T CKSUM %x}", u8temp_csum); }
if (CRC8_u8CalcU16(u16humd, 0xff) != u8humd_csum) {
	Serial << format("{SHTC3 H CKSUM %x}", u8humd_csum); }

// calc temp/humid (degC x 100, % x 100)
int16_t i16Temp = (int16_t)(-4500 + ((17500 * int32_t(u16temp)) >> 16));
int16_t i16Humd = (int16_t)((int32_t(u16humd) * 10000) >> 16);

Serial << "temp=" << int(i16Temp) 
	   << ",humid=" << int(i16Humd) << mwx::crlf;

alloc

Specified as a template argument of a container class (smplbuf, smplque) to allocate or specify an area of memory to be used internally.

This class is not called directly from user code, but is used internally to declare containers.

Class Name
Contents

alloc_attach<T>

specify an already existing buffer

alloc_local<T, int N>

statically allocate a buffer of N bytes internally

alloc_heap<T>

allocate a buffer of the specified size in the heap

In alloc_attach and alloc_heap, initialization methods (init_???()) must be executed according to the memory allocation class.

initialization

void attach(T* p, int n) // alloc_attach
void init_local()        // alloc_local
void init_heap(int n)    // alloc_heap

Initialize with buffer p and size n.

Methods

alloc_size()

uint16_t alloc_size()

Returns the size of the buffer.

_is_attach(), _is_local(), _is_heap()

This method is used to generate a compile error, like static_assert, for a method call description that is different from the expected alloc class.

Wire (using member functions))

Wire (using member function)

The method using member functions has a relatively low level of abstraction and follows the general API system as provided by the C library. The procedures for operating the two-wire serial bus are more intuitive.

However, it is necessary to be explicitly aware of the start and end of bus usage.

Read

requestFrom()

size_type requestFrom(
    uint8_t u8address,
    size_type length,
    bool b_send_stop = true)

Reads the specified number of bytes at once. Since the result of reading is stored in a queue, call the .read() method immediately afterward until the queue is empty.

Parameters
Description

u8address

I2C address to be read

length

Number of bytes read

b_send_stop=true

When true, the STOP bit is set at the end of reading.

return type size_type

Number of bytes read. 0 means read failure.

Code Example

int len = Wire.requestFrom(0x70, 6);
for (int i = 0; i < 6; i++) {
  if (Wire.available()) {
		  au8data[i] = Wire.read();
    Serial.print(buff[i], HEX);
  }
}
// skip the rest (just in case)
// while (Wire.available()) Wire.read(); // normally, not necessary.

Writing

Writing is performed by the write() method after executing beginTransmission(). Call endTranmission() after a series of writing is finished.

	#define DEV_ADDR (0x70)
	const uint8_t msg[2] = 
	  {SHTC3_SOFT_RST_H, SHTC3_SOFT_RST_L};

	Wire.beginTransmission(DEV_ADDR);
	Wire.write(msg, sizeof(msg));
	Wire.endTransmission();

beginTransmission()

void beginTransmission(uint8_t address)

Initialize the export transfer. Call endTransmission() as soon as the writing process is finished.

Parameters
Description

u8address

I2C address to be written out

write(value)

size_type write(const value_type value)

Writes one byte.

Parameters
Description

Return value value

Bytes to be written.

Return value size_type

Number of bytes written. A value of 0 is an error.

write(*value, quantity)

size_type write(
  const value_type* value,
  size_type quantity)

Writes a byte sequence.

Parameters
Description

*value

the byte sequence to be written

Return value size_type

Number of bytes to be written.

Return value size_type

Number of bytes written. 0 is an error.

endTransmission()

uint8_t endTransmission(bool sendStop = true)

Processes the end of the export.

Parameters
Description

sendStop = true

Issue the STOP bit.

Return value uint8_t

0: Success 4: Failure

axis_xyzt

This structure is used to store the values of 3-axis accelerometers, but procedures have been added to increase convenience when stored in a container class.

struct axis_xyzt {
    int16_t x;
    int16_t y;
    int16_t z;
    uint16_t t;
};

get_axis_{x,y,z}_iter()

/*The return type is described as auto&& because it is a long template type name.*/
auto&& get_axis_x_iter(Iter p)
auto&& get_axis_y_iter(Iter p)
auto&& get_axis_z_iter(Iter p)

Generates an iterator that accesses an element on the X, Y, or Z axis using the iterator of the container class containing axis_xyzt as a parameter.

In the example below, buf.begin() and buf.end() are used as iterators for the X axis in the algorithm std::minmax_element.

#include <algorithm>

void myfunc() {  
  // container class
  smplbuf_local<axis_xyzt, 10> buf;
  
  // Submit data for testing
  buf[0] = { 1, 2, 3, 4 };
  buf[1] = { 2, 3, 4, 5 };
  ...
  
  // Algorithm to obtain maximum and minimum values
  auto&& minmax = std::minmax_element(
    get_axis_x_iter(buf.begin()),
    get_axis_x_iter(buf.end()));
  
  Serial << "min=" << int(*minmax.first)
        << ",max=" << int(*minmax.second) << mwx::crlf;
}

get_axis_{x,y,z}()

/*The return type is described as auto&& because it is a long template type name.*/
auto&& get_axis_x(T& c)
auto&& get_axis_y(T& c)
auto&& get_axis_z(T& c)

This function generates a virtual container class from which one of the XYZ axes of the container class containing axis_xyzt is taken. Only the begin() and end() methods are implemented in this generated class. The iterator that can be obtained by these begin() and end() methods is the same as the iterator in the previous section get_axis_{x,y,z}_iter().

#include <algorithm>

void myfunc() {
  // container class
  smplbuf_local<axis_xyzt, 10> buf;
  
  // Submit data for testing
  buf[0] = { 1, 2, 3, 4 };
  buf[1] = { 2, 3, 4, 5 };
  ...
  
  // Extract the X axis in the queue
  auto&& vx = get_axis_x(que);
  
  // Use of ranged for statement
  for (auto&& e : vx) { Serial << int(e) << ','; }
  
  // Algorithm to obtain maximum and minimum values
  auto&& minmax = std::minmax_element(
      vx.begin(), vx.end());
                          
  Serial << "min=" << int(*minmax.first)
        << ",max=" << int(*minmax.second) << mwx::crlf;
}

MWX_APIRET

API return value class that wraps 32-bit type. MSB (bit31) is a flag for failure or success. bit0..30 is used to store the return value.

class MWX_APIRET {
	uint32_t _code;
public:
	MWX_APIRET() : _code(0) {}
	MWX_APIRET(bool b) {
	  _code = b ? 0x80000000 : 0;
  }
	MWX_APIRET(bool b, uint32_t val) {
		_code = (b ? 0x80000000 : 0) + (val & 0x7fffffff);
	}
	inline bool is_success() const { return ((_code & 0x80000000) != 0); }
	inline bool is_fail() const { return ((_code & 0x80000000) == 0); }
	inline uint32_t get_value() const { return _code & 0x7fffffff; }
	inline operator uint32_t() const { return get_value(); }
	inline operator bool() const { return is_success(); }
};

constructor

MWX_APIRET()
MWX_APIRET(bool b)
MWX_APIRET(bool b, uint32_t val)

The default constructor is constructed with a combination of false and 0.

It can also be explicitly constructed with bool and uint32_t types as parameters.

Since the constructor of type bool is implemented, you can use true/false as follows.

MWX_APIRET myfunc() {
  if (...) return true;
  else false;
}

Methods

is_success(), operator bool()

inline bool is_success() 
inline operator bool()

Return true if 1 is set in MSB.

inline bool is_fail()

Return true if MSB is 0.

inline uint32_t get_value()
inline operator uint32_t()

Obtain the value part of bit0..30.

TwePacket

It is a base class of packet type, but the member structure common contains address information and other common information.

class TwePacket {
	public:
		static const E_PKT _pkt_id = E_PKT::PKT_ERROR;
		
		struct {
			uint32_t tick; // system time at interpretation execution [ms]
			uint32_t src_addr; // source address (serial number)
			uint8_t src_lid; // source address (logical address)
			uint8_t lqi; // LQI
			uint16_t volt; // voltage[mV]
		} common;
};

This is used when you want to get minimum information such as address information when you want to store mixed types as pktparser type in arrays, etc.

idenify_packet_type()

If the packet cannot be interpreted as a specific packet, E_PKT::PKT_ERROR is returned.

pktparser

parse<T>

Parses a sequence of bytes.

The T specifies the packet type to be parsed. For example, TwePacketTwelite is specified for 0x81 messages in standard applications.

p and e specify the next to the beginning and the end of the byte sequence.

The return value is of type E_PKT. In case of an error, E_PKT::PKT_ERROR is returned.

user<T>

Returns a reference to an object corresponding to the packet type of the interpreted byte sequence. It can be called if parse<T> was executed beforehand and there were no errors.

The T can be the same type as the one executed with parse<T>, or a TwePacket from which only basic information can be obtained.

Determines the type of packet using the packet data byte sequence as input. The return value is .

pktparser(parser_packet) performs content interpretation on the byte sequence converted by performs content interpretation on the byte sequence converted by .

The above example interprets . parser_ser object converts the message input from Serial into a byte string. This byte string is first identified by to determine the type of the message . Once the message type is determined, the message is parsed by .parse<TwePacketTwelite>(). The parsed result will be of type TwePacketTwelite, and .use<TwePacketTwelite>() is the procedure to extract this object. The TwePacketTwelite type is a class, but it refers to its member variables directly as a structure.

E_PKT identify_packet_type(uint8_t* p, uint8_t u8len)
serparser_heap parser_ser;

void setup() {
    // init ser parser (heap alloc)
    parser_ser.begin(PARSER::ASCII, 256);
}

void loop() {
    int c;
    while ((c = Serial.read()) >= 0) {
        parser_ser.parse(c);
        if (parser_ser.available()) {
            // get buffer object
            auto&& payl = parser_ser.get_buf();
            // identify packet type
            auto&& typ = identify_packet_type(payl.begin(), payl.end());
            
            // if packet type is TWELITE standard 0x81 message
            if (typ == E_PKT::PKT_TWELITE) {
                pktparser pkt; // packet parser object
                // analyze packet data
                typ = pkt.parse<TwePacketTwelite>(payl.begin(), payl.end());
                
                if (typ != E_PKT::PKT_ERROR) { // success!
                    // get data object
                    auto&& atw = pkt.use<TwePacketTwelite>();
                    
                    // display packet inforamtion
                    Serial << crlf << format("TWELITE: SRC=%08X LQI=%03d "
                        , app.u32addr_src, app.u8lqi);
	                  Serial << " DI1..4="
	                      << atw.DI1 ? 'L' : 'H' << atw.DI2 ? 'L' : 'H' 
                        << atw.DI3 ? 'L' : 'H' << atw.DI4 ? 'L' : 'H';
                }
            }
        }
    }
}
template <class T>
E_PKT parse(const uint8_t* p, const uint8_t* e)
template <class T> 
T& use()
E_PKT
serparser
serparser
standard application 0x81 message
identify_packet_type()
E_PKT

serparser

serial format input-output (mwx::serial_parser)

Used for serial format input/output. It has an internal buffer that holds the interpreted binary series. On input, the series is stored in the internal buffer one byte at a time according to the read format, and becomes complete when the interpretation of the series is complete. Conversely, on output, the buffer is output from the internal buffer according to the specified output format.

Three class names are defined according to the memory buffer handling method (alloc).

// serparser_attach : Use existing buffers
serparser_attach

// serparser : Allocate N bytes of buffer internally
serparser_local<N>

// serparser_heap : Allocate buffers in the heap area
serparser_heap

constants (format type)

The type of format to pass in the initialization parameter of begin(). There are two types here: ASCII format and binary format.

constant
type

uint8_t PARSER::ASCII = 1

ASCII format

uint8_t PARSER::BINARY = 2

binary format

Binary format is generally more complicated to handle than ASCII format, including the necessary tools and confirmation methods. Normally, ASCII format should be used.

About format

ASCII format

The ASCII format is a way to represent a sequence of data in binary as a string.

For example, 00A01301FF123456 in ASCII format is expressed as follows. The beginning is :, B1 is the checksum, and the end is [CR:0x0d][LF:0x0a].

:00A01301FF123456B1[CR][LF]

You can omit the terminating checksum. Replace the CRLF series from the checksum with X. This is useful when you want to send data for experiments, etc., although it is less vulnerable to wrong data series due to garbled characters.

:00A01301FF123456X

Definition.

======
Bytes of original data
Bytes
Description

header

1

Data part

N

2N

Each byte of the original data is represented by two ASCII characters (A-F in upper case). For example, 0x1F is represented as 1 (0x31) F (0x46).

checksum

2

The sum of each byte of the data part is calculated in 8-bit width and taken as 2's complement. In other words, the sum of each byte of the data part plus the checksum byte is calculated to be 0 in 8-bit width. The checksum byte is represented by two ASCII characters. For example, in 00A01301FF123456, 0x00 + 0xA0 + ... + 0x56 = 0x4F, the two's complement of which is 0xB1. (i.e. 0x4F + 0xB1 = 0x00)

footer

2

[CR] (0x0D) [LF] (0x0A)

Binary Format

Normally, ASCII format should be used.

However, to check the transmission and reception in experiments, it is necessary to prepare a special terminal that supports binary communication, and checksum calculation is also required. It is more difficult to use than the ASCII format.

Binary format is a method of sending a sequence of data consisting of binary data with a header and a checksum.

For example, 00A01301FF123456 is expressed in binary format as follows.

0xA5 0x5A 0x80 0x08 0x00 0xA0 0x13 0x01 0xFF 0x12 0x34 0x56 0x3D

Definition.

======
Bytes of original data
Bytes in format
Description

header

2

Data Length

2

The data length is two bytes in big-endian format, with MSB (0x8000) set and the length of the data part specified. For example, if the length of the data part is 8 bytes, specify 0x80 0x08.

Data part

N

N

Specifies the original data.

Checksum

1

Calculates the XOR of each byte of the data part. For example, if the data part is 00A01301FF123456, 0x00 xor 0xA0 xor ... 0x56 = 0x3D.

footer

(1)

The checksum is effectively the end of the line. The output from the radio module will be appended with 0x04 (EOT).

Methods

declaration, begin()

// serparser_attach : use existing buffer
serparser_attach p1;

uint8_t buff[128];
p1.begin(ARSER::ASCII, buff, 0, 128);


// serparser : allocate N bytes buffer inside
serparser p2<128>;
p2.begin(PARSER::ASCII);


// serparser_heap : allocate buffer in heap area
serparser_heap p3;
p3.begin(PARSER::ASCII, 128);

The declaration specifies the memory allocation class. Since this specification is complicated, an alias definition is used as described above.

Class name (alias definition) Memory Allocation

Contents

serparser_attach

specify an already existing buffer with begin()

serparser_local<N>

allocate a buffer of N bytes internally

serparser_heap

allocate a heap of the size specified by the parameters of the begin() method

Calls the begin() method according to the memory allocation class.

serparser_attach

void begin(uint8_t ty, uint8_t *p, uint16_t siz, uint16_t max_siz)

The buffer specified by p is used in the [format] (ser_parser.md#nitsuite) specified by ty. The maximum length of the buffer is specified by max_siz and the effective data length of the buffer by siz.

This definition is used especially when you want to format output data columns (see >> operator).

serparser_local<N> - Allocate internal buffer

void begin(uint8_t ty)

Initialize with the format specified by ty.

serparser_heap - Allocated to heap

void begin(uint8_t ty, uint16_t siz)

Initialize the heap by allocating the size specified by siz to the heap in the format specified by ty.

Once allocated, heap space cannot be released.

get_buf()

BUFTYPE& get_buf()

Returns an internal buffer. The buffer will be of type smplbuf<uint8_t, alloc>.

parse()

inline bool parse(uint8_t b)

Processes input characters. Receives a single byte of input string of formatted input and interprets it according to the format. For example, in ASCII format, it receives a series like :00112233X as input. X, one byte at a time, and when the last X` is entered, the interpretation of the format is completed and reported as done.

The parameter to parse() is the input byte, and the return value is true if the interpretation is complete.

When parse() reports that reading is complete, the next parse() will return to the reading-in-progress status.

例

while (Serial.available()) {
    int c = Serial.read();
    
    if (SerialParser.parse(c)) {
        // Format interpretation complete, data sequence obtained in b (smplbuf<uint8_t>)
        auto&& b = SerialParser.get_buf();
        
        // The following is the processing of the resulting data sequence
        if (b[0] == 0xcc) {
          // ...
        }
    }
}

operator bool()

operator bool() 

If true, reading is completed by parse(); if false, interpretation is in progress.

Example (parse() example can be rewritten as follows)

while (Serial.available()) {
    int c = Serial.read();
    
    SerialParser.parse(c);
    
    if(SerialParser) {
        // Format interpretation complete, data sequence obtained in b (smplbuf<uint8_t>)
        auto&& b = SerialParser.get_buf();
        // ...
    }
}

<< operator

Outputs the internal buffer to the stream (Serial) in a formatted format.

Example

uint8_t u8buf[] = { 0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc };

ser_parser pout;
pout.begin(ARSER::ASCII, u8buf, 6, 6); // Specify 6 bytes of u8buf

Serial << pout;// Format output to Serial -> :112233AABBCC??[CR][LF] 

E_PKT

packet type definition

Corresponds to the following packet

Corresponds to the ASCII format output by the Parent Node of App_Wings.

TwePacketUART

The TwePacketAppUart class is the format in which the extended format of the App_UART is received by the parent and repeater application App_Wings.

Various information in the packet data is stored in DataAppUART after parse<TwePacketUART>() execution.

The simple format cannot be interpreted. parse<TwePacketUART>() returns E_PKT::PKT_ERROR. To check the contents, refer to the original byte sequence directly.

DataAppUART structure

The payload is the data part, but the method of data storage changes depending on the macro definition.

If MWX_PARSER_PKT_APPUART_FIXED_BUF is compiled with the value 0, payload refers directly to the byte sequence for packet analysis. If the value of the original byte sequence is changed, the data in payload will be destroyed.

If you define the value of MWX_PARSER_PKT_APPUART_FIXED_BUF to be greater than 0, the payload will allocate a buffer of that value (bytes). However, if the data of the serial message exceeds the buffer size, parse<TwePacketAppUART>() fails and returns E_PKT::PKT_ERROR.

Name
Description

PKT_ERROR

TwePacket does not contain meaningful data such as before packet interpretation or packet type cannot be identified.

PKT_TWELITE

of the standard application App_Twelite is interpreted.

PKT_PAL

Interpret serial format of

PKT_APPIO

of remote control application products/ TWE-APPS/App_IO/uart.html) interpreted by the remote control application .

PKT_APPUART

of the serial communication application products/ TWE-APPS/App_Uart/mode_format.html) is interpreted.

PKT_APPTAG

The UART message of the wireless tag application App_Tag is interpreted. The sensor specific part is not interpreted and the byte string is reported as payload.

PKT_ACT_STD

Output format used in sample, etc.

class TwePacketAppUART : public TwePacket, public DataAppUART
struct DataAppUART {
		/**
		 * source address (Serial ID)
		 */
		uint32_t u32addr_src;

		/**
		 * source address (Serial ID)
		 */
		uint32_t u32addr_dst;

		/**
		 * source address (logical ID)
		 */
		uint8_t u8addr_src;

		/**
		 * destination address (logical ID)
		 */
		uint8_t u8addr_dst;

		/**
		 * LQI value
		 */
		uint8_t u8lqi;

		/**
		 * Response ID
		 */
		uint8_t u8response_id;

		/**
		 * Payload length
		 */
		uint16_t u16paylen;

		/**
		 * payload
		 */
#if MWX_PARSER_PKT_APPUART_FIXED_BUF == 0
		mwx::smplbuf_u8_attach payload;
#else
		mwx::smplbuf_u8<MWX_PARSER_PKT_APPUART_FIXED_BUF> payload;
#endif
	};
0x81 command
TWELITE PAL
UART message
App_IO
App_IO
Extended format
App_UART
Act (Act)

smplbuf

This is a container class with an array structure inside. The maximum size of the buffer is determined at initialization, and it behaves as a variable-length array up to that maximum size.

template <typename T, int N> smplbuf_local
template <typename T> smplbuf_attach
template <typename T> smplbuf_heap

smplbuf is a container class that provides array operations on memory areas specified by element type T and memory allocation method alloc. Since the specification of alloc is complicated, alias definitions using using are used.

Here is an example of object declaration. Immediately after the declaration, a method call is made for initialization. The maximum size of each object is 128 bytes immediately after initialization, and the size is 0. Use while extending the size as needed.

// Array area is a fixed array of class member variables
smplbuf_local<uint8_t, 128> b1;

// Refer to buffer memory that already exist
uint8_t buf[128];
smplbuf_attach<uint8_t> b2;

// Allocate buffer memory at heap
smplbuf_heap<uint8_t> b3;

// Initialize (must do initlialize at setup() for global objects)
void setup() {
    b1.init_local();
    b2.attach(buf, 0, 128);
    b3.init_heap(128);
} 

// In some function
void some_func() {
    smplbuf_local<uint8_t, 128> bl;
    // bl.init_local(); // It can omit if smplbuf_local is declared locally.
    
    bl.push_back('a');
}

Alias classes are defined for uint8_t type.

template <int N>
smplbuf_u8
// smplbuf<uint8_t, alloc_local<uint8_t, N>>

smplbuf_u8_attach
// smplbuf<uint8_t, alloc_attach<uint8_t>>

smplbuf_u8_heap
// smplbuf<uint8_t, alloc_heap<uint8_t>>

Elements can be accessed like normal arrays, using the [] operator, etc., and iterators can also be used to access elements.

void begin() { // begin() runs only once at startup
  smplbuf_u8<32> b1; // initially b1 behaves empty buffer (32bytes is allocated).
  b1.reserve(5); // reserves 5bytes in b1.
  
  b1[0] = 1;
  b1[1] = 4;
  b1[2] = 9;
  b1[3] = 16;
  b1[4] = 25;
  
  for(uint8_t x : b1) { // can use ranged for.
    Serial << int(x) << ",";
  }
}

The class also has push_back() method, which enables some type of algorithm from standard C++ library.

Declaration, Initialize

smplbuf_local<T,N>()
smplbuf_local<T,N>::init_local()

smplbuf_attach<T>(T* buf, uint16_t size, uint16_t N)
smplbuf_attach<T>::attach(T* buf, uint16_t size, uint16_t N)

smplbuf_heap<T>()
smplbuf_heap<T>::init_heap(uint16_t N)

// Example
// Allocated internally as fixed length array.
smplbuf_local<uint8_t, 128> b1;
b1.init_local();

// attach to an existing array.
uint8_t buf[128];
smplbuf_attach<uint8_t> b2;
b2.attach(buf, 0, 128);

// allocate array at heap area.
smplbuf_heap<uint8_t> b3;
b3.init_heap(128); 

Declares a container of type T and size N. After the declaration, call the initialization method.

smplbuf_local allocates an area by a fixed array inside. It can also be initialized by the constructor.

In smplbuf_attach, specify the first pointer T* buf of the buffer to be used, the initial size size and maximum size N of the array. Initialization by the constructor is also possible.

The smplbuf_heap allocates memory in the HEAP area (a memory area that cannot be released but can be allocated at any time). Once allocated, this area cannot be released, so it is usually defined as a global area. Allocation is done by init_heap(). Memory allocation by the constructor is not allowed. Please call init_heap() to use this function.

When creating a global object, initialization by the constructor is not possible. Please call initialization functions init_local(),attach(),init_heap() at the beginning of execution (setup() is recommended).

List of initializers

void in_some_func() {
    smplbuf_local<uint8_t, 5> b1;
    b1.init_local();
    
    b1 = { 0, 1, 2, 3, 4 };
    
    smplbuf_local<uint8_t, 5> b2{0, 1, 2, 3, 4};
}

Initializer list { ... } can be used to initialize members. Except for use in the constructor in a local declaration of smplbuf_local, it is valid after calling the initialization method.

  • right side value of assignment operator (smplbuf_local, smplbuf_attach, smplbuf_heap)

  • constructor (local declaration of smplbuf_local, global declaration is not allowed)

Methods

append(), push_back(), pop_back()

inline bool append(T&& c)
inline bool append(const T& c)
inline void push_back(T&& c)
inline void push_back(const T& c)
inline void pop_back()

Add a member c at the end. The return value of append() is bool, which returns false if the buffer is full and cannot be added.

The pop_back() deletes the trailing entry. However, it does not clear the entry.

empty(), size(), capacity()

inline bool empty()
inline bool is_end()
inline uint16_t size()
inline uint16_t capacity()

empty() returns true when no elements are stored in the array. is_end() returns true when the array is full.

size() returns the number of array elements.

capacity() returns the maximum number of elements in the array.

reserve(), reserve_head(), redim()

inline bool reserve(uint16_t len)
inline void reserve_head(uint16_t len)
inline void redim(uint16_t len)

reserve() extends the size of the array. The area where the array is not stored is initialized by default.

reserve_hear() allocates an area of the specified size at the top of the array. This area cannot be referenced by the container object. For example, it can be used as a container to access a sub-array of a packet payload whose header part is skipped. To return the container to the allocated area so that all of it can be accessed again, give it the same negative value as when it was allocated.

redim() resizes the allocated area. Unlike reserve(), it does not initialize the unused area.

operator []

inline T& operator [] (int i) 
inline T operator [] (int i) const

element.

A negative value for i makes the element from the end of the buffer. For -1, the element is the last element, and for -2, it is one before the end.

Output to mwx::stream

Array objects of type uint8_t (smplbuf<uint8_t, *>) can be output as is to derived objects of mwx::stream.

<< operator

template <class L_STRM, class AL>
	mwx::stream<L_STRM>& operator << (
			mwx::stream<L_STRM>& lhs, mwx::_smplbuf<uint8_t, AL>& rhs) 
			
//例
smplbuf_u8<128> buf;
buf.push_back('a');
buf.push_back('b');
buf.push_back('c');

Serial << buf;
// 出力: abc

Outputs a sequence of bytes for mwx::stream derived objects such as Serial.

to_stream()

inline std::pair<T*, T*> to_stream()

//Example
smplbuf_u8<128> buf;
buf.push_back('a');
buf.push_back('b');
buf.push_back('c');

Serial << buf.to_stream();
// Output: 0123

Used for output to stream purposes. Used to implement the << operator.

Data generation with `mwx::stream

mwx::streamdefines functions and operators to output bytes to a stream, such as<<operator andprintfmt()method. You can use the stream output procedure with a smplbuf array of typeuint8_t` as the output destination.

There are two methods.

  • Using helper object generated by .get_stream_helper().

  • Use the smplbuf class that inherits mwx::stream.

TwePacketPAL

The TwePacketPal class interprets TWELITE PAL packet data. This class handles TWELITE PAL (sensor data and other upstream data) commonly.

class TwePacketPal : public TwePacket, public DataPal { ... };

PAL common data is defined in DataPal.

Generator functions are provided to retrieve data specific to each PAL sensor board.

DataPal structure

Although the packet data structure of PAL differs depending on the connected sensors, etc., DataPal holds the data structure of the common part.

struct DataPal {
	uint8_t u8lqi; // LQI value

	uint32_t u32addr_rpt; // address of the relay

	uint32_t u32addr_src; // source address
	uint8_t u8addr_src; // source logical address

	uint16_t u16seq; // sequence number

	E_PAL_PCB u8palpcb; // PAL board type
	uint8_t u8palpcb_rev; // Revision of PAL board
	uint8_t u8sensors; // Number of sensor data in the data (MSB=1 is an error)
	uint8_t u8snsdatalen; // sensor data length (in bytes)

	union {
		const uint8_t *au8snsdata; // reference to sensor data part
		uint8_t _pobj[MWX_PARSER_PKT_APPPAL_FIXED_BUF]; // each sensor object
	};
};

The PAL packet data structure consists of two blocks: the common part for all PALs and the individual data part. The individual data part does not interpret the packet, but stores it as is. To simplify handling, data exceeding 32 bytes is stored in dynamically allocated uptr_snsdata.

The individual data parts are stored in a structure whose base class is PalBase. This structure is generated by the generator function defined in TwePacketPal.

If the size fits in MWX_PARSER_PKT_APPPAL_FIXED_BUF when parse<TwePacketPAL>() is executed, a separate sensor object is generated.

If it does not fit, a reference to the byte sequence used for analysis is stored in au8snsdata. In this case, if the data in the byte string used for analysis is rewritten, sensor-specific objects cannot be generated.

PalBase

All data structures for each sensor in PAL inherit from PalBase. The sensor data storage status u32StoredMask is included.

	struct PalBase {
		uint32_t u32StoredMask; // Data acquisition flags used internally
	};

PalEvent

PAL events are information that is sent when certain conditions are met by processing sensor information, rather than directly from sensors. For example, when an acceleration sensor detects a certain level of acceleration from a stationary state.

	struct PalEvent {
		uint8_t b_stored; // true if stored
		uint8_t u8event_source; // spare
		uint8_t u8event_id; // event ID
		uint32_t u32event_param;// event parameter
	};

If event data exists, it can be determined by .is_PalEvent() of TwePacketPal being true, and .get_PalEvent() will yield a PalEvent data structure.

Generator functions

Generator functions are used to extract various types of data from sensor PAL.

void print_pal(pktparser& pkt) {
	auto&& pal = pkt.use<TwePacketPal>();
	if (pal.is_PalEvent()) {
		PalEvent obj = pal.get_PalEvent();
	} else
	switch(pal.u8palpcb) {
	case E_PAL_PCB::MAG:
	  {
		  // generate pal board specific data structure.
		  PalMag obj = pal.get_PalMag();
	  } break;
  case E_PAL_PCB::AMB:
	  {
		  // generate pal board specific data structure.
		  PalAmb obj = pal.get_PalAmb();
	  } break;
	  ...
	default: ;
	}
}

To use the generator function, first determine if pkt is an event (.is_PalEvent()). If it is an event, it has get_PalEvent(). Otherwise, it creates an object according to u8palpcb.

get_PalMag()

PalMag get_PalMag()

// MAG
struct PalMag : public PalBase {
    uint16_t u16Volt; // module voltage [mV]
    uint8_t u8MagStat; // state of magnetic switch [0:no magnet,1,2].
    uint8_t bRegularTransmit; // MSB flag of u8MagStat
};

If .u8palpcb==E_PAL_PCB::MAG, the data PalMag of the open/close sensor pal is taken.

get_PalAmb()

PalAmb get_PalAmb()

// AMB
struct PalAmb : public PalBase {
    uint16_t u16Volt; // module voltage [mV]
    int16_t i16Temp; // temperature (100x value)
    uint16_t u16Humd; // humidity (100x value)
    uint32_t u32Lumi; // illuminance (equivalent to Lux)
};

If .u8palpcb==E_PAL_PCB::AMB, the data PalAmb of the environmental sensor pal is taken.

get_PalMot()

PalMot get_PalMot()

// MOT
struct PalMot : public PalBase {
    uint16_t u16Volt; // module voltage [mV]
    uint8_t u8samples; // number of samples
    uint8_t u8sample_rate_code; // sample rate (0: 25Hz, 4:100Hz)
    int16_t i16X[16]; // X axis
    int16_t i16Y[16]; // Y axis
    int16_t i16Z[16]; // Z axis
};

If .u8palpcb==E_PAL_PCB::MOT, the data PalMot of the operating sensor pal is taken.

get_PalEvent()

PalEvent get_PalEvent()

// PAL event
struct PalEvent {
    uint8_t b_stored; // if true, event information is available
    uint8_t u8event_source; // event source
    uint8_t u8event_id; // event ID
    uint32_t u32event_param; // 24bit, event parameter
};

Extracts a PalEvent (PAL event) if .is_PalEvent() is true.

.get_stream_helper()

Helper object type names are resolved by auto&& because they are long. The interfaces defined in mwx::stream, such as << operator, can be used for this object.

The generated helper object bs starts reading/writing from the beginning of the main array b when it is created. If it is at the end of the array, data is added by append(). Each time a read/write operation is performed, the position is moved to the next one.

Helper functions can use the >> operator for reading.

Operators and methods by via a that references a smplbuf array of type uint8_t.

smplbuf_u8<32> b;
auto&& bs = b.get_stream_helper(); // helper object

// Data String Generation
uint8_t FOURCHARS[]={'A', 'B', 'C', 'D'};
bs << FOURCHARS;
bs << ';';
bs << uint32_t(0x30313233); // "0123"
bs << format(";%d", 99);

Serial << b << crlf; // Output to Serial via smplbuf_u8<32> class

//Result: ABCD;0123;99
//..Continuation of the above example
// ABCD;0123;99 <- stored in b

//Variable for storing read data
uint8_t FOURCHARS_READ[4];
uint32_t u32val_read;
uint8_t c_read[2];

// Read out by operator>>
bs.rewind();                //Rewind the position to the beginning.
bs >> FOURCHARS_READ;      //4 chars
bs >> mwx::null_stream(1); //1 char skipping
bs >> u32val_read;         //32bit data
bs >> mwx::null_stream(1); //1 char skipping
bs >> c_read;              //2 chars

// Result
Serial << crlf << "4chars=" << FOURCHARS_READ;
Serial << crlf << format("32bit val=0x%08x", u32val_read);
Serial << crlf << "2chars=" << c_read;

// 4chars=ABCD
// 32bit val=0x30313233
// 2chars=99
mwx::stream
stream_helper
helper object

packet_tx

This class is a wrapper class for the tsTxDataApp structure of the TWENET C library, and objects of derived classes based on this class are obtained from network behaviors or on_tx_comp().

if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
	pkt << tx_addr(0x00)
		<< tx_retry(0x1)
		<< tx_packet_delay(0,50,10);
		
	pack_bytes(pkt.get_payload()
		, make_pair("APP1", 4)
		, uint8_t(u8DI_BM)
	);
  
  pkt.transmit();
}

Generating objects

This is done by the network behavior .prepare_tx_packet().

if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
  ...
}

In the above example, the_twelite.network.use<NWK_SIMPLE>() retrieves an object of the network behavior. The object pkt is created by .prepare_tx_packet() of this object. It is a derived class of packet_tx, although the type name is inferred by auto&&.

This pkt object first returns true or false in the if(). A false return occurs when the queue for sending is full and no more requests can be added.

Transmission Settings

Various settings are configured in the radio packet to deliver the packet to the destination, including destination information. To configure the settings, an object containing the settings is given as the right-hand side value of the << operator.

pkt << tx_addr(0x00)
	  << tx_retry(0x1)
  	<< tx_packet_delay(0,50,10);

The following is a description of the objects used for configuration.

The availability and implications of each setting depend on the network behavior specification.

tx_addr

tx_addr(uint32_t addr)

Specify the destination address addr. See the Network Behavior specification for the destination address value.

  • <NWK_SIMPLE> An address set with an MSB (bit31=0x80000000) means that it is addressed to the serial number of the radio module. 0x00. 0xFE means that the address is addressed to the child module (0x01...0xEF). 0xFE means broadcast to the child (0x01...0xEF), and 0xFF` means broadcast to both the parent and the child.

tx_retry

tx_retry(uint8_t u8count, bool force_retry = false)

Specifies the number of retransmissions. The number of retransmissions is specified by u8count. force_retry is a setting to retransmit a specified number of times regardless of whether the transmission is successful or not.

  • <NWK_SIMPLE> ネットワークビヘイビア<NWK_SIMPLE>では、同じ内容のパケットをu8count+1回送信します。 force_retryの設定は無視されます。

tx_packet_delay

tx_packet_delay(uint16_t u16DelayMin,
                uint16_t u16DelayMax,
                uint16_t u16RetryDur)

Sets the delay between sending packets and the retransmission interval. Specify two values, u16DelayMin and u16DelayMax, in milliseconds [ms]. Transmission will be started at some point during this interval after a transmission request is made. Specify the retransmission interval as the value of u16RetryDur in milliseconds[ms]. The retransmission interval is constant.

The transmission process may not start at the specified timing due to internal processing. In addition, IEEE802.15.4 processing also causes time blurring before packets are created. These timing blurs are an effective means of avoiding packet collisions in many systems.

Strict timing of packet transmission should be considered an exception due to the nature of the IEEE802.15.4 standard.

  • <NWK_SIMPLE> This specification is valid.

If the same packet is retransmitted and arrives more than 1 second after the first transmission, it is not deduplicated because a new packet has arrived. The same packet may be received after 1 second due to a longer retransmission interval or packet arrival delay in relay. You can configure the handling of duplicate packets by initializing the <NWK_SIMPLE> behavior.

tx_process_immediate

tx_process_immediate()

This setting requests that packet transmission be performed "as quickly as possible." Packet transmission processing in TWENET is performed from the TickTimer, which runs every 1 ms. By setting this parameter, the packet transmission request is processed as soon as possible after the request is made. Of course, any setting other than tx_packet_delay(0,0,0) is a meaningless specification.

If other packet transmission processing is in progress, it will be processed as usual.

  • <NWK_SIMPLE> This designation is valid.

tx_ack_required

tx_ack_required()

In wireless packet communications, there is a transmission method in which a short radio message called ACK (ACK) is obtained from the destination after the transmission is completed to indicate that the transmission was successful. By setting this option, transmission with ACK is performed.

  • <NWK_SIMPLE>

This specification is VALID for <NWK_SIMPLE>. It will result in a compile error. <NWK_SIMPLE> is intended to implement a simple working relay network and does not communicate with ACK.

tx_addr_broadcast

tx_addr_broadcast()

Specify broadcast.

  • <NWK_SIMPLE>

This specification is VALID for <NWK_SIMPLE>. It will result in a compile error.

Specify the destination address tx_addr(0xFF) (broadcast) or tx_addr(0xFE) (broadcast to the child) instead.

tx_packet_type_id

tx_packet_type_id(uint8_t)

Specify the type ID of the TWENET radio packet that can be 0..7.

  • <NWK_SIMPLE>

This specification is VALID for <NWK_SIMPLE>. It will result in a compile error.

`` uses type ID internally. Users cannot use it.

packet_rx

This class is a wrapper class for TWENET's tsRxDataApp structure.

This class object is a wrapper class for behavior callback function or by on_rx_packets().

In packet_rx, in particular, the data payload of the packet can be handled by the smplbuf container, and utility functions such as expand_bytes() simplify the payload interpretation description.

At this time, we have implemented methods and other interfaces, mainly those required for the simple relay network <NWK_SIMPLE>.

Methods

get_payload()

smplbuf_u8_attach& get_payload()

Get the data payload of the packet.

If <NWK_SIMPLE> is used, there is header data for <NWK_SIMPLE> at the beginning. The container referred to in the return will be a sub-array excluding the header. If you want to refer to the header part, refer to the tsRxDataApp structure by get_psRxDataApp().

get_psRxDataApp()

const tsRxDataApp* get_psRxDataApp() 

Obtain the receiving structure of the TWENET C library.

get_length()

uint8_t get_length()

Returns the data length of the payload. The value is the same as .get_payload().size().

get_lqi()

uint8_t get_lqi()

Obtain the LQI value (Link Quality Indicator).

LQI is a value that indicates the quality of radio communication, expressed as a number from 0 to 255.

Incidentally, if you want to evaluate it in several levels, you can classify it as follows: less than 50 (bad - less than 80 dbm), 50 to 100 (somewhat bad), 100 to 150 (good), 150 or more (near the antenna), and so on. Please note that these are only guidelines.

get_addr_src_long(), get_addr_src_lid()

uint32_t get_addr_src_long()
uint8_t get_addr_src_lid()

Get the address of the sender.

get_addr_src_long() is the serial number of the sender and MSB(bit31) is always 1.

get_addr_src_lid() is the logical ID of the sender and takes values from 0x00-0xFE (the logical ID specified by <NWK_SIMPLE>).

get_addr_dst()

uint32_t get_addr_dst()

Gets the destination address.

The destination address is specified by the source, and the range of values varies depending on the type of destination.

Value
Explanation

MSB (bit31) is set.

0x00-0xFF

Logical ID (8bit) is specified as the destination.

0x00-0xFF

is_secure_pkt()

bool is_secure_pkt()

Returns true for encrypted packets and false for plaintext.

get_network_type()

uint8_t get_network_type() 

Returns network type of the packet identified by Network BEHAVIOR.

Value
Explanation

mwx::NETWORK::LAYERED

packets from <NWK_LAYERED>

mwx::NETWORK::SIMPLE

packets from <NWK_SIMPLE>

mwx::NETWORK::NONE

neworkless packets

others

error or unknow packet type

mwx::stream

input-output stream

Upper-level class that handles input/output streams.

  • Interfaces to several classes (Serial, Wire, SPI, smplbuf) by polymorphism using CRTP (Curiously Recurring Template Pattern) method.

    • In CRTP, lower classes are defined as template class Derived : public stream<Derived>; and methods of lower classes are referenced from upper classes.

  • This class defines common processing such as print method, << operator, etc., and calls write() method, etc. implemented in lower classes, which is similar to using virtual functions.

Interfaces (implemented in lower classes)

Lower classes implement the functions listed below.

available()

int available()

// example
while(Serial.available()) {
  int c = Serial.read();
  // ... any
}

Returns 1 if the input exists, 0 if it does not.

Parameters
Description

Return value int

0: no data 1: data present

The return value of this implementation is not the buffer length.

flush()

void flush()

// example
Serial.println("long long word .... ");
Serial.flush();

Flush output (wait for output to complete).

read()

int read()

// example
int c;
while (-1 != (c = read())) {
    // any
}

Inputs 1-byte data from the stream. If the data does not exist, return -1.

write()

size_t write(int c)

// example
Serial.write(0x30);

Outputs 1 byte to the stream.

Parameters
Description

n

The character you want to output.

Return value size_t

1 if output succeeds, 0 if it fails.

vOutput()

static void vOutput(char out, void* vp)

This is a static function that produces a single byte output. Since this is not a class method, member variables and other information are not available. Instead, a pointer to the class instance is passed to vp, which is passed as a parameter.

This static function is used internally and the function pointer is passed as a one-byte output function of fctprintf(). This is used to implement the print method, etc.

Parameters
Description

out

the character to output

vp

pointer to a class instance Usually, cast to the original class and call the write() method

Interface

putchar()

void mwx::stream::putchar(char c)

// example
Serial.putchar('A');
// result -> A

Output a single byte.

print(), println()

size_t print(T val, int base = DEC) // T: 整数型
size_t print(double val, int place = 2)
size_t print(const char*str)
size_t print(std::initializer_list<int>)

// example
Serial.print("the value is ");
Serial.print(123, DEC);
Serial.println(".");
// result -> the value is 123.

Serial.print(123.456, 1);
// result -> 123.5

Serial.print({ 0x12, 0x34, 0xab, 0xcd });
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Performs various types of formatted output.

Paramita
Explanation

val

Integer powerups

base

power form BIN binary / OCT 8 math / DEC 10 math / HEX 16 math

place

Number of decimals below the decimal point

back size_t

the number of booklets

printfmt()

size_t printfmt(const char* format, ...);

// example 
Serial.printfmt("the value is %d.", 123);
// result -> the value is 123.

Prints output in printf format.

TWESDK/TWENET/current/src/printf/README.md 参照

operator <<

// examples
Serial << "this value is" // const char*
       << int(123)
       << '.';
       << mwx::crlf;
// result -> this value is 123.

Serial << fromat("this value is %d.", 123) << twe::crlf;
// result -> this value is 123.

Serial << mwx::flush; // flush here

Serial << bigendian(0x1234abcd);
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Serial << int(0x30) // output 0x30=48, "48"
       << '/'
       << uint8_t(0x31); // output '1', not "48"
// result -> 48/1

smplbuf<char,16> buf = { 0x12, 0x34, 0xab, 0xcd };
Serail << but.to_stream();
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Seiral << make_pair(buf.begin(), buf.end());
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Serial << bytelist({ 0x12, 0x34, 0xab, 0xcd });
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.
Argument type
Explanation

char

1-byte output (not formatted as a number)

1-byte output (not formatted as a number)

double

numeric output (printf's "%.2f")

uint8_t

output 1 byte (same as char type)

uint16_t

output 2 bytes (big-endian order)

uint32_t

output 4 bytes (big-endian order)

const char* uint8_t* const char[S]

Output up to the terminating character. Output does not include the terminating character. >(S specifies the size of the fixed array)

uint8_t[S]

Output the array size S bytes as is. (S is the fixed array size specification)

format()

output in printf format

mwx::crlf

output of newline CRLF

mwx::flush

flush output

bigendian()

output numeric types in big-endian order. (right-hand side value)

std::pair<T*, T*>

A pair containing begin(), end() pointers of byte type. Can be created by make_pair. Tis assumed to be of typeuint8_t`. (right-hand side value)

output byte string using bytelist()

std::initializer_list.

smplbuf<uint8_t,AL>&

smplbuf<uint8_t, AL>::to_stream()

When outputting as a byte string, cast to uint8_t, uint16_t, uint32_t type. When outputting numerical values as strings, explicitly cast to int type.

Single-byte types are handled differently depending on the type name. Usually, use the size-conscious uint8_t[S] type.

set_timeout(), get_error_status(), clear_error_status()

uint8_t get_error_status()
void clear_error_status()
void set_timeout(uint8_t centisec)

// example
Serial.set_timeout(100); // 1000msのタイムアウトを設定
uint8_t c;
Serial >> c;

Manages input timeouts and errors using the >> operator.

The timeout period is specified by set_timeout(), and input is processed using the >> operator. If no input is received within the given timeout period, the error value can be read out with get_error_status(). The error status is cleared by clear_error_status().

Argument type
Description

centisec

Sets the timeout period in units of 1/10 second. If 0xff is specified, timeout is disabled.

Error Value

Value
Meaning

0

No Error

1

Error Status

operator >>

inline D& operator >> (uint8_t& v)
inline D& operator >> (char_t& v)
template <int S> inline D& operator >> (uint8_t(&v)[S])
inline D& operator >> (uint16_t& v)
inline D& operator >> (uint32_t& v)
inline D& operator >> (mwx::null_stream&& p)

//// Example
uint8_t c;

the_twelite.stop_watchdog(); // stop watchdog
Serial.set_timeout(0xFF); // no timeout

// read out 1 byte
Serial >> c;
Serial << crlf << "char #1: [" << c << ']';

// skipping
Serial >> null_stream(3); // Read away 3 bytes
Serial << crlf << "char #2-4: skipped";

// Read 4 bytes (limited to fixed-length arrays of type uint8_t)
uint8_t buff[4];
Serial >> buff;
Serial << crlf << "char #5-8: [" << buff << "]";

Input processing.

  • Cannot be executed within setup().

  • Because it waits for polling, depending on the timeout time setting (e.g. no timeout), the watchdog timer may be triggered and reset.

Normally, the following readout is performed during loop().

void loop() {
  uint8_t c;
  while(Serial.available()) {
    Serial >> c;
    // Or c = Serial.read();
    
    switch(c) { ... }  // Branch processing according to the value of c
  }
}

The following is a list of types that can be read and stored.

Argument type
Explanation

1-byte input (big-endian order)

2-byte input (big-endian order)

uint32_t

4-byte input (big-endian order)

uint8_t[S]

input for S bytes (S specifies fixed array size)

null_stream(int n)

read n bytes away

smplque

Container class with FIFO queue structure.

Container class with FIFO queue structure.

As a rule, element types are assumed to be structures that store numbers, numerical values, etc. It is not assumed to store objects that need to be destroyed by the destructor (since there is no process to delete the object when the element is deleted from the queue).

A class Intr can be registered to set interrupt disabling at declaration time. If this class is not specified, normal operation without interrupt disable control is performed.

Example of object declaration. Immediately after the declaration, a method call is made for initialization. The maximum size of any of these objects is 128 bytes immediately after initialization, and the initial size is 0 and nothing is stored. The maximum size cannot be changed.

Since it is a FIFO queue, it is operated using methods such as push(),pop(),front().

Access by iterator is also possible.

Declaration and initialization

Declares a container of type T and size N. After the declaration, call the initialization methods.

The smplque_local allocates the area by a fixed array inside. Initialization by the constructor is also possible.

In smplque_attach, specify the first pointer T* buf of the buffer to be used, the initial size size and the maximum size N of the array. Initialization by the constructor is also possible.

smplque_heapallocates memory in the HEAP area (an area of memory that cannot be released but can be allocated at any time). Once allocated, this area cannot be released, so it is usually defined in the global area. Allocation is done byinit_heap(). Memory allocation by the constructor is not allowed. Please call init_heap()` to use this function.

When creating a global object, initialization by the constructor is not possible (due to compiler limitation). Please call initialization functions init_local(),attach(),init_heap() at the beginning of execution (setup() is recommended).

Methods

push(), pop(), front(), back()

push()` adds an entry to the queue.

pop()` removes an entry from the queue.

front()` refers to the first entry (the first one added).

back()` refers to the last entry (the last one added).

pop_front()` refers to the first entry as a return value and deletes it from the queue.

empty(), size(), is_full()

empty() returns true if the array contains no elements. is_full() returns true when the array is full.

size() returns the number of elements stored in the queue.

capacity() returns the maximum number of elements stored in the queue.

clear()

Erase all elements of the queue.

operator []

element. 0 is the first element added.

Iterator

You can get an iterator by begin() and end(). The beginning of the iterator is the first registered element of the queue. By using the iterator, range for statements and algorithms can be used.

format (mwx::mwx_format)

Enter printf format in mwx::stream

Helper class for writing format format to the << operator of mwx::stream. In the library, it is alias defined as Using format=mwx::mwx_format;.

The maximum number of arguments that can be registered in the variable number argument list is 8. 64-bit parameters such as double and uint64_t type are limited in number. If the limit is exceeded, a compile error will result due to static↵_assert.

  • Store the argument list received in the constructor in a variable internal to the class using the expand function of the parameter pack.

  • Call fctprintf() at the point operator << is called to write data to the stream

Constructor

The constructor stores the format pointer and parameters. The subsequent call with the << operator interprets the format and processes the output.

The fmt must remain accessible until this object is destroyed.

mwx::bigendian

Output data of numeric type in big-endian order to twe::stream

Helper class for mwx::stream << operator to output numeric types as big-endian byte strings.

constructor

mwx::flush

Flush buffered output to twe::stream.

Flush the output buffer of mwx::stream. Instance to a helper class that calls the flush() method.

  • For serial ports, wait polling until output completes.

  • For mwx::simpbuf buffers, output 0x00 at the end (size is not changed)

Output the contents of an array class of type uint8_t. ALC is .

Outputs data of smplbuf&#x3C;T> T is of type uint8_t, AL is a .

smplque is a container class that provides FIFO queue operations for memory areas specified by element type T and . Since the specification of alloc is complicated, an alias definition using using is used.

One application is .

Parameters
Description
Parameters
Description
template <typename T, int N, class Intr> smplbuf_local
template <typename T, class Intr> smplbuf_attach
template <typename T, class Intr> smplbuf_heap
void some_func() {

// alloc locally
smplque_local<uint8_t, 128> q1;

// attach to existing buffer
uint8_t buf[128];
smplque_attach<uint8_t> q2;

// alloc from the HEAP
smplque_heap<uint8_t> q3;

void setup() {
  // Globally defined objects are initialized with setup()
  q1.init_local();
  q2.attach(buf, 128);
  q3.init_heap(128);
}

void some_func() {
  // Local definition of smplque_local can omit init_local()
  smplque_local<uint8_t, 128> q_local;
  ..
}

}
void begin() { // begin() runs only once on startup
	smplque_local<int, 32> q1;
	
	q1.push(1);
	q1.push(4);
	q1.push(9);
	q1.push(16);
	q1.push(25);
	
	while(!q1.empty()) {
		Serial << int(q1.front()) << ',';
		q1.pop();
	}
	// output -> 1,4,9,16,25,
}
void begin() { // begin() runs only once on startup
	smplque_local<int, 32> q1;
	q1.init_local();
	
	q1.push(1);
	q1.push(4);
	q1.push(9);
	q1.push(16);
	q1.push(25);
	
	// Use iterators
	for(int x : q1) {
		Serial << int(x) << ',';
	}
	
	// Application of STL Algorithm
	auto&& minmax = std::minmax_element(q1.begin(), q1.end());
	Serial <<  "min=" << int(*minmax.first)
		     << ",max=" << int(*minmax.second);
	// output -> 1,4,9,16,25,min=1,max=25[]
}
smplbuf_local<T,N>
smplbuf_local<T,N>::init_local()

smplbuf_attach<T>
smplbuf_attach<T>::attach(T* buf, uint16_t N)

smplbuf_heap<T>
smplbuf_heap<T>::init_heap(uint16_t N); 

// Example
// Fixed array inside
smplque_local<uint8_t, 128> q1;
q1.init_local();

// Use an already existing array
uint8_t buf[128];
smplque_attach<uint8_t> q2;
q2.attach(buf, 128);

// Allocate to heap
smplque_heap<uint8_t> q3;
q3.init_heap(128); 
inline void push(T&& c)
inline void push(T& c)
inline void pop()
inline T& front()
inline T& back()

inline T& pop_front()
inline bool empty()
inline bool is_full()
inline uint16_t size()
inline uint16_t capacity()
inline void clear()
inline T& operator[] (int i)
inline smplque::iterator begin()
inline smplque::iterator end()
Serial << format("formatted print: %.2f", (double)3123 / 100.) << mwx::crlf;

// formatted print: 31.23[改行]
format(const char *fmt, ...)

fmt

Format format. See TWESDK/TWENET/current/src/printf/README.md

...

Parameters according to the format format. * The maximum number of parameters is 4. 5 or more parameters will result in a compile error. * Since consistency with the format is not checked, it is not safe for inconsistent input.

Serial << mwx::bigendian(0x1234abcdUL);

// output binary -> 0x12 0x34 0xab 0xcd
template <typename T>
bigendian::bigendian(T v)

v

a value of type uint16_t or uint32_t

for (int i = 0; i < 127; ++i) {
    Serial << "hello world! (" << i << ")" << twe::endl << twe::flush;
}
memory allocation method
memory allocation method
memory allocation method alloc
Access by iterator focusing on a specific member of the structure axis_xyzt

TwePacketTwelite

The TwePacketTwelite class is a standard application 0x81 command of App_Twelite, which is an interpretation of the TwePacketTwelite class.

class TwePacketTwelite : public TwePacket, public DataTwelite { ... };

Various information in the packet data is stored in DataTwelite after parse<TwePacketTwelite>() is executed.

DataTwelite structure

struct DataTwelite {
		//Serial # of sender
		uint32_t u32addr_src;
		
		// Logical ID of the sender
		uint8_t u8addr_src;

		// Destination logical ID
		uint8_t u8addr_dst;

		// Timestamp at time of transmission
		uint16_t u16timestamp;

		// Flag for low latency transmission
		bool b_lowlatency_tx;

		// Number of repeat relays
		uint16_t u8rpt_cnt;

		// LQI
		uint16_t u8lqi;

		// DI status (true is active Lo,GND)
		bool DI1, DI2, DI3, DI4;
		// DI status bitmap (DI1,2,3,4 in order from LSB)
		uint8_t DI_mask;

		// true if DI active (has been active in the past)
		bool DI1_active, DI2_active, DI3_active, DI4_active;
		// Active bitmap of DI (DI1,2,3,4 in order from LSB)
		uint8_t DI_active_mask;

		// Supply voltage of the module [mV].
		uint16_t u16Volt;

		// AD value [mV]
		uint16_t u16Adc1, u16Adc2, u16Adc3, u16Adc4;
		// Bitmap that is set to 1 if AD is active (AD1,2,3,4 in order from LSB)
		uint8_t Adc_active_mask;
};

TwePacketIO

The TwePacketAppIO class interprets the standard app serial message (0x81) of App_IO The TwePacketAppIO class is the one that interprets the

class TwePacketAppIO : public TwePacket, public DataAppIO { ... };

Various information in the packet data is stored in DataTwelite after parse<TwePacketIO>() execution.

DataAppIO structure

struct DataAppIO {
		// Serial # of sender
		uint32_t u32addr_src;
		
		// Logical ID of the sender
		uint8_t u8addr_src;

		// Destination logical ID
		uint8_t u8addr_dst;

		// Timestamp at the time of transmission
		uint16_t u16timestamp;

		// Flag for low latency transmission
		bool b_lowlatency_tx;

		// Number of repeat relays
		uint16_t u8rpt_cnt;

		// LQI value
		uint16_t u8lqi;

		// DI status bitmap (DI1,2,3,4,...) in order from LSB
		uint8_t DI_mask;

		// Active (1 if used) bitmap of DI (DI1,2,3,4,...) in order from LSB
		uint8_t DI_active_mask;
		
		// Bitmap of whether DI is interrupt-derived or not (DI1,2,3,4,...) in order from LSB
		uint16_t DI_int_mask;
};

stream_helper

stream_helper is a helper object that grants the mwx::stream interface. It creates a helper object that references a data class and performs data input/output via the helper object.

The following creates a helper object bs from an array b of smplbufs and inputs data using the mwx::stream::operator <<() operator.

smplbuf_u8<32> b;
auto&& bs = b.get_stream_helper(); // helper object

// Data String Generation
uint8_t FOURCHARS[]={'A', 'B', 'C', 'D'};
bs << FOURCHARS;
bs << ';';
bs << uint32_t(0x30313233); // "0123"
bs << format(";%d", 99);

Serial << b << crlf; // Output to Serial via smplbuf_u8<32> class

//result: ABCD;0123;99

Overview

stream_helper behaves as if the data array were a stream.

Internally, it holds read/write positions in the data array. It behaves as follows.

  • After the last data is read or written, it moves to the next read/write position.

  • After the last data is read or data is appended to the end, the read/write position becomes the end.

  • If the read/write position is the end of the line, then

    • available() becomes false.

    • Read cannot be performed.

    • Writing is appended if it is within the writable range.

generating stream_helper

stream_helper is a data class (smplbuf, EEPROM) member functions.

auto&& obj_helper = obj.get_stream_helper()
// obj is an object of data class, obj_helper type is accepted by auto&& because it is long.

methods

rewind()

void rewind()

Moves the read/write position to the beginning.

seek()

int seek(int offset, int whence = MWX_SEEK_SET)

Set the read/write position.

whence
Set the location

MWX_SEEK_SET

set from the leading position. If offset is 0, it means the same as rewind().

MWX_SEEK_CUR

Move by offset with respect to the current position.

MWX_SEEK_END

end position. Setting offset to 0 sets the end position. If -1 is set, it moves to the last character.

tell()

int tell()

Returns the read/write position. Returns -1 for the end position.

available()

int available()

Returns 0 if the read/write position is the end. If it is not the end, it returns any other value.

smplbuf_strm_u8

Example

The uint8_t type smplbuf_strm_u8?? is also available in the interface, so several methods for streams can be used.

// smplbuf_strm_u8<N> : locally allocated
template <int N> using smplbuf_strm_u8 
  = _smplbuf_stream<uint8_t, mwx::alloc_local<uint8_t, N>>;

// smplbuf_strm_u8_attach : attach to an existing buffer
using smplbuf_strm_u8_attach 
  = mwx::_smplbuf_stream<uint8_t, mwx::alloc_attach<uint8_t>>;

// smplbuf_strm_u8_heap : allocate at HEAP
using smplbuf_strm_u8_heap 
  = mwx::_smplbuf_stream<uint8_t, mwx::alloc_heap<uint8_t>>;

// definition of operator <<
template <class L_STRM, class ALOC>
mwx::stream<L_STRM>& operator << (
        mwx::stream<L_STRM>& lhs, 
        mwx::_smplbuf_stream<uint8_t, ALOC>& rhs)
{
		lhs << rhs.to_stream();
		return lhs;
}
smplbuf_strm_u8<128> sb1;

sb1 << "hello";
sb1 << uint32_t(0x30313233);
sb1 << format("world%d",99);
sb1.printfmt("Z!");

Serial << sb1;
// hello0123world99Z!
stream(stream)

begin()

It is called only once before the first call to the loop() function; since TWENET is already initialized, there is no need to consider constraints such as setup().

The main usage is to

  • Displaying startup messages

  • Writing code for testing

  • Sleep transition immediately after startup

  • Processing that is inconvenient for setup() (radio packet processing, timer operation, etc.)

This callback function definition is optional.

Call back functions

Call back functions describing an application.

This is a callback function that describes the application. Callback means called by the system (library). The user defines several callback functions to describe the behavior of the system.

The following callback functions are mandatory definitions.

  • setup()

  • loop()

If no other functions are defined, an empty function that does nothing is linked instead.

Normal callback call sequence

init_coldboot()
  ↓ (TWENET internal processing: Initialization 1)
setup()
  ↓(TWENET internal processing: Initialization 2)
begin() --- First time only
  ↓
loop() <--+
  ↓       |Event processing, behavior processing
CPU DOZE -+

Please refer to the source code mwx_appcore.cpp if you want to see the exact behavior.

Order of callback invocation on return to sleep

the_twelite.sleep()
  ↓ sleeping...
 
 
init_warmboot()
  ↓ (TWENET internal processing: Initialization 3)
wakeup()
  ↓(TWENET internal processing: Initialization 4)
loop() <--+
  ↓       |Event processing, behavior processing
CPU DOZE -+

Please refer to the source code mwx_appcore.cpp if you want to see the exact behavior.

setup()

the setup function to initialize an application.

Called at the beginning of code execution to write initialization code.

TWENET initialization is also executed after the setup() function exits. Do not do anything other than initialization here, since most of the processing is done after TWENET exits.

Items to be noted are listed below.

  • Sleep the_twenet.sleep() cannot be executed. If you want to sleep immediately after initialization, write the first sleep process in the begin() function.

  • The delay() function is replaced by the processing described below. In this case, the parameter ms does not specify milliseconds. \frz

* Alternative processing for delay().

static inline void delay(uint32_t ms) {
		volatile uint32_t ct = ms * 4096;
		while (ct > 0) {
			--ct;
		}
}

wakeup()

the function called when waking up from sleep.

Called before loop() when waking up from sleep, and includes procedures for initialization after returning from sleep and for branching processing depending on the state of return.

If there is no processing in loop() but only reading of sensors, etc., sleep can be performed again in this function.

This callback function definition is optional.

SM_SIMPLE state machine

SM_SIMPLE is provided to wait for processing such as state transitions, waiting for timeouts, and completion of transmission in the sample code.

The following is a basic code excerpt from SM_SIMPLE.

#include <SM_SIMPLE>

enum class STATE : uint8_t {
	INIT = 0,
	SENSOR,
	TX,
	TX_WAIT_COMP,
	GO_SLEEP
};

SM_SIMPLE<STATE> step;

begin() {
  ...
  step.init(); //initialize
}

loop() {
  do {
    switch(step.state()) {
    case STATE::INIT:
      ...
      step.next(STATE::SENSOR);
    break;
    
    case STATE::SENSOR:
      ...
      step.next(STATE::TX);
    break;
    
    case STATE::TX:
      if (/*success on tx request*/) {
        step.set_timeout(100); // set timeout as 100ms
        step.clear_flag();
          
        step.next(STATE::TX_WAIT_COMP);
      }
    break;
    
    case STATE::TX_WAIT_COMP:
      if (step.is_timeout()) the_twelite.reset_system(); // is timeout?
      if (step.is_flag_ready()) sleepNow(); // is set the flag?
    break;
    
    ...
    }
  } while(step.b_more_loop());
}

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

void sleepNow() {
	step.on_sleep(false); // reset state machine.
  the_twelite.sleep(10000); // 10sec
}

Explanation

To use SM_SIMPLE, you need to define an enum class as a list of states. In the above, it is defined as STATE. The class object is generated as SM_SIMPLE<STATE> step; with this stage as a parameter. The generated class object should be initialized by .setup().

enum class STATE : uint8_t {
	INIT = 0,
	SENSOR,
	TX,
	TX_WAIT_COMP,
	GO_SLEEP
};

SM_SIMPLE<STATE> step;

void setup() {
  step.init();
}

The initial state of SM_SIMPLE has the value 0, corresponding to STATE::INIT in the above example. To get the current state, use .state() and use it as a judgment expression in the switch clause of the do while statement as in the above example.

loop() {
  do {
    switch(step.state()) {
    case STATE::INIT: // State with value 0
    ...

Call .next() for state transitions. If the state changes, b_more_loop() is set to true and the loop in the do while clause is executed again. In the example, calling .next(STATE::TX) from the STATE::SENSOR state will cause the loop to be executed again and the case STATE::TX: clause will also be executed. If the state is not changed, the do while loop is escaped and the loop() is terminated once. Wait until the next call to loop().

  do {
    switch(step.state()) {
    ...
    case STATE::SENSOR:
      ...
      step.next(STATE::TX); // (1)state transition
    break;
    
    case STATE::TX: // (3) Called in the second loop
      if (/*success on tx request*/) {
      ...
    }
  } while (b_more_loop()); // (2) loop continue check

If you want to wait for processing such as completion of transmission, call .clear_flag(), and then signal the completion of processing by .set_flag(uint32_t) in another callback function or the like. Parameters of type uint32_t specified here can be read from .get_flag_value().

If you want to process a timeout, you can record the time when you call .set_timeout(uint32_t) and check if the timeout time has elapsed with .is_timeout().

    case STATE::TX:
      if (/*success on tx request*/) {
        step.set_timeout(100); // set timeout
        step.clear_flag();
          
        step.next(STATE::TX_WAIT_COMP);
      }
    break;
    
    case STATE::TX_WAIT_COMP:
      if (step.is_timeout()) ...; // timeout
      if (step.is_flag_ready()) ...; // is set the flag?
    break;
...

// an event of tx completion
void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus); // set the flag
}

SM_SIMPLE will be used again when returning from sleep, but it should always be called .on_sleep(bool) before sleep. If you put false in the parameter, it will start from the 0 state after recovery, and if you put true, it will resume from the state just before sleep.

void sleepNow() {
	step.on_sleep(false); // reset state machine.
  the_twelite.sleep(10000); // 10sec
}

Source Code

The following is the source code for SM_SIMPLE.

// very simple class to control state used in loop().
template <typename STATE>
class SM_SIMPLE {
	uint32_t _u32_flag_value;  // optional data when flag is set.
	uint32_t _ms_start;		// system time when start waiting.
	uint32_t _ms_timeout;	// timeout duration

	STATE _step;			  // current state
	STATE _step_prev;		// previous state
	bool_t _b_flag; 		// flag control.
public:
	// init
	void setup() { memset(this, 0, sizeof(SM_SIMPLE)); }
	// call befoer sleeping (save state machine status)
	void on_sleep(bool b_save_state = false) {
		STATE save = _step;
		setup();
		if(b_save_state) _step = _step_prev = save;
	}

	// state control
	void next(STATE next) { _step = next; } // set next state
	STATE state() { return _step; } // state number
	bool b_more_loop() { // if state is changed during the loop, set true
		if (_step != _step_prev) { _step_prev = _step; return true; }
		else return false;
	}

	// timeout control
	void set_timeout(uint32_t timeout) {
		_ms_start = millis();
		_ms_timeout = timeout;
	}
	bool is_timeout() { return (millis() - _ms_start) >= _ms_timeout; }

	// flag control
	void clear_flag() { _b_flag = false; _u32_flag_value = 0; }
	void set_flag(uint32_t u32_flag_value = 0) {
		_b_flag = true;
		_u32_flag_value = u32_flag_value; }
	uint32_t get_flag_value() { return _u32_flag_value; }
	bool is_flag_ready() { return _b_flag; }
};
  • Contents may change depending on the version.

  • The main body will be stored in SM_SIMPLE.hpp in the mwx library resource folder.

loop()

the main loop function.

This is the main loop of the application. After the loop ends, the CPU transitions to DOZE mode and waits for the next interrupt with low current consumption.

In the Act description, most of the processing is described in this loop.

The back function definition is optional.

init_coldboot()

Usually not used.

Called at the re-initialization of code execution, with no peripheral API or initialization.

This callback function definition is optional.

init_warmboot()

DESC

Usually not used.

Called in re-initialization when the peripheral API is not initialized after returning from sleep.

This function can be used to detect interrupt factors.

This callback function definition is optional.

on_tx_comp()

on_tx_comp()

Called when transmission is complete.

This function is called from within the MWX library with data stored in ev as packet_ev_tx when the wireless packet transmission is finished. If this function is not defined in the application, a weak function that does nothing is linked.

ev.u8CbId is the ID at the time of transmission and ev.bStatus is a flag indicating success (1) or failure (0) of the transmission.

Setting b_handled to true in this function tells the MWX library that the incoming packet has been processed in the application. If set to processed, it suppresses unnecessary processing. (Do not call event callback functions for the_twelite.app, .board, or .settings)

BEHAVIOR

Class Definition (.hpp)

BEHAVIOR definition requires a class definition as shown below.

The above example defines a BEHAVIOR class with the name MY_APP_CLASS. MY_APP_CLASS must be described in several places.

Define the class name and the base (parent) class. MWX_APPDEFS_CRTP() is a macro.

Here, the necessary definitions are imported by #include.

Constructor definition.

methods

loop()

This is the main loop and has the same role as loop() in the global definition.

on_create()

on_create() is called at object creation time (use<>() method). The val is a parameter for future extension.

on_begin()

on_begin()is called aftersetup()ends.val` is a parameter for future extension.

on_sleep()

Called before sleep. val is a parameter for future extension.

warmboot()

Called in the initial stage when returning from sleep. The val is a parameter for future expansion.

At this point, the peripherals have not yet been initialized. The sleep wake-up factor can be checked.

wakeup()

Called when waking up from sleep. The val is a parameter for future extension.

Sleep can also be called here.

receive()

When a packet is received, it is called with the received packet information as rx.

transmit_complete()

The transmission information is called as evTx when packet transmission is completed. The evTx.u8CbId is the ID at the time of transmission and evTx.bStatus is the flag indicating success (1) or failure (0) of the transmission.

Define handlers (.cpp)

BEHAVIOR handlers (interrupt, event, and state definitions) are defined in a cpp file. The file cannot be split and all handler definitions must be in one file.

Even in the case of BEHAVIORs that do not define handlers, be sure to create the following cpp file.

The required definitions of the MWX library (#include "_mwx_cbs_cpphead.hpp") must be included at the beginning and end of the cpp file.

At the beginning of the file, include the .hpp file of the BEHAVIOR definition as shown above. Specify the class name of the behavior in __MWX_APP_CLASS_NAME. In the above, it is MY_APP_CLASS.

At the end of the file, include the necessary definitions (#include "_mwx_cbs_cpptail.cpp").

The handler definition is written as shown in the following example. Types of definitions are described later. The definition of the handler to be used is described by using the macro for definition. Do not write handlers that are not used.

The MWX_????? _INT() is the definition of an interrupt handler, and MWX_? _EVENT() is the definition of an event handler, and MWX_STATE() is the state definition of a state machine.

Interrupt and Event Handlers

Interrupt handlers are executed when a microcontroller interrupt occurs, interrupting the code currently being executed. For this reason, it is desirable to write as short a process as possible, and great care must also be taken with regard to manipulation of variables and the like.

The interrupt handler has a parameter uint8_t& handled, and setting this value to true will prevent subsequent event calls from being made.

If the interrupt handler exits with handled set to false, the event handler will be called in the application loop (normal code). The event handler has no handled parameter. Since the event handler is normal code, it can perform relatively large processing. However, the event handler also incurs overhead, so it may not be able to handle the processing that is called at each frequent interrupt. In addition, since events are processed by the system's internal FIFO queue, events may be lost if they cannot be processed within a certain period of time.

The following is an explanation of macros for defining handler functions.

DIO

DIO (digital IO) interrupt event. N specifies the target DIO number. The arg is a definition for future extension.

TICKTIMER

TickTimer interrupt and event. The arg is a definition for future extension.

The handled flag of the TickTimer must not be set to true, otherwise TWENET will not work.

TIMER

Timer interrupt event. TheN specifies the number of the target timer. The arg is a definition for future extension.

Other

Definition of other interrupts and events that are not defined standardly in the MWX library and require an understanding of the AHI Peripherals Manual.

Other interrupt events can be received by the following handler functions. These will not be available in the future when dedicated handlers are defined.

Peripheral (AHI) interrupt handler u32DeviceId corresponds to arg and u32ItemBitmap corresponds to arg2.

State Machine

A state machine (state machine) is a method of describing an application that receives messages and operates by transitioning its state in response to those messages.

The events to be received are as follows.

PEV_SetState()

The state is set to s.

Exiting the state handler causes a transition to the next state, followed by a state handler being called with the E_EVENTS_NEW_STATE event.

PEV_u32Elaspsed_ms()

Returns the elapsed time ≪ ms] since the state transition. It is used for purposes such as managing timeouts.

In the above example, a system reset is performed after 100 ms.

PEV_Process()

Called from outside the state handler. Execute the state handler with the event ev parameter u32evarg.

The transmission completion event is communicated to the state machine. In other words, call the state handler.

Do not call the state handler directly. It will cause problems such as E_EVENT_NEW_STATE not being executed.

PEV_KeepStateOnWakeup()

Set just before sleep. After returning from sleep, the previous state is maintained. That is, the state handler is called with E_EVENT_START_UP with sleep started.

PEV_is_coldboot()

Determine if the event is E_EVENT_START_UP on wake-up.

PEV_is_warmboot()

Judges whether the event is E_EVENT_START_UP when returning from sleep.

Example of screen
Serial port selection screen
Main Menu
Wrt Firmware (Application programming)
Act Build & Wrt menu
programming..
complete
Interactive settings mode
Terminal
Settings for VSCode
Open as VSCode
List of build tasks
Selecting a build task
Build progress
Connection with a generic I2C device
Wiring example (AI1-AI4 can be omitted)

BEHAVIOR can be defined by defining the class in the specified way, so that class object. The registered BEHAVIOR will be embedded in TWENET, allowing the user code to describe the application's behavior. It is possible to define callback functions for interrupts and events from TWENET, which is not possible in a loop description. Although it requires more definitions than a loop description, it is suitable for describing more complex applications.

See sample BEHAVIOR .

To generate an interrupt, use , .

In order to generate an interrupt, the is started with software interrupts enabled.

The sample describes the flow of the application's operation, including the start of sensor operation, acquisition of sensor values, wireless packet transmission to completion of transmission, and sleep transition. Please refer to it as an actual example.

Event Name
Description
void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) 
class MY_APP_CLASS: MWX_APPDEFS_CRTP(MY_APP_CLASS)
{
public:
    static const uint8_t TYPE_ID = 0x01;

    // load common definition for handlers
    #define __MWX_APP_CLASS_NAME MY_APP_CLASS
    #include "_mwx_cbs_hpphead.hpp"
    #undef __MWX_APP_CLASS_NAME

public:
    // constructor
    MY_APP_CLASS() {}

    void _setup() {}    
    void _begin() {}

public:
    // TWENET callback handler (mandate)
    void loop() {}
    void on_sleep(uint32_t & val) {}
    void warmboot(uint32_t & val) {}
    void wakeup(uint32_t & val) {}

    void on_create(uint32_t& val) { _setup();  }
    void on_begin(uint32_t& val) { _begin(); }
    void on_message(uint32_t& val) { }

public:
    void network_event(mwx::packet_ev_nwk& pEvNwk) {}
    void receive(mwx::packet_rx& rx) {}
    void transmit_complete(mwx::packet_ev_tx& evTx) {}
};
class MY_APP_CLASS: MWX_APPDEFS_CRTP(MY_APP_CLASS)
    #define __MWX_APP_CLASS_NAME MY_APP_CLASS
    #include "_mwx_cbs_hpphead.hpp"
    #undef __MWX_APP_CLASS_NAME
MY_APP_CLASS() {}
void receive(mwx::packet_rx& rx)
void transmit_complete(mwx::packet_ev_tx& evTx)
#include <TWELITE>
#include "myAppClass.hpp" // BEHAVIOR definition file

/*****************************************************************/
// MUST DEFINE CLASS NAME HERE
#define __MWX_APP_CLASS_NAME MY_APP_CLASS
#include "_mwx_cbs_cpphead.hpp" // Definition at the beginning
/*****************************************************************/
/*****************************************************************/
// common procedure (DO NOT REMOVE)
#include "_mwx_cbs_cpptail.cpp"
// MUST UNDEF CLASS NAME HERE
#undef __MWX_APP_CLASS_NAME
/*****************************************************************/
// TickTimer interrupt
MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled) {
	// blink LED
	digitalWrite(PAL_AMB::PIN_LED, 
	      ((millis() >> 9) & 1) ? PIN_STATE::HIGH : PIN_STATE::LOW);
}

// PAL_AMB::PIN_BIN(12) event
MWX_DIO_EVENT(PAL_AMB::PIN_BTN, uint32_t arg) {
	Serial << "Button Pressed" << mwx::crlf;
	
	static uint32_t u32tick_last;
	uint32_t tick = millis();

	if (tick - u32tick_last > 100) {
		PEV_Process(E_ORDER_KICK, 0UL);
	}

	u32tick_last = tick;
}

// Operation definition of state STATE_0
MWX_STATE(E_MWX::STATE_0, uint32_t ev, uint32_t evarg) {
	if (ev == E_EVENT_START_UP) {
		Serial << "[STATE_0:START_UP]" << mwx::crlf;	
	} else
	if (ev == E_ORDER_KICK) {
		PEV_SetState(E_MWX::STATE_1);
	}
}

// Operation definition for state STATE_1
MWX_STATE(E_MWX::STATE_1, uint32_t ev, uint32_t evarg) {
	if (ev == E_EVENT_NEW_STATE) {
		Serial << "[STATE_1]" << mwx::crlf;	
	} else
	if (ev == E_ORDER_KICK) {
		PEV_SetState(E_MWX::STATE_2);
	} else
	if (ev == E_EVENT_TICK_SECOND) {
		Serial << "<1>";
	}	
}
MWX_DIO_INT(N, uint32_t arg, uint8_t& handled)
MWX_DIO_EVENT(N, arg)
MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled)
MWX_TICKTIMER_EVENT(uint32_t arg)
MWX_TIMER_INT(N, uint32_t arg, uint8_t& handled)
MWX_TIMER_EVENT(N, uint32_t arg)
MWX_MISC_INT(uint32_t arg, uint32_t arg2, handled)
MWX_MISC_EVENT(auint32_t rg, uint32_t arg2)

E_EVENT_START_UP

It is called at system startup. Immediately after power-on, it is called with 0 parameters. Because it is in the initial stage of execution, PEV_Process() is called once from the begin() method to start the operation when transitioning to the normal processing state. It is still called after returning from sleep, but with parameters other than 0. Normal processing can be performed from this state.

E_EVENT_NEW_STATE

It is called in a new state immediately after a state transition. Describes the process that is first executed when a transition is made to a certain state.

E_EVENT_TICK_TIMER

Called by TickTimer every 1ms

E_EVENT_TICK_SECOND

It is called every second.

void PEV_SetState(uint32_t s)
uint32_t PEV_u32Elaspsed_ms()
MWX_STATE(MY_APP_CHILD::STATE_TX, uint32_t ev, uint32_t evarg) {
  ...
  
	if (PEV_u32Elaspsed_ms() > 100) {
		// does not finish TX!
		Serial << "[STATE_TX] FATAL, TX does not finish!" << mwx::crlf << mwx::flush;
		the_twelite.reset_system();
	}
}
void PEV_Process(uint32_t ev, uint32_t u32evarg) {
void transmit_complete(mwx::packet_ev_tx& txev) {
    Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
    PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}
void PEV_KeepStateOnWakeup()
bool PEV_is_coldboot(uint32_t ev, uint32_t u32evarg)
bool PEV_is_warmboot(uint32_t ev, uint32_t u32evarg)
the_twelite
PAL_AMB-behavior
pinMode()
attachDioInt()
Timer object
PAL_AMB-behavior

on_rx_packet()

on_rx_packet()

Receives incoming packets.

void on_rx_packet(mwx::packet_rx& pkt, bool_t &b_handled) 

When a wireless packet is received, this function is called from within the MWX library with the data stored in pkt as packet_rx. If this function is not defined in the application, a weak function that does nothing is linked.

Setting b_handled to true in this function tells the MWX library that the incoming packet has been processed in the application. If set to processed', it suppresses unnecessary processing. (Do not process the_twelite.receiver`)

When using BEHAVIOR, use the callback function in BEHAVIOR.

The the_twelite.receiver is not recommended.

The receiver was previously processed by the_twelite.receiver with the intention of describing it in loop(). However, on_rx_packet() was added because it is a delayed processing by a queue, which in principle causes overflow, and also because it tends to be complicated to describe.

millis()

Obtain the system time, [ms].

Obtain the system time, [ms].

uint32_t millis()

The system tick time is updated by TickTimer interrupt.

random()

Generates an random number.

Generates an random number.

uint32_t random(uint32_t maxval)
uint32_t random(uint32_t minval, uint32_t maxval)

The first line returns the value of 0.. (maxval-1) value is returned. Note that the value of maxval is not the maximum value.

The second line returns the value of minval..maxval-1.

mwx::crlf

twe::stream に改行コードを出力する

Instance of a helper class to output a newline code (CR LF) for the << operator of mwx::stream.

Serial << "hello world!" << mwx::crlf;

delayMicroseconds()

Wait for time by polling (specified in μsec).

It is not included in MWSDK2020_05. Supported packages will be MWSDK_2020_07_UNOFFICIAL or later.

Wait for time by polling (specified in μsec).

void delayMicroseconds(uint32_t microsec)

Wait for a given period of time in microsec.

The time is measured by the TickTimer count. When waiting for a long time, the CPU clock is reduced and polling is performed.

In the setup(), wakeup() function, TickTimer is not yet running, so it waits for a while in a while loop. In this case, the error with the specified value will be large. This loop counter is adjusted to 32Mhz. If the CPU clock is changed in these functions, the error will be proportional to the clock.

If you specify a short time, such as less than 10 for a parameter, the error may be large.

delay()

Waiting for time by polling.

Waiting for time by polling.

void delay(uint32_t ms)

The program waits for a given period of time in ms.

The time is measured by the TickTimer count. When waiting for a long period of time, the CPU clock is decreased and polling is performed.

Every 5ms after calling delay(), TWELITE microcontroller performs internal watchdog processing.

For example, if you execute while(1) delay(1);, the watchdog processing is not performed because 5ms does not elapse inside delay(), and the reset is executed after a certain time.

In the setup(), wakeup() function, the TickTimer is not yet running, so it waits for a time by a while loop. In this case, the error with the specified value will be large. This loop counter is adjusted to 32Mhz. If the CPU clock is changed in these functions, the error will be proportional to the clock.

If you specify a short time, such as 1 or 2 as a parameter, the error may be large.

DIO General purpose IO

API for DIO (General-purpose digital IO)

The following functions are used for general-purpose digital IO (DIO) operations.

  • pinMode()

  • digitalWrite()

  • digitalRead()

  • attachIntDio()

  • detachIntDio()

Constants

Pin name and number

Definition
Name

const uint8_t PIN_DIGITAL::DIO0 .. 19

DIO pins 0 to 19

const uint8_t PIN_DIGITAL::DO0 .. 1

DO pin 0,1

Mode of pin(DIO0..19)

The following enumeration values are handled with the type name E_PIN_MODE.

Definition
Pull-up
Name

PIN_MODE::INPUT

None

Input

PIN_MODE::OUTPUT

None

Output

PIN_MODE::INPUT_PULLUP

Yes

Input

PIN_MODE::OUTPUT_INIT_HIGH

None

Output(init HIGH)

PIN_MODE::OUTPUT_INIT_LOW

None

Output(init LOW)

PIN_MODE::WAKE_FALLING

None

Input, raised pin, falling

PIN_MODE::WAKE_RISING

None

Input, rising pin, rising

PIN_MODE::WAKE_FALLING_PULLUP

Yes

Input, raised pin, falling

PIN_MODE::WAKE_RISING_PULLUP

Yes

Input, rising pin, rising

PIN_MODE::DISABLE_OUTPUT

Yes

return to the input state

Mode of the pin (DO0,1)

The following enumeration values are handled with the type name E_PIN_MODE.

Definition
Name

PIN_MODE::OUTPUT

Contribute

PIN_MODE::OUTPUT_INIT_HIGH

Output (initial state HIGH)

PIN_MODE::OUTPUT_INIT_LOW

Output (initial state LOW)

PIN_MODE::DISABLE_OUTPUT

Stop setting output

pin_state

The following enumeration values are handled with the type name E_PIN_STATE.

Definition
Value
Name

PIN_STATE::HIGH

1

HIGH(=Vcc) level

PIN_STATE::LOW

0

LOW(=GND) level

Rising and falling edge of pin

The following enumeration values are handled with the type name E_PIN_INT_MODE.

Definition
Name

PIN_INT_MODE::FALLING

falling edge

PIN_INT_MODE::RISING

rising edge

pinMode()

Sets the DIO (general-purpose digital IO) pin.

Sets the DIO (general-purpose digital IO) pin.

void pinMode(uint8_t u8pin, E_PIN_MODE mode)

This function allows you to change the state of DIO0..19 and the pins DO0,1. The setting contents are described in the enumeration value of E_PIN_MODE, description of DIO and Description of DO.

DO0,1 are special pins, which in principle are used for other purposes, but can also be configured as outputs. However, these pins have hardware restrictions, so care must be taken when using them.

Both pins must be guaranteed to be at a HIGH level when power is applied. If the circuit is configured to take unstable voltages, the module will not start up.

PAL_AMB-behavior

PAL AMBIENT SENSE PAL is used to acquire sensor values.

  • [BEHAVIOR] (./) is used to describe the Parent Node Child Node.

  • The sensor is described directly using Wire instead of using the board behavior function to obtain values.

  • Child Nodes are described by a state machine.

See the explanation of BRD_APPTWELITE, the explanation of PAL_AMB, and the explanation of PAL_AMB-usenap before the explanation of this ACT. Also see the description of BEHAVIOR.

This sample shows how to write BEHAVIOR. BEHAVIORS are used to describe more complex applications.

ACT FEATURES.

  • Uses the environmental sensor PAL AMBIENT SENSE PAL to acquire sensor values.

  • Use the sleep function to operate with coin cell batteries.

How to use ACT

Preparation for TWELITE

Role
Example

Parent Node

Child Node

When using PAL as the Parent Node, coin cell batteries cannot be used. As a rule of thumb, prepare a power supply environment that can provide a stable current of 50 mA or more.

File Structure

  • PAL_AMB-behavior.hpp : Only setup() is defined. read DIP-SW and if D1..D3 is upper position, it works as Parent Node, otherwise it sets ID corresponding to DIP SW as Child Node.

  • Parent/myAppBhvParent.hpp : behavior class definition for Parent Node

  • Parent/myAppBhvParent.cpp : implementation

  • Parent/myAppBhvParent-handlers.cpp : implementation of handlers

  • Parent/myAppBhvParent.hpp : behavior class definition for Child Nodes

  • Parent/myAppBhvParent.cpp : implementation

  • Parent/myAppBhvParent-handlers.cpp : implementation of handlers

The Parent Node's BEHAVIOR name is <MY_APP_PARENT> and the Child Node is <MY_APP_CHILD>.

Build files can be added by Makefile description /... /install_n_build/makefile.md).

Initialization setup()

// now read DIP sw status can be read.
u8ID = (brd.get_DIPSW_BM() & 0x07);

// Register App Behavior (set differnt Application by DIP SW settings)
if (u8ID == 0) {
	// put settings to the twelite main object.
	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL)  // set channel (pysical channel)
		<< TWENET::rx_when_idle();   // open RX channel

	the_twelite.app.use<MY_APP_PARENT>();
} else {		
	// put settings to the twelite main object.
	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL); // set channel (pysical channel)

	the_twelite.app.use<MY_APP_CHILD>();
}

If the DIP SW reading is 0, register the behavior <MY_APP_PARENT> for the Parent Node, otherwise register the behavior <MY_APP_CHILD> for the Child Node.

If the Parent Node is MONOSTICK, the DIP SW for PAL reads 0 and behaves as the Parent Node. However, this behavior is not defined in the MONOSTICK specifications.

Parent Node BEHAVIOR

The Parent Node behaves as a non-sleeping receiver and outputs packet information to the serial port when it receives a packet from a Child Node.

MY_APP_PARENT::receive()

void MY_APP_PARENT::receive(mwx::packet_rx& rx) {
	uint8_t msg[4];
	uint32_t lumi;
	uint16_t u16temp, u16humid;

	// expand packet payload (shall match with sent packet data structure, see pack_bytes())
	auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end(), msg);
	
	// if PING packet, respond pong!
	if (!strncmp((const char*)msg, (const char*)FOURCHARS, 4)) {
		// get rest of data
		expand_bytes(np, rx.get_payload().end(), lumi, u16temp, u16humid);

		// print them
		Serial << format("Packet(%x:%d/lq=%d/sq=%d): ",
							rx.get_addr_src_long(), rx.get_addr_src_lid(),
							rx.get_lqi(), rx.get_psRxDataApp()->u8Seq)
			   << "temp=" << double(int16_t(u16temp)/100.0)
			   << "C humid=" << double(int16_t(u16humid)/100.0)
			   << "% lumi=" << int(lumi)
			   << mwx::crlf << mwx::flush;
    }
}

When a packet is received for the Parent Node, if the first four characters of the packet can be matched (FOURCHARS), the contents of the packet are displayed.

MY_APP_PARENT::MWX_TICKTIMER_INT()

MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled) {
  // blink LED
  digitalWrite(PAL_AMB::PIN_LED, 
    ((millis() >> 9) & 1) ? PIN_STATE::HIGH : PIN_STATE::LOW);
}

The Parent Node's interrupt handler blinks the LED.

MY_APP_PARENT::MWX_DIO_EVENT(PAL_AMB::PIN_BTN)

MWX_DIO_EVENT(PAL_AMB::PIN_BTN, uint32_t arg) {
	Serial << "Button Pressed" << mwx::crlf;
	
	static uint32_t u32tick_last;
	uint32_t tick = millis();

	if (tick - u32tick_last > 100) {
		PEV_Process(E_ORDER_KICK, 0UL);
	}

	u32tick_last = tick;
}

When the button (5) on the PAL is pressed, the E_ORDER_KICK event is issued to the state machine.

MY_APP_PARENT::MWX_STATE(E_MWX::STATE_0 .. 3)

The state machine is described as a reference for state transitions and is not meaningful for the operation of the application. It executes state transitions by the E_ORDER_KICK event sent from the button, timeouts, and so on.

BEHAVIOR of Child Node

The behavior flow of the Child Node is the same as that of the PAL_AMB-usenap. From the initial sleep, "wake up → start sensor operation → short sleep → wake up → acquire sensor value → wireless transmission → wait for wireless transmission completion → sleep" is repeated.

MY_APP_CHILD::on_begin()

void _begin() {
    // sleep immediately.
    Serial << "..go into first sleep (1000ms)" << mwx::flush;
    the_twelite.sleep(1000);
}

The _begin() function, called from on_begin(), executes the first sleep.

(*It is acceptable to describe this process directly in on_begin() without describing it in _begin() function.)

MY_APP_CHILD::wakeup()

void wakeup(uint32_t & val) {
    Serial << mwx::crlf << "..wakeup" << mwx::crlf;
    // init wire device.
    Wire.begin();
    
    // turn on LED
    digitalWrite(PAL_AMB::PIN_LED, PIN_STATE::LOW);

    // KICK it!
    PEV_Process(E_ORDER_KICK, 0); // pass the event to state machine
}

This is a description of the process of waking up from sleep.

The first time Wire.begin() is executed here, which is redundant for the second and later times when the device wakes from sleep. This process can be moved to on_begin().

MY_APP_CHILD::transmit_complete()

void transmit_complete(mwx::packet_ev_tx& txev) {
    Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
    PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}

Processes E_ORDER_KICK messages to the state machine upon completion of transmission.

MY_APP_CHILD::transmit_complete()

static const uint8_t STATE_IDLE = E_MWX::STATE_0;
static const uint8_t STATE_SENSOR = E_MWX::STATE_1;
static const uint8_t STATE_TX = E_MWX::STATE_2;
static const uint8_t STATE_SLEEP = E_MWX::STATE_3;

Defines the state name.

MY_APP_CHILD::shtc3_???()

MWX_APIRET MY_APP_CHILD::shtc3_start()
MWX_APIRET MY_APP_CHILD::shtc3_read()

This is an example of sensor acquisition implementation for SHTC3. For details on sending commands, etc., refer to the SHTC3 datasheet.

MY_APP_CHILD::ltr308als_???()

MWX_APIRET MY_APP_CHILD::ltr308als_read()
MWX_APIRET MY_APP_CHILD::ltr308als_start()
static MWX_APIRET WireWriteAngGet(uint8_t addr, uint8_t cmd)

This is an example of LTR308ALS sensor acquisition implementation. Please refer to the LTR308ALS datasheet for details on sending commands, etc.

WireWriteAndGet() sends 1 byte of cmd to the device of addr, then receives 1 byte and returns the value.

MY_APP_CHILD::STATE_IDLE (0)

MWX_STATE(MY_APP_CHILD::STATE_IDLE, uint32_t ev, uint32_t evarg) {
	if (PEV_is_coldboot(ev,evarg)) {
		Serial << "[STATE_IDLE:START_UP(" << int(evarg) << ")]" << mwx::crlf;
		// then perform the first sleep at on_begin().
	} else
	if (PEV_is_warmboot(ev,evarg)) {
		Serial << "[STATE_IDLE:START_UP(" << int(evarg) << ")]" << mwx::crlf;
		PEV_SetState(STATE_SENSOR);
	}
}

State 0 has a special meaning. It is the state immediately after startup or after returning from sleep.

Immediately after startup PEV_is_coldboot(ev,evarg) judgment becomes true and is called. Since it goes straight to sleep from on_begin(), it does not contain any code that transitions the state. **At this point, the major initialization has not yet been completed, so complex processing such as sending wireless packets cannot be performed. **In order to perform the first state transition for such processing, send an event from on_begin() and perform the state transition according to that event.

After returning from sleep, there is a first call to PEV_is_warmboot(ev,evarg) which will be true. A call to PEV_SetState() will transition to the STATE_SENSOR state.

MY_APP_CHILD::STATE_SENSOR

MWX_STATE(MY_APP_CHILD::STATE_SENSOR, uint32_t ev, uint32_t evarg) {
	if (ev == E_EVENT_NEW_STATE) {
		Serial << "[STATE_SENSOR:NEW] Start Sensor." << mwx::crlf;

		// start sensor capture
		shtc3_start();
		ltr308als_start();

		// take a nap waiting finish of capture.
		Serial << "..nap for 66ms" << mwx::crlf;
		Serial.flush();
		PEV_KeepStateOnWakeup(); // stay this state on waking up.
		the_twelite.sleep(66, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
	} else
	if (PEV_is_warmboot(ev,evarg)) {
		// on wakeup, code starts here.
		Serial << "[STATE_SENSOR:START_UP] Wakeup." << mwx::crlf;

		PEV_SetState(STATE_TX);
	}
}

When the transition is made from STATE_IDLE after returning from sleep, the state handler for STATE_SENSOR is called continuously. The event ev at this time is E_EVENT_NEW_STATE.

Here, the operation of two sensors, SHTC3 and LTR308ALS, is started. After a certain period of time, the sensors will be ready to acquire data. This time wait is done with the sleep setting of 66ms. Note that PEV_KeepStateOnWakeup() is called before sleep. After this call, the state after returning from sleep is not STATE_IDLE, but the state it was in when it went to sleep, i.e. STATE_SENSOR.

When returning from a short sleep, a call is first made with the PEV_is_warmboot(ev,evarg) decision set to true. At the time of this call, wireless packets can be sent, etc. Transition to STATE_TX.

MY_APP_CHILD::STATE_TX

MWX_STATE(MY_APP_CHILD::STATE_TX, uint32_t ev, uint32_t evarg)
	static int u8txid;

	if (ev == E_EVENT_NEW_STATE) {
		Serial << "[STATE_TX:NEW]" << mwx::crlf;
		u8txid = -1;

		auto&& r1 = shtc3_read();
		auto&& r2 = ltr308als_read();

		Serial << "..shtc3 t=" << int(i16Temp) << ", h=" << int(i16Humd) << mwx::crlf;
		Serial << "..ltr308als l=" << int(u32Lumi) << mwx::crlf;

		if (r1 && r2) {
			if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {

Here, at the time of the E_EVENT_NEW_STATE event, the sensor data reading and wireless packet transmission procedures are started. Please refer to other act sample examples for details of the transmission procedure.

void transmit_complete(mwx::packet_ev_tx& txev) {
    Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
    PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}

    // ↓ ↓ ↓ Send a message

} else if (ev == E_ORDER_KICK && evarg == uint32_t(u8txid)) {
		Serial << "[STATE_TX] SUCCESS TX(" << int(evarg) << ')' << mwx::crlf;
		PEV_SetState(STATE_SLEEP);
}

Unlike the ACT description in the loop, the process of waiting for the message by PEV_Process() from transmit_complete() is used as a confirmation of completion. Sleep is performed upon receipt of the message. Sleep processing is done by transitioning to STATE_SLEEP.

	if (PEV_u32Elaspsed_ms() > 100) {
		// does not finish TX!
		Serial << "[STATE_TX] FATAL, TX does not finish!" << mwx::crlf << mwx::flush;
		the_twelite.reset_system();
	}

Finally, timeout processing is performed. This is in case the completion message of the sent packet has not been returned. PEV_u32Elaspsed_ms() returns the elapsed time since the transition to that state in milliseconds [ms]. If the time has elapsed, the above will perform a system reset the_twelite.reset_system() (assuming this timeout is too much).

MY_APP_CHILD::STATE_SLEEP

MWX_STATE(MY_APP_CHILD::STATE_SLEEP, uint32_t ev, uint32_t evarg) {
	if (ev == E_EVENT_NEW_STATE) {
		Serial << "..sleep for 5000ms" << mwx::crlf;
		pinMode(PAL_AMB::PIN_BTN, PIN_MODE::WAKE_FALLING_PULLUP);
		digitalWrite(PAL_AMB::PIN_LED, PIN_STATE::HIGH);
		Serial.flush();

		the_twelite.sleep(5000); // regular sleep
	}
}

Perfom sleep. Describe it in E_EVENT_NEW_STATE immediately after the transition from the previous state. Since other events may be called just before sleep, be sure to execute the_twelite.sleep() in a decision expression that is executed only once.

+

MONOSTICK BLUE or RED
BLUE PAL or RED PAL
AMBIENT SENSE PAL

digitalReadBitmap()

Reads the values of all ports in the input settings at once.

Included in mwx library 0.1.4 or later

Reads the values of all ports in the input settings at once.

uint32_t digitalReadBitmap()

The values are stored in the order of DIO0 ... DIO19 from the LSB side. DIO19 are stored in this order.

The pins on the HIGH side are set to 1 and the pins on the LOW side are set to 0.

detachIntDio()

to unregister the interrupt handler.

Unregisters the interrupt handler.

void detachIntDio(uint8_t u8pin)

digitalWrite()

Change the setting of the digital output pins.

Change the setting of the digital output pins.

static inline void digitalWrite(uint8_t u8pin, E_PIN_STATE ulVal)

The first parameter specifies the pin number to be set, and the second parameter specifies either HIGH or LOW.

The input is of type E_PIN_STATE. The conversion operator from E_PIN_STATE to int type is not defined, so direct numeric input is not allowed.

digitalRead()

Reads the value of the port of the input configuration.

Reads the value of the port of the input configuration.

Get the input value of a pin that has been previously set as an input, either LOW or HIGH.

No conversion operator from type E_PIN_STATE to type int is defined, so direct assignment to a numeric type is not possible.

static inline E_PIN_STATE digitalRead(uint8_t u8pin)

attachIntDio()

to enable DIO interrupt.

Enables DIO interrupts.

void attachIntDio(uint8_t u8pin, E_PIN_INT_MODE mode)

For a preconfigured pin, the first parameter is the pin number for which you want to enable interrupts, the second is the interrupt direction (rising, falling.

Interrupt handlers and event handlers are written in Application BEHAVIOR.

Example

Set up an interrupt to be generated when the DIO5 pin changes from HIGH to LOW.

void setup() {
  the_twelite.app.use<myAppClass>();
  
  pinMode(PIN_DIGITAL::DIO5, PIN_MODE::INPUT_PULLUP);
  attachIntDio(PIN_DIGITAL::DIO5, PIN_INT_MODE::FALLING);
}

void loop() {
  ;
}

myAppClass.hpp

class myAppClass: public mwx::BrdPal, MWX_APPDEFS_CRTP(myAppClasslMot)
{

};

Basic definition of the application behavior myAppClass. Details are omitted.

myAppClass.cpp

/*****************************************************************/
// MUST DEFINE CLASS NAME HERE
#define __MWX_APP_CLASS_NAME myAppClass
#include "_mwx_cbs_cpphead.hpp"
/*****************************************************************/

MWX_DIO_INT(PIN_DIGITAL::DIO5, uint32_t arg, uint8_t& handled) {
  static uint8_t ct;
  digitalWrite(PIN_DIGITAL::DIO12, (++ct & 1) ? HIGH : LOW);
	handled = false; // if true, no further event.
}

MWX_DIO_EVENT(PIN_DIGITAL::DIO5, uint32_t arg) {
  Serial << '*';	
}

/*****************************************************************/
// common procedure (DO NOT REMOVE)
#include "_mwx_cbs_cpptail.cpp"
// MUST UNDEF CLASS NAME HERE
#undef __MWX_APP_CLASS_NAME
} // mwx
/*****************************************************************/

Description of the interrupt handler of the application behavior myAppClass, which inverts the output setting of DIO12 when an interrupt of DIO5 is generated and displays * on the serial port Serial for events occurring after the interrupt handler is finished.