This commit is contained in:
2025-08-04 12:44:34 +03:00
parent 1143ec3eb5
commit 13d46a62ef
3 changed files with 637 additions and 0 deletions

74
include/zh_avr_i2c.h Normal file
View File

@@ -0,0 +1,74 @@
#ifndef IICULTIMATE_H
#define IICULTIMATE_H
#include <avr/io.h>
#include <avr/interrupt.h>
// #include <avrlibtypes.h>
// #include <avrlibdefs.h>
#define i2c_PORT PORTC // Порт где сидит нога TWI
#define i2c_DDR DDRC
#define i2c_SCL 0 // Биты соответствующих выводов
#define i2c_SDA 1
#define i2c_MasterAddress 0x32 // Адрес на который будем отзываться
#define i2c_i_am_slave 1 // Если мы еще и слейвом работаем то 1. А то не услышит!
#define i2c_MasterBytesRX 1 // Величина принимающего буфера режима Slave, т.е. сколько байт жрем.
#define i2c_MasterBytesTX 1 // Величина Передающего буфера режима Slave , т.е. сколько байт отдаем за сессию.
#define i2c_MaxBuffer 3 // Величина буфера Master режима RX-TX. Зависит от того какой длины строки мы будем гонять
#define i2c_MaxPageAddrLgth 2 // Максимальная величина адреса страницы. Обычно адрес страницы это один или два байта.
// Зависит от типа ЕЕПРОМ или другой микросхемы.
#define i2c_type_msk 0b00001100 // Маска режима
#define i2c_sarp 0b00000000 // Start-Addr_R-Read-Stop Это режим простого чтения. Например из слейва или из епрома с текущего адреса
#define i2c_sawp 0b00000100 // Start-Addr_W-Write-Stop Это режим простой записи. В том числе и запись с адресом страницы.
#define i2c_sawsarp 0b00001000 // Start-Addr_W-WrPageAdr-rStart-Addr_R-Read-Stop Это режим с предварительной записью текущего адреса страницы
#define i2c_Err_msk 0b00110011 // Маска кода ошибок
#define i2c_Err_NO 0b00000000 // All Right! -- Все окей, передача успешна.
#define i2c_ERR_NA 0b00010000 // Device No Answer -- Слейв не отвечает. Т.к. либо занят, либо его нет на линии.
#define i2c_ERR_LP 0b00100000 // Low Priority -- нас перехватили собственным адресом, либо мы проиграли арбитраж
#define i2c_ERR_NK 0b00000010 // Received NACK. End Transmittion. -- Был получен NACK. Бывает и так.
#define i2c_ERR_BF 0b00000001 // BUS FAIL -- Автобус сломался. И этим все сказано. Можно попробовать сделать переинициализацию шины
#define i2c_Interrupted 0b10000000 // Transmiting Interrupted Битмаска установки флага занятости
#define i2c_NoInterrupted 0b01111111 // Transmiting No Interrupted Битмаска снятия флага занятости
#define i2c_Busy 0b01000000 // Trans is Busy Битмаска флага "Передатчик занят, руками не трогать".
#define i2c_Free 0b10111111 // Trans is Free Битмаска снятия флага занятости.
#define MACRO_i2c_WhatDo_MasterOut (MasterOutFunc)(); // Макросы для режимо выхода. Пока тут функция, но может быть что угодно
#define MACRO_i2c_WhatDo_SlaveOut (SlaveOutFunc)();
#define MACRO_i2c_WhatDo_ErrorOut (ErrorOutFunc)();
typedef void (*IIC_F)(void); // Тип указателя на функцию
extern IIC_F MasterOutFunc; // Подрбрости в сишнике.
extern IIC_F SlaveOutFunc;
extern IIC_F ErrorOutFunc;
extern uint8_t i2c_Do;
extern uint8_t i2c_InBuff[i2c_MasterBytesRX];
extern uint8_t i2c_OutBuff[i2c_MasterBytesTX];
extern uint8_t i2c_SlaveIndex;
extern uint8_t i2c_Buffer[i2c_MaxBuffer];
extern uint8_t i2c_index;
extern uint8_t i2c_ByteCount;
extern uint8_t i2c_SlaveAddress;
extern uint8_t i2c_PageAddress[i2c_MaxPageAddrLgth];
extern uint8_t i2c_PageAddrIndex;
extern uint8_t i2c_PageAddrCount;
void Init_i2c(void);
void Init_Slave_i2c(IIC_F Addr);
extern uint8_t WorkLog[100];
extern uint8_t WorkIndex;
#endif

563
zh_avr_i2c.c Normal file
View File

@@ -0,0 +1,563 @@
#include "zh_avr_i2c.h"
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; // Включаем агрегат и начинаем слушать шину.
}