Main

Top-level Finite State Machine for controlling the MSP430 and cuplTag as a whole.

A Finite State Machine is defined in this file and run from main(). This has several features.

Author

Malcolm Mackay

It makes calls to drivers for communicating with the HDC2022 humidity sensor and the NT3H2111 NFC EEPROM.

It controls entry into the ‘programming mode’ sub state machine, where configuration strings can be written using a serial port.

It reads configuration strings from the NFC EEPROM if any are present.

It collects samples from an HDC2022 at a fixed time interval and passes these to the cuplcodec encoder.

Defines

CS_SMCLK_DESIRED_FREQUENCY_IN_KHZ

Target frequency for SMCLK in kHz.

CS_XT1_CRYSTAL_FREQUENCY

Resonant frequency of the XT1 crystal in kHz.

CS_XT1_TIMEOUT

Timeout for XT1 to stabilise at the resonant frequency in SMCLK cycles.

CP10MS

ACLK Cycles Per 10 MilliSeconds. Assumes ACLK = 32768 kHz and a divide-by-8.

EXIT_STATE

State machine exit state.

ENTRY_STATE

State machine entry state.

Typedefs

typedef enum state_codes tstate

States in the Finite State Machine are represented by a code. This is used each time the state machine is run, to determine:

  1. Which state function to call in main().

  2. The next state in lookup_transitions().

typedef enum ret_codes tretcode

Each state function returns at least one code from the list below. This is used by lookup_transitions() to determine the next state.

typedef enum event_codes tevent

Events occur asynchronously to execution of the FSM. The MSP430 will typically wait for an event in sleep mode to save power. An example is an edge on the INT (interrupt) output from a temperature sensor. This is connected to an input on the MSP430, which is configured to call an Interrupt Service Routine (ISR). The ISR sets a flag and ‘wakes up’ the MSP430 from sleep mode.

The main() function is entered, flags are checked then cleared and the event variable is set according to the list of codes below. Finally, the state function is called with the event passed as an argument.

Multiple events can occur simultaneously, but only one at-a-time is passed to the FSM.

Enums

enum state_codes

States in the Finite State Machine are represented by a code. This is used each time the state machine is run, to determine:

  1. Which state function to call in main().

  2. The next state in lookup_transitions().

Values:

enumerator sc_init

State code for init_state()

enumerator sc_init_reqmemon

State code for init_reqmemon()

enumerator sc_init_waitmemon

State code for init_waitmemon()

enumerator sc_init_ntag

State code for init_ntag()

enumerator sc_init_progmode

State code for init_progmode()

enumerator sc_init_configcheck

State code for init_configcheck()

enumerator sc_init_errorcheck

State code for init_errorcheck()

enumerator sc_init_wakeupcheck

State code for init_wakeupcheck()

enumerator sc_init_batvwait

State code for init_batvwait()

enumerator sc_init_rtc_slow

State code for init_rtc_slow()

enumerator sc_init_rtc_1min

State code for init_rtc_1min()

enumerator sc_smpl_checkcounter

State code for smpl_checkcounter()

enumerator sc_smpl_hdcreq

State code for smpl_hdcreq()

enumerator sc_smpl_hdcwait

State code for smpl_hdcwait()

enumerator sc_smpl_hdcread

State code for smpl_hdcread()

enumerator sc_smpl_wait

State code for smpl_wait()

enumerator sc_err_msg

State code for err_msg()

enumerator sc_end

State code for end_state()

enum ret_codes

Each state function returns at least one code from the list below. This is used by lookup_transitions() to determine the next state.

Values:

enumerator tr_ok
enumerator tr_prog
enumerator tr_newconfig
enumerator tr_hdcreq

Request a sample from the HDC2021 sensor.

enumerator tr_updatemin
enumerator tr_deepsleep

cuplTag should enter a deep sleep state LPM3.5

enumerator tr_lowbat
enumerator tr_fail
enumerator tr_samplingloop

cuplTag is in the sampling loop. The reset was caused by an exit from LPM3.5

enumerator tr_por

cuplTag is NOT in the sampling loop. A Power-On-Reset has occurred.

enumerator tr_wait

cuplTag should enter a sleep state, such as LPM0, to wait for an event.

enum event_codes

Events occur asynchronously to execution of the FSM. The MSP430 will typically wait for an event in sleep mode to save power. An example is an edge on the INT (interrupt) output from a temperature sensor. This is connected to an input on the MSP430, which is configured to call an Interrupt Service Routine (ISR). The ISR sets a flag and ‘wakes up’ the MSP430 from sleep mode.

The main() function is entered, flags are checked then cleared and the event variable is set according to the list of codes below. Finally, the state function is called with the event passed as an argument.

Multiple events can occur simultaneously, but only one at-a-time is passed to the FSM.

Values:

enumerator evt_none

No event has occurred.

enumerator evt_timerfinished

The timer peripheral has counted down to 0.

enumerator evt_hdcint

Pin change interrupt received from the HDC2021 temperature and humidity sensor.

Functions

void fram_write_enable()

Enable writes to program FRAM.

Some variables are stored in program FRAM. RAM cannot be used because state is lost in deep sleep mode (LPM3.5). The Program FRAM Write Protect bit must be cleared (and interrupts disabled) before a write.

void fram_write_disable()

Disable writes to program FRAM.

Sets the Program FRAM Write Protect bit and re-enables interrupts.

tretcode init_state(tevent evt)

Initialise clocks and IOs on the MSP430.

All IOs are configured into an initial state. The number of IOs left as inputs (default) must be minimised to reduce power consumption.

The slow Auxiliary Clock (ACLK) is sourced form the external 32.768 kHz crystal. An internal 10 kHz source is used by default. This is power hungry and drifts with temperature.

Next, the Phased-Locked Loop (DCO) generates an output frequency of 1 MHz, by multiplying the external 32.768 kHz crystal frequency up by 31.

Internal clocks MCLK and SMCLK are connected to the DCO output.

1 MHz was selected to minimise current draw from the high impedance coin cell battery. This results in a lower voltage drop after exiting the sleep state. Battery life is limited by this voltage drop. This is not the case if the source impedance is lower. Then it is best to operate at a higher frequency: up to 24 MHz.

Finally, the cause of the reset is read. The program needs to know whether this is just a routine wake-up from sleep (LPM3.5) or the result of a fault.

tretcode init_reqmemon(tevent evt)

This state calls reqmemon().

tretcode init_waitmemon(tevent evt)

This state calls waitmemon().

tretcode init_ntag(tevent evt)

Initialise the dual-interface I2C+NFC EEPROM.

A call is made to ‘nt3h_check_address()’ to make sure the EEPROM is at device address 0x55.

The first EEPROM block is read to check for an NFC text record. If found, configuration strings are extracted and saved into non-volatile memory.

The capability container is written if it needs to be. These 4 bytes indicate that the tag contains an NDEF message.

The programming mode select pin (nPRG) is checked. If it is LOW, the return code is updated.

tretcode init_progmode(tevent evt)

Run the programming mode sub-state machine.

Enables the serial port (UART) and responds to text commands.

It is only intended that this state be entered in a production environment, not by the end user.

The only way to exit is to reset the microcontroller. This can be done by sending a soft reset command ‘<z>’.

tretcode init_configcheck(tevent evt)

Verifies that all configuration strings have been written.

The state machine cannot continue unless the tag is fully configured. There are no default settings.

When configuration is incomplete, a text-based error message is written to the tag and deep-sleep mode is entered.

tretcode init_errorcheck(tevent evt)

Check for an error condition before continuing startup.

An error occurs if the reset was caused by a reason other than a new battery insertion.

When the battery voltage is below a threshold (set in NVM). The state machine should not get stuck in a loop, where an attempt is made to write to the NFC EEPROM before the micro-controller resets. Reset loops can wear out the EEPROM.

In the event of 10 consecutive resets that result in an error, or a single low battery reading:

  1. Report the most recent error in the cupl URL status word.

  2. Do not include any samples in the cupl URL.

  3. Go to a deep sleep state that prevents another reset cycle from occurring for some time.

Otherwise:

  1. Report the most recent error in the cupl URL but treat it as spurious.

  2. Allow the state machine to continue.

Returns

tr_deepsleep when 10 consecutive errors have occurred or the battery voltage is low. Otherwise indicate no errors with tr_ok.

tretcode init_wakeupcheck(tevent evt)

Has the reset has been caused by a routine RTC wake-up?

In the sampling loop, Wake-ups from LPM3.5 occur every minute. These are invoked by an interrupt from the Real Time Clock peripheral.

This function first makes a call to stat_rstcause_is_lpm5wu(). Then it checks if the first integer in Backup Memory is 1. If it is, then the cuplTag is in the sampling loop.

Returns

tr_samplingloop if the cuplTag is in the sampling loop. Otherwise, indicate a power-on-reset with tr_por.

tretcode init_batvwait(tevent evt)

Wait for the battery voltage to stabilise.

It takes some time for capacitors to charge after a battery is inserted. A timer is started in the previous state. The MSP430 waits here in low power mode.

Parameters

evt[in] Event. When set to evt_timerfinished the state machine progresses.

Returns

tr_ok when the timer has finished. Otherwise tr_wait.

tretcode init_rtc_slow(tevent evt)

Configure the Real Time Clock to peripheral to generate one interrupt every 30 minutes.

This is done to prevent the cuplTag from being stuck in the end_state when an error occurs during startup. This state must be entered before any possible transitions to the end_state.

Whatever the error, the cuplTag must wake up and try to start up again. A long time interval has been chosen so that the battery is not depleted.

tretcode init_rtc_1min(tevent evt)

Configure the Real Time Clock peripheral to generate one interrupt every minute.

The cuplTag spends most of the time in a deep sleep mode LPM3.5 to save power. Each minute, it wakes up for a few milliseconds to collect a sample or to increment minutecounter.

TURBO MODE is a special feature that is useful for test purposes. When enabled, the interrupt occurs every 3 seconds. To enable, set the time interval parameter to 0.

tretcode smpl_checkcounter(tevent)
tretcode smpl_hdcreq(tevent evt)

Request a sample from the HDC2021 sensor.

A sample is requested by the MSP430 with hdc2020_startconv(). When data is ready, the HDC2021 makes a HIGH to LOW transition on its INT pin. To save power, the MSP430 sleeps whilst the measurement is taken.

The MSP430 pin connected to INT is made to raise an interrupt when a falling edge is detected.

tretcode smpl_hdcwait(tevent evt)

Wait in LPM3 whilst waiting for a data ready interrupt from the HDC2021 sensor.

Returns

tr_ok when a DRDY interrupt has been detected. Otherwise return tr_wait to indicate that the MSP430 should sleep.

tretcode smpl_hdcread(tevent)
tretcode smpl_wait(tevent)
tretcode err_msg(tevent evt)

Write the NDEF message ndefmsg_badtrns to the NFC EEPROM.

Notify the developer that an invalid state machine transition has been requested. The user should never see this. This will occur if a transition has been requested that does not exist in the state_transitions table.

tretcode end_state(tevent evt)

Put the MSP430 into deep sleep LPM3.5.

Minimise power consumption by powering down as much of the MSP430 (and cuplTag) as possible. Only the Real Time Clock and Backup Memory peripherals remain powered on.

The RTC can generate an interrupt to wake the MSP430 up. When this occurs, the program starts from a reset condition. Only the backup memory can be used to persist state.

This function disables GPIO interrupt sources. It stops any timers, in case these prevent LPM3.5 from being entered. It disables the watchdog, because this is power hungry and the RTC can be used instead (see init_rtc_slow()).

Most importantly it calls memoff() to make sure that the VMEM domain is powered down.

The Supply Voltage Supervisor is disabled to save power. It is not very useful in deep sleep mode. The battery voltage will decline very little between wake-ups from the RTC.

Returns

tr_ok but this is to keep the compiler happy. Deep sleep is entered before the function returns.

tstate lookup_transitions(tstate curstate, tretcode rc)

Look up the next state in the Finite State Machine.

The look-up is performed by iterating through an array of transitions. An error state is returned if no match is found.

Parameters
  • curstate[in] Current state.

  • rc[in] Code returned from the current state.

Returns

Next state.

static void writetxt(const char *msgptr, int len)

Write an NDEF message to the NFC EEPROM.

The NDEF message normally contains one text record. It can be created with an external program and stored as a constant array. The function is used to display simple error messages to the end-user.

Parameters
  • msgptr[in] Pointer an NDEF message array.

  • len[in] Length of the NDEF message.

static void wdog_kick()

Kick the watchdog, to prevent it from timing out.

This is done by writing to the watchdog control register.

static void start_timer(unsigned int intervalCycles)

Start a single-shot timer.

An interrupt fires when the timer has finished counting. The MSP430 can sleep in LPM3 whilst waiting for it. This saves power over delay loops.

The function is best suited to pausing execution for a short time (milliseconds).

Parameters

intervalCycles[in] Number of 4.096 kHz clock cycles to count.

static void memoff()

Power down the VMEM domain.

The load switch enable pin is set low, breaking the circuit between VDD and VMEM. This is done to save power in sleep mode. The NT3H2111 EEPROM will otherwise draw ~10uA.

static tretcode reqmemon(tevent evt)

Enable power to the VMEM domain.

Configure a pin to receive interrupts from the humidity sensor. Set the load switch enable pin HIGH to power up the VMEM domain from VDD.

After this function has been called, the MSP430 must sleep whilst waiting for a Timer interrupt. When this fires, the VMEM voltage should be stable.

static tretcode waitmemon(tevent evt)

Wait for the VMEM voltage to stabilise after power on.

Timer_B1 must be started with start_timer() prior to calling this function.

Parameters

evt[in] Event. When set to evt_timerfinished, I2C is enabled and the state machine progresses.

void main(void)
void TIMER1_B0_ISR(void)
if ((P1IN &BIT1)==0)

Variables

int timerFlag = 0

Flag set by the Timer Interrupt Service Routine.

int hdcFlag = 0

Flag set by the HDC2021 humidity sensor data-ready Interrupt Service Routine.

int minutecounter = 0

Incremented each time the sampling loop is run.

const char ndefmsg_progmode[] = {0x03, 0x3D, 0xD1, 0x01, 0x39, 0x54, 0x02, 0x65, 0x6E, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x6D, 0x69, 0x6E, 0x67, 0x20, 0x4D, 0x6F, 0x64, 0x65, 0x2E, 0x20, 0x43, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6F, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x70, 0x6F, 0x72, 0x74, 0x20, 0x61, 0x74, 0x20, 0x39, 0x36, 0x30, 0x30, 0x20, 0x62, 0x61, 0x75, 0x64, 0x2E, 0xFE}

Hard-coded NDEF message containing one text record “Programming Mode. Connect to serial port at 9600 baud.”

const char ndefmsg_noconfig[] = {0x03, 0x2D, 0xD1, 0x01, 0x29, 0x54, 0x02, 0x65, 0x6E, 0x43, 0x6F, 0x6E, 0x66, 0x69, 0x67, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6B, 0x20, 0x66, 0x61, 0x69, 0x6C, 0x65, 0x64, 0x2E, 0x20, 0x53, 0x65, 0x65, 0x20, 0x63, 0x75, 0x70, 0x6C, 0x54, 0x61, 0x67, 0x20, 0x64, 0x6F, 0x63, 0x73, 0x2E, 0xFE}

Hard-coded NDEF message containing one text record “Config check failed. See cuplTag docs.”

const char ndefmsg_badtrns[] = {0x03, 0x27, 0xD1, 0x01, 0x23, 0x54, 0x02, 0x65, 0x6E, 0x45, 0x72, 0x72, 0x6F, 0x72, 0x3A, 0x20, 0x49, 0x6E, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0xFE}

Hard-coded NDEF message containing one text record “Error: Invalid state transition.”

tretcode (*state_fcns[])(tevent) = {init_state, init_reqmemon, init_waitmemon, init_ntag, init_progmode, init_configcheck, init_errorcheck, init_wakeupcheck, init_batvwait, init_rtc_slow, init_rtc_1min, smpl_checkcounter, smpl_hdcreq, smpl_hdcwait, smpl_hdcread, smpl_wait, err_msg, end_state}
struct transition state_transitions[]

The state transition table.

struct transition

Public Members

tstate src_state

Source state.

tretcode ret_code

Code returned after executing a state function.

tstate dst_state

Destination state.