These five peripherals are the backbone of embedded work. Here’s a practical, no-nonsense cheat sheet with concepts, setup steps, and tiny code examples ([Arduino](https://www.ampheo.com/c/development-board-arduino) & [STM32](https://www.ampheo.com/search/STM32) HAL) you can drop into a project.

**Big picture**
* ADC: converts analog voltage → digital number
* DAC: converts digital number → analog voltage/current
* PWM: digital pin toggled with a duty cycle to emulate analog power
* Timers: hardware counters for timebases, PWM, captures, periodic interrupts
* UART: async serial byte stream (TX/RX) for logs, modules, PCs
**ADC (Analog-to-Digital Converter)**
Key params: resolution (n bits), reference (Vref), sampling time, input impedance, rate
LSB size: LSB = Vref / 2^n (e.g., 12-bit @3.3V → 0.805 mV)
**Typical setup**
1. Select channel/pin, set it as analog input (no pull-ups).
2. Pick Vref (internal/external).
3. Configure sample time to allow the pin to settle (depends on source impedance).
4. Choose single/continuous/scan, optionally enable DMA.
5. Start conversion, read result (poll/interrupt/DMA).
**Arduino example (UNO, 10-bit, A0)**
```
cpp
void setup(){ Serial.begin(115200); }
void loop(){
int raw = analogRead(A0); // 0..1023
float v = raw * (5.0 / 1023.0); // Vref = 5V
Serial.println(v, 3);
delay(10);
}
```
**STM32 HAL (single channel, polling)**
```
c
ADC_HandleTypeDef hadc1;
/* CubeMX: configure ADC1 regular channel + sampling time */
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10);
uint32_t raw = HAL_ADC_GetValue(&hadc1);
float v = (raw * 3.3f) / 4095.0f; // 12-bit, Vref=3.3V
```
**Gotchas & tips**
* Buffer high-impedance sensors with an op-amp or increase sample time.
* Keep analog ground/trace short; add 100 nF near Vref and sensor.
* For noise: oversample/average, or use DMA + moving average.
**DAC (Digital-to-Analog Converter)**
Key params: resolution, output buffer, settling time, output range (often 0..Vref)
Use cases: audio tones, bias voltages, reference levels, slow waveforms.
**STM32 HAL (write a value)**
```
c
HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
uint32_t code = 2048; // midscale for 12-bit
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, code);
```
Arduino “DAC” note: many Arduinos lack true DAC; use analogWrite() (PWM) + RC [filter](https://www.onzuu.com/category/filters), or boards with DAC (e.g., [Arduino Due](https://www.ampheo.com/product/a000062-25542566), [Nano 33 BLE Sense](https://www.ampheo.com/product/abx00031-25542435), Teensy).
Tip: For smooth waveforms, feed DAC by DMA from a lookup table, triggered by a timer.
**PWM (Pulse-Width Modulation)**
Concept: Digital pin toggles at frequency fPWM with duty cycle D%. Average voltage ≈ D · Vcc (after filtering or for power control).
**Frequency calculation (general timer):**
fPWM = f_timer_clk / (Prescaler + 1) / (ARR + 1)
Duty (edge-aligned): CCR / ARR
**Arduino**
```
cpp
// Basic: analogWrite(pin, 0..255) at fixed freq (board dependent)
analogWrite(9, 128); // ~50% duty
```
**STM32 HAL (Timer PWM)**
```
c
// Assume TIM3 CH1 on a PWM-capable pin
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
__HAL_TIM_SET_AUTORELOAD(&htim3, 999); // ARR
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 300); // CCR -> 30% duty
```
**Tips**
* For motors/LEDs, choose kHz to tens of kHz to avoid audible flicker/whine.
* Use hardware PWM (timer channels), not bit-banging.
* For dimming LEDs linearly to the eye, apply gamma correction in software.
**Timers**
Modes: up/down counting, PWM generation, input capture (timestamp edges), output compare (toggle/interrupt), encoder mode, one-pulse.
**Create a periodic interrupt**
**Arduino**
```
cpp
// Use built-in millis()/micros() or libraries (TimerOne, etc.)
static unsigned long t=0;
void loop(){
if (millis() - t >= 1000) { t += 1000; /* do 1 Hz task */ }
}
```
**STM32 HAL (1 kHz tick using TIMx + IRQ)**
```
c
HAL_TIM_Base_Start_IT(&htim2);
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim->Instance == TIM2){ /* 1 kHz job */ }
}
```
**Input capture (period/frequency measurement)**
Configure channel in capture mode; on each edge, read CCRx; delta counts → period.
**Tips**
* Derive all periodic tasks from timers (not delay()), especially for control loops.
* Check clock tree: APB prescalers affect timer clocks (sometimes ×2 on STM32).
**UART (Serial)**
Params: baud, data bits, parity, stop bits (e.g., 115200 8N1).
Flow control: none/RTS/CTS; use it for high baud rates or long bursts.
**Arduino**
```
cpp
void setup(){ Serial.begin(115200); }
void loop(){
if (Serial.available()) {
int b = Serial.read();
Serial.write(b); // echo
}
}
```
**STM32 HAL (blocking & DMA)**
```
c
uint8_t msg[] = "Hello\r\n";
HAL_UART_Transmit(&huart2, msg, sizeof msg - 1, 50);
uint8_t rxbuf[64];
HAL_UART_Receive_DMA(&huart2, rxbuf, sizeof rxbuf);
// In callback HAL_UART_RxCpltCallback or use IDLE-line detection to parse frames
```
**Tips**
* Start with conservative baud (9600/115200), then raise.
* For binary protocols, add framing (start byte, length, CRC) or use SLIP/CBOR/CBUS.
* Enable DMA or ring buffers for continuous streams.
**Common patterns that combine peripherals**
* ADC via DMA + Timer trigger: fixed-rate sampling without CPU jitter.
* DAC via DMA + Timer: waveform generation (sine, chirp).
* PWM + Timer interrupt: closed-loop motor/LED control.
* UART + DMA: high-throughput logging or module control.
**Quick troubleshooting checklist**
* Wrong pin alt-function? (GPIO vs AF for timers/UART)
* Clock not enabled for peripheral/bus? (RCC on [STM32](https://www.ampheoelec.de/search/STM32))
* Interrupt not enabled/priorities wrong?
* Reference voltage or grounds missing for ADC/DAC?
* For PWM: ARR/PSC combo yields wrong frequency?
* UART cable cross: TX↔RX, and common ground present?