Review: PCBWay circuit board fab and assembly

If you’re in the US, and you don’t have a million bucks to spend, the go-to choice for PCBs is usually OSHPark. OSHPark rocks. There is no minimum spend (you can literally order 3 copies of a $1 board if you want to), no minimum board quantity, and the fab tech is decent for 2/4 layer boards. I myself have ordered more boards from OSHPark than I can count, both for work and for my own projects.

OSHPark is great for standard quick-turn style jobs, and is slowly adding more options, like 2oz copper and their “super swift service”, which is really just an expedited option. But what if you want something special? Aluminum-backed, for example? Or 6+ layers? These options start to fall out of the standard quick-turn price range, which means expensive, nonstandard order from local PCB manufacturers.

PCBWay is a China-based PCB fab and assembly house that I used for the first time around 2 years ago. Similar to OSHPark, they offer affordable pricing on basic boards (2/4 layer, 6/6 trace and spacing, etc). They have a nice online quoting tool where you can set your board specs and add options to get a quick idea of time and cost. They also support a pretty solid set of PCB specs and options, including Flex PCBs, Aluminum-backed PCBs, multilayer (10+), and a multitude of silkscreen colors, dielectrics, and plating options. A big bonus is the availability of fast shipping through DHL. PCBWay has also recently pushed out an EAGLE upload conversion tool, which is a great way to avoid generating gerber files if you use EAGLE. Boards usually show up within 2-3 days of shipping, which is insanely fast coming from literally across the world.

PCB quality is great. I’ve ordered several boards over the span of the last few years, all with different options, and I’ve been impressed. If you stick to the standard options (6 mil min. traces and spacing, 0.3mm via), the boards are affordable and very high quality. The copper features are well-formed, and I haven’t seen any breaks or shorts due to masking errors in the boards I’ve purchased. Silk comes out good, assuming you follow the general rules for silk. Soldermask varies a bit based on color, specifically when it comes to hardness. If you plan to do a lot of manual reworks, or durability is important to you, I’d stick to the standard green. In my experience, white and other lighter colored soldermasks can be a bit soft.

Now let’s talk about board assembly.

Assembly is a notoriously expensive process for prototype boards because of the upfront costs. Board houses don’t want to set up their SMT line for your 10-board run. There are houses which specialize in fast prototype runs, but the caveat is that these are usually extremely expensive or rely on inking a deal for large-scale production after the prototypes have been made. There are plenty of companies that can afford to roll their upfront R&D costs down into the price of the product once it’s mass-produced, but usually SMT assembly is out of reach for small operations or anyone without at least several thousands of dollars in budget.

I’ve been working on the design of a product with the eventual goal of listing it on Massdrop. It’s a small board and is fairly dense, with some hard-to-place parts (USB-C connector, 3x3QFN package USB hub chip). I ordered the first proto PCB from OSHPark and hand-soldered it, and it became pretty obvious I was going to have to find a more sustainable solution for the next run of prototypes. The board itself is just under 100 individual components, with placements on both sides, and no through-holes. I decided to give the PCBWay assembly a try, since their offering seemed like a good fit and is extremely affordable. I decided I’d specify components  and distributor in the BOM and let them purchase components. You can also drop-ship components or mail them directly to them.

As an aside, I’ve got a reasonable amount of experience designing PCBs and in particular, following DFM (design for manufacturability) rules. Following these rules, some of which I’ll detail below, help ensure that the board is easily manufactured and will work when you receive it. The easier it is for an assembler to make your board, it’ll be lower cost with higher yields. Don’t panic if you’re not aware or familiar with these rules, but I would suggest getting familiar before starting a PCB assembly order with any assembler. This project in particular, I benefitted greatly from sticking to DFM rules.

Some PCB design/assembly DFM rules. These are just some of the bigger ones. For a more complete list, check out this document.

  • Place large components, like connectors or ICs, on one side of the board (generally designated the top). Place only small & light components, like passives and SMT LEDs, on the bottom side.
  • Keep all SMT items a minimum distance from the board edge, generally 3mm. Exceptions can be made for items like connectors, but your boards will probably need to be panelized (something PCBWay will happily handle for you).
  • Keep SMT items a minimum distance from each other. This distance varies based on the assembler and their placement process, but at minimum 1mm is a good place to start.
  • Make sure all part footprints contain pin1 callouts in some form, along with reference designators. Polarized 2pin components should have a + marking on their anode. This helps assemblers determine rotation, and also allows them to easily identify parts if they need to ask questions.
  • Ensure (no, really, like your life depends on it) that your BOM is well-detailed and contains specific part numbers along with component descriptions, preferred distributor, distributor PN, and matching reference designators on the PCB. A good BOM will save you hours of headache, and providing a good BOM to PCBWay will actually lower your quoted price, because it reduces work and risk on their end.

The hardest part about assembling a board (or designing one to be assembled, for that matter), is getting the small details right. That’s why putting some thought into how your board is going to go together goes a long way. If there is a chance that a part goes on backward (a diode, LED, or polarized cap), put a marking on the silkscreen to make it clear which way the part should be placed. If you have the room, space your components out more than you need to. It’s also a good idea to make a list of the possible mistakes that would be ‘deal-breakers’ and share these with the assembler before your board is put together. A little bit of thought early on can save you a lot of headache.

To this end, I was very impressed with PCBWay’s ability to ask questions when there were ambiguities. I received several emails with questions around component placements and polarities. Each email was well-worded and contained a marked screenshot of the PCB along with the component in question.

It was easy enough to clear up any uncertainty. In addition, they send ‘approval’ images of the first PCB that comes off their line, so that you can do a rough scan for errors or mistakes. Both of these lead to excellent results; 100% of the assembled PCBs I received were built correctly and were fully functional. Not bad! As mentioned earlier, once the PCBs shipped they arrived stateside quickly with no hassle from customs. DHL is slightly more expensive as a shipping option, but in my experience it’s worth every penny spent.

Here are some assorted images of the panelized/separated PCBs and assemblies I got from PCBWay. The product is called the DB1, and there will be more info in the coming months available here. I might as well mention: If you’re thinking about ordering a PCB or assembly from PCBWay, go ahead and use my referral link for $5 free towards your first purchase. (I receive rewards for use of this link, by the way. I also got a few bucks off of some PCBs for sharing this article with PCBWay, but I wouldn’t have written it if I didn’t love what PCBWay is doing)

Questions? Do you have an experience working with PCBWay? Leave a comment below.

Liquid Cooling an ASUS GTX1070 with Using NZXT G12 GPU Cooler & Corsair H75

My buddy recently decided he wanted to watercool his Asus GTX1070. There’s only two real choices: the NZXT G12 bracket (which replace the stock cooler) and the Corsair HG10 N980. The HG10 isn’t actually made for the 10 series, but is apparently easily modified to clear the VRM caps using some power tools. The G12 claims full compatibility with 1070 series cards, so he went with that. He also picked up a Corsair H75 which is fully compatible with the G12 bracket.

After taking the stock cooler off, it was immediately clear that the VRM caps were going to interfere with the curved standoffs the G12 uses to mount to the card.

Nothing a little grinding can’t fix! With a file, some thin washers, and about 15 minutes, it’s easy to modify the G12 to fit this specific card. The main issue here is the flanged lip visible on the bracket below. It hits the top of the VRM caps.

We started by grinding the lip off using a metal file. This only took about 3 minutes; the brackets are relatively soft.

A bit of black sharpie afterward covered it up well enough (these two brackets aren’t visible once the cover is in place).

We also added some very thin washers between the bracket and the topside of the PCB to add the slight extra bit of offset needed to clear the caps. They’re no more than 0.5mm thick.

After making these two changes, everything fit just like it should. We added a thin ring of foam between the white bracket and the H75 to make up for the width the thin washers add, but I think you could get away without doing this.

That’s it! Note that when you’re putting this bracket on, with or without this mod, you should only tighten the thumbscrews until you see the card flex ever so slightly. Once you see this, back each thumbscrew off by about 1 full turn. The cooler does not need to be pressed tightly against the die; over-tightening the cooler will put unnecessary strain on the PCB and components and can lead to premature failure of the card due to thermal cycling. It’s also possible to crack or damage the GPU die.

A few more pictures of the final product:

Adding a buzzer beeper to the Illuminati32/Tarot Naze 32 Flight Controller

My buddy just bought an Illuminati32 FC board from Hobbyking for his new ZMR180 miniquad. The board is pretty sweet: Naze32, MWOSD, 35x35mm form factor, and only 20 bucks (on sale). It was really easy to set up, especially with the ZMR180 PDB that Diatone is shipping. The biggest problem is that there’s no buzzer output! A buzzer driver is one basic necessity every flight controller should come with. Low battery, lost models, and mode change beeps are pretty crucial to the operation of these miniquads. Luckily, it’s relatively simple and very cheap to add one onto this board. You’ll need:

  • High gauge (>30) wire
  • Fine solder and a fine-tipped soldering iron
  • An NPN BJT (2N3904, BC557, etc)
  • A 100Ω resistor
  • Heatshrink tubing or tape

The built-in Naze32 (rev5 or greater) buzzer driver is a NPN transistor used in an open-collector configuration. There is a base resistor on the order of 100Ω used to set the drive current. PA12, or Pin 33 of the STM32 is used to drive the BJT.


It’s possible to do this BEAM style, with SMT components and a small piece of protoboard, or even a with small PCB (perhaps from OSHPark). We chose to do it with a little PCB that’s also got an ATTiny84 on it which controls some RGB led strip.


The hardest part of this is soldering onto the STM32, since PA12 isn’t broken out or used for anything else. See the below image to find the pin you need to solder to. Note that the text on the STM32 isn’t guaranteed to be upright, so look for the pin 1 marker!!!


You’ll need some fine gauge wire (30 gauge wire wrap wire worked well), a fine-tipped soldering iron, and a steady hand.


Once the solder connection is made, don’t hesitate to dump some hot glue onto the connection to keep it from being broken loose. Once you’ve got the connection to PA12, solder one end of the 100Ω resistor to the PA12 wire, and the other to the base of the BJT. Solder the positive lead of your buzzer to the flight controller’s 5V input, and the negative lead of the buzzer to the collector of the BJT. Finally, solder the emitter of the BJT to ground. Be sure to wrap everything in heatshrink or tape so that you don’t accidentally short anything out.

That’s all there is to it! Just plug in a battery without turning on the transmitter and the FC should issue the “no connection” beep if everything worked.

Eachine H8 Quadcopter Custom Firmware Rates/Settings

I recently flashed my new Eachine H8 with some custom firmware (Silver13’s CFW, to be particular) and I spent some time tuning it to be flyable in acro mode with the stock remote. Overall, I think this firmware is super cool and really strokes my roots as a hardware engineer and reverse engineer. Not to mention, it flies as good or better than the stock firmware and is tons of fun to tinker with! Here’s my config.h file, for those who might be interested in a good place to start:

//config.h, edited by jaygreco

#include "defines.h"

// rate pids in pid.c
// angle pids in apid.h ( they control the rate pids)
// yaw is the same for both modes

// not including the "f" after float numbers will give a warning
// it will still work

// rate in deg/sec
// for low rates ( acro mode)
#define MAX_RATE 180.0f
#define MAX_RATEYAW 200.0f

// multiplier for high rates
// devo/module uses high rates only
#define HIRATEMULTI 3.0f

// max angle for level mode (in degrees)
// low and high rates(angle?)
#define MAX_ANGLE_LO 35.0f
#define MAX_ANGLE_HI 55.0f

// max rate for rate pid in level mode
// this should usually not change unless faster / slower response is desired.
#define LEVEL_MAX_RATE_LO 360.0f
#define LEVEL_MAX_RATE_HI 360.0f

// disable inbuilt expo functions
//#define DISABLE_EXPO

// use if your tx has no expo function
// also comment out DISABLE_EXPO to use
// -1 to 1 , 0 = no exp
// positive = less sensitive near center 
#define EXPO_XY 0.6f
#define EXPO_YAW 0.25f

// Hardware gyro LPF filter frequency
// gyro filter 0 = 260hz
// gyro filter 1 = 184hz
// gyro filter 2 = 94hz
// gyro filter 3 = 42hz
// 4 , 5, 6

// software gyro lpf ( iir )
// set only one below
//#define SOFT_LPF_1ST_023HZ
//#define SOFT_LPF_1ST_043HZ
//#define SOFT_LPF_1ST_100HZ
//#define SOFT_LPF_2ND_043HZ
//#define SOFT_LPF_2ND_088HZ
//#define SOFT_LPF_4TH_088HZ
//#define SOFT_LPF_4TH_160HZ
//#define SOFT_LPF_4TH_250HZ

// this works only on newer boards (non mpu-6050)
// on older boards the hw gyro setting controls the acc as well

// Headless mode
// Only in acro mode
// 0 - flip 
// 1 - expert
// 2 - headfree
// 3 - headingreturn
// 4 - AUX1 ( gestures <<v and >>v)
// 5 - AUX2+ ( none )
// 6 - Pitch trims
// 7 - Roll trims
// 8 - Throttle trims
// 9 - Yaw trims
// 10 - on always
// 11 - off always

// rates / expert mode
// 0 - flip 
// 1 - expert
// 2 - headfree
// 3 - headingreturn
// 4 - AUX1 ( gestures <<v and >>v)
// 5 - AUX2+ ( none )
// 6 - Pitch trims
// 7 - Roll trims
// 8 - Throttle trims
// 9 - Yaw trims
// 10 - on always
// 11 - off always
#define RATES 1

// level / acro mode switch
// CH_AUX1 = gestures
// 0 - flip 
// 1 - expert
// 2 - headfree
// 3 - headingreturn
// 4 - AUX1 ( gestures <<v and >>v)
// 5 - AUX2+ ( none )
// 6 - Pitch trims
// 7 - Roll trims
// 8 - Throttle trims
// 9 - Yaw trims
// 10 - on always
// 11 - off always

// channel to initiate automatic flip

// aux1 channel starts on if this is defined, otherwise off.
#define AUX1_START_ON

// use yaw/pitch instead of roll/pitch for gestures

// comment out if not using ( disables trim as channels, will still work with stock tx except that feature )
#define USE_STOCK_TX

// automatically remove center bias ( needs throttle off for 1 second )

// throttle angle compensation in level mode
// comment out to disable

// enable auto throttle in acro mode if enabled above
// should be used if no flipping is performed
// 0 / 1 ( off / on )

// enable auto lower throttle near max throttle to keep control
// comment out to disable

// options for mix throttle lowering if enabled
// 0 - 100 range ( 100 = full reduction / 0 = no reduction )
// lpf (exponential) shape if on, othewise linear

// battery saver ( only at powerup )
// does not start software if battery is too low
// flashes 2 times repeatedly at startup

// under this voltage the software will not start 
// if STOP_LOWBATTERY is defined above

// voltage too start warning
// volts
#define VBATTLOW 3.5f

// compensation for battery voltage vs throttle drop
// increase if battery low comes on at max throttle
// decrease if battery low warning goes away at high throttle
// in volts
#define VDROP_FACTOR 0.60f

// voltage hysteresys
// in volts
#define HYST 0.10f

// enable motor filter
// hanning 3 sample fir filter

// clip feedforward attempts to resolve issues that occur near full throttle
//#define CLIP_FF

// motor transient correction applied to throttle stick

// motor curve to use
// the pwm frequency has to be set independently
//#define MOTOR_CURVE_6MM_490HZ
//#define MOTOR_CURVE_85MM_8KHZ
//#define MOTOR_CURVE_85MM_32KHZ

// pwm frequency for motor control
// a higher frequency makes the motors more linear
//#define PWM_490HZ
//#define PWM_8KHZ
#define PWM_16KHZ
//#define PWM_24KHZ
//#define PWM_32KHZ

// failsafe time in uS
#define FAILSAFETIME 1000000 // one second

// level mode "manual" trims ( in degrees)
// pitch positive forward
// roll positive right
#define TRIM_PITCH 0.0f
#define TRIM_ROLL 1.0f

// ########################################
// things that are experimental / old / etc
// do not change things below

// invert yaw pid for hubsan motors
//#define INVERT_YAW_PID

//some debug stuff
//#define DEBUG

// disable motors for testing
//#define NOMOTORS

// enable serial out on back-left LED
//#define SERIAL

// enable motors if pitch / roll controls off center (at zero throttle)
// possible values: 0 / 1
#define ENABLESTIX 0

// only for compilers other than gcc
#ifndef __GNUC__

#pragma diag_warning 1035 , 177 , 4017

#pragma diag_error 260

// --fpmode=fast ON

Eagle Keyboard Commands for Showing and Hiding Layers

Here’s a short post but one that I think a few people might find useful: CadSoft EAGLE keyboard commands/shortcuts (strangely found under the “Assign” menu in Eagle) to show and hide the Top and Bottom layers independently. I’ve also got one to show all layers, top and bottom. Note that you can change any key a command is bound to when you set up the “assignment” in Eagle.

To set a shortcut in Eagle, choose Options → Assign…. and press the New button.

Screen Shot 2016-04-05 at 8.54.04 PM +  Screen Shot 2016-04-05 at 8.56.32 PM

Once in the menu, choose the key you want the assignment to bind to, along with any modifier keys (Alt, Shift, etc). I chose Alt+0 for showing the bottom layer only, Alt+1 for the top layer only, and F12 for all top and bottom layers.

Screen Shot 2016-04-05 at 8.55.16 PM

Here’s the three Eagle commands for Top, Bottom, and All layers:

  • Top only: display none; display 1 17 18 19 20 21 23 25 27 29 39 41 45 51;
  • Bottom only: display none; display 16 17 18 19 20 22 24 26 28 30 40 42 45 52;
  • All (Top and Bottom): display none; display 1 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 39 40 41 42 45 51 52;

A bit time consuming to implement, but they’ll save you tons of time once you’re actually doing a layout.

Reverse Engineering the Amazon Dash Button’s Wireless Audio Configuration

Update: I’ve learned a bit more and as such, see the bottom of the post for an update. I’ve been slow to update this but now that it’s seeming to be gaining a lot of traffic (leading up to 33c3…hmmmm ;)), I want the info to be accurate and fresh.

During the great Amazon Dash Button Hype of 2015, I saw a few of the early teardowns and blog posts and decided to order a few dash buttons of my own to play around with and reverse engineer. Since the hype has burned off, there hasn’t been much in the way of new information about the inner workings of the button.

Photo credit: Matthew Petroff

The Amazon Dash button is a neat little IOT device which contains an STM32F205 ARM Cortex M3 microcontroller, a Broadcom BCM43362 Wi-Fi module, a permanently attached (boo!) Energizer Lithium AAA battery, an Invensense I2S digital microphone, some serial flash, and assorted LEDs and SMPS power supplies. For the $5 price tag, the Dash Button packs some serious punch! Just the components are worth a considerable amount more than $5.

Playing around with the button, the setup process on iOS quickly caught my attention. It (apparently*) differs from the Android setup process considerably due to differences in the inner workings of iOS. The Android setup involves connecting to the button via a network called “Amazon ConfigureMe”, while the iOS app appears to use ultrasound-esque audio to transfer information to the button for the initial setup.

*I don’t actually have an Android device on hand to test this with, hence the “apparently”.

Without even opening the button, I put together a basic theory on how the button was setup from the iOS app: The app sends a carefully crafted “audio” packet using the iOS CoreAudio Framework, which is then picked up by the Dash Button’s onboard mic and parsed for Wi-Fi config info. If the Wi-Fi credentials are correct, the button phones home to the Amazon configuration servers and the setup continues, but with further config info being sent directly to the button over the Wi-Fi.

I immediately ripped apart the button in search of a way to piggyback on the ADMP441 digital microphone’s I2S bus. I figured it would be trivial to toss a logic analyzer on the bus and decode what I2S data was being sent to the STM32. Since I2S is a very commonly used and extremely well documented audio protocol, I counted on this being a relatively quick task.

While I was impressed with the density of the design, I was most definitely not impressed with the lack of a visible testpoint on the board for the digital microphone’s data line. The EN (enable), SCK (clock), and WS (word select) lines are easily available, but the SD (data) line is nowhere to be found. I poked around for a bit but didn’t see anything that looked promising. I quickly came to the realization that I was probably going to have to analyze the audio protocol as it came out of my iPhone rather than sniff it on the board. This was about the same time that I also realized this was not going to be the quick and dirty analysis I was expecting…

Armed with my RØDE shotgun mic, I took a new approach. Using  Electroacoustics Toolbox, I performed some basic audio analysis on the packets coming from the Amazon iOS app. Based on Matthew Petroff’s Dash Button Teardown, I initially expected some sort of Frequency-Shift Encoded (FSK) modulation scheme. Using the Spectrogram tool, I could see that the configuration data was definitely coming in bursts of 20 packets in a try-retry scheme. It also looked like the frequency of the audio was spread out between 18kHz and 20kHz, which is on par for an audio FSK implementation.

Screen Shot 2015-12-23 at 1.21.00 PM
Spectrogram capture of an entire configuration transmission.

Things got interesting, however, when I took an FFT of an entire transmission. The FFT showed an obvious frequency spread near 19kHz, but lacked the characteristic “double peak” indicating frequency occurrences at both the mark and space frequencies.

FFT of entire configuration transmission.
FFT of entire configuration transmission.
FFT of FSK modulation. Note the very obvious "double peak".
FFT of FSK modulated data. Note the very obvious “double peak” at the mark and space frequencies.

As I examined the FFT, it became clearer and clearer that the configuration data was not being transmitted with an FSK modulation scheme. At this point, I switched to the basic audio oscilloscope tool to try to figure out what was going on. After the first capture, it was pretty obvious that the data was being Amplitude (AM) modulated, with a carrier frequency of 19kHz.

Screen Shot 2015-12-23 at 2.14.16 PM

The data was so clearly AM modulated that I wished I had just popped open the scope to begin with (note to future self)! Here’s a scope capture with a few repeated packets coming through.

Screen Shot 2015-12-23 at 4.01.07 PM

After “configuring” a few different dash buttons and examining the transmitted data, I was getting confused as to why there was so much variation in the peak levels of the packets. I checked for ground loops and background noise before transmitting, and confirmed that the noise floor of my microphone setup was far below the variations in peak amplitude I was seeing. After staring at a few captures, I started to notice that the “variations” were consistent in their amplitudes. Looking some more, I realized that it wasn’t noise at all: the data was intentionally being sent with four distinct amplitude levels!

0000_0000 2 copy

Clever, clever Amazon is using Amplitude-Shift Keying (ASK) modulation with 4-level binary to send the data across to the Dash Button.

The big benefit to this modulation scheme is that it’s got a 2-to-1 compression ratio, so the packet length is theoretically half of the length of an FSK packet. The downside, however, is that the Signal-to-Noise Ratio is halved. This isn’t really a problem, since the data is sent 20 times, and the transmitter (iOS device) can be closely physically located to the receiver (Dash Button).

After these discoveries, I came to a few conclusions:

  • The data is being sent from the iOS app using an ASK modulation scheme, with a carrier frequency of 19kHz. It’s resent 20 times before moving on.
  • Each “bit” (really, two bits) has a nominal bit time of 4ms. There are four levels of bit amplitude and there is no true zero. Every bit level, including 00, has some amplitude associated with it.
  • The first chunk of data is always the same. It looks like a simple calibration sequence, allowing the button to set the decoding thresholds for later down the road.
  • There appears to be both a start and stop glitch on all of the packets. This could be a byproduct of how Amazon is building their ASK packets in-app, or the hardware codec starting and stopping on the iPhone. This glitch isn’t harmful, because the transmission is stable by the time any meaningful data is coming through.
  • The packets are not of a fixed length. Entering a longer SSID or passphrase results in a longer packet.

Now that I had a rough idea of how data was transmitted, I wanted to give decoding some known data a shot. This is where things got really interesting for me, because I’ve got basically no experience in data transmission or communications theory. Luckily, I have a decent eye for patterns, which helped considerably in figuring out what data was represented where in each transmitted packet. I began by choosing an SSID and passphrase that were fairly easy to recognize. I ended up using 7’s and *’s in various combinations and orders. I quickly started to recognize the waveforms of each coming through in the data, but it wasn’t immediately clear how the characters were being translated from their ASCII representation.

7_* (3) copy
Packet containing both 7 and *.

I was getting nervous that some type of encryption was being used on the characters to prevent bored nerds like me from easily snooping on the packets.

In an effort to bruteforce whatever translation was taking place, I sent the characters 1 through 9 in the password field. I assigned amplitude level “1” on the received data as binary 00, level “2” as 01, level “3” as 10, and level “4” as 11. I recorded the ASK levels of each character, and busted out a table of what the received binary data looked like in comparison to the known ASCII value of each character. The first thing that was clear was that the binary representation of each character definitely related to the next, which was good news. This ruled out any sort of encryption or lookup-table based character set. The next observation was that the binary data was decrementing, rather than incrementing as the transmitted ASCII characters should be. It was also evident that it was somehow scrambled or flipped from the known representation.

After a bit of bit order manipulation, I arrived at three conclusions:

  • The levels I picked (level “4” as 11, and level “1” as 00) were incorrect. Flipping these levels yields non-inverted bits, which then results in upwards-counting binary data.
  • Each 8-bit ASCII representation of a character was actually being transmitted “backwards” from how I expected, with the first 2-bits transmitted representing the LSB end of the ASCII character. Characters themselves are transmitted in the order they are entered.
  • Each block is 4 pulses long, which represents a total of 8 bits of data.

Armed with the encoding info, my final task was to write a piece of software which would listen to the audio sent by the iOS app and decode it into various representations. Doing it by hand was fun for a bit, but got tedious quickly. I rather arbitrarily settled on MATLAB, mostly because it’s easy to interface with audio components, manipulate WAV data, and filter and analyze datasets. I also figured it would be a good way to sharpen up my MATLAB since it’s been a bit since I’ve fired it up.

With a few hours of coding, I’ve got a script that can listen via my external mic, trim the acquired data to a single packet (albeit semi-manually), and separate and decode each block into it’s decimal, hexadecimal, and ASCII representations. It then saves this as a CSV file.

To to this, the MATLAB utilizes the built-in MATLAB AudioRecorder function. It then waits for user input in regards to the bounds of a single packet. With these, it trims the data and performs some simple filtering and peak detection. The peak detection is done using a Hilbert Transform (a very common and useful digital peak detection method). It then finds each subsequent peak and indexes them based on their amplitude to find the corresponding binary data.

Captured and trimmed audio data displayed in MATLAB.
Captured and trimmed audio data displayed in MATLAB.
The same packet after filtering and peak detection. Each level of peak is indicated with a different colored symbol.
The same packet after filtering and peak detection. Each level of peak is indicated with a different colored symbol.

I also (for no good reason) wrote a tool that goes in the reverse: punch in an array of 4 levels (1/2/3/4), and out comes a psudeo-ASK representation of it.

Because why not?
Because why not?

Using these software tools and a several packets, I discovered a few things:

  • The first two blocks of hypothesized “calibration sequence” is definitely that. They’re 10 bits each, which doesn’t match the rest of the packet. I’ve looked at hundreds of packets and they all start the same way. My MATLAB code actually uses these to find out where to start looking for real data. Handy!
  • Block 3 (Decimal rep) is the total length of the data which will come after it, in “number of blocks”.
  • Blocks 4-9 in every packet appear to be some sort of UDID/CRC. I’ll come back to this later.
  • Block 10 (Decimal rep) is the length of the SSID, in blocks.
  • Block 11 (ASCII rep) is the first char of the SSID. In this example, it’s only one character long.
  • Block 12 (Decimal rep) is the length of the passphrase. This isn’t always block 12, it’s dependent on whatever the length of the SSID is. It’s also always present immediately after the SSID, regardless if there’s a passphrase or not. If there isn’t, it’s just decimal 0, indicating that there is no passphrase.
  • Block 13 (ASCII rep) is the first char of the passphrase, if it exists. It’s also only one char long in this case.
Various blocks numbered by order of occurrence.
Various blocks numbered by order of occurrence.
Hypothesized purpose of each block of data.
Hypothesized purpose of each block of data.

The last real question remaining is: what are blocks 4-9? In every packet I sent, they were different. I immediately thought some sort of CRC but the packet changed at times when I didn’t change the SSID or the passphrase, so it’s hard for me to tell. I’m leaning toward a on-demand Unique Device identifier (UDID) generated in the iOS app, potentially in combination with a CRC. With 48 bits to spare, a 32 bit UDID along with a 16 bit CRC seems more than reasonable.

With this scheme, device setup would look something like this:


  • User logs into their Amazon account from the app. This takes place every time a Dash Button is configured. Amazon then generates a “short” (<=48 bits) UDID for the Dash Button which associates it with an Amazon Account. They also store this somewhere on their servers.
  • The SSID and passphrase for the Wi-Fi connection are sent via audio packet to the Dash Button, along with the UDID that was just generated.
  • The Dash Button parses the data and attempts to connect to the Wi-Fi network. If it’s successful, it phones home to the Amazon servers with the supplied UDID. The Amazon servers “register” the button as active and tell the iOS app to continue setup.
  • From here, any further configuration data is sent to the button over the network, including what account is registered to the button (likely with more sophisticated verification than I’m alluding to*), what product the button is ordering, and shipping preferences.

*Just looking at the string dumps from the Dash Button firmware show that there is more sophisticated authentication taking place, it’s just hard to say when. I’m tempted to decompile the firmware just for fun, but I’ve already spent enough time looking at this damn $5 button…

And of course, here’s the final outcome of my efforts:


I’ve attached my MATLAB code in the off chance anyone wants to try this at home. It’ll probably take some tweaking for your specific setup.

Here’s the MATLAB code on GitHub.

That’s all I’ve got so far. I’m still curious in figuring out the six mystery blocks: if you’ve got any thoughts on it feel free to let me know. I might make another followup post taking a look at the firmware using IDA or something in the future, we’ll see. And of course if any Amazon employees want to get ahold of me and tell me how far off I was, I’d be okay with that too 🙂

Thanks to Matthew Petroff, GitHub user dekuNukem, and anyone else whom I may have forgotten to credit.

EDIT: It’s been pointed out to me by a few looking deeper into the button’s internals that the modulation scheme actually IS FSK with four carriers at 18130, 18620, 19910, and 19600Hz. I believe the reason why it so strongly resembled ASK when I observed the audio packets is because of the awful frequency response at the higher end of my phone, my mic, or both. A linear attenuation right at the top of the audible spectrum would explain the highest frequency being measured as lower amplitude. That being said, all encoding and modulation schemes still apply, with the highest frequency encoding representing binary 11.

In addition, there is in fact a CRC16 attached to each packet. It’s the first two bytes after the packet length declaration. Also, that length byte includes the length of the two bytes of CRC. That leaves 32 bits for the UDID, which is POSTed to the Amazon servers at where XX us US for the United States, DE for Germany, etc.  This jives quite strongly with my initial guess of button registration. Thanks to Benedikt Heinz (@EIZnuh) for sharing some of his research into the button’s firmware!