feat: initial
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
90
README.md
90
README.md
@@ -1,3 +1,89 @@
|
||||
# zh_avr_encoder
|
||||
# FreeRTOS based AVR library for rotary encoder
|
||||
|
||||
FreeRTOS based AVR library for rotary encoder.
|
||||
## 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();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
119
include/zh_avr_encoder.h
Normal file
119
include/zh_avr_encoder.h
Normal file
@@ -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
|
1
version.txt
Normal file
1
version.txt
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
191
zh_avr_encoder.c
Normal file
191
zh_avr_encoder.c
Normal file
@@ -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);
|
||||
}
|
Reference in New Issue
Block a user