Нужен совет, о организации приема передачи между МК по 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»