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

Return to the regular view of this page

As of 2025-08-01

Functions

Global Functions

1 - System Functions

Functions for handling time and random numbers

1.1 - millis()

Get system time
Gets the system time in milliseconds.
uint32_t millis()

The system time is updated by TickTimer interrupts.

1.2 - delay()

Delay processing
Performs a delay using polling.
void delay(uint32_t ms)

Performs a delay for the period specified by ms.

Time measurement is done using the TickTimer count. When a long delay is specified, the CPU clock is reduced during the polling process.

1.3 - delayMicroseconds()

Delay processing (microseconds)
Performs a delay using polling (specified in microseconds).
void delayMicroseconds(uint32_t microsec)

Performs a delay for the duration specified by microsec.

Time measurement is done using the TickTimer count. When a long delay is specified, the CPU clock is reduced during polling.

1.4 - random()

Random number generation
Generates a random number.
uint32_t random(uint32_t maxval)
uint32_t random(uint32_t minval, uint32_t maxval)

The first function returns a value in the range 0..(maxval-1). Note that the value of maxval is not the maximum value itself.

The second function returns a value in the range minval..maxval-1.

2 - General Purpose Digital IO

Functions to handle DIO ports

The following functions are used to operate General Purpose Digital IO (DIO).

  • pinMode()
  • digitalWrite()
  • digitalRead()
  • attachIntDio()
  • detachIntDio()

Constants

Pin Names and Numbers

DefinitionName
const uint8_t PIN_DIGITAL::DIO0 .. 19DIO pins 0 to 19
const uint8_t PIN_DIGITAL::DO0 .. 1DO pins 0,1

Pin Modes (DIO0..19)

The following enumerated values are handled by the type E_PIN_MODE.

DefinitionPull-upName
PIN_MODE::INPUTNoInput
PIN_MODE::OUTPUTNoOutput
PIN_MODE::INPUT_PULLUPYesInput with Pull-up
PIN_MODE::OUTPUT_INIT_HIGHNoOutput (initial state HIGH)
PIN_MODE::OUTPUT_INIT_LOWNoOutput (initial state LOW)
PIN_MODE::WAKE_FALLINGNoInput, wake pin, falling edge
PIN_MODE::WAKE_RISINGNoInput, wake pin, rising edge
PIN_MODE::WAKE_FALLING_PULLUPYesInput, wake pin, falling edge with pull-up
PIN_MODE::WAKE_RISING_PULLUPYesInput, wake pin, rising edge with pull-up
PIN_MODE::DISABLE_OUTPUTYesReturn to input state

Pin Modes (DO0,1)

The following enumerated values are handled by the type E_PIN_MODE.

DefinitionName
PIN_MODE::OUTPUTOutput
PIN_MODE::OUTPUT_INIT_HIGHOutput (initial state HIGH)
PIN_MODE::OUTPUT_INIT_LOWOutput (initial state LOW)
PIN_MODE::DISABLE_OUTPUTStop output setting

Pin States

The following enumerated values are handled by the type E_PIN_STATE.

DefinitionValueName
PIN_STATE::HIGH1HIGH level (=Vcc level)
PIN_STATE::LOW0LOW level (=GND level)

Pin Rising and Falling Edges

The following enumerated values are handled by the type E_PIN_INT_MODE.

DefinitionName
PIN_INT_MODE::FALLINGFalling edge
PIN_INT_MODE::RISINGRising edge

2.1 - pinMode()

Initialization function
Configures the settings for DIO (General Purpose Digital IO) pins.
void pinMode(uint8_t u8pin, E_PIN_MODE mode)

This function allows configuration of the states of DIO0 to DIO19 and DO0,1 pins. For details on the configuration values, refer to the enumerated values of E_PIN_MODE in the DIO explanation and the DO explanation.

2.2 - digitalWrite()

Digital output function
Changes the setting of a digital output pin.
static inline void digitalWrite(uint8_t u8pin, E_PIN_STATE ulVal)

Beforehand, set the target pin as output using pinMode(). The first parameter specifies the pin number to be set. The second parameter specifies either HIGH or LOW.

2.3 - digitalRead()

Digital input function
Reads the value of a port configured as input.
static inline E_PIN_STATE digitalRead(uint8_t u8pin)

You can get the input value of a pin previously configured as input as LOW or HIGH.

2.4 - attachIntDio()

Function to register a DIO interrupt handler
Registers a DIO interrupt handler.
void attachIntDio(uint8_t u8pin, E_PIN_INT_MODE mode)

For a pin configured as input beforehand, the first parameter is the pin number for which you want to enable the interrupt, and the second parameter specifies the interrupt direction (rising edge, falling edge).

Example

Sets up an interrupt that triggers 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 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
/*****************************************************************/

Interrupt handler description for the behavior myAppClass. When an interrupt occurs on DIO5, it toggles the output setting of DIO12, and after the interrupt handler finishes, an event occurs that prints * to the serial port Serial.

2.5 - detachIntDio()

Function to unregister a DIO interrupt handler
Unregisters a DIO interrupt handler.
void detachIntDio(uint8_t u8pin)

2.6 - digitalReadBitmap()

Digital input function (batch)
Reads the values of the input-configured ports in batch.
uint32_t digitalReadBitmap()

Values are stored in order from the LSB side: DIO0 … DIO19.

Pins on the HIGH side are set to 1, and pins on the LOW side are set to 0.

3 - Utility Functions

Other utility functions

3.1 - Printf Implementation

Functionality similar to C standard printf
The MWX library provides an implementation similar to the commonly used printf() function in C.
int mwx_printf(const char* format, ...)
int mwx_snprintf(char* buffer, size_t count, const char* format, ...)

mwx_printf() outputs formatted text to the Serial object. It performs the same processing as Serial.printfmt().

mwx_snprintf() performs snprintf formatting to a buffer.

3.2 - pack_bits()

Set bits to 1 at specified positions
Sets bits to 1 at the specified positions.
constexpr uint32_t pack_bits(...)

Parameters are specified as variadic arguments, each indicating a bit position (integer from 0 to 31). For example, pack_bits(1,3,6) returns ((1UL<<1)|(1UL<<3)|(1UL<<6)).

Background

This function simplifies the notation in situations where values are referenced or set in various bitmaps such as IO port (DI, DO) states.

3.3 - collect_bits()

Create a bitmap from specified bit positions
Extracts values from specified bit positions in an integer and constructs a bitmap in the specified order.
constexpr uint32_t collect_bits(uint32_t bm, ...)

From the value specified in the parameter bm, this function extracts the values corresponding to the 0..31 bit positions specified by the subsequent variadic parameters. The extracted values are arranged in the order of the parameters and returned as a bitmap.

The bit ordering of the resulting bitmap places the first parameter in the highest bit and the last parameter at bit 0.

uint32_t b1 = 0x12; // (b00010010)
uint32_t b2 = collect_bits(b1, 4, 2, 1, 0);
  // bit4->1, bit2->0, bit1->1, bit0->0
  // b2=0x10 (b1010)

In this example, bits 4, 2, 1, and 0 of b1 are extracted, resulting in (1,0,1,0). This is interpreted as b1010, resulting in a calculated value of 0x10.

Background

This function simplifies code where values are referenced or set in various bitmaps, such as IO port (DI, DO) statuses.

3.4 - Byte array utils

Conversion between byte arrays and 16/32-bit integers
Generates 16/32-bit integers from byte arrays, or generates byte arrays from 16/32-bit integers.

Reading

Retrieve uint16_t or uint32_t values from a byte array interpreted as uint8_t in big-endian order.

	inline uint8_t G_BYTE(const uint8_t*& p) {
		return *(p)++;
	}
	inline uint16_t G_WORD(const uint8_t*& p) {
		uint32_t r = *p++;
		r = (r << 8) + *p++;
		return r;
	}
	inline uint32_t G_DWORD(const uint8_t*& p) {
		uint32_t r = *p++;
		r = (r << 8) + *p++;
		r = (r << 8) + *p++;
		r = (r << 8) + *p++;
		return r;
	}

p is incremented by the number of bytes read.

Writing

Writes uint8_t, uint16_t, or uint32_t values in big-endian order to the byte array pointed to by q.

	inline uint8_t& S_OCTET(uint8_t*& q, uint8_t c) {
		*q++ = c;
		return *q;
	}
	inline uint8_t& S_WORD(uint8_t*& q, uint16_t c) {
		*(q) = ((c) >> 8) & 0xff; (q)++;
		*(q) = ((c) & 0xff); (q)++;
		return *q;
	}
	inline uint8_t& S_DWORD(uint8_t*& q, uint32_t c) {
		*(q) = ((c) >> 24) & 0xff; (q)++;
		*(q) = ((c) >> 16) & 0xff; (q)++;
		*(q) = ((c) >>  8) & 0xff; (q)++;
		*(q) = ((c) & 0xff); (q)++;
		return *q;
	}

q is incremented by the number of bytes written.

Background

These utilities simplify operations during the construction and decomposition of data payloads in wireless packets.

You may also use the simplified pack_bytes() and expand_bytes() functions.

3.5 - pack_bytes()

Generate a byte sequence by concatenating element data
Generates a byte sequence by concatenating element data.
uint8_t* pack_bytes(uint8_t* b, uint8_t* e, ...)

pack_bytes takes container class begin(), end() iterators as parameters and writes the data specified by the following parameters into the container as a byte sequence.

The data types given as variadic arguments are as follows.

Data TypeBytesDescription
uint8_t1
uint16_t2Stored in big-endian order
uint32_t4Stored in big-endian order
uint8_t[N]NFixed-length array of uint8_t
std::pair<char*,N>NPair of array and length for char* or uint8_t* arrays. Can be created with make_pair().
smplbuf_u8& pack_bytes(smplbuf_u8& c, ...)

pack_bytes takes a container object as a parameter and writes the data specified by the following parameters into the container as a byte sequence. It appends to the end using the container’s .push_back() method.

The data types given as variadic arguments are as follows.

Data TypeBytesDescription
uint8_t1
uint16_t2Stored in big-endian order
uint32_t4Stored in big-endian order
uint8_t[N]NFixed-length array of uint8_t
std::pair<char*,N>NPair of array and length for char* or uint8_t* arrays. Can be created with make_pair().
smplbuf_u8?.size()smplbuf<> container of uint8_t type. Stores data of container length (.size()).

Example

auto&& rx = the_twelite.receiver.read();

smplbuf<uint8_t, 128> buf;
mwx::pack_bytes(buf
	, uint8_t(rx.get_addr_src_lid())	// src addr (LID)
	, uint8_t(0xCC)							      // cmd id (0xCC, fixed)
	, uint8_t(rx.get_psRxDataApp()->u8Seq)	// sequence number
	, uint32_t(rx.get_addr_src_long())		// src addr (long)
	, uint32_t(rx.get_addr_dst())			// dst addr
	, uint8_t(rx.get_lqi())					  // LQI
	, uint16_t(rx.get_length())				// payload length
	, rx.get_payload() 						    // payload
);

In this example, attributes and payload of the received packet are re-stored into another buffer buf.

Background

To simplify the description of byte arrays of type uint8_t used for generating data payloads of wireless packets and extracting data.

auto&& rx = the_twelite.receiver.read();

uint8_t data[128];
data[0] = rx.get_addr_src_lid();
data[1] = 0xCC;
data[2] = rx.get_psRxDataApp()->u8Seq;
data[4] = rx.get_addr_src_long() & 0x000000FF;
data[5] = (rx.get_addr_src_long() & 0x0000FF00) >> 8;
data[6] = (rx.get_addr_src_long() & 0x00FF0000) >> 16;
data[7] = (rx.get_addr_src_long() & 0xFF000000) >> 24;
...

The above is the simplest description, but a byte array can be generated using Byte array utils as follows.

auto&& rx = the_twelite.receiver.read();

uint8_t data[128], *q = data;
S_OCTET(q, rx.get_addr_src_lid());
S_OCTET(q, 0xCC);
S_OCTET(q, rx.get_psRxDataApp()->u8Seq);
S_DWORD(q, rx.get_addr_src_long());
S_DWORD(q, rx.get_addr_dst());
S_OCTET(q, rx.get_lqi());
S_WORD(q, rx.get_length());
for (auto x : rx.get_payload()) {
  S_OCTET(q, x);
}

3.6 - expand-bytes()

“Decompose a byte sequence and store it into variables”
Decompose a byte sequence and store it into variables.
const uint8_t* expand_bytes(
        const uint8_t* b, const uint8_t* e, ...)

expand_bytes() takes a combination of iterators of type uint8_t* as parameters. These specify the beginning of the target to be parsed and the iterator just past the end. If parsing reaches the position of e, it results in an error and returns nullptr.

If there is no error in expansion, it returns the iterator for the next reading.

The variable parameters should be specified as follows:

Number of bytesData lengthExplanation
uint8_t1
uint16_t2Expanded as big-endian order
uint32_t4Expanded as big-endian order
uint8_t[N]NFixed-length array of uint8_t
std::pair<char*,N>NA pair of array and array length N of type char* or uint8_t* created by make_pair()

Example

auto&& rx = the_twelite.receiver.read();

char fourchars[5]{};
auto&& np =
	expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
		, make_pair((uint8_t*)fourchars, 4)
    );

// 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]
);

In this example, first a 4-byte string is read. Here, make_pair() is used to explicitly read 4 bytes of data.

Using the returned iterator np as a base, the next data is read. The next data consists of a uint8_t type followed by five uint16_t types.

Background

To simplify the description of byte arrays of type uint8_t used for generating and extracting data payloads of wireless packets.

auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];

uint8_t *p = rx.get_payload().begin();
fourchars[0] = p[0];
fourchars[1] = p[1];
fourchars[2] = p[2];
fourchars[3] = p[3];
fourchars[4] = 0;
p += 4;

u8DI_BM_remote = (p[0] << 8) + p[1]; p+=2;
au16AI_remote[0] = (p[0] << 8) + p[1]; p+=2;
...

The above is the simplest description, but you can read from byte arrays using Byte array utils as follows:

auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];

uint8_t *p = rx.get_payload().begin();
fourchars[0] = G_BYTE(p);
fourchars[1] = G_BYTE(p);
fourchars[2] = G_BYTE(p);
fourchars[3] = G_BYTE(p);
fourchars[4] = 0;

u8DI_BM_remote = G_WORD(p);
au16AI_remote[0] = G_WORD(p);
...

3.7 - CRC8, XOR, LRC

Checksum calculation
Calculates values commonly used in checksum computations.
uint8_t CRC8_u8Calc(uint8_t *pu8Data, uint8_t size, uint8_t init=0)
uint8_t CRC8_u8CalcU32(uint32_t u32c, uint8_t init=0)
uint8_t CRC8_u8CalcU16(uint16_t u16c, uint8_t init=0)
uint8_t XOR_u8Calc(uint8_t *pu8Data, uint8_t size)
uint8_t LRC_u8Calc(uint8_t* pu8Data, uint8_t size)

Performs calculations for CRC8, XOR, and LRC (used in ASCII format).

CRC8_u8CalcU16() and CRC8_u8CalcU32() compute the CRC8 for u16c and u32c assuming big-endian order.

Background

These functions were added as library procedures because they are used for validating wireless packet data sequences, ASCII format checksums (LRC), and various sensor data checks.

3.8 - div10(), div100(), div1000()

Calculate quotient and remainder when divided by 10, 100, or 1000
Calculates the quotient and remainder when divided by 10, 100, or 1000.
struct div_result_i32 {
		int32_t quo; // quotient
		int16_t rem; // remainder
		uint8_t b_neg;  // true if negative
		uint8_t digits_rem; // digits of remainder
};

div_result_i32 div10(int32_t val);
div_result_i32 div100(int32_t val);
div_result_i32 div1000(int32_t val);

In some cases, sensor values multiplied by 100 are passed as uint16_t type, but on microcontrollers without division circuits, calculation processing takes considerable time. Therefore, calculations are performed using approximate calculations and corrections with addition, subtraction, multiplication, and bit shifts.

Pass the value to be calculated in val, the variable to store the remainder in rem, and the variable to store the sign in neg.

The return value is the quotient (always positive), rem contains the remainder (always positive), and neg stores true if negative.

Due to algorithm constraints (digit overflow), the calculable value range is limited for div100() and div1000(). div100() supports values from -99999 to 99999, and div1000() supports values from -999999 to 999999.

Example usage

auto d1 = div100(sns_val.u16temp_object);
auto d2 = div100(sns_val.u16temp_object);

Serial
	<< crlf << format("..Object  = %c%2d.%02d"
									, d1.b_neg ? '-' : '+', d1.quo, d1.rem)
	        << format("  Ambient = %c%2d.%02d"
									, d2.b_neg ? '-' : '+', d2.quo, d2.rem);

Calculation speed

About one-tenth of the time.

Output of results

// Conversion options
struct DIVFMT {
  static const int STD = 0; // displays with minimal digits (no padding, no positive sign)
  static const int PAD_ZERO = 1; // set padding character as '0' instead of ' '.
  static const int SIGN_PLUS = 2; // put '+' sign if value is positive or 0.
  static const int PAD_ZERO_SIGN_PLUS = 3; // PAD_ZERO & SIGN_PLUS
  static const int SIGN_SPACE = 4; // put ' ' sign if value is positive or 0.
  static const int PAD_ZERO_SIGN_SPACE = 5; // PAD_ZERO & SIGN_SPACE
};

// Class to store string conversion results
class _div_chars {
  ...
  const char* begin() const {...}
  const char* end() const {...}
  const char* c_str() const { return begin(); }
  operator const char*() const { return begin(); }
};

// format() method
_div_chars div_result_i32::format(
    int dig_quo = 0, uint32_t opt = DIVFMT::STD) const;

// Implementation of interface to Serial
template <class D> class stream {
...
		inline D& operator << (const mwx::_div_chars&& divc);
		inline D& operator << (const mwx::div_result_i32&&);
		inline D& operator << (const mwx::div_result_i32&);
};

The div_result_i32 class that stores the division result has a format() method to obtain a _div_chars class object. The _div_chars class object contains a string buffer and provides methods to access the string buffer as const char*. Also, the << operator for the Serial object is implemented.

The first parameter dig_quo of the format() method specifies the number of output digits (excluding the sign). If the output digits are insufficient (below), it is filled with spaces or 0. The second parameter opt specifies the format.

opt parameterDescription
DIVFMT::STDStandard output, fills insufficient digits with spaces, and adds - only for negative values.
DIVFMT::PAD_ZEROFills insufficient digits with 0.
DIVFMT::SIGN_PLUSAdds + sign for positive values as well.
DIVFMT::PAD_ZERO_SIGN_PLUSFills insufficient digits with 0 and adds + sign for positive values.
DIVFMT::SIGN_SPACEAdds a space sign instead of + for positive values.
DIVFMT::PAD_ZERO_SIGN_SPACEFills insufficient digits with 0 and adds a space sign instead of + for positive values.

Example

//// Direct output from div_result_i32 object
Serial << div100(-1234) << crlf;
// Result: -12.34

//// Output with 3 digits
Serial << div100(3456).format(3, DIVFMT::PAD_ZERO_SIGN_PLUE) << crlf;
// Result: +034.56

//// Use c_str() to get const char*
char str1[128];
auto x = div100(-5678);
mwx_snprintf(str1, 16, "VAL=%s", x.format.c_str()); // const char*
Serial << str1;
// Result: VAL=-56.78

Background

In TWELITE BLUE/RED, division is a costly operation, so a division algorithm with limited purposes was added.

Within the library, some sensor values such as temperature and humidity are represented using values multiplied by 100 (e.g., 25.12℃ as 2512), so a simple procedure to obtain the quotient and remainder when divided by 100 was defined.

dev_result_i32::format() is provided to avoid complexity when formatting output.

3.9 - Scale utils

Optimized value scaling processing
Scale (expand/contract) between 8bit values (such as 0..255) and user-friendly 0..1000 (per mille, ‰) values. To achieve low computational cost, division (x*1000/255) is replaced with multiplication and bit shifts for approximate calculation.
static inline uint8_t scale_1000_to_127u8(uint16_t x)
static inline uint16_t scale_127u8_to_1000(uint8_t x)
static inline uint8_t scale_1000_to_255u8(uint16_t x)
static inline uint16_t scale_255u8_to_1000(uint8_t x)
static inline uint8_t scale_1000_to_256u8(uint16_t x)
static inline uint16_t scale_256u16_to_1000(uint16_t x)

scale_1000_to_127u8()

Scales 0..1000 to 0..127. Uses (16646*x+65000)>>17 for approximate calculation.

scale_127u8_to_1000()

Scales 0..127 to 0..1000. Uses (2064000UL*x+131072)>>18 for approximate calculation.

scale_1000_to_255u8()

Scales 0..1000 to 0..255. Uses (33423*x+65000)>>17 for approximate calculation.

scale_255u8_to_1000()

Scales 0..255 to 0..1000. Uses (1028000UL*uint32_t(x)+131072)>>18 for approximate calculation.

scale_1000_to_256u8()

Scales 0..1000 to 0..256. Uses (33554*x+66000) >> 17 for approximate calculation.

Note: For x=999,1000 the calculated value becomes 256, but returns 255 as the range of uint8_t.

scale_256u16_to_1000()

Scales 0..256 to 0..1000. Uses (1028000UL*uint32_t(x)+131072)>>18 for approximate calculation.

Background

Values to be set in hardware are often based on binary such as 0..255, while numbers handled in user applications are easier to manage when based on decimal such as 0..1000. These scale conversions define formulas that do not use division.

3.10 - pnew()

Simplifies the syntax for placement new
Simplifies the syntax of placement new.
template <class T, class... Args>
T* pnew(T& obj, Args&&... args) {
    return (T*)new ((void*)&obj) T(std::forward<Args&&>(args)...);
}

For example, it can be used as follows. You can also pass constructor arguments.

class my_class {
    int _a;
public:
    my_class(int a = -1) : _a(a) {}
};

my_class obj_1; // This constructor is not called!
my_class obj_2; // This constructor is not called!

void setup() {
    mwx::pnew(obj_1);    // Equivalent to my_class obj_1;
	mwx::pnew(obj_2, 2); // Equivalent to my_class obj_2(2);
    ...
}

Background

Due to compiler constraints, constructors of global objects are not called. One method to initialize them is using placement new. However, the placement new syntax can appear verbose.

Another method is to use std::unique_ptr (or eastl::unique_ptr).

std::unique_ptr<my_class> obj_3;

void setup() {
    obj_3.reset(new my_class(3));
    		// On TWELITE microcontrollers, `new` can only allocate once and `delete` cannot be used,
            // so in practice it is equivalent to a global object.
}