Phillip Pearson - web + electronics notes

tech notes and web hackery from a new zealander who was vaguely useful on the web back in 2002 (see: python community server, the blogging ecosystem, the new zealand coffee review, the internet topic exchange).


Imaging old 3.5" floppy disks (DOS 720k, DOS 1.44M, ADFS 320k, ADFS 800k, DFS 100k, DFS 200k) in New Zealand using libdsk/dsktrans

Wow, it's been a while since I last posted. Since then I started working at Google, made a ton of hardware projects for the Acorn series of computers: Electron, BBC Micro, BBC Master, and have enjoyed a year and a half of marriage.

I spent a week in New Zealand visiting family for Christmas. My main project while there was to clean up a whole lot of stuff I left behind when I moved to the USA, which, among a whole lot of physical stuff like university notes, included various hard disks, and several hundred floppy disks in various formats. Most of them were 1.44M DOS disks, but the most interesting ones (because I was hoping to find traces of the earliest programs I'd written, in the 80s) were in various Acorn formats like 320k ADFS and 200k DFS, as well as the newer 800k and 1.6M ADFS.

Luckily it appears that the last desktop computer I bought, an Athlon XP machine from 2005, was capable of reading *all* the formats. As confirmed by Andrew Benham in London, the libdsk project and its dsktrans tool work really well and can autodetect all the formats I was interested in.

Healthy disks could be copied quickly with: dsktrans /dev/fd0 image.dsk

Disks with errors required: dsktrans -retry 10 -stubborn /dev/fd0 image.dsk

I never needed to specify -format; I experimented a bit with some unknown disks, but it looks like the ones that dsktrans couldn't autodetect were unreadable using any of the formats I tried.

This results in .DSK format files, which contain the data from the sectors on the disk, but also quite a bit of metadata about the disk, and can be converted into raw images suitable for reading in emulators by running them through dsktrans again:

For DOS disks, which macOS will happily open if given .dmg extensions: dsktrans image.dsk -otype raw image.dmg

For ADFS disks: dsktrans image.dsk -otype logical image.adf

I assume the ADFS approach will also work for DFS, but either I haven't found an emulator that understands 3.5" DFS disks yet, or my disks were too damaged.


Things you learn working at a startup

So it's kinda stressful, and I wouldn't recommend spending your whole life like this, but that adage about learning everything all at once when you're working at a tiny startup is kinda true. If I don't write all this down, I'm going to forget it, so here's what I've been up to in the last few weeks:

On September 15th, Mode Media, where I'd worked since 2011 (or 2009 if you include the time at Ning before we got acquired by Mode), went out of business. I was lucky enough to be rescued from the ashes of the company because an investor, Cyndx, wanted to buy Ning and keep it running, and immediately sent six of us ex-Mode employees contracts for the next week, and put in a bid to buy Ning. A week later, a completely unrelated investor, Noosphere Ventures, swept in and bought the company instead, which was exciting to say the least, but seems to be working out well.

This meant that Ning went from being a low priority maintenance mode job, supported by a ton of people at Mode who worked on other things most of the time, to being extremely important, and also super understaffed. We were down to three engineers and no ops, IT, or QA. I had been taking care of most of the backend of the system for a while, but now I was also responsible for ops, DBA, and IT work. So, basically, we went from a 150-person company to a 6-person startup. Luckily our entire engineering team is pretty seasoned (I think that each of us has at least 10-15 years of industry experience), our CEO/GM has decades of experience negotiating with suppliers to keep everything under control, and we got to keep our two most experienced support folks, to keep the lines of communication open with customers.

Notable stuff I've done since then:

- Hunted around to find passwords, SSH keys, and SSL certificates for everything in the system. Edited databases to give myself administrator access where necessary.

- Poked my way through the system until I found the LDAP servers, and brought up an OpenDJ replica on EC2, along with copies of our Confluence and JIRA servers.

- Debugged a ton of MySQL server outages, mainly caused by failing disks or partitions getting full after a replication failure caused binary log purging to stop. Rebuilt several 900GB databases from backups and fixed up replication.

- Moved everything from two AWS accounts onto a third account (for the new company). This meant coding up a hacked up MapReduce-y type set of Python scripts to copy 140 terabytes (400 million files) from an S3 bucket in one AWS account to a bucket in another account in a couple of days, learning enough about CloudFormation to bring up a copy of an old stack in a new account, rebuilding a bunch of Wordpress blogs from backups and disk snapshots, and copying a ton of random data around in S3.

- Learned that granting someone from another account access to your S3 bucket gives them the ability to create files that you have no access to, unless you set a bucket policy to force them to give you full control. Good thing I'd optimized my copy scripts so I could rerun the whole thing in 12 hours...

- Learned some tricks for setting up an AWS VPC, like making sure to turn on DNS hostnames, to avoid getting a warning about not being able to resolve the hostname every time you run sudo, and also some other apps refusing to start up.

More to come, I'm sure... once this is all taken care of, I imagine there'll be way more cloud work, and tasks to bring as much of our infrastructure as possible into 2016, and enable future development. It's definitely keeping me busy, but it's exciting for sure!


Phil's guide to Burning Man, part 1

This year will be Burning Man #7 for me and #3 for my fiancée, but #1 for many of our friends, who we've finally convinced to come with us :D

We have a ton of advice to share. It probably won't all fit in one blog post, but here goes. I expect to write about each of the following:

  • Basics: when and where
  • Tickets, and obtaining them
  • Transport: getting in and out
  • Camping: friends and family -- your support network
  • Accommodation: your tent and everything inside
  • Your bike: essential for getting around
  • Food
  • Hygiene

Here we go!


If you know nothing about Burning Man, start by reading everything you can on the official Burning Man site, but especially the preparation page and the 10 principles. I'd highly recommend watching Spark: A Burning Man Story too; you can stream it on Netflix.

In a nutshell: Burning Man is a week long party in the Black Rock Desert, to the north of Reno, Nevada. It runs for about a week, ending on Labor Day: in 2016 it's Sunday August 28 - Monday September 5. It's a festival of some sort, except all the entertainment comes from the participants; there aren't any "official" acts, but hundreds of groups of determined campers make amazing art and throw parties large and small, so it feels less like a music festival and more like you've stumbled upon some foreign-but-familiar city filled with hedonistic artists and house party hosts.

Some jargon: "Burning Man" is the event, "Black Rock City" is the place. If you attend Burning Man, you're a "burner", and BM is typically referred to as "the burn". The desert floor is called "the playa", and the ever-present beige dust is "playa dust". When you or your car or your stuff is covered in enough of it, you're "playafied".

I highly recommend trying to catch as much of the event as possible; I've always gone in on Monday or Tuesday, and left on Labor Day. If you can't spare that much time, you can trim a little off each end of the week: you could probably feel pretty satisfied coming in during the day on Wednesday and leaving early Sunday morning. Wednesday night is a big party night, as are Friday and Saturday. I personally love staying for Sunday night; while there's less to do during the day as many camps start packing up early Sunday morning, the Temple Burn that evening is beautiful, and the departure line ("exodus") is much shorter on Monday morning.

Assuming you intend to go, at this point in the year you need to be thinking about a few things:

  • Ticket: you need one to get in. They cost about $400, plus another $80 or so for your car. It's not the end of the world if you don't have one yet, but you will have to hustle.
  • Transport: how are you getting there? Do you have your own car, or are you going to rent one or drive with friends? Are you renting an RV to sleep in, or do you just need something big enough for you and your gear?
  • Friends: who are you camping with? You probably want to join a pre-existing camp. If you don't know any burners, it's time to start making friends!

Those are your priorities right now. Less critical, but still important later on, are the details of how you'll survive the event itself.


The main ticket sale is over now, which means ticket availability will be pretty scarce for the next little while. Ask around in case one of your friends picked up more tickets than they needed, though. Some camps are permitted to pre-purchase tickets (because the Burning Man Organization wants to make sure they can attend), so your camp organizer may be able to point you in the right direction.

Failing that, when tickets start hitting mailboxes around July, they'll also start showing up on Craigslist. Right before the event, there's an "OMG Sale", which releases a few thousand more tickets onto the market.


If you're camping in a tent (which doesn't have to be so bad -- I'll be writing another post with details on how I do it relatively comfortably), you just need a ride for you and your gear. You'll end up with a surprising amount of stuff; I've never ended up with spare room in a car after packing two people, their gear, and their bikes, especially once you include food and ~18 gallons of water per person.

If you're planning on renting an RV, enquire now. I looked into it in December and found that the cheapest RVs available were around the $5000 mark -- it seems that taking an RV is now an expensive luxury. If you don't mind adding 10 hours to the drive, you can save some money by renting in LA or Las Vegas instead of San Francisco or Reno.

Renting a car will cost you ~$600 with all the insurance, and you'll need a bike rack also. If you have a brand new car that you can't stand to get dirty, you probably don't want to take it (because it'll smell like playa dust for up to a year afterwards), but the playa-inflicted damage isn't that bad; it's probably equivalent to a couple of years of ski trips.

I rented an ordinary compact from various rental agencies in 2010, 2011, and 2012, and it was a little cramped, but worked just fine -- although you'll save on time and stress during the packing process if you rent a bigger car.

There are various other options available to you if you have someone to haul all your gear, and possibly all your food: buses, trains, and planes!

Burning Man runs the official Burner Express bus all the way from San Francisco to BRC, and also for the short hop from Reno to BRC. It has its own entry line, so you get in much faster than driving, except your luggage is limited to one or two suitcases. This is probably an excellent way to get in solo, as long as you have transport for your bike (or you rent one from Playa Bike Repair).

Consider also mixing it up: Last year one campmate packed his bike and cooler in our camp trailer, took Amtrak from SF to Reno last year (and loved it!), then joined other campmates in their RV from there.

Finally, the luxury option is to catch a small plane from Reno airport directly in to BRC; this will set you back about $1000, but is by far the quickest way to get in and out, and the view is amazing.

Your camp

Unless you really like roughing it, it's essential to join a pre-existing camp that has done this before and is fairly good at it. A really organized camp will have a generator, trash disposal, greywater disposal, a shower or two, perhaps their own porta-pottie, a kitchen with a grill or two, a chill space, and a public space. IMHO the absolute minimum for comfort is: a shower, good greywater disposal (so individuals don't have to haul their dirty shower water home), and a chill space.

Some camps will require you to work planned shifts as well as pay camp dues and bring gifts for the bar, etc. Others won't be very demanding at all. If you're camping on Esplanade, 2:00, or 10:00 (the highest visibility streets), or in one of the plazas, you'll probably have to spend time helping out with whatever entertainment your camp is contributing. If you're camping further inside the city, your camp might be purely residential, or you might be putting on smaller parties. Shop around to find something that works for you :)

For friends who are camping with us: the camp I'm part of has a shower, workable greywater disposal, some solar panels and car batteries for lighting, a kitchen dome, and a really nice chill area out front. Some campmates put together a giant trampoline type hammock every year, and we also have a dome full of chairs to hang out in during the day. We also have no camp dues, and nobody will yell at you if you don't do any work (but folks who do help out will be remembered when it comes to allocating pre-sale tickets the following year).

That's all for now... the next blog post will be about tents and bikes, I imagine!

... more like this: []


Cheap Chinese STM32F103 boards

I recently ordered a bunch of STM32F103 boards from China. They have a rectangular footprint like the Teensy or Arduino Micro, and a 48-pin STM32F103C8T6 MCU on board -- a 72MHz Cortex-M3 with 64kB of flash and 20kB of RAM. It's just the single MCU chip on board, with a bootloader, and the SWD pins brought out to a header, which should make it quite flexible; I'll be able to program/debug them with Eclipse and OpenOCD if I want full control, or I can use the STM32duino Arduino port if I just want to push code to them. The best part is that they only cost $2.25 each on Aliexpress, or about $3.50 on eBay, which is about half the cost of the chip alone when bought in the US.

The boards I've ordered are 2.1" long by 0.9" wide, with a rows of 20 pins along each side, and 0.6" from the center of one row to the other. I've made a KiCad part and footprint for them:

Part/library → myelin-kicad-libraries/stm32f103c8t6-module-china.lib

Footprint/module → myelin-kicad.pretty/stm32f103c8t6-module-china.kicad_module

These should be good for controlling LEDs over USB, or for soldering into projects where I feel that a Teensy would be too expensive.


CMSIS-DAP work: implemented raw JTAG support, and ported the HID firmware to Pro Micro and Teensy 3.2

While I am so happy to have my J-Link back, the couple of weeks without it have been very productive in terms of open source contributions. After finding out that OpenOCD didn't support raw JTAG mode on CMSIS-DAP adapters, I bit that off as a potential project, and eventually got it working, then did some performance tuning, and I'm pretty pleased with it now. With my LPC-Link2, it can program the flash in an ATMEGA32U4 over JTAG at about 1/4-1/3 of the speed of the J-Link (which is kind of a speed demon). I'm going to let it soak on GitHub for a while, then clean it up and submit it to OpenOCD once it's had a bit of scrutiny.

Clone myelin/openocd and check out the cmsis-dap-jtag branch to try it out.

Implementing this required getting pretty familiar with the CMSIS-DAP source code and protocol, and at some point I realized that it wouldn't be super hard to port the SWD/JTAG debugger part of the CMSIS-DAP firmware over to any USB capable microcontroller. Full CMSIS-DAP support requires the debugger part, plus a serial port and a mass storage device emulator that's also capable of flashing a .bin file to a chip, but I'm not doing this to provide a USB interface to a custom board (like the ones on the mbed site), so I've skipped those two. I'll probably add in the serial port sometime, because I wrote serial bridge code for teensy-openocd-remote-bitbang already.

This was mainly a matter of getting rid of Keil-specific code, plus a small amount of debugging:

  • The _OUT functions (e.g. PIN_SWDIO_OUT) take a uint32_t with a boolean value in the LSB, but often junk in the higher bits.
  • A 32-bit processor is expected, so there was one point where I needed to add (uint32_t) casts to avoid losing the high bytes of a word.

So here's CMSIS-DAP firmware for your Pro Micro (ATMEGA32U4) or Teensy 3.2 (MK20DX256) board.



Trying out the CNC mill at Noisebridge

I've been doing quite a bit of fabrication work at Noisebridge over the last year or so -- mostly electronics, plus a bit of 3D printing. One bit of equipment I've been interested in for a while is the MaxNC 10 CNC mill, which has been covered by a sheet for a while and appeared out of order, but has recently been unearthed and looks a bit happier. I gave it a try tonight, and it looks like it should be usable!

It's controlled by a computer under the desk running an ancient version of Ubuntu, with a realtime kernel for the control software. Booting it up and double-clicking the maxnc10ol link on the desktop brought up LinuxCNC. Flipping the red switch on the mill brought it to life, and the machine started up properly after hitting F1 then F2.

The axes are set up so that the origin is the bottom left front corner of the thing being milled, and the positive direction is right/back/up in the x/y/z axes:

  • X axis: + moves the platform left, - moves the platform right
  • Y axis: + moves the platform towards the user, - moves the platform towards the machine
  • Z: + moves the bit up, - moves the mill down

When homing axes, you can only move an inch in the 'negative' direction before the software will stop you -- need to rehome the axis (redefine your negative position as 0) then hit F1 twice and F2 again, then you can move it another inch. So it took a few tries to get the machine to move far enough to the left (moving the platform over to the right), and a couple to get the Y axis homed.

Once the spindle looks like it's in a safe place, you can run your program with Machine | Run Program. This will start the spindle and move through the program, then stop wherever it finished. It happily ran through its demo program.

The mill is a little small for the sorts of things I have in mind (large wooden boards cut in interesting shapes, like a lot of art at Burning Man these days) but could work nicely for PCB milling -- someone at Noisebridge has had some luck with this -- if I get some cheap PCB blanks from Aliexpress, and a couple of carbide mill bits. I suspect that the sweet spot here would be for very simple single-sided boards which are SMD-only... for example, one-off custom LED fixtures with a string of addressable LEDs and no onboard controller (or a very simple one that doesn't need any complicated traces or small pads -- so my favourite tiny MCU, the MKE04Z8VTG4, would be out, but the SOIC version would be OK, or maybe the 0.8mm TQFP44 MKE*VLD* chips).


Still trying to get JTAG working with the ESP8266

Good news -- my replacement J-Link is in the mail! Reliable hardware on the way.

In the meantime, I'm still working on my CMSIS-DAP adapter and my ESP8266 board. I'd mis-soldered a 10uF capacitor between TCK and ground (instead of 3V3 and ground), which was stopping anything from working, but after hooking my Saleae box up to the board, I debugged that fairly quickly. I fixed some bugs in the code, but OpenOCD still failed to do the initial chain scan, so something's still broken.

To debug this, I changed tack: OpenOCD also has a "remote_bitbang" driver, where it sends a series of ASCII characters to a TCP socket, to drive a very very simple JTAG adapter. This sounds like a recipe for the worst JTAG performance ever, except that the Teensy 3.2's serial-over-USB performance is very very good, and I saw some pulses as short as 2us in the logic capture, so this should be able to get me ~250 kbit/s, which is certainly good enough for now.

Here's the teensy-openocd-remote-bitbang code, on GitHub

It took a bit of messing around with socat on OS X to get it to bridge between a TCP socket and a serial port, but I found the magic set of arguments eventually:

socat -d -d -d file:/dev/tty.usbmodem1485121,clocal=1,cs8,nonblock=1,ixoff=0,ixon=0,ispeed=9600,ospeed=9600,raw,echo=0,crtscts=0 tcp-listen:3335,reuseaddr

The corresponding OpenOCD incantation is:

openocd -d -c "reset_config srst_only; interface remote_bitbang; remote_bitbang_port 3335; remote_bitbang_host localhost" -f target/esp8266.cfg

Unfortunately OpenOCD *still* couldn't scan the JTAG chain, and it did get bits out TDO this time, so that hardware is not completely broken, but I still have some debugging to do.

Update: I suspect TDO is in high-impedance mode -- it looks like it's floating around 1.5V. Perhaps the ESP8266 doesn't enable its JTAG pins while in reset? Either that or maybe I need to program the chip with code that enables the JTAG pins.

Update 2: Got it working! It turns out that my understanding of remote_bitbang's reset signals was the wrong way around -- RESET is active-low electrically, but the remote_bitbang protocol spec assumes active high. So "reset=0" means 3v3 on the reset pin, and vice versa. The ESP8266 JTAG pins are high-impedance in reset, which is why I saw TDO floating. This doesn't explain why my CMSIS-DAP code didn't work, but at least now I have a known-good state to compare with.


Kinetis E and OpenOCD update, and some Kinetis FTMRH flasher code and JTAG rambling

Following up on my last post, I managed to get OpenOCD flashing a Kinetis MKE02Z64VLD2 chip, to the point that I could hook it up to Kinetis Design Studio and program/debug almost as easily as with my J-Link, albeit without unlimited flash breakpoints, and it would often get confused about where it was on startup, although after hitting F8 everything would come back to normal.

I posted to the OpenOCD-devel mailing list and got a bunch of responses from others who had been working on the same problem, including Ivan Meleca, who had written a flash driver for the KE02/4/6 series but hadn't had time to upload it. I tested this out tonight on my MKE02Z64VLD2 and MKE04Z8VTG4 boards and it works well, so look forward to seeing that in mainline OpenOCD soon.

Ivan's code works, so I don't want to mess with it, but I'm proud of a couple of little tricks I figured out for my own implementation. I wrote a Python script to process the output of arm-none-eabi-gcc -g -O0 -mcpu=cortex-m0plus -mthumb -c -Wa,-adhln, extract out the opcodes, and comment everything up nicely, which lets you build self-contained routines that are easy to download and run on target devices. I'm not sure how everybody else wrote their flasher code, but they all seem to have ended up with slightly differently formatted hex bytes embedded in C files, so maybe by hand? Anyway, my flash algorithms and C-to-assembly-to-C code are up on GitHub as myelin-mcu-flash.

Next, my esp8266-jtag boards came back from OSHPark, and I soldered one up and connected it to my LPC-Link2, only to find that OpenOCD doesn't support JTAG operations on CMSIS-DAP adapters, only SWD. So I started writing some code to get that working. The next day, someone wrote on OpenOCD-devel that they were trying to do the same thing. So hopefully we can combine efforts and sort it out. I've learned a lot about JTAG in the last few days -- in a nutshell it's a very complicated way to reset a chip and read and write two registers, but it turns out that you can do a *lot* with those building blocks :)

Here are some notes from my attempts to get OpenOCD to support the raw JTAG operations in the CMSIS-DAP API, to talk to my ESP8266 board.

To get JTAG to work, you need to be able to clock in arbitrary sequences on the TMS pin, and you need to be able to clock in arbitrary sequences on the TDI pin with TMS low, while capturing output on the TDO pin, and setting TMS high as you clock in the final bit. This makes more sense if you look at the JTAG state diagram. TEST LOGIC RESET resets the test logic. Entering CAPTURE-DR copies data from the device into the DR register, and entering UPDATE-DR copies data from the register to the device. Every entry into SHIFT-DR clocks a bit from TDI into DR (on the rising edge of TCK), and a bit from DR out TDO (on the falling TCK edge). CAPTURE-IR, SHIFT-IR, and UPDATE-IR work similarly. All other states are just intermediaries that make it convenient to navigate around the state machine without needing too long of a sequence on TMS.

The relevant CMSIS-DAP operations, using the defines in the mbed CMSIS-DAP code:

  • ID_DAP_SWJ_Sequence, which clocks out a sequence on TMS
  • ID_DAP_JTAG_Sequence, which clocks out sequences on TDI, and optionally sets TMS and captures TDO.

DAP_SWJ_Sequence is already supported, because it's necessary to switch between JTAG and SWD mode. ID_DAP_JTAG_Sequence isn't used for SWD, but we need it for raw JTAG. The packet format seems to be:

  • byte 0: the number of TDI bit sequences in the packet
  • byte 1: sequence info for the first sequence
  • byte 2..length(sequence 1): sequence data for the first sequence
  • ... other sequences follow ...

The max sequence length is 8 bytes (64 bits), and the sequence info byte is: (0x80 if capturing TDO, else 0) | (0x40 if TMS is set, else 0) | (sequence length in bits, with 64 encoded as 0).

This suggests that a 'scan' operation (writing something into the IR or DR register) will require one DAP_SWJ_Sequence command to move the TAP to Shift_IR or Shift_DR and one DAP_JTAG_Sequence command to write the command. We can probably just leave the TAP in the shift state once the command is done, but we could use another DAP_SWJ_Sequence to get back to the idle state if that's not okay.

Looking at the bitbang driver, it looks like the desired end state is given in cmd->cmd.scan->end_state, the input and type (input or output) can be obtained via jtag_build_buffer and jtag_scan_type, and it's an IR scan if cmd->cmd.scan->ir_scan is set. The actual scan function moves to the TAP_DRSHIFT or TAP_IRSHIFT state as required, then clocks the data in/out on TDI/TDO, then executes another state_move to get to the desired end state. So I guess we'll actually require three DAP operations to do this, because of the final requirements for TMS (unless we want to try to pack those into the JTAG_Sequence packet).

11pm update: Implemented! It compiles, but I haven't tested it at all yet, so expect many bugs..


Working toward being able to flash Kinetis E chips with OpenOCD, using USBDM flash routines

My J-Link broke yesterday, so I'm back to using either my FRDM-KE04Z board with USBDM firmware or my LPC-Link2 with CMSIS-DAP firmware to program my Kinetis E devices. USBDM seems to only support Windows and Linux, so I'm trying out the LPC-Link2 with OpenOCD today.

My chip is a Kinetis MKE02Z64VLD2 (64 kB flash, 4 kB RAM, 20 MHz max), which OpenOCD doesn't have a flash algorithm for. With any luck it should be able to debug my chip out of the box, but I'm going to have to steal code from USBDM to get it to download my code.

First things first... Homebrew seems to install OpenOCD 0.8.0 when you run 'brew install open-ocd', so I built and installed 0.10.0-dev from git. Copying /usr/local/share/openocd/script/kl25.cfg as ke02.cfg and running openocd with the following in openocd.cfg seems to get it started:

interface cmsis-dap
adapter_khz 500
transport select swd

source ke02.cfg

Once OpenOCD is running, it listens on ports 3333 (GDB remote protocol) and 4444 (command shell). telnet localhost 4444 gives you something like the J-Link Commander console. 'reset' and 'halt' do the expected things, so it looks like it's handling the SWD protocol just fine.

Now to figure out how to extract the KE02Z64 flashing code from USBDM... here's what I've figured out:

So it looks like we have all the pieces of the puzzle... the trick will be to get FlashProgrammer_ARM.cpp wired up to OpenOCD's interface code, so it can write and execute code on the target.

(to be continued)


Using a Teensy as a USB-Serial bridge to program an ESP8266

Hopefully in future I'll be able to program my ESP8266 modules using JTAG, but I have a project from last year which used the ESP8266, didn't bring out the JTAG pins, and tied GPIO15 (TDO) to ground, which means it would require desoldering the ESP-03 module to fix. I programmed it using the ESP8266 Arduino extension and a Sparkfun FTDI Basic Breakout board, which unfortunately doesn't bring out RTS, which meant for a lot of power cycling. To make matters worse, the FT232 seems to crash sometimes when subjected to this sort of thing, losing control of its DTR output and needing power cycling *itself*.

I happen to have a couple of Teensy 3.2 boards here, however, and they're super easy to program up to act as a more flexible serial adapter. Here's the sketch I ended up with, which happily programs my ESP-03 at the max upload speed selectable in the ESP8266 Arduino IDE, 921600 baud, and with any luck will work with the ESP GDB stub:

// Simple USB-Serial bridge for programming ESP8266 modules

// new connector:
// GND (black) RXD (grey)  GPIO0 (brown)
// 5V (n/c)    TXD (white) RTS (purple)

// RX2 = pin 9, white wire, connects to ESP8266 TX [saleae ch0]
// TX2 = pin 10, grey wire, connects to ESP8266 RX [saleae ch1]
#define HWSerial Serial2
// DTR = pin 11, brown wire, connects to GPIO0 on the ESP8266 [saleae ch2]
#define DTR_PIN 11
// RTS = pin 12, purple wire, connects to CH_PD on the ESP8266 [saleae ch3]
#define RTS_PIN 12

uint32_t current_baud = 9600;
uint8_t current_dtr = -1, current_rts = -1;

void setup() {
  Serial.begin(9600); // fake value -- USB always 12Mbit
  pinMode(RTS_PIN, OUTPUT);
  digitalWrite(RTS_PIN, LOW);
  pinMode(DTR_PIN, OUTPUT);
  digitalWrite(DTR_PIN, HIGH);

void loop() {
  if (Serial.available() > 0) {
    uint8_t c =;
  while (HWSerial.available() > 0) {
  // update params
  uint32_t baud = Serial.baud();
  if (baud != current_baud) {
    current_baud = baud;
  uint8_t rts = Serial.rts();
  if (rts != current_rts) {
    current_rts = rts;
    digitalWrite(RTS_PIN, current_rts ? LOW : HIGH);
  uint8_t dtr = Serial.dtr();
  if (dtr != current_dtr) {
    current_dtr = dtr;
    digitalWrite(DTR_PIN, current_dtr ? LOW : HIGH);
... more like this: [, ]