Friday, February 22, 2019

DTMF Board with BU8872


I wanted to create an Arduino shield with a BU8872 DTMF chip on it so that I can decode DTMF from an audio source with an Arduino sketch.  Since the chip itself was only available in a surface-mount SSOP-16 form factor, I elected to do the entire board with surface mount components.


I followed the recommended schematic in the datasheet.  I'm routing the audio input into the A5 pin on the arduino header.  This was done just in case I wanted to attempt audio decoding natively on the Arduino.  The three digital pins map to D5,6,7 on the Arduino.  I found a surface-mounted crystal that should work with the chip as well.

Board Layout

The layout was pretty straightforward.  Because I was hand-etching this board, I elected to place components on the back of the board.  This was to ease the soldering of the traces to the header pins.  In a stacking header, the bottom of the shield is the pin side, and the top has the plastic female sockets.  It would be difficult to solder under those plastic sockets to establish the connection, so I put everything on the bottom of the board.

Note, I used a ground plane on this board, but the traces bisected the board.  Though not pictured in this board layout, after assembling the board, I bridged a few traces with 1206 Zero ohm resistors to jumper the ground connection around so that I had a full ground plane.  

Finished board

The finished board is pictured here.  As mentioned above, you can see the two 1206 zero ohm resistors bridging the long traces, assuring that the ground plane has conductivity across the entire board.  In a future revision, I'll formally put pads on the board layout to document their placement.

Test Setup

You can see the board sandwiched on the arduino to the right.  There is a logging shield on top, then the DTMF shield, and finally, an Arduino Leonardo underneath.  I actually tested this using a VHF receiver module, and transmitting from an HT to send the tones. 

Coding challenges

The code for this board is fairly straightforward.  I only ran into one vexing issue  The "DST" pin is supposed to drop cleanly after the DTMF audio tone is dropped.  In practice, there seems to be some "jitter" where the DST pin oscillates between 0 and 1 at the end of the tone.  The datasheet has a little RC filter which might have compensated for that, but I really didn't understand how to calculate appropriate values for the filter components.  In a future board, I'll probably design in the filter, and if I don't want it, I can always jumper it with 0 ohm resistors.  Anyway, the code snip below is very simple, and demonstrates what I had to do to get around the jitter issue on the DST pin.

The Code

#define EST_PIN 5
#define ACK_PIN 6
#define SD_PIN  7
#define EST_JITTER    40         // Number of consecutive reads of EST that must be "off" before assuming tone is over

int ReadDTMF()
  int i, dtmf;

  dtmf = 0;
  // Iterate for the 4 bits of data we need to get from the BU8872
  for (i = 0; i <4; i++) {
    digitalWrite(ACK_PIN, 1);               // Pull ACK high
    delayMicroseconds(10);                  // Wait a bit
    dtmf += (digitalRead(SD_PIN) << i);     // Read the value from the SD pin, shifted appropriately and add to the rest
    digitalWrite(ACK_PIN, 0);               // Pull ACK low
    delayMicroseconds(10);                  // Wait a bit 

void setup() 
  while(!Serial);         // Arduino Leonardo issue - wait for the serial port
  pinMode(EST_PIN, INPUT);
  pinMode(ACK_PIN, OUTPUT);
  pinMode(SD_PIN, INPUT);
 // When powered up, the BU8872 is awakened by sending 4 pulses on the ACK pin.  
 // A ReadDTMF will do the trick. We just ignore the result.
  int ignored = ReadDTMF();    

void loop() 
    int dtmf;
    int i;

    // If we have a DTMF tone, grab it
    if (digitalRead(EST_PIN)) {
       dtmf = ReadDTMF();
       Serial.print("Got DTMF code: ");  Serial.println(dtmf);

      // We have jitter on the EST pin.  Make sure it is clear for several consecutive milliseconds before assuming the tone is over.
      i = 0;
      while (i < EST_JITTER) {
         if (digitalRead(EST_PIN)) {    
            i = 0;    // If the pin jitters high, restart our count
         } else {
      Serial.println("RST Clear");

Sample Output

Future Enhancements

  1. Add the RC filter to the output section.
  2. Test RC filter values to see if the jitter can be eliminated.
  3. Put placeholders in the schematic and board layout for the ground plane jumpers.
  4. Add a trim pot on the audio input line.
  5. Bonus points: Add a test mode to the board and analog pin that puts a peak detector circuit on the pin (or maybe a separate one) and uses the ADC to calculate the RMS voltages of the input audio, to facilitate adjusting the input audio volume.

Bill of Materials

RefID     Digi-Key Description QTY
C1, C2 445-1270-1-ND CAP CER 12PF 50V 5% NP0 0603 2



CAP CER 0.1uF 25V 10% X7R 0603

C5 399-6049-1-ND CAP ALUM 10UF 20% 63V SMD 1
Y1 CTX1023CT-ND CRYSTAL 4.194304MHZ 12PF SMD 1

Monday, March 5, 2018

PCB Etching notes

PCB Etching Notes

I did a little research, and found it still took some trial and error to come up with a PCB etching procedure that worked reliably for me.  I also noted that, since I don't etch boards all that often, I kept forgetting how I do it.  So, with that in mind, here are my notes on PCB Etching for my "Note To Self" category.


Circuit design

  • I use Diptrace to design my PCBs
  • Set the trace width and spacing to 16 mil, if possible.  Thicker traces like this work better for hand etching.  I've succeeded with 10 mil, but the traces were very thin and uneven.  Thicker is better.
  • To put letters on the board (copper characters), use "Objects / Place Text".  Hit Enter to complete text entry.  Then right click on the text.  Under "Properties" set the "Type" to "Signal".  That will leave the letters as copper on the board, and leave some space around it.

Saving Gerbers

  • Nothing special.  Save the Top layer for use in etching.

Print using Gerbv

  • Note, I use Gerbv in Windows.  I don't know how these instructions might work on Linux or Mac systems.
  • By default, gerbv will show the layer in color, rather than in a strong black monochrome.  I set the following properties before printing:
    • Under "Layer / Change Color"
      • Set Opacity slider to 255.
      • Set Color name to "#09090e"
      • The image will turn black on the screen.  
    • Under "Layer / Modify Orientation"
      • Set "Mirroring about Y axis"
      • This adjusts for the fact that we're putting the paper face down, reversing the image on the copper.
  • Do test prints, check that it's very dark, and that the size is right.
  • Laser print on the Gloss paper and cut it out.

Copper Clad preparation

  • Scrub the copper clad with steel wool to get it shiny.
  • Clean it with Isopropyl alcohol.
  • Keep fingers away from the surface.

Toner transfer

  • Use 1 or 2 pieces of scotch tape to adhere the gloss paper, face down, onto the copper clad.
  • Warm up the Laminator on the 5 mil (hotter) setting.
  • Run the copper clad, with paper on upper face, through the laminator about a dozen times.  I rotate the copper clad end to end as I do it.  After about 8 passes, as the copper is getting really hot, I rotate the copper clad 90 degrees and run it through sideways a few times.  When I think it's nearly done, I may do a pass or two with the paper side down.
  • The copper clad should be unpleasantly hot to handle by the time you're done.
  • After about a dozen passes, drop the copper clad into a pan of water.  Keep it submerged for about 5-7 minutes.
  • Remove the copper clad from the water, and peel off the paper.  If all goes well, the paper should pull away leaving the toner on the board.
  • The toner should be well secured to the board.  Go ahead and rub it with your fingertip to make sure that any glue or paper pulp is removed from the bare copper.  Swish it in the water and make sure the copper looks clean.
  • As the toner dries, it's not uncommon for it to turn a bit white.  That's OK.  When it does that, make sure the bare copper spots (to be etched) do NOT have white on them.  If they do, the acid won't etch the copper away.  I usually just keep rubbing with my fingertip until the copper areas remain copper colored when the board is dry.


  • Dry the board off, and then drop it into a pan of the etchant.
  • It takes about 10 minutes or so for the copper to come off.  Note, the copper turns pink during one stage of the etching.  That's NOT the fiberglass PCB.  The pink copper is still conductive, and needs to be etched away.  Let is soak longer.
  • When it's done, you should see yellow PCB where the etching occurred.
  • Remove from the etchant, and rinse with water.
  • Note, you can save the etchant and reuse it.
  • After etching is done, remove the toner from the board with acetone.  I put some on a few napkins and scrub the board.
  • Rinse the board off again, until it's nice and clean. 
  • Inspect the copper and traces to make sure it all came out right.

Liquid Tin

  • I put liquid tin on the board after etching.  It protects the copper from tarnishing so that soldering will be more effective later.
  • Put the PCB in a dish and pour liquid tin on it.  The copper will turn silver immediately.
  • Rinse with water.  Save the unused liquid tin.

Monday, January 8, 2018

Hojo and the case of the deaf Tentec Omni V

Hojo and the case of the deaf Tentec Omni V

A friend from the local ham club handed me his Tentec Omni V.  He hadn't used it in a while, and was preparing to sell it to another ham when he noticed it appeared to be very weak on receive.  He asked me to take a look.

Power supply

Right off the bat, I checked the power supply.  It was reading low.  The trimmer pot was right next to the activity LED, so I couldn't resist dialing it up to a proper voltage.

Initial problems

I powered up the rig and found the LED display blank.  I opened the covers and wiggled a few things directly behind the LED display.  It flashed back on.  The board was not seated very well, apparently.

At the same time, one of two incandescent bulbs behind the S-meter was intermittent.  I snugged up the fixture with a pair of pliers, and the bulb became more reliable.

Having taken care of those little physical problems, I could finally try injecting a signal into the radio.  Sure enough, it was quite hard of hearing.  I needed a signal of about -20 dbm to be discernible.  I confirmed the problem existed across multiple bands.


I opened up the radio and started tracing the signal.  I injected a tone on 14 Mhz (1) and followed the path.  It got through the antenna selection circuits and initial attenuator just fine.  It reached the front-end mixer board (2), and exited that board mixed to 9 Mhz (3).  I also verified that the signal disappeared when the injected signal was stopped.  So far so good. The signal passed through the 9 MHZ IF board (3)  -> (4) unmolested and was amplified slightly.  From there, it went into the Pass Band Tuning board (4), and finally into the AF/IF board (5).  There's where the trouble began.

I measured a good signal coming into the board (5 above).  It passes through a few amplifier stages first thing.  I checked at the end of that stage and found no signal.  Tracing back, I found the signal disappeared after passing through a J310 Fet.

I next confirmed that there was power on the "+REG" rail.  It was reading 8.78 volts.  Unfortunately, I had no idea what the voltage was supposed to be.  After about 40 minutes of tracing the +REG rail back to a power board, I found a knockoff sentence in the service manual that +REG should be 8.5 volts.  So, it was a touch high, but shouldn't have been a problem for this situation.

Having narrowed it down to this Fet, I checked my parts bin, and voila, I had a few in stock!

The repair

The board had approximately a jillion connectors on it.  I carefully photographed everything before removing it.

Now that I had a better view of the FET in question, I confirmed the model, and that my parts-bin FET matched.

I pulled the FET and replaced it.


After putting things back together, the radio was hearing loud and clear.  My Service Monitor can generate signals as low as -130 DBM.  The signal was clearly discernible all the way down.

Talking with the owner, he was satisfied that the repair was sufficient, so I stopped there, rather than attempting to find alignment instructions.


Monday, January 1, 2018

Zeta FX-79 Buffalo Build - Port planning, Smartport and RSSI

Zeta FX-79 Buffalo Build - Port planning, Smartport and RSSI

Serial Port Planning

The Omnibus F4 Pro V2 offers 3 UARTs.
  • UART1 - Used for SBUS
  • UART6 - Available
  • UART3 / I2C - Available if I2C is not needed
My Ublox Neo-M8N GPS with Compass module will need UART6 for the GPS, and the I2C pins for the Compass functionality.  Unfortunately, I want to do Smartport as well, but don't have a UART.  So, instead, I'll use softserial for the Smartport connection.

My configuration will be as follows:
  • UART1 - Used for SBUS
  • UART6 - GPS
  • UART3 / I2C - Compass
  • Softserial - Smartport


The Softserial ports are available on the Omnibus F4 Pro V2 via two pads on the front of the board.  I followed the FrSky SmartPort using SoftwareSerial procedure at the bottom of the Omnibus F4 page.  They call for a 1k ohm resistor between the RX and TX pins.  I had one in 0603 in my parts bin, so I put it between the pads, and then attached a small wire-wrap wire to the RX pin.  It ended up working fine.

I love surface mount soldering!  Here's a 1K 0603 resistor from my parts bin.

There's the resistor, temporarily sitting on top of the processor.  I circled the two pads it's going to be crossing below.

Here's the resistor installed, with a bit of Wire Wrap wire connecting to the RX pin.  

After confirming that all worked well, I went ahead and secured this wire with a dab of glue to the top of the processor chip, for strain relief.

In order to use the Softserial port, it needs to be enabled in the iNav Configurator.

I enabled Softserial, and while I was at it, also enabled telemetry.
After a reboot, I went into the ports page, found the Softserial port, and configured it for Smartport at 57,600 bps, as suggested.

The final step was to go into the CLI and apply two settings:

set smartport_uart_unidir=OFF
set telemetry_inversion=on
Having done that, I was able to go onto my transmitter, do a "discover", and telemetry values populated from the flight controller.

One additional note.  The GPS telemetry does not appear for a "discover" on the radio until you have a GPS lock.  This caused me quite a bit of frustration.  Once the telemetry is discovered, it will be on the list on the radio, thereafter.  You do not have to rediscover it after it is in the list.


The Omnibus F4 V2 uses a tiny little pad in the middle of the board to receive RSSI data from the receiver.  Many people are soldering a small wire to the pad, and just hanging it off the Flight Controller, with a bunch of glue and hope holding it in place.  I didn't really like that solution.  Instead, I opted to hijack the RAM pins to make the RSSI externally available.

The RAM pins on the Omnibus F4 pro V2 are wired together, but otherwise isolated, if you do not solder a jumper elsewhere on the board, linking them either to VCC or the 5v rail.  Since I left the jumper undone, the RAM pins are just tied to each other.    Since I'm powering my Camera and VTX via an external wiring harness, the RAM pins are unused.

I soldered a small jumper from the RSSI pad to one of the RAM pins.  I will then solder a heavier gauge wire into the other RAM pin hole, and connect that to the wiring harness going off to the receiver.  This should provide greater strain relieve for the RSSI jumper.  I think this is a much more resilient solution.  Here's the jumper wire going to one of the RAM pins.  This picture does not illustrate the wire harness leading off the board.

One thing to note.  I was concerned that the RSSI pin might have problems.  The MCU on the F4 board is running at 3.3 volts.  The receiver runs at 5 volts.  I was worried that the RSSI pin might be overdriven by the receiver.  I wondered whether a voltage divider would be needed to scale the voltage down.  Fortunately, FrSky had considered this.  The RSSI value is documented to be a PWM value between 0 - 3.3 volts.  So, even though the receiver is running at 5 volts, it is safe to run the wire to the Flight Controller.  So, the result was, I didn't have to do anything special.  I felt better having researched it, however.

Thursday, December 28, 2017

Zeta FX-79 Buffalo Build - Wiring plan

Zeta FX-79 Buffalo Build - Wiring plan

The stock configurations for the FX-79 seem to recommend running with 3s lipos, so that's what I'm planning to do.  I'll be using two 5000 mah batteries in parallel.    My Flight Controller is rated up to 5s, as far as I can tell on the forums, so I have the option of upgrading later if I wish.

The Omnibus F4 Pro V2 comes with an onboard 3 amp BEC.  However, as I read the forums, I understand that it can get rather warm.  As a precaution, I'm planning to run very little through the onboard BEC.

Power Plan

  • Direct from batteries
    • Flight controller - rated to 5s (I think).
    • Runcam and VTX (through a filter).  Both are rated up to 6s.
    • ESC (via the Flight Controller for current measuring)
  • BEC on Flight Controller
    • GPS
    • Uart inverter
  • External BEC
    • Servos
    • Receiver
  • External BEC (auxiliary port)
    • Lights or special effects
I roughed out a wire plan on my office whiteboard.

About Club24A

There are known problems with this board with regards to reporting accurate current if VCC is jumpered into the Camera or VTX pins.  It's known as "Club24A" in the forums.  I'm avoiding that problem entirely by not using the onboard jumper to apply power to the RAM pins.  I'm actually hijacking the RAM pins for another purpose (RSSI).  I'm providing the power to the camera and VTX via the wiring harness, apart from the Omnibus board, so I do not expect any of the Club24A issues.

Riser pins

Since I'm providing power to most of the external devices via the external BEC rather than through the FC, it's led to a rather convoluted wiring harness.  While the FC provides 3 pins in most places, for powered servo cables, I'm generally using just the signal wire, and drawing power from my external BEC.  I decided to solder wires directly onto the Flight Controller and terminate with female servo connectors.  This makes the flight controller a bit of a hassle to wire up, but with care, it should be alright. 

I'm planning to use extension cables everywhere, so that the FC can be removed for calibration or reprogramming if necessary.  Also, being the most convoluted part of the build, it should be fairly easy to transplant, if the plane crashes badly enough that I need to replace the foam.

Here's the flight controller wiring, about 3/4 done.  I'll update this picture when it's complete.  Note the heavy cables which lead to the batteries and ESC.  The servo cables patch into other cables to provide power.

Monday, December 25, 2017

Zeta FX-79 Buffalo Build - Preliminaries

Zeta FX-79 Buffalo Build

My wife bought me a Zeta FX-79 Buffalo flying wing for Christmas.  I'll be chronicling the build in this blog post.  My motivation for this plane was to have an FPV platform to use for balloon "Search and Rescue" operations.  I'd like to have an FPV camera, and a higher resolution camera for post-flight analysis.  I'd also like to be able to fly my Kenwood TH-D72A radio as an airborne APRS digipeater platform.  Since my trackers are very low power (10mw or so), the hope is that an airborne digipeater might be helpful in recovery operations.

I purchased the fittings from a variety of sources.  The Bill of Materials follows. 

I'll be doing the build with the Omnibus F4 Pro V2 flight controller, which includes an OSD and can relay Smartport Telemetry back to my Tanaris Plus transmitter.

In future posts, I'll document the build process and initial flights.

Sunday, August 20, 2017

AFSK modulation experiments - success!

AFSK modulation experiments - success!

So, in the previous blog entry, I confirmed that I had come up with some numbers and timer values to make the frequencies I needed.  For posterity, I'll document them here.

DAC configration

I'm running my DAC with 24 data points.  I generated my sinewave table using the script that I blogged previously.  Based on the results from AFSK modulation experiments - part 4, I set the PPM on the Si5351b to 30, and the DAC_PCT to 35 to achieve about 3000hz deviation.  It looks as follows:

#define DAC_PCT 35          // Percentage of DAC output#define SINE_RES        24const uint16_t sinewave[SINE_RES] = {        (0 * DAC_PCT / 100) + 2048, (530 * DAC_PCT / 100) + 2048, (1023 * DAC_PCT / 100) + 2048,        (1448 * DAC_PCT / 100) + 2048, (1773 * DAC_PCT / 100) + 2048, (1978 * DAC_PCT / 100) + 2048,        (2047 * DAC_PCT / 100) + 2048, (1978 * DAC_PCT / 100) + 2048, (1773 * DAC_PCT / 100) + 2048,        (1448 * DAC_PCT / 100) + 2048, (1024 * DAC_PCT / 100) + 2048, (530 * DAC_PCT / 100) + 2048,        (0 * DAC_PCT / 100) + 2048, (-530 * DAC_PCT / 100) + 2048, (-1023 * DAC_PCT / 100) + 2048,        (-1448 * DAC_PCT / 100) + 2048, (-1773 * DAC_PCT / 100) + 2048, (-1978 * DAC_PCT / 100) + 2048,        (-2047 * DAC_PCT / 100) + 2048, (-1978 * DAC_PCT / 100) + 2048, (-1773 * DAC_PCT / 100) + 2048,        (-1448 * DAC_PCT / 100) + 2048, (-1024 * DAC_PCT / 100) + 2048, (-530 * DAC_PCT / 100) + 2048,};

Clock speed

I'm testing on the Nucleo-L152RE, which has a STM32L152RET6 processor.  My flght board uses an STM32L152CB, which is quite similar.  I'm running it at a clock speed of 32 MHZ, and the prescalars set such that the APB1 clock speed is also 32mhz.

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;  RCC_OscInitStruct.HSIState = RCC_HSI_ON;  RCC_OscInitStruct.HSICalibrationValue = 16;  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL4; // 32mhz  RCC_OscInitStruct.PLL.PLLDIV = RCC_PLL_DIV2;  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)

Timer calculations

Based on the 32mhz clock, I calculated timers as follows.  The goal for the DAC timer was to be able to simply toggle the "counter" value back and forth, in order to change the tone between 1200hz and 2200hz.  You can calculate the Clock Ticks yourself by dividing:

Clock_Ticks_Hz = 32000000 / (DAC Datapoints * Prescaler * Counter)

Function Timer DAC sinewave
data points
Prescaler Counter Clock ticks (hz)
Baud Timer
TIM2 n/a13,333 2 1200.03001
DAC timer
1200 hz
TIM6 24 101 11 1200.120012
DAC timer
2200 hz
TIM6 24101 6 2200.220022

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 13333 - 1;
  htim2.Init.Period = 2 - 1; // 1200 hz
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;

  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
    Error_Handler(__FILE__, __LINE__);

  htim6.Instance = TIM6;
  htim6.Init.Prescaler = 101 - 1;
  htim6.Init.Period = 11 - 1;
// 1200 hz

  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;

  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
    Error_Handler(__FILE__, __LINE__);
  // We turn on the ARPE register so that the counter is only updated AFTER a clean
  // count completes.  Otherwise, we run the risk of overcounting an interval.
  htim6.Instance->CR1  |= TIM_CR1_ARPE; // Turn on auto-preload register

The ARPE bit

Note that on the DAC timer (TIM6) we set the ARPE bit in the Control Register.   This causes any changes to the counter to take effect AFTER the next timer pop.  This assures that each timer cycle is complete, so that the DAC data points are properly spaced.

Switching between 1200hz and 2200hz

Because I'm using the DAC to make the sine wave, and just adjusting the TIM6 timer to change the frequency, I am able to greatly simplify the code that most APRS implementations use.  Rather than using a 512 element phase array, I'm simply using a 24 element sine array for the DAC.  I no longer need to track the phase position when switching.  Rather, I just adjust the timer counter.

The code is simple, but it confused me at first, because AX.25 is using NRZI encoding.  So, here's a bit about that, before showing the code.

From Wikipedia:
At the datalink level, AX.25 specifies HDLC (ISO 3309)[3] frames transmitted with NRZI encoding.   
In NRZI encoding, you're not sending the actual bits, but rather toggling whatever bit you are sending, based on the next data value.  I borrowed a few lines from the Tracksoar code, and modified them to my purposes:

if ((current_byte & 1) == 0) {
// Toggle tone (1200 <> 2200)
current_timer_counter ^= (COUNTER_1200 ^ COUNTER_2200); // Switch to the opposite counter value
__HAL_TIM_SET_AUTORELOAD(&htim6,current_timer_counter); // Set frequency

That's it.  No math about phase arrays and accmulators!  Yay!

Initial test failures

I only had 3 tiny bugs in my code which prevented APRS from working.

  1. I actually FORGOT the HAL_TIM_SET_AUTORELOAD command after setting the current_timer_counter.  lol.  
  2. The more subtle one was that I had misread the manual, and thought that the ARPE bit was set by default.  When I tested without it, there were hesitations in the middle of the transmission. It was obvious what was going on when I heard it, and simple to fix.
  3. I contrived the test by hard-coding GPS values into the strings that the aprssend() call uses.  I used the wrong number of digits in the Longitude, and so my Kenwood D72 caught my callsign, it wouldn't decode it until I fixed that coordinate.  Along the way, I noticed that my APRS comment was also too long.  I wanted it to read "Mr. Watson, come here, I want you." Unfortunately, that's too many characters.  So, I changed it to "Mr. Watson, Come here!..."

Successful test