Radio communication part 3

Radio should be used when transmitting data over long distances, or when cables are impractical or undesirable. This is the third in a series of articles that use XBee S2 radios to build a wireless sensor network. In this article data will be stored on a microSD card at a remote device. It uses code developed in writing_to_a_microsd_card and the other articles in this series.

wireless sensor network

Figure 1: Wireless sensor network using XBee S2 radios (a.k.a XB24-ZB)

Why the need to store data at a remote device? As the number of remote devices increase and the rate of data transfer increases, the chance of packet collisions also increases. For hourly or daily temperature readings, for example, very simple devices can be used that spend much of their time in sleep mode. However, for many engineering applications, data often needs to be recorded at 100Hz or more. Thus data will be stored on a microSD card local to the sensor. Completed log files can then be streamed from each device in turn.

If sensor data is stored local to the device, why the need to transfer it? One reason is to be able to see the data as it is recorded, to ensure the devices are working. This also allows any problems with the sensors to be diagnosed and fixed before the test is complete. Another reason is that the device may fail or be unrecoverable.

Parts list

For this article, the parts list from:

will be used.

Extending the API library

The firmware developed in parts 1 and 2 (see html, html) was extended to include data packets. In particular, the following packet types were added:

  • ZigBee Transmit Request (DataTx)
  • ZigBee Receive Packet (DataRx)
  • ZigBee Transmit Status (DataResponse)

Up to 8 bytes of data may be sent using the DataTx packet type. When a ZigBee receives a DataTx packet, it sends a DataRx to USART with the data and other information. It then sends a DataResponse packet back to the co-ordinator device to signal whether or not the transmission was successful.

The API packet library was written in such a way that it could be used for both the PC software and AVR firmware. However, while Python could be used for the PC interface, the ATmega328p required a hex file compiled from solely C/C++. This file required, in particular, an interrupt routine that would receive DataRx packets, parse them and write the resulting data to the microSD card.

Hardware

After writing the seven packet types, the compiled binary was about 8K. Along with the 8K required for the SD-reader library, an MCU with more than 16K flash was required. Along with this was the requirement for both SPI (for the microSD) and USART (for the radio). The ATmega328p was chosen with this in mind.

As for the rest of the electronics, it was a simple process of wiring up the different breakout boards.

Debugging

The first difficulty was USART transmission from the ATmega328P to the microSD card. Eventually, the firmware written for avr_serial_communication was refactored for use on an ATmega328p MCU. After verifying, this code was then used transmit the received radio data.

After sending the first packet, the data over USART was 0x7e 0x00 0x13 0x2c. This was too short, and the packet type 0x2c was not valid. Printing the "Receiver Error Flags" (Frame error, FE0, Data Overrun, DOR0 and Parity error, UPE0) showed that there was a data overrun error by the third received character.

To solve this, the ISR(USART_RX_vect) function was stripped down to it's absolute bare minimum:

#define USART_RX_BUFFER_SIZE 32
#define UART_RX_BUFFER_MASK (UART_RX_BUFFER_SIZE - 1)

static volatile unsigned int UART_RxBuf[USART_RX_BUFFER_SIZE];
static volatile unsigned char UART_RxHead;

ISR(USART_RX_vect)
{
    while (!valbit(UCSR0A,RXC0));
    USART_RxHead = (USART_RxHead + 1) & USART_RX_BUFFER_MASK;
    USART_RxBuf[USART_RxHead] = ((UCSR0A << 8) | UDR0);
}

This fixed the buffer overflow error, and allowed the received data to be used later.

The last and by far the hardest problem was to do with memory allocation. The API library compiled and ran fine on a PC, but the ATmega328p has 2K of SRAM. Also, due to the different way in which memory is allocated on an MCU there is a possibility of fragmentation and stack-heap collisions. In technical terms this is called "running out of memory".

Weird and wonderful things happened when the MCU ran out of memory. Variables were overwritten, arrays were overrun, and objects that should have been initialized were not. This in turn led to results like corruption of the microSD card filesystem. The variety of the problems made the bug very difficult to track down. For MCUs, it is safest to:

  1. statically allocate all memory at startup, and
  2. free all memory at shutdown.

So the firmware was rewritten to do this.

Result

A DataTx with value "TxData" was sent from the PC to the remote radio:

Transmit:
  Type: DataTx
  Frame ID: 0x52
  Options: 0000
  Destination address: 000000000000ffff
  Network address: fffe
  RF data: TxData

Response:
  Type: DataResponse
  Frame ID: 0x52
  Retry count: 0000
  Delivery status: 0000
  Discovery status: 0000
  Network address: fffe

Note that the network address was 0xfffe and not 0x0b69, which was the remote radio's network address. This is because the response packet includes the network address the DataTx packet was sent to, not the address of the radio that received it. The delivery status and discovery status correspond to "Success" and "No Discovery Overhead" respectively.

The remote radio then sent a DataRx packet over USART to the MCU. The MCU parsed the packet, retrieved the data and wrote it to a file on the microSD card.

Discussion

This was by far the hardest step in the series so far. To get to this stage, not only was a USART library written, but the entire API library was rewritten with static memory allocation. Also, running out of memory on the MCU caused some very strange bugs that took a long time to discover and resolve.

After successfully sending one packet, a brief attempt at finding the bandwith was made. To do this, the string "TxData" was sent 100 times, which took about 50 seconds to write to the microSD card. This is about 120bps, which is quite poor. The USART is currently operating at 9,600 bps and according to the datasheet the radios are capable of an RF data rate of 250,000 bps. It is not clear what is causing the bottleneck, but finding it will be the next task.

Conclusion

Third step complete! Data has been successfully transferred from a PC to a remote microSD card over radio. At the moment, the system has a bandwidth of about 120bps, but this will almost certainly be improved.

Update: first attempte at increasing bandwidth

Three things were changed to improve bandwidth.

  1. the baud rate of the local radio was increased to 115.2k
  2. the baud rate of the remote radio and MCU was increased from 9600 to 250k
  3. the amount of data per packet was increased to 64 bytes

Using these settings, 100 such packets took 43s to transmit, corresponding to a data rate of about 1.2 kbps (compare that to the rated speed of 250 kbps). This was primarily due to the increase in data per packet. To remove errors in transmission, the Rx buffer had to be increased to 128 bytes.

It is possible to send a DataTx packet and receive a response in an average of 0.08 ms. However, a pause of 2 seconds was required to allow the data to be properly written to the microSD card. This means the bottleneck is in the firmware.

It is difficult to profile a program that runs on an MCU, but it seems that is what is required to optimize the code. The only other method is to guess, or use "premature optimization".