Function, procedure, interrupt sevice routine, callback function - which is better to use?

A project log for IuT voltmeter for a breadboard

Measure several voltages on a breadboard and display results on a smartphone for less than $10 for parts

alexandernAlexanderN 07/16/2017 at 11:280 Comments

Some time ago I came accross the term REST (ReST) and how important it was. I tried reading a lot of articles (starting from Wikipedia and ending with the likes of "How did I explain REST [and some other little things] to my wife") to get the idea. Some of the sources cited a long list of references which included PhD theses, books and internet archives going a few years back. Unfortunately I am not a diamond to be around forever and work through these lists. (Most importantly, there is no known diamond of my weight as of yet.) Therefore, when I figured out that, if I folow the table below, I comply with REST/ReST/RESTful/you_name_it

Action on resourceHTTPSQL (for comparison)
Access existingGETSELECT
Update existingPUTUPDATE
Delete existingDELETEDROP

I have got my rest despite some people would rightly say that I missed a lot of important points. This log entry is used to clarify a few points that I think are important before describing recent amendments to the firmware.

Here are my highly opinionated bullet points

1. What computers are and what they can do (preamble 1)

They are collections of CMOS gates which outputs can be in one out of two states (High/Low, 1/0, ON/OFF). A single output represents a bit. Single bits can be used for embedded developments and serial comunications (bit bang) but computers at present operate at least 8 bits or one byte at once. (The byte was adopted since IBM/360s. At present AFAIK only PIC16s allow native single bit manipulations whereas some SIMD instructions can process up to 1024 bits at once.) Stored program computers read some bytes called an instruction from an electronic memory first, then access some storage (may be from the same memory) to get the operand(s), then place these operands and operation code to the inputs of an electronic circuit called ALU to produce the result. Finally, the result goes to the designated storage. The good thing is that a computer could process millions/billions or even more instructions per second, and particular ALU designs allow interpretations of the results as additions, subtractions and so on for a trained eye.

2. What computer programming is about (preamble 2)

This is the way to define operands and instructions, and interpret the results in some human understandable way. All programming systems like C++, Arduino, OpenGL, Bootstrap etc are here to bridge the semantic gap. Most people refer to the program as to what was written by a human and to the code as to what is actually controling the computer at the run time.

3. How did the computer programming evolve (preamble 3)

First computers AFAIK operated just the fixed sequence of instruction, which was OK to compile the artillery tables they were designed for. It was no point to run such a program the second time as the results would be perfectly the same. Later computers became more useful as they included provisions to branch the code depending on some operands, and repeat the same code snippets to achieve some desired condition. IIRC a typical code consits of 60% instructions to move data around, 30% instructions to perform some actual computing, and 10% instructions to branch the code (Patterson & Hennesy ?).

However, until the development of IBM/360, computers were not code compatible, and every computer was to be manufactured to an order and was programmed individually. A big boost to the programmers' productivity came when system software called assembler was introduced to computing. Assemblers allowed writing computer codes in human readable format at the first time (e.g. write ADD R1, 25 instead of some 1010011101). With the IBM/360 it became sensible to develop high level languages that would be more easier to use by humans to program various computers in the same way by using the same piece of software called compiler.

Since then the assembly language usage was shrinking to system programming, resolving bottlenecks, access to specific computer internals, high speed and real time tasks as it can be crafted to be more efficient than what a compiler should generate for compatibility and code correctness reasons. (Notes: there is a way to include some assembly statements into high level code for the above reasons called inline assembly. Quite recent webassembly (webasm) aims to bring back native code execution to high level web programming in order to speed up code execution.)

4. How did the programming methodologies (definition of data and instructions) evolve; functions vs procedures

Integer data pieces differ by their length and whether they should be treated as having or not having the sign (e.g. uint8_t, int32_t etc). Floating point data pieces can be of two lengths, either float or double. There are also strings for text and more complicated data types starting from arrays, structures, unions and going all the way to JSON data, SQL tables, video streams etc.

90% of machine instructions are executed in sequence as only the remaining 10% branch the code. Many microcontrollers (MCUs) do not support all the arithmetic operations (rarely integer multiply, even more rarely integer division, and only recently high end MCUs started to support floating point). These operations need to be performed programmatically, taking many instructions to completion. It makes serious memory saving not to include the required code snippets every time when, say, multiplication is required, but place this code somewhere in the memory once and CALL it as needed. Explicit CALL instruction is required in assembly, and it is followed by the memory address of the code snippet. In high level languages it is enough to write the name of the required code module followed by the list of the parameters (e.g. multiplicands). If the code returns a value, it is called a function, otherwise it is called a procedure or a subroutine. Code modules facilitate programs reuse and sharing, and in high level languages isolate parts of code from each other preventing various bugs. They are also very useful to hide the actual program complexity. For example, a single high level statement like

printf ( "%f", sin(angle*pi/180) );
would require execution of perhaps a few hundreds of machine instructions. Competent use of subroutines/functions was called procedural programming. (Note: inline keyword for the higher speed of execution.)

Code branching is implemented using if..them..else; switch; try...catch. Loops are defined using for; do...while; while... . Using these constructs only with appropriate indentation was used to be called structural programming. At present in some languages (Python) one simply cannot program any other way.

Object-oriented programming, among some other things, allows encapsulating relevant data (called members) and procedures (called methods) into programming constructs of then novel type called objects.

5. Interrupts and interrupt service routines (ISRs)

Most computers need to keep some attention to the events that are asynchronous to the code execution (e.g. they need to react when the user taps the touch screen or a particular time interval, set by an internal timer, has expired). There is the need to process such an interrupt to the main code's execution then come back and resume the interrupted code. A piece of code (program) that handles this actions is called the interrupt service routine (ISR), and for a good reason as effectively it handles some call but the call made by internal or external hardware.

The ISR needs to save the state of the interrupted code, do what is required then restore operation of the interrupted code. These actions are straightforward in high level languages (interrupt keyword) but can be quite peculiar in assembly (already mentioned PIC16).

On the other hand, a programmer can prepare and list parameters for any procedure/function before calling them. In contrast an interrupt can happen any time, and it is neither possible to supply some actual parameters to the ISR nor use the ISR result(s) directly. Global (or extern) variables need to be used for this purpose instead.

An ISR should operate some hardware relevant to the triggered interrupt, in particular clear the interrupt flag (this action can be facilitated by some code generating tools like STM32CubeMX).

Therefore an ISR can be treated as a procedure that responds to asynchronous internal and external events (interrupts), and exchange data through some globally defined variables.

6. Callback functions

I experienced callbacks at first when started using MATLAB GUIs. These functions were called by the MATLAB interpreter when the user performed some GUI-related action like entering text or simply clicking the mouse. Using these callbacks relieves the programmers from programming a lot of things like processing the mouse interrupt, figure out what application it was relevant to, call the application's relevant subroutine etc. Eventually I was able to, instead of editing script files, enter the required simulation parameters in a GUI window then click the "start" button to run the code, which was very handy.

Callback operation is especially important for ESP8266/85 as WiFi communications need to be processed and acknowledged in some timely and conventional manner. Letting the users to write their own ISRs could easily break both the correct data exchange and RF compliance requirements. Therefore the built in ESP firmware handles the RF interrupts then calls the programmer's supplied code (callback function) to process the communication outcomes according to the intended application.

Callback support is included in some programming languages like Lua and Javascript. As the callback function is called by some intermediary, the relevant parameters can be passed onto it, and the result used directly. (Note : both Lua and Javascript allow anonymous (nameless) callback functions.)

Therefore a callback function can be treated as a procedure that responds to some asynchronous external or internal events and is called not by the event itself but by some middleware instead, which can supply the parameters and utilise the returned value.