Wireless and Mobile Communication Tutorial from Scratch (2026)
Wireless communication has transformed how we connect, enabling mobile phones, Wi-Fi, Bluetooth, IoT sensors, and satellite communications. This tutorial explores the physical layer fundamentals of radio transmission, the cellular system architecture from 2G to 5G/6G, and the protocols that make mobile networking possible. From a startup, I built a LoRaWAN-based IoT system and learned the hard realities of path loss, interference, and battery optimization.
We will simulate OFDM modulation, analyze MIMO spatial multiplexing, and model a cellular network in Python.
Radio Propagation and Path Loss Models
Signal propagation suffers from path loss, shadowing, and multipath fading. The free-space path loss model: PL = 20*log10(d) + 20*log10(f) + 32.44 (dB). The Okumura-Hata model is widely used for urban cellular planning. Rayleigh fading models non-line-of-sight environments; Ricean fading includes a dominant LOS path.
import math, random
def fspl(d_km, f_mhz): return 20*math.log10(d_km) + 20*math.log10(f_mhz) + 32.44
def okumura_hata(d_km, f_mhz, hb=50, hm=2, urban=True):
ahm = 3.2*(math.log10(11.75*hm))**2 - 4.97 if urban else 0
L = 69.55 + 26.16*math.log10(f_mhz) - 13.82*math.log10(hb) - ahm
L += (44.9 - 6.55*math.log10(hb))*math.log10(d_km)
return L
class Rayleigh:
def __init__(self,sig=1): self.sig=sig
def samp(self):
x=random.gauss(0,self.sig); y=random.gauss(0,self.sig)
return math.sqrt(x*x+y*y)
OFDM and 4G/5G Physical Layer
Orthogonal Frequency Division Multiplexing divides the spectrum into many orthogonal subcarriers. Each subcarrier carries a low-rate data stream, making OFDM robust to multipath. Cyclic prefix insertion eliminates inter-symbol interference. In 5G NR (New Radio), subcarrier spacing is scalable (15, 30, 60, 120 kHz). Resource blocks are 12 subcarriers x 14 OFDM symbols.
import numpy as np
def ofdm_sym(data, nfft=64, cp_len=16):
N=nfft; mod=16
pilot = [1 if i%3==0 else 0 for i in range(N)]
sym = np.zeros(N, dtype=complex)
sym[1:N//2] = data[:N//2-1]; sym[N//2+1:] = data[N//2-1:][::-1]
sym[np.array(pilot,bool)] = 1+0j
t = np.fft.ifft(np.fft.ifftshift(sym), N)
cp = t[-cp_len:]
return np.concatenate([cp, t])
np.random.seed(42); data=(np.random.rand(31)*2-1)+(np.random.rand(31)*2-1)*1j
s=ofdm_sym(data); print(f'OFDM: {len(s)} samples, CP={16//64*100}% overhead, P={np.max(np.abs(s)):.2f}')
MIMO and Spatial Multiplexing
MIMO uses multiple antennas at transmitter and receiver to improve throughput (spatial multiplexing) or reliability (diversity). In spatial multiplexing, independent data streams are transmitted from each antenna and separated by the receiver using channel state information. The MIMO channel capacity scales as min(Nt, Nr) * log2(1+SNR) for high SNR.
import numpy as np
class MIMO:
def __init__(self, Nt, Nr): self.Nt=Nt; self.Nr=Nr
def channel(self): return (np.random.randn(self.Nr,self.Nt)+1j*np.random.randn(self.Nr,self.Nt))/np.sqrt(2)
def capacity(self, H, snr=20):
snr_lin = 10**(snr/10); I = np.eye(self.Nt)
_,S,_ = np.linalg.svd(H)
# water-filling
return np.sum(np.log2(1 + snr_lin/self.Nt * S**2))
def zf_detector(self, H, y):
W = np.linalg.pinv(H)
return W @ y
Nt=4; Nr=4; mimo=MIMO(Nt,Nr); H=mimo.channel(); C=mimo.capacity(H,20)
print(f'4x4 MIMO capacity at 20dB: {C:.2f} bps/Hz')
Cellular Networks: 2G to 5G Evolution
2G brought digital voice (GSM) with TDMA/FDMA. 3G (UMTS) introduced WCDMA and packet-switched data. 4G (LTE) is all-IP with OFDMA. 5G NR adds mmWave, massive MIMO, network slicing, and ultra-reliable low-latency communications (URLLC). The core evolved from circuit-switched (2G) through EPC (4G) to the Service-Based Architecture (5G).
class gNB: # 5G base station
def __init__(self, cell_id, freq, bw):
self.id=cell_id; self.freq=freq; self.bw=bw; self.ues={}
self.scs = 30*(bw//100) # 30 kHz subcarrier spacing
def attach(self, ue):
ue.cell=self; ue.state='CONNECTED'; self.ues[ue.ueid]=ue
print(f'UE {ue.ueid} attached to gNB {self.id}')
def schedule(self, ue, bytes):
prb = bytes//(self.scs*12*168//1000) # rough PRB estimate
print(f'Scheduled UE {ue.ueid}: {prb} PRBs')
class UE:
def __init__(self, ueid, cat): self.ueid=ueid; self.cat=cat; self.cell=None; self.state='IDLE'
def send(self, data):
if self.cell: self.cell.schedule(self, len(data))
Wi-Fi: IEEE 802.11 Standards
Wi-Fi operates in unlicensed 2.4 GHz and 5 GHz bands. CSMA/CA with RTS/CTS avoids collisions. 802.11ax (Wi-Fi 6) adds OFDMA and MU-MIMO. 802.11be (Wi-Fi 7) introduces 320 MHz channels, 4096-QAM, and multi-link operation. The DCF uses exponential backoff: each station picks a random backoff counter from [0, CW].
import random
class WiFiStation:
def __init__(self, addr): self.addr=addr; self.cw=15; self.bc=0; self.state='IDLE'
def sence_busy(self): return random.random()<0.3
def backoff(self): self.bc=random.randint(0,self.cw); self.state='BACKOFF'
def tx(self):
if self.state=='BACKOFF':
if self.bc>0: self.bc-=1; return None
if self.sence_busy(): self.cw=min(self.cw*2+1,1023); self.backoff(); return None
self.cw=15; self.state='IDLE'; return b'data'
class AP:
def __init__(self): self.stas={}
def assoc(self, sta): self.stas[sta.addr]=sta
def dcf_tx(self):
for sta in self.stas.values():
pkt=sta.tx()
if pkt: print(f'STA {sta.addr} TX {pkt}')
IoT Protocols: LoRaWAN and NB-IoT
IoT connectivity requires low power, long range, and low cost. LoRaWAN uses chirp spread spectrum in unlicensed ISM bands with star-of-stars topology. Class A (lowest power) opens receive windows after uplink. NB-IoT is a 3GPP licensed-band standard using 200 kHz bandwidth with deep indoor penetration. Both support thousands of devices per base station.
import hashlib, time
class LoRaWAN:
def __init__(self, deveui, appkey):
self.deveui=deveui; self.appkey=appkey; self.nwkkey=None; self.appskey=None
self.fcnt=0; self.adr=True; self.datarate='SF12_125'
def join(self):
join_req = bytes.fromhex(self.deveui)+bytes.fromhex(self.appkey[:16])
self.nwkkey=hashlib.aes.new(self.appkey).encrypt(join_req[:16])
self.appskey=hashlib.aes.new(self.appkey).encrypt(join_req[16:])
return True
def send(self, port, data):
self.fcnt+=1; fhdr=bytes([self.deveui[0]])+self.fcnt.to_bytes(2,'little')
payload=fhdr+bytes([port])+data
mic=hashlib.aes.new(self.nwkkey,hashlib.MODE_CMAC,payload).digest()[:4]
return payload+mic
class NBIoT:
def __init__(self, imsi): self.imsi=imsi; self.pwr=23; self.nprb=1
def coverage_enh(self, lvl=1): return {'rep':16 if lvl==1 else 64, 'prb':self.nprb}
def pdu(self, data): return b'\x00'+len(data).to_bytes(2,'big')+data
Frequently Asked Questions
What is the difference between FDMA, TDMA, and CDMA?
FDMA divides frequency bands; TDMA divides time slots; CDMA uses orthogonal codes to share the same frequency and time. OFDMA combines FDMA and TDMA with orthogonal subcarriers.
What is path loss?
Path loss is the reduction in power density as a signal propagates through space. It depends on distance, frequency, antenna gains, and obstacles. The free-space model predicts ideal loss.
How does MIMO improve throughput?
Spatial multiplexing sends independent data streams over multiple antennas. With N antennas, throughput can increase N-fold. Receive diversity uses multiple antennas to combat fading.
What is the difference between LoRaWAN and NB-IoT?
LoRaWAN uses unlicensed spectrum with lower data rates but longer battery life (10+ years) and lower cost. NB-IoT uses licensed spectrum with better QoS, higher data rates, but higher power.
Originally published on Ayodhyyya. Last updated June 1, 2026.