I decided to write this guide, because there are a few details and nuances that I will probably forget soon and then I will have to rediscover them again. So lets get dirty!

Arduino pro mini

This device probably does not need too much introduction. Small, bare-bones ATmega 328P with a LED and a reset button. Comes in 3V3 flavor, which is important for me, as most electronics I touch these days is 3V3. Also, it is good to work with 3V3 boards exclusively as there is then far less change to fry something up.

With boards that are kind of 3V3, or 3V3 compatible but not fully, sometimes a 5V impulse could happen on a GPIO and something else down that communication line can get's damaged. Increasingly not worth the risk. Especially since the proper tools are accessible and inexpensive (it was not always the case). One such tool is CH341A programmer board.

Blue CH341A board

I own a blue CH341A board, which is a bit different than the more well-known one, the black one. The blue one has a proper way to switch levels between 5V and 3V3 for both the VCC supply pin as well as for GPIO pins, meaning full 3V3 in my dictionary. Exactly what one needs. With the black CH341A board, the mod needs to be done to get everything down to 3V3 properly, as without the mod you risk frying your components with 5V.

This blue magic board probably also does not need to much introduction, in short it is a IC that allows to convert USB to various common serial and parallel interface including UART, I2C and SPI, among the few. It is usually used for flashing BIOS or firmware flash memory chips on the motherboards using the SPI mode but can be used to program Microchip's AVR devices using either SPI again or even UART mode.

Avrdude

Avrdude is the actual piece of software that moves the compiled firmware from the computer, through the programmer - the CH341A in our case - into the microcontroller. Latest version can be downloaded via Homebrew:

brew install avrdude

CH341A support in avrdude was added around version 7.2. At the time of writing, the version of avrdude obtained via Homebrew is 8.0 and Ican confirm it supports the CH341A. This can be checked like the following (might need to escape the question mark like \? depending on the shell used):

avrdude -c ? 2>&1 | grep ch341a

Which outputs the following in case of success (and nothing otherwise):

ch341a             = CH341A programmer: note AVR F_CPU > 6.8 MHz (ISP)

Let's move on.

Using SPI mode

Now with the support for CH341A on avrdude confirmed, it can be used to flash the chip (or the fuses) directly via SPI mode:

avrdude -v -e -D -p mega328p -c ch341a -U flash:w:firmware.hex:i

The support here is important because it is relevant only for this SPI mode, not the UART mode which is more complex to setup in this case and we get back to it later. For the completeness, here is the connection table:

CH341AArduinoConnection Purpose
GNDGNDCommon ground reference
VCCVCCPower supply for Arduino
SCK13SPI Clock
MOSI11Master Out Slave In data
MISO12Master In Slave Out data
CS0RSTChip/Slave Select, Reset

Note that for this to work, the mode jumper on on blue CH341A board should be set to SPI/I2C and the D1 LED should be glowing red, but there might be different variants.

Using UART mode

UART mode is getting to the core of the Arduino pro mini. The point of this product is that there is no onboard USB connector nor USB-UART converter. Instead, there is a small piece of software, called bootloader in the ATmega328P, that right at the start of the boot process waits for the data that come over UART and writes them into the flash memory. This way the program get's into the microcontroller. Here's the pinout, and the main reason I wrote this article:

CH341AArduinoConnection Purpose
GNDGNDCommon ground reference
VCCVCCPower supply for Arduino
TXDRXDSerial data connection
RXDTXDSerial data connection
MOSIDTRAutomatic reset

By looking into the CH341A datasheet, we can see that the pin 20 serves as a MODEM liaison output signal, data terminal ready, low-level active or DTR in short, when using 4.3.Asynchronous serial interface pins. The same pin serves as MOSI for the SPI mode. I mean, the Arduino can be programmed even without this pin, but then manual reset pressing is required just after the avrdude command is issued.

MacOS driver

To to get to the avrdude command itself, one next hurdle have to be cleared. Unfortunately, as things currently stands, Mac needs to install a driver for this serial converted to be able to talk with UART mode.

For me, the SPI mode worked straight away on Mac but when I enabled UART mode by switching a jumper, nothing happened. I could not see the device listed in /dev/* and the D2 LED, which according to mentions around the internet, should be glowing blue, no LED was lit. At first I thought I have a faulty model, as it was bought off Aliexpress, so I ordered another one, just to experience the same issue.

Then I tried to plug it into my ThinkPad T470 Arch machine and the blue D2 LED started glowing like there's no tomorrow. I then dual booted Windows 10 on the same machine and after a minute or so, windows updater picked it up, installed the driver and, again, blue LED was on. That meant something was off on my Mac. After a bit of searching I found a shady looking driver:

brew install --cask wch-ch34x-usb-serial-driver

With newer MacOS versions, including mine, it is also required to allow installing kernel extensions from given vendor in the settings and everywhere it is recommended to reboot after the installation. The blue LED was now on as well.

The serial device

With the driver on Mac installed and the blue D2 LED lit, next step is to determine the actual device the system has created for the communication. I could not find any good documentation on this however, so discovery here we go. Make sure the UART jumper is selected, unplug the CH341A device:

ls -l /dev/* > before.txt

Replug the device back:

ls -l /dev/* > after.txt

Compare the two:

diff before.txt after.txt

This results in the following on my system:

> crw-rw-rw-  1 root        wheel      0x9000007 Feb 28 08:12 /dev/cu.wchusbserial1440

Thus the device is located at /dev/cu.wchusbserial1440, but note that the actual number can change over time. Test by reading the Arduino pro micro ATmega 328P's signature:

avrdude -v -p m328p -P /dev/cu.wchusbserial1440 -c arduino -b 57600 -U signature:r:-:h

Which results in the following:

Avrdude version 8.0
Copyright see https://github.com/avrdudes/avrdude/blob/main/AUTHORS

System wide configuration file is /opt/homebrew/etc/avrdude.conf
User configuration file /Users/peterbabic/.avrduderc does not exist

Using port            : /dev/cu.wchusbserial1440
Using programmer      : arduino
Setting baud rate     : 57600
AVR part              : ATmega328P
Programming modes     : SPM, ISP, HVPP, debugWIRE
Programmer type       : Arduino
Description           : Arduino bootloader using STK500 v1 protocol
HW Version            : 2
FW Version            : 1.16

AVR device initialized and ready to accept instructions
Device signature = 1E 95 0F (ATmega328P, ATA6614Q, LGT8F328P)
Reading signature memory ...
Writing 3 bytes to output file <stdout>
0x1e,0x95,0xf

Avrdude done.  Thank you.

Note that we are now using aruidno as a device, not ch341a under the -c parameter. Also note that when using the bootloader mode, we need to specify the baud rate of 57500. Programming is done the same way as with the SPI mode:


avrdude -v -e -D -p m328p -P /dev/cu.wchusbserial1440 -c arduino -b 57600 -U flash:w:firmware.hex:i

Enjoy!