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>
Send / Receive I2C commands
Sleep periodically for running with a button cell.
Header files are <NWK_SIMPLE>
(Simple network support), <STG_STD>
(Interactive settings mode) and <SM_SIMPLE>
(Simple state machines).
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
.)
Below using the SHTC3.
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
Each procedures are shown below.
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.
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
.
Return the value of CONV_TIME
.
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.
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.
Called once when the TWELITE is started. This function performs various initializations.
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.
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>
.
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.
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.
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).
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.
Initialization for the I2C device.
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.
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.
It is not convenient for the main loop to operate during interactive settings mode, so it is fixed in this state.
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.
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.
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.
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.
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.
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).
Here, it waits for the completion of transmission. Judgment of timeout (in case of error) or transmission completion event is made.
Processes the sleepNow()
function. By calling this function, the TWELITE wireless microcontroller goes to sleep.
This is a system event called when transmission is completed. Here .set_flag()
is called to set the flag of step
.
Procedures for going to sleep.
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
.
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.
Type | Example |
---|---|
Name | Description |
---|---|
Byte | Description |
---|---|
Parent
Run the Act Parent_MONOSTICK on the MONOSTICK BLUE or RED
Children
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.
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