#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_adc/adc_oneshot.h"
// ---------------- Pin configuration -------------
#define LED_RED 13
#define LED_BLUE 8
#define ADC_CH_X ADC_CHANNEL_3 // Joystick X-axis - GPIO 4
#define ADC_CH_Y ADC_CHANNEL_4 // Joystick Y-axis - GPIO 5
// ---------------- Joystick thresholds ----------------
#define ADC_BITS 12
#define ADC_MAX ((1 << ADC_BITS) - 1) // 4095 for 12 bits
#define CENTER 2048 // joystick center (approx half)
#define DEADZONE 400 // ignore small variations
#define SAMPLES 8 // average this many samples
// ---------------- Zones ------------------------------
#define ZONE_NONE 0
#define ZONE_RED_1S 1
#define ZONE_RED_3S 2
#define ZONE_BLUE_1S 3
#define ZONE_BLUE_3S 4
// ADC1 handle (created in adc_oneshot_setup and used everywhere)
static adc_oneshot_unit_handle_t adc1_handle = NULL;
// ================== Function Declarations =====================
void init_output(gpio_num_t pin);
void leds_off();
void blink_led_period(gpio_num_t pin, int period_ms);
void blink_red_1s();
void blink_red_3s();
void blink_blue_1s();
void blink_blue_3s();
void adc_oneshot_setup();
int adc_read_avg(adc_channel_t ch, int samples);
int select_zone(int x, int y);
// ============================= Main ========================================
void app_main() {
init_output(LED_RED);
init_output(LED_BLUE);
leds_off();
adc_oneshot_setup();
while (1) {
int x = adc_read_avg(ADC_CH_X, SAMPLES);
int y = adc_read_avg(ADC_CH_Y, SAMPLES);
printf("x=%d y=%d\n", x, y);
int z = select_zone(x, y);
switch (z) {
case ZONE_RED_1S: blink_red_1s(); break;
case ZONE_RED_3S: blink_red_3s(); break;
case ZONE_BLUE_1S: blink_blue_1s(); break;
case ZONE_BLUE_3S: blink_blue_3s(); break;
default:
leds_off();
vTaskDelay(pdMS_TO_TICKS(60));
break;
}
}
}
// ========================= Function Definitions ============================
/*
Function name- init_output
Description- Configure a GPIO as output; disable pulls and interrupts for clean drive.
Parameters- pin (gpio_num_t): GPIO to configure
return type- none
*/
void init_output(gpio_num_t pin) {
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
gpio_pullup_dis(pin);
gpio_pulldown_dis(pin);
gpio_intr_disable(pin);
}
/*
Function name- leds_off
Description- Turn both LEDs off.
Parameters- none
return type- none
*/
void leds_off() {
gpio_set_level(LED_RED, 0);
gpio_set_level(LED_BLUE, 0);
}
/*
Function name- blink_led_period
Description- Blink a given GPIO with the specified period (ms), 50% duty.
Parameters- pin (gpio_num_t), period_ms (int)
return type- none
*/
void blink_led_period(gpio_num_t pin, int period_ms) {
gpio_set_level(pin, 1);
vTaskDelay(pdMS_TO_TICKS(period_ms / 2));
gpio_set_level(pin, 0);
vTaskDelay(pdMS_TO_TICKS(period_ms / 2));
}
/*
Function name- blink_red_1s
Description- Blink RED LED with 1 second period.
Parameters- none
return type- none
*/
void blink_red_1s() { blink_led_period(LED_RED, 1000); }
/*
Function name- blink_red_3s
Description- Blink RED LED with 3 second period.
Parameters- none
return type- none
*/
void blink_red_3s() { blink_led_period(LED_RED, 3000); }
/*
Function name- blink_blue_1s
Description- Blink BLUE LED with 1 second period.
Parameters- none
return type- none
*/
void blink_blue_1s() { blink_led_period(LED_BLUE, 1000); }
/*
Function name- blink_blue_3s
Description- Blink BLUE LED with 3 second period.
Parameters- none
return type- none
*/
void blink_blue_3s() { blink_led_period(LED_BLUE, 3000); }
/*
Function name- adc_oneshot_setup
Description- Create ADC1 oneshot unit and configure X/Y channels (12-bit, 11 dB).
Parameters- none
return type- none
*/
void adc_oneshot_setup() {
adc_oneshot_unit_init_cfg_t unit_cfg = {
.unit_id = ADC_UNIT_1
};
adc_oneshot_new_unit(&unit_cfg, &adc1_handle);
adc_oneshot_chan_cfg_t chan_cfg = {
.bitwidth = ADC_BITWIDTH_12,
.atten = ADC_ATTEN_DB_11
};
adc_oneshot_config_channel(adc1_handle, ADC_CH_X, &chan_cfg);
adc_oneshot_config_channel(adc1_handle, ADC_CH_Y, &chan_cfg);
}
/*
Function name- adc_read_avg
Description- Read 'samples' oneshot ADC values from channel 'ch' and return the average.
Parameters- ch (adc_channel_t), samples (int)
return type- int (average raw ADC count)
*/
int adc_read_avg(adc_channel_t ch, int samples) {
long sum = 0;
for (int i = 0; i < samples; ++i) {
int raw = 0;
adc_oneshot_read(adc1_handle, ch, &raw);
sum += raw;
vTaskDelay(pdMS_TO_TICKS(2)); // small pause for stability
}
return (int)(sum / samples);
}
/*
Function name- select_zone
Description- Map joystick X/Y positions to a zone label using center + deadzone.
Parameters- x (int), y (int)
return type- int (ZONE_* constant)
*/
int select_zone(int x, int y) {
//To have consistent output behavior on each axis, we map the actions of the LEDs to one axis for each colour. (JI - 2024-09-01)
if (x > CENTER + DEADZONE) return ZONE_BLUE_1S; // right
if (x < CENTER - DEADZONE) return ZONE_BLUE_3S; // left
if (y > CENTER + DEADZONE) return ZONE_RED_1S; // up
if (y < CENTER - DEADZONE) return ZONE_RED_3S; // down
return ZONE_NONE; // centered
}