diff --git a/Makefile b/Makefile index 4b90ef9..c03f194 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ format: .check-name run-debug: format @rm -f ${name}/${name}.debug - @gcc -std=c99 -o2 ${name}/*.c -lm -o ${name}/${name}.debug && ./${name}/${name}.debug + @gcc -DDEBUG -std=c99 -o2 ${name}/*.c -lm -o ${name}/${name}.debug && ./${name}/${name}.debug compile: format @rm -f ${name}/build/CMakeCache.txt diff --git a/buzzer/CMakeLists.txt b/buzzer/CMakeLists.txt index 2fabe7d..d6fb81b 100644 --- a/buzzer/CMakeLists.txt +++ b/buzzer/CMakeLists.txt @@ -20,9 +20,38 @@ set_target_properties(picotool PROPERTIES # Initialise the Raspberry Pi Pico SDK pico_sdk_init() +set(FREERTOS_SRC_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../freertos) +set(FREERTOS_CONFIG_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../config) + +include(${FREERTOS_SRC_DIRECTORY}/portable/ThirdParty/GCC/RP2350_ARM_NTZ/FreeRTOS_Kernel_import.cmake) + +# Include FreeRTOS directories +include_directories( + ${FREERTOS_CONFIG_DIRECTORY} + ${FREERTOS_SRC_DIRECTORY}/include + ${FREERTOS_SRC_DIRECTORY}/portable/ThirdParty/GCC/RP2350_ARM_NTZ/non_secure +) + +set(FREERTOS_SOURCES + ${FREERTOS_SRC_DIRECTORY}/event_groups.c + ${FREERTOS_SRC_DIRECTORY}/list.c + ${FREERTOS_SRC_DIRECTORY}/queue.c + ${FREERTOS_SRC_DIRECTORY}/stream_buffer.c + ${FREERTOS_SRC_DIRECTORY}/tasks.c + ${FREERTOS_SRC_DIRECTORY}/timers.c + ${FREERTOS_SRC_DIRECTORY}/portable/MemMang/heap_3.c +) + +set(FREERTOS_PORT_FILES + ${FREERTOS_SRC_DIRECTORY}/portable/ThirdParty/GCC/RP2350_ARM_NTZ/non_secure/port.c + ${FREERTOS_SRC_DIRECTORY}/portable/ThirdParty/GCC/RP2350_ARM_NTZ/non_secure/portasm.c + ${FREERTOS_SRC_DIRECTORY}/portable/ThirdParty/GCC/RP2350_ARM_NTZ/non_secure/mpu_wrappers_v2_asm.c +) + # Add executable. Default name is the project name, version 0.1 -add_executable(buzzer main.c ) +add_executable(buzzer main.c log.c gpio.c jobs.c ${FREERTOS_SOURCES} ${FREERTOS_PORT_FILES}) +add_definitions(-DPICO) pico_set_program_name(buzzer "buzzer") pico_set_program_version(buzzer "0.1") @@ -33,7 +62,7 @@ pico_enable_stdio_usb(buzzer 1) # Add the standard library to the build target_link_libraries(buzzer - pico_stdlib hardware_pwm) + pico_stdlib hardware_pwm pico_multicore hardware_exception) # Add the standard include files to the build target_include_directories(buzzer PRIVATE diff --git a/buzzer/circuit.jpg b/buzzer/circuit.jpg new file mode 100755 index 0000000..32d2684 Binary files /dev/null and b/buzzer/circuit.jpg differ diff --git a/buzzer/gpio.c b/buzzer/gpio.c new file mode 100644 index 0000000..39da6db --- /dev/null +++ b/buzzer/gpio.c @@ -0,0 +1,124 @@ +//#define PICO + +#ifdef PICO +#include +#include +#include + +#define DEFAULT_WRAP_LEVEL 20 +#else +#include +#include +#include +#include + +#define GPIO_OUT 1 +#define GPIO_IN 0 + +static size_t GPIO_INPUT_MOCK_COUNTER = 0; +#endif + +#include "gpio.h" +#include "log.h" + +void _sleep_ms(size_t value) { +#ifdef PICO + sleep_ms(value); +#else + usleep(value * 1000); +#endif +} + +void _sleep_us(size_t value) { +#ifdef PICO + sleep_us(value); +#else + usleep(value); +#endif +} + +void gpio_set_level(gpio *g, uint16_t level) { + if (g->has_pwm && g->direction == GPIO_OUT) { + g->level = level; + g->state = level > 0; +#ifdef PICO + pwm_set_gpio_level(g->pin, level); +#else + debug("", g->pin, level); +#endif + } +} + +void gpio_set_state(gpio *g, bool state) { + if (!g->has_pwm && g->direction == GPIO_OUT) { +#ifdef PICO + gpio_put(g->pin, state); +#else + // must be trace instead + // debug("", g->pin, state); +#endif + } +} + +bool gpio_get_input(gpio* g) { + if (g->direction == GPIO_IN) { +#ifdef PICO + g->state = gpio_get(g->pin); +#else + int value = rand() % (10 + 1); + if (value == 0) { + g->state = false; + return false; + } + g->state = GPIO_INPUT_MOCK_COUNTER++ % value == 0; + debug("gpio get input (state: %d)", g->state); +#endif + return g->state; + } + return false; +} + +void blink_failed(size_t pin, uint32_t delay_ms) { +#ifdef PICO + for (;;) { + gpio_put(pin, false); + _sleep_ms(delay_ms); + + gpio_put(pin, true); + _sleep_ms(delay_ms); + } +#else + for(;;) { + debug("fatal error occurred, please restart the application"); + _sleep_ms(delay_ms); + } +#endif +} + +void blink_passed(size_t pin, uint32_t delay_ms, char* msg) { +#ifdef PICO + gpio_put(pin, true); + _sleep_ms(delay_ms); + gpio_put(pin, false); + _sleep_ms(delay_ms); +#else + debug(msg); +#endif +} + +void init_gpio(void) { +#ifdef PICO + stdio_init_all(); + for (size_t i = 0; i < NB_GPIO; i++) { + gpio_init(GPIOS[i]->pin); + gpio_set_dir(GPIOS[i]->pin, GPIOS[i]->direction); + + if (GPIOS[i]->has_pwm) { + uint slice_num = pwm_gpio_to_slice_num(GPIOS[i]->pin); + pwm_set_enabled(slice_num, true); + pwm_set_wrap(slice_num, DEFAULT_WRAP_LEVEL); + } + } + blink_passed(GPIOS[0]->pin, 200, "gpios init with success"); +#endif +} diff --git a/buzzer/gpio.h b/buzzer/gpio.h new file mode 100644 index 0000000..238bef8 --- /dev/null +++ b/buzzer/gpio.h @@ -0,0 +1,62 @@ +//#define PICO + +#ifndef GPIO_H +#define GPIO_H + +#ifdef PICO +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include + +#define PICO_DEFAULT_LED_PIN 0 +#define GPIO_OUT 1 +#define GPIO_IN 0 +#endif + +#define NB_GPIO 3 + +typedef struct gpio gpio; +struct gpio { + size_t pin; + size_t direction; + bool has_pwm; + uint16_t level; + bool state; +}; + +static gpio GPIO_DEFAULT_LED = (gpio) { + .pin = PICO_DEFAULT_LED_PIN, .direction = GPIO_OUT, .has_pwm = false, .level = 0, .state = false +}; + +static gpio GPIO_BUTTON = (gpio) { + .pin = 16, .direction = GPIO_IN, .has_pwm = false, .level = 0, .state = false +}; + +static gpio GPIO_BUZZER = (gpio) { + .pin = 15, .direction = GPIO_OUT, .has_pwm = false, .level = 0, .state = false +}; + +static gpio* GPIOS[NB_GPIO] = {&GPIO_DEFAULT_LED, &GPIO_BUTTON, &GPIO_BUZZER}; + +void gpio_set_level(gpio *g, uint16_t level); +void gpio_set_state(gpio *g, bool state); +bool gpio_get_input(gpio* g); + +void init_gpio(void); + +void blink_failed(size_t pin, uint32_t delay_ms); +void blink_passed(size_t pin, uint32_t delay_ms, char* msg); + +void _sleep_ms(size_t value); +void _sleep_us(size_t value); + +#endif \ No newline at end of file diff --git a/buzzer/jobs.c b/buzzer/jobs.c new file mode 100644 index 0000000..ce92ca9 --- /dev/null +++ b/buzzer/jobs.c @@ -0,0 +1,171 @@ +//#define PICO + +#ifdef PICO +#include +#include +#include "FreeRTOS.h" +#include "task.h" +#include "semphr.h" +#else +#include +#include +#include +#endif + +#include "gpio.h" +#include "jobs.h" +#include "log.h" + +#define BUTTON_BUFFETING_SLEEP_DURATION_MS 20 + +int init_app_state(AppState *s) { + s->action = STOP; + +#ifdef PICO + s->lock = xSemaphoreCreateMutex(); +#else + if (pthread_mutex_init(&s->lock, NULL) != 0) { + printf("error: mutex init failed\n"); + return -1; + } +#endif + + return 0; +} + +int lock(AppState *s) { +#ifdef PICO + if (xSemaphoreTake(s->lock, (TickType_t)10) != pdTRUE) { + printf("unable to lock the FreeRTOS task\n"); + return -1; + } + return 0; +#else + return pthread_mutex_lock(&s->lock); +#endif +} + +int release(AppState *s) { +#ifdef PICO + xSemaphoreGive(s->lock); + return 0; +#else + return pthread_mutex_unlock(&s->lock); +#endif +} + +int cancel_task(AppState *s) { + debug("stopping task..."); +#ifdef PICO + if (s->task != NULL) { + vTaskDelete(*s->task); + free(s->task); + gpio_set_state(GPIOS[2], false); + return 0; + } +#else + if (s->task != NULL) { + if (pthread_cancel(*s->task) != 0) { + perror("error: unable to cancel the thread task\n"); + return -1; + } + + if (pthread_join(*s->task, NULL) != 0) { + perror("error: unable to wait for finished task\n"); + return -1; + } + free(s->task); + s->task = NULL; + } + return 0; +#endif +} + + +int launch_task(AppState *s, FnJob f) { + debug("starting task thread..."); +#ifdef PICO + TaskHandle_t* task = malloc(sizeof(TaskHandle_t)); + if (task == NULL) { + blink_failed(GPIOS[0]->pin, 300); + } + + BaseType_t res = + xTaskCreate(f, "task", configMINIMAL_STACK_SIZE, + NULL, configMAX_PRIORITIES - 1U, task); + + if (res != pdPASS) { + printf("error: unable to launch run alarm task task, code error: %d\n", + res); + return -1; + } + s->task = task; +#else + pthread_t* task = malloc(sizeof(task)); + if (task == NULL) { + perror("error: unable to allocate memory for task thread"); + return -1; + } + + if (pthread_create(task, NULL, f, s) != 0) { + perror("error: thread task creation failed\n"); + return -1; + } + + s->task = task; +#endif + return 0; +} + +void check_btn_state(void* args) { + TaskParam* params = (TaskParam*) args; + AppState *s = params->s; + bool has_proceed = false; + + for (;;) { + if (!gpio_get_input(GPIOS[1])) { +#ifdef PICO + vTaskDelay(pdMS_TO_TICKS(BUTTON_BUFFETING_SLEEP_DURATION_MS)); +#else + _sleep_ms(BUTTON_BUFFETING_SLEEP_DURATION_MS); +#endif + if(!gpio_get_input(GPIOS[1]) && !has_proceed) { + if (lock(s) != 0) { + blink_failed(PICO_DEFAULT_LED_PIN, 200); + } + + if (s->action == START) { + s->action = STOP; + cancel_task(s); + if (release(s) != 0) { + blink_failed(PICO_DEFAULT_LED_PIN, 200); + } + gpio_set_state(GPIOS[0], false); + has_proceed = true; + continue; + } + + s->action = START; + + if (launch_task(s, params->f) != 0) { + blink_failed(PICO_DEFAULT_LED_PIN, 200); + } + + if (release(s) != 0) { + blink_failed(PICO_DEFAULT_LED_PIN, 200); + } + + gpio_set_state(GPIOS[0], true); + has_proceed = true; + } + continue; + } + has_proceed = false; + } +} + +#ifdef PICO +void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName) { + blink_failed(PICO_DEFAULT_LED_PIN, 200); +} +#endif \ No newline at end of file diff --git a/buzzer/jobs.h b/buzzer/jobs.h new file mode 100644 index 0000000..00082cd --- /dev/null +++ b/buzzer/jobs.h @@ -0,0 +1,47 @@ +//#define PICO + +#ifndef JOBS_H +#define JOBS_H + +#ifdef PICO + #include "FreeRTOS.h" + #include "task.h" + #include "semphr.h" +#else + #include +#endif + +typedef enum ACTION Action; +enum ACTION {STOP, START}; + +typedef struct AppState AppState; +struct AppState { + Action action; + + #ifdef PICO + TaskHandle_t* task; + SemaphoreHandle_t lock; + #else + pthread_mutex_t lock; + pthread_t* task; + #endif +}; + +typedef void (*FnJob)(void* params); + +typedef struct TaskParam TaskParam; +struct TaskParam { + AppState* s; + FnJob f; +}; + +int init_app_state(AppState *s); + +void check_btn_state(void* args); + +int lock(AppState *s); +int release(AppState *s); +int cancel_task(AppState *s); +int launch_task(AppState *s, FnJob f); + +#endif \ No newline at end of file diff --git a/buzzer/log.c b/buzzer/log.c new file mode 100644 index 0000000..610e6de --- /dev/null +++ b/buzzer/log.c @@ -0,0 +1,28 @@ +#include +#include +#include + +#include "log.h" + +void debug(const char *format, ...) { +#ifdef DEBUG + if (format == NULL) { + return; + } + + time_t now; + time(&now); + char time_str[26]; + ctime_r(&now, time_str); + time_str[24] = '\0'; + + fprintf(stdout, "[DEBUG] [%s] ", time_str); + + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + + fprintf(stdout, "\n"); +#endif +} \ No newline at end of file diff --git a/buzzer/log.h b/buzzer/log.h new file mode 100644 index 0000000..dc06d79 --- /dev/null +++ b/buzzer/log.h @@ -0,0 +1,6 @@ +#ifndef LOG_H +#define LOG_H + +void debug(const char *format, ...); + +#endif \ No newline at end of file diff --git a/buzzer/main.c b/buzzer/main.c index 0c6dd2e..6bcfc66 100644 --- a/buzzer/main.c +++ b/buzzer/main.c @@ -1,45 +1,129 @@ +//#define PICO + +#ifdef PICO #include #include -#include +#include "FreeRTOS.h" +#include "task.h" +#else +#include +#include +#include +#include +#include -#define GPIO_BUTTON 16 -#define GPIO_BUZZER 15 -#define NB_GPIO 2 +#define PICO_DEFAULT_LED_PIN 0 +#define GPIO_OUT 1 +#define GPIO_IN 0 +#endif -typedef struct gpio gpio; -struct gpio { - size_t pin; - size_t direction; -}; +#include -static gpio GPIOS[2] = { - (gpio) {.pin = GPIO_BUZZER, .direction = GPIO_OUT}, - (gpio) { - .pin = GPIO_BUTTON, .direction = GPIO_IN +#include "log.h" +#include "jobs.h" +#include "gpio.h" + +#define ALARM_BASE_FREQ 2000 // auditive frequency human range: [20; 20000] hertz +#define ALARM_PERIOD_MS 500 +#define PI 3.14 + +/** + * Oscillator for passive buzzer hardware. + * + * It generates desired `freq_hz` for `duration_ms`. + * + * NOTE: `sleep_us` method does not impact FreeRTOS engine + * since, the scheduler started on two cores (RP2350). The behavior may differs + * on one core. Update the FreeRTOS tick freq and implement `pdUS_TO_TICKS` if needed. + */ +void gen_freq(gpio* g, size_t freq_hz, size_t duration_ms) { + if (!freq_hz) { + gpio_set_state(g, 0); + return; } -}; -void init_gpio() { - gpio_init(PICO_DEFAULT_LED_PIN); - gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); + float period = (1.0 / freq_hz) * 1000000; // signal period in micro seconds + size_t duty_period = period / 2; // working period = period / 2 + size_t nb_period = duration_ms * 1000 / period; // number of period to play - for (size_t i = 0; i < NB_GPIO; i++) { - gpio_init(GPIOS[i].pin); - gpio_set_dir(GPIOS[i].pin, GPIOS[i].direction); + debug("freq_hz = (%ld), period_us = (%.2f), duty_period_us = (%ld), nb_period = (%ld)", freq_hz, period, + duty_period, nb_period); + + for (size_t i = 0; i < nb_period; i++) { + gpio_set_state(g, true); + _sleep_us(duty_period); + gpio_set_state(g, false); + _sleep_us(duty_period); } } +void run_alarm(void *const params) { + for(;;) { + debug("running alarm..."); + float sin_val; + int tone_val; + + const size_t duration_ms = ALARM_PERIOD_MS / 36; + + for (size_t i = 0; i < 360; i+=10) { + sin_val = sinf(i * (PI / 180)); + tone_val = ALARM_BASE_FREQ + sin_val * (ALARM_BASE_FREQ / 10); + gen_freq(GPIOS[2], tone_val, duration_ms); + } + } +} + +/* Initialize the main FreeRTOS task */ +void init_rtos_main(TaskParam *params) { +#ifdef PICO + BaseType_t res = + xTaskCreate(check_btn_state, "main", configMINIMAL_STACK_SIZE, + (void *const)params, configMAX_PRIORITIES - 1U, NULL); + + if (res != pdPASS) { + blink_failed(GPIOS[0]->pin, 500); + } +#endif +} + +/** + * A program to run alarm on button pushed. + * + * To activate the alarm, push the button. Same for desactivation. + * The alarm is launched in a separate task and controlled by + * the main task (which holds the push button logic). + * + * Hardware: + * - 1x Passive Buzzer + * - 1x Push button + * - 1 NPN transistor for signal amplification + * - 1x 1k ohm resistor (NPN collector) + * - 2x 10k ohm resistor + * + * You can run the application in "DEBUG" mode using: + * `make run-debug name=buzzer` + * The FreeRTOS engine is replaced by threads. + */ int main() { - stdio_init_all(); init_gpio(); - while (true) { - if (!gpio_get(GPIOS[1].pin)) { - gpio_put(PICO_DEFAULT_LED_PIN, true); - gpio_put(GPIOS[0].pin, true); - continue; - } - gpio_put(GPIOS[0].pin, false); - gpio_put(PICO_DEFAULT_LED_PIN, false); - } + AppState as; + init_app_state(&as); + + TaskParam params = (TaskParam) { + .s = &as, + .f = run_alarm, + }; + +#ifdef PICO + init_rtos_main(¶ms); +#else + check_btn_state(¶ms); +#endif + +#ifdef PICO + vTaskStartScheduler(); +#endif + + blink_failed(PICO_DEFAULT_LED_PIN, 200); }