Files
zh_avr_i2c/zh_avr_i2c.c
2025-08-08 07:48:16 +03:00

579 lines
19 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "zh_avr_i2c.h"
avr_err_t zh_avr_i2c_master_init(void)
{
}
avr_err_t zh_avr_i2c_master_transmit(const uint8_t addr, uint8_t *data, uint8_t size, size_t delay)
{
}
avr_err_t zh_avr_i2c_master_receive(const uint8_t addr, uint8_t *data, uint8_t size, size_t delay)
{
}
avr_err_t zh_avr_i2c_master_transmit_receive(const uint8_t addr, uint8_t *write_data, uint8_t write_size, uint8_t *read_data, uint8_t read_size, size_t delay)
{
}
void DoNothing(void);
uint8_t i2c_Do; // Переменная состояния передатчика IIC
uint8_t i2c_InBuff[i2c_MasterBytesRX]; // Буфер прием при работе как Slave
uint8_t i2c_OutBuff[i2c_MasterBytesTX]; // Буфер передачи при работе как Slave
uint8_t i2c_SlaveIndex; // Индекс буфера Slave
uint8_t i2c_Buffer[i2c_MaxBuffer]; // Буфер для данных работы в режиме Master
uint8_t i2c_index; // Индекс этого буфера
uint8_t i2c_ByteCount; // Число байт передаваемых
uint8_t i2c_SlaveAddress; // Адрес подчиненного
uint8_t i2c_PageAddress[i2c_MaxPageAddrLgth]; // Буфер адреса страниц (для режима с sawsarp)
uint8_t i2c_PageAddrIndex; // Индекс буфера адреса страниц
uint8_t i2c_PageAddrCount; // Число байт в адресе страницы для текущего Slave
// Указатели выхода из автомата:
IIC_F MasterOutFunc = &DoNothing; // в Master режиме
IIC_F SlaveOutFunc = &DoNothing; // в режиме Slave
IIC_F ErrorOutFunc = &DoNothing; // в результате ошибки в режиме Master
uint8_t WorkLog[100]; // Лог пишем сюда
uint8_t WorkIndex = 0; // Индекс лога
ISR(TWI_vect) // Прерывание TWI Тут наше все.
{
/*
PORTB ^= 0x01; // Дрыгаем ногой порта, для синхронизации логического анализатора и отметок вызова TWI
// Отладочный кусок. Вывод лога работы конечного автомата в буфер памяти, а потом. По окончании работы через UART на волю
if (WorkIndex <99) // Если лог не переполнен
{
if (TWSR) // Статус нулевой?
{
WorkLog[WorkIndex]= TWSR; // Пишем статус в лог
WorkIndex++;
}
else
{
WorkLog[WorkIndex]= 0xFF; // Если статус нулевой то вписываем FF
WorkIndex++;
}
}
*/
switch (TWSR & 0xF8) // Отсекаем биты прескалера
{
case 0x00: // Bus Fail (автобус сломался)
{
i2c_Do |= i2c_ERR_BF;
TWCR = 0 << TWSTA |
1 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Go!
MACRO_i2c_WhatDo_ErrorOut break;
}
case 0x08: // Старт был, а затем мы:
{
i2c_index = 0; // Обнуляем индекс буфера.
if ((i2c_Do & i2c_type_msk) == i2c_sarp) // В зависимости от режима
{
i2c_SlaveAddress |= 0x01; // Шлем Addr+R
}
else // Или
{
i2c_SlaveAddress &= 0xFE; // Шлем Addr+W
}
TWDR = i2c_SlaveAddress; // Адрес слейва
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Go!
break;
}
case 0x10: // Повторный старт был, а затем мы
{
if ((i2c_Do & i2c_type_msk) == i2c_sawsarp) // В зависимости от режима
{
i2c_SlaveAddress |= 0x01; // Шлем Addr+R
}
else
{
i2c_SlaveAddress &= 0xFE; // Шлем Addr+W
}
// To Do: Добавить сюда обработку ошибок
TWDR = i2c_SlaveAddress; // Адрес слейва
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Go!
break;
}
case 0x18: // Был послан SLA+W получили ACK, а затем:
{
if ((i2c_Do & i2c_type_msk) == i2c_sawp) // В зависимости от режима
{
TWDR = i2c_Buffer[i2c_index]; // Шлем байт данных
i2c_index++; // Увеличиваем указатель буфера
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Go!
}
if ((i2c_Do & i2c_type_msk) == i2c_sawsarp)
{
i2c_PageAddrIndex = 0; // Обнулили указатель буфера адреса страницы.
TWDR = i2c_PageAddress[i2c_PageAddrIndex]; // Или шлем адрес странцы (по сути тоже байт данных)
i2c_PageAddrIndex++;
// Увеличиваем указатель буфера страницы
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Go!
}
}
break;
case 0x20: // Был послан SLA+W получили NACK - слейв либо занят, либо его нет дома.
{
i2c_Do |= i2c_ERR_NA;
// Код ошибки
TWCR = 0 << TWSTA |
1 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Шлем шине Stop
MACRO_i2c_WhatDo_ErrorOut // Обрабатываем событие ошибки;
break;
}
case 0x28: // Байт данных послали, получили ACK! (если sawp - это был байт данных. если sawsarp - байт адреса страницы)
{ // А дальше:
if ((i2c_Do & i2c_type_msk) == i2c_sawp) // В зависимости от режима
{
if (i2c_index == i2c_ByteCount) // Если был байт данных последний
{
TWCR = 0 << TWSTA |
1 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Шлем Stop
MACRO_i2c_WhatDo_MasterOut // И выходим в обработку стопа
}
else
{
TWDR = i2c_Buffer[i2c_index]; // Либо шлем еще один байт
i2c_index++;
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Go!
}
}
if ((i2c_Do & i2c_type_msk) == i2c_sawsarp) // В другом режиме мы
{
if (i2c_PageAddrIndex == i2c_PageAddrCount) // Если последний байт адреса страницы
{
TWCR = 1 << TWSTA |
0 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Запускаем Повторный старт!
}
else
{ // Иначе
TWDR = i2c_PageAddress[i2c_PageAddrIndex]; // шлем еще один адрес страницы
i2c_PageAddrIndex++; // Увеличиваем индекс счетчика адреса страниц
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Go!
}
}
}
break;
case 0x30: // Байт ушел, но получили NACK причин две. 1я передача оборвана слейвом и так надо. 2я слейв сглючил.
{
i2c_Do |= i2c_ERR_NK; // Запишем статус ошибки. Хотя это не факт, что ошибка.
TWCR = 0 << TWSTA |
1 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Шлем Stop
MACRO_i2c_WhatDo_MasterOut // Отрабатываем событие выхода
break;
}
case 0x38: // Коллизия на шине. Нашелся кто то поглавней
{
i2c_Do |= i2c_ERR_LP; // Ставим ошибку потери приоритета
// Настраиваем индексы заново.
i2c_index = 0;
i2c_PageAddrIndex = 0;
TWCR = 1 << TWSTA |
0 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Как только шина будет свободна
break; // попробуем передать снова.
}
case 0x40: // Послали SLA+R получили АСК. А теперь будем получать байты
{
if (i2c_index + 1 == i2c_ByteCount) // Если буфер кончится на этом байте, то
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
0 << TWEA |
1 << TWEN |
1 << TWIE; // Требуем байт, а в ответ потом пошлем NACK(Disconnect)
} // Что даст понять слейву, что мол хватит гнать. И он отпустит шину
else
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // Или просто примем байт и скажем потом ACK
}
break;
}
case 0x48: // Послали SLA+R, но получили NACK. Видать slave занят или его нет дома.
{
i2c_Do |= i2c_ERR_NA;
// Код ошибки No Answer
TWCR = 0 << TWSTA |
1 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Шлем Stop
MACRO_i2c_WhatDo_ErrorOut // Отрабатываем выходную ситуацию ошибки
break;
}
case 0x50: // Приняли байт.
{
i2c_Buffer[i2c_index] = TWDR; // Забрали его из буфера
i2c_index++;
// To Do: Добавить проверку переполнения буфера. А то мало ли что юзер затребует
if (i2c_index + 1 == i2c_ByteCount) // Если остался еще один байт из тех, что мы хотели считать
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
0 << TWEA |
1 << TWEN |
1 << TWIE; // Затребываем его и потом пошлем NACK (Disconnect)
}
else
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // Если нет, то затребываем следующий байт, а в ответ скажем АСК
}
break;
}
case 0x58: // Вот мы взяли последний байт, сказали NACK слейв обиделся и отпал.
{
i2c_Buffer[i2c_index] = TWDR; // Взяли байт в буфер
TWCR = 0 << TWSTA |
1 << TWSTO |
1 << TWINT |
i2c_i_am_slave << TWEA |
1 << TWEN |
1 << TWIE; // Передали Stop
MACRO_i2c_WhatDo_MasterOut // Отработали точку выхода
break;
}
// IIC Slave ============================================================================
case 0x68: // RCV SLA+W Low Priority // Словили свой адрес во время передачи мастером
case 0x78: // RCV SLA+W Low Priority (Broadcast) // Или это был широковещательный пакет. Не важно
{
i2c_Do |= i2c_ERR_LP | i2c_Interrupted; // Ставим флаг ошибки Low Priority, а также флаг того, что мастера прервали
// Restore Trans after.
i2c_index = 0; // Подготовили прерваную передачу заново
i2c_PageAddrIndex = 0;
} // И пошли дальше. Внимание!!! break тут нет, а значит идем в "case 60"
case 0x60: // RCV SLA+W Incoming? // Или просто получили свой адрес
case 0x70: // RCV SLA+W Incoming? (Broascast) // Или широковещательный пакет
{
i2c_Do |= i2c_Busy; // Занимаем шину. Чтобы другие не совались
i2c_SlaveIndex = 0; // Указатель на начало буфера слейва, Неважно какой буфер. Не ошибемся
if (i2c_MasterBytesRX == 1) // Если нам суждено принять всего один байт, то готовимся принять его
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
0 << TWEA |
1 << TWEN |
1 << TWIE; // Принять и сказать пошли все н... NACK!
}
else
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // А если душа шире чем один байт, то сожрем и потребуем еще ACK!
}
break;
}
case 0x80: // RCV Data Byte // И вот мы приняли этот байт. Наш или широковещательный. Не важно
case 0x90: // RCV Data Byte (Broadcast)
{
i2c_InBuff[i2c_SlaveIndex] = TWDR; // Сжираем его в буфер.
i2c_SlaveIndex++; // Сдвигаем указатель
if (i2c_SlaveIndex == i2c_MasterBytesRX - 1) // Свободно место всего под один байт?
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
0 << TWEA |
1 << TWEN |
1 << TWIE; // Приянть его и сказать NACK!
}
else
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // Места еще дофига? Принять и ACK!
}
break;
}
case 0x88: // RCV Last Byte // Приянли последний байт
case 0x98: // RCV Last Byte (Broadcast)
{
i2c_InBuff[i2c_SlaveIndex] = TWDR; // Сожрали его в буфер
if (i2c_Do & i2c_Interrupted) // Если у нас был прерываный сеанс от имени мастера
{
TWCR = 1 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // Влепим в шину свой Start поскорей и сделаем еще одну попытку
}
else
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // Если не было такого факта, то просто отвалимся и будем ждать
}
MACRO_i2c_WhatDo_SlaveOut // И лениво отработаем наш выходной экшн для слейва
break;
}
case 0xA0: // Ой, мы получили Повторный старт. Но чо нам с ним делать?
{
// Можно, конечно, сделать вспомогательный автомат, чтобы обрабатывать еще и адреса внутренних страниц, подобно еепромке.
// Но я не стал заморачиваться. В этом случае делается это тут.
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // просто разадресуемся, проигнорировав этот посыл
break;
}
case 0xB0: // Поймали свой адрес на чтение во время передачи Мастером
{
i2c_Do |= i2c_ERR_LP | i2c_Interrupted; // Ну чо, коды ошибки и флаг прерваной передачи.
// Восстанавливаем индексы
i2c_index = 0;
i2c_PageAddrIndex = 0;
} // Break нет! Идем дальше
case 0xA8: // Либо просто словили свой адрес на чтение
{
i2c_SlaveIndex = 0; // Индексы слейвовых массивов на 0
TWDR = i2c_OutBuff[i2c_SlaveIndex]; // Чтож, отдадим байт из тех что есть.
if (i2c_MasterBytesTX == 1)
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
0 << TWEA |
1 << TWEN |
1 << TWIE; // Если он последний, мы еще на NACK в ответ надеемся
}
else
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // А если нет, то ACK ждем
}
break;
}
case 0xB8: // Послали байт, получили ACK
{
i2c_SlaveIndex++; // Значит продолжаем дискотеку. Берем следующий байт
TWDR = i2c_OutBuff[i2c_SlaveIndex]; // Даем его мастеру
if (i2c_SlaveIndex == i2c_MasterBytesTX - 1) // Если он последний был, то
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
0 << TWEA |
1 << TWEN |
1 << TWIE; // Шлем его и ждем NACK
}
else
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
0 << TWEN |
1 << TWIE; // Если нет, то шлем и ждем ACK
}
break;
}
case 0xC0: // Мы выслали последний байт, больше у нас нет, получили NACK
case 0xC8: // или ACK. В данном случае нам пох. Т.к. больше байтов у нас нет.
{
if (i2c_Do & i2c_Interrupted) // Если там была прерваная передача мастера
{ // То мы ему ее вернем
i2c_Do &= i2c_NoInterrupted;
// Снимем флаг прерваности
TWCR = 1 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // Сгенерим старт сразу же как получим шину.
}
else
{
TWCR = 0 << TWSTA |
0 << TWSTO |
1 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // Если мы там одни, то просто отдадим шину
}
MACRO_i2c_WhatDo_SlaveOut // И отработаем выход слейва. Впрочем, он тут
// Не особо то нужен. Разве что как сигнал, что мастер
break; // Нас почтил своим визитом.
}
default:
break;
}
}
void DoNothing(void) // Функция пустышка, затыкать несуществующие ссылки
{
}
void Init_i2c(void) // Настройка режима мастера
{
i2c_PORT |= 1 << i2c_SCL | 1 << i2c_SDA; // Включим подтяжку на ноги, вдруг юзер на резисторы пожмотился
i2c_DDR &= ~(1 << i2c_SCL | 1 << i2c_SDA);
TWBR = 0x80; // Настроим битрейт
TWSR = 0x00;
}
void Init_Slave_i2c(IIC_F Addr) // Настройка режима слейва (если нужно)
{
TWAR = i2c_MasterAddress; // Внесем в регистр свой адрес, на который будем отзываться.
// 1 в нулевом бите означает, что мы отзываемся на широковещательные пакеты
SlaveOutFunc = Addr; // Присвоим указателю выхода по слейву функцию выхода
TWCR = 0 << TWSTA |
0 << TWSTO |
0 << TWINT |
1 << TWEA |
1 << TWEN |
1 << TWIE; // Включаем агрегат и начинаем слушать шину.
}