Ligne 2 : |
Ligne 2 : |
| | | |
| == Introduction == | | == Introduction == |
− | {{ambox|text=Before starting this point, we recommand to follow all the sensors testing steps (BMP280 sensor, TMP36 Sensor, RFM69HCW radio and RFM69HCW Testing). | + | {{ambox|text=Before starting this point, we recommand to follow all the sensors testing steps (BMP280 sensor, TMP36 Sensor, RFM69HCW radio, RFM69HCW Testing and onboard NeoPixel). |
| | | |
| It contains all the details about the wiring, install needed libraries and conduct basic testing.}} | | It contains all the details about the wiring, install needed libraries and conduct basic testing.}} |
Ligne 9 : |
Ligne 9 : |
| * Air temperature | | * Air temperature |
| * Air pressure | | * Air pressure |
− | and transmissing the information via the RFM69HCW radio module. | + | and transmitting the information via the RFM69HCW radio module. |
| | | |
| == Wiring == | | == Wiring == |
Ligne 60 : |
Ligne 60 : |
| |} | | |} |
| | | |
− | == Testing == | + | == Download the code == |
| + | The code is available for download on the [https://github.com/mchobby/cansat-belgium GitHub associated to this wiki]. |
| + | |
| + | {{download-box|Téléchargez mission1-serial-radio-capture.ino|https://raw.githubusercontent.com/mchobby/cansat-belgium/master/mission1-serial-radio-capture/mission1-serial-radio-capture.ino}} |
| + | |
| + | == About testing == |
| Now, we will move forward in several steps. | | Now, we will move forward in several steps. |
| # Getting data from sensors + send them it over the serial connexion (to confirm good working) + transmit over radio | | # Getting data from sensors + send them it over the serial connexion (to confirm good working) + transmit over radio |
| # Testing the radio reception | | # Testing the radio reception |
| # Going autonomous (removing Serial Connexion waiting) + add the Lipo | | # Going autonomous (removing Serial Connexion waiting) + add the Lipo |
− | === Reading and transmitting data === | + | |
| + | The code proposed here under has been tested up to 23197 iterations without issue, time when we decided to ends the test :-) . |
| + | |
| + | Once uploaded to your Feather, open the Serial Monitor and set it to 9600 bauds. '''The sketch would wait until you open the Serial Monitor to start transmitting the data'''. |
| + | |
| + | You should see the following messages appears on the Serial Monitor. |
| + | |
| + | [[Fichier:ENG-CANSAT-MISSION1-CAPTURE-20.png]] |
| + | |
| + | Where we could see the transmitted messages with the packetnum packet index, timing and data. |
| + | |
| + | The screen also displays the '''ACK''' acknowledgement send back by the receiver. |
| + | |
| + | == Structuring the data == |
| + | The radio module only sends buffer of binary data to the receiver. This is a bit rough but efficient. |
| + | |
| + | So to transport the data to the receiver, we need to transform the values (float, integer) into their string representation. |
| + | |
| + | When having multiple data in their string representation is not enough, they must also been organized. |
| + | |
| + | '''The final format must be easy to parse and very compact''' (smaller is the radio message and higher is the chance for him to get to the ground without error). |
| + | |
| + | We propose the following format: |
| + | <nowiki>:data1|data2|data3|data4;/r/n</nowiki> |
| + | |
| + | where: |
| + | * ''':''' is the begin of data stream |
| + | * ''';''' is the end of data stream |
| + | * '''/r/n''' are optional carriage return + line feed characters.<br />This will would make the messages user friendly when the the messages are viewed in a console or terminal. |
| + | * '''|''' is the separator between data items. |
| + | * '''datax''' are the string representation of the various data. The characters ;:| are forbidden in this area. |
| + | |
| + | we would also recommend to use: |
| + | * '''packetnum''' as data1. packetnum is a simple variable increment of one unit after each transmission. This would allow the receiver to detect lost message (since it would exist holes in the numbering of received messages). |
| + | * '''timing_info''' as data2. This would help to create timing chart or time base data analysis. We suggest to use the Arduino's {{fname|millis()}} function which count the number of milliseconds since the last microcontroler reset. |
| + | |
| + | As explained later in the code the {{fname|packet_str}} variable contains the message to be transmitted to the ground. The Arduino's {{fname|String}} class would ease the transformation of data to their string representation. |
| + | <syntaxhighlight lang="c"> |
| + | String packet_str = String( ":"+String(packetnum,DEC)+"|" ); |
| + | packet_str.concat( String( ms,DEC)+"|" ); |
| + | packet_str.concat( String( temperature, 2 )+"|" ); |
| + | packet_str.concat( String( bme_hpa, 2 )+"|" ); |
| + | packet_str.concat( String( bme_temp, 2 )+";\r\n" ); |
| + | </syntaxhighlight> |
| + | |
| + | == LEDs and Error management == |
| + | Being able to understand rapidly what's happening inside your object is essential to rapidly fix the issue. |
| + | |
| + | The best is to figure out what's happening is to use LED, blink status, heartbeat. |
| + | |
| + | By doing so, no need to open a Serial Monitor or diagnostic tool to figure out the status of the object. |
| + | |
| + | [[Fichier:ENG-CANSAT-MISSION1-CAPTURE-10.png|360px]] |
| + | |
| + | The NeoPixel LED does turn GREEN when the Feather M0 switch on to normal operation (when it runs your Arduino Sketch). |
| + | |
| + | In the following sample, we do take the control over the NeoPixel LED to switch it off at the end of {{fname|setup()}} function. This means that all buses and devices are properly initialized. |
| + | |
| + | The RADIO_LED wired on the Pin 13 is used to signal radio status when emitting a message. |
| + | |
| + | {| class="wikitable" border="1" |
| + | |- |
| + | | align="center" | LED operation |
| + | | align="center" | Description |
| + | | align="center" | Fix the issue |
| + | |- style="font-size: 90%" |
| + | | align="left" | NeoPixel GREEN |
| + | | align="left" | The {{fname|setup()}} function did not complete initialization because of a crash. |
| + | | align="left" | Check the wiring of sensors. Test each sensor separately (with their tests code). If this not working, remove all sensors except the one you are testing. |
| + | |- style="font-size: 90%" |
| + | | align="left" | NeoPixel OFF |
| + | | align="left" | The {{fname|setup()}} did complete successfully. The main {{fname|loop()}} is not running. |
| + | | align="left" | ''Nothing to do here, just check the RADIO_LED for more informations''. |
| + | |- style="font-size: 90%" |
| + | | align="left" | RADIO LED = 1 pulse 50ms |
| + | | align="left" | The LED is pulsed for each successfully send message + getting ACK from the receiver. The code wait 500ms max for the ACK. |
| + | | align="left" | ''Nothing to do here''. |
| + | |- style="font-size: 90%" |
| + | | align="left" | RADIO LED = 2 pulse 50ms + pause 100ms |
| + | | align="left" | Message send but error while decoding the ACK response. |
| + | | align="left" | ''This is not critical, the most important is that the message was sent successfully''. |
| + | |- style="font-size: 90%" |
| + | | align="left" | RADIO LED = 3 pulse 50ms + pause 150ms |
| + | | align="left" | Not ACK message received within the 500ms after message was sent.<br />This can be interpreted as "Is there someone listening the message?" because there are not reply. |
| + | | align="left" | ''This is not critical, the most important is that the message was sent successfully''. |
| + | |} |
| + | |
| + | == The code explained == |
| + | Here some explanation about the {{fname|mission1-serial-radio-capture.ino}} sketch used in the CanSat. |
| + | |
| This Arduino sketch would: | | This Arduino sketch would: |
− | # Wait for the serial connexion to be established before starting the sketch | + | # '''Wait for the serial connexion''' to be established before starting the sketch |
| # Collect the sensor data | | # Collect the sensor data |
| # Send it to serial connexion | | # Send it to serial connexion |
| # Send it over the radio connexion | | # Send it over the radio connexion |
| | | |
| + | <div style="margin: 15px 0; background: rgba(255,204,102,.3); display: block; padding: 15px 15px 15px 15px; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; border: 1px solid #ff9900;" >Don't forget to update the radio frequency {{fname|RF69_FREQ}} and the encryption key {{fname|key[]}} </div> |
| + | |
| + | First, the script will includes all the needed libraries. |
| + | |
| + | <syntaxhighlight lang="c"> |
| + | #include <Wire.h> |
| + | #include <SPI.h> |
| + | #include <Adafruit_Sensor.h> |
| + | #include <Adafruit_BMP280.h> |
| + | #include <Adafruit_NeoPixel.h> |
| + | #include <RH_RF69.h> |
| + | </syntaxhighlight> |
| + | |
| + | Then, it defines the parameters for the radio module and the pinout used to wire the RFM69HCW radio module to the Feather M0. |
| + | |
| + | The last line create the object {{fname|rf69}} to control the module. |
| + | |
| + | <syntaxhighlight lang="c"> |
| + | #define RF69_FREQ 433.0 |
| + | |
| + | #define RFM69_CS 6 |
| + | #define RFM69_INT 9 |
| + | #define RFM69_RST 10 |
| + | #define RADIO_LED 13 |
| + | |
| + | RH_RF69 rf69(RFM69_CS, RFM69_INT); |
| + | </syntaxhighlight> |
| + | |
| + | Defining the parameters to control the NeoPixel LED available on the board. That LED is wired on the Pin 8. |
| + | |
| + | The last line creates an objet pixel which is a Pixels Strand of only 1 pixel length. |
| + | |
| + | <syntaxhighlight lang="c"> |
| + | #define NEOPIXEL 8 |
| + | #define NUMPIXELS 1 |
| + | |
| + | Adafruit_NeoPixel pixel = Adafruit_NeoPixel(NUMPIXELS, NEOPIXEL, NEO_GRB + NEO_KHZ800); |
| + | </syntaxhighlight> |
| + | |
| + | Defining the parameter and objects for temperature and pressure sensor. |
| + | <syntaxhighlight lang="c"> |
| + | #define temperaturePin A3 |
| + | |
| + | Adafruit_BMP280 bme; // wired with I2C |
| + | </syntaxhighlight> |
| + | |
| + | Initialize the serial connexion @ 9600 bauds, the BMP sensor, the radio module ({{fname|init_radio_module()}}) and pixel. |
| + | |
| + | <div style="margin: 15px 0; background: rgba(204,14,14,.3); display: block; padding: 15px 15px 15px 15px; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; border: 1px solid #CCC;" >The {{fname|while(!Serial)}} waits that you open the serial monitor so effectively starts the sketch.</div> |
| + | |
| + | The NeoPixel is turned when the {{fname|setup()}} function is complete. |
| + | |
| + | <syntaxhighlight lang="c"> |
| + | void setup() { |
| + | Serial.begin(9600); |
| + | |
| + | // wait until serial console is open |
| + | while (!Serial) { delay(1); } |
| + | |
| + | if (!bme.begin()) { |
| + | Serial.println("Could not find a valid BMP280 sensor, check wiring!"); |
| + | while (1); |
| + | } |
| + | |
| + | init_radio_module(); |
| + | |
| + | // everything is right! So switch off neopixel |
| + | pixel.begin(); |
| + | pixel.setPixelColor(0, pixel.Color(0,0,0)); // switch off |
| + | pixel.show(); |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | Now, we can focus on the main loop. |
| + | |
| + | The first step is to send the column header (so we know what are the data) if not done yet. |
| + | |
| + | Then we reads the sensors (as we have tested them, this should not be a surprise). We also capture the time with the function {{fname|millis()}}, so the {{fname|ms}} variable contains the number of milliseconds since the last reset. |
| + | |
| + | Finally, we do increment the {{fname|packetnum}} variable. This would allows to track lost packets on the receiver side. |
| + | |
| + | {{fname|packet_str}} is the message to send via radio. It is composed with {{fname|String}} objects and concatenation operations. {{fname|String}} are welcome to transform '''float''' into string representation since most common float to string C standard functions would fail to work properly onto Arduino alike plateforms. |
| + | |
| + | The key function to transform the '''String object''' into a '''C buffer'' is {{fname|packet_str.c_str()}} which offer an access to the underlying array of bytes (exactly what the radio module library would need). |
| + | |
| + | The remaining of the radio transmission code is almost the same as the RFM69HCW module testing code (except that error messages are remplaced by Blinking LED). |
| + | |
| + | <syntaxhighlight lang="c"> |
| + | bool header_send = false; |
| + | // packet number increment at each data transmission |
| + | int16_t packetnum = 0; |
| + | void loop() { |
| + | // --- SEND COLUMNS HEADER ------------------- |
| + | if( !(header_send) ){ |
| + | send_header(); |
| + | header_send = true; |
| + | } |
| + | |
| + | // --- READ SENSORS --------------------------- |
| + | float voltage = getVoltage(temperaturePin); |
| + | float temperature = (voltage - .5) *100; |
| + | |
| + | float bme_temp = bme.readTemperature(); |
| + | float bme_hpa = bme.readPressure(); |
| + | |
| + | unsigned long ms = millis(); |
| + | packetnum += 1; // increment |
| + | |
| + | // --- Compose the Message to send ------------ |
| + | String packet_str = String( ":"+String(packetnum,DEC)+"|" ); |
| + | packet_str.concat( String(ms,DEC)+"|" ); |
| + | packet_str.concat( String( temperature, 2 )+"|" ); |
| + | packet_str.concat( String( bme_hpa, 2 )+"|" ); |
| + | packet_str.concat( String( bme_temp, 2 )+";\r\n" ); |
| + | |
| + | // send to Serial |
| + | Serial.print( packet_str.c_str() ); |
| + | // Send over Radio |
| + | rf69.send((uint8_t *)(packet_str.c_str()), packet_str.length()); |
| + | rf69.waitPacketSent(); |
| + | |
| + | // Now wait for a reply |
| + | uint8_t buf[4]; // We limit the quantity received data |
| + | uint8_t len = sizeof(buf); |
| + | |
| + | if (rf69.waitAvailableTimeout(500)) { |
| + | // Should be a reply message for us now |
| + | if (rf69.recv(buf, &len)) { |
| + | Serial.print(": "); |
| + | Serial.println((char*)buf); |
| + | Blink(RADIO_LED, 50, 1); //blink LED once, 50ms between blinks |
| + | } else { |
| + | Serial.println("Receive failed"); |
| + | Blink(RADIO_LED, 50, 1); //blink LED once, 50ms between blinks |
| + | } |
| + | } else { |
| + | Serial.println("No reply, is another RFM69 listening?"); |
| + | Blink(RADIO_LED, 50, 3 ); // blink 3 times, 50ms between blinks |
| + | } |
| + | |
| + | // Going to next round |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | The {{fname|init_radio_module()}} function is called from the {{fname|setup()}}. |
| + | |
| + | This function does all the stuff to initialize the RFM69HCW modules. Set the transmission power, the frequency and the '''encryption key'''. |
| + | |
| + | <syntaxhighlight lang="c"> |
| + | void init_radio_module() { |
| + | pinMode(RADIO_LED, OUTPUT); |
| + | pinMode(RFM69_RST, OUTPUT); |
| + | digitalWrite(RFM69_RST, LOW); |
| + | |
| + | Serial.println("Feather RFM69 TX Test!"); |
| + | Serial.println(); |
| + | |
| + | // manual reset |
| + | digitalWrite(RFM69_RST, HIGH); |
| + | delay(10); |
| + | digitalWrite(RFM69_RST, LOW); |
| + | delay(10); |
| + | |
| + | if (!rf69.init()) { |
| + | Serial.println("RFM69 radio init failed"); |
| + | while (1); |
| + | } |
| + | Serial.println("RFM69 radio init OK!"); |
| + | // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module) |
| + | // No encryption |
| + | if (!rf69.setFrequency(RF69_FREQ)) { |
| + | Serial.println("setFrequency failed"); |
| + | } |
| + | |
| + | // If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the |
| + | // ishighpowermodule flag set like this: |
| + | rf69.setTxPower(20, true); // range from 14-20 for power, 2nd arg must be true for 69HCW |
| + | |
| + | // The encryption key has to be the same as the one in the server |
| + | uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, |
| + | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; |
| + | rf69.setEncryptionKey(key); |
| + | |
| + | pinMode(RADIO_LED, OUTPUT); |
| + | |
| + | Serial.print("RFM69 radio @"); |
| + | Serial.print((int)RF69_FREQ); |
| + | Serial.println(" MHz"); |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | This function send the header information to the Serial monitor. |
| + | |
| + | Ideally, this function should also send it via the radio. |
| + | |
| + | <syntaxhighlight lang="c"> |
| + | void send_header() { |
| + | String s1 = String( F("***HEADER***\r\n") ); |
| + | Serial.print( s1 ); |
| + | String s2 = String( F(":counter|time_ms|temperature|pressure_hpa|temp2;\r\n") ); |
| + | Serial.print(s2); |
| + | String s3 = String( F("***DATA***\r\n") ); |
| + | Serial.print( s3 ); |
| + | |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | Helper function used to blink a LED. Note that a pause of 3 time the blinking time. This will ease the identification of blink code into other blinking patterns. |
| + | |
| + | <syntaxhighlight lang="c"> |
| + | void Blink(byte PIN, byte DELAY_MS, byte loops) { |
| + | for (byte i=0; i<loops; i++) { |
| + | digitalWrite(PIN,HIGH); |
| + | delay(DELAY_MS); |
| + | digitalWrite(PIN,LOW); |
| + | delay(DELAY_MS); |
| + | } |
| + | // exit: wait 3 times the delay |
| + | delay( 3* DELAY_MS ); |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | This function returns the voltage for the analog Pin. |
| + | |
| + | It converts a digital value between 0 & 1024 (from ADC) to voltage between 0 & 3.3 volts. |
| + | |
| + | <syntaxhighlight lang="c"> |
| + | float getVoltage(int pin){ |
| + | // each unit equal 3.3 / 1024 = 3.2 millivolts |
| + | return (analogRead(pin) * .0032); |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | == Fault tolerant design == |
| + | The goal is to transmit the data to the ground station.<br />The code of the Emitter (this section) and Receiver (next section) are doing the job. |
| + | |
| + | However, what would happens to your data if the antenna did break? All the data are lots! |
| + | |
| + | This is where the "Extra Flash" would be a great help! |
| + | |
| + | As showed earlier, it is also possible to store/save the data into the Flash. |
| + | |
| + | A good approach would be: |
| + | # to save the data in the Flash |
| + | # then send it over Radio. |
| | | |
| + | In this way, the data stays available inside the CanSat and could be extracted as suited. |
| | | |
| {{ENG-CANSAT-TRAILER}} | | {{ENG-CANSAT-TRAILER}} |