Sunday, April 17, 2016

Blinky lights

Wisp1 LED uses and meanings

The Wisp1 has three LEDs, Green, Red and Blue.   Their blinking rate will convey status.

  • Green is the overall "system" LED.  
  • Red is for GPS status.  
  • Blue is for the Si5351 transmitter status.  

My intention is to have the LEDs active for the first 30 minutes after the tracker is powered on.  After that, they will be disabled to conserve power.

LED Color Blink Rate Meaning
GreenOff Tracker powered off, or STOP mode
Green3 hz RTC problem - Switched to LSI
Green1 hz Tracker up and operating normally
GreenOn System Error - Tracker abnormally stopped
RedOff GPS Off
Red3 hz GPS Seeking Time
Red1 hz GPS Seeking Lock
RedOn GPS Locked
BlueOff Si5351 powered off.  Not Transmitting
Blue1.4648 hz Transmitting
BlueOn Si5351 Error


Establishing "Top of Second"

Establishing Top of Second

The WSPR protocol requires that sender and receiver's times are relatively in sync with UTC. Transmissions happen on the "0 second" boundary on even numbered minutes.  14:02:00 and 14:06:00 are examples of valid times to start a WSPR transmission.

On the tracker, we have a GPS chip.  Even before a full GPS coordinate lock is established, the chip first gets a time lock with the satellites.  The time is uttered in the GPS NMEA strings and can be used to synchronize the beginning of the transmission.

The Delay

The GPS chip establishes what time it is, and then sends the NMEA string down the UART at, say, 9600 baud.  It takes some time for that NMEA string to be uttered, and for the microprocessor to decode it, and decide what second it is.  While not a horribly long time, it could cause us to get off by a bit.

Say we had a NMEA string like this:

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n

That's 66 characters long.   With 8 bits, 1 start, 1 stop, that's 10 bits per character - 660 bits of data. At 9600 baud, that's 68.8ms.  Assuming some delay before the characters are uttered, and some processing time after they're received, and we could be upward of a few hundred milliseconds off, and that assumes that the GPS utters the NMEA string immediately, beginning at the top of the second.

An alternative - using the TIMEPULSE

The U-Blox MAX7 GPS chip can generate a time pulse, and it can be configured in a number of different ways.  For the Wisp1, I've opted to configure it as follows.  The Rising Edge of the signal on the TIMEPULSE line will be at Top of Second.  The TIMEPULSE line will generate an EXTI interrupt on leading edge.  So, I'll get an interrupt at the top of each second.   In the ISR for that interrupt, I will zero my "milliseconds" counter that I get from HAL_Systick().   After that, I will await the next NMEA string to come in, and parse the time.  From that string, I can retroactively determine which second the ToS interrupt was for.  My milliseconds counter will then be counting right from ToS.

Sneaky Tricks - Time Pulse "Locked"

The U-Blox allows for configuration of the frequency and duty cycle of the time pulse.  I'm configuring the pulse at 1 HZ, with a 10% duty cycle, meaning it's held high for 100ms, and down for 900ms.   
The U-Blox also has another feature that I intend to use to simplify life a little.  It has settings for both "Time Pulse" and "Time Pulse Locked".  The former is how often to generate a pulse, regardless of lock status, and the second is the rate at which to generate time pulses once the GPS has locked into the time.
At GPS initialization time, I will set the GPS up so that the "Duty Cycle" for the TIMEPULSE in non-locked state is 0%, and 10% in locked state.  Basically, this means that the GPS will not generate any interrupts until it has a time lock.  This avoids the situation of messing with the timers before the GPS actually knows what time it is.
Further, given that I intend to turn off the GPS between uses, I will set up the ISR for the TIMEPULSE to only receive one interrupt.  Basically, after turning on the GPS, I will receive one timepulse interrupt, at the top of a second.  I can zero my millisecond timer at that point, and await the NMEA string to determine exactly which second I'm on.  I have no further use for time pulses, as the CPU clock will keep me on track after that.
After going into STOP mode (for power conservation) and waking up again, it's likely my clock will be off.  Since I'll be powering on the GPS again each time I wake up, I'll simply add this timepulse logic to the subroutine I call whenever I power up the GPS.  The GPS will give me a good time synchronization long enough to do my transmission, and go back to sleep for the next go-around.

Sources of error

HAL_Systick()
So, when the interrupt comes in, I intend to zero my "milliseconds" counter.  However, I do not intend to mess with the counter used internally by the HAL_Systick() function.  I'm not sure if there are other dependencies there which I might confuse.  Rather than confuse the matter, I'll be keeping my own "milliseconds" counter, which is incremented by Systick.   I'll zero it when the TIMEPULSE interrupt comes in.  However, the attentive student may note that the timepulse interrupt may happen at any time as the Systick counter counts down between it's 1 millisecond pops.  A potential worst case scenario is that I zero my systick counter, and one 32 MHZ clock tick later, Hal_Systick() pops again, leaving me nearly 1ms off.  On average, though, I'm more likely to be 0.5ms off.  Given that by simply parsing the GPS strings, I'd likely be more than 100 milliseconds off, I'll live. 
GPS delays
The GPS provides for configuring compensation for "antenna cable delay" and "RF group delay". These are adjustments in nanoseconds.  I haven't bothered to configure them.  I'm sure they should be something but god only knows how I'd figure that out.  Being a very small source of error (in the nanoseconds), I'm ignoring it.

Guestimate

Just off the hip, given the average of 0.5 ms error with systick, and other error, I'm going to assume that my tracker will be within 1.0 millisecond of correct.  Close 'nuff, I think. 

Taking a few deep breaths about timing

It has to be remembered that WSPR is specified to operate if the sending and receiving stations are +/- 1 second of correct.  That means that the stations may be almost 2000 ms apart on time, and still work.  Also, the receiving station might be several thousand km away.  I've seen a pico balloon received from 3000 km away.  In air (299,700 km/s) that signal takes 10 ms to travel from sender to receiver.  So, my tracker being off by 1 millisecond is still awesome.  Also, it pays to note that some other WSPR implementations I've seen are simply parsing the NMEA strings to figure out the time, and they're operating beautifully with much higher time error than I anticipate.

Timepulse configuration

Here's the commentary from the Wisp1 source code I'm using for timepulse configuration.
    /* CFG_TP5 message: No pulse until we have time lock.  Then pulse with rising edge at top of second.
     * 0xB5, 0x62, 0x06, 0x31, 0x20, 0x00, - CFG-TP5 message, 32 bytes
     * 0x00, 0x00, 0x00, 0x00  - tpIdx=TIMEPULSE, reserved0, reserved1
     * 0x00, 0x00, 0x00, 0x00  - Antenna and RF Group Delay.  0ns each
     * 0x01, 0x00, 0x00, 0x00 - freqPeriod 1hz
     * 0x01, 0x00, 0x00, 0x00 - freqPeriodLocked 1hz
     * 0x00, 0x00, 0x00, 0x00 - pulseLenRatio (no timepulse)
     * 0x0a, 0x00, 0x00, 0x00 - pulseLenRatioLocked (10 %)
     * 0x00, 0x00, 0x00, 0x00 - userConfigDelay
     * 0x6f, 0x00, 0x00, 0x00 - flags: Active,LogGPSFreq, lockedOtherSet, isFreq, alignToTow, Polarity
     * 0xd2, 0x2b  - checksum

     */

WSPR timer - prescaler and counter

Calculating the prescalar and counter values for WSPR

I intend to use the TIM7 timer on the STM32L151CB microcontroller.  TIM7 is fed from the APB1 clock source, which can be configured via the PLL in a number of ways (see  below).

According to the WSPR spec:  "Keying rate: 12000/8192 = 1.4648 baud (tones / second).  If we flip that ratio, we will have seconds per tone:  8192 / 12000 = 0.682666 secs / tone.

The math

Doing some math, we have:

   ticks_per_tone = AHB1_MHZ ticks/sec * (8,192 / 12,000) secs/tone

The 12,000 in the denominator will cause us some fits. Breaking down 12,000, it's 3*4*1000.  The 3 is a problem.  One way to cope is by putting a "3" in the numerator in AHB1.

AHB1 options

I'm considering two speeds for AHB1 - 32MHZ, and 24 MHZ.  The HSE is running at 16mhz.  I can set the PLL multiplier to 4/2 for 32mhz, or to 3/2 for 24 MHZ.

  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL4;    // RCC_PLL_MUL3 for 24mhz
  RCC_OscInitStruct.PLL.PLLDIV = RCC_PLL_DIV2;


At AHB1 = 32 MHZ
At 32 MHZ, the math looks like this.  
   ticks_per_tone = 32,000,000 * 8,192 / 12,000
   ticks_per_tone = 32,000 * 8,192 / 12
   ticks _per_tone = 8,000 * 8,192 / 3
   ticks_per_tone = 21,845,333.33333

At AHB1 = 24 MHZ
At 24 MHZ, the math looks like this.  
   ticks_per_tone = 24,000,000 * 8,192 / 12,000
   ticks_per_tone = 2,000 * 8,192 
   ticks_per_tone = 216,000,000 

Prescalar and Counter

The prescalar and counter for TIM7 are 16 bit values.  Given that 8,192 is in the ticks_per_tone calculation, it becomes an attractive option for the prescalar.

AHB1 Prescaler Counter
24,000,0008,1922,000
32,000,0008,1922,666.666 (round to 2,666)

Impact

Switching AHB1 to 24mhz is a pretty minor concession.  The ADCs would run a little slower, but I can think of little impact on functions I'd need for WSPR.

On the flip size, AHB1 at 32mhz means that each tone will be off by (.333 * 8192) = 2,730.66 ticks per tone.  That's 2,730.66 / 32,000,000 = 0.0853 milliseconds per tone.  Across the 162 tones of a WSPR message, we would end off by 13.82ms.  

I'll be doing another blog post on establishing the "Top Of Second" for beginning the WSPR transmission.  WSPR documentation shows that your clock should be within +/- 1 second.  My objective is to be within 2ms.  I think an added deviation of 13.82ms by the end is negligible in the grand scheme of things.  I'm undecided at the moment, but I've tried both settings and the timing seems right, with some superficial stopwatch testing.

Sunday, April 3, 2016

Copying STMCubeMX libraries into Eclipse


Building STM32CubeMX code in Eclipse


The process of building code in Eclipse from STM32CubeMX can be complicated.  Below are step by step instructions I've used to successfully build code for an STM32L151CB processor.  They should work for any STM32Lxx processor, I suspect.

Export the libraries from Cube

From STM32CubeMX

Select Project / Settings

Notice, I have my IDE set to EWARM.  There is no option for Eclipse, unfortunately.
         
Select: Project / Generate Code

The resulting source directory look like this:

Setting up the project in Eclipse

    Create new project

      1. File/New/C++ Project
        1. Set Project Name.  
        2. Select "Hello World ARM Cortex-M C/C++ Project
        3. Target Processor 
          1. Processor Core:  Cortex-M3
          2. Clock (Hz): 16000000
          3. Flash Size (kB): 128
          4. Ram size (kB): 20
          5. Use System Calls: "Freestanding (no POSIX system calls)
          6. Trace output: ARM ITM (via SWO)
          7. Checkboxes
            1. Select only:
              1. Check some warnings
              2. Use -Og on debug
        4. Folders
          1. Include folder: include
          2. Source folder: src
          3. System folder: system
          4. CMSIS library folder: cmsis
          5. C library folder: newlib
          6. Linker scropts folder: ldscripts
          7. Vendor CMSIS name: stm32l1xx
        5. Select Configurations
          1. Select bot Debug and Release.
        6. Tool Chains
          1. Take defaults
        7. Finish

    Delete Prototype files 

    We'll be using the files from Cube.  We get rid of the ones placed in here by Eclipse.  We'll just use the hierarchy and some of the configuration it created.

    From within the Eclipse IDE, delete the following files from the new project:
      1. <Eclipse_Project>/system/include/cmsis/*  (leave the empty folder)
      2. <Eclipse_Project>/system/include/cortexm  (whole directory)
      3. <Eclipse_Project>/system/src/cmsis/*
      4. <Eclipse_Project>/system/src/cortexm  (whole directory)
      5. <Eclipse_Project>/system/src/newlib (whole directory)
      6. <Eclipse_Project>/src/*.*
      7. <Eclipse_Project>/include/*.*

      Drag and drop files from Cube

      Drag and drop files from Windows (or your platform) into the Eclipse IDE.  That way, they will automatically be added to the makefiles.  When prompted to "Copy" vs "Link the files", select "Copy".

      Header files
        1. Copy <cube_output>/Drivers/CMSIS/Include/*  to <Eclipse_Project>/system/include/cmsis
        2. Copy the following three files from <cube_output>/Drivers/CMSIS/Device/ST/STM32L1xx/Include/to <Eclipse_Project>/system/include/cmsis
          1. stm32l151xb.h 
          2. stm32l1xx.h 
          3. system_stm32l1xx.
        3. Copy <cube_output>/Drivers/STM32L1xx_HAL_Driver/inc/* to <Eclipse_Project>/system/include/stm32l1xx/
      Source files
        1. Copy the following two files from <cube_output>/Drivers/CMSIS/Device/ST/STM32L1xx/Source/Templates/ to <Eclipse_Project>/system/src/cmsis
          1. system_stm32l1xx.c
          2. gcc/startup_stm32l151xb.s
        2. Change the assembly file suffix to ".S"
          1. Rename <Eclipse_Project>/system/src/cmsis/startup_stm32l151xb.s to startup_stm32l151xb.S
        3. Copy <cube_output>/Drivers/STM32L1xx_HAL_Driver/src/* to <Eclipse_Project>/system/src/stm32l1xx/
        4. Copy <cube_output>/src/* to <Eclipse_Project>/src
        5. Rename <Eclipse_Project>/src/main.c -> main.cpp
        6. Copy <cube_output>/inc/* to <Eclipse_Project>/include
      Block unused files
      Right-click on the following files in Eclipse, select "Resource Configurations / Exclude from Build". Hit "Select All" to choose both Debug and Release, and hit "OK".
      • <Eclipse_Project>/system/src/stm32l1xx/stm32l1xx_hal_msp_template.c

      Update properties / includes, and pre-processor macros

      Preprocessor definitions
      For simplicity, set the preprocessor defines the same for the Assembler, C, and C++.  The following defines are set:
      • STM32L151xB
      • USE_HAL_DRIVER
      • OS_USE_TRACE_ITM
      • TRACE
      • DEBUG
      • ARM_MATH_CM3
      • HSE_VALUE=16000000
      • USE_FULL_ASSERT


      Fix Linker parameters

      Under Properties / C/C++ Build / Tool Settings / Cross ARM C++ Linker / 

      • General
        • All checkboxes cleared except "Remove Unused Sections"
      • Miscellaneous
        • All checkboxes cleared.
        • Other linker Flags: --specs=rdimon.specs -Wl,--start-group -lgcc -lc -lc -lm -lrdimon -Wl,--end-group
      1. Fix ldscripts/mem.ld
      2. MEMORY
        { 
          RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16K  
          CCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 0K  
          FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K 
          RW_EEPROM (rw) : ORIGIN = 0x08080000, LENGTH = 4K  
          FLASHB1 (rx) : ORIGIN = 0x00000000, LENGTH = 0  
          EXTMEMB0 (rx) : ORIGIN = 0x00000000, LENGTH = 0  
          EXTMEMB1 (rx) : ORIGIN = 0x00000000, LENGTH = 0  
          EXTMEMB2 (rx) : ORIGIN = 0x00000000, LENGTH = 0  
          EXTMEMB3 (rx) : ORIGIN = 0x00000000, LENGTH = 0  
          MEMORY_ARRAY (xrw)  : ORIGIN = 0x20002000, LENGTH = 0  
        }
        

      Source code modifications

      The trace code from Eclipse has to be modified slightly to compile with the Cube libraries.

      Modify system/src/diag/trace_impl.c
      1. Change line 10 include from "cmsis_device.h" to "stm32l1xx.h"

      Build the code