#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "cJSON.h"
#include "esp_timer.h"
// Hardware connection definitions
// GPIO 2 -----> LED1 ----> GND
// GPIO 5 -----> LED2 ----> GND
// GPIO 0 -----> Button1 ----> GND
// GPIO 4 -----> Button2 ----> GND
// UART0 -------> Serial Monitor (Simulated)
// --- Define GPIOs for LEDs and Buttons ---
#define LED1_GPIO_PIN GPIO_NUM_2
#define LED2_GPIO_PIN GPIO_NUM_5
#define BTN1_GPIO_PIN GPIO_NUM_0
#define BTN2_GPIO_PIN GPIO_NUM_4
// --- UART and JSON configuration ---
#define UART_PORT UART_NUM_0
#define UART_BAUD_RATE 115200
#define BUF_SIZE (1024)
// --- PWM configuration ---
#define LEDC_TIMER LEDC_TIMER_0
#define LEDC_MODE LEDC_LOW_SPEED_MODE
#define LEDC_DUTY_RESOLUTION LEDC_TIMER_13_BIT // 8192 levels
#define LEDC_DUTY 4096 // 50% duty cycle
#define MAX_FREQUENCY 1000 // New define to cap max frequency
// --- Debounce time for buttons ---
#define DEBOUNCE_TIME_MS 20
// --- Auto-stop time in milliseconds ---
#define AUTO_STOP_TIME_MS 10000
// --- Structure for an LED command ---
typedef enum {
CMD_START,
CMD_STOP,
CMD_TOGGLE
} command_t;
typedef struct {
command_t command;
int frequency; // Used only for CMD_START
} led_command_t;
// --- LED State Structure ---
typedef struct {
gpio_num_t gpio_pin;
ledc_channel_t ledc_channel;
ledc_timer_t ledc_timer;
int last_freq;
bool is_on;
esp_timer_handle_t auto_stop_timer;
QueueHandle_t command_queue;
} led_state_t;
static const char *TAG = "MAIN";
static led_state_t led1_state;
static led_state_t led2_state;
// --- Function to initialize the LEDC PWM for a specific LED ---
static void pwm_init(led_state_t *led_state) {
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE,
.duty_resolution = LEDC_DUTY_RESOLUTION,
.timer_num = led_state->ledc_timer,
.freq_hz = 100, // Initial frequency, will be updated
.clk_cfg = LEDC_AUTO_CLK,
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
ledc_channel_config_t ledc_channel = {
.gpio_num = led_state->gpio_pin,
.speed_mode = LEDC_MODE,
.channel = led_state->ledc_channel,
.timer_sel = led_state->ledc_timer,
.duty = 0, // Initially off
.hpoint = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}
// --- Callback for the auto-stop timer ---
static void IRAM_ATTR auto_stop_timer_cb(void* arg) {
led_state_t* led_state = (led_state_t*) arg;
ESP_LOGI(TAG, "Auto-stop timer expired for LED on GPIO %d", led_state->gpio_pin);
led_command_t cmd = { .command = CMD_STOP };
xQueueSendFromISR(led_state->command_queue, &cmd, NULL);
}
// --- Task to control an LED's PWM based on commands from its queue ---
static void led_task(void *pvParameter) {
led_state_t *led_state = (led_state_t *)pvParameter;
led_command_t cmd;
// Create the auto-stop timer for this LED
const esp_timer_create_args_t timer_args = {
.callback = &auto_stop_timer_cb,
.arg = led_state,
.name = "auto-stop-timer"
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &led_state->auto_stop_timer));
while (1) {
if (xQueueReceive(led_state->command_queue, &cmd, portMAX_DELAY) == pdPASS) {
ESP_LOGI(TAG, "Received command for LED on GPIO %d", led_state->gpio_pin);
// Cancel any running auto-stop timer before processing a new command
if (esp_timer_is_active(led_state->auto_stop_timer)) {
esp_timer_stop(led_state->auto_stop_timer);
}
if (cmd.command == CMD_START) {
if (!led_state->is_on) {
// Start the LED with the new or last saved frequency
int new_freq = (cmd.frequency > 0) ? cmd.frequency : led_state->last_freq;
// Cap the frequency to prevent errors
if (new_freq > MAX_FREQUENCY) {
ESP_LOGW(TAG, "Requested frequency %d is too high, capping at %dHz", new_freq, MAX_FREQUENCY);
new_freq = MAX_FREQUENCY;
}
led_state->last_freq = new_freq;
ESP_LOGI(TAG, "Starting LED on GPIO %d at %dHz", led_state->gpio_pin, new_freq);
ledc_set_freq(LEDC_MODE, led_state->ledc_timer, new_freq);
ledc_set_duty(LEDC_MODE, led_state->ledc_channel, LEDC_DUTY);
ledc_update_duty(LEDC_MODE, led_state->ledc_channel);
led_state->is_on = true;
// Start the 10-second auto-stop timer
esp_timer_start_once(led_state->auto_stop_timer, AUTO_STOP_TIME_MS * 1000);
} else {
ESP_LOGI(TAG, "LED on GPIO %d is already running.", led_state->gpio_pin);
// If a new command is received while running, restart the timer
esp_timer_start_once(led_state->auto_stop_timer, AUTO_STOP_TIME_MS * 1000);
}
} else if (cmd.command == CMD_STOP) {
if (led_state->is_on) {
// Stop the LED
ESP_LOGI(TAG, "Stopping LED on GPIO %d", led_state->gpio_pin);
ledc_set_duty(LEDC_MODE, led_state->ledc_channel, 0);
ledc_update_duty(LEDC_MODE, led_state->ledc_channel);
led_state->is_on = false;
}
} else if (cmd.command == CMD_TOGGLE) {
// This command is primarily for the buttons
if (led_state->is_on) {
led_command_t stop_cmd = { .command = CMD_STOP };
xQueueSend(led_state->command_queue, &stop_cmd, 0);
} else {
led_command_t start_cmd = { .command = CMD_START, .frequency = led_state->last_freq };
xQueueSend(led_state->command_queue, &start_cmd, 0);
}
}
}
}
}
// --- Task to monitor button presses with debouncing ---
static void button_task(void *pvParameter) {
gpio_num_t button_pin = (gpio_num_t)pvParameter;
led_state_t *led_state = NULL;
if (button_pin == BTN1_GPIO_PIN) {
led_state = &led1_state;
} else if (button_pin == BTN2_GPIO_PIN) {
led_state = &led2_state;
}
if (led_state == NULL) {
vTaskDelete(NULL); // Should not happen
}
// Configure GPIO for button input with pull-up resistor
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.pin_bit_mask = (1ULL << button_pin);
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&io_conf);
bool last_state = gpio_get_level(button_pin);
TickType_t last_press_time = 0;
while (1) {
bool current_state = gpio_get_level(button_pin);
TickType_t current_time = xTaskGetTickCount();
// Check for state change and debounce
if (current_state != last_state) {
if (current_time - last_press_time > pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) {
if (current_state == 0) { // Button pressed (active low)
ESP_LOGI(TAG, "Button on GPIO %d pressed", button_pin);
led_command_t cmd = { .command = CMD_TOGGLE };
xQueueSend(led_state->command_queue, &cmd, 0);
}
}
// --- FIX ---
// The last_press_time variable must only be updated when a state change is detected.
last_press_time = current_time;
}
last_state = current_state;
vTaskDelay(pdMS_TO_TICKS(10)); // Poll every 10ms
}
}
// --- Task to read and parse JSON commands from UART ---
static void uart_task(void *pvParameter) {
uint8_t *data = (uint8_t *)malloc(BUF_SIZE);
if (data == NULL) {
ESP_LOGE(TAG, "Failed to allocate UART buffer.");
vTaskDelete(NULL);
}
// Configure UART parameters
const uart_config_t uart_config = {
.baud_rate = UART_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
};
ESP_ERROR_CHECK(uart_param_config(UART_PORT, &uart_config));
ESP_ERROR_CHECK(uart_driver_install(UART_PORT, BUF_SIZE * 2, 0, 0, NULL, 0));
// --- Main UART loop ---
while (1) {
int len = uart_read_bytes(UART_PORT, data, (BUF_SIZE - 1), 20 / portTICK_PERIOD_MS);
if (len > 0) {
data[len] = '\0'; // Null-terminate the string
ESP_LOGI(TAG, "Received UART data: %s", (char *)data);
cJSON *root = cJSON_Parse((char *)data);
if (root == NULL) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL) {
ESP_LOGE(TAG, "cJSON parse error before: %s", error_ptr);
}
continue;
}
cJSON *mod = cJSON_GetObjectItemCaseSensitive(root, "MOD");
cJSON *com = cJSON_GetObjectItemCaseSensitive(root, "COM");
cJSON *freq = cJSON_GetObjectItemCaseSensitive(root, "FREQ");
if (cJSON_IsString(mod) && cJSON_IsString(com)) {
led_command_t cmd;
QueueHandle_t target_queue = NULL;
if (strcmp(mod->valuestring, "LED1") == 0) {
target_queue = led1_state.command_queue;
} else if (strcmp(mod->valuestring, "LED2") == 0) {
target_queue = led2_state.command_queue;
}
if (target_queue != NULL) {
if (strcmp(com->valuestring, "START") == 0) {
cmd.command = CMD_START;
if (cJSON_IsNumber(freq)) {
cmd.frequency = freq->valueint;
} else {
cmd.frequency = 0; // Use last saved frequency
}
xQueueSend(target_queue, &cmd, 0);
} else if (strcmp(com->valuestring, "STOP") == 0) {
cmd.command = CMD_STOP;
xQueueSend(target_queue, &cmd, 0);
} else {
ESP_LOGW(TAG, "Invalid command received.");
}
} else {
ESP_LOGW(TAG, "Invalid module received.");
}
} else {
ESP_LOGW(TAG, "JSON format error: MOD or COM not found or not a string.");
}
cJSON_Delete(root);
}
}
free(data);
vTaskDelete(NULL);
}
// --- Main application entry point ---
void app_main(void) {
ESP_LOGI(TAG, "Initializing system...");
// Initialize LED state variables
led1_state = (led_state_t){
.gpio_pin = LED1_GPIO_PIN,
.ledc_channel = LEDC_CHANNEL_0,
.ledc_timer = LEDC_TIMER_0, // Changed to a different timer to allow independent control if needed
.last_freq = 10, // Default frequency
.is_on = false,
.command_queue = xQueueCreate(10, sizeof(led_command_t))
};
led2_state = (led_state_t){
.gpio_pin = LED2_GPIO_PIN,
.ledc_channel = LEDC_CHANNEL_1,
.ledc_timer = LEDC_TIMER_1, // Changed to a different timer
.last_freq = 20, // Default frequency
.is_on = false,
.command_queue = xQueueCreate(10, sizeof(led_command_t))
};
if (!led1_state.command_queue || !led2_state.command_queue) {
ESP_LOGE(TAG, "Failed to create command queues.");
return;
}
// Initialize PWM for both LEDs
pwm_init(&led1_state);
pwm_init(&led2_state);
// Create FreeRTOS tasks
xTaskCreate(led_task, "led1_task", 4096, (void *)&led1_state, 5, NULL);
xTaskCreate(led_task, "led2_task", 4096, (void *)&led2_state, 5, NULL);
xTaskCreate(button_task, "btn1_task", 2048, (void *)BTN1_GPIO_PIN, 5, NULL);
xTaskCreate(button_task, "btn2_task", 2048, (void *)BTN2_GPIO_PIN, 5, NULL);
xTaskCreate(uart_task, "uart_task", 4096, NULL, 6, NULL);
}