Modifications

Sauter à la navigation Sauter à la recherche
16 326 octets ajoutés ,  6 mars 2022 à 17:20
Ligne 1 : Ligne 1 :  
{{ENG-CANSAT-NAV}}
 
{{ENG-CANSAT-NAV}}
   −
== Wiring ==  
+
== Introduction ==  
 +
{{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.}}
 +
 
 
The following Wiring is used to capture  
 
The following Wiring is used to capture  
 
* Air temperature
 
* Air temperature
 
* Air pressure
 
* Air pressure
And transmissing the information via the HC
+
and transmitting the information via the RFM69HCW radio module.
 +
 
 +
== Wiring ==
 +
=== Wire the barometric sensor ===
 +
The BMP280 is wired on the I2C bus of the Feather.
 +
 
 +
[[Fichier:ENG-CANSAT-BMP280-wiring.png|320px]]
 +
 
 +
=== Wire the temperature sensor ===
 +
Then connect the TMP36 sensor as follows:
 +
* The pin 1 (on the left) to a power source (3.3V),
 +
* The pin 3 (the the right droite) to the ground/GND.
 +
* The pin 2 (middle one) to the A3 analogue input.
 +
 
 +
[[Fichier:ENG-CANSAT-TMP36-01.png|360px]]
 +
 
 +
=== Wire the radio module ===
 +
Finally wire the RFM69HCW radio as follows:
 +
 
 +
[[Fichier:ENG-CANSAT-RFM69HCW-Wiring-Feather.jpg|360px]]
 +
 
 +
{| class="wikitable" border="1"
 +
|-
 +
| align="center" | '''Feather M0 Express'''
 +
| align="center" | '''RFM69'''
 +
|- style="font-size: 90%"
 +
| align="left" | 3V
 +
| align="left" | VIN
 +
|- style="font-size: 90%"
 +
| align="left" | GND
 +
| align="left" | GND
 +
|- style="font-size: 90%"
 +
| align="left" | MO
 +
| align="left" | MOSI
 +
|- style="font-size: 90%"
 +
| align="left" | MI
 +
| align="left" | MISO
 +
|- style="font-size: 90%"
 +
| align="left" | SCK
 +
| align="left" | SCK
 +
|- style="font-size: 90%"
 +
| align="left" | 6
 +
| align="left" | CS
 +
|- style="font-size: 90%"
 +
| align="left" | 9
 +
| align="left" | G0
 +
|- style="font-size: 90%"
 +
| align="left" | 10
 +
| align="left" | RST
 +
|}
 +
 
 +
== 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.
 +
# Getting data from sensors + send them it over the serial connexion (to confirm good working) + transmit over radio
 +
# Testing the radio reception
 +
# Going autonomous (removing Serial Connexion waiting) + add the Lipo
 +
 
 +
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:
 +
# '''Wait for the serial connexion''' to be established before starting the sketch
 +
# Collect the sensor data
 +
# Send it to serial 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}}
29 836

modifications

Menu de navigation