Нужен совет, о организации приема передачи между МК по TWI

Обсуждаем контроллеры компании Atmel.
Ответить
Аватара пользователя
7seg
Потрогал лапой паяльник
Сообщения: 303
Зарегистрирован: Ср май 03, 2017 03:22:26

Нужен совет, о организации приема передачи между МК по TWI

Сообщение 7seg »

Ужа на протяжении долгого времени мучаю себя, вопросом как же все таки правильно гонять данные на плате между мк по шине TWI(i2c).
В итоге, все сводиться к тому что запутываюсь в конечном автомате TWI.
Вот последние попытки:
Master:
Спойлер

Код: Выделить всё

/******************************************************************************/
 Реализация TWI режим Master                       
/******************************************************************************/
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <avr/eeprom.h>
#include <stdlib.h>
#include <string.h>
/********************************Личные_Макросы********************************/
#define SET_BIT(port,bit)		port|=(1<<bit)
#define CLR_BIT(port,bit)		port&= ~(1<<bit)
#define INV_BIT(port,bit)		port ^=(1<<bit)
#define BIT_IS_CLEAR(sfr,bit)	(!(_SFR_BYTE(sfr) & (1<<(bit))))
#define BIT_IS_SET(sfr,bit)		(_SFR_BYTE(sfr) & (1<<(bit) ))
/*********************** Статусные коды TWI модуля ****************************/
// Общие статусные коды.
#define TWI_START                  0x08 //Состояние START сформировано.
#define TWI_REP_START              0x10 //Состояние повторный START сформировано.
#define TWI_ARB_LOST               0x38 //потерян приоритет.
// Статусные коды ведущего передатчика.
#define TWI_MTX_ADR_ACK            0x18 //передан пакет SLA+W, получено подтверждение.
#define TWI_MTX_ADR_NACK           0x20 //передан пакет SLA+W, не получено подтверждение.
#define TWI_MTX_DATA_ACK           0x28 //передан байт данных, получено подтверждение.
#define TWI_MTX_DATA_NACK          0x30 //передан байт данных, не получено подтверждение.
// Статусные коды ведущего приемника.
#define TWI_MRX_ADR_ACK            0x40 //передан пакет SLA+R, получено подтверждение.
#define TWI_MRX_ADR_NACK           0x48 //передан пакет SLA+R, не получено подтверждение.
#define TWI_MRX_DATA_ACK           0x50 //Байт данных принят, передано подтверждение.
#define TWI_MRX_DATA_NACK          0x58 //принят байт данных без подтверждения.
// Другие статусные коды.
#define TWI_NO_STATE               0xF8 //Неопределенное состояние (TWINT = "0").
#define TWI_BUS_ERROR              0x00 //Ошибка, из-за некоректных состояний СТАРТ или СТОП.
// Пользовательские коды.
#define TWI_SUCCESS                0xFF //Успешное завершение.
/************************ макросы получения байта адреса **********************/
#define TWI_SLAVE_ADDR				15			
#define TWI_GET_READ_BYTE(ADDR)		((ADDR)<<1)|1;
#define TWI_GET_WRITE_BYTE(ADDR)	((ADDR)<<1)|0;
/************************ Переменные, константы TWI ***************************/
#define TWI_BUFFER_SIZE		19 // Размер буфера TWI модуля. (8*10+16*4+1)
#define TWI_READ_BIT		0  // Позиция R/W бита в адресном пакете.
#define TWSR_MASK			0xfc  
#define TWI_SPEED			400UL
// Буфер для TWI.
uint8_t TWI_Buf[TWI_BUFFER_SIZE]={0};
// Размер получаемого/отправляемого пакета данных.
uint8_t TWI_MsgSize=0;
// Текущий статус TWI.
uint8_t TWI_State = TWI_NO_STATE;
/****************************************************************************
Инициализация и установка частоты сигнала
****************************************************************************/
uint8_t TWI_MasterInit(uint16_t fr)
{
	static uint8_t i;
	static uint16_t twbrValue;
	static uint8_t pre[4] = {2, 8, 32, 128};
	for(i = 0; i<4; i++){
		twbrValue = (uint8_t)((((F_CPU)/1000UL)/fr)-16)/pre[i];
		if ((twbrValue > 0)&& (twbrValue < 256)){
			TWBR = twbrValue;
			TWSR = i;
			TWDR = 0xFF;
			TWCR = (1<<TWEN);
			return TWI_SUCCESS;
		}
	}
	return 0;
}
// Ждем, пока модуль занят.
void TWI_TransceiverBusy(void)
{
	while (TWCR & (1<<TWIE)){}
}
// Получить статус, работа с TWI завершена.
uint8_t TWI_GetFinishStep(void)
{
	return (!(TWCR & (1<<TWIE)));
}
// Получить статус TWI модуля.
uint8_t TWI_GetState(void)
{
	TWI_TransceiverBusy();
	// Возвращаем статус TWI модуля.
	return TWI_State;
}
// Передать данные.
void TWI_SendData(uint8_t *msg, uint8_t msgSize)
{
	// Ждем, когда TWI модуль освободится.
	TWI_TransceiverBusy();
	// Сохряняем кол.байт для передачи.
	TWI_MsgSize = msgSize;
	// Сохряняем первый байт сообщения.
	TWI_Buf[0]  = msg[0];
	// Если первый байт типа SLA+W.
	if (!(msg[0] & (1<<TWI_READ_BIT)))
	{
		// Сохряняем остальную часть сообщения.
		for (uint8_t i = (msgSize-1); i > 0; i--)
		{
			TWI_Buf[i] = msg[i];
		}
	}
	// Обнуляем переменную, которая хранит статус операции.
	TWI_State = TWI_NO_STATE;
	// Разрешаем прерывание и формируем состояние старт.
	TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA);
}
// Отправляем запрос на чтение.
void TWI_ReadBytes(uint8_t address, uint8_t msgSize)
{
	// Объявляем массив для отправки.
	uint8_t msgBuf[2];
	// Адресный пакет.
	msgBuf[0] = TWI_GET_READ_BYTE(address);
	// Передаем данные.
	TWI_SendData(msgBuf, msgSize);
}
/********************************  Прерывания  ********************************/
// Обработчик прерывания TWI модуля.
ISR(TWI_vect,ISR_BLOCK)
{
	static uint8_t TWI_BufIndex;
	uint8_t stat = TWSR & TWSR_MASK;
	switch (stat)
	{
		// Состояние START сформировано.
		case TWI_START:
		// Состояние повторный START сформировано.
		case TWI_REP_START:
		TWI_BufIndex = 0;
		// Был передан пакет SLA+W и получено подтверждение.
		case TWI_MTX_ADR_ACK:
		// Был передан байт данных и получено подтверждение.
		case TWI_MTX_DATA_ACK:
		if (TWI_BufIndex < TWI_MsgSize)
		{
			// Загружаем в регистр данных следующий байт.
			// TWDR (TWI Data Register) - Регистр данных.
			TWDR = TWI_Buf[TWI_BufIndex];
			// Сбрасываем флаг TWINT.
			TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
			TWI_BufIndex++;
		}
		else
		{
			TWI_State = TWI_SUCCESS;
			// Формируем состояние СТОП, сбрасываем флаг, запрещаем прерывания.
			TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO)|(0<<TWIE);
		}
		break;
		// Байт данных принят и передано подтверждение.
		case TWI_MRX_DATA_ACK:
		TWI_Buf[TWI_BufIndex] = TWDR;
		TWI_BufIndex++;
		// Был передан пакет SLA+R и получено подтвеждение.
		case TWI_MRX_ADR_ACK:
		if (TWI_BufIndex < (TWI_MsgSize-1))
		{
			// Если это не предпоследний принятый байт, формируем подтверждение.
			TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA);
		}
		else
		{
			// Если приняли предпоследний байт, подтверждение не формируем.
			TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
		}
		break;
		// Был принят байт данных без подтверждения.
		case TWI_MRX_DATA_NACK:
		TWI_Buf[TWI_BufIndex] = TWDR;
		TWI_State = TWI_SUCCESS;
		// Формируем состояние стоп.
		TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO);
		break;
		// Был потерян приоритет.
		case TWI_ARB_LOST:
		// Сбрасываем флаг TWINT, формируем повторный СТАРТ.
		TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA);
		break;
		// Был передан пакет SLA+W и не получено подтверждение.
		case TWI_MTX_ADR_NACK:
		// Был передан пакет SLA+R и не получено подтверждение.
		case TWI_MRX_ADR_NACK:
		// Был передан байт данных и не получено подтверждение.
		case TWI_MTX_DATA_NACK:
		// Ошибка на шине из-за некоректных состояний СТАРТ или СТОП.
		case TWI_BUS_ERROR:
		default:
		TWI_State = stat;
		// Запретить прерывание.
		TWCR = (1<<TWEN)|(0<<TWIE)|(0<<TWINT)|(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC);
		break;
	}
}

/***********************************************************/

union Byte
{
	uint8_t Byte;
	struct
	{
		uint8_t B0:1;
		uint8_t B1:1;
		uint8_t B2:1;
		uint8_t B3:1;
		uint8_t B4:1;
		uint8_t B5:1;
		uint8_t B6:1;
		uint8_t B7:1;
	}Bits;
};

union Byte OneOUT;		//  (Выход) U1
union Byte TwoOUT;		//  (Выход) U2
union Byte ThreeOUT;	//  (Выход) U3
union Byte FourOUT;		//  (Выход) U4
union Byte FifthOUT;	//  (Выход) U5

union Byte OneIN;		//  (Вход) U9
union Byte TwoIN;		//  (Вход) U10
union Byte ThreeIN;		//  (Вход) U11
union Byte FourIN;		//  (Вход) U12
union Byte FifthIN;		//  (Вход) U13
struct Time64{
	uint16_t VarOne;
	uint16_t VarTwo;
	uint16_t VarThree;
	uint16_t VarFour;
	}Time;
void CpyVarToBuf()
{
	memcpy(&TWI_Buf[1],&OneOUT.Byte,sizeof(uint8_t));
	memcpy(&TWI_Buf[2],&TwoOUT.Byte,sizeof(uint8_t));
	memcpy(&TWI_Buf[3],&ThreeOUT.Byte,sizeof(uint8_t));
	memcpy(&TWI_Buf[4],&FourOUT.Byte,sizeof(uint8_t));
	memcpy(&TWI_Buf[5],&FifthOUT.Byte,sizeof(uint8_t));
	
	memcpy(&TWI_Buf[6],&OneIN.Byte,sizeof(uint8_t));
	memcpy(&TWI_Buf[7],&TwoIN.Byte,sizeof(uint8_t));
	memcpy(&TWI_Buf[8],&ThreeIN.Byte,sizeof(uint8_t));
	memcpy(&TWI_Buf[9],&FourIN.Byte,sizeof(uint8_t));
	memcpy(&TWI_Buf[10],&FifthIN.Byte,sizeof(uint8_t));
	
	memcpy(&TWI_Buf[11],&Time,sizeof(Time));
}
uint8_t ttest=0;
int main(void)
{
	TWI_MasterInit(TWI_SPEED);
	sei();
	//Заполним переменные значениям для теста.
	Time.VarOne=255;
	Time.VarTwo=2;
	Time.VarThree=3;
	Time.VarFour=60000;
	OneOUT.Bits.B0=1;
	FifthOUT.Bits.B0=1;
	FifthIN.Bits.B7=1;
	
	//Откопируем данные в буфер для отправки в TWI
	CpyVarToBuf();
	
	
	while(1)
	{
		
		//Пишем в TWI_Slave
		//TWI_Buf[0]=TWI_GET_WRITE_BYTE(TWI_SLAVE_ADDR);
		//TWI_SendData(TWI_Buf,TWI_BUFFER_SIZE);
		
		//Читаем из TWI_SLAVE
		TWI_ReadBytes(TWI_SLAVE_ADDR,TWI_BUFFER_SIZE);
		asm volatile ("nop");
	}
	return 0;
}

Slave:
Спойлер

Код: Выделить всё

/************************************************************************/
Реализация TWI режим Slave									
/************************************************************************/
#define F_CPU 20000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <util/atomic.h>
#include <avr/sfr_defs.h>
#include <util/twi.h>
/********************************Макросы*********************************/
#define SET_BIT(port,bit)		port|=(1<<bit)
#define CLR_BIT(port,bit)		port&= ~(1<<bit)
#define INV_BIT(port,bit)		port ^=(1<<bit)
#define BIT_IS_CLEAR(sfr,bit)	(!(_SFR_BYTE(sfr) & _BV(bit)))
#define BIT_IS_SET(sfr,bit)		(_SFR_BYTE(sfr) & _BV(bit))
/************************************************************************/
#define I2C_ADDR			15
#define TWI_BUFFER_SIZE		18

uint8_t rxBuff[TWI_BUFFER_SIZE]={0};
uint8_t txBuff[TWI_BUFFER_SIZE]={0};
	
static void (*I2C_recv)(uint8_t,uint8_t*,uint8_t);
static void (*I2C_req)(uint8_t*,uint8_t);

void I2C_setCallbacks(void (*recv)(uint8_t,uint8_t*,uint8_t), void (*req)(uint8_t*,uint8_t))
{
	I2C_recv = recv;
	I2C_req = req;
}

void I2C_received(uint8_t msgRcv,uint8_t* msgBuf,uint8_t bufPtr)
{
	msgBuf[bufPtr] = msgRcv;
}

void I2C_requested(uint8_t* msgBuf,uint8_t bufPtr)
{
	TWDR = msgBuf[bufPtr];		//*(msgBuf+bufPtr);
}


void I2C_stop(void)
{
	cli();
	TWCR = 0;
	TWAR = 0;
	sei();
}
ISR(TWI_vect)
{
	static uint8_t bufPtr;
	switch(TW_STATUS)
	{
		case TW_ST_SLA_ACK:
			bufPtr=0;
		case TW_ST_DATA_ACK:
			I2C_req(&txBuff[0],bufPtr++);
			TWCR = (1<<TWIE) | (1<<TWINT) | (1<<TWEA) | (1<<TWEN);
		break;
		case TW_ST_DATA_NACK:
			TWCR =	(1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC);
			break; 
		case TW_SR_SLA_ACK:
			bufPtr   = 0;                                 
			TWCR =	(1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC);
		break;
		case TW_SR_DATA_ACK:
			I2C_recv(TWDR,&rxBuff[0],bufPtr++);
			TWCR = (1<<TWIE) | (1<<TWINT) | (1<<TWEA) | (1<<TWEN);
		break;
		case TW_BUS_ERROR:
			TWCR = 0;
			TWCR = (1<<TWIE) | (1<<TWINT) | (1<<TWEA) | (1<<TWEN);
		break;
		default:
			TWCR = (1<<TWIE) | (1<<TWINT) | (1<<TWEA) | (1<<TWEN);
		break;
	}
}


void I2C_init(uint8_t address)
{
	cli();
	TWAR = (address << 1);
	TWCR = (1<<TWIE) | (1<<TWEA) | (1<<TWINT) | (1<<TWEN);
	sei();
	I2C_setCallbacks(I2C_received, I2C_requested);
}
int main()
{
	I2C_init(I2C_ADDR);
	for (uint8_t i=0;i<TWI_BUFFER_SIZE;i++){
		txBuff[i]=((i*2)+2);
	}
	while(1)
	{
	}
	return 0;
}

И вроде даже все нормально читается\пишется,как говориться приделывай в конце пакета CRC8 и радуйся жизни.
Ну я прям чувствую что где то у меня с этим TWI косяк. Мб кто чего посоветует ?
andrei23061996@gmail.com
.................................................................................................................
Аватара пользователя
ARV
Ум, честь и совесть. И скромность.
Сообщения: 18544
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск
Контактная информация:

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение ARV »

вот никак не могу понять - для чего налаживать связь между МК по этому интерфейсу? ну, когда к чужим девайсам надо подключиться - тут выбора нет, а когда сам себе такие задачи ставишь - зачем, почему, для чего?! Есть ведь USART, для обмена по которому вообще даже думать не надо...
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Аватара пользователя
7seg
Потрогал лапой паяльник
Сообщения: 303
Зарегистрирован: Ср май 03, 2017 03:22:26

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение 7seg »

ARV писал(а):Есть ведь USART, для обмена по которому вообще даже думать не надо...
Да в том то и проблема что он уже занят на втором мк , поэтому и хочу посадить туда слейв TWI.

Добавлено after 2 minutes 15 seconds:
Да и все таки, хотелось бы под рукой иметь готовую обвертку над аппаратной возможностью TWI(slave\master) в виде .с и .h файла )
andrei23061996@gmail.com
.................................................................................................................
Аватара пользователя
GoldenAndy
Поставщик валерьянки для Кота
Сообщения: 1925
Зарегистрирован: Чт июл 28, 2016 07:58:37
Откуда: Kyiv, UA
Контактная информация:

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение GoldenAndy »

Все уже сделано до нас.
Посмотрите в сторону библиотеки i2c Ultimate от DI HALT'a.
Он заморочился и сделал обработчик прерывания TWI со всеми состояниями автомата.
СпойлерСсылка на дружественный ресурс, да простят меня модераторы
ИзображениеИзображение
Изображение
 
Telegram               Лучшая благодарность ->
[+]
Аватара пользователя
7seg
Потрогал лапой паяльник
Сообщения: 303
Зарегистрирован: Ср май 03, 2017 03:22:26

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение 7seg »

goldenandy писал(а): i2c Ultimate от DI HALT'a.
Покапал его исходники, как я понял он решил не разделять слейв\мастер на разные модули а решил сделать общий ?)Или я ошибаюсь ?
andrei23061996@gmail.com
.................................................................................................................
Аватара пользователя
Z_h_e
Собутыльник Кота
Сообщения: 2708
Зарегистрирован: Сб май 14, 2011 21:16:04
Откуда: г. Чайковский

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение Z_h_e »

7seg писал(а):Да в том то и проблема что он уже занят на втором мк ,
Можно организовать программно, да еще в полудуплексном режиме, по одной проволке будет данные тудой сюдой гонять.
Какого рода данные передаются?
Изображение
Добро всегда побеждает зло. Поэтому кто победил - тот и добрый.
Аватара пользователя
GoldenAndy
Поставщик валерьянки для Кота
Сообщения: 1925
Зарегистрирован: Чт июл 28, 2016 07:58:37
Откуда: Kyiv, UA
Контактная информация:

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение GoldenAndy »

7seg, посмотрите у ДиХальта примеры.
Он не то что бы не разделял, но инициализация в режиме мастера и слейва - раздельная. И буферы тоже.
Честно, я режим слейва не делал ни разу. А в режиме мастера юзаю его либу (с мелкими доработками) во всех проектах, где нужен TWI.
Дико удобно, заполнить бфер - и выплюнуть старт.
Потом коллбек-функцией получить результат - автобус приехал, автобус уехал или автобус сломался.
И там отдельная коллбек-функция для слейва - как байт прилетел - можно его забрать.
ИзображениеИзображение
Изображение
 
Telegram               Лучшая благодарность ->
[+]
Аватара пользователя
7seg
Потрогал лапой паяльник
Сообщения: 303
Зарегистрирован: Ср май 03, 2017 03:22:26

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение 7seg »

Z_h_e писал(а):Какого рода данные передаются?
Сидит протокол ModBus в slave режиме , передает 40 дискретных входов и 40 дискретных выходов. + 4 16 битные переменные .

Походу надо еще раз в целом прочитать дш по i2c. А то при промоторе КА TWI. начинаю путаться.
Уф.. что он такой сложный.... :kill:

Добавлено after 10 minutes 14 seconds:
Re: Нужен совет, о организации приема передачи между МК по TWI
goldenandy, слейв который я написал вроде работает и причем достаточно быстро.)) Но мне не нравиться в моей версии вся эта работа с указателями на функции,шагать по массиву через указатель.
Если бы вот этот трешь был написан не мной, я бы в жизни не разобрался что он делает ))

static void (*I2C_recv)(uint8_t,uint8_t*,uint8_t);
static void (*I2C_req)(uint8_t*,uint8_t);

void I2C_setCallbacks(void (*recv)(uint8_t,uint8_t*,uint8_t), void (*req)(uint8_t*,uint8_t))
{
I2C_recv = recv;
I2C_req = req;
}


Охота чего более лаконичного) Еще раз почитаю Спецификацию TWI и попробую взять сорцы ДиХальта за основу.
andrei23061996@gmail.com
.................................................................................................................
Аватара пользователя
ARV
Ум, честь и совесть. И скромность.
Сообщения: 18544
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск
Контактная информация:

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение ARV »

7seg писал(а):Если бы вот этот трешь был написан не мной, я бы в жизни не разобрался что он делает
во что значит пренебрежение простой штукой typedef :)

Добавлено after 2 minutes 58 seconds:
Re: Нужен совет, о организации приема передачи между МК по TWI

Код: Выделить всё

typedef void (*callback_recv)(uint8_t,uint8_t*,uint8_t);
typedef void (*callback_reqv)(uint8_t*,uint8_t);

static callback_recv I2C_recv;
static callback_reqv I2C_reqv;

void I2C_setCallbacks(callback_recv recv, callback_reqv reqv){
  I2C_recv = recv;
  I2C_req = req;
}
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Аватара пользователя
7seg
Потрогал лапой паяльник
Сообщения: 303
Зарегистрирован: Ср май 03, 2017 03:22:26

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение 7seg »

ARV точняк ) я и забыл на самом деле уже про существования typedef ))
andrei23061996@gmail.com
.................................................................................................................
Аватара пользователя
7seg
Потрогал лапой паяльник
Сообщения: 303
Зарегистрирован: Ср май 03, 2017 03:22:26

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение 7seg »

Решил все таки взять за основу реализацию от ДиХалта.
В итоге походу нашел у него ошибку:(Хотя сомневаюсь и решил спросить тут)
Слейв режим:
Исходник .H
Спойлер

Код: Выделить всё

#ifndef IICULTIMATE_H
#define IICULTIMATE_H

#include <avr/io.h>
#include <avr/interrupt.h>

#define i2c_PORT	PORTC				// Порт где сидит нога TWI
#define i2c_DDR		DDRC
#define i2c_SCL		5					// Биты соответствующих выводов
#define i2c_SDA		4

#define i2c_MasterAddress 	0xB0		// Адрес на который будем отзываться
#define i2c_i_am_slave		1			// Если мы еще и слейвом работаем то 1. А то не услышит!

#define i2c_MasterBytesRX	20			// Величина принимающего буфера режима Slave, т.е. сколько байт жрем.
#define i2c_MasterBytesTX	20			// Величина Передающего буфера режима Slave , т.е. сколько байт отдаем за сессию.

#define i2c_MaxBuffer		20			// Величина буфера Master режима RX-TX. Зависит от того какой длины строки мы будем гонять
#define i2c_MaxPageAddrLgth	1			// Максимальная величина адреса страницы. Обычно адрес страницы это один или два байта.
// Зависит от типа ЕЕПРОМ или другой микросхемы.

#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;

extern void Init_i2c(void);
extern void Init_Slave_i2c(IIC_F Addr);

#endif

Исходник .С
Спойлер

Код: Выделить всё

#include "IIC_ultimate.h"

void DoNothing(void);

uint8_t i2c_Do=0;								// Переменная состояния передатчика IIC
uint8_t i2c_InBuff[i2c_MasterBytesRX]={0};		// Буфер прием при работе как Slave
uint8_t i2c_OutBuff[i2c_MasterBytesTX]={0};		// Буфер передачи при работе как Slave
uint8_t i2c_SlaveIndex=0;						// Индекс буфера Slave

uint8_t i2c_Buffer[i2c_MaxBuffer]={0};			// Буфер для данных работы в режиме Master
uint8_t i2c_index=0;							// Индекс этого буфера
uint8_t i2c_ByteCount=0;						// Число байт передаваемых

uint8_t i2c_SlaveAddress=0;						// Адрес подчиненного

uint8_t i2c_PageAddress[i2c_MaxPageAddrLgth]={0};	// Буфер адреса страниц (для режима с sawsarp)
uint8_t i2c_PageAddrIndex=0;						// Индекс буфера адреса страниц
uint8_t i2c_PageAddrCount=0;						// Число байт в адресе страницы для текущего Slave

// Указатели выхода из автомата:
IIC_F MasterOutFunc = &DoNothing;			//  в Master режиме
IIC_F SlaveOutFunc 	= &DoNothing;			//  в режиме Slave
IIC_F ErrorOutFunc 	= &DoNothing;			//  в результате ошибки в режиме Master

uint8_t WorkLog[40]={0};						// Лог пишем сюда
uint8_t WorkIndex=0;						// Индекс лога

ISR(TWI_vect)								// Прерывание 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:	// Старт был, а затем мы:
		{
			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)
			{
				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
				TWCR = 0<<TWSTA|0<<TWSTO|1<<TWINT|1<<TWEA|1<<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);
	uint8_t i;
	uint16_t twbrValue;
	uint8_t pre[4] = {2, 8, 32, 128};
	for(i = 0; i<4; i++){
		twbrValue = ((((F_CPU)/1000UL)/100)-16)/pre[i];
		if ((twbrValue > 0)&& (twbrValue < 256)){
			TWBR = (uint8_t)twbrValue;
			TWSR = i;
			TWDR = 0xFF;
			TWCR = (1<<TWEN);
			return ;
		}
	}
}

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;		// Включаем агрегат и начинаем слушать шину.
}

В автомате TWI ,case 0xB8

у него если это был не последний байт иницилизация ригистра выглядит так :
TWCR = 0<<TWSTA|0<<TWSTO|1<<TWINT|1<<TWEA|0<<TWEN|1<<TWIE;

Вопрос зачем он записывает в TWEN 0 ? Как я понимаю этим он отключит TWI.

А правильная установка регистра должна быть следующая :
TWCR = 0<<TWSTA|0<<TWSTO|1<<TWINT|1<<TWEA|1<<TWEN|1<<TWIE;

Или все таки я где то ошибся ?
andrei23061996@gmail.com
.................................................................................................................
Аватара пользователя
GoldenAndy
Поставщик валерьянки для Кота
Сообщения: 1925
Зарегистрирован: Чт июл 28, 2016 07:58:37
Откуда: Kyiv, UA
Контактная информация:

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение GoldenAndy »

похоже, ди тут опечатался.
Судя по ДШ (Мега8(L) - стр 182) - TWEN д.б. 1 всегда, а 0/1 должен быть бит TWEA

Добавлено after 24 minutes 13 seconds:
Re: Нужен совет, о организации приема передачи между МК по TWI
Хотя я вот смотрю тестовый пример ДиХальта - у него TWEN тоже выключается....
но оно ж у него работало ? Значит надо тестировать либо в протеусе, либо на макетке...
ИзображениеИзображение
Изображение
 
Telegram               Лучшая благодарность ->
[+]
Аватара пользователя
7seg
Потрогал лапой паяльник
Сообщения: 303
Зарегистрирован: Ср май 03, 2017 03:22:26

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение 7seg »

Уже прогнал тест. Все таки у него походу опечатка. У него в примерах данный участок автомата не задействован. Т.к не использовал он возможность сделать мастером запрос данных от слейва(контроллера).
andrei23061996@gmail.com
.................................................................................................................
Аватара пользователя
GoldenAndy
Поставщик валерьянки для Кота
Сообщения: 1925
Зарегистрирован: Чт июл 28, 2016 07:58:37
Откуда: Kyiv, UA
Контактная информация:

Re: Нужен совет, о организации приема передачи между МК по T

Сообщение GoldenAndy »

Ок, спс.
Написал ДиХальту.
ИзображениеИзображение
Изображение
 
Telegram               Лучшая благодарность ->
[+]
Ответить

Вернуться в «AVR»