Having sorted out the concepts of what needed to be implemented, the next step was to write the software that would drive the basic building blocks of the remote control – switches, IR receiver, IR transmitter and LEDs.
The first step for me in these developments is to name the software or system under development. After some deliberation, Umote was my unanimous choice.
The basic functionality for Umote is described in Part 1 of this series. Additionally, during testing it became clear that an additional operating mode was needed, one that would allow switches that had been previously programmed to be cleared from EEPROM. There are therefore three basic modes – Run, Program and Clear – for the control software.
The basic structure of the control code was always going to be straightforward, as there is well defined and restricted ‘real world’ interaction, depicted in the block diagram below.
The control software needs to read from switches and control the IR transmission LED and switch feedback LEDs. In program mode, the software also receives the transmitted code through the IR receiver. EEPROM memory is used to store and retrieve the IR codes that are programmed into Umote.
From their prior experience with commercially available systems, users have the expectation that a remote’s battery will last a long time. Unfortunately an Arduino processor running at full power would not live up to that expectation, so power management is an important element of the control software to maximise battery life during Run mode.
As a start, the power indicator LED on the Nano was removed as it was not required and provided a reduction of about 10 mA current consumption. Testing also showed that putting the Arduino Nano into a deep sleep dropped the current usage from 45 mA to 3.5 mA. With a 1000 mAh battery, this gives a standby time of around 286 hours (12 days). I could live with that.
After some thought, it was also clear that the IR receiver was not required during Run mode, so it too could be switched off. Rather than connecting it directly to 5V, it changed to being powered by an Arduino output pin to easily allow the required on/off control.
Consistent User Interface
Although Umote is a simple device, it is important to provide a consistent User Interface across the different modes of operation. User output is limited to a few LEDs so the user interface design was for them to
- initially confirm the Run/Clear/Program through blinks and patterns of the LEDs at power up
- slow flash when input is required from the user (eg, programming a key)
- remain solid on when a function is being executed (eg, switch press)
- give one short flashes when an action has terminated successfully
- give two short flashes when an action has terminated unsuccessfully or with an error
Implementing run modes as Finite State Machines
I implement most of my Arduino projects as Finite State Machines in order to create the illusion of multiple things happening at once. This project was no exception, with separate FSM created for each of the three different run modes. The loop() function only acts as a scheduler to invoke the correct FSM.
All the FSM start with an INIT state and end with and END state. This is a convenience that allows for an easy and consistent initialization and tidy up at the end of each run through the FSM.
The simplest FSM is for Clear mode. After initialising the software moves to IDLE state. In this state it is looking for a switch to be pressed. When this is detected, the state is changed to CLEAR, in which the relevant EEPROM storage is cleared of any programmed IR code. The FSM then transitions to END, followed by IDLE, starting the process all over again.
Run mode FSM is more complex. Following INIT the FSM enters SLEEP state. In this state the processor is put into a deep sleep, awakened by an I/O level change interrupt caused by the user pressing a switch to action IR transmission. Once awake the FSM moves to the IDLE state, where the software waits for the switch debounce routine to confirm the switch was actually switched on, as level change is also triggered from an on-to-off transition. If a true press is detected, then FSM moves to SEND, otherwise it goes straight to END. In SEND the EEPROM is read, the IR code is transmitted through the IR transmitter LED, and FSM moves on to WAIT1. WAIT1 is an initialisation state for the delay timer at the end of a transmission. WAIT2 implements a delay() timer until the required delay has expired at which point the FSM will END and then transition back to SLEEP.
Finally, Program mode FSM uses INIT to turn on the IR receiver, quickly transitioning to IDLE, where it waits for a key to be pressed. Once this is confirmed, IRLib is invoked to read the incoming IR stream and FSM transitions to RECV. In this state it flashes the switch feedback LED and monitors IRLib for an end to the read. If the incoming stream can be decoded, the FSM moves to DECODE, where the decoded parameters are stored in EEPROM. If the incoming IR message cannot be decoded, the FSM moves to RECORD, where the raw timing data are stored in EEPROM – these are still usable but may not be as reliable as the decoded message. From DECODE and RECORD, the FSM transitions to FLASH, where the user is informed of the outcome. Then a transition to END and finally back to IDLE.
The source code for Umote can be found on my Arduino Libraries site.