Try   HackMD

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 & STM32 HAL) you can drop into a project.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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, or boards with DAC (e.g., Arduino Due, Nano 33 BLE Sense, 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)
  • 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?