Arduino millis()

Arduino millis() is a function that returns the current time (it does not take any arguments). It is important to know how the time value is returned:

It returns time as an unsigned long - why this fact is important will become apparent as you read this page.

The millis() function is driven by a millisecond timed interrupt that increments an unsigned long every time it activates. The millis() function just returns the value of that variable.

Arduino millis vs delay

Lets just say at the start of this discussion - "Don't Use delay()". This will save you time. Read on to find out why...or have a look here (a different page on delay() - examples and explanation of why you should not use delay() are in that page.

Arduino milis() is an interrupt driven function meaning that it is always operating in the background while your code is working. Interrupts are used to update the value that millis() outputs so that after every millisecond that value will increase by one.You can access the current value of the millis() at any time simply by calling the function millis() and placing the result into your own unsigned long variable (or you can compare the millis() output to another constant).

The function delay() on the other hand is not interrupt driven but just causes a "Do Nothing" loop to execute wasting processor time. The function delay() depends on the interrupt driven output from Timer0 so delay() can not be used within an interupt service routine as while an ISR is executed interrupts are turned off.

Since it is not interrupt driven no other code can operate - it just effectively stops the processor from doing anything else while it counts down a delay time.

Note: The above discussion is talking about main code operation - interrupts still interrupt main code and operate e.g. timers or I2C interfaces etc. still work.
Note: millis() does not always increment by one due to the way the timing is calculated within the interrupt routine. The link takes you to a detailed simulation of millis() in this page.

How to use Arduino millis as delay

The function millis() returns the time from when the processor was last reset (or powered up).

Instead of stopping in a delay() function you can use millis() by testing its value within a loop. To do that you store the current time in a variable and if the output from millis() is bigger than the stored value (bigger by the delay time that you want to wait for) then you take action. See the " Arduino millis LED Flashing" section.

Arduino millis LED Flashing

Or how to use millis() to give a non blocking delay.

The following code shows using arduino millis() to create a non-blocking delay i.e.other code can operate while the delay timer is working. In this case,millis() is compared to a stored time value held in an unsigned long (which must be initialised at the start).

The value of oldtime is set to a value of millis() at the start. if the value of millis(), which is increasing every millisecond, is greater than 500 counts above oldtime then the conditional expression in the if statement becomes true. This means 500 milliseconds have past since the value of oldtime was set i.e. a delay of 500ms.

Within the body of the if-statement, below,the LED is toggled from its previous state. This means the LED is flashing at a rate of 1Hz or once a second.

TIP: it is vital that you set oldtime within the if-statement to the current millis() value so that the next delay period can be performed otherwise the if-statement expression would always be true after the first time the expression became true.

Since millis() is only tested by the conditional if statement, you can add other code within the loop to do other useful work i.e. using Arduino millis() in this way does not stop the processor.

#define LED 13

void loop(void){
static uint8_t tog=0;
static uint32_t oldtime=millis();

    if ( (millis()-oldtime) > 500) {
       oldtime = millis();

       tog = ~tog; // Invert
       if (tog) digitalWrite(LED,HIGH); else digitalWrite(LED,LOW);

Note: The type uint32_t is the same type as "unsigned long". uint32_t is used in embedded programming as it directly specifies the number of bits within the type, whereas "unsigned long" may have a different number of bits for different compilers.

Arduino millis Timer : Non blocking delay

In fact you can use the same code above to easily make a timer using arduino millis that can be used to time any event from 1ms up to 49.7 days. The 49.7 days limit starts from when the Arduino is reset or powered up.

The following code has a delay of 500ms

    if ( (millis()-oldtime) > 500) {
       oldtime = millis();

      // Do something every after 500ms


For an action every 12 hours you would use a delay interval of (12 * 60 * 60 * 1000) = 43200000ms replacing 500 with 43200000L (The L indicates a long value to the comiler). For days you need more milliseconds e.g. 3 days would require (3 * 24 * 60 * 60 * 1000) =259200000 again replacing 500 with 259200000L. To stop the repetition of the action see here.

Arduino millis limit

For the Arduino the max value of millis is :

4,294,967,295 or (0xffffffff)

This is because the Arduino millis data type is :

unsigned long (which can also be written as uint32_t which you can more easily see the number of bits in th type.

The count that an unsigned long is capable of holding is: pow(2,32)-1 or 4,294,967,295 or 4 billion 294 million 967 thousand and 295. So if every count is worth a millisecond then how many days is that?

First divide by 1000 for the seconds, then by 60 for the minutes then by 60 for the hours then by 24 for the days = ~49.71 days.

After aprroximately 50 days (or a bit more than 49.71 days) the timer wraps round to zero and this is the Arduino millis overflow problem.

Note: Arduinos usually have a resonator (3 pin) instead of a crystal (2 pin) and these are not as accurate as crystals.

If you are designing a project that must time for more than 49 days then this could cause a problem because at the wrap around point, time that was increasing incrementally in milliseconds, suddenly goes to zero. If you recorded a time stamp for a data logging device using millis after 49.71 days the time stamp would return to the start time i.e. wrong.

Another problem is using the timer for delays e.g. to flash an LED, where you wait for the timer to reach the current time plus 500ms say, if the timer wrapped within that 500ms then the time to wait would instead be 50 days which is a bit of a long time to flash an LED off!

TIP: If you want to go beyond 50 days add another variable that acts as extra bits in front of the unsigned long e.g. an unsigned char would extend time by 256 * 50 days. Increment it every time the millis() time wraps around. The downside is that you would need to include that 8 bit quantity in all time calculations so you would need to make your own "greater than" operation etc.

How to detect the arduino millis overflow

One easy way to detect the overflow is to do what you should not do and use a signed long (BUT ONLY FOR THE DECTION OF OVERFLOW). See here for why you Must Not Use Long (a signed integer) for counting time when using millis().

When the overflow occurs the hex value of a 32 bit integer goes from 0xFFFFFFFF to 0. For a signed long the left most bit is the sign bit. Therefore at the overflow the value (interpreted as a signed long will go from negative to positive).

That is the signed representation of 0xFFFFFFFF is -1 ~ negative, and the signed interpretation of 0 is positive (or at least you can use the less than operator to detect negative values).

Note: The following code has not been tested.
uint32_t time;
int32_t detect;
int8_t halfway;

void setup(void) {

void loop(void) {

    detect= (long)time;

    if (detect<0) halfway = 1; // this is the millis value 0x80000000
    if (halfway && detect>0){

    if (overflow) {
       // Do something about the overflow
       overflow = 0;

Time Conversions for Arduino millis

Arduino millis to Seconds

Since millis is a shortened engineering term for milliseconds and milli stands for 1/1000th there are 1000 milliseconds in one second. Therefore to count seconds divide millis by 1000.

Seconds = millis()/1000;

Often you will want to work with millis() in fractions of a second e.g. for flashing an LED every 400ms you would compare millis() to 200

Arduino millis to Hours

If you want to get to hours when using Arduino millis as a Timer you need to do some more division:

Minutes = ( millis()/1000 ) / 60;

Hours = ( ( millis()/1000 ) / 60 ) / 60;

Arduino millis to Hours Minutes Seconds and Days

The full set of time values is here:

Days = Hours/24;

Hours = Minutes / 60;

Minutes = Seconds / 60;

Seconds = millis()/1000;

As noted previously days can only have a maximum value of 50.

Note: Since Hours. Minutes and Seconds and Days can only have a maximum value below 255 you can use type uint8_t for each of the above quantities. uint8_t is the same as "unsigned char".

Arduino millis not accurate

No it is not! Well, it does quite a good job.

There are two reasons :

  1. The clock source - This is true of all crystal oscillator based systems but some Arduino boards use resonator that performs worse than a crystal.
  2. The implementation - In general standard crystal oscilaltor frequences that are easy for humans to read e.g.16MHz are not divisible by a binary number so you can't get an exact output period. Arduino code adjusts for this problem using a correction algorithm..

Reason One - The Clock Source

Arduino millis is not accurate and one reason for this is that most Arduino boards come with a 16MHz resonator fitted. This is a cheap oscillation component that is OK for most applications. it will drift around with temperature but will always be approximately 16MHz.

If you are looking for accuracy, you will need an Arduino with a crystal on board (or make up your own bare-bones version with a crystal). You can tell if yours only has a resonator as resonators are three pin devices whereas crystals are two pin devices.

The problem is that to retro fit a crystal (it can be done!) to an Arduino board requires two extra components to make the crystal oscillate correctly (capacitive loads are required). These are two capacitors, each connected to one pin of the crystal and then to ground. They range from 12pF to 33pf - check for the correct capacitor specified by the manufacturer - or just use a couple of 15pF. You of course also need to attach the crystal to the clock inputs of the microcontroller (same connections as the resonator - but not the middle ground one).

A better solution is to use an external RTC

The accuracy of the crystal is specified as ppm and is usually around 100ppm (a 32kHz watch crystal can get to 20ppm - not much use for a microcontroller unless you need low power operation).

If you really want an accurate timestamp then use an ovenised timekeeping chip (a cheap 2ppm one is the DS3232). This does not output a clock for direct use by the microcontroller so you could keep the resonator and use the DS3232 as the accurate timestamp device.

Reason Two - Timer 0 implementation

This second reason is not really that Arduino millis is not accurate (it becomes accurate) but more about the implementation of the timer which is clever but will cause a small jitter. The interrupt for the millisecond timer (using Timer 0 in wiring.c) uses prescalers to divide down the main clock 16MHz but the output of the timer is off by a small amount. When the error gets bug enough a correction factor is used to adjust the timer value.

So in the long term the millis() timer is accurate but in the short term it could be out by a small amount.

Timer 0 is setup so that it has a prescaler of 64. It is an 8 bit timer so overflows every 256 counts. Therefore for a 16MHz clock, the repeat time for timer 0 is (1.0/16e6)*256*64 = 0.001024 of 1024 us which is close to 1ms but not actually there.

How Timer 0 is corrected for 1ms operation

There are two variables used in the correction and a few macro definitions:

clockCyclesPerMicrosecond gives a result of 16 for a 16MHz clock.

Two other macros are:

clockCyclesToMicroseconds(a) = ( (a) / clockCyclesPerMicrosecond )

microsecondsToClockCycles(a) = ( (a) * clockCyclesPerMicrosecond )

Within wiring.c the following definitions are made:

MICROSECONDS_PER_TIMER0_OVERFLOW = clockCyclesToMicroseconds( 64 * 256) = 16384 / 16 = 1024

Which just shows it is a long winded way of doing a simple calculation that does have the advantage of being generic so it means for different clocks a different result will occur. However the code comments state that the fractional timings calculation (below) is only accurate for 8Mhz and 16MHz which, if you think about it; For 8 and 16MHz the numbers, they divide out exactly with no remainder. So the question is - is doing it in a generic way any use at all? Charles Moore would say don't add in code that may or may not be used in the future - strip it all out so that it is simple to read and understand and only add it in if it is actually used!



The above when used later inserts 1.024 into the code at the point of use which is in fact when an unsigned long is incremented so that value would be incremented by one.

The more interesting definitions are the parts that deal with fractional increments

FRACT_INC = ( MICROSECONDS_PER_TIMER0_OVERFLOW % 1000 >> 3 ) = 24 >> 3 = 3 (16MHz)

FRACT_MAX = ( 1000 >>3 ) = 125

The reason stated for doing the above right shifts is so that the numbers fit into a byte.

Three variables are used in the correction and output of the millis value (timer0_millis - below).

unsigned long timer0_overflow_count - only used in microseconds calculation.

unsigned long timer0_millis

Byte timer0_fract

Every time in the interrupt:

timer0_millis is increased by MILLIS_INC (or by 1)

timer0_fract is increased by FRACT_INC (or by 3)

timer0_overflow_count is increased by one - this is the unadjusted Timer0 interrupt count.

Fractional adjustment in the interrupt vector:

// copy these to local variables so they can be stored in registers
// (volatile variables must be read from memory on every access)
unsigned long m = timer0_millis;
unsigned char f = timer0_fract;

if (f >= FRACT_MAX) {
m += 1;

In the above code, m and f are defined as local variables to allow the compiler to use register which are faster (check this actually happens by examining output assembler code).

The fractional action is that if timer0_fract is greater or equal than FRACT_MAX (125) then subtract FRACT_MAX and increase timer0_millis by one (>= is used since 125 is not a multiple of 3).

So every time that the interrupt fires 3 is added until the accumulated error (timer0_fract) is greater or equal to 125 which will be when time0_fract 1st reaches 126 or 42 interrupt calls. Interrupts are called with a time period of 1024 microseconds therefore the time before a millisecond correction is made is 42 *1024 = 43008us.

Therefore you will get a correction of the Arduino millisecond timer every ~43ms.

Since the timer0 interrupt is running slow the millisecond timer is incremented by 1 every ~43ms

The first time round the loop after correction 1 is left in timer0_fract.

The second time 2 is left in timer0_fract.

so small fractional errors eventually get corrected after 3 times ~43ms.

TCL simulation

The following TCL program simulates the action of the interrupt code for Timer 0:

proc sim_arduino_timer0 {} {

console show
set c 0

 set timer0_millis 0
 set timer0_frac 0
 set FRACT_INC 3
 set FRACT_MAX 125
 set m 0
 set f 0

 set line ""

 for {set i 0} {$i<1100} {incr i} {

#   stprint   "$m $f\n"
    puts "$m $f"

    set m [expr $m + $MILLIS_INC]
    set f [expr $f + $FRACT_INC]
       if {$f >= $FRACT_MAX} {
          set f [expr $f - $FRACT_MAX]
          incr m

  update idletasks



Some of the output it gererates is shown below (left is the millis increment and the right is the fractional part).:

0 0
1 3
2 6
3 9
4 12
5 15
6 18
7 21
8 24
9 27
10 30
11 33
12 36
13 39
14 42
15 45
16 48
17 51
18 54
19 57
20 60
21 63
22 66
23 69
24 72
25 75
26 78
27 81
28 84
29 87
30 90
31 93
32 96
33 99
34 102
35 105
36 108
37 111
38 114
39 117
40 120
41 123
43 1
44 4
45 7 

The first adjustment is when the millis output is at 42 and the fractional part has just passed 125. Instead of the expected output of 42 an adjusted output of 43 is output instead i.e the correction algorithm jumps past 42 and so millis() does not output the value of 42 at all. At about every 42 ms this correction is made again.

Program showing millis and micros output

The next program shows the actual output from an Arduino uno with a resonator as the clock.

void setup() {

void loop() {
static unsigned int c=0,stop=0;
unsigned long mil,mic;

  // Note Reduce this parameter the more is done in the loop! to get 3~4 outputs
// delayMicroseconds(50);
  if (c<2100 && stop==0) {

      mil = millis(); // Capture as close together as possible
      mic = micros();
     Serial.print( mil );
     Serial.print(" ");
     Serial.println( mic );

  } else stop =1;

The relevant section is here (the first adjustment at millis()==42 ):

40 41164
40 41604
41 42040
41 42480
41 42920 *** millis skips a beat here
43 43368 ***
43 43808
44 44244
44 44684
45 45132
45 45572
45 46016
46 46460
46 46892
47 47340

The above program takes samples of the current millis() and micros() timer outputs, and outputs more than one result since the interrupt timer is asynchronous to the main loop code i.e. you don't know when it is going to change so to see changes you have to make the loop run faster than the millisecond timer (hence the high baud rate and higher sample number 2100). You can see here millis on the left and micros on the right - micros is 4us inaccurate so is better than millis(). Observe the transition point at 42 ( where 42 is not output by millis() ) i.e. the same as the simulation output which is reassuring!

You can also see that the millis() output is quite a way out just before the transition e.g. reading millis at the 41st change means you should be reading from 42.0 to 42.9 instead you get 41 (the worst value is just before the adjustment where millsi() outputs 41ms and the actual time is 42.9ms. You get a jump as the correction factor is added so the millis() function never outputs 42ms and instead outputs 43 as the next result - this brings the millis() output back into alignment with the correct time. This correction process repeats all the time millis() is operating.

However the correction factor at the 42nd interrupt corrects the millis output to 43.3 for millis output 43 so in the long term the millisecond timer is accurate. This oscillation around the correct output is called jitter.

What happens if you use Arduino millis long?

If you decide to use a "long" type definition for the comparison storage variable then you are using a signed quantity instead of the unsigned quantity output by the millis function.

If you were to make a timer using "long" and output the value of long using a Serial command (or any command that converts an integer quality to a string) such as:


...where timer is specified as long and updates, perhaps to seconds as:

timer = millis()/1000;

Then at some point in the operation of your code (when the timer reaches the mid point) the leftmost bit of the unsigned output of millis() will become high and the print function will interpret this a the sign bit. Therefore it will output a negative number and Arduino millis goes negative (or appears to be negative). For a signed calculation the value is interpreted as negative and the print function treats long as a signed quality and therefore outputs a negative number.

Try this of code in Arduino

uint32_t ulong;
int32_t slong,minus1,plus1,s1;
void setup(void) {


    minus1 = -1;
    plus1 = 1;

    Serial.println("\n Observe plus 1");
    s1 = plus1;
    ulong = (unsigned long) s1;
    Serial.print(" signed plus 1 dec :");Serial.println(s1);
    Serial.print(" signed plus 1 hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned plus 1 dec :");Serial.println(ulong);
    Serial.print(" unsigned plus 1 hex :");Serial.println(ulong,HEX);

    Serial.println("\n Observe minus 1");
    s1 = minus1;
    ulong = (unsigned long) s1;
    Serial.print(" signed dec :");Serial.println(s1);
    Serial.print(" signed hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned dec :");Serial.println(ulong);
    Serial.print(" unsigned hex :");Serial.println(ulong,HEX);

    Serial.println("\n suppose millis() reaches unsigned value 0xffffffff-1");
    Serial.println(" Observe the signed value");
    s1 = (long) (0xffffffff-1);
    ulong = (unsigned long) s1;
    Serial.print(" signed minus 1 dec :");Serial.println(s1);
    Serial.print(" signed hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned dec :");Serial.println(ulong);
    Serial.print(" unsigned hex :");Serial.println(ulong,HEX);

    Serial.println("\n Observe signed value of 0x7fffffff - no problem");
    s1 = (long) (0x7fffffff);
    ulong = (unsigned long) s1;
    ulong = (unsigned long) s1;
    Serial.print(" signed dec :");Serial.println(s1);
    Serial.print(" signed hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned dec :");Serial.println(ulong);
    Serial.print(" unsigned hex :");Serial.println(ulong,HEX);

    Serial.println("\n Observe signed value of 0x80000000 (The sign bit)");
    s1 = (long) (0x80000000);
    ulong = (unsigned long) s1;
    ulong = (unsigned long) s1;
    Serial.print(" signed dec :");Serial.println(s1);
    Serial.print(" signed hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned dec :");Serial.println(ulong);
    Serial.print(" unsigned hex :");Serial.println(ulong,HEX);

void loop(void) {

This is the result you'll get:

 Observe plus 1
 signed dec :1
 signed hex :1
 unsigned dec :1
 unsigned hex :1

 Observe minus 1
 signed dec :-1
 signed hex :FFFFFFFF
 unsigned dec :4294967295
 unsigned hex :FFFFFFFF

 suppose millis() reaches unsigned value 0xffffffff-1
 Observe the signed value
 signed dec :-2
 signed hex :FFFFFFFE
 unsigned dec :4294967294
 unsigned hex :FFFFFFFE

 Observe signed value of 0x7fffffff - no problem
 signed dec :2147483647
 signed hex :7FFFFFFF
 unsigned dec :2147483647
 unsigned hex :7FFFFFFF

 Observe signed value of 0x80000000 (The sign bit)
 signed dec :-2147483648
 signed hex :80000000
 unsigned dec :2147483648
 unsigned hex :80000000

You can see that the sign bit is very important (the left most bit) and if you use signed types you will get negative output numbers displayed, even though the unsigned version is correct i.e. it has the expected bit value - or hex value shown.

Also shown is the flip over point where using signed long is OK until you reach 2147483647 (0x7fffffff) add one to that and you get -2147483648 (0x80000000). In terms of days a timer will appear to work fine for ~25 days and then adding one results in a negative output. The explanation of number of days that millis() covers is here.

The easy way round that is to use unsigned long (uint32_t) when dealing with millis().

How to make a simple scheduler using Arduino millis

The aim of this Arduino millis example is to make a simple scheduler algorithm to start different actions at different times. This is only a simple example and you can find multitasking schedulers that transfer operation to a different task saving variables so that tasks can be interrupted stopped and restarted. There will also be the concept of flags that allow communication between tasks. This simple example is definitely not that type but it can be useful nevertheless.

#define LED 13

void setup(void) {
     Serial.println("Simple Scheduler");


void loop(void){
static uint8_t tog=0;
static uint32_t ledtime= millis(),s1=ledtime,s2=s1,s3=s1,s4=s1;

    // LED Flash
    if ( (millis()-ledtime) > 500) {
       ledtime = millis();

       tog = ~tog; // Invert
       if (tog) digitalWrite(LED,HIGH); else digitalWrite(LED,LOW);

    // 10 second message
    if ( (millis()-s1) > 10000) {
       s1 = millis();

       Serial.println("10 SECONDS");

    // 5 second message
    if ( (millis()-s2) > 5000) {
       s2 = millis();

       Serial.println("five SECONDS");

    // 3 second message
    if ( (millis()-s3) > 3000) {
       s3 = millis();

       Serial.println("-3- SECONDS");

    // 2 second message
    if ( (millis()-s4) > 2000) {
       s4 = millis();

       Serial.println("*2* SECONDS");


Scheduling initial times for offset start using Arduino millis

TIP: TIP: To avoid everything happening at the same time set the initial conditions to be offset from each other

You can offset the start time so the are not a multiple of 1000, because if they are then they will fire more or less at the same time and serial output will be generated, and serial output takes a bit of time thus changing the actual time that subsequent time matches happen.

For example you could write the following initialisation (using random offsets):

static uint32_t ledtime=millis(),s1=ledtime+66,s2=s1+90,s3=s1+187,s4=s1+231;

This would mean that the starting times of each timed output are offset from each other - only the start times - the subsequent repeat times would be at the repeat time specified in the code - they would still be offset so they would not happen at the exact same time. So the processor would not have to do the actions associated with each timeout at the same time. Therefore the code will operate more smoothly and not have to do a big processing burp!

How to make a one-shot timer with Arduino millis

This code only does one serial output action action after a set time and then stops. Although using Arduino millis() allows other operations to continue i.e. the LED keeps flashing but only one message is output.

#define LED 13

void setup(void) {

     Serial.println("Simple Scheduler");


void loop(void){
static uint8_t tog=0,s1done=0;
static uint32_t ledtime= millis(),s1=ledtime;

    // LED Flash
    if ( (millis()-ledtime) > 500) {
       ledtime = millis();

       tog = ~tog; // Invert
       if (tog) digitalWrite(LED,HIGH); else digitalWrite(LED,LOW);

    // 3 second message - one shot
    if ( !s1done && (millis()-s1) > 3000) {
       s1 = millis();
       s1done = 1;

       Serial.println("-3- SECONDS ONE SHOT ONLY");

New! Comments

Have your say about what you just read! Leave me a comment in the box below.

Privacy Policy | Contact | About Me

Site Map | Terms of Use

Subscribe to the MicroZine Newsletter and collect your free microcontroller Ebooks, download project code and more...

Visit our Facebook Page:

   Click Here

Recent Articles

  1. How to use a DS18B20 on the Arduino for easy temperature measurement

    How to Easily Use the DS18B20 Maxim One-Wire thermometer with Arduino example code showing external or parasitic power modes.

    Read more

  2. The TCS230 Color Sensing Chip: How it works and how to use it.

    How to use the TCS230 (/TCS3200) Color detector chip and easily add it to any of your projects.

    Read more

  3. Arduino millis

    How to use Arduino millis() for delays but still make the processor do work - Stop using delay()

    Read more

  4. [Arduino Tutorial] : How to use the 74HC595 shift register with shiftOut().

    The Essential Guide to the 74HC595; What it is and how you can easily use one in any of your projects.

    Read more

  5. Pic Programmer Types

    Which pic programmer do you need? This page discusses PIC programmers and gives some essential information on choosing or building your own programmer.

    Read more

  6. Arduino Pulsein: an easy way to measure pulse periods in microseconds.

    learn how to use Arduino pulseIn and pulseInLong to get the most accurate pulse measurement on an Arduino.

    Read more

Readers Comments

"I wanted to thank
you so so so much
for all the information
you have provided in
your site it's


- Ranish Pottath

"This site really is
the best and my favorite.
I find here many useful
projects and tips."

- Milan


"Awesome site,
very, very easy and nice
to navigate!"

- Matt

Learn Microcontrollers

"Interested in

Sign up for The
Free 7 day guide:


"I am a newbie to PIC
and I wanted to say
 how great your
site has been for me."

- Dave


"Your site is a great
and perfect work.

- Suresh


"I couldn't find the correct
words to define
yourweb site.

Very useful, uncovered,
honest and clear.

Thanks so much for
your time and works.

- Anon

Back to Top