// Incluindo bibliotecas necessárias.
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "pico/time.h"
#include "hardware/pwm.h"
#include "hardware/adc.h"
// Definindo pinos que vamos usar
#define SERVO_PIN 8
#define ANALOG_X_PIN 27
#define BUZZER_B_PIN 10
#define BUZZER_A_PIN 21
#define BUTTON_B_PIN 6
#define BUTTON_A_PIN 5
#define DISPLAY_SDA 14
#define DISPLAY_SCL 15
/*
* Inicializando variáveis globais
*
* wrap_servo: Valor do wrap para o PWM do servo motor.
*
* wrap_buzzer: Valor do wrap para o PWM dos buzzers.
*
* slice_buzzer: Variável declarada globalmente para nos permitir ativar e
* desativar os buzzers no fim de curso.
*
* analog_finish: Flag que indica quando a próxima amostra de nosso ADC está
* pronta.
*
* print_ready: Flag que indica quando devemos printar informações de nosso
* interesse na porta USB.
*
* mute_buzzer: Flag para mutar os buzzers insuportáveis.
*
* change_unit: Variável para mudar as unidades mostradas no display.
*
* Algumas variáveis são inicializadas com o valor "0" para evitar pontos
* flutuantes.
*/
uint16_t wrap_servo = 65535;
uint8_t wrap_buzzer = 255;
uint slice_buzzer_a;
uint slice_buzzer_b;
volatile bool analog_finish = false;
volatile bool print_ready = false;
volatile bool mute_buzzer = false;
volatile int change_unit = 0;
static volatile absolute_time_t last_request_time;
/*
* Inicializando funções que precisamos
*/
void start();
bool analog_finish_callback(struct repeating_timer *t);
bool print_ready_callback(struct repeating_timer *t);
void button_callback(uint gpio, uint32_t events);
// FUNÇÃO MAIN
int main()
{
stdio_init_all(); // Ativando comunicação USB com o computador.
start(); // Inicializando pinos da placa.
/*
* Inicializando variáveis locais
*
* analog_read: Valor da leitura do ADC de 12 bits (ou seja, ira armazenar
* valores de 0 a 4095), que ira realizar a leitura da posição de nosso
* analógico (também chamado de joystick).
*
* compensation: Variável que serve para "deslocar" o início da leitura de
* nosso sensor analógico. Como o 0º do motor é com 2,5% de duty cicle,
* devemos compensar esses 2,5% de duty cicle do resultado da leitura do ADC.
* Sem isso, quando colocamos o analógico para esquerda, antes de o analógico
* chegar no seu fim, o servo motor já estaria no 0º.
*
* scale: Variável que serve para "alongar" a nossa faixa de valores do ADC.
* Seu objetivo é consegui colocar a faixa de valores de 0 a 4095 dentro da
* faixa de 2,5% a 12,5% de duty cicle para nosso servo motor. Isso permite
* que o analógico consiga representar com um nível satisfatório de precisão
* a posição do servo motor em 0º quando ele está totalmente para a esquerda,
* e 180º quando ele está totalmente para a direita.
*
* conversion: Variável que faz todas as operações necessárias na leitura do
* analógico, fazendo a compensação da posição inicial e o escalonamento da
* faixa de valores.
*
* duty_cicle: Variável que representa o duty cicle de nosso PWM de 50 Hz.
* Para que o servo motor fique em 0º, precisamos de um duty cicle de 2,5%,
* e para que fique em 180º, precisamos de 12,5% de duty cicle.
*
* Algumas variáveis são inicializadas com o valor "0" para evitar pontos
* flutuantes.
*/
uint16_t analog_read = 0;
uint16_t compensation = 1638;
float scale = 1.6;
float conversion = 0;
float duty_cicle = 0;
// Contador que nos informa quando a amostra de nosso ADC está pronta.
repeating_timer_t analog_timer;
add_repeating_timer_ms(10, analog_finish_callback, NULL, &analog_timer);
// Contador que nos informa quando devemos printar algo na porta serial.
repeating_timer_t print_timer;
add_repeating_timer_ms(1000, print_ready_callback, NULL, &print_timer);
while (true) {
if(analog_finish == true) {
// Realizando leitura do ADC e fazendo correções.
analog_read = adc_read();
conversion = (analog_read*scale)+compensation;
duty_cicle = (100.0*conversion/65535.0);
// Configurando uma zona morta para nosso analógico.
if(conversion > 4800 && conversion < 5100) {
pwm_set_gpio_level(SERVO_PIN, 4916);
} else if(conversion < 1700) {
pwm_set_gpio_level(SERVO_PIN, 1638);
if(mute_buzzer == false)
pwm_set_enabled(slice_buzzer_b, true);
} else if(conversion > 8100) {
pwm_set_gpio_level(SERVO_PIN, 8192);
if(mute_buzzer == false)
pwm_set_enabled(slice_buzzer_a, true);
} else {
pwm_set_gpio_level(SERVO_PIN, conversion);
pwm_set_enabled(slice_buzzer_a, false);
pwm_set_enabled(slice_buzzer_b, false);
}
analog_finish = false;
}
if(print_ready == true) {
/*
* O valor da leitura real ainda será printado na porta serial, mas
* o motor ira receber a filtragem da zona morta.
*/
printf("Nível do PWM: %.3f%%\nLeitura analógica: %.0f\n",
duty_cicle, conversion);
print_ready = false;
}
sleep_ms(10);
}
}
/*
* Função para inicializar todos os pinos e configurações do Raspberry Pi
* Pico que precisamos antes do main.
*/
void start()
{
// Coletando os slices dos pinos de PWM.
uint slice_servo = pwm_gpio_to_slice_num(SERVO_PIN);
slice_buzzer_a = pwm_gpio_to_slice_num(BUZZER_A_PIN);
slice_buzzer_b = pwm_gpio_to_slice_num(BUZZER_B_PIN);
// Configurando os pinos para PWM.
gpio_set_function(SERVO_PIN, GPIO_FUNC_PWM);
gpio_set_function(BUZZER_A_PIN, GPIO_FUNC_PWM);
gpio_set_function(BUZZER_B_PIN, GPIO_FUNC_PWM);
// Ajustando o divisor e wrap dos PWMs.
pwm_set_clkdiv(slice_servo, 38.15);
pwm_set_clkdiv(slice_buzzer_a, 255.9375);
pwm_set_clkdiv(slice_buzzer_b, 255.9375);
pwm_set_wrap(slice_servo, wrap_servo);
pwm_set_wrap(slice_buzzer_a, wrap_buzzer);
pwm_set_wrap(slice_buzzer_b, wrap_buzzer);
// Inicializando o PWM com 0% de ciclo de trabalho.
pwm_set_gpio_level(SERVO_PIN, 0);
pwm_set_enabled(slice_servo, true);
// Os buzzers só vão ativar quando solicitado no código.
pwm_set_gpio_level(BUZZER_A_PIN, 64);
pwm_set_enabled(slice_buzzer_a, false);
pwm_set_gpio_level(BUZZER_B_PIN, 64);
pwm_set_enabled(slice_buzzer_b, false);
// Inicializando ADC do joystick.
adc_init();
adc_gpio_init(ANALOG_X_PIN);
adc_select_input(1);
// Configurando pinos de entrada.
gpio_init(BUTTON_A_PIN);
gpio_set_dir(BUTTON_A_PIN, GPIO_IN);
gpio_pull_up(BUTTON_A_PIN);
gpio_init(BUTTON_B_PIN);
gpio_set_dir(BUTTON_B_PIN, GPIO_IN);
gpio_pull_up(BUTTON_B_PIN);
gpio_set_irq_enabled_with_callback(
BUTTON_A_PIN,
GPIO_IRQ_EDGE_FALL,
true,
&button_callback
);
gpio_set_irq_enabled(
BUTTON_B_PIN,
GPIO_IRQ_EDGE_FALL,
true
);
}
/*
* Rotina de tratamento de interrupção que nos avisa quando a amostra de
* nosso ADC está pronta (a cada 10ms).
*/
bool analog_finish_callback(struct repeating_timer *t)
{
analog_finish = true; // flag para ser tratada em nosso main.
return true; // Retorna verdadeiro para que o timer continue depois.
}
/*
* Rotina de tratamento de interrupção que evita com que seja mandado
* uma quantia enorme de mensagens na porta serial (USB) (a cada 1s).
*/
bool print_ready_callback(struct repeating_timer *t)
{
print_ready = true; // flag para ser tratada em nosso main.
return true; // Retorna verdadeiro para que o timer continue depois.
}
void button_callback(uint gpio, uint32_t events) {
absolute_time_t now = get_absolute_time();
if(gpio == BUTTON_B_PIN) {
if (absolute_time_diff_us(last_request_time, now) > 50000) {
last_request_time = now;
mute_buzzer = !mute_buzzer;
}
} else if(gpio == BUTTON_A_PIN) {
if (absolute_time_diff_us(last_request_time, now) > 50000) {
last_request_time = now;
mute_buzzer = !mute_buzzer;
}
}
}