Введение
Все операции, выполняемые TWI модулем, завершаются установкой бита прерывания (TWINT бит регистра TWCR) и соответствующего статусного кода (старшие шесть разрядов регистра TWSR). Это позволяет организовать обмен данными по шине с помощью прерываний.
Суть подхода в следующем. Мы формируем сообщение для передачи, затем инициируем ее старт и разрешаем прерывания. Дальнейшую работу выполняет автоматически вызываемый обработчик прерывания TWI модуля, а микроконтроллер может заниматься другими задачами.
Такой подход экономит время микроконтроллера, потому что не нужно ожидать установки бита прерывания в цикле, чтобы продолжить процесс обмена данными.
В этой части мы рассмотрим работу ведущего TWI устройства на прерываниях. Код для этого материала основан на руководстве фирмы Atmel - AVR315: Using TWI module as I2C master. В интернете есть его перевод. Загуглите в Яндексе, если интересно.
Я сделал свой аутентичный перевод этого руководства, скоро его выложу.
Обработчик прерывания TWI модуля
Благодаря наличию статусных кодов в TWI модуле, обработчик прерывания органично реализуется в виде автомата (state machine), где каждое состояние - это один статусный код.
Традиционный способ построения автоматов основан на использовании оператора ветвления- switch. Начальный код для обработчика прерывания TWI выглядит так.
//буфер для сообщения
volatile static uint8_t twiBuf[TWI_BUFFER_SIZE];
//сколько байт нужно передать
volatile static uint8_t twiMsgSize;
//статус модуля
volatile static uint8_t twiState = TWI_NO_STATE;
...
#pragma vector=TWI_vect
__interrupt void TWI_ISR(void)
{
//берем статусный код модуля
uint8_t stat = TWSR & TWSR_MASK;
//обрабатываем его
switch (stat){
// состояние START сформировано
case TWI_START:
// состояние повторный START сформировано
case TWI_REP_START:
// был передан пакет SLA+W и получено подтверждение
case TWI_MTX_ADR_ACK:
// был передан байт данных и получено подтверждение
case TWI_MTX_DATA_ACK:
//байт данных принят и передано подтверждение
case TWI_MRX_DATA_ACK:
//был передан пакет SLA+R и получено подтверждение
case TWI_MRX_ADR_ACK:
//был принят байт данных без подтверждения
case TWI_MRX_DATA_NACK:
//был потерян приоритет
case TWI_ARB_LOST:
// был передан пакет 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_START и TWI_REP_START по сути одинаковые. Передача данных только началась, нужно установить указатель на первый элемент массива twiBuf, загрузить его в регистр данных TWDR, инкрементировать указатель и сбросить флаг прерывания.
TWI_MTX_ADR_ACK - передан адресный пакет SLA+W и получено подтверждение. Можно продолжать передачу данных, если это не единственный байт сообщения. Загружаем байт данных в регистр TWDR, увеличиваем указатель и сбрасываем флаг прерывания. Если это единственный байт сообщения, формируем состояние СТОП, сбрасываем флаг прерывания и запрещаем его. И еще устанавливаем статус окончания передачи данных.
TWI_MTX_DATA_ACK - передан байт данных и получено подтверждение. Можно продолжать передачу данных, если это был не последний байт сообщения. Загружаем байт данных в регистр данных, увеличиваем указатель и сбрасываем флаг прерывания. Если это был последний байт сообщения, формируем состояние СТОП, сбрасываем флаг прерывания, запрещаем его и устанавливаем статус окончания передачи данных.
В четырех состояниях выполняются практически одинаковые действия, поэтому их можно объединить. Код для этой части обработчика будет таким.
case TWI_START:
case TWI_REP_START:
ptr = 0; //индексная переменная для массива
case TWI_MTX_ADR_ACK:
case TWI_MTX_DATA_ACK:
if (ptr < twiMsgSize){ //если не все передано
//загружаем байт сообщения
TWDR = twiBuf[ptr];
//сбрасываем флаг TWINT
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
//увеличиваем индексную переменную
ptr++;
}
else{ //если передано все
//устанавливаем состояние, что данные переданы
twiState = TWI_SUCCESS;
//формируем СТОП, запрещаем прерывания
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO)|(0<<TWIE);
}
break;
Продолжаем.
TWI_MRX_DATA_ACK - принят байт данных и передано подтверждение. В регистре данных TWDR лежит принятый байт, мы должны скопировать его в массив и увеличить указатель. Затем нужно проверить, какой этот байт по счету? Если он предпоследний, то для приема последнего байта нам не нужно формировать подтверждение, если нет, то нужно. Далее мы сбрасываем бит прерывания и устанавливаем бит подтверждения в зависимости от ситуации.
TWI_MRX_ADR_ACK - передан пакет SLA-R и получено подтверждение. Ведущий отозвался на свой адрес и можно получить от него данные. Проверяем, следующий байт будет последним или нет. Если да, то формировать подтверждение не нужно, если нет, то нужно. Далее мы сбрасываем бит прерывания и устанавливаем бит подтверждения в зависимости от ситуации.
Опять частично одинаковый код в разных состояниях, который мы можем объединить. Код для этой части обработчика будет таким.
case TWI_MRX_DATA_ACK:
twiBuf[ptr] = TWDR; //сохраняем байт данных
ptr++;
case TWI_MRX_ADR_ACK:
if (ptr < (twiMsgSize-1)){ //это предпоследний байт?
//нет, формируем подтверждение
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA);
}
else {
//да, подтверждение не формируем
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
}
break;
TWI_MRX_DATA_NACK - принят байт без подтверждения. Значит это последний байт, который мы должны принять. Копируем его из регистра данных в свой массив. Устанавливаем какой-нибудь флаг, что прием данных окончен. Формируем состояние СТОП, сбрасываем флаг прерывания и запрещаем его.
case TWI_MRX_DATA_NACK:
twiBuf[ptr] = TWDR;
twiState = TWI_SUCCESS;
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO);
break;
TWI_ARB_LOST - потеря приоритета. Приоритет может быть потерян, если несколько ведущих устройств одновременно начали передачу данных. В этом случае можно сбросить флаг прерывания и сформировать состояние повторного старта. Состояние старта сформируется, когда шина освободится.
case TWI_ARB_LOST:
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA);
break;
Далее идут статусные коды, связанные с неполадками на шине.
TWI_MTX_ADR_NACK - передан пакет SLA+W и не получено подтверждение
TWI_MRX_ADR_NACK - передан пакет SLA+R и не получено подтверждение
TWI_MTX_DATA_NACK - передан байт данных и не получено подтверждение
TWI_BUS_ERROR - ошибка на шине из-за некорректных состояний СТАРТ или СТОП
Такие коды могут возникнуть, если ведомое устройство не отвечает или прекратило отвечать. В этом случае нужно сохранить ошибочный статусный код и запретить прерывание TWI модуля.
case TWI_MTX_ADR_NACK:
case TWI_MRX_ADR_NACK:
case TWI_MTX_DATA_NACK:
case TWI_BUS_ERROR:
default:
//сохраняем статусный код
twiState = stat;
//запрещаем прерывания модуля
TWCR = (1<<TWEN)|(0<<TWIE);
Полный код обработчика прерывания TWI модуля выглядит так.
//буфер для сообщения
volatile static uint8_t twiBuf[TWI_BUFFER_SIZE];
//сколько байт нужно передать
volatile static uint8_t twiMsgSize;
//статус модуля
volatile static uint8_t twiState = TWI_NO_STATE;
...
#pragma vector=TWI_vect
__interrupt void TWI_ISR(void)
{
static uint8_t ptr;
uint8_t stat = TWSR & TWSR_MASK;
switch (stat){
case TWI_START:
case TWI_REP_START:
ptr = 0;
case TWI_MTX_ADR_ACK:
case TWI_MTX_DATA_ACK:
if (ptr < twiMsgSize){
TWDR = twiBuf[ptr];
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
ptr++;
}
else{
twiState = TWI_SUCCESS;
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO)|(0<<TWIE);
}
break;
case TWI_MRX_DATA_ACK:
twiBuf[ptr] = TWDR;
ptr++;
case TWI_MRX_ADR_ACK:
if (ptr < (twiMsgSize-1)){
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA);
}
else {
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
}
break;
case TWI_MRX_DATA_NACK:
twiBuf[ptr] = TWDR;
twiState = TWI_SUCCESS;
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO);
break;
case TWI_ARB_LOST:
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA);
break;
case TWI_MTX_ADR_NACK:
case TWI_MRX_ADR_NACK:
case TWI_MTX_DATA_NACK:
case TWI_BUS_ERROR:
default:
twiState = stat;
TWCR = (1<<TWEN);
}
}
Для окончательной ясности привожу блок схему работы обработчика прерывания TWI модуля, которую я взял из руководства AVR315. Картинка кликабельна.
Как запускается обработчик прерывания
Обработчик прерывания есть, но как его запустить? Очень просто. Записать во внутренний буфер (twiBuf) сообщение и количество байт для передачи (twiMsgSize), сформировать состояние СТАРТ и разрешить прерывание TWI модуля. Когда модуль сформирует на шине состояние СТАРТ, установится бит TWINT и запустится обработчик прерывания. Всю дальнейшую работу по передаче сообщения выполнит он.
Функция передачи сообщения выглядит так.
void TWI_SendData(uint8_t *msg, uint8_t msgSize)
{
uint8_t i;
/*ждем ,когда TWI модуль освободится*/
while(TWCR & (1<<TWIE));
/*сохр. количество байт для передачи
и первый байт сообщения*/
twiMsgSize = msgSize;
twiBuf[0] = msg[0];
/*если первый байт типа SLA+W, то
остальное сообщение тоже сохраняем*/
if (!(msg[0] & (TRUE<<TWI_READ_BIT))){
for (i = 1; i < msgSize; i++){
twiBuf[i] = msg[i];
}
}
twiState = TWI_NO_STATE ;
/*разрешаем прерывание и формируем состояние старт */
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA);
}
Драйвер TWI модуля
Я объяснил принцип работы с TWI по прерываниям, теперь расскжу о драйвере, в котором объединено все выше описанное.
Драйвер состоит из двух файлов twim.h и twim.c. В заголовочном файле twim.h определена частота микроконтроллера и размер передающего/приемного буфера TWI модуля, статусные коды для ведущего устройства и прототипы функций. В сишной файле twim.c описана реализация пользовательских функций и обработчик прерывания TWI модуля.
Пользовательские функции драйвера.
uint8_t TWI_MasterInit(uint16_t fr) - инициализирует TWI модуль и устанавливает частоту SCL сигнала. Возвращает TWI_SUCCESS, если требуемую скорость удалось установить и 0, если не удалось. Скорость задается в килогерцах (от 1 до 400). Вызывается в начале main функции.
Пример:
/*установить скорость обмена 100 кГц*/
TWI_MasterInit(100);
void TWI_SendData(uint8_t *msg, uint8_t msgSize) - сохраняет сообщение и инициирует передачу/прием данных по TWI/I2C шине, формируя состояние СТАРТ и разрешая прерывания. В качестве параметров принимает указатель на буфер, в котором хранится сообщение, и количество байтов, которые нужно передать/принять. Первый байт сообщения для функции должен содержать адрес ведомого устройства и бит R/W.
Функция может вызываться после функции инициализации и глобального разрешения прерываний.
Пример:
uint8_t buf[8];
...
TWI_MasterInit(100);
__enable_interrupt();
/*подготавливаем сообщение*/
buf[0] = (DS1307_ADR<<1)|0;
buf[1] = 0;
buf[2] = 0;
buf[3] = 0;
buf[4] = 0;
/*отправляем его*/
TWI_SendData(buf, 5);
uint8_t TWI_GetData(uint8_t *msg, uint8_t msgSize) - получить принятые данные. Она просто копирует принятые ранее данные в буфер msg. Переменная msgSize определяет сколько байт нужно скопировать. Функция возвращает 0, если сообщение не было принято и 1 если сообщение получено.
Эта функцию нужно вызывать после функции TWI_SendData(..), которой был передан адресный пакет SLA-R.
Пример:
/*подготавливаем сообщение*/
buf[0] = (DS1307_ADR<<1)|1; //бит R/W установлен в 1
buf[1] = 0;
/*отправляем его*/
TWI_SendData(buf, 5);
//в байты buf[1]..buf[4] запишется принятое сообщение
/*копируем данные из буфера драйвера в свой*/
TWI_GetData(buf, 5);
uint8_t TWI_GetState(void) - возвращает статус TWI модуля.
TWI_NO_STATE - неопределенное состояние
TWI_SUCCESS - сообщение передано/получено
TWI_MTX_ADR_NACK - передан пакет SLA+W и не получено подтверждение
TWI_MRX_ADR_NACK - передан пакет SLA+R и не получено подтверждение
TWI_MTX_DATA_NACK - передан байт данных и не получено подтверждение
TWI_BUS_ERROR - ошибка на шине из-за некорректных состояний СТАРТ или СТОП
Пример использования драйвера ведущего TWI устройства смотрите в тестовых проектах.
Файлы
DS1307-iar-3.rar
DS1307-proteus.rar
DS1307-AS6-3.rar
DS1307-winavr-3.rar
DS1307-cv-3.rar
Comments
Ждем проекты для winavr.
Интересует точность хода часов.
Как организовать коррекцию(ручну ю и автоматическую)?
Как привязать часы к времени UTC?
/*подготавливаем сообщение*/
buf[0] = (DS1307_ADR<<1)|1; //бит R/W установлен в 1
buf[1] = 0;
/*отправляем его*/
TWI_SendData(buf, 5);
//в байты buf[1]..buf[4] запишется принятое сообщение
/*копируем данные из буфера драйвера в свой*/
TWI_SendData(buf, 5);
Подправьте,пожалуйста, последнюю строку в соответствии с приемом данных.
PS: пардон за первых два "кривых" сообщения – проглядел теги Code:
Code:
/*копируем данные из буфера драйвера в свой*/
TWI_GetData(buf, 5);
Наверное так
int tmp = ((int)hi_reg
int tmp = ((int)hi_reg<<8)|(low_reg);
/*подготавливаем сообщение*/
buf[0] = (DS1307_ADR<<1)|0; //адресный пакет
buf[1] = 0; //адрес регистра
buf[2] = (5<<4)|5; //значение секунд
buf[3] = (5<<4)|9; //значение минут
buf[4] = 0; //значение часов
/*отправляем его*/
TWI_SendData(buf, 5);
А как отправить число месяц и год?
Code:
/*подготавливаем сообщение*/
buf[0] = (DS1307_ADR<<1)|0; //адресный пакет
buf[1] = 0x04; //адрес регистра дней
buf[2] = (1<<4)|4; //день - 14
buf[3] = (0<<4)|1; //месяц - 01
buf[4] = (1<<4)|4; //год - 14
/*отправляем его*/
TWI_SendData(buf, 5);
Я так делаю:
Code:
IND_Time(hour, min);
void IND_Time(unsigned char n1, unsigned char n2)
{
data[0]=number[n2&0xf]; //единицы
data[1]=number[n2>>4]; //десятки
data[2]=number[n1&0xf];//единицы
data[3]=number[n1>>4]; //десятки
}
а то IAR многим не знаком
Единственно не разобрался как часть библиотеки twim " Обработчик прерывания TWI модуля" переписать под winavr, поэтому спросил.
Учимся на твоих статьях!
P.S.
Своевременный ответ^^
Но при компиляции у меня ошибки, правда я драйвер этот засунул в RTOS. Вроде работает, но глюки при сравнении значении с датчика
../GCC-RTOS.c:90:2: warning: implicit declaration of function 'TWI_SendData'
../GCC-RTOS.c:97:2: warning: implicit declaration of function 'TWI_GetData'
../GCC-RTOS.c:150:1: warning: implicit declaration of function 'TWI_MasterInit '
hourOutDS1306 = (((buf[3] & 0xF0) >> 4)*10)+(buf[3] & 0x0F);
затем сравниваю
if (hourOutDS1306
Павел, подскажите как можно модернизировать код чтобы при отсутствии связи с подчиненным устройством, контроллер не зависал. Какой-то тайм аут отсутствия ответа.
Code:
time=0;
while (!(TWCR & (1<<TWINT))&&time<255)time++; //ждем
а вообще конечно лучше через прерывание запускать тви
Был бы очень признателен, если бы кто нибудь ткнул носом :)
На сайте Атмела есть: AVR311: Using the TWI module as I2C slave on tinyAVR and megaAVR devices
Потрите пожалуйста сообщение, все работает EEPROM читается, датчик температуры LM75 читается. Видимо заработался вчера вечером. :)
for (i = 1; i < msgSize; i++){
twiBuf = msg;
}
переменная i начинается с 1 ?
и если ставлю строку /*ждем ,когда TWI модуль освободится*/
while(TWCR & (1
и если ставлю строку /*ждем ,когда TWI модуль освободится*/
Code:
/*ждем ,когда TWI модуль освободится*/
while(TWCR & (1<<TWIE));
и если ставлю строку
Code:
/*ждем ,когда TWI модуль освободится*/
while(TWCR & (1<<TWIE));
то дисплей не включается, а если ее закоментить то все работает.
всем заранее спасибо.
RSS feed for comments to this post