The most important development that the radio needs is more testing in real-world applications. I don’t have much to add about that.
I have chosen not to implement some of the features offered by the radio, but these features could be useful nevertheless:
- Selective ack – The radio supports disabling Enhanced Shockburst on a packet-by-packet basis. This would be useful for broadcasting one packet to multiple stations without having the acks interfering with each other. I tried to get this working, but my code had packets failing even when auto-ack was enabled, and it wasn’t important enough to spend a lot of time on.
- Carrier detect – the nRF24L01 product specification describes how to output a carrier signal, which is a signal made up entirely of logic 1’s (the signal is slightly modulated by each packet’s preamble). In conjunction with the carrier detect bit in the CD register, this can be used to determine if a station is up and running. For example, this can be used to determine if a station is the first one to start up, which could be useful in the network layer described below. The problem is that while a station is broadcasting a carrier, it can’t receive packets.
- Tx FIFO – the current driver doesn’t take advantage of the Tx FIFO, in that only one packet can be loaded into the FIFO at once. Taking full advantage of the Tx FIFO would be useful if one needed to maximize throughput, but students typically don’t need to do that so this is a low priority.
- Dynamic payload – using the dynamic payload feature would make the driver a little more flexible. The payload length could be determined by the length argument to the transmit function, and any structure could be sent efficiently to any address (this would also be useful for the network layer). Again, this is only useful for maximizing throughput. Currently the driver just always uses a 32-byte packet instead of allowing the programmer to vary the packet width.
- Ack payload – this feature would be useful if a station always has a response to a packet ready, such as a sensor or status update. This would be equivalent to the radio returning the contents of its STATUS register whenever the master shifts in an instruction over SPI.
RTOS Radio Task
One option for the next major revision to the radio driver is to change it from a passive layer between the application and the radio to an active task running on the RTOS. A radio task would allow tasks to write packet payloads to buffers in the radio driver, so that when the driver task gets a time slice and has a packet in the send buffer, it sends the packet. The radio task would also buffer received packets, and signal tasks that are waiting for packets on the receiving Rx pipe. The task interface would be similar to the one described in the network layer section below, but without additional headers or routing; it would make a good first step towards the network layer. The driver already assumes that the application will implement a radio task when it is using the RTOS. Ideally however, the radio task would be hidden by the RTOS and present an interface to the application, so that more radio details could be hidden.
The radio task should be a periodic task, not system or round robin A system task would ensure that packets are transmitted as soon as possible and that tasks waiting for packets are notified as soon as the packet comes in. On the other hand, transmitting a packet can take up to 7.6 ms, which could easily cause interrupted periodic tasks to exceed their time limits. A round robin task would not cause problems with periodic tasks, but it might get starved if higher-priority tasks are busy. Also, if the round robin radio task is pre-empted while it is reading or writing payloads to the radio then the CE pin will be low and the radio will be unavailable until the radio task can complete the SPI transaction and re-enable the radio. This will increase the likelihood of the radio dropping packets. Using a periodic task would ensure that packets are processed regularly, and if the stations are synchronized then a distributed system can synchronize radio transmissions (e.g. to ensure that the channel is clear when transmitting).
RTOS Network Layer
The computer science mechatronics classes are shifting focus away from solo robots and toward distributed systems. Integrating the radio with the RTOS to provide wireless networking functionality would make distributed systems easier to implement by adding support for basic sockets. I haven’t had time to work on an implementation of such a network layer, but I’ve put a bit of thought into it. There are two major design issues to be considered: the routing mechanism, and the interface to the application.
We want to address stations with sockets, made up of a logical address and a port. Maintaining a network of logical addresses will need some sort of routing table to translate between the logical addresses and the physical addresses (i.e. the radios’ Rx pipes). This could be as simple as a hard-coded table stored at each station, but ideally it would be more adaptable to stations being dynamically added to and removed from the network. An ambitious system could form a long-range mesh network by rebroadcasting packets, similar to how the Internet Protocol routes traffic from one node to another, but rebroadcasting is well beyond the scope of the routing system’s initial design. It might be more appropriate to say address resolution instead of routing at this stage, but I’m going to stick with routing.
One approach is to build routing tables on all stations as they join the network. I see two options: having every station send the new station the list of addresses that the existing stations have created, or having a single station responsible for updating new stations. The former option distributes the work of updating the new station between all the existing stations, but it would be hard to coordinate the updates so that the radio transmissions do not conflict with each other. The latter option would not have any packet conflicts, but might put an undue load on the transmitting station if there are a lot of addresses that need to be sent. If each station in the network has its periodic radio tasks synchronized, then the distributed approach might be easier to implement.
When a station is disconnected from the network, some distributed applications might be able to switch the station’s tasks to another operational station. This can be accomplished by enabling a pipe on the backup station using the dropped station’s physical address, so that packets sent to the old station’s logical address end up on the backup. If the old station comes back up, then when it tries to request its address the routing system will deny it (since the address is still in use) and it can enact a protocol to get its address back from the backup. Even if the application can’t switch the disconnected station’s tasks to another station, it might still be useful to have a backup station to accept the packets that would otherwise be dropped.
The RTOS would be able to provide a somewhat different interface than that of the raw radio driver. It would have system calls for creating, listening on, and sending to a socket:
SOCKET Socket_Create(uint8_t address, uint8_t port)
The create function verifies that the address is not in use on the network (i.e. it’s not in the routing table), and that the port is free on the local station. If the socket can be created, then the RTOS sends the logical address and the physical address to the rest of the network, so that the other stations can add it to their routing tables. The return value of Socket_Create is just a 16-bit value that contains the logical address and port number.
void Socket_Listen(SOCKET* sock)
The listen function notifies the RTOS that a task wants to listen on the given socket. The RTOS’s radio task copies all received packets into a local buffer (probably it would have buffer space for multiple packets). At every tick, the RTOS compares each packet in the buffer against the list of sockets that have tasks listening to them. If the packet’s header matches the address and port of a socket on which a task is listening, then the packet is left in the buffer for the task to retrieve. The listen function doesn’t block, it just registers the task as a listener on a socket; the wait function allows a task to block.
A task calls the wait function to wait until it receives a packet on its socket. The RTOS places the task in a listen queue, and the task stays there until the RTOS receives a packet on the task’s socket. This assumes that a task can listen to only one socket, which seems like a reasonable limitation to impose. If there is a packet ready for the task’s socket then the wait function returns immediately. By separating the listen functionality from the wait functionality, the RTOS will know not to drop a task’s packets even if the task is not actively waiting for the packet.
SOCKET Socket_Read(void* taskbuffer, uint8_t len)
When the wait system call returns, it indicates to the task that the RTOS has a packet ready for the task to read. The task calls the read system call to get the packet. When this function is called the RTOS copies the packet payload to the supplied buffer pointer and removes the packet from the RTOS’s buffer. The read function returns the socket by which the sender can be addressed. Alternately, the read function could return the number of bytes that were transmitted and the socket value could be retrieved with another function, I’m not sure which is better.
SOCKET_SEND_RESULT Socket_Send(uint8_t address, uint8_t port, void* buffer, uint8_t len)
The send system call transmits some data to the socket identified by the given address and port. The RTOS translates the 8-bit logical address into a 5-byte radio address using its routing mechanism, creates a packet, and sends it. The function returns a status code to indicate a dropped packet, an invalid address, or a successful transmission. The RTOS might add its own high-level ack packet to verify that the packet was successfully delivered to a task, but that’s getting up into the transport layer and I think it’s fair to ignore that problem for now (note that the RTOS could not use the nRF24L01’s ack payload feature for this, because it wouldn’t have the data ready to send until after the packet was received).
The network packet consists of a 4-byte header and up to 28 bytes of payload. The header contains the destination address and port, and the sender address and port. The packet structure would be hard-coded to 32 bytes, like the structure the current driver uses (see the driver page).