Learning Goals 5 min
By the end of this lesson you will be able to:
- Explain that every
charis secretly a small integer — its ASCII code — and print both the character and its number usingSerial.println(c)andSerial.println((int)c). - Convert a typed digit character (
'7') into its numeric value (7) with the formulac - '0', and use the result to drive code (e.g. blink an LED that many times). - Use simple character arithmetic —
c + 32,c - 'A' + 'a'— to toggle case, and use the helpersisDigit()andisAlpha()to test what kind of character you received.
Warm-Up 10 min
L01-26 left a question hanging. We wrote if (c == 'r') and it worked — but what is 'r' really? Is it a letter? A picture of a letter? A number? Today's lesson opens the hood.
Quick-fire puzzle
Computers store everything — pictures, music, text, this web page — as numbers. There's no letter r anywhere inside an Arduino, only ones and zeros. So when your sketch reads 'r' from the Serial Monitor, what number does it actually receive?
- Guess: when you type
Aand Send, what number do you think the Arduino sees? (Pick anything — there's no penalty for being wrong.) - Guess: when you type
a(lowercase), is the number the same as forA, or different? - Guess: when you type the digit
5, does the Arduino receive the number5, or a different number?
Reveal the answer
Ais the number 65. It always has been, in every computer made since the 1960s.ais the number 97 — a totally different number. Computers are case-sensitive because the codes literally are different.- The digit
5is the number 53, not 5! The character'5'and the integer5are different things. That's a real-world bug that has caught millions of beginners.
This translation table — letter ↔ number — is called ASCII (pronounced "ASK-ee"), short for American Standard Code for Information Interchange. It's the foundation of every text file, terminal, and serial connection on the planet. Today's lesson teaches you to use it.
New Concept — characters are numbers in disguise 15 min
The big idea — one type, two faces
A char variable holds one number from 0 to 255. That's it. The reason we call it a "character" type is that when you ask Serial.println(c), the Monitor consults the ASCII table and prints the matching letter instead of the raw number. Internally there is no letter — only a number with a letter painted on it.
You've been doing arithmetic on these numbers without realising it. c == 'r' is really c == 114, because 'r' is just the compiler's friendlier way of writing 114. The single-quote syntax from L01-26 is a shortcut, not a different kind of value.
The ASCII table — the bits you actually need
The full table has 128 entries, but only three patches matter for everyday Arduino work. Memorise these three landmark numbers and you can derive the rest.
| Character | Code | Then… |
|---|---|---|
'0' | 48 | '1'=49, '2'=50, …, '9'=57 |
'A' | 65 | 'B'=66, 'C'=67, …, 'Z'=90 |
'a' | 97 | 'b'=98, 'c'=99, …, 'z'=122 |
Three patterns to notice:
- The digits
'0'…'9'are consecutive. Subtracting'0'from a digit character gives its real value.'7' - '0'= 55 − 48 = 7. - The capitals
'A'…'Z'are consecutive. So are the lowercases'a'…'z'. - Lowercase is always exactly 32 more than its uppercase.
'a' - 'A'= 97 − 65 = 32. Soc + 32converts uppercase to lowercase, andc - 32goes the other way.
Reveal the number behind any character
To see the number that lives inside a char, cast it to int with (int)c. This tells the compiler "print the number, not the letter":
char c = 'A';
Serial.println(c); // prints: A
Serial.println((int)c); // prints: 65This trick is the single most useful debugging move when something character-related goes wrong — show me what number is really in the variable.
The digit conversion — the most useful trick
Anytime the user types a digit and you want to use it as a number (to blink an LED that many times, set a brightness, etc.), subtract '0':
char c = Serial.read(); // suppose the user typed '7'
int n = c - '0'; // n is now 7, ready to use as a numberThis works because '0' is 48 and '7' is 55, so the subtraction gives 7. The same trick works for any digit 0–9. This is the trap from the warm-up — without the subtraction, the digit '5' would behave as 53 inside arithmetic. The Arduino would happily blink an LED 53 times.
Character-classification helpers
Arduino includes a small family of helpers that answer "is this character a…?" by returning true or false. Two are common enough to memorise:
isDigit(c); // true if c is '0'..'9'
isAlpha(c); // true if c is 'A'..'Z' OR 'a'..'z'You could write these yourself with chained ifs and the ASCII codes above (c >= '0' && c <= '9'), but the helpers are already there and clearer to read. Use them.
Why it matters
Two reasons. First, characters showing up as random-looking numbers is one of the most common confusion-points for beginners — once you know why, the confusion vanishes for life. Second, character arithmetic is the foundation of L01-28's switch/case command parser, of every form-input validator on the web, of every digit-by-digit text-to-number conversion ever written. It's not a niche topic; it's everywhere.
Worked Example — the character inspector 20 min
You're going to build a sketch that tells you everything about whatever character you type — the character itself, its ASCII code, and whether it's a digit or a letter. No wiring at all — just the USB cable.
Step 1 — The sketch
// Character inspector — type anything, see its ASCII code
void setup() {
Serial.begin(9600);
Serial.println("Type a single character and press Send.");
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
Serial.print("char='");
Serial.print(c);
Serial.print("' code=");
Serial.print((int)c);
Serial.print(" digit?=");
Serial.print(isDigit(c));
Serial.print(" alpha?=");
Serial.println(isAlpha(c));
}
}Step 2 — Upload and explore
- Upload at 9600 baud. Open the Monitor; set the line-ending dropdown to "No line ending" (L01-26 rule).
- Type
Aand Send. The Monitor shows:char='A' code=65 digit?=0 alpha?=1 - Type
aSend. Now you seecode=97— a different number, even though to a human it's "almost the same letter". - Type
5Send.code=53. This is the famous trap. The character'5'is the number 53, not 5. - Type a space Send.
code=32,digit?=0,alpha?=0. Even the space is a number. - Type
?Send.code=63. Punctuation is in the table too.
Step 3 — Convert a digit to its real value
Now add the digit-conversion trick. Extend the sketch so that when you type a digit, it also prints "as number = X" with the actual numeric value:
if (isDigit(c)) {
int n = c - '0';
Serial.print(" as number = ");
Serial.println(n);
}
else {
Serial.println(); // just end the line
}(Replace the original final Serial.println(isAlpha(c)); with Serial.print(isAlpha(c)); so the new line can append cleanly.)
Upload, type 5. Now the Monitor shows:
char='5' code=53 digit?=1 alpha?=1 as number = 5The 5 at the end is the result of 53 - 48. This is the line you'll write in real sketches every time the user types a numeric command.
Step 4 — Flip the case
Add one more block that uses the +32 trick to print the opposite-case version of any letter you type:
if (c >= 'A' && c <= 'Z') {
Serial.print(" lower = ");
Serial.println((char)(c + 32));
}
else if (c >= 'a' && c <= 'z') {
Serial.print(" upper = ");
Serial.println((char)(c - 32));
}Notice the (char) cast before printing the result. Without it, Serial.println(c + 32) would print the number 97, not the letter a — because adding 32 to 'A' gives an int, and println prints ints as numbers. The cast says "treat this number as a character for the purpose of printing".
Type H. Monitor: … lower = h. Type h. Monitor: … upper = H. Eight characters of arithmetic and you have a case toggler that works on every letter in the alphabet.
Trace it on paper
For each typed character, predict what the Monitor would show on the inspector sketch from Step 2. No upload — work it out from the table.
| Typed | char | code | digit? | alpha? |
|---|---|---|---|---|
Z | Z | ____ | 0 | 1 |
z | z | ____ | 0 | 1 |
0 | 0 | ____ | 1 | ____ |
! | ! | 33 | 0 | 0 |
If you can fill in the gaps using only the three landmarks ('0'=48, 'A'=65, 'a'=97), the table has stuck.
Try It Yourself — three character tricks 20 min
Goal: A code-reporter. Whatever character you type, the Arduino replies with only the ASCII code (a single line, just the number). No wiring.
Plan: standard input pattern; one Serial.println((int)c); in the body.
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
Serial.println((int)c);
}
}Questions:
- Type each of these in turn and write down what comes back:
A B C a b c 0 1 2 ?. Eight rows in the Monitor — what pattern do you notice in the numbers? ____ - Type
Zthena. The capital comes back as a smaller number than the lowercase. Roughly what's the gap? ____ (Hint: count from Z=90 to a=97.) - Try removing
(int)from the print line. What happens, and why? ____
Goal: A blink-N-times sketch. Wire one LED on D9 (L01-07 circuit). Type a digit 0–9; the Arduino blinks the LED that many times.
Plan: read a char; if it's a digit, convert with c - '0', then a for loop from L01-11 to blink that many times.
const int LED_PIN = 9;
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
Serial.println("Type a digit 0-9.");
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
if (isDigit(c)) {
int n = c - '0';
Serial.print("Blinking ");
Serial.print(n);
Serial.println(" times.");
for (int i = 0; i < n; i = i + 1) {
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
}
}
}
}Questions:
- What happens if you remove the
c - '0'and passcdirectly to thefor's upper bound? Why? ____ (Hint:'5'is 53.) - What happens when you type
0? Does the LED blink, and how many times? ____ - What happens when you type
a? Why doesn't the LED do anything? ____
Goal: A case-toggling echo. Whatever letter you type, the Arduino echoes the opposite-case version. H → h; h → H; digits and punctuation come back unchanged. No wiring.
Plan: three branches using ASCII range tests — uppercase letter, lowercase letter, anything else. Use (char)(c + 32) and (char)(c - 32) for the conversions.
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
if (c >= 'A' && c <= 'Z') {
Serial.println((char)(c + 32));
}
else if (c >= 'a' && c <= 'z') {
Serial.println((char)(c - 32));
}
else {
Serial.println(c);
}
}
}Questions:
- Why is the
(char)cast needed before printing? Try removing it for thec + 32line — what does the Monitor show then, and why? ____ - You could also write the swap as
c - 'A' + 'a'instead ofc + 32. Both work. Which version do you find easier to read, and why? ____ - How would you change the sketch so that punctuation also flips — for example,
?becomes!? (You don't have to write the code; just describe the approach in one sentence.) ____
Mini-Challenge — the digit-and-letter remote 10 min
"Type 3r to blink the red LED three times"
Wire two LEDs — red on D9, green on D11 (any two pins from the L01-15 circuit will do). Build a sketch that combines digit-conversion and character commands. The user types a digit, then r or g:
3r→ red LED blinks 3 times5g→ green LED blinks 5 times0r→ nothing happens (zero blinks)
This needs two characters, which means two loop passes. Keep a mutable global int pendingCount = -1; that holds the digit while waiting for the letter that follows it. (-1 means "no digit waiting".)
Your task:
- If the incoming char is a digit, store its value in
pendingCount. Print a friendly"Got count: 3"so the user knows the digit was received. - If the incoming char is
'r'or'g'andpendingCount > 0, blink the chosen LEDpendingCounttimes. ResetpendingCount = -1;afterwards. - If the user types
rwith no digit pending (i.e.pendingCountis still-1), print"No count yet — type a digit first". - Helpfully, ignore anything else (an unexpected character resets
pendingCountback to-1).
It works if:
- Typing
3ras one Send blinks red exactly 3 times. - Typing
3alone primes the count; later typinggblinks green 3 times. - Typing
ralone prints the "no count yet" warning and the LED stays off.
Reveal one valid sketch
// Digit-and-letter remote — type Nr or Ng to blink
const int RED_PIN = 9;
const int GREEN_PIN = 11;
int pendingCount = -1;
void blinkPin(int pin, int n) {
for (int i = 0; i < n; i = i + 1) {
digitalWrite(pin, HIGH);
delay(200);
digitalWrite(pin, LOW);
delay(200);
}
}
void setup() {
Serial.begin(9600);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
Serial.println("Type digit then r or g (e.g. 3r).");
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
if (isDigit(c)) {
pendingCount = c - '0';
Serial.print("Got count: ");
Serial.println(pendingCount);
}
else if ((c == 'r' || c == 'g') && pendingCount >= 0) {
int pin = (c == 'r') ? RED_PIN : GREEN_PIN;
blinkPin(pin, pendingCount);
Serial.print("Done ");
Serial.println(c);
pendingCount = -1;
}
else if (c == 'r' || c == 'g') {
Serial.println("No count yet — type a digit first");
}
else {
pendingCount = -1;
}
}
}This is the first sketch in the syllabus that holds state between characters — pendingCount remembers what you typed last so it can act on the next thing. That pattern (one variable, updated each character) is the seed of every multi-character parser, including the proper command-line shell you'll build in Level 2. The (c == 'r') ? RED_PIN : GREEN_PIN line uses a shortcut called the ternary operator — read it as "if c is 'r', use RED_PIN, else use GREEN_PIN".
Recap 5 min
Every char is secretly a small integer — its ASCII code. 'A' is 65, 'a' is 97, '0' is 48. Cast with (int)c to see the number; cast with (char)n to show the letter. The trap: '5' is the number 53, not 5 — subtract '0' to get the real digit value. Case-toggle by adding or subtracting 32. Use isDigit(c) and isAlpha(c) to test before you do arithmetic. This whole lesson is the foundation for L01-28's clean command parser.
- ASCII
- American Standard Code for Information Interchange — the 1960s table that maps every keyboard character to a number from 0 to 127. Used by every computer ever made since.
- Character code
- The integer that represents a character inside a computer's memory.
'A''s character code is 65. - Cast
- Telling the compiler "treat this value as a different type for one moment".
(int)ctreats a char as an int (so it prints as a number);(char)ntreats an int as a char (so it prints as a letter). - Digit conversion
- The pattern
c - '0'that turns a digit character into its numeric value. The most-used ASCII trick in everyday code. isDigit(c)- Built-in helper that returns true if
cis between'0'and'9'. Use as a guard beforec - '0'. isAlpha(c)- Built-in helper that returns true if
cis a letter — either uppercase or lowercase. - Case shift of 32
- The fact that every lowercase letter's code is exactly 32 more than its uppercase.
c + 32moves uppercase to lowercase;c - 32moves lowercase to uppercase.
Homework 5 min
The secret-code translator. Build a sketch that "encodes" any letter you type by shifting it forward in the alphabet by 1. Type a → reply b; type r → reply s; type z → reply a (wrapping back to the start). Same for uppercase: A → B, Z → A. Non-letters come back unchanged.
- Use the standard input pattern with
isAlpha(or the manual range tests) to decide if the char is a letter. - For each letter, compute its shifted version. The wrap (z→a, Z→A) needs an
ifcheck — or, if you've spotted it, the modulo trick((c - 'a' + 1) % 26) + 'a'. - Print only the encoded letter back, no labels — so the Monitor turns into a tiny cipher machine.
This is called a Caesar cipher, used by Julius Caesar to send messages two thousand years ago. Today you'll write one in twenty lines of Arduino.
Also: a design reflection on paper.
- Write down the ASCII codes for the characters in your own first name (just the letters, in order). Use the table from the lesson — no Monitor cheating. ____
- Why does the
+ 1shift need a wrap step at the end of the alphabet, but the simple case-toggle of L01-27 (just+ 32) doesn't need one? ____ (Hint: how much "room" is between Z and a in the ASCII table?) - Caesar's cipher shifts by 1. What if you wanted shift-by-3, or shift-by-13? What single number in your sketch would you change? ____
Bring back next class:
- The saved
.inofile (call itcaesar-cipher). - A phone photo of the Monitor showing yourself typing a short word like
helloand getting back its encoded version (which should beifmmp). - Your three written reflection answers, in your notebook.
Heads up for next class: L01-28 "switch / case" uses today's character-arithmetic ideas to clean up the long if/else if chains from L01-26. The same four-key remote will become half the lines, and adding new commands becomes a one-line change.