Перейти к содержанию
  • записей
    8
  • комментариев
    7
  • просмотров
    1 795

USI


parovoZZ

1 926 просмотров

Итак,
USI
В сети не так уж и много русскоязычного материала по данному модулю, с англоязычным дела не лучше. Я через яндекс нашел всего пару статей - одна из них на сайте уважаемого DiHalt, вторая просто очень хороший перевод даташита с комментариями редактора. Изучение USI рекомендую начать с этого материала, а за подробностями уже обратится к даташиту. Второе - необходимо обязательно скачать материалы апноута с сайта микрочипа - там есть примеры кода. Здесь не буду пересказывать сказанное, а лишь покажу сливки. Блок-схема USI:

a_004.thumb.png.0205fb7aa71105ca6db9c36d5837cb8b.png

USI as I2C
В этом режиме у ас задействованы пины DI/SDA и USCK/SCL. Первое обозначение для SPI, второе для I2C. У пина SDA необходимо вручную менять режим работы в зависимости от направления передачи данных. При этом в режиме входа пин работает как самый обычный, а вот в режиме выхода у нас подключается драйвер с открытым стоком. Да - в драйверах интерфейса I2C отсутствует верхний драйвер - он заменен на подтягивающий резистор для исключения КЗ на линии.
Как видно из блок-схемы, пин SDA подключается либо в начало, либо в конец сдвигового регистра. Для сдвига битов в этом регистре он может тактироваться от 4-х разных источников. Что это за источники? Это строб USICLK, это внешнее тактирование (в режиме работы SLAVE - здесь я его не разбираю), можно тактироваться от таймера T0 (у меня, к сожалению, пока не взлетело - поэтому тоже не разбираю), а также строб USITC. В чем разница между USICLK и USITC спросите вы? Для тактирования по стробу USICLK в этот бит необходимо записывать попеременно то "0", то "1". В USITC необходимо писать всегда "1" и такая запись будет формировать противоположный фронт. Для подсчета сделанных тактов у нас в наличии имеется 4-х битный счетчик. Счетчик при переполнении взводит флаг USIOIF, а также умеет генерировать прерывание.
Также присутствует очень важный бит аппаратного детектора старта, который нам подложит подлянку. Бит зовется USISIF. Дело в том, что этому аппаратному детектору все равно, кто формирует старт - мы или кто-то на шине. Реакция у него всегда одна - он прижимает линию SCL к нулю. Чтобы линию поднять обратно, необходимо сбросить этот флаг. В статье на сайте у DiHalt-а автор не смог победить это и потому обходил данный момент с помощью костылей. Странно (
Открываем редакторы и пишем код.
Для начала напишем функцию приемо-передачи битов. Функция одна, а вот порт SDA будем переключать до вызова данной функции.

//.. Функция передачи
uint8_t SHT_USI_Transfer (uint8_t temp)
{
    USISR = temp;                   			// Помещаем настройки в регистр USISR - количество циклов счетчика до переполнения.

    temp =    (0<<USISIE)|(0<<USIOIE)|          // Прерывания запрещены
            (1<<USIWM1)|(0<<USIWM0)|      		// USI как I2C.
            (1<<USICS1)|(0<<USICS0)|(1<<USICLK)|    // Программно тактируем шину
            (1<<USITC);               				 //

    SCK_PORT |= (1<<Pullup_SCK);                // Включили подтяжку на линии SCK

    do
    {
        //USICR = temp;               			// Формируем положительный фронт на линии SCK
        //while(!(SCK_PIN & (1<<SCK)) );        // Ждем поднятия линии SCK
        USICR = temp;                			// Формируем отрицательный фронт на линии SCK
    }while( !(USISR & (1<<USIOIF)) );           // Крутимся в цикле до переполнения счетчика
   
    SCK_PORT &= ~ ((1<<SCK) | (1<<Pullup_SCK)); // Прижимаем SCK к нулю, выключаем подтяжку

    temp  = USIDR;                    			// Читаем принятые данные
    USIDR = 0xFF;                    			// Отпускаем линию DATA
    DATA_DDR |= (1<<DATA);                		// Пин DATA как выход

    return temp;                    			// Возвращаем принятые данные
}

Сперва включаем подтяжку на линии SCK - напомню, что подтяжку мы включаем на соседнем пине, который электрически соединен с пином SCK. Далее мы в цикле каждый раз пишем "1" в бит USITC. При этом на выходе у нас формируется меандр, который тактирует счетчик и двигает данные в сдвиговом регистре. На выполнение итерации цикла уходит 8 тактов процессора. Таким образом, при тактовой частоте в 500 кГц мы имеем скорость передачи битов 62.5 кГц. При разборе режима SPI я покажу самую скорострельную работу тактирования. А тактироваться показанным выше способом имеет смысл только в одном случае - если раскомментировать код внутри цикла. Как видно из него, мы ждем поднятия потенциала на линии SCK. Т.к. у нас резисторы заранее неизвестного номинала, то и наверняка сходу мы не сможем правильно оценить 100% рабочую частоту. Но для устройства с экономичным питанием правильным будет тактироваться с максимальной эффективностью, т.е. CLK/2, а частоту тактирования шины регулировать прескалером осциллятора (в нашем случае можно смело выставлять делитель на 64. Можно и на 32 - тогда получим частоту 125 кГц. Но необходимо тестировать на минимальном напряжении питания. Может не хватить тока через подтягивающие резисторы для перезарядки емкостей).

Теперь напишем функцию-алгоритм работы с датчиком SHT1x.

//.. Функция приемо-передачи
uint8_t SHT_USI_Start_Transceiver (uint8_t *data, uint8_t measurement)
{
    uint8_t USISR_8bit =    (1<<USISIF) | (1<<USIOIF) | (1<<USIPF) | (1<<USIDC) |
                    (0x0<<USICNT0);
    uint8_t USISR_1bit =    (1<<USISIF) | (1<<USIOIF) | (1<<USIPF) | (1<<USIDC) |
                    (0xE<<USICNT0);
                   
    //.. Формируем старт  
    SCK_PORT |= (1<<SCK) | (1<<Pullup_SCK);            		// Отпускаем линию SCK и включаем подтяжку

    DATA_PORT &= ~((1<<DATA) | (1<<Pullup_DATA));        	// Прижимаем DATA к нулю, выключаем подтяжку

    SCK_PORT &= ~((1<<SCK) | (1<<Pullup_SCK));        		// Прижимаем SCK к нулю, выключаем подтяжку

    SCK_PORT |= (1<<SCK) | (1<<Pullup_SCK);            		// Отпускаем линию SCK и включаем подтяжку
                            								// Здесь сработал аппаратный детектор старта,
    USISR = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC);  // который прижимает линию SCK к нулю. Чтобы отпустить
                            								// линию SCK, необходимо очистить флаг USISIF
    DATA_PORT |= (1<<DATA) | (1<<Pullup_DATA);        	// Отпускаем DATA, включаем подтяжку
    SCK_PORT &= ~((1<<SCK) | (1<<Pullup_SCK));        	// Прижимаем SCK к нулю, выключаем подтяжку

    //.. Отправить команду
    USIDR = *data;                    					// Загрузили в регистр USIDR данные

    SHT_USI_Transfer (USISR_8bit);            			// Отправляем данные

    DATA_DDR &= ~(1<<DATA);                				// Проверяем сигнал ACK от датчика
    if (SHT_USI_Transfer(USISR_1bit) & (1<<NACK_BIT))
            return(1);            						// Если NACK - то выходим

    if (measurement)                    				// Если команда на измерение
    {
        //.. Ожидание измерения
        GIMSK = (1<<PCIE0);                				// Разрешаем прерывание на пинах PCINT0..PCINT7
        PCMSK0 = (1<<PCINT6);            				// Выделяем маской прерывание только на PCINT6 / DATA

        Sleep(SLEEP_MODE_PWR_DOWN);            			// Уходим в глубокий сон

        GIMSK = 0;                						// Запрещаем прерывания на пинах      
    }
    else                        						// Если команда на запись регистра
    {                        							// статуса
        USIDR = *(++data);                				// Записываем в датчик настройки
        SHT_USI_Transfer (USISR_8bit);      

        DATA_DDR &= ~(1<<DATA);            				// Проверяем сигнал ACK от датчика
        if (SHT_USI_Transfer(USISR_1bit) & (NACK_BIT<<1))
            {return(1);}
                else
                {return(0);}
    }
   
    *(data++) = SHT_USI_Transfer (USISR_8bit);        	// Читаем старший байт данных  
                            							// Формируем квитанцию ACK
    DATA_DDR |= (1<<DATA);                				// Линия DATA на выход
    DATA_PORT &= ~((1<<DATA) | (1<<Pullup_DATA));       // Прижимаем DATA к нулю, выключаем подтяжку

    SCK_PORT |= (1<<SCK) | (1<<Pullup_SCK);            	// Отпускаем линию SCK и включаем подтяжку
    SCK_PORT &= ~((1<<SCK) | (1<<Pullup_SCK));        	// Прижимаем SCK к нулю, выключаем подтяжку      
   
    DATA_DDR &= ~(1<<DATA);                				// Линия DATA на вход
    Pullup_PORT |= (1<<Pullup_DATA);            		// Включаем подтяжку на линии DATA

    *data = SHT_USI_Transfer (USISR_8bit);            	// Принимаем младший байт данных      
                            							// Формируем квитанцию NACK
    DATA_PORT |= (1<<DATA) | (1<<Pullup_DATA);        	// Отпускаем DATA, включаем подтяжку

    SCK_PORT |= (1<<SCK) | (1<<Pullup_SCK);            	// Отпускаем линию SCK и включаем подтяжку
    SCK_PORT &= ~((1<<SCK) | (1<<Pullup_SCK));        	// Прижимаем SCK к нулю, выключаем подтяжку

    return(0);

Данная функция является универсальной - она позволяет как запрашивать результат у датчика, так и конфигурировать его. Датчик SHT1x. как и любое другое I2C устройство, различает команды на запись и чтение с помощью последнего бита. Но в этой функции этого делать не будем (чтобы не накручивать лишние такты МК в режиме Active). а признак чтения или записи будем формировать снаружи (мы же знаем,что делает та или иная команда?).
Первое, что необходимо сделать - сформировать старт. Смотрим на картинку в самом верху и понимаем - старт необходимо формировать полностью вручную. Я не буду уж совсем углубляться в brain fuck программирование (хотя и можно =)), поэтому я беру текущее значение порта, накладываю маску по "или" и записываю обратно. Хотя, можно банально переписать текущее состояние всех битов в регистре и обойтись одной только записью. Этим мы сэкономим одну команду (а сэкономленные команды - это сэкономленные электроны в источнике питания. Хм. Надо подумать). Как я говорил чуть выше, как только мы сформируем стандартный старт, у нас сразу же срабатывает аппаратный детектор старта. Он нам залочит линию SCK на уровне нуля и мы не сможем ничего делать, пока не сбросим соответствующий бит. Сбрасываем. Формируем ещё один импульс и переходим к отправке команды - записываем ее в регистр USIDR. Дальше вызываем функцию передачи битов. После этого нам необходимо получить сигнал ACK от датчика. Для этого переводим пин SDA на вход и с помощью всё той же функции отправки битов формируем один импульс. Для формирования одного импульса нам необходимо в счетчик записать число 14. Счетчик переживет два такта и дальше уйдет на сброс, тем самым сформируется импульс на линии SCK. Пару слов о ручном управлении линией SDA. Несмотря на то, что на пин SDA прицеплен драйвер ОС, пин может управляться как от регистра PORTA, так и от сдвигового регистра. Логика управления - "И". Т.е. если где-то затесался нолик, то и на выходе будет ноль. Поэтому необходимо следить, чтобы старший бит в регистре USIDR был единицей, если мы хотим вручную управлять пином.
Команду мы отправили и теперь нам надо отправить настройки или же получить результат измерения. Если мы настраиваем датчик, то всё аналогично предыдущей операции - отправляем данные и ждем подтверждения. Выходим из функции - в ней нам делать больше нечего. С получением данных поступаем по-другому. Т.к. для конвертации данных датчику необходимо время, то мы уйдем в глубокий сон. Датчик в процессе измерения держит линию SDA в единице, а по окончании оного прижимает её к нулю. Этим мы и воспользуемся. Но перед этим хочу напомнить, что регистр PINx всегда прицеплен к пину, если только мы специально его не отцепляем - в каком бы режиме пин не находился. Поэтому нам достаточно разрешить прерывание по этому пину, вызвать обработчик прерывания по изменению логического уровня на пине и разбудить МК. Для этого в регистре GIMSK мы разрешаем формировать прерывание от целой грядки пинов, а чтобы исключить ненужные - в регистре PCMSK0 мы накладываем маску (прим. если пинов с прерываниями много, то после поднятия флага прерывания нам необходимо вручную проверить, какой из пинов вызвал прерывание).
После того, как датчик закончил измерение и разбудил нас, нам необходимо считать данные. Датчик ВСЕГДА отдает два байта! Какие бы мы настройки в него не пихали. На каждый принятый байт нам необходимо сформировать квитанцию ACK. Делаем это вручную и без фанатизма (brain fuck программирования=)). Для экономии процессорного времени весь код я оформил в линейном виде - мне здесь важно быстро отстреляться и залечь в сон.

Ну и самое простое - инициализация USI в режим I2C:

//.. Функция инициализации
void SHT_USI_Init (void)
{  
    SCK_DDR |= (1<<SCK);                // Пин SCK как выход
    DATA_DDR |= (1<<DATA);                // Пин DATA как выход

    SCK_PORT &= ~ ((1<<SCK) | (1<<Pullup_SCK));        // Прижимаем SCK к нулю, выключаем подтяжку
    DATA_PORT |= (1<<DATA) | (1<<Pullup_DATA);        // Отпускаем DATA, включаем подтяжку
  
    USIDR =     0xFF;
    USICR =    (0<<USISIE)|(0<<USIOIE)|            // Прерывания выключены
        (1<<USIWM1)|(0<<USIWM0)|            // USI в качестве I2C
        (1<<USICS1)|(0<<USICS0)|(1<<USICLK)|        // Шину тактируем сами
        (0<<USITC);

    USISR =    (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)|    // Очищаем флаги,
            (0x0<<USICNT0);            // и сбрасываем счетчик
}

Здесь все просто. Но то ли лыжи не едут, то ли я....Инициализация в режим I2C занимает значительное время. И если при тактировании 1 МГц модуль успевает перейти в режим к моменту работы с ним, то при тактировании 8 МГц - нет.

0 Комментариев


Рекомендуемые комментарии

Комментариев нет

Присоединяйтесь к обсуждению

Вы публикуете как гость. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Добавить комментарий...

×   Вставлено с форматированием.   Восстановить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

Загрузка...
×
×
  • Создать...