Developing a Remote Sensor Firmware using Test Driven Development

Developing firmware for an embedded remote sensor that needs to adhere to a specific protocol has challenges due to the limited resources and the complexity of the implementation. Experience on a previous project (a remote sensor gateway) demonstrated how Test Driven Development could assist when working on complex systems. This write-up describes the approach used in the development of an embedded sensor node using TDD along with the workarounds I applied to the issues that were encountered along the way. It was not an easy project to start as it required a change in mindset and also a change in how one uses the available tools for embedded systems. The Unity and CMock utilities were a great help to ensure that the tests could be developed in a standard way.

DESIGN GOALS

  • Support for XBee radio units.
  • Communicate with the existing gateway.
  • Operate on an optimal sized microcontroller.
    • USART x2.
    • I2C/TWI.
  • Support a diagnostics logging feature.
  • Support for the INA219 module.
  • Low power consumption i.e. support for Sleep Mode

A search was made for a microcontroller that could possibly fit the purpose. The idea was to have a microcontroller that supported enough memory and peripherals to get the job done. It was unlikely that the selected chip would contain any sophisticated debugging like JTAG so it was important to have two USART ports. One of the ports would be dedicated to the XBee radio module and the other would be used for diagnostics logging. Since I tend to use AVR devices, I settled on the ATmega1608.

I created a custom development board to support the GPIO I was looking for. This has the additional benefit of being able to sort out any hardware design issues that could surface in the final product.

CONCEPT DESIGN

BLOCK DIAGRAM

The block diagram depicts the physical aspects that were thought of some time ago. The original design contains much more but the extra aspects were removed for the scope of this project. The microcontroller module, i.e. the custom development board, interfaces with the Telemetry Unit i.e. the XBee module via a USART and the Voltage/Current sensor interfaces via I2C/TWI. There is then a spare UART leaving the block. I find these concepts important to maintain and not to be too specific about the implementation of the underlying modules. This helped to organise the code later.

Block Diagram

STATE DIAGRAM

It was planned from the start to implement a Finite State Machine (FSM) as defined in the state diagram below. The device would power up and go through some initialisation steps and go into an idle state. After a determined amount of time, the device will wake, perform a reset (i.e. wake the MODEM) and send out a READY message on a broadcast address. This message should be received by a nearby gateway. The gateway will check if the node is known. If not, the gateway will send a NODE-INTRO-REQ message, whereupon the node will respond with a package of data introducing itself. If the node is known, then the gateway will respond with a DATA-REQ message. The node will then read the sensor information and package a DATA response for the gateway to record. When the node receive an acknowledgement that its messages have been received, it will put the MODEM and itself to sleep and be in the idle state. The broadcast address is only used when a gateway is not known. After the first interaction with a gateway, the node will save the address of the gateway for use next time.

State Diagram

MESSAGES

The messages sent between the sensor node and the gateway are based on a token/value structure. The goal was to be able to transmit data but still remain in the approximately 70 byte data frame limit for the XBee. The tokens are one byte values that are predefined to describe the data and metadata that the devices will deal with. The structure of the message always begins with the operation and then the serial ID of the transmitting device. The operation will dictate the rest of the structure. The serial ID for the current implementation is taken directly from the 10 byte device ID available in the ATmega. Ideally, this could also have come from some other external silicon serial ID device. But therefore the microcontroller serial ID comes for free.

READY

The READY message alerts the gateway that a node is attempting to communicate. The gateway will validate the source of the message and will then respond directly back to the node with either a NODE-INTRO-REQ, if the node is not known, or a DATA-REQ if the node is known to the system.

Byte 0Byte 1Byte 2Byte 3 -Byte 13
0x11 (OPERATION)0x21 (READY)0x12 (SERIAL_ID)Device Serial ID
DATA

On receipt of a DATA-REQ, the node will collect the information that it is to send and package this into a data frame and send this to the gateway. The structure of Data message is shown below. From byte 14, this becomes a repeating group of a one bye property token and then a four bye property value.

Byte 0Byte 1Byte 2Byte 3 -Byte 13Byte 14Byte 15
0x11 (OPERATION)0x23 (DATA)0x12 (SERIAL_ID)Device Serial IDProperty 1 tokenProperty 1 value
NODE-INTRO

On receipt of the NODE-INTRO-REQ, then node will respond with the information that describes the node. This will include the domain and class of the device and then a repeating group of one bye property delimiter and a one byte property token.

Byte 0Byte 1Byte 2Byte 3 – Byte13 Byte 14Byte 15Byte 16Byte 17Byte 18Byte 19
0x11 (OPERATION)0x23 (NODE-INTRO)0x12 (SERIAL_ID)Device Serial ID0x13 (DOMAIN)Domain token0x14 (CLASS)Class Token0x30 (PROPERTY)Property Token
Domain and Class

The domain and class is a way to describe the type and purpose of a node.

  • DOMAIN
    • POWER
      Devices of this time operate in the power domain. This means that they can measure or even provide controls.
    • CAPACITY
      The capacity domain concerns the measurement of volume.
    • RATE
      The rate domain is concerned with the capturing flow or movement information.
  • CLASS
    • SENSOR
      A sensor class device is expected to read some sensor value and provide this information in response to a DATA-REQ from the gateway.
    • ACTUATOR
      An actuator class device enable some action to be performed in the field.

IMPLEMENTATION

The first thing I needed to do was organise the code, or at least understand how it should be organised in such a way that I could test the individual aspects. The idea was to follow on from what I learned from Test-Driven Development with Python by Harry Percival and divide the code into modules and consider these modules in different conceptual areas such as Domain modules and Adapter modules. The Domain modules consider the business aspects of the logic and are agnostic to the underlying hardware being used. It is the adapter’s job to provide a simplified interface between the domain modules and the actual hardware peripherals. The idea here is to then create code that theoretically should be able to be ported to another device with only the adapters needing any changes. Equally, in the case of this node, if we change the type of modem being used, then as long as the adapter interface does not change, the node should still operate.

Module Organisation

The problem came with my understanding of using Unity and CMock. I found the documentation very simplistic which created a challenge to apply these concepts with no previous experience. The Test Driven Development for Embedded C by J. W. Grenning was a great resource. Although I tried to maintain the Domain/Adapter concept, I did make some design/implementation decisions not to abstract too much since I was intending to work only in 16K of program space.

I had to find a way in which to be able to patch in the mock modules to be used by the tests but not break the build to be deployed to the target device. I found that the profiles as given in the MPLAB integrated development environment became very useful. I had previously never really taken much notice of profiles but now I see real value in the feature. I left the main profile configured for the ATmega1608 and with the main.c file as the main entry point. For the tests I created a profile for each of the domain and adaptor tests respectively. They each had their own main file to host the main(void) function and I included and excluded the C and head of files that each of those profiles required. When using CMock. It was important for the test profiles to be configured with the Simulator only and not any ISP tool. This would ensure that the tests would run locally and not need any hardware. I found this a significant improvement to the development workflow in itself. I could get immediate feedback on the success of the test without using any hardware. This meant I could work in my lab or on a laptop on the dining room table since I could run the tests anywhere, anytime.

Profile configuration – Establishing the files to include/exclude in the build
Profile configuration for one of the test builds – Configured for the larger ATmega4808 and the Simulator

LIBAVRXBEE

I had decided on using the libavrxbee utility from Nick Harris to help with the creation of the XBee messages. This library does not interact with the radio module. It will only help to prepare the data for transmission or for receiving.

I had issues with getting the library to work correctly, particularly with larger messages. I found an entry that mentioned that the x8-cc compiler does not support the malloc API on 8-bit machines as the memory is too restricted to use this API safely. The libavrxbee library uses this API in a couple of places. I made some small changes to the library to accommodate fixed sized arrays rather than allocating space. I figured I could justify this change since my usage of the library was very specific and the number and type of messages were finite.

The Test Driven Development approach was helpful in proving the changes. I created the expected XBee message frames using the XCTU utility from DIGI International. These messages could be compared to the data created by the libavrxbee library and were then included in the test cases as the expected values.

TEST CREATION

The CMock utility was an excellent tool but did take some getting used to. Particularly when devising tests for USART where a call to the underlying peripheral API accepts or returns one byte at a time. Here I took the tips from J. W. Grenning and structured the code so that it could be tested. As mentioned above, I had three profiles, main, test_fsm and test_adapter. For the Finite State Machine (test_fsm), I had a set of mock adapter modules. This way I could focus on the business aspect and this set up the expectation on what interfaces the adapters should support.

The Node implementation with mock adapter modules

In the adapter set of tests (test_adapter), I focused only on testing the public functions and used mocks for the actual hardware peripheral modules.

Adapter tests don’t need to be concerned with the main Node module. They only need to implement the public APIs and use the mock hardware peripheral modules.

The MPLAB tool set i.e. xc8-cc reportedly supports a stimuli file during debugging. This enables IO for the USART etc to be predefined for the purpose of testing. I struggled to get this working. This does not mean it does not work. My understanding of this limits me to have a clear idea of what steps I need to perform to get it working and my patients drew me to the features of CMock to set up what I refer to as fixtures. That is to programatically predefine the expected information to and from the I2C/TWI and USART.

The features of interest are

  • Ignore
    • Functions that are part of a mock and are potentially called multiple times but are of no interest to the specific test can be ignored.
  • Callback
    • This enabled content to be recorded to an array as a form of spy that could be inspected after the function under test completed.

These features of CMock were mentioned in various blogs, however, the samples did not quite work for me out of the box as had been implied. I found that I needed to create a configuration file for CMock that would provide these implementations.

As development progressed and the number of mocks and tests increased, I soon found that the program and data space of the ATmega1608 soon filled, blocking me from further development. Using the profiles meant that I could change the target device to the larger ATmega4808, which is a compatible AVR series-0 device. There I had enough Flash and data area that could comfortably accommodate further Unity tests and CMock modules.

FEATURES

LOGGING

An original design concept was to have printf configured to use USART1 as a channel for logging. I soon realised that the format strings used in printf would also soon have an impact on the program space. I could have shortened or removed the statements but I did not want to compromise the quality of the logging. Switching the test profiles to the ATmega4809 helped a great deal, but I felt there was more that could be done. This became apparent when I was at the stage of assembling the components to flash to the target device. The logging was causing the build to fail on the ATmega1608. I therefore introduced the concept of logging levels. Such concepts are not new. I introduced a set of macros for the various log levels controlled by an object macro called LOGGER_LEVEL. Behind the scenes, the preprocessor would only include in those logging statements that were defined for their respective level. All others would be excluded from the compile.

The table below shows the relationship to the specified logging level and the resource usage. On the ATmega1608, with DEBUG configured, the code fails to build for this target. I never felt that this was an issue since these debug statements were mainly of use for debugging with the tests.

PROFILEPROGRAM
OFF

INFO

DEBUG
DATA
OFF

INFO

DEBUG

Main (ATmega1608)

74%

96%

FAIL

51%

51%

FAIL

Test_fsm (ATmega4808)

39%

65%

70%

30%

30%

30%

Test_adapter (ATmega4808)

50%

82%

90%

39%

39%

39%

Node Logging
INFO: FSM_READY_STATE: Sending READY
INFO: modem_get_coord_addr: Getting addr EEPROM: 0013 A200 415C 0F82
INFO: Sending: 7E 00 1B 10 00 00 13 A2 00 41 5C 0F 82 FF FE 00 00 11 21 12 30 54 43 30 4B 51 A4 8A 15 0E E7
INFO: Received Data: 7E 00 19 90 00 13 A2 00 41 5C 0F 82 FF FE C1 11 22 12 30 54 43 30 4B 51 A4 8A 15 0E A5
INFO: modem_get_coord_addr: Getting addr cache: 0013 A200 415C 0F82
INFO: , data: 11 22 12 30 54 43 30 4B 51 A4 8A 15 0E
INFO: FSM_READY_STATE: Operation NODE_TOKEN_DATAREQ - Calling the DATAREQ callback.
INFO: node_data_collection: transmitting the collected data
INFO: Parsing the data for NODE_TOKEN_DATA
INFO: modem_get_coord_addr: Getting addr cache: 0013 A200 415C 0F82
INFO: Sending: 7E 00 2A 10 00 00 13 A2 00 41 5C 0F 82 FF FE 00 00 11 23 12 30 54 43 30 4B 51 A4 8A 15 0E 31 46 B6 F3 40 32 68 E8 9F 3A 33 A6 9B 44 3C 36
INFO: FSM_DATA_STATE: A message has arrived
INFO: Received Data: 7E 00 19 90 00 13 A2 00 41 5C 0F 82 FF FE C1 11 24 12 30 54 43 30 4B 51 A4 8A 15 0E A3
INFO: modem_get_coord_addr: Getting addr cache: 0013 A200 415C 0F82
INFO: FSM_DATA_STATE: Operation NODE_TOKEN_DATAACK - Calling the DATAACK callback.
Gateway Logging
DEBUG:main:heartbeat
DEBUG:main:heartbeat
DEBUG:digi.xbee.reader:XBeeSerialPort '/dev/ttyS0' - RECEIVED - OperatingMode.API_MODE: 7E 00 19 90 00 13 A2 00 41 62 9B FB FF FE C1 11 21 12 30 54 43 30 4B 51 A4 8A 15 0E 9B
DEBUG:digi.xbee.reader:XBeeSerialPort '/dev/ttyS0' - RECEIVED - DATA: 0013A20041629BFB - 11 21 12 30 54 43 30 4B 51 A4 8A 15 0E
INFO:leawood.adapters.xbee:XBee received message 112112305443304b51a48a150e
INFO:leawood.adapters.token:detokenise: tokens 112112305443304b51a48a150e
DEBUG:leawood.adapters.token:detokenise: Getting the operation
DEBUG:leawood.adapters.token:detokenise: token_0 (operation): 17
DEBUG:leawood.adapters.token:detokenise: operation: READY
DEBUG:leawood.adapters.token:detokenise: token_0 (serial_id): 18
INFO:leawood.adapters.token:detokenise: serial_id: 305443304b51a48a150e
DEBUG:leawood.adapters.token:detokenise: data: bytearray(b'')
DEBUG:leawood.adapters.token:detokenise: data length: 0
INFO:leawood.adapters.token:detokenise: returning parsed tokens: {'operation': 'READY', 'serial_id': '305443304b51a48a150e'}
INFO:leawood.domain.hardware:Gateway receivied message READY, {}
INFO:leawood.services.messagebus:Calling the message handler >
INFO:leawood.domain.hardware:Operation READY
INFO:leawood.domain.hardware:The requesting DATA from node Node: serial_id: {self.serial_id}, address: {self.addr64bit}
INFO:leawood.adapters.xbee:Creating the remote device at 0013A20041629BFB
INFO:leawood.adapters.xbee:Sending from 0013A200415C0F82 to 0013A20041629BFB
INFO:leawood.adapters.xbee:message XBeeTelegram(serial_id='305443304B51A48A150E', operation='DATAREQ', payload=None)
INFO:leawood.adapters.token:telegram operation DATAREQ, seria_id 305443304B51A48A150E payload None
DEBUG:digi.xbee.sender:XBeeSerialPort '/dev/ttyS0' - SENT - OperatingMode.API_MODE: 7E 00 1B 10 2F 00 13 A2 00 41 62 9B FB FF FE 00 00 11 22 12 30 54 43 30 4B 51 A4 8A 15 0E AC
DEBUG:digi.xbee.reader:XBeeSerialPort '/dev/ttyS0' - RECEIVED - OperatingMode.API_MODE: 7E 00 07 8B 2F FF FE 00 00 00 48
DEBUG:main:heartbeat
DEBUG:digi.xbee.reader:XBeeSerialPort '/dev/ttyS0' - RECEIVED - OperatingMode.API_MODE: 7E 00 28 90 00 13 A2 00 41 62 9B FB FF FE C1 11 23 12 30 54 43 30 4B 51 A4 8A 15 0E 31 46 B6 F3 40 32 68 E8 9F 3A 33 A6 9B 44 3C EA
DEBUG:digi.xbee.reader:XBeeSerialPort '/dev/ttyS0' - RECEIVED - DATA: 0013A20041629BFB - 11 23 12 30 54 43 30 4B 51 A4 8A 15 0E 31 46 B6 F3 40 32 68 E8 9F 3A 33 A6 9B 44 3C
INFO:leawood.adapters.xbee:XBee received message 112312305443304b51a48a150e3146b6f3403268e89f3a33a69b443c
INFO:leawood.adapters.token:detokenise: tokens 112312305443304b51a48a150e3146b6f3403268e89f3a33a69b443c
DEBUG:leawood.adapters.token:detokenise: Getting the operation
DEBUG:leawood.adapters.token:detokenise: token_0 (operation): 17
DEBUG:leawood.adapters.token:detokenise: operation: DATA
DEBUG:leawood.adapters.token:detokenise: token_0 (serial_id): 18
INFO:leawood.adapters.token:detokenise: serial_id: 305443304b51a48a150e
DEBUG:leawood.adapters.token:detokenise: data: bytearray(b'1F\xb6\xf3@2h\xe8\x9f:3\xa6\x9bD<') DEBUG:leawood.adapters.token:detokenise: data length: 15 DEBUG:leawood.adapters.token:detokenise: data: 3146b6f3403268e89f3a33a69b443c DEBUG:leawood.adapters.token:detokenise: data: 3268e89f3a33a69b443c DEBUG:leawood.adapters.token:detokenise: data: 33a69b443c INFO:leawood.adapters.token:detokenise: returning parsed tokens: {'operation': 'DATA', 'serial_id': '305443304b51a48a150e', 'bus_voltage': 7.616000175476074, 'shunt_voltage': 0.0012199999764561653, 'load_current': 0.012000000104308128} INFO:leawood.domain.hardware:Gateway received message DATA, {'bus_voltage': 7.616000175476074, 'shunt_voltage': 0.0012199999764561653, 'load_current': 0.012000000104308128} INFO:leawood.services.messagebus:Calling the message handler >
INFO:leawood.domain.hardware:Operation DATA, posting to the repository
INFO:leawood.adapters.rest:Posting {"serial_id": "305443304B51A48A150E", "data": [{"bus_voltage": 7.616}, {"shunt_voltage": 0.00122}, {"load_current": 0.012}]}
INFO:leawood.adapters.rest:POST https://xxxxxxxxxxxxxxx-xxxxxxxxxxxxxxx.com/ords/leawood_dev/api/1.0/data_points
payload: {"serial_id": "305443304B51A48A150E", "data": [{"bus_voltage": 7.616}, {"shunt_voltage": 0.00122}, {"load_current": 0.012}]}
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): xxxxxxxxxxxxxxx-xxxxxxxxxxxxxxx.com:443
DEBUG:urllib3.connectionpool:https://xxxxxxxxxxxxxxx-xxxxxxxxxxxxxxx.com:443 "POST /ords/leawood_dev/api/1.0/data_points HTTP/1.1" 200 None
INFO:leawood.domain.hardware:Operation DATA, sending DATA_ACK
INFO:leawood.adapters.xbee:Creating the remote device at 0013A20041629BFB
INFO:leawood.adapters.xbee:Sending from 0013A200415C0F82 to 0013A20041629BFB
INFO:leawood.adapters.xbee:message XBeeTelegram(serial_id='305443304B51A48A150E', operation='DATAACK', payload=None)
INFO:leawood.adapters.token:telegram operation DATAACK, seria_id 305443304B51A48A150E payload None
DEBUG:digi.xbee.sender:XBeeSerialPort '/dev/ttyS0' - SENT - OperatingMode.API_MODE: 7E 00 1B 10 30 00 13 A2 00 41 62 9B FB FF FE 00 00 11 24 12 30 54 43 30 4B 51 A4 8A 15 0E A9
DEBUG:digi.xbee.reader:XBeeSerialPort '/dev/ttyS0' - RECEIVED - OperatingMode.API_MODE: 7E 00 07 8B 30 FF FE 00 00 00 47
DEBUG:main:heartbeat
DEBUG:main:heartbeat
DEBUG Debug level from a test output
INFO: =============== ready_data_collection_test ====================
DEBUG: node_set_callback: Setting callback for the operation: 01
DEBUG: node_set_callback: Setting callback for the operation: 02
DEBUG: node_set_callback: Setting callback for the operation: 03
DEBUG: node_set_callback: Setting callback for the operation: 04
DEBUG: node_set_callback: Setting callback for the operation: 05
INFO: ready_data_collection_test: READY DATAREQ - Data Collection Data Send and then DATAREQ
INFO: get_dataReq_response: Response NODE_TOKEN_DATAREQ
INFO: get_dataack_response: response: NODE_TOKEN_DATAACK
INFO: ready_data_collection_test: Testing the send operation for DATAREQ
DEBUG: node_set_callback: Setting callback for the operation: 01
DEBUG: node_set_callback: Setting callback for the operation: 02
DEBUG: node_set_callback: Setting callback for the operation: 05
DEBUG: node_message_to_stream: BEGIN for operation 23
INFO: Parsing the data for NODE_TOKEN_DATA
DEBUG: node_message_to_stream: END message_length: 28
INFO: expected_message_stream: 11 23 12 02 C0 2B E2 09 C0 2D E2 07 C0 31 00 00 28 41 32 00 00 80 3F 33 00 00 20 40
INFO: message_stream: 11 23 12 02 C0 2B E2 09 C0 2D E2 07 C0 31 00 00 28 41 32 00 00 80 3F 33 00 00 20 40
DEBUG: node_check: Checking for operation: 23, state: 04
DEBUG: node_fsm_execution: Executing for state: 04
DEBUG: NODE_RESET: count: 0
DEBUG: NODE_RESET: Setting next state as READY
DEBUG: node_fsm_execution: end - new state: 01
DEBUG: node_fsm_poller: BEGIN
DEBUG: node_fsm_poller: Polling…
DEBUG: Waiting for a response - 2000 ms…
DEBUG: node_fsm_execution: Executing for state: 01
DEBUG: FSM_READY_STATE: Send the READY signal and wait for the reply. count: 0
INFO: FSM_READY_STATE: Sending READY
DEBUG: node_message_to_stream: BEGIN for operation 21
DEBUG: node_message_to_stream: END message_length: 13
DEBUG: FSM_READY_STATE: Checking for a response
DEBUG: FSM_READY_STATE: A message has arrived: frame_type: 144, operation: 34 INFO: , data:
INFO: FSM_READY_STATE: Operation NODE_TOKEN_DATAREQ - Calling the DATAREQ callback.
DEBUG: node_data_collection: BEGIN
INFO: node_data_collection: transmitting the collected data
DEBUG: node_message_to_stream: BEGIN for operation 23
INFO: Parsing the data for NODE_TOKEN_DATA
DEBUG: node_message_to_stream: END message_length: 28
DEBUG: node_data_collection: END
DEBUG: FSM_READY_STATE: Called 01
DEBUG: FSM_READY_STATE: End with state: 02
DEBUG: node_fsm_execution: end - new state: 02
DEBUG: node_fsm_poller: Polling…
DEBUG: Waiting for a response - 2000 ms…
DEBUG: node_fsm_execution: Executing for state: 02
DEBUG: FSM_DATA_STATE: Waiting for ack on the transmitted data count: 1
INFO: FSM_DATA_STATE: A message has arrived
INFO: FSM_DATA_STATE: Operation NODE_TOKEN_DATAACK - Calling the DATAACK callback.
DEBUG: node_data_received: BEGIN
DEBUG: node_data_received: END
DEBUG: FSM_DATA_STATE: End with state: 00
DEBUG: node_fsm_execution: end - new state: 00
DEBUG: node_fsm_poller: END
node_test:240:ready_data_collection_test:PASS
INFO: =============== ready_node_intro_test ====================

SLEEP MODE

One of the important design goals was to put the system to sleep when in idle mode. This would be my first attempt at exploring this feature of the microcontroller. It would also mean having to put the XBee radio module to sleep as well.

To achieve this I employed the Period Interrupt Timer (PIT) which can be configured to trigger an interrupt every 30 seconds – RTC prescaler Oscillator set to 1kHz, the RTC prescaler Oscillator with a PIT period selection of 32768 cycles. My goal was to have the node sleep for up to 24 hours, so this was not going to suffice. I found a post that suggests calling the sleep_mode() API in a loop to create a longer sleep period. The loop would sleep for 30 seconds, way, check the iteration and go back to sleep. The average power usage over time for this quick wake-check-sleep is acceptable.

The next challenge was to put the XBee to sleep. There is a SLEEP_RQ pin. It was not enough to set this pin high to put the device to sleep. First the device needed to be configured using XCTU. The Sleep Mode (SM) option needed to be set to the appropriate sleep mode. In my case SM = 1 – Pin Sleep. Secondly pin D8 had to be configured to support the sleep signal D8 = SLEEP_REQUEST input. The table below shows the current draw during the various states of the node. A good portion of the 9 mA load during the idle time can be attributed to the power indicator LED that is installed on my development board.

STATELOAD

Idle

9 mA

READY

43 mA

NODE INTRO

43 mA

TOOLS

CONCLUSION

The sensor node was a complex project with many aspects. The envisaged device had to implement a finite state machine and adhere to a protocol as well as achieve other design goals. The Test Driven Development approach helped to break the system down to the various components and tackle them individually. I could first concentrate on the domain level aspects and leave the actual hardware implementation to later when I had more understanding of what it was I wanted from the interfaces to the hardware. The mock and testing frameworks were instrumental in creating uniform tests that did not have to consider the inner workings of their dependencies. I can’t say that I followed the Test Driven Design paradigm to the letter. My use of this technique is still work in progress. Overcoming the temptation to create code before the tests was hard and I certainly gave in a few times. I found it curious how my limited understanding of Unity and Cmock actually blocked me from establishing a list of tests as prescribed in the text. As my understanding on how to use these frameworks grew, then my ability to apply TDD also improved. I look forward to the next opportunity to apply the tools and techniques and to improve my skills in this area.

LESSONS LEARNED

There were many lessons learned on this project as there were many issues to resolve. Here is a rough list of the areas and what I was able to achieve.

  • Build process
    • the use of build profiles
    • How to run the build from the command line
    • Manages the object files to be compiled and linked
    • Helps to manage the target device
  • Unity
    • Building complex tests
  • Cmock
    • Generating mock code
    • configuring Cmock to generate specific APIs
  • EPROM
    • Reading and writing to the EEPROM
  • Serial ID
    • Reading the serial ID of the microcontroller
  • Sleep
    • Initiating and waking from sleep mode
    • forcing the XBee module into sleep mode
  • Macros
    • Creating function type C macros

NEXT STEPS

The tests are passing and the node is communicating with the gateway and the data is reaching the database. However the node is a collection of development boards and jumper wires. The end-to-end testing is positive but I still need a deployable device. The next step will be to package the hardware so that I can deploy it in the field. Further tests can then be developed to see how the gateway reacts to multiple sensor nodes.

I feel that I have only scratched the surface of Unity and Cmock. I feel there are improvements to the way in which I have implemented the tests.

REFERENCES

2 thoughts on “Developing a Remote Sensor Firmware using Test Driven Development

Leave a comment