Exploring and using the 3 cent microcontroller. Impression: A legitimate replacement for simple use-case projects.

Idly I decided to research what is the cheapest microcontroller you can get and develop with, and I came across a Hackaday post regarding precisely this topic. Tim from [coldcpu] has put in a decent effort at categorizing the cheapest microcontrollers available, along with their associated pros and cons. In another separate Hackaday post, someone used a Padauk microcontroller to bit-bang the WS2812b protocol.

This led me down the rabbit hole where I had some fun watching Dave of EEVblog fame evaluate these microcontrollers over a series of videos. He seemed to have quite a positive opinion on them. There is also some work that has been done through the EEVblog forums on creating an open source compiler in the Small Device C Compiler (SDCC). It was fun reading for awhile, but my enthusiasm was dampened somewhat when I realized that I needed to build my own programmer. Added to that was the realization that there would not be an easy way to debug these microcontrollers through SDCC since it does not support the In Circuit Emulator (ICE) for Padauk.  

However, when merely days later, the author of The Amazing $1 Microcontroller, Jay Clarson published an in depth look at these microcontrollers, my interest was rekindled and I decided to give these microcontrollers a spin.

First Impressions

After playing around with these chips for awhile, I find that these low cost multipurpose chips are excellent for projects where all you need is to toggle a pin or take an input. You can use them as protocol decoders, GPIO expanders, motor controllers, logic ICs... the list goes on. Basically anything where you don't need a ton of space, memory, or speed, this is the IC for you.

So why use this chip over say something like an EFM8 or a STM32? Well for starters you can get these in SOT-16 packages, which makes them reasonably easy to hand solder. And why over something like PIC16? A PIC would cost about 20 times as much, and with a Padauk, I don't even have to think to plonk it down in a build for testing. If it works out, great! If it doesn't, no great loss.

Below I will give a more detailed overview on how these chips can be obtained and used. In the last section I will also provide some example code to get started with.

If you want to get started exploring the material, Padauk's website and documentation is all available in English.

IDE

I admit that when I first opened the IDE I felt that it looked old and kind of clunky. The font was jarring at first, but it has begun to grow on me. It is easy to read and reminds me of old-school command lines.

I have also come to appreciate the IDE a lot. It starts quickly and every option has a purpose. It feels like someone actually went through and removed all the extraneous options. It continues immediately where I left off. Opening new projects is a snap, and switching views is really quick. It might have to do with it being a 30MB download. Project files are easy to replicate and there's none of that nonsense about having to create the project in the IDE itself before it can be recognized as a valid project. It integrates flawlessly with the programmer and the ICE and I have no issues running it.

Code Generate Tool

The Padauk IDE helps you to generate code for the various communication peripherals as the ICs do not have these as hardware peripherals. This can be both a curse and a blessing. On one hand there isn't a 100 page pdf of registers to work through to configure, on the other hand you have to spend cycles, memory, and power to implement UART, I$^2$C, and SPI.

I found the example code to be quite helpful and useful, although I struggled with trying to understand the I$^2$C code with it's goto-s.

Application Code

This is where I have to take off points. The information provided here is a jumbled mess and some links are missing. I wish this section was cleaned up because it has such good potential as a guide to using Padauk ICs, especially those who haven't had a chance to use them before. However, frequently you can find some enlightening parts about the logic behind Mini-C.

Software Support

There is no software support of any type. There are no forums to post questions or get answers from. The error messages in the IDE can be quite cryptic as well.

In my experience, using both the application documentation in the IDE as well as the documentation in the datasheet was sufficient to keep me going. I found that register programming here is pretty rewarding as compared to some of the larger microcontrollers because there are a smaller number of registers to use, mainly because you don't have to configure communication peripherals.

The support you can get through email is surprisingly good for sourcing information and they got back to me within a day when I had a few questions on the QFN versions of these chips. Answer: there is only one supplier selling one QFN version of these ICs, so don't expect to be able to get QFN ICs unless you need huge quantities of these ICs. However, they were less able to help with my questions on firmware development.

Hardware lineup

Padauk mostly manufactures One Time Programmable (OTP) parts, although it does have some Multiple Time Programmable (MTP) ICs. This makes sense in the context where you are manufacturing a million of these parts and are only programming them once. It saves on having to dedicate a few pins to programming and debugging, and it probably also costs less as well.

The development framework for Padauk is that you design and debug the circuit with the In Circuit Emulator (ICE) and when it is time to transfer it onto a PCB you load the programmer with the firmware and then program each OTP IC once. Ideally, the IC would work as well as using the ICE, as it should emulate each IC correctly.

Padauk offers these ICs mostly in the SOT form factor. The QFN form factor is largely unavailable through distributors and probably only available through direct requests.

ROM ranges from 1k to 4k words, and RAM ranges from 16 to 256 bytes.

Where can I buy these microcontrollers?

From the manufacturer's website:

http://www.padauk.com.tw/en/contact/method.aspx?num=2

This page lists the authorized distributors. The only problem is that a lot of these distributors are from the 1688, which only caters to customers in China. An alternative is to source from Taobao, and there are more sellers for Padauk parts than what is listed here on the website. However, Taobao can be quite a hassle to ship from, especially if you can't read Mandarin, and the shipping is expensive if you are shipping small quantities.

Another alternative is to buy from LCSC, a Chinese version of Digikey and Mouser. While they are not listed as an authorized distributor, in my experience, LCSC is the best choice for getting the programmer and In Circuit Emulator (ICE) for Padauk because the website options are in English and they provide fairly good options when it comes to shipping. I didn't know about this until recently, but LCSC also gives you a pretty steep discount on shipping if you order PCBs and components. While the programmers and ICEs usually listed as not in stock, you can get a quotation from LCSC in a day and they are happy to source it for you. Having wrangled with shipping from Taobao, I think this might be the best method for obtaining Padauk programmers and emulators for now. I found the prices to be quite similar to other sellers.

Buying from LCSC

You will want to get the 5S-P-003 programmer and the 5S-I-02B emulator as these are the latest versions of their respective types.

Programming Experience

I first got the programmer and sourced two different ICs: PFS173 ($0.08) that can be reprogrammed multiple times, and the original 3 cent microcontroller, the PMS150c.

The programmer came with a 16 pin SMT test socket so I was able to program the SOT versions of these chips. One really nice feature is that the IDE includes an 'OS detect' program that helps to detect what chip is being used. When I used the OS detect program, it indicated on the programmer that Pin16 was open, and I found that one of the headers on the SMT test socket was not soldered in correctly. Once I resolved that issue, everything worked smoothly.

Programmer

The programmer makes a really loud beep when programming has failed and it really startled me when it first happened. But I guess it makes sense on a noisy factory floor. There is also a large green blinking CHECK? that appears in the top right of the Writer program. If that is the case, check to make sure all connections are correct and connected. If programming is successful, a blue PASS will flash instead.

To program, click on the Auto Program button in the Writer. You can also press the Program button on the programmer itself once you have uploaded the most recent .pdk file onto the programmer.

The progammer also indicates to you how you should connect your IC. On the back of the programmer are a series of jumpers that you can use. For example, if you are using a SOT16 IC, and in the writer window it says S16/D16: JP2 then you should check that all the rows on the JP2 header are connected on the back of the programmer. If you are using a SOT14 IC (S14: JP2 / IC Shift 1), then you should also connect your IC one space from the top of the ZIF socket on the programmer.

The programmer also compares its version against that on the computer, and the writer warns you if they are different. Loading the code onto the programmer is fast and you can use it independent of a computer. The programmer will display the expected IC, checksum, as well as the program name on the LCD.

Overall, I found the programmer very user friendly.

Code Examples

I wrote some starter code if those are interested in getting started. This is written for the PFS173 microcontroller, which is the newest flash-based microcontroller in the Padauk lineup.

Pinout for PFS173

Simple GPIO

Toggle a LED when a button is pushed.

Configuration options: PA0: Input, PA3: Output

#include	"extern.h"

// PA0: Input, PA3: Output

void	FPPA0 (void)
{
	.ADJUST_IC SYSCLK=IHRC/2;
	PA = 0b_0000_0000; // Port A Data register
	PAC = 0b_1111_1110; // Port A Control register, 0/1: input/output
	PAPH = 0b_0000_0001; // Port A Pull-High Register 0/1: disable/enable
	$ PADIER 0b_1111_1001; // Port A Digital Input Enable Register, 1/0: enable/disable, Bit 2:1 is reserved
	while (1)
	{
		if( pa.0 == 0)
		{
			pa.3 = 1;

		}
		else 
		{
			pa.3 = 0;
		}
//		...
//		wdreset;

	}
}

ADC with UART output

Read an ADC and output it over UART.

Configuration options: ADC: PB2, UART: PB0, PB1.

#include	"extern.h"

BYTE out;

FPPA_Duty		=>	_SYS(INC.FPPA_NUM);	// Single FPPA = 1, Mult FPPA = 2 or 4/8/...

Baud_Rate		=>	9600;
/*
UART_Delay		=>	(System_Clock / FPPA_Duty) / Baud_Rate;

	if	System_Clock	=	8,000.000 Hz
		FPPA_Duty		=	/16
	so	FPPA_Clock		=	System_Clock / FPPA_Duty	=	500.000	Hz

	if	Baud_Rate		=	19200
	so	UART_Delay		=	500.000 / 19200 = 26.0416...
	so	match, 26 cycles send one bit.	< 0.5%

	if	Baud_Rate		=	38400
	so	UART_Delay		=	500.000 / 38400 = 13.02083...
	so	match, 13 cycles send one bit.	< 0.5%

	if	Baud_Rate		=	56000
?	so	UART_Delay		=	500.000 / 56000 = 8.9285...		<	1.0%

	if	Baud_Rate		=	57600
X	so	UART_Delay		=	500.000 / 57600 = 8.6805...		:	fail
*/
UART_Delay		=>	( (System_Clock / FPPA_Duty) + (Baud_Rate/2) ) / Baud_Rate;
//	+ (Baud_Rate/2) : to round up or down

Test_V0			=>	System_Clock / 1000 * 995;
Test_V1			=>	UART_Delay * Baud_Rate * FPPA_Duty;
Test_V2			=>	System_Clock / 1000 * 1005;

#if	(Test_V1 < Test_V0) || (Test_V1 > Test_V2)
	.echo	%Test_V0 <= %Test_V1 <= %Test_V2
	.error	Baud_Rate do not match to System Clock
#endif

UART_Out		BIT	PB.0;
UART_In			BIT	PB.1;

static	void	UART_Send (void)
{
	BYTE	cnt;
	BYTE	UART_Data_Out;

	UART_Data_Out	=	out;

	//	Start Bit
	set0	UART_Out;				//	1

	#if	FPPA_Duty == 1
		cnt	=	8;						//	2 ~ 3
		.Delay	3;						//	4 ~ 6
		do
		{	//	Data Bit * 8
			.Delay	UART_Delay - 10;
			sr		UART_Data_Out;		//	7
			if (CF)
			{
				nop;					//	10
				UART_Out	=	1;		//	1
			}
			else
			{
				UART_Out	=	0;		//	1
				.delay	2;				//	2 ~ 3
			}
		} while (--cnt);				//	4 ~ 6
		.Delay	UART_Delay - 5;
	#else
		.Delay	UART_Delay - 4;
		cnt	=	8;						//	2 ~ 3

		//	Data Bit * 8
		do
		{
			sr		UART_Data_Out;		//	4		4
			swapc	UART_Out;			//			1
			.Delay	UART_Delay - 4;
		} while (--cnt);				//			2, 3

		.Delay	2;						//			3 ~ 4
	#endif

	//	Stop Bit
	set1	UART_Out;				//	1
	.Delay	2 * UART_Delay - 2;
}									//	2






void	FPPA0 (void)
{
	.ADJUST_IC	SYSCLK=IHRC/2;		//	SYSCLK=IHRC/2

	//	Insert Initial Code
	//  UART_HandShake();
	PBC = 0b_0000_0011; // Set PB0 ~ PB3 as Input
	PBPH = 0b_0000_0000; // Disable pull high register
	PBDIER = 0b_0000_0011; // Disable digital input for PB0 ~ PB3

	$ ADCC Enable,PB3; // Enable.
	$ ADCC Enable,PB2; // The $ indicates to the compiler that 'Enable' and 'PB2' are keywords and it should look in the inc file.


	$ ADCM /16; // For system clock 8Mhz as indicated in the datasheet, clock selection
	.Delay 8*400; // delay 400us, datasheet does not state why. Probably for the pins to be set up

	// .error Hey this is an error <- nice debugging tool
	// .echo Okay done here <- Another nice tool.


	while (1)
	{
		AD_START = 1; // This is a bit in the ADCC register, write 1 to start, read 1 to see if ADC is finished.
		while(!AD_DONE) NULL; // Because AD_Done is defined as it's own 'word', so we don't need to reference the ADCC register
		
 
		out = ADCR; // ADC conversion result, the accuracy of this device is 18 bits.
		
		UART_Send(); // send out the value. Note that this is essentially a uint8 value. 
		.Delay 1000000; // don't want to flood the buffer of the receiving device.
//		...
//		wdreset;

	}
}

Simple PWM

This PWM counts from 0 to 100 using the hardware timer.

Configuration Options: PA0 output


#include	"extern.h"



void	FPPA0 (void)
{
	.ADJUST_IC	SYSCLK=IHRC/16, IHRC = 16MHz, VDD=5V;		//	SYSCLK=IHRC/2

	Byte duty = 60;
	Byte _duty = 100-duty;
	Byte increment = 1;

	PWMG0DTL = 0x00; //PWM0 Duty Value Low Register * You have to write to the low register first [2:1]
	PWMG0DTH = _duty; //PWM0 Duty Value HIGH Register [10:3]
	PWMGCUBL = 0x00; //PWM0 Counter Upper Bound Low Register, Bits 2:1
	PWMGCUBH = 100; //PWM0 Count Upper Bound High Register, Bits 10:3

	$ PWMG0C Enable,Inverse,PA0; 
	$ PWMGCLK Enable,/1,SYSCLK;

	while (1)

	{ 
		PWMG0DTL = 0x00;
		PWMG0DTH = duty;
		
		if (duty==100)
		{
			duty=100;
			increment=-1;
		}
		else if(duty==0) // it cannot compare negative values.
		{
			duty=0;
			increment=1;
		}
		else
		{
			nop;
		}
		duty = duty + increment;
		.delay 10000;
		
	}
}

Interrupts

Timers