From d03d26cef120eec8c31ba69c1f68af9613e06132 Mon Sep 17 00:00:00 2001 From: Razvalyaev Date: Fri, 13 Mar 2026 15:47:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20i2c=20=D0=B8=20=D1=80=D0=B0=D1=81=D0=BF=D0=B8=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=9C=D0=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MDK-ARM/Core/App/i2c.c | 971 ++++++++++++++++++++++++++++ MDK-ARM/Core/App/i2c.h | 183 ++++++ MDK-ARM/Core/App/main.c | 3 +- MDK-ARM/Core/Config/periph_config.h | 22 + MDK-ARM/Template.uvoptx | 74 ++- MDK-ARM/Template.uvprojx | 5 + Pinout.xlsx | Bin 0 -> 11414 bytes 7 files changed, 1226 insertions(+), 32 deletions(-) create mode 100644 MDK-ARM/Core/App/i2c.c create mode 100644 MDK-ARM/Core/App/i2c.h create mode 100644 Pinout.xlsx diff --git a/MDK-ARM/Core/App/i2c.c b/MDK-ARM/Core/App/i2c.c new file mode 100644 index 0000000..57fadd8 --- /dev/null +++ b/MDK-ARM/Core/App/i2c.c @@ -0,0 +1,971 @@ +/** + ****************************************************************************** + * @file i2c.c + * @author Разваляев Алексей + * @brief Драйвер I2C на основе PLIB035. + * Этот файл содержит: + * + Инициализацию I2C + * + Функции для работы в режиме мастера (блокирующие): + * - Блокирующая передача/прием + * - Функции чтения/записи регистров устройств + * - Проверка наличия устройства + * + ****************************************************************************** + * @attention + * + * Использование этого драйвера предполагает наличие корректных настроек: + * - Определена конфигурационная структура i2c_config в periph_config.h + * - Определены пины SDA/SCL для I2C + * + ****************************************************************************** + * @verbatim + ============================================================================== + ##### Как использовать этот драйвер ##### + ============================================================================== + + 1. Настройка в periph_config.h: + (+) Определите i2c_config для I2C + (+) Настройте частоту SCL (FSFreq для стандартного режима, HSFreq для высокоскоростного) + (+) Используйте enum для настройки: + • I2C_Mode_Master - режим мастера + • I2C_Mode_Slave - режим ведомого + + 2. Инициализация: + (+) i2c_init_first() - настройка GPIO, тактирования + (+) i2c_init(&hi2c, &config) - инициализация I2C + + 3. Запуск I2C: + (+) I2C_Start(&hi2c) - включение I2C + + 4. Работа в режиме мастера (блокирующие функции): + - Основные функции: + (+) I2C_Master_Transmit(&hi2c, addr, buf, size, timeout) - передача данных + (+) I2C_Master_Receive(&hi2c, addr, buf, size, timeout) - прием данных + (+) I2C_Master_WriteReg(&hi2c, addr, reg, val, timeout) - запись регистра + (+) I2C_Master_ReadReg(&hi2c, addr, reg, buf, size, timeout) - чтение регистра + (+) I2C_Master_WriteRegs(&hi2c, addr, reg, buf, size, timeout) - запись нескольких регистров + (+) I2C_Master_ReadRegs(&hi2c, addr, reg, buf, size, timeout) - чтение нескольких регистров + (+) I2C_Master_IsDeviceReady(&hi2c, addr, timeout) - проверка наличия устройства + + 5. Обработка ошибок: + (+) I2C_GetLastError(&hi2c) - получение кода последней ошибки + (+) Callback-функция ошибки вызывается автоматически при ошибках + + 6. GPIO для I2C: + (+) Пины настраиваются автоматически при вызове i2c_init_first() + (+) При необходимости можно вызвать i2c_gpio_deinit() для восстановления + + ============================================================================== + ##### Особенности работы ##### + ============================================================================== + + - Режимы работы: + - Master: инициирует передачу данных, управляет шиной + - Slave: реагирует на запросы мастера + + - Скорости работы: + - Standard Mode (до 100 кГц) + - Fast Mode (до 400 кГц) + - Fast Mode Plus (до 1 МГц) + - High Speed Mode (до 3.4 МГц) + + - Адресация: + - 7-битная адресация (стандартная) + - 10-битная адресация (поддерживается) + + - Обработка ошибок: + - Обрабатываются ошибки шины, арбитража, таймаута + - При ошибке вызывается ErrCallback и сохраняется код ошибки + - После ошибки требуется переинициализация I2C + + - Функции работы с регистрами: + - Упрощают чтение/запись регистров устройств на шине I2C + - Поддерживают различные размеры регистров (8/16 бит) + + - Проверка устройства: + - Функция проверяет ответ устройства на свой адрес + - Полезна для проверки подключения устройств + + @endverbatim + ****************************************************************************** + */ + +//-- Includes ------------------------------------------------------------------ +#include "periph_config.h" + +I2C_HandleTypeDef hi2c; /*!< Хендл I2C */ + +//-- Private function prototypes ----------------------------------------------- +static OperationStatus __i2c_master_wait_bus_free(I2C_HandleTypeDef *hi2c, uint32_t timeout); +static OperationStatus __i2c_master_send_start(I2C_HandleTypeDef *hi2c, uint32_t timeout); +static OperationStatus __i2c_master_send_address(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t direction, uint32_t timeout); +static OperationStatus __i2c_master_send_stop(I2C_HandleTypeDef *hi2c, uint32_t timeout); +static OperationStatus __i2c_master_transmit_blocking(I2C_HandleTypeDef *hi2c, uint8_t *buf, uint16_t size, uint32_t timeout); +static OperationStatus __i2c_master_receive_blocking(I2C_HandleTypeDef *hi2c, uint8_t *buf, uint16_t size, uint32_t timeout); +static void __i2c_set_error(I2C_HandleTypeDef *hi2c, uint8_t error); + +//-- I2C Init functions ------------------------------------------------------- + +/** + * @brief Первичная инициализация I2C + * @details Настройка I2C и хендла: GPIO, тактирование + */ +void i2c_init_first(void) +{ +#if (USE_I2C==1) + // Настройка пинов для I2C + i2c_gpio_init(); + + // Включение тактирования и сброс I2C + RCU_APBClkCmd(RCU_APBClk_I2C, ENABLE); + RCU_APBRstCmd(RCU_APBRst_I2C, ENABLE); + + // Инициализируем I2C + hi2c.Instance = I2C; + hi2c.LastError = I2C_ERROR_NONE; + i2c_init(&hi2c, &i2c_config); +#endif +} + +/** + * @brief Инициализация I2C + * @param hi2c указатель на хендл I2C + * @param NewConfig указатель на новую конфигурацию I2C, иначе используется та, что в структуре + * @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка + */ +OperationStatus i2c_init(I2C_HandleTypeDef *hi2c, I2C_ExtInit_TypeDef *NewConfig) +{ + if(!hi2c || !hi2c->Instance) + return ERROR; + + if(NewConfig != NULL) + { + hi2c->Config = NewConfig; + } + if(hi2c->Config == NULL) + { + return ERROR; + } + + // Сбрасываем ошибку + hi2c->LastError = I2C_ERROR_NONE; + + // Настраиваем частоту SCL + if(hi2c->Config->I2C_Init.HSMode == ENABLE) + { + // Высокоскоростной режим + I2C_HSFreqConfig(hi2c->Config->HSFreq, hi2c->Config->I2CFreq); + } + else + { + // Стандартный/быстрый режим + I2C_FSFreqConfig(hi2c->Config->FSFreq, hi2c->Config->I2CFreq); + } + + // Настраиваем таймаут + if(hi2c->Config->I2C_Init.Timeout != DISABLE) + { + I2C_TimeoutClkDivConfig(hi2c->Config->I2C_Init.TimeoutClkDiv); + I2C_SetTimeoutCounterLoad(hi2c->Config->I2C_Init.TimeoutLoad); + } + + // Настраиваем дополнительные функции + I2C_AlertResponseMatchCmd(hi2c->Config->I2C_Init.AlertResponse); + I2C_GlobalCallMatchCmd(hi2c->Config->I2C_Init.GlobalCall); + + return OK; +} + +/** + * @brief Установка коллбека I2C + * @param hi2c указатель на хендл I2C + * @param CallbackType Тип коллбека + * @param Callback Функция коллбека + * @retval OperationStatus OK - если успешно, ERROR - при ошибке + */ +OperationStatus I2C_Set_Callback(I2C_HandleTypeDef* hi2c, I2C_CallbackTypeDef CallbackType, void (*Callback)()) +{ + if (!hi2c || !hi2c->Instance || !hi2c->Config) + return ERROR; + + switch(CallbackType) + { + case I2C_Callback_Tx: + hi2c->Config->TxCallback = Callback; + break; + case I2C_Callback_Rx: + hi2c->Config->RxCallback = Callback; + break; + case I2C_Callback_Addr: + hi2c->Config->AddrCallback = Callback; + break; + case I2C_Callback_Error: + hi2c->Config->ErrCallback = Callback; + break; + + default: + return ERROR; + } + return OK; +} + +//-- I2C API functions -------------------------------------------------------- + +/** + * @brief Запуск I2C + * @param hi2c указатель на хендл I2C + * @retval OperationStatus OK - если успешно, ERROR - при ошибке + */ +OperationStatus I2C_Start(I2C_HandleTypeDef *hi2c) +{ + if (!hi2c) + return ERROR; + + // Сбрасываем ошибку + hi2c->LastError = I2C_ERROR_NONE; + + // Включаем I2C + I2C_Cmd(ENABLE); + + // Если режим ведомого, настраиваем адрес и включаем распознавание + if(hi2c->Config->I2C_Init.Mode == I2C_Mode_Slave) + { + if(hi2c->Config->I2C_Init.Addr10Bit == ENABLE) + { + I2C_Slave10AddrCmd(ENABLE); + I2C_SetSlave10Addr(hi2c->Config->I2C_Init.SlaveAddr >> 8); + I2C_SetSlaveAddr(hi2c->Config->I2C_Init.SlaveAddr & 0xFF); + } + else + { + I2C_Slave10AddrCmd(DISABLE); + I2C_SetSlaveAddr(hi2c->Config->I2C_Init.SlaveAddr); + } + I2C_SlaveCmd(ENABLE); + } + + return OK; +} + +/** + * @brief Остановка I2C + * @param hi2c указатель на хендл I2C + * @retval OperationStatus OK - если успешно, ERROR - при ошибке + */ +OperationStatus I2C_Stop(I2C_HandleTypeDef *hi2c) +{ + if (!hi2c) + return ERROR; + + // Выключаем I2C + I2C_Cmd(DISABLE); + + // Если режим ведомого, выключаем распознавание адреса + if(hi2c->Config->I2C_Init.Mode == I2C_Mode_Slave) + { + I2C_SlaveCmd(DISABLE); + } + + return OK; +} + +/** + * @brief Получение кода последней ошибки + * @param hi2c указатель на хендл I2C + * @retval Код последней ошибки I2C + */ +uint8_t I2C_GetLastError(I2C_HandleTypeDef *hi2c) +{ + if (!hi2c) + return I2C_ERROR_NONE; + + return hi2c->LastError; +} + +//-- I2C Master functions ----------------------------------------------------- + +/** + * @brief Передача данных в режиме мастера (блокирующий режим) + * @param hi2c указатель на хендл I2C + * @param addr адрес устройства на шине I2C + * @param buf указатель на буфер данных + * @param size размер данных в байтах + * @param timeout таймаут ожидания (мс) + * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте + */ +OperationStatus I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, + uint16_t addr, + uint8_t *buf, + uint16_t size, + uint32_t timeout) +{ + OperationStatus status; + + if (!hi2c || !buf || size == 0) + { + __i2c_set_error(hi2c, I2C_ERROR_NONE); + return ERROR; + } + + // Проверяем, свободна ли шина + status = __i2c_master_wait_bus_free(hi2c, timeout); + if (status != OK) + return status; + + // Отправляем START + status = __i2c_master_send_start(hi2c, timeout); + if (status != OK) + return status; + + // Отправляем адрес с направлением "запись" + status = __i2c_master_send_address(hi2c, addr, I2C_Direction_Transmitter, timeout); + if (status != OK) + { + // При NACK отправляем STOP и выходим + if (hi2c->LastError == I2C_ERROR_NACK) + { + __i2c_master_send_stop(hi2c, timeout); + } + return status; + } + + // Передаем данные + status = __i2c_master_transmit_blocking(hi2c, buf, size, timeout); + + // Отправляем STOP (даже при ошибке в данных) + __i2c_master_send_stop(hi2c, timeout); + + // Если была ошибка передачи данных, возвращаем ошибку + if (status != OK) + return status; + + // Вызываем callback успешной передачи + if (hi2c->Config->TxCallback) + hi2c->Config->TxCallback(); + + return OK; +} + +/** + * @brief Прием данных в режиме мастера (блокирующий режим) + * @param hi2c указатель на хендл I2C + * @param addr адрес устройства на шине I2C + * @param buf указатель на буфер приема + * @param size количество принимаемых байт + * @param timeout таймаут ожидания (мс) + * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте + */ +OperationStatus I2C_Master_Receive(I2C_HandleTypeDef *hi2c, + uint16_t addr, + uint8_t *buf, + uint16_t size, + uint32_t timeout) +{ + OperationStatus status; + + if (!hi2c || !buf || size == 0) + { + __i2c_set_error(hi2c, I2C_ERROR_NONE); + return ERROR; + } + + // Проверяем, свободна ли шина + status = __i2c_master_wait_bus_free(hi2c, timeout); + if (status != OK) + return status; + + // Отправляем START + status = __i2c_master_send_start(hi2c, timeout); + if (status != OK) + return status; + + // Отправляем адрес с направлением "чтение" + status = __i2c_master_send_address(hi2c, addr, I2C_Direction_Receiver, timeout); + if (status != OK) + { + // При NACK отправляем STOP и выходим + if (hi2c->LastError == I2C_ERROR_NACK) + { + __i2c_master_send_stop(hi2c, timeout); + } + return status; + } + + // Принимаем данные + status = __i2c_master_receive_blocking(hi2c, buf, size, timeout); + + // Отправляем STOP (даже при ошибке приема) + __i2c_master_send_stop(hi2c, timeout); + + // Если была ошибка приема данных, возвращаем ошибку + if (status != OK) + return status; + + // Вызываем callback успешного приема + if (hi2c->Config->RxCallback) + hi2c->Config->RxCallback(); + + return OK; +} + +/** + * @brief Запись регистра устройства (блокирующий режим) + * @param hi2c указатель на хендл I2C + * @param addr адрес устройства на шине I2C + * @param reg адрес регистра + * @param value значение для записи + * @param timeout таймаут ожидания (мс) + * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте + */ +OperationStatus I2C_Master_WriteReg(I2C_HandleTypeDef *hi2c, + uint16_t addr, + uint16_t reg, + uint8_t value, + uint32_t timeout) +{ + uint8_t data[3]; + uint16_t reg_size = (hi2c->Config->I2C_Init.RegSize == I2C_RegSize_8bit) ? 1 : 2; + + if (reg_size == 1) + { + data[0] = (uint8_t)reg; + data[1] = value; + return I2C_Master_Transmit(hi2c, addr, data, 2, timeout); + } + else + { + data[0] = (uint8_t)(reg >> 8); + data[1] = (uint8_t)reg; + data[2] = value; + return I2C_Master_Transmit(hi2c, addr, data, 3, timeout); + } +} + +/** + * @brief Чтение регистра устройства (блокирующий режим) + * @param hi2c указатель на хендл I2C + * @param addr адрес устройства на шине I2C + * @param reg адрес регистра + * @param buf указатель на буфер для данных + * @param size размер данных для чтения + * @param timeout таймаут ожидания (мс) + * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте + */ +OperationStatus I2C_Master_ReadReg(I2C_HandleTypeDef *hi2c, + uint16_t addr, + uint16_t reg, + uint8_t *buf, + uint16_t size, + uint32_t timeout) +{ + OperationStatus status; + uint8_t reg_data[2]; + uint16_t reg_size = (hi2c->Config->I2C_Init.RegSize == I2C_RegSize_8bit) ? 1 : 2; + + // Сначала отправляем адрес регистра + if (reg_size == 1) + { + reg_data[0] = (uint8_t)reg; + status = I2C_Master_Transmit(hi2c, addr, reg_data, 1, timeout); + } + else + { + reg_data[0] = (uint8_t)(reg >> 8); + reg_data[1] = (uint8_t)reg; + status = I2C_Master_Transmit(hi2c, addr, reg_data, 2, timeout); + } + + if (status != OK) + return status; + + // Затем читаем данные + return I2C_Master_Receive(hi2c, addr, buf, size, timeout); +} + +/** + * @brief Запись нескольких регистров (блокирующий режим) + * @param hi2c указатель на хендл I2C + * @param addr адрес устройства на шине I2C + * @param reg начальный адрес регистра + * @param buf указатель на буфер данных + * @param size количество регистров для записи + * @param timeout таймаут ожидания (мс) + * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте + */ +OperationStatus I2C_Master_WriteRegs(I2C_HandleTypeDef *hi2c, + uint16_t addr, + uint16_t reg, + uint8_t *buf, + uint16_t size, + uint32_t timeout) +{ + OperationStatus status; + uint16_t reg_size = (hi2c->Config->I2C_Init.RegSize == I2C_RegSize_8bit) ? 1 : 2; + + // Проверяем, свободна ли шина + status = __i2c_master_wait_bus_free(hi2c, timeout); + if (status != OK) + return status; + + // Отправляем START + status = __i2c_master_send_start(hi2c, timeout); + if (status != OK) + return status; + + // Отправляем адрес с направлением "запись" + status = __i2c_master_send_address(hi2c, addr, I2C_Direction_Transmitter, timeout); + if (status != OK) + { + // При NACK отправляем STOP и выходим + if (hi2c->LastError == I2C_ERROR_NACK) + { + __i2c_master_send_stop(hi2c, timeout); + } + return status; + } + + // Отправляем адрес регистра (первый байт) + if (reg_size == 1) + { + status = __i2c_master_transmit_blocking(hi2c, (uint8_t*)®, 1, timeout); + } + else + { + // Для 16-битного адреса отправляем старший, затем младший байт + uint8_t reg_bytes[2] = {(uint8_t)(reg >> 8), (uint8_t)reg}; + status = __i2c_master_transmit_blocking(hi2c, reg_bytes, 2, timeout); + } + + if (status != OK) + { + __i2c_master_send_stop(hi2c, timeout); + return status; + } + + // Отправляем данные + status = __i2c_master_transmit_blocking(hi2c, buf, size, timeout); + + // Отправляем STOP + __i2c_master_send_stop(hi2c, timeout); + + // Если была ошибка передачи данных, возвращаем ошибку + if (status != OK) + return status; + + // Вызываем callback успешной передачи + if (hi2c->Config->TxCallback) + hi2c->Config->TxCallback(); + + return OK; +} + +/** + * @brief Чтение нескольких регистров (блокирующий режим) + * @param hi2c указатель на хендл I2C + * @param addr адрес устройства на шине I2C + * @param reg начальный адрес регистра + * @param buf указатель на буфер для данных + * @param size количество регистров для чтения + * @param timeout таймаут ожидания (мс) + * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте + */ +OperationStatus I2C_Master_ReadRegs(I2C_HandleTypeDef *hi2c, + uint16_t addr, + uint16_t reg, + uint8_t *buf, + uint16_t size, + uint32_t timeout) +{ + // Для чтения нескольких регистров используется та же функция, что и для одного + // т.к. после отправки адреса регистра можно читать любое количество байт + return I2C_Master_ReadReg(hi2c, addr, reg, buf, size, timeout); +} + +/** + * @brief Проверка наличия устройства на шине + * @param hi2c указатель на хендл I2C + * @param addr адрес устройства на шине I2C + * @param timeout таймаут ожидания (мс) + * @retval OperationStatus OK - если устройство отвечает, ERROR - если нет ответа + */ +OperationStatus I2C_Master_IsDeviceReady(I2C_HandleTypeDef *hi2c, + uint16_t addr, + uint32_t timeout) +{ + OperationStatus status; + + if (!hi2c) + return ERROR; + + // Проверяем, свободна ли шина + status = __i2c_master_wait_bus_free(hi2c, timeout); + if (status != OK) + return status; + + // Отправляем START + status = __i2c_master_send_start(hi2c, timeout); + if (status != OK) + return status; + + // Отправляем адрес с направлением "запись" (любое направление подойдет для проверки) + status = __i2c_master_send_address(hi2c, addr, I2C_Direction_Transmitter, timeout); + + // Отправляем STOP независимо от результата + __i2c_master_send_stop(hi2c, timeout); + + // Если устройство ответило ACK, значит оно готово + if (status == OK && hi2c->LastError == I2C_ERROR_NONE) + return OK; + + return ERROR; +} + +//-- I2C GPIO functions ------------------------------------------------------- + +/** + * @brief Инициализация GPIO для I2C + */ +void i2c_gpio_init(void) +{ +#if USE_I2C==1 + // Получаем структуры + GPIO_Init_TypeDef *sda_config = gpio_get_init(I2C_GPIO_Port, I2C_SDA_Pin); + GPIO_Init_TypeDef *scl_config = gpio_get_init(I2C_GPIO_Port, I2C_SCL_Pin); + + // SDA пин + if (sda_config != NULL) + { + GPIO_StructInit(sda_config); + sda_config->Pin = I2C_SDA_Pin; + sda_config->AltFunc = ENABLE; + sda_config->Digital = ENABLE; + sda_config->OutMode = GPIO_OutMode_OD; // Открытый сток для I2C + sda_config->PullMode = GPIO_PullMode_PU; // Подтяжка к питанию для I2C + GPIO_Init(I2C_GPIO_Port, sda_config); + } + + // SCL пин + if (scl_config != NULL) + { + GPIO_StructInit(scl_config); + scl_config->Pin = I2C_SCL_Pin; + scl_config->AltFunc = ENABLE; + scl_config->Digital = ENABLE; + scl_config->OutMode = GPIO_OutMode_OD; // Открытый сток для I2C + scl_config->PullMode = GPIO_PullMode_PU; // Подтяжка к питанию для I2C + GPIO_Init(I2C_GPIO_Port, scl_config); + } +#endif +} + +/** + * @brief Деинициализация GPIO для I2C + */ +void i2c_gpio_deinit(void) +{ +#if USE_I2C==1 + // Получаем структуры + GPIO_Init_TypeDef *sda_config = gpio_get_init(I2C_GPIO_Port, I2C_SDA_Pin); + GPIO_Init_TypeDef *scl_config = gpio_get_init(I2C_GPIO_Port, I2C_SCL_Pin); + + // Восстанавливаем оригинальные настройки из таблицы + if (sda_config != NULL) + { + GPIO_StructInit(sda_config); + sda_config->Pin = I2C_SDA_Pin; + GPIO_Init(I2C_GPIO_Port, sda_config); + } + + if (scl_config != NULL) + { + GPIO_StructInit(scl_config); + scl_config->Pin = I2C_SCL_Pin; + GPIO_Init(I2C_GPIO_Port, scl_config); + } +#endif +} + +//-- I2C private functions ---------------------------------------------------- + +/** + * @brief Установка ошибки I2C + */ +static void __i2c_set_error(I2C_HandleTypeDef *hi2c, uint8_t error) +{ + if (!hi2c) + return; + + hi2c->LastError = error; + + // Вызываем callback ошибки, если он установлен + if (hi2c->Config->ErrCallback) + hi2c->Config->ErrCallback(error); +} + +/** + * @brief Ожидание освобождения шины I2C + */ +static OperationStatus __i2c_master_wait_bus_free(I2C_HandleTypeDef *hi2c, uint32_t timeout) +{ + uint32_t starttick = millis(); + + while(I2C_BusBusyStatus() == SET) + { + if(millis() - starttick > timeout) + { + __i2c_set_error(hi2c, I2C_ERROR_BUS_BUSY); + return ERROR; + } + } + + return OK; +} + +/** + * @brief Отправка условия START + */ +static OperationStatus __i2c_master_send_start(I2C_HandleTypeDef *hi2c, uint32_t timeout) +{ + uint32_t starttick = millis(); + I2C_State_TypeDef state; + + // Формируем START + I2C_StartCmd(); + + // Ожидаем подтверждения формирования START + while(1) + { + state = I2C_GetState(); + + if (state == I2C_State_STDONE || state == I2C_State_HMTMCOK) + { + // START успешно сформирован + return OK; + } + else if (state == I2C_State_BERROR) + { + __i2c_set_error(hi2c, I2C_ERROR_BUS_ERROR); + return ERROR; + } + else if (state == I2C_State_IDLARL || state == I2C_State_HIDLARL) + { + __i2c_set_error(hi2c, I2C_ERROR_ARBITRATION_LOST); + return ERROR; + } + + if(millis() - starttick > timeout) + { + __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT); + return ERROR; + } + } +} + +/** + * @brief Отправка адреса устройства + */ +static OperationStatus __i2c_master_send_address(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t direction, uint32_t timeout) +{ + uint8_t address; + uint32_t starttick = millis(); + I2C_State_TypeDef state; + + // Формируем адрес + if (addr > 0x7F) + { + // 10-битный адрес - первая часть всегда на запись + address = 0xF0 | ((addr >> 7) & 0x06); + I2C_SetData(address | I2C_Direction_Transmitter); + } + else + { + // 7-битный адрес + address = (addr << 1); + I2C_SetData(address | direction); + } + + // Сбрасываем флаг прерывания + I2C_ITStatusClear(); + + // Ожидаем подтверждения адреса + while(1) + { + state = I2C_GetState(); + + // ACK получен для 7-битного адреса + if ((direction == I2C_Direction_Transmitter && + (state == I2C_State_MTADPA || state == I2C_State_HMTADPA)) || + (direction == I2C_Direction_Receiver && + (state == I2C_State_MRADPA || state == I2C_State_HMRADPA))) + { + return OK; + } + // NACK получен + else if ((direction == I2C_Direction_Transmitter && + (state == I2C_State_MTADNA || state == I2C_State_HMTADNA)) || + (direction == I2C_Direction_Receiver && + (state == I2C_State_MRADNA || state == I2C_State_HMRADNA))) + { + __i2c_set_error(hi2c, I2C_ERROR_NACK); + return ERROR; + } + // Ошибка шины + else if (state == I2C_State_BERROR) + { + __i2c_set_error(hi2c, I2C_ERROR_BUS_ERROR); + return ERROR; + } + // Потеря арбитража + else if (state == I2C_State_IDLARL || state == I2C_State_HIDLARL) + { + __i2c_set_error(hi2c, I2C_ERROR_ARBITRATION_LOST); + return ERROR; + } + + if(millis() - starttick > timeout) + { + __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT); + return ERROR; + } + } +} + +/** + * @brief Отправка условия STOP + */ +static OperationStatus __i2c_master_send_stop(I2C_HandleTypeDef *hi2c, uint32_t timeout) +{ + uint32_t starttick = millis(); + + // Формируем STOP + I2C_StopCmd(); + + // Сбрасываем флаг прерывания + I2C_ITStatusClear(); + + // Ожидаем освобождения шины (опционально, не обязательно) + // while(I2C_BusBusyStatus() == SET) + // { + // if(millis() - starttick > timeout) + // { + // __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT); + // return ERROR; + // } + // } + + return OK; +} + +/** + * @brief Блокирующая передача данных + */ +static OperationStatus __i2c_master_transmit_blocking(I2C_HandleTypeDef *hi2c, uint8_t *buf, uint16_t size, uint32_t timeout) +{ + uint32_t starttick; + I2C_State_TypeDef state; + + for(uint16_t i = 0; i < size; i++) + { + // Отправляем байт данных + I2C_SetData(buf[i]); + + // Сбрасываем флаг прерывания + I2C_ITStatusClear(); + + starttick = millis(); + + // Ожидаем подтверждения байта + while(1) + { + state = I2C_GetState(); + + // ACK получен + if (state == I2C_State_MTDAPA || state == I2C_State_HMTDAPA) + { + break; + } + // NACK получен + else if (state == I2C_State_MTDANA || state == I2C_State_HMTDANA) + { + __i2c_set_error(hi2c, I2C_ERROR_NACK); + return ERROR; + } + // Ошибка шины + else if (state == I2C_State_BERROR) + { + __i2c_set_error(hi2c, I2C_ERROR_BUS_ERROR); + return ERROR; + } + // Потеря арбитража + else if (state == I2C_State_IDLARL || state == I2C_State_HIDLARL) + { + __i2c_set_error(hi2c, I2C_ERROR_ARBITRATION_LOST); + return ERROR; + } + + if(millis() - starttick > timeout) + { + __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT); + return ERROR; + } + } + } + + return OK; +} + +/** + * @brief Блокирующий прием данных + */ +static OperationStatus __i2c_master_receive_blocking(I2C_HandleTypeDef *hi2c, uint8_t *buf, uint16_t size, uint32_t timeout) +{ + uint32_t starttick; + I2C_State_TypeDef state; + + for(uint16_t i = 0; i < size; i++) + { + starttick = millis(); + + // Ожидаем приема байта + while(1) + { + state = I2C_GetState(); + + // Байт получен с ACK или NACK + if (state == I2C_State_MRDAPA || state == I2C_State_MRDANA || + state == I2C_State_HMRDAPA || state == I2C_State_HMRDANA) + { + // Читаем данные + buf[i] = I2C_GetData(); + + // Для последнего байта отправляем NACK + if (i == size - 1) + { + I2C_NACKCmd(); + } + + break; + } + // Ошибка шины + else if (state == I2C_State_BERROR) + { + __i2c_set_error(hi2c, I2C_ERROR_BUS_ERROR); + return ERROR; + } + // Потеря арбитража + else if (state == I2C_State_IDLARL || state == I2C_State_HIDLARL) + { + __i2c_set_error(hi2c, I2C_ERROR_ARBITRATION_LOST); + return ERROR; + } + + if(millis() - starttick > timeout) + { + __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT); + return ERROR; + } + } + } + + return OK; +} diff --git a/MDK-ARM/Core/App/i2c.h b/MDK-ARM/Core/App/i2c.h new file mode 100644 index 0000000..aa90e8b --- /dev/null +++ b/MDK-ARM/Core/App/i2c.h @@ -0,0 +1,183 @@ +/** + ****************************************************************************** + * @file i2c.h + * @author Разваляев Алексей + * @brief Драйвер I2C на основе PLIB035. + * Данный файл содержит определения типов, структур и прототипы функций + * для работы с I2C, включая: + * + Структуры и typedef для I2C + * + Прототипы функций для инициализации и API драйвера + * + ****************************************************************************** + */ + +#ifndef __I2C_H +#define __I2C_H + +//-- Includes ------------------------------------------------------------------ +#include "plib035.h" +#include "plib035_i2c.h" +#include "retarget_conf.h" + +//-- Defines ------------------------------------------------------------------- +// Дефайны для пинов I2C +#define I2C_SCL_Pin GPIO_Pin_0 /**< PA0 — I2C SCL */ +#define I2C_SDA_Pin GPIO_Pin_1 /**< PA1 — I2C SDA */ +#define I2C_GPIO_Port GPIOA /**< GPIO порт I2C */ + +// Стандартные частоты I2C +#define I2C_STANDARD_MODE 100000 /**< 100 kHz */ +#define I2C_FAST_MODE 400000 /**< 400 kHz */ +#define I2C_FAST_MODE_PLUS 1000000 /**< 1 MHz */ +#define I2C_HIGH_SPEED_MODE 3400000 /**< 3.4 MHz */ + +// Максимальное время ожидания по умолчанию (мс) +#define I2C_DEFAULT_TIMEOUT 1000 + +// Коды ошибок I2C +#define I2C_ERROR_NONE 0x00 +#define I2C_ERROR_BUS_BUSY 0x01 +#define I2C_ERROR_TIMEOUT 0x02 +#define I2C_ERROR_NACK 0x03 +#define I2C_ERROR_ARBITRATION_LOST 0x04 +#define I2C_ERROR_BUS_ERROR 0x05 +#define I2C_ERROR_PEC_FAIL 0x06 + +//-- Types --------------------------------------------------------------------- + +/** + * @brief Типы callback-функций I2C + */ +typedef enum +{ + I2C_Callback_Tx, /*!< Передача данных завершена */ + I2C_Callback_Rx, /*!< Приём данных завершён */ + I2C_Callback_Addr, /*!< Ведомый получил свой адрес */ + I2C_Callback_Error, /*!< Ошибка I2C */ + +} I2C_CallbackTypeDef; + +/** + * @brief Режимы работы I2C + */ +typedef enum +{ + I2C_Mode_Master, /*!< Режим мастера */ + I2C_Mode_Slave, /*!< Режим ведомого */ +} I2C_ModeTypeDef; + +/** + * @brief Направление передачи I2C + */ +typedef enum +{ + I2C_Direction_Transmitter, /*!< Направление: передатчик */ + I2C_Direction_Receiver, /*!< Направление: приемник */ +} I2C_DirectionTypeDef; + +/** + * @brief Размер регистра устройства + */ +typedef enum +{ + I2C_RegSize_8bit, /*!< Размер регистра: 8 бит */ + I2C_RegSize_16bit, /*!< Размер регистра: 16 бит */ +} I2C_RegSizeTypeDef; + +/** + * @brief Структура инициализации I2C + */ +typedef struct +{ + I2C_ModeTypeDef Mode; /*!< Режим работы: I2C_Mode_Master или I2C_Mode_Slave */ + FunctionalState HSMode; /*!< Высокоскоростной режим */ + FunctionalState Addr10Bit; /*!< 10-битная адресация */ + I2C_RegSizeTypeDef RegSize; /*!< Размер регистра: I2C_RegSize_8bit или I2C_RegSize_16bit */ + uint16_t SlaveAddr; /*!< Адрес ведомого (для режима Slave) */ + + FunctionalState Timeout; /*!< Таймаут: */ + I2C_TimeoutClkDiv_TypeDef TimeoutClkDiv; /*!< Делитель тактирования таймаута */ + uint8_t TimeoutLoad; /*!< Значение загрузки счетчика таймаута */ + + FunctionalState AlertResponse; /*!< Ответ на тревогу */ + FunctionalState GlobalCall; /*!< Общий вызов */ + +} I2C_Init_TypeDef; + +/** + * @brief Расширенная конфигурация I2C + */ +typedef struct +{ + I2C_Init_TypeDef I2C_Init; /*!< Базовая конфигурация I2C */ + + uint32_t I2CFreq; /*!< Частота тактирования I2C в Гц */ + uint32_t FSFreq; /*!< Частота SCL в стандартном режиме в Гц */ + uint32_t HSFreq; /*!< Частота SCL в высокоскоростном режиме в Гц */ + + /* Callback функции */ + void (*TxCallback)(void); /*!< Вызывается при завершении передачи */ + void (*RxCallback)(void); /*!< Вызывается при завершении приема */ + void (*AddrCallback)(void); /*!< Вызывается когда ведомый получает свой адрес */ + void (*ErrCallback)(uint8_t error); /*!< Вызывается при ошибке I2C с кодом ошибки */ + +} I2C_ExtInit_TypeDef; + +/** + * @brief Хендл I2C + */ +typedef struct +{ + I2C_TypeDef *Instance; /*!< Регистры I2C */ + I2C_ExtInit_TypeDef *Config; /*!< Конфигурация I2C */ + + uint8_t LastError; /*!< Код последней ошибки */ + +} I2C_HandleTypeDef; + +//-- External handles ---------------------------------------------------------- + +extern I2C_HandleTypeDef hi2c; + +//-- Exported functions prototypes --------------------------------------------- + +/* Init functions */ + +/* Первичная инициализация I2C */ +void i2c_init_first(void); +/* Инициализация I2C */ +OperationStatus i2c_init(I2C_HandleTypeDef *hi2c, I2C_ExtInit_TypeDef *NewConfig); +/* Инициализация GPIO для I2C */ +void i2c_gpio_init(void); +/* Деинициализация GPIO для I2C */ +void i2c_gpio_deinit(void); + +/* API functions*/ + +/* Установка callback-функции I2C */ +OperationStatus I2C_Set_Callback(I2C_HandleTypeDef *hi2c, I2C_CallbackTypeDef CallbackType, void (*Callback)()); +/* Запуск I2C */ +OperationStatus I2C_Start(I2C_HandleTypeDef *hi2c); +/* Остановка I2C */ +OperationStatus I2C_Stop(I2C_HandleTypeDef *hi2c); + +/* Получение кода последней ошибки */ +uint8_t I2C_GetLastError(I2C_HandleTypeDef *hi2c); + +/* Передача данных в режиме мастера (блокирующий режим) */ +OperationStatus I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t *buf, uint16_t size, uint32_t timeout); +/* Прием данных в режиме мастера (блокирующий режим) */ +OperationStatus I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t *buf, uint16_t size, uint32_t timeout); +/* Запись регистра устройства (блокирующий режим) */ +OperationStatus I2C_Master_WriteReg(I2C_HandleTypeDef *hi2c, uint16_t addr, uint16_t reg, uint8_t value, uint32_t timeout); +/* Чтение регистра устройства (блокирующий режим) */ +OperationStatus I2C_Master_ReadReg(I2C_HandleTypeDef *hi2c, uint16_t addr, uint16_t reg, uint8_t *buf, uint16_t size, uint32_t timeout); +/* Запись нескольких регистров (блокирующий режим) */ +OperationStatus I2C_Master_WriteRegs(I2C_HandleTypeDef *hi2c, uint16_t addr, uint16_t reg, uint8_t *buf, uint16_t size, uint32_t timeout); +/* Чтение нескольких регистров (блокирующий режим) */ +OperationStatus I2C_Master_ReadRegs(I2C_HandleTypeDef *hi2c, uint16_t addr, uint16_t reg, uint8_t *buf, uint16_t size, uint32_t timeout); + +/* Проверка наличия устройства на шине */ +OperationStatus I2C_Master_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t addr, uint32_t timeout); + +#endif /*__I2C_H*/ diff --git a/MDK-ARM/Core/App/main.c b/MDK-ARM/Core/App/main.c index 3e1b17f..c939f39 100644 --- a/MDK-ARM/Core/App/main.c +++ b/MDK-ARM/Core/App/main.c @@ -17,8 +17,9 @@ void periph_init() { sysclk_init(); uart_init_first(); - adc_init_first(); tmr_init_first(); + adc_init_first(); + i2c_init_first(); gpio_init(); #ifdef RETARGET retarget_init(); diff --git a/MDK-ARM/Core/Config/periph_config.h b/MDK-ARM/Core/Config/periph_config.h index 734aee3..19811f3 100644 --- a/MDK-ARM/Core/Config/periph_config.h +++ b/MDK-ARM/Core/Config/periph_config.h @@ -33,6 +33,7 @@ #include "uart.h" #include "tmr.h" #include "adc.h" +#include "i2c.h" /* Обработчик ошибок */ void Error_Handler(void); @@ -59,6 +60,10 @@ void Error_Handler(void); #define USE_ADC_DC2 0 /*!< Использовать Компаратор 2 */ #define USE_ADC_DC3 0 /*!< Использовать Компаратор 3 */ +/* I2C */ +#define USE_I2C 0 /*!< Использовать I2C */ + + /** @note Для RETARGET надо объявить этот дефайн в проекте Options for Target -> C/C++ -> Defines */ @@ -306,6 +311,23 @@ static ADC_DC_ExtInit_TypeDef adc_dc3_config = { }; #endif + +//-- I2C Конфигурации --------------------------------------------------------- +#if USE_I2C==1 +static I2C_ExtInit_TypeDef i2c_config = { +//Mode, HSMode, Addr10Bit, RegSize, SlaveAddr + I2C_Mode_Master, DISABLE, DISABLE, I2C_RegSize_8bit, 0x00, +//Timeout, TimeoutClkDiv, TimeoutLoad + DISABLE, I2C_TimeoutClkDiv_Div4, 0x00, +//AlertResponse, GlobalCall + DISABLE, DISABLE, +//I2CFreq, FSFreq, HSFreq + SYSCLK_CORE_CLOCK_MHZ * __MHZ, I2C_STANDARD_MODE, I2C_HIGH_SPEED_MODE, +//TxCallback, RxCallback, AddrCallback, ErrCallback + NULL, NULL, NULL, NULL +}; +#endif + //-- NVIC Конфигурации -------------------------------------------------------- /** @brief Приоритеты прерываний, 0 - самый высокий приоритет*/ static uint8_t NCIV_Priorities[] = diff --git a/MDK-ARM/Template.uvoptx b/MDK-ARM/Template.uvoptx index cd80bec..5ef52ac 100644 --- a/MDK-ARM/Template.uvoptx +++ b/MDK-ARM/Template.uvoptx @@ -380,6 +380,18 @@ 0 0 0 + .\Core\App\i2c.c + i2c.c + 0 + 0 + + + 2 + 11 + 1 + 0 + 0 + 0 .\Core\App\sysclk.c sysclk.c 0 @@ -387,7 +399,7 @@ 2 - 11 + 12 1 0 0 @@ -407,7 +419,7 @@ 0 3 - 12 + 13 5 0 0 @@ -419,7 +431,7 @@ 3 - 13 + 14 5 0 0 @@ -431,7 +443,7 @@ 3 - 14 + 15 5 0 0 @@ -443,7 +455,7 @@ 3 - 15 + 16 5 0 0 @@ -455,7 +467,7 @@ 3 - 16 + 17 5 0 0 @@ -467,7 +479,7 @@ 3 - 17 + 18 5 0 0 @@ -479,7 +491,7 @@ 3 - 18 + 19 1 0 0 @@ -491,7 +503,7 @@ 3 - 19 + 20 1 0 0 @@ -503,7 +515,7 @@ 3 - 20 + 21 5 0 0 @@ -515,7 +527,7 @@ 3 - 21 + 22 1 0 0 @@ -535,7 +547,7 @@ 0 4 - 22 + 23 1 0 0 @@ -547,7 +559,7 @@ 4 - 23 + 24 2 0 0 @@ -567,7 +579,7 @@ 0 5 - 24 + 25 1 0 0 @@ -579,7 +591,7 @@ 5 - 25 + 26 1 0 0 @@ -591,7 +603,7 @@ 5 - 26 + 27 1 0 0 @@ -603,7 +615,7 @@ 5 - 27 + 28 1 0 0 @@ -615,7 +627,7 @@ 5 - 28 + 29 1 0 0 @@ -627,7 +639,7 @@ 5 - 29 + 30 1 0 0 @@ -639,7 +651,7 @@ 5 - 30 + 31 1 0 0 @@ -651,7 +663,7 @@ 5 - 31 + 32 1 0 0 @@ -663,7 +675,7 @@ 5 - 32 + 33 1 0 0 @@ -675,7 +687,7 @@ 5 - 33 + 34 1 0 0 @@ -687,7 +699,7 @@ 5 - 34 + 35 1 0 0 @@ -699,7 +711,7 @@ 5 - 35 + 36 1 0 0 @@ -711,7 +723,7 @@ 5 - 36 + 37 1 0 0 @@ -723,7 +735,7 @@ 5 - 37 + 38 1 0 0 @@ -735,7 +747,7 @@ 5 - 38 + 39 1 0 0 @@ -755,7 +767,7 @@ 0 6 - 39 + 40 1 0 0 @@ -767,7 +779,7 @@ 6 - 40 + 41 1 0 0 @@ -779,7 +791,7 @@ 6 - 41 + 42 5 0 0 diff --git a/MDK-ARM/Template.uvprojx b/MDK-ARM/Template.uvprojx index 2fbcc92..0cf3359 100644 --- a/MDK-ARM/Template.uvprojx +++ b/MDK-ARM/Template.uvprojx @@ -434,6 +434,11 @@ 1 .\Core\App\adc.c + + i2c.c + 1 + .\Core\App\i2c.c + sysclk.c 1 diff --git a/Pinout.xlsx b/Pinout.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..28e500fe25e9dda4b5c4a89b142f1f264025f1d0 GIT binary patch literal 11414 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