I'm going to borrow ideas from when I developed for USB Audio on Android. USB uses setup bytes to create "control" transfers. These setup bytes inform the recipient what kind of data we're sending (control, isochronous, bulk) or what direction (host to slave, or vice versa). That's what I'm going to be doing with my NRF24L01+ transfers.
To future-proof this, I will need to implement multiple-transfer data segments if I ever need to send a long string to an Arduino module. The first two bits will determine whether the entire 16-bits is the only single message, final message, or whether there are more coming after this so we can do some simple data integrity checking. "Mid-message" and "final" types would not have other setup bits, it would just be one byte (with the two bits) telling them that they're mid-message or final type and the rest 15 bytes are for data. Like the USB, I would need to know whether this message is meant as a "get" to pull information from the Arduino to RPi (i.e. getting the temperature reading), or whether it's just to set values (i.e. Set thermostat tempertaure, turn lamp on, etc.). That leaves me with 5 bits left in this first byte, these will be used for recipient ID matching so we know who the message is targeted to. This gives me 32 devices (1 RPi + 31 Arduino modules)
1st byte setup:
- 1-2nd bit: Ordering of transfer (1 = "single", 2 = "initial", 3 = "mid-message", 4 = "final")
- 3rd bit: 0 = "get", 1 = "set"
- 4th-8th bit: Recipient device address (32-maximum node points)
Next, I need to be able to send arguments. For example, I have a sensor module with MPU-6050 which has sensors for 6-axes (3 for accelerometer and 3 for gyroscope) plus a temperature. That gives 7 possible values to poll from the RPi. I'm going to believe that 4 bits is enough parameters for each module (16 possible values to get and set). If I need more, that's what the multiple transfer is meant to future-proof.
2nd byte setup:
- 1-4th bit: Parameters or arguments to deliver (16 possible parameters)
- 5-6th bit: Predefined Timeout values (4 possible values)
- 7th bit: Delivered receipt? 1 = "require receipt", 0 = "no receipt"
- 8th bit: Left blank
One more information is needed, the size of the data. This will use a 16-bit integer so will use up two more bytes. I can go with 8-bits but that means I'm capped at data size of 256 bytes. Not sure how much I will need but better safe than sorry.
3rd-4th byte: Size of data in bytes.
Here is the struct of the message that contains all the setup information on the C++ file for the RPi. For now the MAX_DATA_SIZE is just 1, so I can only send 1 float value at the moment.
// Message type struct typedef struct Message { char block[16]; // The actual transfer block char data[MAX_DATA_SIZE]; int order; int size; // Size of data int param; // 0-15 (for preset arguments on receiving end) int type; // 0 = get, 1 = set int rcp_addr; // 5-bit address int timeout; // 4 possible preset timeout values int reply; } Message;
So according to what was designed above, the first setup byte would be constructed via:
// 0x01 = ensure 1 bit // 0x03 = ensure 2 bits // 0x0F = ensure 4 bits // 0x1F = ensure 5 bits int firstSetupBYte = ((0x03 & m.order) << 6) + ((0x01 & m.type) << 5) + (0x1F & m.rcp_addr); int secondSetupByte = ((0x0F & m.param) << 4) + ((0x03 & m.timeout) << 2) + (0x01 & m.reply << 1);
Then I'm going to copy these bytes into the block array of the Message struct and send it out.
memcpy(&m.block, &firstSetupByte, sizeof(char)); memcpy(&m.block[1], &secondSetupByte, sizeof(char)); memcpy(&m.block[2], &m.size, sizeof(short)); memcpy(&m.block[4], &m.data, sizeof(char) * m.size); printf("size: %i\n", sizeof(m.data)); printf("Sending float: %f\n", m.data[0]); printf("m.order: %i\n", m.order); printf("m.type: %i\n", m.type); printf("m.rcp_addr: %i\n", m.rcp_addr); printf("m.param: %i\n", m.param); printf("m.timeout: %i\n", m.timeout); printf("m.reply: %i\n", m.reply); printf("%s\n", bytesToBits(firstSetupByte)); radio.write( &m.block, sizeof(char) * 16);
On the Arduino side, I'm going to create the same struct:
// Message type struct typedef struct Message { char block[16]; float data[MAX_DATA_SIZE]; short size; // Size of data char order; char param; // 0-15 (for preset arguments on receiving end) char type; // 0 = get, 1 = set char rcp_addr; // 5-bit address char timeout; // 4 possible preset timeout values char reply; };
After receiving the radio using:
Message m; radio.read( &m.block, sizeof(char) * 16);
We need to decode the setup bytes and this is done by the following snippet. This is basically just a reverse of the RPi's code.
m.order = (0x03 & (m.block[0] >> 6)); m.type = (0x01 & (m.block[0] >> 5)); m.rcp_addr = (0x1F & (m.block[0] >> 0)); m.param = (0x0F & (m.block[1] >> 4)); m.timeout = (0X03 & (m.block[1] >> 2)); m.reply = (0x01 & (m.block[1] >> 1));
Here's my full loop code for my Arduino sketch also outputting the entire 16 bytes in binary form.
void loop() { unsigned long got_time; if( radio.available()){ while (radio.available()) { // Get the incoming message radio.read( &m.block, sizeof(char) * 16); // Copy the first and only block of data (offset by 4 setup bytes) memcpy(data.mFloat, m.block + 4, 4); Serial.print(F("Message.block[16]: \n ")); Serial.print(*(byte *)m.block[0], BIN); // Output all 16 bytes in binary for(int i = 1; i < 16; i++){ Serial.print(" "); Serial.print(*(byte *)(m.block[i]), BIN); } Serial.print("\nValue of float: "); Serial.println((*(float *)(data.mFloat))); m.order = (0x03 & (m.block[0] >> 6)); m.type = (0x01 & (m.block[0] >> 5)); m.rcp_addr = (0x1F & (m.block[0] >> 0)); m.param = (0x0F & (m.block[1] >> 4)); m.timeout = (0X03 & (m.block[1] >> 2)); m.reply = (0x01 & (m.block[1] >> 1)); Serial.print("m.order: "); Serial.println((String)(int)m.order); Serial.print("m.type: "); Serial.println((String)(int)m.type); Serial.print("m.rcp_addr: "); Serial.println((String)(int)m.rcp_addr); Serial.print("m.param: "); Serial.println((String)(int)m.param); Serial.print("m.timeout: "); Serial.println((String)(int)m.timeout); Serial.print("m.reply: "); Serial.println((String)(int)m.reply); Serial.print("\n\n"); } radio.stopListening(); // First, stop listening so we can talk radio.startListening(); // Now, resume listening so we catch the next packets. } } // Loop
Testing this code (sending from the RPi side) sending two different setups:
pi@raspberrypi ~/rf24libs/RF24/examples_RPi $ g++ -Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s -Wall -I../ -lrf24-bcm send.cpp -o send && sudo ./send pp size: 4 Sending float: 293.100006 m.order: 2 m.type: 0 m.rcp_addr: 7 m.param: 9 m.timeout: 1 m.reply: 0 00000000 10000111 pi@raspberrypi ~/rf24libs/RF24/examples_RPi $ g++ -Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s -Wall -I../ -lrf24-bcm send.cpp -o send && sudo ./send pp size: 4 Sending float: -99.989998 m.order: 0 m.type: 1 m.rcp_addr: 25 m.param: 2 m.timeout: 0 m.reply: 1 00000000 00111001
And I get, from my Arduino serial monitor, proper values!
Message.block[16]: 10000111 1011 1010011 0 101011 1110011 10101111 0 0 0 0 0 0 0 0 0 Value of float: 293.10 m.order: 2 m.type: 0 m.rcp_addr: 7 m.param: 9 m.timeout: 1 m.reply: 0 Message.block[16]: 10111000 10111000 1010011 0 11011011 10010110 11111011 111110 0 0 0 0 0 0 0 0 Value of float: -99.99 m.order: 0 m.type: 1 m.rcp_addr: 25 m.param: 2 m.timeout: 0 m.reply: 1
Pretty exciting stuff when you get things going! In a few days I shall attach my DC relay with a lamp and try to remotely turn the lamp on/off.
No comments :
Post a Comment