Arduino Software Basics

Summary

A high-level discussion of computers, software and related terminology.

Software Basics

Software is a set of instructions that run on a computer. A computer is a machine that recognizes various instructions and executes them in a sequence.

Computers & CPUs

The word "computer" came from the name of a job that was present until the 1950 & early 60s. These were people who calculated various things, formulas, mathematical equations. etc. They used calculators to compute these things or did it by hand. They computed formulas, therefore they were called computers. The show "Hidden Figures" https://www.imdb.com/title/tt4846340 is about 3 women who were computers at NASA.

Today computers are machines usually built from a wide variety of chips. The main chip is called the CPU "Central Processing Unit".

There are memory chips which hold RAM Random Access Memory. It is "random access" because you can access any memory location at will. There are other kinds of memory that only allow you to access a memory only a location at a time.

The other chips on a computer fall into two broad categories:

  • support chips
  • peripheral chips

Support chips help connect other chips to each other.

Peripheral chips provide access to the "outside world", say to Wi-Fi and ethernet signals, cellular signals, and other communication methods like I2C, SPI or CAN, or to other components like LEDs, speakers, motors, etc.

Binary

We all know that computers use binary. But why binary?

Binary came about based on the simple fact that most computers use electricity. It is possible to have non-electrical computers, but they are rare. In its simplest operation, electricity is either flowing or it is not. The key insight the first computer engineers made was to assign meaning to those two states. Flowing means "on". Not flowing means "off". And then they took a little further jump and assigned 1 to "on" and 0 to "off". Note that this assignment is arbitrary. It is just as OK to assign 0 to "on" and 1 to "off". Some chips and devices do exactly that since it is simply more convenient for it.

But 0 and 1 doesn't get you very far. So the computer engineers took the next small step. They put multiple circuits side-by-side. So say they put 3 circuits in a row. They now have 8 possible combinations. And again, they took a little further jump and assigned numbers to them:

   on/off   value
   000       0
   001       1
   010       2
   011       3
   100       4
   101       5
   110       6
   111       7

This is the base-2 or "binary" numbering system. Each on/off column is called a "bit" which is short for "binary digit". BTW binary was invented by Leibniz in 1701. https://www.cut-the-knot.org/do_you_know/BinaryHistory.shtml

Then the engineers took the next small step, but it was a fundamental insight. We have, so far, been assigning numbers to the binary combinations. But we can assign any arbitrary meaning to each of those combinations.

For example, we could assign the letter "a" to 101 (=5). "b" to 110 (=6) and so on. If we had enough bits we could represent all 26 letters of English in both lower and upper case, punctuation, special symbols and, confusingly, numerals i.e. the character "3" or "5". BTW Caesar used this scheme to encode his messages so that no one else could read them without knowing the assignments.

You need about 7 or 8 bits to do fully encode English. Eventually that encoding was standardized, it became ASCII American Standard Code for Information Interchange, see https://www.asciitable.com To do all languages of the world, you need more complex encodings, see unicode, UTF-8, UTF-16 etc.

Or another example, you can assign these numbers to areas of a television screen. When a bit is a 1, a dot appears in a specific location on the screen, so 101010... would be a pattern of light and dark dots. But then go the next step and assign 3 bits per location. The first makes the location Red, the second makes the location Green and the last bit makes the location Blue. If you tweak the control circuitry just right you can turn on all 3 bits and get a white dot, turn on Red and Blue and get purple and so on.

So far so good, but then the engineers made a much bigger, more fundamental, insight. They realized that they could assign those encoding values to instructions or "Operation Codes" ("opcodes" for short). For a silly example, you could give a recipe in binary:

   000   turn off burner
   001   turn on burner
   010   put frying pan on burner
   011   add steak to frying pan
   100   move steak from frying pan to plate
   101   flip steak
   110   wait N minutes where the next number  # minutes
   111   add salt and pepper to steak

So cooking a steak would be something like this:

   010   ; preheat frying pan
   001
   111   ; add spices to the steak
   110   ; wait 1 minute
   001
   011   ; put the steak in the preheated frying pan
   110   ; wait 5 minutes
   101   
   101   ; flip the steak
   110   ; wait 5 minutes
   101   
   100   ; move steak from frying pan to plate

Hmm, there's a bug in that program. Can you find it? It's subtle, but I think you'll be able to find it in the end.

CPU operation

The instructions that a CPU runs are called operation codes or "opcodes" for short. It also has a set of "registers". These are special, named, memory locations internal to the computer. In small CPUs there may only be one or two registers say A and B, large CPUs have 20 or 60 or more.

The opcodes are stored in memory and there is a special register called the Instruction Pointer (IP) that points to the next instruction to execute. So the sequence is:

  1. get the opcode at the memory location pointed to by IP
  2. based on the that opcode, additional parameters after the opcode may be retrieved as well. The IP is incremented as necessary. At this point, the IP points to the next opcode to execute.
  3. load a value from a memory location into a register, e.g. load location 0000 into register A
    • store the contents of a register to a memory location, e.g. store register B into location 0001
    • add register A and register B and store the result in register A
    • etc.

How does Software run on a computer

Software is just a set of opcodes organized in a special way such that the CPU can execute them all in the right order.

A programmer writes software, usually in a special language. That language may require a simple translation into opcodes (.e.g. Assembler) or it may be more abstract and high level such as Java, Ruby and Python. Or it may be an intermediate language like C or C++.

These high level languages require a compiler. A compiler is a program itself, and it translates from the high level, abstract, representation into Assembler and then converts that into low level opcodes.

Linking

When a program is short enough then there is only one component i.e. the full set of code itself.

But as programs get larger, it is better to use the ancient strategy of divide and conquer. Take a large program and split it up into smaller chunks, test them and compile them and when everyone one is ready put all those generated code together into the final executable.

That last step is called "linking" and is accomplished with a tool called a linker. It takes all the individual pieces and links them together into a single file can be installed on the computer or microcontroller.

That sounds fairly easy, but there are subtle issues that arise. For example, each of the chunks (usually called a "library") may have specific memory addresses in it. If the chunk happens to be placed in the correct spot where the addresses are correct, then all is well! But in most cases, those addresses have to be shifted relative to point to the correct, final memory address in the final executable. The linker takes care of all of those issues.

C/C++ functions

Divide and conquer can be applied at a smaller scale. It is very convenient for humans to break the overall code into tinier and tinier chunks called functions.

Functions represents a single conceptual behavior that is given a name. That behavior can then be invoked by "calling" a function. Wherever the microprocessor happens to be, the function can be called by jumping to the start of it, and when it is complete, the microprocessor jumps back to where it started off.

To keep track of where that return address is, the language uses a concept called a "stack". So the full operation is:

   ; save the address of the instruction after the next one on the Stack
   jump to the start of function XYZ
   ; ... the next instruction ...

   ; start of function XYZ is here
   ; ... the rest of function XYZ
   get the address from the top of the Stack
   jump to that address 

Using this technique, nested functions can be done e.g. function ABC() which calls DEF() etc.

The Bug

At the start of the recipe the burner is turned on:

   010   ; preheat frying pan
   001   ; turn on burner

but nowhere is it turned off i.e. there is no 000 turn off burner in the code. Not good!

- John Arrizza