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. ![STM32-ADC-Tutorial-Explained](https://hackmd.io/_uploads/rkibF0tugx.jpg) **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?