Chapter 2: Number Systems

September 17, 2026 · View on GitHub

Introduction

Every value in our firmware — addresses, register contents, bit masks, constants — is stored as a pattern of ones and zeros. This chapter teaches the three number systems you will encounter on every page of this tutorial: decimal, binary, and hexadecimal. You will learn to convert between them, understand bit numbering, and recognize the specific constants used in our blink driver.

Decimal — Base 10

Decimal is the number system humans use every day. It has ten digits: 0 through 9. Each position represents a power of 10:

$ 4 2 3 | | | | | +-- 3 \times $10^{0}$ = 3 | +------ 2 \times $10^{1}$ = 20 +---------- 4 \times $10^{2}$ = 400 ---- 423 $

Decimal appears in our firmware for delay values, GPIO pin numbers, and loop counts.

Binary — Base 2

Binary has two digits: 0 and 1. Each digit is called a bit. Each position represents a power of 2:

$ 1 1 0 1 | | | | | | | +-- 1 \times $2^{0}$ = 1 | | +------ 0 \times $2^{1}$ = 0 | +---------- 1 \times $2^{2}$ = 4 +-------------- 1 \times $2^{3}$ = 8 -- 13 (\text{decimal}) $

The processor operates entirely in binary. Every register is 32 bits wide — 32 individual ones and zeros.

In GNU assembly, binary literals use the 0b prefix:

  .hword 0x1101                                  # EXE + RISCV + RP2350

Hexadecimal — Base 16

Hexadecimal (hex) has sixteen digits: 0–9 and A–F (where A=10, B=11, C=12, D=13, E=14, F=15). Each hex digit represents exactly four bits:

HexBinaryDecimal
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
A101010
B101111
C110012
D110113
E111014
F111115

This makes hex the preferred notation for memory addresses and register values because each hex digit maps directly to four bits.

The 0x Prefix

In assembly and C, hexadecimal numbers are written with a 0x prefix:

0x40028000 = 0100 0000 0000 0010 1000 0000 0000 0000 (binary)

Every memory-mapped address in our firmware is written in hex:

  .equ XOSC_BASE,  0x40048000                    # crystal oscillator base
  .equ RESETS_BASE, 0x40020000                   # reset controller base

Bit Numbering

Bits in a 32-bit register are numbered 0 (least significant, rightmost) to 31 (most significant, leftmost):

Bit:  31 30 29 28 ... 3  2  1  0
       |  |  |  |      |  |  |  |
MSB ---+  |  |  |      |  |  |  +--- LSB
          |  |  |      |  |  |
          v  v  v      v  v  v

When we write (1<<6), we mean a 32-bit value with only bit 6 set:

0000 0000 0000 0000 0000 0000 0100 0000 = 0x00000040

This notation appears throughout our firmware for setting and clearing individual hardware control bits.

Common Bit Patterns in Our Firmware

PatternHexBinary (relevant bits)Used For
1<<60x00000040bit 6 setIO_BANK0 reset bit
1<<70x00000080bit 7 setOD (output disable) pad bit
1<<80x00000100bit 8 setISO (isolation) pad bit
1<<110x00000800bit 11 setCLK_PERI enable bit
1<<310x80000000bit 31 setXOSC STABLE status bit
0x1f0x0000001Fbits 4:0 setFUNCSEL mask (5 bits)
0x050x00000005bits 2,0 setFUNCSEL = SIO (GPIO function)

Two's Complement — Signed Numbers

RISC-V uses two's complement for signed integers. In a 32-bit register:

  • Bit 31 is the sign bit: 0 = positive, 1 = negative.
  • To negate a number: invert all bits and add 1.
DecimalBinary (8-bit example)
+50000 0101
-51111 1011
+1270111 1111
-1281000 0000

The bgez instruction in our XOSC polling loop depends on two's complement: when bit 31 (the STABLE bit) is set, the signed interpretation is negative, so bgez (branch if greater than or equal to zero) does not branch — meaning "stable."

Data Sizes on RISC-V RV32

NameSizeRISC-V Load/Store
Byte8 bitslb / sb
Halfword16 bitslh / sh
Word32 bitslw / sw

Our firmware uses word (32-bit) access for all peripheral registers because the RP2350's memory-mapped registers are 32 bits wide.

Summary

  • Decimal, binary, and hexadecimal are the three number systems used in firmware programming.
  • Every hex digit maps to exactly four bits.
  • Bit numbering starts at 0 (LSB) and increases leftward to 31 (MSB).
  • The shift expression (1<<n) creates a mask with only bit n set.
  • Two's complement represents signed numbers; bit 31 is the sign bit.
  • RISC-V uses word (32-bit), halfword (16-bit), and byte (8-bit) data sizes.