USI
Итак,
USI
В сети не так уж и много русскоязычного материала по данному модулю, с англоязычным дела не лучше. Я через яндекс нашел всего пару статей - одна из них на сайте уважаемого DiHalt, вторая просто очень хороший перевод даташита с комментариями редактора. Изучение USI рекомендую начать с этого материала, а за подробностями уже обратится к даташиту. Второе - необходимо обязательно скачать материалы апноута с сайта микрочипа - там есть примеры кода. Здесь не буду пересказывать сказанное, а лишь покажу сливки. Блок-схема USI:
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 Комментариев
Рекомендуемые комментарии
Комментариев нет
Присоединяйтесь к обсуждению
Вы публикуете как гость. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.