This C programming tutorial will be the first in a series of C tutorials. The posts in this blog thus far cover both the theoretical and the more practical, but until now none covered programming.
The C programming language is universal. Some readers may be C programming experts (unlike me). Others may be new or maybe haven’t even written one line of code. This series of posts will assume you’re brand new to C programming (and I’m sure I’ll get to scrape some of the rust off my C skills in the process). Those with C experience may find it makes a good review. If you are new to C, this C programming tutorial is a great place to start.
If you’ve worked with PICs or any sort of microcontroller before, there’s a good chance you have some experience in C programming. Arduino sketches are very similar to C, so ditto there.
Since this is an introductory post, we’ll first talk a bit about the history of C and how a high-level language like C gets translated into ones and zeros that a computer or microcontroller can understand and act on.
After that, we’ll go over some basic C programming syntax, rules, and best practices.
Let’s do this thing.
C Programming Tutorial 1
History of C Programming (Abridged Version)
Early programmers would program in binary.
Writing endless strings of 1’s and 0’s wasn’t very fun, quickly becoming tedious and error prone.
What was needed was a more human readable way to write programs. To solve this, the assembler was invented. An assembler is just another piece of software that can translate instructions like add a to b into the binary format a microcontroller can understand. Some of you may have heard of assembly language. This is where it came from.
These days, most of us don’t use assembly language (at least directly) due to powerful microprocessors, intelligent compilers, and lots of available memory. Some of us are sadistic and like programming in assembly ;-). To each his own. If nothing else, you’ll learn more about the registers and other internal workings of your microcontroller of choice.
However, programming in assembly language can be a real chore. It requires the programmer to write one line of code for every instruction the machine will follow. Once again, a better way was needed.
Programmers today owe their productivity to higher-level languages like C, C++, Java and others.
High-level programming languages (like C) offer several benefits. First, they allow programmers to think in a more natural language with real or semi-real words and algebraic expressions. The second is productivity. A lot more can be done with one line of code than in assembly. Finally, unlike assembly language, high-level languages allow programs to be ported to different computers with different processors and operating systems.
In the early 1970s a guy named Dennis Ritchie developed the C language for Unix operating systems. It evolved from two other languages you’ve probably never heard of: BCPL and B. Today, it’s one of the most popular languages. In fact, there’s a good chance the operating system on your computer is written in C or some variation/combination.
Hardware/Software Interface: From C to Binary
The typical application running on your PC or tablet can contain hundreds of thousands of lines of code which can contain complex instructions.
The problem is that a microprocessor or microcontroller can only execute simple, low-level instructions. These simple instructions include things like add variable a to variable b or take variable b and put it in register x.
And, to complicate matters, microprocessors and microcontrollers only speak in binary. If you’re not familiar with binary numbers, please read A Bit of Fun with Binary Number Basics.
The statement below may make total sense to you if you’ve been working with microcontrollers or Arduino for a while. Or, if not, it may not make any sense at all. Either way, just go with me here for a minute. This is just an illustration which does not require complete understanding of the code.
Int Temp = analogRead(sensorPin); If (Temp > 500) { //more complex instructions go here }
How does a complex statement like the one above actually run on a microcontroller that only knows basic arithmetic, logic, moving and shifting? How do these statements translate into the ones and zeros that the microcontroller can actually understand?
Glad you asked. This is what we’re going to answer now.
Microcontrollers are electronic devices, so to speak to one you need to send electronic signals. That’s where binary numbers come into play. A zero represents low or off, usually 0 V or close to it, while a one represents high or on, usually either about 5 V or 3.3 V, depending on the processor.
Become the Maker you were born to be. Try Arduino Academy for FREE!
Going from complex instructions to simple ones the microcontroller understands requires several layers of software that translate the high-level operations into simpler instructions.
Enter compilers and their constituent parts.
The compiler first transforms the C program into assembly language.
The assembler, which is usually built in to the compiler software package, then translates the assembly language program into machine language. It then creates object files, which combine machine language, data, and information it needs to place instructions properly in memory. Often, the assembler creates multiple object files which need to be put together.
This is where the linker — another part of the compiler software package — shines. The linker will take all the independently assembled machine language programs and object files and put them together. This produces an executable file that the microprocessor can understand and run.
Figure 1 illustrates this beautifully. Note that the loader simply loads the executable file from the disk into memory.
Figure 1: Compiling a C program. The rubber meets the road — going from complex instructions to simple binary instructions the microprocessor can understand.
There is one more thing I want to point out about the C programming language.
Some of you may have experience programming in BASIC, which is an interpreted language. C is a compiled language. As we now know, a compiled language takes source files (the code you write), compiles, assembles and links them to produce an object file. An interpreter is just a program that simulates an instruction set architecture.
Compiled code tends to run faster and perform better than interpreted code, though JIT (just in time) compiling does increase its performance. Some languages use interpreters, others, like C, use compilers.
Programming in C vs Arduino
Since this is the first C programming tutorial, a few words are in order on this subject due to the enormous popularity of Arduino boards.
Some of you use stand-alone or naked microcontrollers for your projects. After all, this is how things are actually made and mass-produced in the real world. And it’s cheaper.
Others use platforms or ecosystems such as the Arduino almost (or entirely) exclusively.
Finally, some of you may use both depending on your goals and background.
For hobbyists, the number of people who use platforms like Arduino has exceeded those who only use naked microcontrollers.
Arduino sketches are written in a language similar to C, though a sketch itself is not completely compatible with C.
In Arduino, the main() function is hidden from view and added for you when you compile or “verify” your sketch. Also, there are two functions which the Arduino ecosystem absolutely requires: setup() and loop(). The only function C requires is main().
C also lacks built-in functions for using microcontroller I/O such as digitalWrite().
To make learning simple, the Arduino IDE designers hide a lot of detail and functionality behind layers of abstraction, many of which come in the form of libraries. Note that the C programming language also uses libraries. They are added during the linking process.
Though there are a few slight differences, if you can become a competent C programmer, you’ll also master the Arduino IDE and crush it on your Arduino projects. And if you desire to port the code from Arduino to a naked micro it will be a cinch.
Now that we know some C history and a bit about how high-level code converts to electrical signals the processor can use, let’s dive into the meat of this C programming tutorial and talk more specifically about the language.
C Programming: Syntax & Good Programming Practice
Just like English (or any other language), C and other programming languages have rules. These rules are referred to as syntax.
If you get the syntax wrong, your program most likely won’t compile resulting in an error during compilation. If by chance it does compile, your program will most likely not work as you intend.
C programming tutorial 1, lesson 1: syntax is important!
Earlier, we went into detail on how the compiler works. Now it’s time to think of the compiler as the most annoying English teacher in the world who takes everything literally.
For example, let’s say you type teh rather than the by accident. Instead of realizing that you probably meant the and circling the error in red ink, she’ll immediately stop reading your paper, give it back to you, and tell you she has no idea what you mean and can’t read your paper until you fix it. Then, if you make another mistake the process repeats until all spelling and grammar are perfect. Once that happens she’ll read it and you’ll get a grade.
At first this may seem intimidating so let’s talk a bit more about C programming syntax.
In English, you end each sentence with a period. In C, you end each statement with a semicolon. For example:
int myNumber = 25;
declares an integer named myNumber and sets it to 25.
If you forget to add a semicolon where you should the compiler will give you a cryptic error message, so remember to use them.
While you can put more than one C statement on one line, good practice (especially for beginners) says to keep one statement to each line to avoid confusion.
For example, you probably shouldn’t write a line of code like the one below.
int myNumber = 25; int anotherNumber = 36; if ( myNumber < y && anotherNumber < z ) printf( "this is kind of hard to read and confusing" );
Don’t worry if you don’t understand the code yet. Eventually you will.
A better way to write that same code is shown below.
int myNumber = 25; int anotherNumber = 36; if ( myNumber < y && anotherNumber < z ) printf( "this isn’t so bad" );
There are four things to notice about the code above.
First, it’s a heck of a lot easier to read and understand. Second, each statement has its own line. Third, the first part of the “if” statement does not have a semicolon at the end. Finally, the second part of the “if” statement (with printf in front) has a slight indent.
Indents in code make it more readable. Since C ignores white space, good practice dictates to use indents with if statements and other types of control structures.
What’s white space? Exactly what it sounds like.
I could write:
if ( myNumber < y && anotherNumber < z ) printf( "this isn’t so bad" );
or I could skip a few extra lines and write:
if ( myNumber < y && anotherNumber < z ) printf( "this isn’t so bad" );
Note that although it works, the second example is bad practice and sloppy. Both snippets compile the same and produce the same result. The complier ignores white space with no typing in it.
C programming tutorial 1, lesson 2: use good form and make your code easy to read!
Comments in programming are one of the most basic, yet useful things. There are a few ways to write comments in C. Comments can either start with // or you can enclose them between /* and */.
//this is an example comment. //here is a longer comment… //…and it takes more than one line. /*Here is an example of a longer comment written in C this way. This can take up multiple lines in the editor, as we can see here.*/
All the above are valid comments.
The compiler ignores comments. They are descriptive statements for humans to read, so you and others can understand your code. In other words, comments should explain what the code does. Good programming practice says to use them generously. I’d bet you money that after a while you will forget what something in your code does and burn hours of time trying to figure out your own program. Don’t be that person.
You can use comments to explain lines of code that aren’t obvious with // after a given statement like below.
int c = a + b; //add a and b, then store it in c.
Yeah, the code above is pretty obvious, but you get the point. It’s okay to include comments on the same line as a statement.
You can also insert long, multiline comments in the beginning of your code explaining what the program does, who the author is, the date, revisions, etc. using /* and */.
The Arduino sketch snippet in figure 2 depicts this concept. Notice the comments give a ton of information about the program.
C programming tutorial 1, lesson 3: use comments generously!
Figure 2: opening comments for an Arduino program. This type of comment comes in handy for any C program on any platform.
There is more to say about syntax and good programming practice. To avoid information overload, we’ll address it when appropriate in future posts.
C Programming Tutorial: More to Come
In no way is this series meant to be a full-blown course on C programming, but C is a well-documented language and there are a ton of references available.
We have a lot more to cover, so there are more C programming tutorials to come, though I probably will insert posts on other topics in between.
Future tutorials will talk about things like functions, variables, control structures, formatting and more so stay tuned.
Meanwhile, what’s your go-to programming language? Leave a comment and tell us about it.
Become the Maker you were born to be. Try Arduino Academy for FREE!
Electronics Tips & Tutorials Sent Directly to Your Inbox
Submit your email & you'll get:
- Exclusive content that I don't put on the blog
- The checklist 10 mistakes all electronics enthusiasts make (& how to avoid them)
- And more!
Alan ford says
Thank you for this excellent and very refreshing tutorial! The only language i started (and stayed) with (long time ago), is basic. So, speaking about c, i have some restrictions in understanding c due to formatting of my brain into basic. Could you provide more help for us, crippled ones , how to understand differences? Thank you!
Brian says
Thanks Alan, always great to see readers getting stoked about the things you write! Stay tuned, I’ll be doing more C programming tutorials to hopefully heal the crippled (and myself along the way)!
Inez Maxine Neal says
Thanks
VASILEIOS says
nice tutorial like it , WE WANT MORE
Brian says
And soon you will get more!
Dart vs Javascript says
Actually, the beginners need these programming tutorial segment for making these fields in their grips. If the coders have any issues or doubts to run the code check this post to overcome easily.