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 898bdbe..d0b53e2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,95 @@ -# zh_avr_i2c +# FreeRTOS based AVR library for I2C bus -AVR library for I2C bus. \ No newline at end of file +## Features + +1. Simple read and write data. + +## Dependencies + +1. [zh_avr_free_rtos](http://git.zh.com.ru/avr_libraries/zh_avr_free_rtos) + +## Using + +In an existing project, run the following command to install the component: + +```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_i2c +``` + +In the application, add the component: + +```c +#include "zh_avr_i2c.h" +``` + +## Example + +Master read and write data: + +```c +#include "avr/io.h" +#include "stdio.h" +#include "zh_avr_i2c.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); + +void i2c_example_task(void *pvParameters) +{ + zh_avr_i2c_master_init(false); + for (;;) + { + avr_err_t err = zh_avr_i2c_master_probe(0x38, 100 / portTICK_PERIOD_MS); + if (err == AVR_OK) + { + uint8_t data_send = 111; + uint8_t data_read = 0; + printf("Data Send %d.\n", data_send); + zh_avr_i2c_master_transmit(0x38, (uint8_t *)&data_send, sizeof(data_send), 100 / portTICK_PERIOD_MS); + zh_avr_i2c_master_receive(0x38, (uint8_t *)&data_read, sizeof(data_read), 100 / portTICK_PERIOD_MS); + printf("Data Read %d.\n", data_read); + data_send = 55; + printf("Data Send %d.\n", data_send); + zh_avr_i2c_master_transmit(0x38, (uint8_t *)&data_send, sizeof(data_send), 100 / portTICK_PERIOD_MS); + zh_avr_i2c_master_receive(0x38, (uint8_t *)&data_read, sizeof(data_read), 100 / portTICK_PERIOD_MS); + printf("Data Read %d.\n", data_read); + data_send = 14; + printf("Data Send %d.\n", data_send); + zh_avr_i2c_master_transmit(0x38, (uint8_t *)&data_send, sizeof(data_send), 100 / portTICK_PERIOD_MS); + zh_avr_i2c_master_receive(0x38, (uint8_t *)&data_read, sizeof(data_read), 100 / portTICK_PERIOD_MS); + printf("Data Read %d.\n", data_read); + } + else + { + printf("Device Not Found.\n"); + } + printf("Task Remaining Stack Size %d.\n", uxTaskGetStackHighWaterMark(NULL)); + vTaskDelay(5000 / portTICK_PERIOD_MS); + } + vTaskDelete(NULL); +} + +int main(void) +{ + UBRR0H = (BAUD_PRESCALE >> 8); + UBRR0L = BAUD_PRESCALE; + UCSR0B = (1 << RXEN0) | (1 << TXEN0); + UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); + stdout = &uart; + xTaskCreate(i2c_example_task, "i2c example task", 110, NULL, tskIDLE_PRIORITY, NULL); + vTaskStartScheduler(); + return 0; +} +``` diff --git a/include/zh_avr_i2c.h b/include/zh_avr_i2c.h new file mode 100644 index 0000000..ce54695 --- /dev/null +++ b/include/zh_avr_i2c.h @@ -0,0 +1,80 @@ +#pragma once + +#include "FreeRTOS.h" +#include "event_groups.h" +#include "avr/io.h" +#include "avr/interrupt.h" +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef enum + { + AVR_FAIL = -1, + AVR_OK, + AVR_ERR_NO_MEM, + AVR_ERR_INVALID_ARG, + AVR_ERR_INVALID_STATE, + AVR_ERR_INVALID_SIZE, + AVR_ERR_NOT_FOUND, + AVR_ERR_NOT_SUPPORTED, + AVR_ERR_TIMEOUT, + AVR_ERR_INVALID_RESPONSE, + AVR_ERR_INVALID_CRC, + AVR_ERR_INVALID_VERSION, + AVR_ERR_NOT_FINISHED, + AVR_ERR_NOT_ALLOWED + } avr_err_t; + + /** + * @brief Initialize I2C bus. + * + * @param[in] pullup Using internal pullup resistors. + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_i2c_master_init(const bool pullup); + + /** + * @brief Probe I2C address. + * + * @param[in] addr Address I2C device. + * @param[in] xTicksToWait Wait timeout in FreeRTOS ticks. + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_i2c_master_probe(const uint8_t addr, TickType_t xTicksToWait); + + /** + * @brief Send data to I2C address. + * + * @param[in] addr Address I2C device. + * @param[in] data Pointer to data for send. + * @param[in] size Data size. + * @param[in] xTicksToWait Wait timeout in FreeRTOS ticks. + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_i2c_master_transmit(const uint8_t addr, uint8_t *data, uint8_t size, TickType_t xTicksToWait); + + /** + * @brief Read data from I2C address. + * + * @param[in] addr Address I2C device. + * @param[out] data Pointer to buffer for read. + * @param[in] size Data size. + * @param[in] xTicksToWait Wait timeout in FreeRTOS ticks. + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_i2c_master_receive(const uint8_t addr, uint8_t *data, uint8_t size, TickType_t xTicksToWait); + + avr_err_t zh_avr_i2c_master_transmit_register(const uint8_t addr, uint16_t *reg, uint8_t *data, uint8_t size, TickType_t xTicksToWait); // To Do + avr_err_t zh_avr_i2c_master_receive_register(const uint8_t addr, uint16_t *reg, uint8_t *data, uint8_t size, TickType_t xTicksToWait); // To Do + +#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_i2c.c b/zh_avr_i2c.c new file mode 100644 index 0000000..fbc8e47 --- /dev/null +++ b/zh_avr_i2c.c @@ -0,0 +1,216 @@ +#include "zh_avr_i2c.h" + +#define ZH_ERROR_CHECK(cond, err, ...) \ + if (!(cond)) \ + { \ + return err; \ + } + +#define I2C_OK 0x01 +#define I2C_NACK 0x02 +#define I2C_COLLISION 0x04 +#define I2C_BUS_FAIL 0x08 + +#define I2C_START ((1 << TWINT) | (1 << TWEN) | (1 << TWIE)) +#define I2C_MASTER_READ 1 +#define I2C_MASTER_WRITE 0 + +typedef enum +{ + MASTER_WRITE, + MASTER_READ, + MASTER_WRITE_REG, + MASTER_READ_REG +} _work_mode_t; + +avr_err_t _zh_avr_i2c_master_start(TickType_t xTicksToWait); + +static EventGroupHandle_t _event_group_handle = NULL; +static uint8_t _target_i2c_address = 0; +volatile static uint8_t _work_mode = 0; +volatile static uint8_t *_master_data = NULL; +volatile static uint8_t _master_data_size = 0; +static bool _master_is_initialized = false; + +avr_err_t zh_avr_i2c_master_init(const bool pullup) +{ + _event_group_handle = xEventGroupCreate(); + ZH_ERROR_CHECK(_event_group_handle != NULL, AVR_ERR_NO_MEM); + cli(); + DDRC &= ~(1 << PORTC5 | 1 << PORTC4); + if (pullup == true) + { + PORTC |= 1 << PORTC5 | 1 << PORTC4; + } + TWBR = ((F_CPU / 100000) - 16) / 2; + TWSR = 0xF8; + sei(); + _master_is_initialized = true; + return AVR_OK; +} + +avr_err_t zh_avr_i2c_master_probe(const uint8_t addr, TickType_t xTicksToWait) +{ + uint8_t temp = 0; + return zh_avr_i2c_master_transmit(addr, &temp, sizeof(temp), xTicksToWait); +} + +avr_err_t zh_avr_i2c_master_transmit(const uint8_t addr, uint8_t *data, uint8_t size, TickType_t xTicksToWait) +{ + ZH_ERROR_CHECK(data != NULL || size > 0, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(_master_is_initialized == true, AVR_ERR_INVALID_STATE); + _work_mode = MASTER_WRITE; + _target_i2c_address = addr; + _master_data = data; + _master_data_size = size; + return _zh_avr_i2c_master_start(xTicksToWait); +} + +avr_err_t zh_avr_i2c_master_receive(const uint8_t addr, uint8_t *data, uint8_t size, TickType_t xTicksToWait) +{ + ZH_ERROR_CHECK(data != NULL || size > 0, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(_master_is_initialized == true, AVR_ERR_INVALID_STATE); + _work_mode = MASTER_READ; + _target_i2c_address = addr; + _master_data = data; + _master_data_size = size; + return _zh_avr_i2c_master_start(xTicksToWait); +} + +avr_err_t zh_avr_i2c_master_transmit_register(const uint8_t addr, uint16_t *reg, uint8_t *data, uint8_t size, TickType_t xTicksToWait) +{ + // To Do. + return AVR_OK; +} +avr_err_t zh_avr_i2c_master_receive_register(const uint8_t addr, uint16_t *reg, uint8_t *data, uint8_t size, TickType_t xTicksToWait) +{ + // To Do. + return AVR_OK; +} + +avr_err_t _zh_avr_i2c_master_start(TickType_t xTicksToWait) +{ + TWCR = I2C_START | (1 << TWSTA); + EventBits_t bits = xEventGroupWaitBits(_event_group_handle, I2C_OK | I2C_NACK | I2C_COLLISION | I2C_BUS_FAIL, pdTRUE, pdFALSE, xTicksToWait); + if ((bits & I2C_OK) != 0) + { + return AVR_OK; + } + else if ((bits & I2C_NACK) != 0) + { + return AVR_ERR_INVALID_RESPONSE; + } + else + { + return AVR_FAIL; + } +} + +ISR(TWI_vect) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + switch (TWSR & 0xF8) + { + case 0x00: // Bus error. + TWCR = I2C_START | (1 << TWSTO); + xEventGroupSetBitsFromISR(_event_group_handle, I2C_BUS_FAIL, &xHigherPriorityTaskWoken); + break; + case 0x08: // A START condition has been transmitted. + switch (_work_mode) + { + case MASTER_WRITE: + case MASTER_WRITE_REG: + case MASTER_READ_REG: + TWDR = (_target_i2c_address << 1) | I2C_MASTER_WRITE; + break; + case MASTER_READ: + TWDR = (_target_i2c_address << 1) | I2C_MASTER_READ; + break; + default: + break; + } + TWCR = I2C_START; + break; + case 0x10: // A repeated START condition has been transmitted. + // To Do. + break; + case 0x18: // SLA+W has been transmitted. ACK has been received. + TWDR = *(_master_data++); + --_master_data_size; + TWCR = I2C_START; + break; + case 0x20: // SLA+W has been transmitted. NACK has been received. + TWCR = I2C_START | (1 << TWSTO); + xEventGroupSetBitsFromISR(_event_group_handle, I2C_NACK, &xHigherPriorityTaskWoken); + break; + case 0x28: // Data byte has been transmitted. ACK has been received. + if (_master_data_size-- == 0) + { + TWCR = I2C_START | (1 << TWSTO); + xEventGroupSetBitsFromISR(_event_group_handle, I2C_OK, &xHigherPriorityTaskWoken); + } + else + { + TWDR = *(_master_data++); + TWCR = I2C_START; + } + break; + case 0x30: // Data byte has been transmitted. NACK has been received. + TWCR = I2C_START | (1 << TWSTO); + xEventGroupSetBitsFromISR(_event_group_handle, I2C_NACK, &xHigherPriorityTaskWoken); + break; + case 0x38: // Arbitration lost in SLA+W or data bytes. Arbitration lost in SLA+R or NACK bit. + TWCR = I2C_START | (1 << TWSTO); + xEventGroupSetBitsFromISR(_event_group_handle, I2C_COLLISION, &xHigherPriorityTaskWoken); + break; + case 0x40: // SLA+R has been transmitted. ACK has been received. + switch (_work_mode) + { + case MASTER_WRITE: + break; + case MASTER_READ: + if (_master_data_size == 1) + { + TWCR = I2C_START; + } + else + { + TWCR = I2C_START | (1 << TWEA); + } + break; + case MASTER_WRITE_REG: + break; + case MASTER_READ_REG: + break; + default: + break; + } + break; + case 0x48: // SLA+R has been transmitted. NACK has been received. + TWCR = I2C_START | (1 << TWSTO); + xEventGroupSetBitsFromISR(_event_group_handle, I2C_NACK, &xHigherPriorityTaskWoken); + break; + case 0x50: // Data byte has been received. ACK has been returned. + *(_master_data++) = TWDR; + if (--_master_data_size == 1) + { + TWCR = I2C_START; + } + else + { + TWCR = I2C_START | (1 << TWEA); + } + break; + case 0x58: // Data byte has been received. NACK has been returned. + *(_master_data) = TWDR; + TWCR = I2C_START | (1 << TWSTO); + xEventGroupSetBitsFromISR(_event_group_handle, I2C_OK, &xHigherPriorityTaskWoken); + break; + default: + break; + } + if (xHigherPriorityTaskWoken == pdTRUE) + { + portYIELD(); + }; +} \ No newline at end of file