// ============================================================================
// Semana 7 — ADC + PWM (RP2040) — Estilo Prof. Tomazati (otimizado e modular)
// Objetivo:
// • Slider (GP26 / ADC0) em 0..1023 com ZONA MORTA central:
// - Para a direita : aceleração (GP20) 0° → 180° proporcional
// - Para a esquerda : freios (GP19 e GP18) 0° → 180° proporcional (ambos)
// • Direção (GP27 / ADC1) em 0..1023 -> 0° → 180° proporcional (GP21)
// • PWM 50 Hz (TOP=62_500, CLKDIV=40) → 600..2400 µs nos servos (0..180°)
// Notas de projeto:
// • ADC do RP2040 é 12 bits (0..4095). Para trabalhar em 0..1023 (10 bits)
// • Todo cálculo usa apenas aritmética inteira (sem float) para eficiência.
// • Código estruturado em módulos: PINAGEM/CONSTS, INIT, UTIL, LÓGICA.
// ============================================================================
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/pwm.h"
#include "hardware/gpio.h"
#include <stdint.h>
// ---------------------------------------------------------------------------
// PINAGEM & CONSTANTES DE SISTEMA
// ---------------------------------------------------------------------------
// --- Saídas (servos) ---
#define PIN_SERVO_DIR 21 // GP21 -> direção
#define PIN_SERVO_ACEL 20 // GP20 -> aceleração
#define PIN_SERVO_FREIO_F 19 // GP19 -> freio dianteiro
#define PIN_SERVO_FREIO_T 18 // GP18 -> freio traseiro
// --- Entradas analógicas (potenciômetros) ---
#define ADC_SLIDER_PIN 26 // GP26 -> ADC0 (acel/freio)
#define ADC_DIR_PIN 27 // GP27 -> ADC1 (direção)
// --- ADC ---
#define ADC_CH_SLIDER 0 // canal físico para GP26
#define ADC_CH_DIR 1 // canal físico para GP27
// --- Escalas "10-bit" (coerente com 0..1023) ---
#define SLIDER_MIN 0
#define SLIDER_MAX 1023
#define SLIDER_MID 512
// --- Zona morta em torno do centro
#define DEAD_ZONE_10BIT 40 // em torno de 492 a 532
// --- Servo padrão 50 Hz (RP2040 @125 MHz; CLKDIV=40; TOP=62_500) ---
#define PWM_WRAP 62500u // TOP para período de 20 ms
#define CLKDIV_SERVO 40.0f // divisor de clock fracionário (usado na config do SDK)
// Range largura de pulso
#define SERVO_US_MIN 600u // equivalente a 0°
#define SERVO_US_MAX 2400u // equivalente a 180°
#define SERVO_US_SPAN (SERVO_US_MAX - SERVO_US_MIN) //Calcula a amplitude
//total da largura de pulso
//(1800 µs), usada para mapeamentos
//proporcionais.
// Inversões opcionais (1 = inverte sentido do servo)
#define INVERT_DIR 0
#define INVERT_ACEL 0
#define INVERT_FREIO 0
// ---------------------------------------------------------------------------
// UTILITÁRIOS — mapeamentos, conversões e helpers
// ---------------------------------------------------------------------------
/*
* Converte graus (0..180) em largura de pulso (microsegundos).
* 0° -> SERVO_US_MIN
* 180°-> SERVO_US_MAX
*/
static inline uint16_t graus_to_us(uint16_t graus) {
if (graus > 180) graus = 180;
return (uint16_t)(SERVO_US_MIN + ((uint32_t)SERVO_US_SPAN * (uint32_t)graus) / 180u);
}
/*
* Converte largura de pulso (microsegundos) para contagens de PWM (inteiro).
* Base de tempo: 125 MHz / 40 = 3.125 contagens/µs → counts = us * 3125 / 1000
*/
static inline uint16_t us_to_counts(uint16_t us) {
if (us < SERVO_US_MIN) us = SERVO_US_MIN;
if (us > SERVO_US_MAX) us = SERVO_US_MAX;
uint32_t counts = ((uint32_t)us * 3125u) / 1000u; // 1 µs ≈ 3.125 counts (inteiro)
if (counts > PWM_WRAP) counts = PWM_WRAP;
return (uint16_t)counts;
}
/*
* Inverte a largura de pulso (e, consequentemente, o sentido do servo)
se o flag de inversão for verdadeiro.
* O pulso invertido é calculado subtraindo o pulso atual (us)
da soma dos pulsos mínimo e máximo
*/
static inline uint16_t maybe_invert_us(uint16_t us, bool invert) {
if (!invert) return us; //us = pulso atual
const uint16_t sum = SERVO_US_MIN + SERVO_US_MAX; // 3000
return (sum > us) ? (sum - us) : us;
}
/*
*Realiza a leitura do ADC. O valor de 12 bits lido (adc_read())
é deslocado 2 bits à direita (>> 2), convertendo-o de 0..4095 para 0..1023 (10 bits),
conforme o projeto.
* RP2040 retorna 0..4095; para 0..1023 basta deslocar 2 bits à direita (>>2).
*/
static uint16_t adc_read10_avg(uint input_channel, int samples) {
adc_select_input(input_channel);
uint32_t acc = 0;
for (int i = 0; i < samples; ++i) {
acc += (adc_read() >> 2);
}
return (uint16_t)(acc / (uint32_t)samples); // 0..1023, média simples
//para redução de ruído
}
/*
Função para definir o duty cycle do PWM, primeiro convertendo a
largura de pulso desejada em µs para a contagem de PWM e
depois escrevendo este valor no canal específico.
* Grava nível de PWM correspondente ao pulso desejado (µs) no pino do servo.
*/
static inline void servo_write_us(uint pin, uint16_t us) {
const uint slice = pwm_gpio_to_slice_num(pin);
const uint chan = pwm_gpio_to_channel(pin);
pwm_set_chan_level(slice, chan, us_to_counts(us));
}
// ---------------------------------------------------------------------------
// INICIALIZAÇÃO — ADC e PWM
// ---------------------------------------------------------------------------
/*
* Configuração de um pino para funcionar como saída de SERVO (PWM 50 Hz).
* - Seta função PWM no pino
* - Configura divisor e TOP para 20 ms
* - Inicia em posição mínima (0°) para "parado"
*/
static void pwm_setup_servo(uint pin) {
gpio_set_function(pin, GPIO_FUNC_PWM); //Atribui a função PWM ao pino GPIO especificado.
const uint slice = pwm_gpio_to_slice_num(pin);
pwm_config cfg = pwm_get_default_config();
pwm_config_set_clkdiv(&cfg, CLKDIV_SERVO); // Configura o divisor de clock (40.0f) para o slice de PWM
pwm_config_set_wrap(&cfg, PWM_WRAP); //Configura o valor TOP (62500) para o período de 20 ms (50 Hz)
pwm_init(slice, &cfg, true); //Inicializa o slice (bloco) de PWM com as configurações.
servo_write_us(pin, SERVO_US_MIN); // Começa em 0° (pulso 600 microsegundos)
}
/*
* Inicialização geral: stdio, ADC (GPIO26/27), PWM dos 4 servos.
*/
static void sistema_init(void) {
stdio_init_all();
// Inicializa e configura o GPIO 26 (e 27) para ser uma entrada analógica
adc_init();
adc_gpio_init(ADC_SLIDER_PIN); // ADC0
adc_gpio_init(ADC_DIR_PIN); // ADC1
// Chama a função de configuração PWM para os quatro pinos de servo.
pwm_setup_servo(PIN_SERVO_DIR);
pwm_setup_servo(PIN_SERVO_ACEL);
pwm_setup_servo(PIN_SERVO_FREIO_F);
pwm_setup_servo(PIN_SERVO_FREIO_T);
}
// ---------------------------------------------------------------------------
// LÓGICA DE CONTROLE — algoritmo principal que traduz
//as leituras do potenciômetro para comandos de servo (largura de pulso em µs).
// ---------------------------------------------------------------------------
/*
* Atualiza servo de direção (GP21) proporcionalmente ao potenciômetro de direção:
* direção_10 = 0..1023 → graus = 0..180 → pulso (µs)
*/
static void atualiza_direcao(void) {
const uint16_t dir_10 = adc_read10_avg(ADC_CH_DIR, 8);// Lê o potenciômetro de direção
//(ADC1 ou GP27), com 8 amostras para média.
const uint16_t graus = (uint16_t)((uint32_t)dir_10 * 180u / SLIDER_MAX); //Mapeia o valor de 10 bits
//(0..1023) para um ângulo de 0..180 graus (mapeamento proporcional)
uint16_t us = graus_to_us(graus);
us = maybe_invert_us(us, INVERT_DIR);
servo_write_us(PIN_SERVO_DIR, us); // Escreve a largura de pulso final no
} //servo de direção, após a conversão e possível inversão de sentido
/*
Calcula os limites esquerdo (left_edge)
e direito (right_edge) da Zona Morta.
*/
static void atualiza_acel_e_freios(void) {
const uint16_t sld_10 = adc_read10_avg(ADC_CH_SLIDER, 8); // 0..1023
const uint16_t left_edge = SLIDER_MID - DEAD_ZONE_10BIT;
const uint16_t right_edge = SLIDER_MID + DEAD_ZONE_10BIT;
/*
Se o potenciômetro estiver dentro da ZM, todos os servos
(aceleração e freios) são definidos para o pulso mínimo
(SERVO_US_MIN, ou 0°), garantindo que o veículo não se mova.
*/
if (sld_10 >= left_edge && sld_10 <= right_edge) {
// ZONA MORTA — tudo parado (0°)
servo_write_us(PIN_SERVO_ACEL, SERVO_US_MIN);
servo_write_us(PIN_SERVO_FREIO_F, SERVO_US_MIN);
servo_write_us(PIN_SERVO_FREIO_T, SERVO_US_MIN);
}
/*
Aceleração (Direita). O potenciômetro está na faixa de aceleração.
*/
else if (sld_10 > right_edge) {
const uint16_t span = (SLIDER_MAX - right_edge);// Calcula o alcance útil para aceleração
const uint16_t delta = (sld_10 - right_edge); // Calcula a posição relativa dentro deste alcance útil
const uint16_t graus = (uint16_t)((uint32_t)delta * 180u / span);//Mapeia o valor relativo
//(delta) para o ângulo de aceleração (0..180°).
uint16_t us_ac = graus_to_us(graus);
us_ac = maybe_invert_us(us_ac, INVERT_ACEL);
servo_write_us(PIN_SERVO_ACEL, us_ac); //Atualiza o servo de aceleração
servo_write_us(PIN_SERVO_FREIO_F, SERVO_US_MIN); //Servo freio F fica em 0°
servo_write_us(PIN_SERVO_FREIO_T, SERVO_US_MIN); //Servo freio T fica em 0°
}
else { // sld_10 < left_edge → ESQUERDA — freios proporcionais 0°..180°
const uint16_t span = left_edge; // Calcula o alcance útil para frenagem
const uint16_t distance = (left_edge - sld_10); // Calcula a "distância" percorrida na faixa de freio
const uint16_t graus = (uint16_t)((uint32_t)distance * 180u / span);//Mapeia o valor de distância
//para o ângulo de freio (0..180°)
uint16_t us_fr = graus_to_us(graus);
us_fr = maybe_invert_us(us_fr, INVERT_FREIO);
//Atualiza ambos os servos de freio (FREIO_F e FREIO_T) com o mesmo pulso.
servo_write_us(PIN_SERVO_ACEL, SERVO_US_MIN);//servo de aceleração é mantido em 0°
servo_write_us(PIN_SERVO_FREIO_F, us_fr);
servo_write_us(PIN_SERVO_FREIO_T, us_fr);
}
}
// ---------------------------------------------------------------------------
//APLICAÇÃO — laço principal
// ---------------------------------------------------------------------------
int main(void) {
sistema_init();
// Aguarda 200 ms para os servos se posicionarem na
//posição inicial (0°) e o sistema se estabilizar.
sleep_ms(200);
while (true) {
atualiza_direcao();//Executa a leitura do potenciômetro de direção e o
//controle do servo de direção.
atualiza_acel_e_freios();//Executa a lógica complexa de aceleração/freios com a Zona Morta
sleep_ms(10); //Introduz um atraso de 10 milissegundos,
//limitando a taxa de atualização do sistema para aproximadamente 100 Hz.
}