Differences

This shows you the differences between two versions of the page.

Link to this comparison view

piep:examples:testradio [2019/04/03 18:36] (current)
Line 1: Line 1:
 +[[kes:​gettingstarted|Back to Getting Started]]
  
 +[[kes:​examples|Back to Examples]]
 +=====Test Radio Example=====
 +
 +This example demonstrates setting up communication(sending and receiving data frames) between two radios using KoliadaES and its radio API. You will be requiring two radio chips for this example (you can use  CC2541s or CC2530s).
 +  ​
 +
 +This examples helps you in learning APIs and application firmware that can be used to
 +
 +
 +  ​
 +1. To figure out if nodes on the same channel can communicate with each other. ​    
 +  ​
 +2. Once they are communicating with each other, they can ping each other continuously. ​
 +   
 +3. There are different data frames that these nodes send to each other. Each frame contains the message packaged in  a different way. You will be learning about these frames further in detail as you proceed with the tutorial.
 +
 +
 +Before you proceed with this tutorial, you must have IAR and Flash programmer setup. This tutorial is currently based on the TI CC2541 or CC2530 which uses the 8051. You also need to have two nodes(2541 or 2530 radios) before you can proceed with this example.
 +
 +===== Step 1 =====
 +
 +You need to flash the **koliada hex file** on the **2541** using the Flash programmer.
 +Before flashing you need to set the latest **koliada SDK** using the **setsdk.exe** file from the repository named **CC8051 SDK**.
 +
 +You can always find the latest SDK and hex file under the repository named **CC8051 SDK**.
 +Now run the **setsdk.exe** and set the latest sdk. Then, you can flash the latest hex file using the flash programmer.
 +
 +
 +Choose the latest sdk hex file and hit perform actions to flash the hex file. 
 +
 +**Note:** After flashing the hex file, you must know that the board config must be flashed only if you are using any I/O pins. In this particular example there are no I/O configurations and hence you would be moving on to the next step of flashing the Test radio application firmware.
 +
 +
 +===== Step 2 =====
 +
 +The test radio application firmware can be downloaded from the repo CC8051 SDK. Once you pull the repo, to open the project workspace under IAR. You need to navigate to CC8051 SDK / examples / iar_EW6.5 / and open the **CC8051.eww** with IAR embedded workbench. Select the TestRadio as active project. ​
 +
 +In this example we should be testing the communication between two nodes and hence the first node sends a ping frame to the second node(neighbor node) on the same channel and the second node sends ping response(gnip) frame back to the first node. 
 +
 +Before flashing the example firmware to the cc2541 or cc2530, you need to be sure which files need to be included in the build. ​
 +
 +When you flash the first node here make sure you have the ping message build files and settings selected. You can choose the required build from the project explorer as shown below.
 +
 +{{em:​pingbuild.png?​600x300}}
 +
 +You can notice that the gnip files excluded from the build.
 +
 +
 +Now  the project can be compiled and flashed by using the download and debug button.. ​
 +
 +{{kes:​public:​iar:​debug.png?​800x800}}
 +
 +Once you flash the ping build into the first node(2541 chip). You should be seeing this on term (serial monitor).
 +
 +{{em:​pingop2.png?​800x800}}
 +
 +Keep this term window open to notice what happens when the gnip build is flashed into the second node.
 +
 +Now the node flashed with ping code continuously sends ping frames and waits for any node to respond to the ping.      ​
 +Similarly, for the second node you should be having the gnip response build files and settings selected as shown below.
 +
 +{{em:​gnipbuild.png?​800x400}}
 +
 +You can now notice here that the ping files are excluded from the build. Now  the project can be compiled and flashed by using the download and debug button.
 +
 +**OUTPUT ON TERM**
 +
 +After gnip is flashed into the second node, you should be seeing this on term (serial monitor) of node 1(ping node).
 +
 +{{em:​pingoutput2.png?​800x400}}
 +
 +After gnip is flashed into the second node, you should be seeing this on term (serial monitor) of node 2(gnip node).
 +
 +{{em:​gnipoutput2.png?​800x400}}
 +
 +===== Explanation =====
 +
 +
 +The main function calls 2 important initialization functions.
 +The WaitEvent listens for events to happen. In this case it waits for events like timer events to send the ping messages and rxEvents when response frames are recieved for the ping messages sent, etc.
 +
 +The initRadio which is used to initialize the radio.
 +
 +The initIO is used to set the RF data settings such as which channel the radio should operate on and then setting frame IDs, setting up timers to send the ping messages etc. 
 +
 +
 +<code c>
 +int main(int args, char **argv)
 + {
 + print("​%s\n\n",​ _versionStr);​
 +
 + // init the radio
 + void initRadio();​ initRadio();​
 +
 + // set up for RF data
 + void initIO(); initIO();
 +
 + // ready to rock'​n'​roll
 + print("​Ready...\n"​);​
 +
 + // wait for system events
 + WaitEvent(0,​ 0);// never returns
 + }
 +</​code>​
 +
 +We can walkthrough io.c, ui.c and rxEvent.c which give clear picture of this example project. ​
 +
 +We will first have the perspective of the ping node.
 +
 +==== Ping node explanation ====
 +
 +  The nodeId is assigned with a random number by using the randomByte();​ api 
 +
 + The **[[kes:​api:​radio|rfSetChannel]]** sets the channel to 13. You can choose any chaneel between 13-24.Then the frame ID and the frame signature are set using **[[kes:​api:​radio|rfSetRxEvent]]** and 
 +**[[kes:​api:​radio|rfIocntl(kRfSetFrameSig,​...)]]**. ​
 +
 +The  **[[kes:​api:​objectclass|objectCreate(gnipEvent)]]** is then used to create a gnip Event(receive Event) that happens whenever a gnip repsonse frame is sent by the receiver node(gnip node).
 +
 +On this event (**[[kes:​api:​eventclass|OnEvent(resendTimer,​ (HANDLER) resendHandler)]]**) the resend handler will be called.
 +
 +The  **[[kes:​api:​objectclass|objectCreate(resendTimer,​ kIntervalTimer)]]** is then used to create a timer object that sets an interval timer(gnip response timeout). ​
 +This is time interval between two successive pings.
 +
 + On this timer event (**[[kes:​api:​eventclass|OnEvent(gnipEvent,​ (HANDLER) gnipHandler)]]**) the gnip handler will be called.
 +
 +The **[[kes:​api:​radio|rfEnableRx]]** starts listening for incoming frames.
 +
 +The resend timer event is forced for the first time using **[[kes:​api:​eventclass|PostEvent()]]** ​ to get started with sending the first ping message. The following ping messages will be sent in sync with the interval timer.
 +
 +When the Event is posted first the resend handler is called. ​
 +<code C>
 +
 +void initIO()
 + {
 + printf("​Ping - ");
 +
 + // initial set up
 + nodeId = randomByte();​
 + rfSetChannel(13);​ //​ RF channel
 + rfSetRxEvent(rxEvent,​ rxBuf, sizeof(rxBuf));​ //​ set the frame ID
 + rfIocntl(kRfSetFrameSig,​ FRAME_ID0 << 8 | FRAME_ID1);​ //​ frame ID
 +
 + // gnip event handler
 + objectCreate(gnipEvent);​
 + OnEvent(gnipEvent,​ (HANDLER) gnipHandler);​
 +
 + // response timeout timer
 + objectCreate(resendTimer,​ kIntervalTimer);​
 + OnEvent(resendTimer,​ (HANDLER) resendHandler);​
 +
 + print("​Node ID: %02x\n",​ nodeId);
 +
 + // start listening
 + rfEnableRx();​
 +
 + // start sending...
 + PostEvent(resendTimer);​
 + }
 +
 +</​code>​
 +
 +The resend handler calls the function **sendPingFrame();​**
 +
 +<code C>
 +
 +DefineTimer(resendTimer);​
 +void resendHandler()
 + {
 + // send next frame
 + sendPingFrame();​
 + }
 +
 +</​code>​
 +
 +We can walkthrough the sendPingFrame() function now.
 +
 +First the data frame to be sent is packed into a buffer. The Frame size can be altered by changing the kFrameSize. The Frame will consist of important bits that represent FrameID, Frame Type, node ID, the build details and rssi. Once the data is packed into the frame(buffer). The **[[kes:​api:​radio|rfSendFrame]]** is used to send a frame(message) to the other nodes.
 +
 +<code C>
 +static void sendPingFrame()
 + {
 + // send a ping frame
 + byte len;
 + static byte txBuf[kFrameSize];​
 +
 + // pack up the BDM
 + len = 0;
 + txBuf[len++] = (UInt8) FRAME_ID0;
 + txBuf[len++] = (UInt8) FRAME_ID1; // magic
 + txBuf[len++] = (UInt8) PING_FRAME;//​ frame type
 +
 + txBuf[len++] = (UInt8)nodeId;​  ​ // our id
 +#if 0
 + txBuf[len++] = (UInt8)BOARD_MAJOR;​
 + txBuf[len++] = (UInt8)BOARD_MINOR;​
 + txBuf[len++] = (UInt8)BOARD_CONFIG;​
 +#endif
 +
 + txBuf[len++] = (UInt8) (build >> ​ 0) & 0xff;
 + txBuf[len++] = (UInt8) (build >> ​ 8) & 0xff;
 + txBuf[len++] = (UInt8) (build >> 16) & 0xff;
 + txBuf[len++] = (UInt8) (build >> 24) & 0xff;
 +
 + txBuf[len++] = (UInt8) rssi; rssi = 0;
 +//​ txBuf[len++] = (UInt8) buttons;​ buttons = 0;
 +
 +#if 0
 + txBuf[len++] = (UInt8) (Vdd >> 0) & 0xff;
 + txBuf[len++] = (UInt8) (Vdd >> 8) & 0xff;
 + txBuf[len++] = (UInt8) (Vbat >> 0) & 0xff;
 + txBuf[len++] = (UInt8) (Vbat >> 8) & 0xff;
 +#else
 + txBuf[len++] = (UInt8) 0;
 + txBuf[len++] = (UInt8) 0;
 + txBuf[len++] = (UInt8) 0;
 + txBuf[len++] = (UInt8) 0;
 +#endif
 +
 + assert(len <= kFrameSize - 2);
 +//​ dump(txBuf,​ len, 1);
 +
 + // send it out
 + rfSendFrame(txBuf,​ len);
 +
 + // and reset resend timer
 + cmSetTimer(resendTimer,​ TICKS((randomByte() % 10) + 5));
 +
 + static byte i; char c[4] = {'​|',​ '/',​ '​-',​ '​\\'​};​
 + debug("​%c\b",​ c[i++ % 4]);
 + }
 +</​code>​
 +
 +Now that the message has been sent to other nodes we should be receiving a gnip response. Now we can take a look on how the gnip response is handled.
 +
 +We need to walkthrough three things in particular.
 +
 +The rxEvent.c which receives the response and filters the received frame depending on its type i.e. ping frame or gnip frame
 +Secondly we will take a look at the gnipRx function in io.c that is called by the rxEvent handler when it receives a gnip frame.
 +Then we can take a look at the gnip handler that is called when a gnipEvent is forced from within the gnipRx function.
 +
 +The rxEvent(byte *buf, byte len) is called when a frame is received. When a frame is received, the Rx is disabled using **[[kes:​api:​radio|waitRx]]** until the received frame is processed. ​   The frame id can be extracted from the 3 byte of the the received frame buffer(buf[2]) and hence the frame id is identified and the functions pingRx and gnipRx are called with respect to the frame id. In this case, we will receive a gnip response frame from the receiver node and hence the gnipRx() is called. ​
 +
 +<code C>
 +#include "​Koliada.h"​
 +#include "​ping.h"​
 +
 +void pingRx(byte *buf, byte len);
 +void gnipRx(byte *buf, byte len);
 +
 +void rxEvent(byte *buf, byte len)
 + {
 + // running on interrupt!
 + //
 + // buf[0] = RSSI
 + // buf[1] = CORR
 + // buf[2] = protocol type
 +
 + // disable rx
 + waitRx();
 +
 + // pass it up to the application
 + switch (buf[2])
 + {
 + case PING_FRAME:
 + pingRx(buf,​ len);
 + break;
 +
 + case GNIP_FRAME:
 + gnipRx(buf,​ len);
 + break;
 + }
 + }
 +</​code>​
 +
 +The gnipRx function is called from the rxEvent handler when a gnip frame is recieved. Once the gnipRx is called, the 4th byte from the received frame which is buf[3] contains the node ID. This node ID value must match the current ping(sender) node's ID. 
 +
 +If the node IDs do not match, then the frame is not the expected one and hence the Rx is enabled to listen for messages again using **[[kes:​api:​radio|postRx]]**.
 +
 +But, if the node IDs do match, then it is the expected reply and so the the gnipEvent is posted using 
 +**[[kes:​api:​eventclass|PostEvent()]]**.
 +
 +<code c>
 +void gnipRx(byte *buf, byte len)
 + {
 + // buf[n]
 + // where n=
 + //   0: rssi
 + //   1: corr
 + //   2: protocol
 + //
 + //   3: nodeId
 + //   4: rssi
 +
 + if (buf[3] != nodeId)
 + {
 + // not our reply!
 + // enable rx
 + postRx();
 + return;
 + }
 +
 + // post the gnip event
 + PostEvent(gnipEvent,​ buf, (word) len);
 + }
 +</​code>​
 +
 +The gnip handler unpacks the rssi from the received frame. The rssi values from the received frame are then printed out. After processing the required rssi values, the Rx is enabled again using **[[kes:​api:​radio|postRx]]**. The sendPingFrame function is again called to send the next ping frame out.
 +
 +
 +<code c>
 +
 +DefineEvent(gnipEvent);​
 +void gnipHandler(EVENT e, char *buf, word len)
 + {
 + rssi = buf[0]; ​       // gnip frame rssi
 + int rssiDiff = (int) buf[0] - (int) buf[4];
 +
 + debug("​rssi:​ %d/%d %u\n", (signed char) buf[4], (signed char) buf[0], abs(rssiDiff));​
 +
 + // enable rx
 + postRx();
 +
 + // send next frame
 + sendPingFrame();​
 + }
 +
 +</​code>​
 +
 +==== Gnip node Explanation ====
 +
 +The **[[kes:​api:​radio|rfSetChannel]]** sets the channel to 13. You can choose any channel between 13-24.Then the frame ID and the frame signature are set using **[[kes:​api:​radio|rfSetRxEvent]]** and 
 +**[[kes:​api:​radio|rfIocntl(kRfSetFrameSig,​...)]]**. ​
 +
 +The  **[[kes:​api:​objectclass|objectCreate(displayTimer,​ kIntervalTimer,​ TICKS(1000));​]]** is then used to create a timer object that sets an interval timer with an interval of one second. Hence this timer event will fire every second.
 +
 + On this timer event (**[[kes:​api:​eventclass|OnEvent(displayTimer,​ (HANDLER) displayHandler)]]**) the display handler will be called.
 +
 +
 +The **[[kes:​api:​radio|rfEnableRx]]** starts listening for incoming frames.
 +
 +<code c>
 +void initIO()
 + {
 + printf("​Gnip - ");
 +
 + // mfg & test radio
 + rfSetChannel(13);​
 + rfSetRxEvent(rxEvent,​ rxBuf, sizeof(rxBuf));​
 + rfIocntl(kRfSetFrameSig,​ FRAME_ID0 << 8 | FRAME_ID1);
 +
 + objectCreate(displayTimer,​ kIntervalTimer,​ TICKS(1000));​
 + OnEvent(displayTimer,​ (HANDLER) displayHandler);​
 + cmStartTimer(displayTimer);​
 +
 + // start listening
 + rfEnableRx();​
 + }
 +</​code>​
 +
 +  The display handler prints important details every second. These details include the number of pings, board config used, the build version, Vdd and Vbat. It also prints the rssi values belonging to the node that sent a ping frame most recently. ​
 +All these values are unpacked from the most recently received ping frame which is stored in lastPing. We will be seeing futher how the ping frames are stored in the lastPing buffer.
 +  ​
 +<code c>
 +DefineTimer(displayTimer);​
 +void displayHandler()
 + {
 + word pings = pingCount; pingCount = 0;
 + teta build;
 + word Vdd, Vbat;
 + static byte line;
 + byte *buf = lastPing;
 +
 + build =
 + ((teta) buf[ 7]) << ​  0 |
 + ((teta) buf[ 8]) << ​  8 |
 + ((teta) buf[ 9]) << ​ 16 |
 + ((teta) buf[10]) << ​ 24 ;
 +
 + rssi = buf[0]; // ping frame rssi
 +
 + Vdd =  ((word) buf[13]) << ​  0 |
 + ((word) buf[14]) << ​  8 ;
 + Vbat = ((word) buf[15]) << ​  0 |
 + ((word) buf[16]) << ​  8 ;
 +
 + if (line++ % 16 == 0)
 + {
 + debug("​\n ​      id : Board Config ​         RSSI    Vdd  Vbat\n"​);​
 + //  0000: v1.1.4.3e3ac99e 38/0 00 3544  826
 + }
 + debug("​%3d - %04x: v%u.%u.%u.%08lx ", pings, buf[3], buf[4], buf[5], buf[6], build);
 + debug("​%d/​%d %02x ", (signed char) rssi, buf[11], buf[12]);
 + debug("​%4u %4u", Vdd, Vbat);
 + debug("​\n"​);​
 + }
 +
 +</​code>​
 +
 +Now we can take a look how the ping request sent from the ping node(sender node) is handled on the gnip node(receiver end).
 +
 +We need to walkthrough two things in particular.
 +
 + The rxEvent.c which receives the request and filters the received frame depending on its type i.e. ping frame or gnip frame.
 +Secondly we will take a look at the pingRx function in io.c that is called by the rxEvent handler when it receives a ping frame.
 +
 +
 +The rxEvent(byte *buf, byte len) is called when a frame is received. When a frame is received, the Rx is disabled using **[[kes:​api:​radio|waitRx]]** until the received frame is processed. ​  The frame id can be extracted from the 3 byte of the the received frame buffer(buf[2]) and hence the frame id is identified and the functions pingRx and gnipRx are called with respect to the frame id. In this case, we will receive a ping response frame from the sender node and hence the pingRx() is called. ​
 +
 +<code C>
 +#include "​Koliada.h"​
 +#include "​ping.h"​
 +
 +void pingRx(byte *buf, byte len);
 +void gnipRx(byte *buf, byte len);
 +
 +void rxEvent(byte *buf, byte len)
 + {
 + // running on interrupt!
 + //
 + // buf[0] = RSSI
 + // buf[1] = CORR
 + // buf[2] = protocol type
 +
 + // disable rx
 + waitRx();
 +
 + // pass it up to the application
 + switch (buf[2])
 + {
 + case PING_FRAME:
 + pingRx(buf,​ len);
 + break;
 +
 + case GNIP_FRAME:
 + gnipRx(buf,​ len);
 + break;
 + }
 + }
 +</​code>​
 +
 +The pingRx function copies the received ping request frame into the buffer lastPing and increments the number of  pings.
 +
 +The reply message to the received request is then packed and sent using the **[[kes:​api:​radio|rfSendFrame]]**.
 +
 +**Note:** While packing the reply message the node ID(buf[3]) of the node that sent the ping request is packed in the repsonse message so that the ping node can identify that the reply message was actually addressed to it or not by comparing the nodeID. The node ID can be taken out from 4 th byte of the message buf[3]. After sending the reply message, Rx is enabled again using **[[kes:​api:​radio|postRx]]**.
 +
 +<code c>
 +void pingRx(byte *buf, byte len)
 + {
 + // buf[n]
 + // where n=
 + //   0: rssi
 + //   1: corr
 + //   2: protocol
 + //
 + //   3: deviceId
 + //   4: BOARD_MAJOR
 + //   5: BOARD_MINOR
 + //   6: BOARD_CONFIG
 +
 + //   7: build
 + //   8: build
 + //   9: build
 + //  10: build
 +
 + //  11: rssi - remote rssi of local reply
 + //  12: buttons
 + //  13: Vdd
 + //  14: Vdd
 + //  15: VBat
 + //  16: VBat
 +
 +//​ dump(buf,​ len, 1);
 +#if 0
 + teta build;
 + word Vdd, Vbat;
 + static byte line;
 +
 + build =
 + ((teta) buf[ 7]) << ​  0 |
 + ((teta) buf[ 8]) << ​  8 |
 + ((teta) buf[ 9]) << ​ 16 |
 + ((teta) buf[10]) << ​ 24 ;
 +
 + rssi = buf[0]; // ping frame rssi
 +
 + Vdd =  ((word) buf[13]) << ​  0 |
 + ((word) buf[14]) << ​  8 ;
 + Vbat = ((word) buf[15]) << ​  0 |
 + ((word) buf[16]) << ​  8 ;
 +
 + if (memcmp(lastPing,​ buf, len) != 0)
 + {
 + memcpy(lastPing,​ buf, len);
 +
 + if (line++ % 16 == 0)
 + {
 + debug("​\n id : Board Config ​       RSSI    Vdd  Vbat\n"​);​
 + // ​ 0000: v1.1.4.3e3ac99e 38/0 00 3544  826
 + }
 + debug("​%d - %04x: v%u.%u.%u.%08lx ", pingCount, buf[3], buf[4], buf[5], buf[6], build);
 + debug("​%d/​%d %02x ", (signed char) rssi, buf[11], buf[12]);
 + debug("​%4u %4u", Vdd, Vbat);
 + debug("​\n"​);​
 + }
 +#else
 + // copy the msg
 + memcpy(lastPing,​ buf, len);
 + pingCount++;​
 +#endif
 +
 + // reply to a beacon ping
 + static byte txBuf[kFrameSize];​
 + len = 0;
 + txBuf[len++] = (UInt8) FRAME_ID0;
 + txBuf[len++] = (UInt8) FRAME_ID1; ​  // magic
 + txBuf[len++] = (UInt8) GNIP_FRAME;​ //​ frame type
 + txBuf[len++] = (UInt8) buf[3]; // station
 + txBuf[len++] = (UInt8) rssi;        // ping frame rssi
 +
 + assert(len <= kFrameSize - 2);
 +
 + // and send it out
 + rfSendFrame(txBuf,​ len);
 +
 + // enable rx
 + postRx();
 + }
 +</​code>​
 +
 +We have now accomplished setting up communication between two radios using koliadaES. The tutorial helped you setup two radios with one radio as the sender and the other radio as receiver. Now you can try having multiple senders(ping nodes) ​ and a single receiver(gnip node) or having single sender(ping node)  and multiple receivers(gnip nodes), etc and observe your results.
      Copyright © 2018-2019 Koliada, LLC
  • piep/examples/testradio.txt
  • Last modified: 2019/04/03 18:36
  • (external edit)