Saturday, 26 November 2016

The TT7F's First Launch

This is a throwback to the August launch I wrote about in The TT7F Payload but haven't finished the story yet. Now, three months later, it is probably time to conclude it.
Early in the morning with the 8:00-9:00 launch in mind I set up all the equipment in the garden. The latest predictions remained favourable suggesting 8:30 launch might have been ideal.
I filled the balloon while estimating the proper lift with the standard 'water in a bottle' technique. Not having a regulator on the hydrogen cylinder, the balloon was ready pretty quickly. I also filled a small party balloon to have it fly first and ensure there wasn't any strong wind higher up that could blow the balloon to one of the plenty of trees that were around.
Powering up the tracker, tuning in with the receiver (an rtl-sdr dongle with a helix antenna), verifying that the balloon has appeared on the map, turning on the camera - all checked, I moved to the front yard and let go of the balloon.
The unit cleared the surrounding obstacles without any problems and slowly went on its way.
The receiving station was somewhat unorthodox, nevertheless, a significant improvement on the previous lousy notebook with a mobile Internet. The snow shovel supported helix antenna performed quite nicely throughout the majority of the flight. Like this, the tracking experience was much more enjoyable than on my previous flights.
The received signal was sufficient even though a little 'shaky' (perhaps the thin guitar string antenna was responsible for that) and decoded without problems. The wind took the balloon predominantly towards west
and in a similar direction from where I was which meant that I didn't have to redirect the antenna too often.
Just a few hundred meters in ascended altitude and receivers from Poland (many thanks) started to upload TT7F's telemetry as well. The furthest reception was by SP1TMN at about 484km (based on his position on the map). The final proof that the tracker could operate at large distances not just in the somewhat closer proximities as used in testing.
Initially I was a little worried about the ascent rate. The balloon started cautiously with about 2m/s (with all the calculations done for 4m/s). Especially the moment where the speed fell from 3m/s to just about 1.5m/s quickly summoned flashbacks of last year. Luckily the speed eventually grew and brought the final average up to 3.69m/s.
The balloon rose and rose and since about 15km in altitude, thanks to the clear skies, I was able to track it with a pair of binoculars all the way to its burst. That came after 3 hours and 8 minutes at 42337m which was just a few tens of meters shy of the the predicted 42383m.
This is a graph of TT7F's horizontal speed I calculated from the GPS data. Particularly interesting to me were the numbers towards the end that show what winds the balloon encountered at around the 40km mark. It seemed to me while watching the balloon live on the map that it quite sped up at those altitudes so I was intrigued to verify that in data. Generally the everyday soundings by the met offices don't go that high so the wind speeds up there were an unanswered question to me.
The tracker ran the old software that didn't have the ADC measurements properly handled yet so the readings jump around quite wildly. Despite that a little averaging shows the expected temperature profile as measured by the mcu's on-chip temperature sensor.
The battery voltage seemed to react in accordance with the temperature. After all the two AAA Energizers were exposed to the outside atmosphere without any insulation.
With the descent rate graph above it's probably time to address the elephant in the room and the main reason for why I am writing this summary three months after the launch. Put simply, I didn't find the payload nor have since. As I said, I was watching the balloon with binoculars and actually managed to see the burst itself. When I returned to the PC, I found out the signal got a bit wild and the frequency had drifted out of the dl-fldigi's decode bands. Also none of the Polish receivers seemed to be able to decode the signal anymore. Upon restoring decoding, I saw the descent speed substantially higher than expected. I remember seeing the balloon in binoculars even after it burst suggesting most of it remained attached. That would explain the plummeting speed. The main issue, however, occurred at around 12km in altitude where the signal simply disappeared. Habitat's last upload has the payload at 14916m while a partial decode pulled out of dl-fldigi puts the last position at 49.4978N 17.5210E and 12471m. As to what might have happened. One possibility is that the tracker, light as it was, hit a layer of strong wind that might have slowed it temporarily causing it to collide with the rest of the balloon breaking of the guitar string antenna (honestly, not build for this). Another possibility is that the exposed batteries simply froze (at least temporarily, the region coincides with the lowest temperatures this time magnified by the increased speed) despite being Energizer Ultimate Lithiums.
I packed a notebook, the sdr dongle, a yagi, a pair of binoculars and after roughly estimating the possible landing location (simply projection the fastest descend prediction I had from the actual burst coordinates) I drove off to the site. Comparison between a prediction some 24 hours to launch and the actual trajectory can be seen above.
Upon arrival I couldn't find the signal from any of a few locations overlooking the potential area of impact. If the thin antenna survived the rough descent, it quite possibly didn't the consequent hard landing. Wandering the endless fields with a pair of binoculars (after all a 4m² white sheet of latex can't just disappear without a trace) for the following three hours led nowhere, so I returned home with nothing but a tank top tan lines.
That started off a series of potential descent trajectory modellings, consecutive visits and lengthy however unfruitful searches. On one visit I spent the whole time in a corn field. In a 4 hour purgatory I manged to cover just a fraction of it gaining nothing but eternal hate towards corn.
On another occasion I closely inspected the grounds of an electrical substation that unfortunately happened to be in the middle of the suspected area. But the payload didn't seem to be there nor on any of the many high-voltage power lines.
The last visit came with the beginning of November. The main reason was that the corn fields should have been harvested by then and I wanted to walk through them to see if there weren't any remnants. No luck on that occasion either. Above is a photo overlooking the area to give a little perspective on the balloon's whereabouts while it was still transmitting. The view is oriented south-east.
The map shows the overall searched area. Most of it are fields out of which many were cropless thus easily explorable with binoculars. By the November visit most of the fields seemed ploughed so it seems unlikely the payload may still be lying in one of them without anyone having noticed it yet. It may have been destroyed during the harvest or by one of the ploughs. Somebody may have found it and kept it. Or it may have landed in a bushy ditch or a row of trees between the fields. I may have walked within 10 meters of it unable to see it. It's really hard to say.
This is an example set of potential descent trajectories I made. They differ in origin to see to what extent they follow the part of the actual descent I had received (42337m to 12471m). Another variable is the air speed and direction data used. From the start I was using data from the closest met office's sounding (Prostejov) published at 12:00AM GMT with TT7F bursting at 9:37:30. Only during writing this post it occurred to me to use TT7F's own ascent data as input (possibly more accurate) as well. The result can be seen in the blue to purple trajectories. Since not even these match the real part of the trajectory perfectly, I reckon the conditions have evolved a little in the three hours of flight (and possibly in the 79.8km the balloon covered) so the truth will probably be somewhere in between. The modelling was done second by second in Excel. Another variable I tinkered with was descent speed. I dived into the depths of Habitat's archives to dig out a couple of 'rushed' descent telemetries to match TT7F's own. I also modelled a few slow descents for comparison (could the balloon have formed a sail and glided away at lower altitudes?). The variables are plenty and actual data scarce.


Above is a more interactive view of the probable landing area with all the search attempts and photos in a little more detail. It was made in Google's My Maps.

Given the trend in my recovery attempts: TT7 several hours, TT7-40 two months, TT7F three months and counting. It's probably not for the worst that I am slowly shifting my efforts towards floaters. In the end I am glad that I have finally manged to get above 40k in altitude (officially, who knows how high TT7-40 had gotten). The 42337m for a 150g payload seem quite nice. It is a shame I didn't get the video recording back. Given the altitude it reached and the flight time, the SD card quite possibly holds one of the highest full flight recordings. May it rests in peace... or a ditch probably.

Wednesday, 2 November 2016

The TT7F Software

This article will zoom in on programming the SAM3S8B microcontroller that runs TT7F. There are currently two Github repositories with code for the tracker. The first one TT7F-UpDw-Tracker is the complete code used on the first launch of TT7F in the Up-and-Down setup. The second one TT7F-Float-Tracker is the main repository and work in progress that currently contains all the libraries, however, so far not any final routine.
Coming from Arduino and AVR environment which is very rich in resources, I had trouble finding out how to even start with somewhat more advanced ARM processor at first. The hardware side of programming TT7F was pointed out in The TT7F Hardware so I will start at the moment the erased and reset microcontroller connects to a USB port. From the start I have been using Atmel's SAM-BA software.
After selecting the proper port and board the software connects to the MCU and provides a view of the state of the internal memories.
At address 0x00400000 we can see the start of the erased internal flash memory that will eventually host our code compiled to individual instructions. The SAM3S8B has one 512KB plane of flash memory that apart from holding the code can be used to store data.
To do the coding, I chose Atmel Studio 6.2. After creating a 'New Project' and selecting the desired device 'ATSAM3S8B', it opens an almost empty script. The #include "sam.h" line contains a series of #defines and #includes that search for the specific device within the whole SAM family and appropriate files to include in the project that hold a number of structures, enumerations and define directives to simplify the coding. The other pre-included thing in the project is the SystemInit(); line in the main function. This routine sets the number of wait cycles for reading instructions from the flash, initializes and switches the mcu to run with the main oscillator, and finally initializes the PLL and switches the mcu to the maximum 64MHz frequency. After this the custom code may follow.

Atmel Studio also includes a number of example projects that came handy when I was finding out how to make individual peripherals work.


WatchDog ARM_WATCHDOG.c
One thing to do straight away in the script, unless you already know that you want to implement it, is to disable WatchDog which is enabled by default. Otherwise it means that the mcu resets every 8 seconds or so.

1
2
3
4
int main(void)
{
 SystemInit();
 WDT->WDT_MR = WDT_MR_WDDIS; // disable WatchDog (enabled by default)

This can be done by writing the WDT_MR_WDDIS bit in the WDT_MR register as shown above. The line 4 also demonstrates general way of accessing registers in the code.

#define RTT        (0x400E1430U) /**< \brief (RTT       ) Base Address */
#define WDT        (0x400E1450U) /**< \brief (WDT       ) Base Address */
#define RTC        (0x400E1460U) /**< \brief (RTC       ) Base Address */

The WDT bit represents a specific base address in the memory for all the registers of one peripheral.

typedef struct {
  WoReg WDT_CR; /**< \brief (Wdt Offset: 0x00) Control Register */
  RwReg WDT_MR; /**< \brief (Wdt Offset: 0x04) Mode Register */
  RoReg WDT_SR; /**< \brief (Wdt Offset: 0x08) Status Register */
} Wdt;

The WDT_MR part then represents an offset of the specific register from the base address.

#define WDT_MR_WDDIS (0x1u << 15) /**< \brief (WDT_MR) Watchdog Disable */

And finally WDT_MR_WDDIS refers to the desired bit operation. In this case writing the bit 15 to '1' and all the other bits in the register to '0'. All this information is nicely shown by Atmel Studio upon right clicking the part of interest and choosing 'Goto Implementation'.
WatchDog is a safety feature that resets the mcu after a specific time. This is avoided by including commands to reset the WatchDog counter at specific places in the script. In case the execution of the code hangs at some point the counter runs out and resets the mcu. If the execution goes according to the plan the WatchDog reset command stops it from resetting the mcu and starts another round of timing.
The WDT_MR register can be written only once after mcu reset so the decision to use WatchDog or not is permanent. The 12-bit counter runs on the 32.768kHz Slow Clock divided by 128 allowing up to 16s WatchDog period.


Delay ARM_DELAY.c
Another useful thing is to set up a routine to manage delays when needed. The MCU offers the 24-bit SysTick timer for this purpose.

#define SYSTICK_LOAD 12000 // 64000 - 64MHz; 12000 - 12MHz; ...

Depending on the configured processor speed SYSTICK_LOAD in ARM_DELAY.h has to be adjusted. The timer is then used to fire an interrupt every 1ms that increments the 'timestamp' variable which determines whether it is time to exit the SysTick_delay_ms() function. The timer has to be initialized by SysTick_delay_init().


LED ARM_LED.c
The TT7F board has two LEDs. After power up or reset most of the microcontroller pins (general purpose I/O lines - PAx pins) are by default pulled up to the operating voltage (3.3V) and controlled by the Parallel Input/Output Controller (PIO) with the output disabled. The pull up makes one of the LEDs slightly glow without configuring the PIO. The other one doesn't glow because it is located on a System I/O Line (PB5) that by default has the pull up disabled.

PMC->PMC_PCER0 |= (1 << ID_PIOA); // enable clock to the peripheral

Generally, it is necessary to enable the clock to a specific peripheral first before it can be used. Although, there are exceptions such as the RTT, RTC, etc. peripherals that are continuously supplied with clock signal and writing to the PMC_PCER0/1 registers is not required.

1
2
3
4
5
6
7
8
void LED_PB5_init(void)
{
 MATRIX->CCFG_SYSIO |= (1 << 5);  // System I/O lines must be configured here as well
 PMC->PMC_PCER0 |= (1 << ID_PIOB); // enable clock to the peripheral
 PIOB->PIO_OER |= PIO_PB5;  // enable Output on PB5
 PIOB->PIO_PER |= (1 << 5);  // enable PIOC control (disable peripheral control)
 PIOB->PIO_PUDR |= (1 << 5);  // disable pull-up
}

As opposed to the LED on PA0, controlling the PB5 requires assigning the System I/O Line to the PIOB controller by writing to MATRIX->CCFG_SYSIO register as can be seen on the line 3.
The LED library contains a couple of functions to either toggle the LEDs or turn them on for x milliseconds.


ADC ARM_ADC.c
TT7F uses three ADC channels to sample voltage on the solar panels (AD3), voltage on the battery (AD9) and the internal temperature sensor (AD15). Compared to the initial ADC script I used in TT7F-UpDw-Tracker I managed to improve on the volatility of the measurements.

ADC->ADC_MR |= (0x01 << 28) | (0x18 << 24) | (0x05 << 16) | (0x05 << 8);

Firstly by properly setting up the ADC_MR register in ADC_start() that should now have the TRACKTIM and TRANSFER timings according to the ADC's Electrical Characteristic bit in the datasheet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for(uint32_t i = 0; i < n_averaged; i++)
{
 ADC->ADC_CR = ADC_CR_START;   // begin AD conversion
 while(!(ADC->ADC_ISR & (0x01 << channel))); // End of Conversion x
 ADCdata += ADC->ADC_CDR[channel];  // read the value
}
 
ADC->ADC_CHDR = (1 << channel);    // channel disable
 
ADCdata = ADCdata / n_averaged; // finish the computation of the average

Secondly by sampling a number of times (usually about 100) and averaging the result within the ADC_sample() function.
This is an example of sampling the internal temperature sensor with 100 averaging for 10 minutes. Since the ADC output is a raw number between 0 and 4095, to get the final value in °C it has to be multiplied by 0.30402 and 274.887 has to be subtracted from the result. The actual calculation adds an offset (TEMP_OFFSET) to the final result that can be found in ARM_ADC.h. For this offset every ADC has to be compensated individually.

ADC_start();
uint16_t AD3data = ADC_sample(3, 100);
uint16_t AD9data = ADC_sample(9, 100);
uint16_t AD15data = ADC_sample_temperature(100);
ADC_stop();

This is an example of the complete routine as I do it on TT7F.


UART ARM_UART.c
The UART interface is used to communicate with the Ublox module. Specifically the UART1. The UART0 pins are then routed to the side panel of the PCB as a backup programming interface. The Ublox receives and transmits at 9600 baud and this speed has to be matched on the mcu side as well. Specifically in ARM_UART.h  the UART1_BAUD define directive that represents a master clock divisor. As such its value to achieve 9600Hz is dependent on the master clock speed (64MHz by default, however, the mcu offers a wide range of possible frequencies at which it can run) and must reflect that.

PIOB->PIO_PDR |= PIO_PB2;  // disable PIO control on RX line
PIOB->PIO_PDR |= PIO_PB3;  // disable PIO control on TX line
PIOB->PIO_ABCDSR[0] &= ~(1 << 2); // select Peripheral A function on PB2
PIOB->PIO_ABCDSR[1] &= ~(1 << 2); // select Peripheral A function on PB2
PIOB->PIO_ABCDSR[0] &= ~(1 << 3); // select Peripheral A function on PB3
PIOB->PIO_ABCDSR[1] &= ~(1 << 3); // select Peripheral A function on PB3

Unlike in the LED example, the UART1 interface is located in the 'Peripheral A' domain and the pins have to be switched from PIO control to Peripheral A. This is done by disabling the PIO control in PIO_PDR register and selecting the desired Peripheral in the PIO_ABCDSR[0] and PIO_ABCDSR[1] registers.

UART1->UART_IER |= UART_IER_RXRDY;  // enable RXRDY interrupt
NVIC_EnableIRQ(UART1_IRQn);

While transmitting on the UART lines is done manually byte by byte. Receiving, due to its nature, utilizes an RX interrupt routine. Interrupts in general have to be enabled both in the specific peripheral and the Nested Vectored Interrupt Controller (NVIC) as seen above. In some case (other peripherals) it is necessary to enable the interrupt in NVIC first and then set up the peripheral's registers.
The RXRDY interrupt fires when a complete character is received and the result can be read in the UART_RHR register.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void UART1_Handler(void)
{
 if(UART1_buffer_pointer < UART1_BUFFER_SIZE)
 {
  UART1_RX_buffer[UART1_buffer_pointer] = UART1->UART_RHR;
  UART1_buffer_pointer++;
 }else{
  UART1_temp = UART1->UART_RHR;
 }
}

When an interrupt fires, the processor enters the proper Handler (UART1_Handler() in this case). Given its specific use, I opted for a simple solution and set up a large enough buffer to hold any one sentence the Ublox module may send. This buffer is then automatically filled byte by byte as they are received and in the end should hold a complete sentence ready to be processed. After that the buffer is cleared and the
'UART1_buffer_pointer' reset to '0'.


UBLOX ARM_UBLOX.c
This library contains a variety of functions ranging from UBX and NMEA parsers, RTTY telemetry constructors to ASCII converter routines. After reset the Ublox module, by default, periodically outputs a number of standard NMEA sentences. At the same time it is open to receive commands consistent with the UBX protocol.

static uint8_t request0107[8] = {0xB5, 0x62, 0x01, 0x07, 0x00, 0x00, 0x08, 0x19};
static uint8_t requestGNGGA[15] = "$EIGNQ,GGA*39\r\n";

Using the UART1_TX() function repeatedly to send all the bytes within one of the arrays above equates to sending a command to the GPS module. In this case the module would respond by sending back one UBX-NAV-PVT message that contains numerous information about the module's position, current time, number of satellites, etc., or by sending back a GNGGA NMEA message that contains similar information. The main difference between the two protocols is that UBX communicates in binary values while NMEA in human readable ASCII format. That presents different challenges to the subsequent parsers.

static uint8_t setNMEAoff[28] = {0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0xD0, 0x08, 0x00, 0x00, 0x80, 0x25, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xA9};
static uint8_t setNAVmode[44] = {0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00, 0x2C, 0x01, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xE8};

As mentioned above, the default settings result in periodic NMEA message updates. That is not very handy for simple parsing nor potential power saving. Generally, the first thing I do is to send 'setNMEAoff' command to the module to stop these periodic updates, followed by 'setNAVmode' command to set the module in airborne mode. An important thing for high altitude ballooning. Otherwise the module stops outputting position data above approximately 12000 meters in altitude. Then I simply request the UBX or NMEA messages as I need them.

1
2
3
UBLOX_send_message(request0107, 8);
UBLOX_fill_buffer_UBX(GPSbuffer, 100);
UBLOX_parse_0107(GPSbuffer);

This is an example sequence of commands. First it requests the UBX-NAV-PVT message from Ublox (line 1). Then it waits for the response by expecting a specific number of bytes to arrive in the UART1_RX_buffer[] or it times out (line 2). If the expected number of bytes arrives, it copies them into 'GPSbuffer'. And finally it runs the respective parser (line 3). Within the parser routine it verifies that the received data actually represent the requested message, checks the checksum and parses individual data into respective global variables. The NMEA procedure is very similar.

$$$$TT7F1,1,20:31:15,49.49171,18.22271,1131,11,3,2048,4120,15, *0C5D\n

After having the current data prepared in appropriate variables, all that remains is running the UBLOX_construct_telemetry_UBX() function. The result is a telemetry string such as the one above in the desired output buffer.
In telemetry construction, it is necessary to make sure any potential '0's right after decimal point are handled properly and the software doesn't omit them. This is predominantly a latitude/longitude issue. In my case, I use ASCII_8bit_transmit() for the integer part of the coordinate and ASCII_32bit_LATLON_DECIMAL_transmit() for the decimal part. I make sure the 'figures' parameter matches the input's number of decimal places. For testing the parser and telemetry constructor, I wrote UBX_Generator to have something for an input.

$GPGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B

One unpleasant difference between UBX and NMEA protocols is that the latter outputs latitude and longitude data in degrees and decimal minutes format (see above) while UBX provides nice and handy 32-bit integers representing decimal degrees such as 472852330 85652670 for the above coordinates.
All the different messages that the module outputs or can be used as commands are described in detail in the documentation provided by u-blox. Just google for: 'u-blox 8 Receiver Description Including Protocol Specification'. Another option is to install u-blox's software called 'u-center' and copy the messages from there. Generally, it can be used to connect to a u-blox module and send the commands from the GUI, however, TT7F board doesn't provide any simple way to connect just the module to a PC.


SPI ARM_SPI.c
The SPI interface is used to communicate with Si4060. Similarly to UART its speed is defined by a master clock divider (SPI_SCBR in ARM_SPI.h) hence it's current processor frequency dependent. The Si4060's SPI interface can run at up to 10MHz which is useful for the more elaborate modulations (GFSK utilized in APRS requires quick communication). I used 8MHz (SPI_SCBR 8) when running of the 64MHz PLL and 6MHz (SPI_SCBR 2) with a 12MHz crystal oscillator.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
uint16_t SPI_write(uint16_t data, uint8_t lastxfer)
{
 ...

 if (lastxfer)
 {
  val = data;
  SPI->SPI_CSR[0] &= ~(SPI_CSR_CSAAT);
 }else{
  val = data;
  SPI->SPI_CSR[0] |= SPI_CSR_CSAAT;
 }

 ...
}

The library contains a simple function SPI_write() that sends a byte of data and returns the byte received from the other device. The 'lastxfer' parameter is used to mark the last byte in a series of SPI_writes to de-assert the Chip Select line.


Si4060 ARM_SI4060.c
The transmitter's documentation describes numerous ways to construct packets with preamble, sync word, header, CRC checksum, etc. to transmit in the 'FIFO mode'. I haven't tested this method at all. All my implementations used the 'TX Direct mode' exclusively. In this mode, the data to be transmitted are fed to the device in real time through the GPIO1 pin. Thus all the timing and bit streaming is done by the TT7F's microcontroller.

1
2
3
4
5
SPI_write(0x20, 0);  // CMD: GET_INT_STATUS
SPI_write(0x00, 0);  // PH_CLR_PEND
SPI_write(0x00, 0);  // RESERVED0
SPI_write(0x00, 1);  // CHIP_CLR_PEND
SI4060_CTS_check_and_read(9); // check clear-to-send (CTS) 

After mandatory initialization of the Si4060 transmitter via SI4060_init() which temporarily sets the SDN pin HIGH and runs the POWER_UP command, the device is ready. Above is a typical command structure. First byte is the command itself followed by a few arguments (in this case clearing interrupts). After the last byte and de-asserting SPI's Chip Select pin, check clear-to-send routine is entered.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
while(dt != 0xFF && CTS_timeout)
{
 CTS_timeout--;
 SPI->SPI_CR = SPI_CR_SPIEN; // enable SPI if disabled
 SPI_write(0x44, 0);  // READ_CMD_BUFF
 data = SPI_write(0x00, 0);
 dt = (uint8_t)data;
 if(dt == 0xFF) break;
 SPI->SPI_CR = SPI_CR_SPIDIS; // workaround to de-assert chip select
}

The function repeatedly sends the READ_CMD_BUFF command until Si4060 responds with 0xFF signalling the command was executed and any potential data is ready to be clocked out.
The detailed API documentation is up for download at www.silabs.com as 'EZRadioPRO API Rev C2A-A2A'.

#define FREQUENCY_RTTY  434287000UL
#define TX_DEVIATION_RTTY 16
#define POWER_LEVEL  0x7F

#define COMPARE_VALUE_RTTY 2500
#define TIMER_CLOCK_RTTY 0x03

SI4060_init();
SI4060_setup_pins(0x02, 0x04, 0x02, 0x02, 0x00, 0x00);
SI4060_frequency_offset(0);
SI4060_frequency(FREQUENCY_RTTY);
SI4060_frequency_deviation(TX_DEVIATION_RTTY);
SI4060_power_level(POWER_LEVEL);
SI4060_modulation(2, 1);    // FSK, asynchronous
SI4060_change_state(0x07);    // TX state

TC0_init_RTTY_NORMAL();
SI4060_tx_RTTY_string_TC0("$$$$TT7F,2,tc0*A55D\n\0");
TC0_stop();

SI4060_deinit();

These are the typical steps to initiate a simple RTTY transmission. The code first sets all sorts of parameters such as selecting FSK modulation, the deviation of the two frequencies, the power of the output signal and commands the transmitter to begin transmission by entering 'TX state'. Right after that it configures TimerCounter0 to handle the timing of individual bits. The 'TIMER_CLOCK_RTTY' and 'COMPARE_VALUE_RTTY' define directives are responsible for transmission's baud rate. The former selects the clock utilized by the timer (in this case MCK/128) while the latter defines the number of clock periods until rising the CPCS flag in the TC0 status register and reseting the counter (2500 resulting in 200 baud rate).


Example Implementation
Putting it all together, the main() function may look something like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int main(void)
{
 SystemInit();   // initialize the Main CLK at 64MHz
 WDT->WDT_MR = WDT_MR_WDDIS; // disable WatchDog
 SysTick_delay_init();  // configure the delay timer

 LED_PA0_disable();
 LED_PB5_disable();

 UART1_init();   // initialize communication to u-blox
 SPI_init();   // initialize communication to Si4060
 ADC_init();   // initialize ADC pins

 UBLOX_request_UBX(setNMEAoff, 28, 10, UBLOX_parse_ACK);
 UBLOX_request_UBX(setNAVmode, 44, 10, UBLOX_parse_ACK);

 while(1)
 {
  telemCount++;

  ADC_start();
  AD3data = ADC_sample(3, 100);
  AD9data = ADC_sample(9, 100);
  AD15data = ADC_sample_temperature(100);
  ADC_stop();

  UBLOX_request_UBX(request0107, 8, 100, UBLOX_parse_0107);
  UBLOX_construct_telemetry_UBX(TXbuffer, sequence);

  SI4060_init();
  SI4060_setup_pins(0x02, 0x04, 0x02, 0x02, 0x00, 0x00);
  SI4060_frequency_offset(0);
  SI4060_frequency(FREQUENCY_RTTY);
  SI4060_frequency_deviation(TX_DEVIATION_RTTY);
  SI4060_power_level(POWER_LEVEL);
  SI4060_modulation(2, 1);
  SI4060_change_state(0x07);

  TC0_init_RTTY_NORMAL();
  SI4060_tx_RTTY_string_TC0(TXbuffer);
  TC0_stop();

  SI4060_deinit();
 }
}

After the opening initializations the script enters a while loop that repeatedly steps through data collection (ADC measurements, ublox requests), transmitter initialization, transmission and transmitter de-initialization. Then it starts another round.

#include "sam.h"
#include "ARM_ADC.h"
#include "ARM_DELAY.h"
#include "ARM_LED.h"
#include "ARM_SI4060.h"
#include "ARM_SPI.h"
#include "ARM_UART.h"
#include "ARM_UBLOX.h"

To make the basic code complete, it requires the involved libraries to be included at top of the script.

uint32_t sequence = 0;

uint16_t AD3data = 0;
uint16_t AD9data = 0;
uint16_t AD15data = 0;

volatile uint32_t SI4060_buffer[16];
volatile uint8_t TC_rtty_gfsk_lookup = 0;
volatile uint32_t TXdone_get_data = 1;
volatile uint8_t TXdata_ready = 0;
uint32_t TXmessageLEN = 0;
uint16_t APRS_packet_size = 0;
uint8_t TXbuffer[TX_BUFFER_SIZE];
uint8_t APRSpacket[APRS_BUFFER_SIZE];

volatile uint8_t UART0_RX_buffer[UART0_BUFFER_SIZE];
volatile uint8_t UART1_RX_buffer[UART1_BUFFER_SIZE];
volatile uint32_t UART0_buffer_pointer = 0;
volatile uint32_t UART1_buffer_pointer = 0;
volatile uint32_t UART0_temp = 0;
volatile uint32_t UART1_temp = 0;

uint8_t GPS_UBX_error_bitfield = 0b00100000;
uint16_t GPS_NMEA_latitude_int = 0;
uint32_t GPS_NMEA_latitude_dec = 0;
uint16_t GPS_NMEA_longitude_int = 0;
uint32_t GPS_NMEA_longitude_dec = 0;
uint8_t GPS_NMEA_NS = 1;
uint8_t GPS_NMEA_EW = 1;
int32_t GPS_UBX_latitude = 0;
int32_t GPS_UBX_longitude = 0;
float GPS_UBX_latitude_Float = 0.0;
float GPS_UBX_longitude_Float = 0.0;
int32_t GPSaltitude = 0;
uint8_t GPShour = 0;
uint8_t GPSminute = 0;
uint8_t GPSsecond = 0;
uint8_t GPSday = 0;
uint8_t GPSmonth = 0;
uint16_t GPSyear = 0;
uint8_t GPSsats = 0;
uint8_t GPSfix = 0;
uint8_t GPSnavigation = 0;
uint8_t GPSpowermode = 0;
uint8_t GPSpowersavemodestate = 0;
int32_t GPSgroundspeed = 0;
int32_t GPSheading = 0;
uint32_t telemCount = 0;
uint32_t telemetry_len = 0;
int32_t GPS_UBX_latitude_L = 0;
int32_t GPS_UBX_longitude_L = 0;
int32_t GPSaltitude_L = 0;
uint8_t GPS_NMEA_NS_L = 0;
uint8_t GPS_NMEA_EW_L = 0;
uint16_t GPS_NMEA_latitude_int_L = 0;
uint32_t GPS_NMEA_latitude_dec_L = 0;
uint16_t GPS_NMEA_longitude_int_L = 0;
uint32_t GPS_NMEA_longitude_dec_L = 0;

And a number of variables and arrays defined ahead of the main() function.
Clicking 'Build Solution' in Atmel Studio checks the script for errors and compiles it.
It also provides some useful information in the Output window such as how much flash memory the instructions will take up (Program Memory Usage) and what portion of SRAM is already used up at compile time (Data Memory Usage).
Returning to SAM-BA, all that needs to be done here is to insert path leading to the compiled .bin file and click 'Send File'. After refreshing the memory display it now shows the flash filled with instructions from the 0x00400000 address onwards. Last thing to do is to 'Execute' the 'Boot from Flash (GPNVM1)' command and physically reset the microcontroller.
The result can be checked in an SDR software at the appropriate frequency (due to me using an uncalibrated RTL-SDR dongle here, the displayed frequency is off by about 60kHz).
And a successfully decoded telemetry string in dl-Fldigi.

This article hopefully outlined some of the basics in programming TT7F and SAM3S8 in general. A couple more specialized blog posts concerning software may appear in the future.

Thursday, 25 August 2016

The TT7F Payload

callsign: TT7F
frequency: 434.287Mhz
modulation: RTTY, 100 baud, 8N2, 450Hz shift
date: 26th August 2016
time: 6:00-7:00UTC (8:00-9:00CEST)
place: Roznov pod Radhostem, CZ

balloon: Hwoyee 1600
payload: TT7F tracker, Mobius ActionCam, parachute (155g)
gas: Hydrogen (1.996m3)
target neck lift: 626g
target ascent rate: 4m/s
predicted burst altitude: 42383m
predicted time to burst: 177min

A year after my unideal 40k attempt with TT7-40, I have finished the first 'light' version of the new TT7F tracker and I intend to test it with an UP and DOWN flight. Initially, I wanted just a smaller balloon without a camera, but as I got to the planning itself, I couldn't resist to have another go at 40km with a proper 1600g balloon. Another motivator was the fact that I still haven't managed to get a video of a full flight. With TT7-40 the camera gave up after 5 hours some 15 minutes before the delayed burst.
That led me to the first upgrade. I exchanged the 2400mAh LiPo battery for a bigger 3200mAh one. That meant 24g mass increase (42g to 66g) while potentially providing about 1.5 hours additional recording time (taking into account the expected decrease in the battery's capacity at low temperatures). The first freezer test with just the camera and the battery in a sealed bag (to avoid humidity) didn't go that well. A direct contact between the battery and a frozen chicken turned out to be beyond its low temperature tolerance and it stopped recording after about 2 hours (kept on recording after being warmed up again outside the freezer).
For the payload box, I used a simple design carved out of a 2cm thick board of extruded polystyrene. I also made openings for the camera's buttons and LEDs in the top to be able to control it even after the payload is all sealed.
This time, the package coped with -18°C inside the freezer so well that the recording stopped only after filling the whole 64GB SD card some 9 hours later.
Another issue that I wanted to improve upon from the last time was stabilizing the camera and avoiding
excessive rotation. I started by ordering ball bearing swivels to use in a few places.
Next, I tried to equip the box with a couple of stabilizing elements. I then watched it perform in gust wind and had it record sample videos for comparison. It turned out that although the elements reacted to the wind and turned in its direction it may not be what you want in a situation where the winds come in gusts from different direction. The best footage came out of the most uniform payload without any additional elements.

One more thing about the camera. Since the LiPo battery compared to the original one is too big to fit inside the camera's casing, it has happened to me a couple of times that a slight unintended pull on the extension cord led to the camera switching off. The battery connector on the PCB is quite small and probably not robust enough to withstand all sorts of pulling without temporarily stopping power going in. I tried testing this with my payload box by furiously shaking it and throwing it around. The camera kept on recording despite all of this so I consider the payload being prepared for such inconvenience.
This specific TT7F tracker doesn't have the LTC3105 nor the MT9D111 connector and regulators soldered on. Since I am still some time before finishing and properly testing the SSDV capabilities, I decided to fly a 'light' version of the tracker this time. With this version's current consumption of about 130mA (when powered by a LiPo ~3.7V), I opted for two AAA batteries to power it and fitted a standard AAA battery holder on the backside of the tracker.
The Energizer Ultimate Lithium batteries should offer about 1200mAh while weighing 15g. With those I did another freezer test.
The resulting data as received directly from the tracker show that the two Energizers managed to power the tracker for 7.5 hours before dying. One thing I haven't solved about the tracker is a relatively high volatility of the ADC measurements when powered through the TPS63031 (battery powered). When the tracker is powered via the USB interface, the measurements are much more stable.
For the actual flight, I constructed a 434MHz ground plane antenna from a guitar string (0.2mm thick) as used on some previous light weight floaters.
The dimensions were 164mm for the driven element and 328mm for the ground plane strings. Attached were to the GND pads and vias on the bottom of the PCB.
This antenna weighs only 0.26g.
The new antenna tested at about 400m with line of sight. Both RTTY and APRS packets decoded without problems.
The finished tracker weighs 8.39g while with the batteries the total rises to 23.57g.
The tracker also transmits an APRS packet once a minute. However, the Si4060 transmitter is equipped with 434MHz matching and the antenna is likewise designed for the same band, so the transmitted power at the 2m band (144.8MHz) isn't great. I managed to decode the packets myself from a few hundred meters distances even with some obstacles in the way.
However, I wasn't able to reach a proper IGate. Here, I was hoping to be heard by one at the top of the mountain in the distance (18.6km). The tracker still had just a piece of wire instead of the ground plane antenna at that time, and the balloon's altitude may provide a better path for the signal, but the APRS performance is an open question.
I also tested the whole set running as it was supposed to to verify that there was no interference from the camera to the GPS module. There are three ball bearing swivels in this setup. One underneath the camera box holding the tracker on a string. The second at the top of the camera box and the third at the balloon end of the cord. Despite that I noticed that the swivel's ability to counteract the rotations depended on the type of string used.
Initially, I opted for the bottom white string due to its lower weight (0.68g per meter). However, the string seemed to be a little elastic and kept acumulating energy that led to continual rotations and counter rotations of the payload. The tricolour string (0.64g per meter) suffered from similar problems. In the end, I found the upper white string to improve the swivel's performance significantly, but it was bought out by its higher mass (1.23g per meter). The cord's material is probably braided nylon while the unsuccessfull candidates were cotton made.
This is the final payload box with all the cords. The knobs tied from the nylon cord didn't seem to by very secure, so I rather sewed the endings together to make sure. The string between the payload box and the tracker is approximately 1m long while the one between the box and the ballon is 4m. I also secured the tracker's string around the battery as opposed to just relying on the extruded polystyren not riping apart.
The payload box with the camera, battery and all the strings weigh 115.41g.
For the parachute, I decided to try and make one based on Ed Moore's instructions from his UKHAS talk. It's a cross form parachute with an area of 0.145m2 (length 0.55m, width 0.153m) made of Skytex, a material used in paraglides.
After initial tests, I replaced the white cotton cords with the tricolour ones because of too much tangling. The finished parachute weighs 12.56g plus 1.39g for another swivel.


Here, I am testing the parachute with a 200g weight. Equations suggest a landing speed of 4.7m/s with a 150g payload and 6.7m/s with 300g. The total mass to be lifted by the balloon is about 155g. However, it is problematic to predict how much of the balloon itself will remain attached to the system after burst and even more problematic to estimate how much drag will the balloon remnants add to the parachute's drag. Nevertheless, these are the parameters I am going with.
Contrary to my previous two flights, I decided to use hydrogen this time. It is much cheaper than helium and has lower density as well, so it should allow the balloon to ascend even higher. This is a 40l cylinder filled to 150atm giving 5.6m3 of usable gas. It is about 1.7m tall and weighs 50kg, so it isn't ideal for too much moving and handling.
I had in hand only one regulator that turned out to have a reverse thread so it wouldn't fit. Trying releasing a little gas by slightly turning the valve directly turned out to be doable, therefore I'll simply fasten a hose to the output and fill the balloon this way.


I couldn't resist. I had to verify that it really was hydrogen :-).

Adding it all together, the tracker's 23.57g, the parachute's 13.95g and the remaining payload's 115.41g total to 152.93g to be lifted (further calculations will consider 155g to account for any aditional ties). For intended 4m/s ascent rate, the Random Engineering's calculator gives 626g neck lift for 177 minutes of ascent to 42383m burst altitude.
This is the latest prediction some 24 hours before the launch. I made a few of them with varying parameters. The purple dots are ascent rate of 3 and 5m/s. The blue dots are descent rate of 5 and 10m/s. And the red dots are burst altitude of 41000 and 43000m.