diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 430d374..1def3be 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,89 @@ -# zh_avr_encoder +# FreeRTOS based AVR library for rotary encoder -FreeRTOS based AVR library for rotary encoder. \ No newline at end of file +## Features + +1. Support some encoders on one device. + +## Using + +In an existing project, run the following command to install the components: + +```text +cd ../your_project/lib +git clone http://git.zh.com.ru/avr_libraries/zh_avr_free_rtos +git clone http://git.zh.com.ru/avr_libraries/zh_avr_common +git clone http://git.zh.com.ru/avr_libraries/zh_avr_encoder +``` + +In the application, add the component: + +```c +#include "zh_avr_encoder.h" +``` + +## Examples + +One encoder on device: + +```c +#include "avr/io.h" +#include "stdio.h" +#include "zh_avr_encoder.h" + +#define BAUD_RATE 9600 +#define BAUD_PRESCALE (F_CPU / 16 / BAUD_RATE - 1) + +int usart(char byte, FILE *stream) +{ + while ((UCSR0A & (1 << UDRE0)) == 0) + { + } + UDR0 = byte; + return 0; +} +FILE uart = FDEV_SETUP_STREAM(usart, NULL, _FDEV_SETUP_WRITE); + +zh_avr_encoder_handle_t encoder_handle = {0}; + +int main(void) +{ + UBRR0H = (BAUD_PRESCALE >> 8); + UBRR0L = BAUD_PRESCALE; + UCSR0B = (1 << RXEN0) | (1 << TXEN0); + UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); + stdout = &uart; + zh_avr_encoder_init_config_t encoder_init_config = ZH_AVR_ENCODER_INIT_CONFIG_DEFAULT(); + encoder_init_config.gpio_port = AVR_PORTD; + encoder_init_config.a_gpio_number = PORTD5; + encoder_init_config.b_gpio_number = PORTD6; + encoder_init_config.pullup = true; + encoder_init_config.encoder_min_value = -10; + encoder_init_config.encoder_max_value = 20; + encoder_init_config.encoder_step = 1; + encoder_init_config.encoder_number = 1; + zh_avr_encoder_init(&encoder_init_config, &encoder_handle); + double position = 0; + zh_avr_encoder_get(&encoder_handle, &position); + printf("Encoder position %d.\n", (int)position); + zh_avr_encoder_set(&encoder_handle, 5); + zh_avr_encoder_reset(&encoder_handle); + vTaskStartScheduler(); + return 0; +} + +void zh_avr_encoder_event_handler(zh_avr_encoder_event_on_isr_t *event) // Do not delete! +{ + printf("Encoder number %d position %d.\n", event->encoder_number, (int)event->encoder_position); + printf("Interrupt Task Remaining Stack Size %d.\n", uxTaskGetStackHighWaterMark(NULL)); +} + +// ISR(PCINT0_vect) // For AVR_PORTB. +// ISR(PCINT1_vect) // For AVR_PORTC. +ISR(PCINT2_vect) // For AVR_PORTD. +{ + if (zh_avr_encoder_isr_handler(&encoder_handle) == pdTRUE) + { + portYIELD(); + } +} +``` diff --git a/include/zh_avr_encoder.h b/include/zh_avr_encoder.h new file mode 100644 index 0000000..b340981 --- /dev/null +++ b/include/zh_avr_encoder.h @@ -0,0 +1,119 @@ +#pragma once + +#include "FreeRTOS.h" +#include "semphr.h" +#include "avr_err.h" +#include "avr_port.h" +#include "stdbool.h" +#include "avr/interrupt.h" +#include "avr/pgmspace.h" + +#define ZH_AVR_ENCODER_INIT_CONFIG_DEFAULT() \ + { \ + .task_priority = configMAX_PRIORITIES, \ + .stack_size = 124, \ + .queue_size = 1, \ + .gpio_port = 0, \ + .a_gpio_number = 0, \ + .b_gpio_number = 0, \ + .pullup = false, \ + .encoder_min_value = -100, \ + .encoder_max_value = 100, \ + .encoder_step = 1, \ + .encoder_number = 0} + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct // Structure for initial initialization of encoder. + { + uint8_t task_priority; // Task priority for the encoder isr processing. @note It is not recommended to set a value less than configMAX_PRIORITIES. + uint16_t stack_size; // Stack size for task for the encoder isr processing processing. @note The minimum size is 124 bytes. + uint8_t queue_size; // Queue size for task for the encoder processing. Depends on the number of encoders. + uint8_t gpio_port; // Encoder GPIO port. @note Must be same for A and B GPIO. + uint8_t a_gpio_number; // Encoder A GPIO number. + uint8_t b_gpio_number; // Encoder B GPIO number. + bool pullup; // Using internal pullup resistors. + int32_t encoder_min_value; // Encoder min value. @note Must be less than encoder_max_value. + int32_t encoder_max_value; // Encoder max value. @note Must be greater than encoder_min_value. + double encoder_step; // Encoder step. @note Must be greater than 0. + uint8_t encoder_number; // Unique encoder number. + } zh_avr_encoder_init_config_t; + + typedef struct // Encoder handle. + { + uint8_t gpio_port; // Encoder GPIO port. + uint8_t a_gpio_number; // Encoder A GPIO number. + uint8_t b_gpio_number; // Encoder B GPIO number. + int32_t encoder_min_value; // Encoder min value. + int32_t encoder_max_value; // Encoder max value. + double encoder_step; // Encoder step. + double encoder_position; // Encoder position. + uint8_t encoder_number; // Encoder unique number. + uint8_t encoder_state; // Encoder internal state. + bool is_initialized; // Encoder initialization flag. + } zh_avr_encoder_handle_t; + + typedef struct // Structure for sending data to the event handler when cause an interrupt. @note Should be used with zh_avr_encoder event base. + { + uint8_t encoder_number; // Encoder unique number. + double encoder_position; // Encoder current position. + } zh_avr_encoder_event_on_isr_t; + + /** + * @brief Initialize encoder. + * + * @note The encoder will be set to the position (encoder_min_value + encoder_max_value)/2. + * + * @param[in] config Pointer to encoder initialized configuration structure. Can point to a temporary variable. + * @param[out] handle Pointer to unique encoder handle. + * + * @note Before initialize the encoder recommend initialize zh_avr_encoder_init_config_t structure with default values. + * + * @code zh_avr_encoder_init_config_t config = ZH_AVR_ENCODER_INIT_CONFIG_DEFAULT() @endcode + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_encoder_init(const zh_avr_encoder_init_config_t *config, zh_avr_encoder_handle_t *handle); + + /** + * @brief Set encoder position. + * + * @param[in, out] handle Pointer to unique encoder handle. + * @param[in] position Encoder position (must be between encoder_min_value and encoder_max_value). + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_encoder_set(zh_avr_encoder_handle_t *handle, double position); + + /** + * @brief Get encoder position. + * + * @param[in] handle Pointer to unique encoder handle. + * @param[out] position Encoder position. + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_encoder_get(const zh_avr_encoder_handle_t *handle, double *position); + + /** + * @brief Reset encoder position. + * + * @note The encoder will be set to the position (encoder_min_value + encoder_max_value)/2. + * + * @param[in, out] handle Pointer to unique encoder handle. + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_encoder_reset(zh_avr_encoder_handle_t *handle); + + /** + * @brief Encoder ISR handler. + */ + BaseType_t zh_avr_encoder_isr_handler(zh_avr_encoder_handle_t *handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..afaf360 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/zh_avr_encoder.c b/zh_avr_encoder.c new file mode 100644 index 0000000..117c72a --- /dev/null +++ b/zh_avr_encoder.c @@ -0,0 +1,191 @@ +#include "zh_avr_encoder.h" + +#define ENCODER_DIRECTION_CW 0x10 +#define ENCODER_DIRECTION_CCW 0x20 + +static const uint8_t _encoder_matrix[7][4] PROGMEM = { + {0x03, 0x02, 0x01, 0x00}, + {0x23, 0x00, 0x01, 0x00}, + {0x13, 0x02, 0x00, 0x00}, + {0x03, 0x05, 0x04, 0x00}, + {0x03, 0x03, 0x04, 0x00}, + {0x03, 0x05, 0x03, 0x00}, +}; + +TaskHandle_t zh_avr_encoder = NULL; +static QueueHandle_t _queue_handle = NULL; +static bool _is_initialized = false; + +static avr_err_t _zh_avr_encoder_validate_config(const zh_avr_encoder_init_config_t *config); +static avr_err_t _zh_avr_encoder_configure_interrupts(const zh_avr_encoder_init_config_t *config, zh_avr_encoder_handle_t *handle); +static void _zh_avr_encoder_isr_processing_task(void *pvParameter); + +avr_err_t zh_avr_encoder_init(const zh_avr_encoder_init_config_t *config, zh_avr_encoder_handle_t *handle) +{ + avr_err_t err = _zh_avr_encoder_validate_config(config); + ZH_ERROR_CHECK(err == AVR_OK, err); + handle->encoder_number = config->encoder_number; + handle->encoder_min_value = config->encoder_min_value; + handle->encoder_max_value = config->encoder_max_value; + handle->encoder_step = config->encoder_step; + handle->encoder_position = (handle->encoder_min_value + handle->encoder_max_value) / 2; + handle->gpio_port = config->gpio_port; + handle->a_gpio_number = config->a_gpio_number; + handle->b_gpio_number = config->b_gpio_number; + err = _zh_avr_encoder_configure_interrupts(config, handle); + ZH_ERROR_CHECK(err == AVR_OK, err); + handle->is_initialized = true; + _is_initialized = true; + return AVR_OK; +} + +avr_err_t zh_avr_encoder_set(zh_avr_encoder_handle_t *handle, double position) +{ + ZH_ERROR_CHECK(handle->is_initialized == true, AVR_FAIL); + ZH_ERROR_CHECK(position <= handle->encoder_max_value && position >= handle->encoder_min_value, AVR_ERR_INVALID_ARG); + handle->encoder_position = position; + return AVR_OK; +} + +avr_err_t zh_avr_encoder_get(const zh_avr_encoder_handle_t *handle, double *position) +{ + ZH_ERROR_CHECK(handle->is_initialized == true, AVR_FAIL); + *position = handle->encoder_position; + return AVR_OK; +} + +avr_err_t zh_avr_encoder_reset(zh_avr_encoder_handle_t *handle) +{ + ZH_ERROR_CHECK(handle->is_initialized == true, AVR_FAIL); + handle->encoder_position = (handle->encoder_min_value + handle->encoder_max_value) / 2; + return AVR_OK; +} + +static avr_err_t _zh_avr_encoder_validate_config(const zh_avr_encoder_init_config_t *config) +{ + ZH_ERROR_CHECK(config != NULL, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->task_priority > tskIDLE_PRIORITY && config->stack_size >= 124, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->queue_size > 0, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->encoder_max_value > config->encoder_min_value, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->encoder_step > 0, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->gpio_port >= AVR_PORTB && config->gpio_port <= AVR_PORTD, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->a_gpio_number >= 0 && config->a_gpio_number <= 7, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->b_gpio_number >= 0 && config->b_gpio_number <= 7, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->a_gpio_number != config->b_gpio_number, AVR_ERR_INVALID_ARG); + return AVR_OK; +} + +static avr_err_t _zh_avr_encoder_configure_interrupts(const zh_avr_encoder_init_config_t *config, zh_avr_encoder_handle_t *handle) +{ + switch (config->gpio_port) + { + case AVR_PORTB: + DDRB &= ~((1 << config->a_gpio_number) | (1 << config->b_gpio_number)); + if (config->pullup == true) + { + PORTB |= ((1 << config->a_gpio_number) | (1 << config->b_gpio_number)); + } + PCICR |= (1 << PCIE0); + PCMSK0 |= ((1 << config->a_gpio_number) | (1 << config->b_gpio_number)); + break; + case AVR_PORTC: + DDRC &= ~((1 << config->a_gpio_number) | (1 << config->b_gpio_number)); + if (config->pullup == true) + { + PORTC |= ((1 << config->a_gpio_number) | (1 << config->b_gpio_number)); + } + PCICR |= (1 << PCIE1); + PCMSK1 |= ((1 << config->a_gpio_number) | (1 << config->b_gpio_number)); + break; + case AVR_PORTD: + DDRD &= ~((1 << config->a_gpio_number) | (1 << config->b_gpio_number)); + if (config->pullup == true) + { + PORTD |= ((1 << config->a_gpio_number) | (1 << config->b_gpio_number)); + } + PCICR |= (1 << PCIE2); + PCMSK2 |= ((1 << config->a_gpio_number) | (1 << config->b_gpio_number)); + break; + default: + return AVR_ERR_INVALID_ARG; + break; + } + if (_is_initialized == false) + { + _queue_handle = xQueueCreate(config->queue_size, sizeof(zh_avr_encoder_handle_t)); + ZH_ERROR_CHECK(_queue_handle != NULL, AVR_ERR_NO_MEM); + BaseType_t x_err = xTaskCreate(_zh_avr_encoder_isr_processing_task, "zh_avr_encoder", config->stack_size, NULL, config->task_priority, &zh_avr_encoder); + if (x_err != pdPASS) + { + vQueueDelete(_queue_handle); + return AVR_FAIL; + } + } + return AVR_OK; +} + +BaseType_t zh_avr_encoder_isr_handler(zh_avr_encoder_handle_t *handle) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + uint8_t temp = 0; + switch (handle->gpio_port) + { + case AVR_PORTB: + temp = pgm_read_byte(&_encoder_matrix[handle->encoder_state & 0x0F][(((PINB & (1 << handle->b_gpio_number)) == 0 ? 0 : 1) << 1) | ((PINB & (1 << handle->a_gpio_number)) == 0 ? 0 : 1)]); + break; + case AVR_PORTC: + temp = pgm_read_byte(&_encoder_matrix[handle->encoder_state & 0x0F][(((PINC & (1 << handle->b_gpio_number)) == 0 ? 0 : 1) << 1) | ((PINC & (1 << handle->a_gpio_number)) == 0 ? 0 : 1)]); + break; + case AVR_PORTD: + temp = pgm_read_byte(&_encoder_matrix[handle->encoder_state & 0x0F][(((PIND & (1 << handle->b_gpio_number)) == 0 ? 0 : 1) << 1) | ((PIND & (1 << handle->a_gpio_number)) == 0 ? 0 : 1)]); + break; + default: + break; + } + if (temp != handle->encoder_state) + { + handle->encoder_state = temp; + switch (handle->encoder_state & 0x30) + { + case ENCODER_DIRECTION_CW: + if (handle->encoder_position < handle->encoder_max_value) + { + handle->encoder_position = handle->encoder_position + handle->encoder_step; + if (handle->encoder_position > handle->encoder_max_value) + { + handle->encoder_position = handle->encoder_max_value; + } + xQueueSendFromISR(_queue_handle, handle, &xHigherPriorityTaskWoken); + } + break; + case ENCODER_DIRECTION_CCW: + if (handle->encoder_position > handle->encoder_min_value) + { + handle->encoder_position = handle->encoder_position - handle->encoder_step; + if (handle->encoder_position < handle->encoder_min_value) + { + handle->encoder_position = handle->encoder_min_value; + } + xQueueSendFromISR(_queue_handle, handle, &xHigherPriorityTaskWoken); + } + break; + default: + break; + } + } + return xHigherPriorityTaskWoken; +} + +static void _zh_avr_encoder_isr_processing_task(void *pvParameter) +{ + zh_avr_encoder_handle_t queue = {0}; + zh_avr_encoder_event_on_isr_t event = {0}; + while (xQueueReceive(_queue_handle, &queue, portMAX_DELAY) == pdTRUE) + { + event.encoder_number = queue.encoder_number; + event.encoder_position = queue.encoder_position; + extern void zh_avr_encoder_event_handler(zh_avr_encoder_event_on_isr_t * event); + zh_avr_encoder_event_handler(&event); + } + vTaskDelete(NULL); +} \ No newline at end of file