# How can anyone use a microcontroller which has only 384 bytes of program memory?

For instance a PIC10F200T

Virtually any code you write will be larger than that, unless it is a single purpose chip. Is there any way to load more program memory from external storage or something? I'm just curious, I don't see how this could be very useful... but it must be.

• There are plenty of applications for tiny microcontrollers, from special-purpose signal generators, to protocol converters, to "nodes" in a larger control system, etc., etc., – Dave Tweed May 1 '13 at 15:17
• Well a chess playing program takes 672 bytes so that's no good. en.wikipedia.org/wiki/1K_ZX_Chess – John Burton May 1 '13 at 16:41
• Here are some examples of what can be done with tiny programs (less than 256 bytes). – hammar May 1 '13 at 19:37
• What do you mean, "unless its a single purpose chip"? The majority of embedded systems are single purpose. – Jeanne Pindar May 1 '13 at 20:25
• Back in college, I built a fully functional traffic light program for a 8085/8155 computer (max 256 bytes) I assembled. It had walk buttons, and some sensors that would simulate the presence of a vehicle. – Zoredache May 2 '13 at 6:07

You kids, get off my lawn!

384 bytes is plenty of space to create something quite complex in assembler.

If you dig back through history to when computers were the size of a room, you'll find some truly amazing feats of artistry executed in <1k.

For instance, read the classic Story of Mel - A Real Programmer. Admittedly, those guys had 4096 words of memory to play with, the decadent infidels.

Also look at some of the old demoscene competitions where the challenge was to fit an "intro" into the bootblock of a floppy, typical targets being 4k or 40k and usually managing to include music and animation.

Edit to add: Turns out you can implement the world's first $100 scientific calculator in 320 words. Edit for the young 'uns: • Floppy = floppy disk. • Bootblock = 1st sector of the floppy read at bootup. • Demoscene = programming competitions amongst hacker groups. • Assembler = fancy way of programming a device if you're too soft to use 8 toggle switches and a "store" button. • The Atari 2600 game console had only 4KB of storage in the ROM game cartridges (though some games got around this limitation by using bank switching to access more than 4K). – Johnny May 1 '13 at 21:21 • Eons ago I made a pretty realistic bird-chirp (enough that people looked for the bird rather than suspecting the computer), the guts of which (but not the randomizing code that kept it from making exactly the same sound each time) would have rattled around in 384 bytes and I had the additional restrictions of no writable addresses and a zero byte wasn't permitted in the binary. – Loren Pechtel May 2 '13 at 2:19 • I need to get out more, remembered this from back in the day - screen saver in 368 bytes: aminet.net/package/util/blank/368blanker – John U May 2 '13 at 14:14 • +1 for "The Story of Mel". One of the greatest things I've read all week. – Justin ᚅᚔᚈᚄᚒᚔ May 2 '13 at 14:53 • @JohnU: The first few games on the Atari 2600 were all 2K. Many developers never designed any games that went beyond 4K, because even though 8K chips were affordable (and some companies' carts simply used half of a 4K chip) adding bank-switching to a card using a standard (active-low chip-select) chip increased the number of support chips from one to three. – supercat May 2 '13 at 16:45 Microcontrollers are sufficiently cheap that they are often used to do really simple things that in years past would more likely have been done with discrete logic. Really simple things. For example, one might want a device to turn on an output for one second every five seconds, more precisely than a 555 timer would be able to do.  movwf OSCCON mainLp: ; Set output low clrf GPIO movlw 0xFE movwf TRIS clrwdt call Wait1Sec clrwdt call Wait1Sec clrwdt call Wait1Sec clrwdt call Wait1Sec ; Set output high bsf GPIO,0 clrwdt call Wait1Sec goto mainLp Wait1Sec: movlw 6 movwf count2 movlw 23 movwf count1 movlw 17 movwf count0 waitLp: decfsz count0 goto waitLp decfsz count1 goto waitLp decfsz count2 goto waitLp retlw 0  That would be a real, usable, application, in less than 32 words (48 bytes) of code space. One could easily add a few options to have some I/O pins control timing options and still have lots of room to spare, but even if all the chip did was precisely what's shown above it might still be cheaper and easier than any alternative using discrete logic. BTW, the clrwdt instructions could be moved into the subroutine, but doing so would make things less robust. As written, even if a glitch causes the return-address stack to get corrupted, the watchdog won't get fed until execution returns to the main loop. If that never happens, the watchdog will reset the chip after a couple seconds. • Honestly, you could optimise your code a bit, setting a bad example to the children - 5 separate calls to wait1sec??? Wastrel! ;) – John U May 1 '13 at 16:38 • @JohnU: FYI, the code uses separate calls because if it used a count-to-zero counter and the count got glitched, the loop might run 255 times rather than four, while feeding the watchdog once per second. While it would be possible to guard against that by checking on each loop whether the count was in range, the code to do that ends up being more complicated than five calls and five clrwdt instructions. This isn't the most absolutely-failsafe counter arrangement possible, but some consideration is given to safety issues (e.g. the avoidance of clrwdt within a subroutine). – supercat May 1 '13 at 18:52 • @coder543: In the absence of things like power-supply noise, not very. On the other hand, in parts without a brown-out detector, it's possible for all sorts of crazy things to happen if VDD falls to a level between the minimum operating voltage and ground, and then rises back to normal. One should generally try to ensure that any state in which the device may finds itself will revert to normal in a reasonable period of time. The two seconds or so for the watchdog to kick in may be unavoidable, but four minutes for a glitched counter to reach zero might be a bit much. – supercat May 1 '13 at 19:38 • @coder543, they happen more often at an important demo than you want to believe. This kind of thinking is also required when building deeply embedded things that have no means to call for help or report an error. Or are inaccessible (think deep sea or outer space) even if an error did get noticed. – RBerteig May 1 '13 at 21:11 • @JohnU: I did notice it, but figured that explaining why I wrote the code as I did might be helpful. Incidentally, I was also trying to show that small tasks can fit in a small processor even if they're not optimized absolutely perfectly. – supercat May 2 '13 at 16:39 "ONLY" 384 bytes? Way back in the day, I had the job of writing an entire operating system (by myself) for a specialized computer that served the ship, pipeline, and refinery management industry. The company's first such product was 6800 based and was being upgraded to 6809, and they wanted a new OS to go along with the 6809 so they could eliminate the license costs of the original operating system. They also were bumping up the boot rom's size to 64 bytes, up from 32. If I recall correctly - it WAS about 33 years ago! - I convinced the engineers to give me 128 bytes so I could put the whole operating system's device drivers on the rom and thus make the whole device more reliable and versatile. This included: • Keyboard driver with key debounce • Video driver • Disk drive driver and rudimentary file system (Motorola "abloader format", IIRC), with built-in ability to treat "banked" memory as if it were really-fast disk-space. • Modem Driver (they got the FSK backwards, so these modems only talked with each other) Yes, all of these were as bare-bones as it gets, and hand-optimized to remove every extraneous cycle, but perfectly serviceable and reliable. Yes, I shoehorned all of that into the available bytes - oh, it ALSO set up interrupt handling, the various stacks, and initialized the real-time / multi-tasking operating system, prompted the user on boot options, and booted the system. A friend of mine who's still affiliated with the company (its successor) told me a few years ago that my code is still in service! You can do a LOT with 384 bytes... • you say boot rom, and you mention moving drivers onto the boot rom... this indicates to me that there was a secondary storage medium available. In this discussion, we've already determined that you cannot load code from external storage on this PIC. – coder543 May 2 '13 at 21:00 • @coder543 That misses the point: 384 bytes is enough to do quite a lot! The original question read like a complaint that 384 wasn't enough to do anything useful - it was more than I needed - a LOT more - to provide all the fundamental components of a real-time, multi-tasking operating system... – Richard T May 2 '13 at 21:09 You can use this for very small applications (e.g. delayed PSU start, 555 timer replacement, triac-based control, LED blinking etc...) with smaller footprint than you'd need with logic gates or a 555 timer. • I just noticed that those first two examples were from Stack itself! well played. – coder543 May 1 '13 at 16:56 • +1 for mentioning footprint. Sometimes size is everything. – embedded.kyle May 1 '13 at 16:56 I designed a humidity sensor for plants that tracks the amount of water the plant has and blinks an LED if the plant needs water. You can make the sensor learn the type of plant and thus change its settings while running. It detects low voltage on the battery. I ran out of flash and ram but was able to write everything in C code to make this product work flawlessly. I used the pic10f that you mention. Here is the code I made for my Plant Water Sensor. I used the pic10f220 since it has an ADC module, it has the same memory as the pic10f200, I'll try to find the Schematic tomorrow. The code is in spanish, but its very simple and should be easily understood. When the Pic10F wakes from sleep mode, it will reset so you have to check if it was a PowerUp or a reset and act accordingly. The plant setting is kept in ram since it never really powers down. MAIN.C /* Author: woziX (AML) Feel free to use the code as you wish. */ #include "main.h" void main(void) { unsigned char Humedad_Ref; unsigned char Ciclos; unsigned char Bateria_Baja; unsigned char Humedad_Ref_Bkp; OSCCAL &= 0xfe; //Solo borramos el primer bit WDT_POST64(); //1s ADCON0 = 0b01000000; LEDOFF(); TRIS_LEDOFF(); for(;;) { //Se checa si es la primera vez que arranca if(FIRST_RUN()) { Ciclos = 0; Humedad_Ref = 0; Bateria_Baja = 0; } //Checamos el nivel de la bateria cuando arranca por primera vez y cada 255 ciclos. if(Ciclos == 0) { if(Bateria_Baja) { Bateria_Baja--; Blink(2); WDT_POST128(); SLEEP(); } if(BateriaBaja()) { Bateria_Baja = 100; //Vamos a parpadear doble por 100 ciclos de 2 segundos SLEEP(); } Ciclos = 255; } //Checamos si el boton esta picado if(Boton_Picado) { WDT_POST128(); CLRWDT(); TRIS_LEDON(); LEDON(); __delay_ms(1000); TRIS_ADOFF(); Humedad_Ref = Humedad(); Humedad_Ref_Bkp = Humedad_Ref; } //Checamos si esta calibrado. Esta calibrado si Humedad_Ref es mayor a cero if( (!Humedad_Ref) || (Humedad_Ref != Humedad_Ref_Bkp) ) { //No esta calibrado, hacer blink y dormir Blink(3); SLEEP(); } //Checamos que Humedad_Ref sea mayor o igual a 4 antes de restarle if(Humedad_Ref <= (255 - Offset_Muy_Seca)) { if(Humedad() > (Humedad_Ref + Offset_Muy_Seca)) //planta casi seca { Blink(1); WDT_POST32(); SLEEP(); } } if(Humedad() >= (Humedad_Ref)) //planta seca { Blink(1); WDT_POST64(); SLEEP(); } if(Humedad_Ref >= Offset_Casi_Seca ) { //Si Humedad_Ref es menor a Humedad, entonces la tierra esta seca. if(Humedad() > (Humedad_Ref - Offset_Casi_Seca)) //Planta muy seca { Blink(1); WDT_POST128(); SLEEP(); } } SLEEP(); } } unsigned char Humedad (void) { LEDOFF(); TRIS_ADON(); ADON(); ADCON0_CH0_ADON(); __delay_us(12); GO_nDONE = 1; while(GO_nDONE); TRIS_ADOFF(); ADCON0_CH0_ADOFF(); return ADRES; } //Regresa 1 si la bateria esta baja (fijado por el define LOWBAT) //Regresa 0 si la bateria no esta baja unsigned char BateriaBaja (void) { LEDON(); TRIS_ADLEDON(); ADON(); ADCON0_ABSREF_ADON(); __delay_us(150); //Delay largo para que se baje el voltaje de la bateria GO_nDONE = 1; while(GO_nDONE); TRIS_ADOFF(); LEDOFF(); ADCON0_ABSREF_ADOFF(); return (ADRES > LOWBAT ? 1 : 0); } void Blink(unsigned char veces) { while(veces) { veces--; WDT_POST64(); TRIS_LEDON(); CLRWDT(); LEDON(); __delay_ms(18); LEDOFF(); TRIS_ADOFF(); if(veces)__delay_ms(320); } }  MAIN.H /* Author: woziX (AML) Feel free to use the code as you wish. */ #ifndef MAIN_H #define MAIN_H #include <htc.h> #include <pic.h> __CONFIG (MCPU_OFF & WDTE_ON & CP_OFF & MCLRE_OFF & IOSCFS_4MHZ ); #define _XTAL_FREQ 4000000 #define TRIS_ADON() TRIS = 0b1101 #define TRIS_ADOFF() TRIS = 0b1111 #define TRIS_LEDON() TRIS = 0b1011 #define TRIS_LEDOFF() TRIS = 0b1111 #define TRIS_ADLEDON() TRIS = 0b1001 #define ADCON0_CH0_ADON() ADCON0 = 0b01000001; // Canal 0 sin ADON #define ADCON0_CH0_ADOFF() ADCON0 = 0b01000000; // Canal 0 con adON #define ADCON0_ABSREF_ADOFF() ADCON0 = 0b01001100; //Referencia interna absoluta sin ADON #define ADCON0_ABSREF_ADON() ADCON0 = 0b01001101; //referencia interna absoluta con ADON //Llamar a WDT_POST() tambien cambia las otras configuracion de OPTION #define WDT_POST1() OPTION = 0b11001000 #define WDT_POST2() OPTION = 0b11001001 #define WDT_POST4() OPTION = 0b11001010 #define WDT_POST8() OPTION = 0b11001011 #define WDT_POST16() OPTION = 0b11001100 #define WDT_POST32() OPTION = 0b11001101 #define WDT_POST64() OPTION = 0b11001110 #define WDT_POST128() OPTION = 0b11001111 #define Boton_Picado !GP3 #define FIRST_RUN() (STATUS & 0x10) //Solo tomamos el bit TO //Offsets #define Offset_Casi_Seca 5 #define Offset_Muy_Seca 5 //Low Bat Threshold #define LOWBAT 73 /* Los siguientes valores son aproximados LOWBAT VDD 50 3.07 51 3.01 52 2.95 53 2.90 54 2.84 55 2.79 56 2.74 57 2.69 58 2.65 59 2.60 60 2.56 61 2.52 62 2.48 63 2.44 64 2.40 65 2.36 66 2.33 67 2.29 68 2.26 69 2.23 70 2.19 71 2.16 72 2.13 73 2.10 74 2.08 75 2.05 76 2.02 77 1.99 78 1.97 */ #define LEDON() GP2 = 0; //GPIO = GPIO & 0b1011 #define LEDOFF() GP2 = 1; //GPIO = GPIO | 0b0100 #define ADON() GP1 = 0; //GPIO = GPIO & 0b1101 #define ADOFF() GP1 = 1; //GPIO = GPIO | 0b0010 unsigned char Humedad (void); unsigned char BateriaBaja (void); void Delay_Parpadeo(void); void Blink(unsigned char veces); #endif  Let me know if you have questions, I'll try to answer based on what I remember. I coded this several years ago so don't check my coding skills, they have improved :). Final Note. I used the Hi-Tech C compiler. • I'd actually be really interesting in reading how you did this. Did you take any notes at all while you were doing it that you wouldn't mind sharing on the webs? – RhysW May 2 '13 at 13:14 • Hello RhysW, I believe I still have the code. It was really simple actually. I could send you my code if you are interested. Let me know. The circuit I designed is very simple and cool, only 3 resistors, one p-channel mosfet (for reverse battery protection) a 100nF cap and an LED. I use and internal diode in the pic10f to use as a reference for battery measurement and to keep the ADC readings constant. – scrafy May 2 '13 at 16:36 • That sounds like a neat project. Is there any chance you could post the details here (or at least post them somewhere and link to them)? – Ilmari Karonen May 3 '13 at 2:17 • Hello scrafy! Please, if you have something to add to an answer, use the "edit" link instead of posting a new answer, since this site uses voting and doesn't work like a forum. – clabacchio May 3 '13 at 8:11 One thing that I haven't seen mentioned: The microcontroller you mentioned is only$0.34 each in quantities of 100. So for cheap, mass-produced products, it can make sense to go to the extra coding trouble imposed by such a limited unit. The same might apply to size or power consumption.

• That was exactly my first thought. Also: If I would be a startup with a neat idea, but only few hundred bucks loose, stuff like this can mean the difference between get-back-to-day-job and quit-day-job. – phresnel Jun 24 '15 at 8:44

When I was in high school, I had a teacher that insisted that light dimming was too difficult a task for a student such as I to tackle.

Thus challenged I spent quite a bit of time learning and understanding phase based light dimming using triacs, and programming the 16C84 from microchip to perform this feat. I ended up with this assembly code:

'Timing info:
'There are 120 half-cycles in a 60Hz AC waveform
'We want to be able to trigger a triac at any of 256
'points inside each half-cycle.  So:
'1 Half cycle takes 8 1/3 mS
'1/256 of one half cycle takes about 32.6uS
'The Pause function here waits (34 * 0xD)uS, plus 3uS overhead
'This was originally assembled using Parallax's "8051 style"
'assembler, and was not optimized any further.  I suppose
'it could be modified to be closer to 32 or 33uS, but it is
'sufficient for my testing purposes.

list 16c84

movlw   0xFD     '11111101
tris    0x5      'Port A
movlw   0xFF     '11111111
tris    0x6      'Port B
WaitLow:             'Wait for zero-crossing start
btfss   0x5,0x0  'Port A, Bit 1
goto    WaitLow  'If high, goto WaitLow
WaitHigh:            'Wait for end of Zero Crossing
btfsc   0x5,0x0  'Port A, Bit 1
goto    WaitHigh 'If low, goto waitHigh
call    Pause    'Wait for 0xD * 34 + 3 uS
bcf     0x5,0x1  'Put Low on port A, Bit 1
movlw   0x3      'Put 3 into W
movwf   0xD      'Put W into 0xD
call    Pause    'Call Pause, 105 uS
bsf     0x5,0x1  'Put High on Port A, Bit 1
decf    0xE      'Decrement E
movf    0x6,W    'Copy Port B to W
movwf   0xD      'Copy W to 0xD
goto    Start    'Wait for zero Crossing
Pause:               'This pauses for 0xD * 34 + 3 Micro Seconds
'Our goal is approx. 32 uS per 0xD
'But this is close enough for testing
movlw   0xA      'Move 10 to W
movwf   0xC      'Move W to 0xC
Label1:
decfsz  0xC      'Decrement C
goto    Label1   'If C is not zero, goto Label1
decfsz  0xD      'Decrement D
goto    Pause    'If D is not zero, goto Pause
return           'Return


Of course you'd need to modify this for the chip you mention, and maybe add a cheap serial routine for input since your chip doesn't have an 8 bit wide port to listen to, but the idea is that a seemingly complex job can be done in very little code - you can fit ten copies of the above program into the 10F200.

You can find more project information on my Light Dimming page. Incidentally I never did show this to my teacher, but did end up doing a number of lighting rigs for my DJ friend.

Well, years ago I wrote a temperature controller with serial I/O (bit-banging the serial I/O because the MCU didn't have a UART) and a simple command interpreter to talk to the controller. MCU was a Motorola (now Freescale) MC68HC705K1 which had a whopping 504 bytes of program memory (OTPROM) and about 32 bytes of RAM. Not as little as the PIC you reference, but I remember having some ROM left over. I still have a few assembled units left, 17 years later; wanna buy one?

So yeah, it can be done, at least in assembly.

In any case, I've written very simple C programs recently that would probably have fit inside 384 bytes when optimized. Not everything requires large, complex software.

You can write a blink a LED with 384 bytes program memory, and even more.

As far as I know, it is not possible to extend the program memory with an external chip (unless you're building a full ASM interpreter in the 384 bytes, which would be slow). It is possible to extend data memory with an external chip (EEPROM, SRAM) though.

• It wouldn't be hard to build a Turing machine simulator in 384 bytes... – Chris Stratton May 1 '13 at 15:23
• @ChrisStratton I meant a full interpreter, so that the 'extended program memory' would've the same features as normal. – Keelan May 1 '13 at 15:24
• Yes, that's what I suggested a means of tightly implementing. The rest is just compiler design... – Chris Stratton May 1 '13 at 15:25
• If one wanted program logic to be stored in an external EEPROM, trying to emulate the PIC instruction set would not be the way to go. A better approach would be to design an instruction set which was optimized for use with the virtual machine; indeed, that is the approach that Parallax took with their "Basic STAMP" in the 1990's. It was a PIC with 3072 bytes of code space, paired with a serial EEPROM chip. – supercat May 1 '13 at 15:39
• BTW, an additional note about the BASIC stamp: it was introduced at a time when flashed-based or EEPROM-based microcontrollers were comparatively rare, but serial EEPROM chips were fairly cheap. For applications that didn't need much speed, a fixed-code micro with a serial EEPROM part would be cheaper than a comparably-sized EEPROM or flash-based micro. The design of the BASIC Stamp wouldn't make sense today, but it was quite practical when it was introduced. – supercat May 2 '13 at 16:43

It's actually worse than you think. Your linked Mouser page is confusing matters when it specifies this processor as having 384 bytes of program memory. The PIC10F200 actually has 256 12-bit words of program memory.

So, what can you do with that? The 12-bit PIC instruction set used by the PIC10F20x devices are all single-word instructions, so after you subtract a few instructions for processor setup, you're left with enough space for a program of about 250 steps. That's enough for a lot of applications. I could probably write a washing machine controller in that kind of space, for example.

I just looked over the available PIC C compilers, and it looks like about half of them won't even try to emit code for a PIC10F200. Those that do probably put out so much boilerplate code that you might only be able to write an LED flasher in the space left. You really want to use assembly language with such a processor.

• You are right about the 256 instruction words. Actually one of them is taken up with the oscillator calibration constant, so you get 255 usable instructions. Also, the 10F200 doesn't use the usual PIC 16 14-bit instruction set. It uses the PIC 12 12-bit instruction set. However, I agree with your basic premises. I've done lots of useful things with a PIC 10F200. +1 – Olin Lathrop Jan 4 '15 at 16:35
• @OlinLathrop: I've clarified the answer. I got the PIC16 term from page 51 of the datashseet, but I've decided it's clearer to just refer to "the 12-bit instruction set." The part prefix isn't a reliable guide to the instruction set used. – Warren Young Jan 4 '15 at 17:51

waving my cane in my day, we had to etch our own bits out of sand!

In 1976 (or thereabouts) the Atari 2600 VCS system was one of the most popular "video game platforms" of the time. In it, the microprocessor (MOSTEK 6507) ran at a blazing ~1 MHz and had ****128 bytes of RAM**.

A second example that I recall of a microcontroller with extremely limited RAM (~128 bytes) was a PIC12F used on a DC-DC converter. This micro also had to use assembly language in order to run at all.

• The OP isn't talking about RAM, he's talking about program space. The program space in the Atari 2600 is in the cartridge, not in the RIOT chip. The 2600 supported program ROMs up to 4 kiB without bank switching. (And some commercial cartridges did do bank switching!) As for your PIC12F example, the OP's got you beat: the PIC10F20x series devices have either 16 or 24 bytes of SRAM. – Warren Young Jan 4 '15 at 16:04