12. Протокол I2C и Bascom



Автор: WildCat

IIC (I2C) - это сокращение от Inter-IC. I2C - разработка компании Philips, начатая в 80-х годах. В наши дни бытовая техника сделала резкий скачок в сторону усложнения схемотехники. В одном устройстве встречается множество микросхем. Как следствие - запутанная разводка и плотный монтаж печатных плат. Philips решила, что неплохо было бы использовать простую последовательную шину. Вместо широких параллельных шин можно было бы использовать всего два проводника. В такой шине один провод отвечает за данные, а второй - за тактовые импульсы, SDA и SCL соответственно.
I2C стандарт 1992 года использует семибитные адреса, что позволяет одновременное подключение до 128 ИС к одной шине.
Более подробно про этот протокол можно почитать в спецификации I2C.

Адресация в I2C

Число микросхем на одной шине ограничено 128, потому что каждому чипу соответствует 7-битный адрес. Сравните это, например, с домами на одной улице. Если у нескольких домов будет один и тот же номер, то почтальон придет в замешательство, не зная, куда бросить извещение о посылке с деталями.
Адреса задаются частично аппаратно в микросхеме, частично их можно задавать вручную путем подключения соответствующих выводов к питанию/земле. Это позволяет независимо подключать несколько одинаковых микросхем к одной шине.
Для примера рассмотрим известную PCF8574P. Это восьмибитный расширитель портов общего назначения. В нем аппаратно установлен адрес 0100 A2 A1 A0. Часть 0100 уже прошита в чипе, а часть A2 A1 A0 можно задать вручную. Мы можем сделать это, подключая соответствующие выводы к + питания или земле:



Вот тут микросхема слева имеет адрес 0100100, а справа - 0100000.

Стандарты I2C

Изначально стандарт I2C работал с тактовой частотой до 100кГц. В 1992 году был добавлен Быстрый режим с максимальной частотой 400кГц. Также в этом варианте была реализована 10-битная адресация. В 1998 году появился Высокоскоростной режим с максимальной скоростью 3,4 Мбит/с. (вероятно, Philips сочла Мбит/с более удобной величиной, чем килогерцы) Быстрый и высокоскоростной режимы одинаково хорошо работают с частотой и 100кГц.

Подробнее про стандарт

Philips обладает патентом на I2C. Это значит, что любая компания, желающая выпускать микросхемы с поддержкой I2C должна заплатить за это и подписать соглашение.
Большинство микроконтроллеров AVR обладают двухпроводным интерфейсом (угадайте, что это), по крайней мере, так сказано в документации. Это, конечно же, пресловутый I2C. Видимо, эта терминология используется из жадности и нежелания платить деньги Philips за то, чем все и так прекрасно пользуются.

Стандарт I2C предусматривает подтягивающие резисторы на SDA и SCL. Это одна из наиболее распространенных ошибок. Забудьте про подтягивание, и ваша схема будет безупречно не работать. Как правило, хватает резистора в 10кОм. Иногда, при достижении максимальной скорости обмена, можно импользовать более низкие значения. Проверьте даташиты микроконтроллера и вашей микросхемы.



Иногда можно встретить резисторы 100-330 Ом в линиях. Это делается для уменьшения взаимного влияния.
Длина шины I2C не ограничена. Единственное, что общая емкость линии с подключенными устройствами не должны превышать 400 пФ.

Устройство, начинающее передачу, называется Ведущим (Master, мастер), а устройство, к которому оно обращается, - Ведомым (соответственно, Slave, мазохист раб). Во всех наших примерах AT90S2313 выступает в роли ведущего устройства. Ведомое устройство молчит всегда, пока его не спросит ведущее. Тактовые импульсы на SCL тоже обеспечивает ведущее устройство. Мастер отвечает за временные интервалы в передаче. Если ведомое устройство должно вернуть какие-то значения мастеру, он также должен отвечать за все временные интервалы. I2C позволяет работу нескольких ведущих устройств на одной шине, однако во всех наших примерах только один "мастер" и один или несколько "рабов".

Что можно учинить с I2C?

Главное преимущество стандарта - это использование всего двух ног контроллера, что актуально для небольших МК. Конечно, за такое удобство придется платить - передача одного байта займет время передачи 8 бит + время начала/окончания передачи. Поэтому I2C безусловно медленнее параллельной шины. Однако, если вы не гонитесь за мегибитами, I2C - идеальный вариант.

Многие изготовители выпускают занятные микросхемы с I2C. Philips, как изобретатель, лидирует в этом деле. Иногда можно встретить целые модули с I2C интерфейсом (ТВ-тюнеры, дисплеи...).

Что же делать?

Вот что мы решили препарировать:
- Philips PCF8574
- TI PCF8574
- On-Semi JLC1562B
- Philips PCF8591
- Natsem LM76
- Philips graphics display LPH7653
- Linear Technology LT6904

PCF8574 - это восьмибитный расширитель портов общего назначения. Он добавляет 8 дополнительных пинов ввода/вывода. Используйте его для подключения светодиодов, реле или клавиш/клавиатуры. У него есть выход прерывания, на котором появляется низкий уровень после любого изменения состояния входов. После чтения состояния портов его уровень автоматически возвращается в высокий. Это очень удобный способ вызова прерывания при вводе. Контроллер тут же читает что изменилось и принимает решение.

Заметка: Philips выпускает две модификации чипа: PCF8574 и PCF8574A. Они отличаются только адресом!

PCF8574: 0100. A2 A1 A0
PCF8574A: 0111. A2 A1 A0

Изучите эти мелочи подробнее в даташите.
Если вам нужно больше портов ввода/вывода, используйте PCF8575 той же Philips. Это уже 16-битный расширитель портов.
Texas Instruments выпускает одноименные чипы, одинаковые по характеристикам (и корпусам).

On-Semi JLC1562B так же восьмибитный расширитель общего назначения, но у него есть кое-что еще: 6-битный ЦАП и компаратор на младших пяти разрядах. Однако у него нет выхода прерывания. Компаратор совместно с ЦАП и некоторым программным кодом может таинственно превращаться в 6-битный АЦП.

PCF8591 от Philips имеет один 8-битный ЦАП и четыре 8-битных АЦП. Аналоговые входы можно настроить как одиночные, так и на сложение и вычитание сигналов. Вот вам датаshit для ковыряния.

Компания National Semiconductor выпускает микросхему термометра LM76. Сами они заявляют, что назначение этих чипов - 'оценка' температуры, так что не надейтесь на космическую точность. В общем, подробнее в очередном связанном документе. В чипе находится температурный датчик и 12-битный АЦП (+ знаковый разряд).

LPH7653 от Philips - это маленький графический дисплей, вероятно, выдранный из старого телефона. Даташита на него никто никогда не видел, но говорят, что кто-то знает, как управлять им.

LT6904 от Linear Technology - волшебный чип-синтезатор частоты от 1 кГц до 64 МГц.

Конечно, этот список - всего лишь маленький пример того, что нам доступно для экспериментирования. На самом деле в природе существует гораздо больше хороших микросхем, ищите их на сайтах ведущих производителей по запросу I2C.

Стандартные команды чтения/записи

Команды чтения и записи I2C работают с одним байтом за раз. Стандартная процедура записи такая:

I2cstart
I2cwbyte ICaddress
I2cwbyte Bytetosend
I2cwbyte ...
I2cstop
I2cstart производит сброс линии и заставляет все устроства внимательно слушать. Затем отправляется адрес интересующей нас микросхемы. Она начинает обмен, в это время остальные молчат. Теперь по I2cwbyte отправляются один или несколько байтов. Прием каждого байта подтверждается микросхемой. Наконец, I2cstop освобождает линию.

Стандартная процедура чтения:
I2cstart
I2cwbyte ICaddress
I2crbyte Bytetoread, Ack
I2crbyte Bytetoread, Nack
I2cstop
Так же, как и в первом случае, I2cstart сбрасывает линию. Все устройства начинают слушать. Затем опять передается адрес интересующей микросхемы. Теперь по I2crbyte принимаются один или несколько байтов. После переменной, в которую происходит считываение, стоит ключевое слово Ack или Nack. Ack сообщает, что нужно считать еще байт, Nack - что этот принятый байт был последним. Об этом подробнее рассказано в документации Philips по I2C, о которой говорилось выше.

Ошибки I2C

В BASCOM зарезервирована переменная Err типа Bit. Если происходит ошибка I2C, в нее помещается 1. Правда никому не известно, что с ней делать. На самом деле, что? Обычно ошибки возникают из-за аппаратных проблем, поэтому вряд ли пользователь сможет что-то сделать.

Простая светодиодная мигалка

Давайте забацаем простую мигалку, используя PCF8574. Соберите на макетной плате схедующую несложную схему:



Затем наберите такую программу:
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10

Config Pind.6 = Output

Const Pcf8574write = &H40
Const Pcf8574read = &H41

Set Portd.6
Waitms 1000
Reset Portd.6
Waitms 1000

Do
  Set Portd.6
  I2cstart
  I2cwbyte Pcf8574write
  I2cwbyte 255
  I2cstop
  Waitms 500
  Reset Portd.6
  I2cstart
  I2cwbyte Pcf8574write
  I2cwbyte 0
  I2cstop
  Waitms 500
Loop

End
Две ножки порта D зарезервированы под I2C. I2Cdelay задает скорость шины в 200кГц. Pind.6 настроен на выход, к нему подключен светодиод.

Скомпилируйте код и загрузите в чип. Теперь светодиоды моргают как на контроллере, так и на расширителе портов. Но заметьте, что мигают они в противофазе, это потому диод на расширителе подключен катодом к выходу. Когда на выходе 1, то диод с обеих сторон видит напряжение питание, и гореть он не может. Когда на выходе 0, то ток течет через светодиод в лапку расширителя, и диод светится.

Если вы хотите использовать выводы PCF8574 на вход, вам нужно установить на них высокий уровень, записав 1. Затем, используя кнопку, дергать их на землю (и читать с них 0) или не дергать (и соответственно, читать 1). Но это мы рассмотрим позже.

Скорость I2C

Bascom использует стандартную частоту обмена для I2C в 200 кГц. С помощью параметра I2CDelay можно задать и другую.
Config I2Cdelay = 10
Этот код задаст частоту в 100 кГц. Используйте 5 для 200 кГц (по умолчанию). Соответственно, 2 задаст частоту в 500 кГц, а 1 в 1 МГц. Используйте значения 10 или более для низкоскоростных приложений. Иногда при использовании длинных проводов (большая емкость!) нужно понизить скорость обмена, чтобы избежать ошибок.
С другой стороны, большинство микросхем с I2C могут работать на гораздо больших скоростях, чем указано в даташите. Но нет гарантии, что они будут работать в таком режиме при любых условиях, так что несколько раз проверьте работу устройства.

Использование I2cinit

Лапки AVR, используемые с I2C, конфигурируются словами Config SDA/SCL. После подачи питания на контроллер, на них установится логический ноль. Но после команды Portx или после изменения направления input/output, на них могут оказаться непредсказуемые уровни. Поэтому нужно написать:
I2cinit
чтобы насильно установить на них низкий уровень перед началом или продолжением I2C обмена.

Считываем состояние выключателя через PCF8574

Соберем следующую схему:



И зальем в наш контроллер следующую программу:
i2c-pcf8574-switch.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10

Config Pind.6 = Output

Const Pcf8574write = &H40
Const Pcf8574read = &H41

Const Shortwait = 50
Const Longwait = 250

Const Switchbit = 0

Dim Ledwait As Byte
Dim Pcf8574port As Byte

'Поместим на все ножки PCF8574 единицу и будем проверять
I2cstart
I2cwbyte Pcf8574write
I2cwbyte 255
I2cstop

Do
  'Read Pcf8574 port
  I2cstart
  I2cwbyte Pcf8574read
  I2crbyte Pcf8574port , Nack
  I2cstop
  'Determine state of Switchbit bit
  If Pcf8574port.switchbit = 1 Then
    Ledwait = Shortwait
  Else
    Ledwait = Longwait
  End If

  Set Portd.6
  Waitms Ledwait
  Reset Portd.6
  Waitms Ledwait
Loop

End
Константы определяют длинную и короткую продолжительности вспышки светодиода.
Константа SwitchBit определяет состояние выключателя на Pin 0 (bit 0) PCF8574.
Две переменные типа Byte задают продолжительность вспышки светодиода и состояние порта PCF8574.
Программа начинается с установки всех ножек порта PCF8574 в высокий уровень, так что их можно использовать как входы.
В цикле Do Loop состояние порта PCF8574 определяется так: сначала отсылается I2cstart, затем PCF8574 читает адрес, а затем I2crbyte используется для получения текущего значения порта PCF8574. I2cstop прекращает обмен.

Скомпилируйте программу и отправьте в контроллер. Проследите за её работой - теперь, когда выключатель разомкнут, светодиод моргает часто, если замкнут - редко.

Использование прерываний PCF8574

Программа, описанная выше, достаточно глупая. Время опроса выключателя зависит от состояния переменной Ledwait (пока отрабатывается задержка, контроллер больше ничего не делает). Это плохой пример программирования. Но мы с вами умные и воспользуемся прерываниями от PCF8574. Если вы еще не знаете, что это такое, то срочно прочитайте статью о прерываниях перед тем, как мы продолжим.
Как было сказано ранее, PCF8574 будет генерировать сигнал прерывания (низкий уровень) каждый раз, когда поменяется состояние на его входах.
Соберем такую схему:



И запишем такую программу:
i2c-pcf8574-switch-int.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10

Config Pind.6 = Output
Config Pind.2 = Input
Config Int0 = Falling

Const Pcf8574write = &H40
Const Pcf8574read = &H41

Const Shortwait = 50
Const Longwait = 250

Const Switchbit = 0

Dim Ledwait As Byte
Dim Pcf8574port As Byte

Ledwait = Shortwait

On Int0 Pcfint

Enable Interrupts
Enable Int0

I2cstart
I2cwbyte Pcf8574write
I2cwbyte 255
I2cstop

Do
  Set Portd.6
  Waitms Ledwait
  Reset Portd.6
  Waitms Ledwait
Loop

'PCF8574 interrupt routine
Pcfint:
  'debounce wait time
  Waitms 10
  'read the input pins
  I2cstart
  I2cwbyte Pcf8574read
  I2crbyte Pcf8574port , Nack
  I2cstop
   'Determine state of Switchbit bit
  If Pcf8574port.switchbit = 1 Then
    Ledwait = Shortwait
  Else
    Ledwait = Longwait
  End If
  Gifr = 64
Return
End
Теперь у нас в конфигурации появилась новая строка: Config Int0 = Falling. Это означает, что только спады сигнала на входе Int0 будут вызывать прерывание программы
Также, Int0 на Portd.2 у нас настроен как вход.
В переменную Ledwait изначально кладется значение константы Shortwait.
Когда произойдет прерывание, программа перейдет на метку Pcfint.
Прерывания включены глобально, отдельно включено прерывание Int0.
Программа будет зацикленно моргать светодиодом, пока не случится прерывание. В подпрограмме обработки первым делом выполняется задержка 10 мс, чтобы исключить ложные срабатывания от дребезга контактов. После этого считывается состояние порта PCF8574, а точнее одного бита, помеченного как Switchbit. Если полученное значение - 1, то то переменную Ledwait приравниваем к Shortwait, иначе к Longwait.

Компилируем программу, загружаем в чип. Наблюдаем, что светодиод мигает часто. Замыкаем выключатель и видим, что он стал мигать медленнно. Вместо выключателя можно использовать кнопку.

Обработка данных с оптического энкодера через PCF8574

AVR можно научить читать значения напрямую с энкодера, об этом есть отдельная одноименная глава под номером 18. Но может случиться так, что вам будет не хватать свободных пинов. Тогда на помощь к нам приходит все тот же расширитель PCF8574.
Рассмотрим (значит, соберем на макетной плате!) схему:



И откроем в BASCOM следующую программу:
i2c-pcf8574-int-encoder.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
'default I2C speed
Config I2cdelay = 5
Config Pind.6 = Output     'Тут будет светодиод
Config Pind.2 = Input      'Прерывание от PCF8574 на вход Int0
Config Int0 = Falling
Config Lcd = 16 * 2
Config Lcdpin = Pin , Db4 = Portb.4 , Db5 = Portb.5 , Db6 = Portb.6 , Db7 = Portb.7 , E = Portb.3 , Rs = Portb.2
Config Lcdmode = Port

Const Pcf8574write = &H40
Const Pcf8574read = &H41
Const Optencmask = &B00000011   'Энкодер AB подключен к младшим двум битам
Const Optencbmask = &B000000001 'Удерживаем B-бит

Dim Pcf8574input As Byte
Dim Encoderval As Byte
Dim Encounter As Integer
Dim Oldbval As Byte

Encounter = 0
Oldbval = 0
Cls
Lcd "encounter:"

On Int0 Pcfint

'Настроим расширитель на вход
I2cstart
I2cwbyte Pcf8574write
I2cwbyte Optencmask
I2cstop

Enable Interrupts
Enable Int0


'Сообщим положение энкодера
'Следим за светодиодом на Portd.6 : Если он постоянно включен или выключен,
'то вероятно, срабатывает слишком много  прерываний
Do
  Set Portd.6
  Waitms 5
  Locate 1 , 12
  Lcd Encounter ; "      "
  Reset Portd.6
  Waitms 5
Loop

Pcfint:
  'read the input pins
  I2cstart
  I2cwbyte Pcf8574read
  I2crbyte Pcf8574input , Nack
  I2cstop
  Encoderval = Pcf8574input And Optencmask
  If Oldbval = 1 Then
    Encoderval = Encoderval + 4
  End If
  If Encoderval < 2 Then
    Decr Encounter
  Elseif Encoderval > 5 Then
    Decr Encounter
  Else
    Incr Encounter
  End If
  Oldbval = Pcf8574input And Optencbmask
Return

End
Config Lcdpin используется для задания портов для работы с ЖКИ (в данном случае они совпадают со значениями по умолчанию).
Optencmask используется для установки двух младших битов на энкодере. Optencbmask используется для задания только бита "B".
В основном цикле светодиод на Portd.6 мигает, а на экран выводится положение энкодера. В подпрограмме прерывания считывается байт с I2C порта, из него извлекаются состояния битов на энкодере и определяется его положение, которое обновляется.

Опять же, компилируем и загружаем прошивку в чип. Крутим ручку энкодера и видим, как меняется значение положения на ЖКИ. Если вам достался энкодер с высоким разрешением, вы можете заметить, как светодиод буде включен в течение всего времени, пока вы врашаете ручку. Число генерируемых прерываний настолько велико, что программе не остается времени для возврата в основной цикл. Очень вероятно, что некоторые прерывания будут пропушены, и отображаемое значение не будет меняться так часто, как вам хотелось бы. Вы можете улучшить программу, изменив значение I2cdelay на более низкое, тем самым получив меньшую задержку отклика. Но будьте осторожны при подключении нескольких устройств к одной шине.

Использование ON Semi JLC1562B вместо PCF8574

У JLC1562B нет выхода прерываний, однако есть 6-битный ЦАП, напряжение на нем можно изменять в 64 уровнях, от 0 до 4 вольт, с шагом 0,0625. На входах JLC1562B есть компаратор. На младших 5 битах можно установить порог компаратора ("B") как на половину питающего напряжения, так и на выход ЦАП. Старшие три бита компаратора ("A") всегда настоены на порог в половину питающего напряжения.
По сравнению с PCF8574, в JLC1562B DAC имеет другие режимы чтения и записи. Это нужно знать, чтобы использовать его вместо PCF8574, а также грамотно настроить ЦАП и управлять порогом компаратора. Если используется JLC1562B, как замена PCF8574, последовательность работы с I2C не изменится.
Если вы хотите настроить ЦАП:

- Отправьте JLC1562B адрес записи
- Отправьте байт состояния входов/выходов
- Отправьте 6-битное значение ЦАП (0-63) в младшие 6 бит
- В двух старших битах шестой задает порог компаратора: 0 задает порог в половину питающего напряжения,
1 задает порог значением выхода АЦП, 7 бит управляет "защелкой",
0 защелкивает данные после команды чтения,
1 защелкивает данные, когда компаратор B переключается из 0 в 1

Чтобы использовать ЦАП, соберите следующую схему:



И наберите следующую программу:
i2c-jlc1562b-dac.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10
Config Pind.6 = Output

Const Jcl1562bwrite = &H70

Dim Dacval As Byte

Do
  Set Portd.6
  For Dacval = 0 To 63
    I2cstart
    I2cwbyte Jcl1562bwrite
    I2cwbyte 0
    I2cwbyte Dacval
    I2cstop
  Next Dacval
  Reset Portd.6
Loop

End
Адрес для записи в JLC1562B установлен в 70 (в шестнадцатеричной системе). Сверяйтесь с даташитом!
В цикле Do Loop включается светодиод, а в JLC1562B записывается 0 в первый байт, а во второй пишется значение счетчика от 0 до 63 для ЦАП. В этом втором байте старшие биты (порог компаратора и защелкивание) остаются равными 0.

Ловкими и отточенными движениями отправляем скомпилированную прошивку в контроллер. Наблюдаем на выходе ЦАП (13 нога) пилообразный сигнал:



JLC1562B в роли 6-битного АЦП

Вы можете настроить компаратор JLC1562 так, чтобы он использовал в качестве источника опорного напряжения выход ЦАП. Если мы установим выход ЦАП, скажем, в 2 вольта и приложим чуть меньшее напряжение к ногам D0..D4, то считанное значение будет равно 0. Если чуть поднять напряжение, то считываться будет уже 1. Так что любой из выводов D0..D4 можно заставить исполнять функции "сыроватого" 6-битного АЦП, если изменять напряжение на ЦАП за 64 шага и считывать значение компаратора после каждого шага.
Соберём следующую схему:



И, как обычно, наберем программу:
i2c-jlc1562b-adc.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10
Config Pind.6 = Output

Const Jcl1562bwrite = &H70
Const Jcl1562bread = &H71
'mask out all but the lower six dac bits
Const Jcl1562bcompon = &B11000000
'make this pin high to use it as input
Const Jclmask = &B00000001

Dim Dacval As Byte
Dim Dacwrite As Byte
Dim Jclinput As Byte

Cls
Do
  Reset Portd.6
  For Dacval = 0 To 63 Step 1
    Dacwrite = Dacval Or Jcl1562bcompon
    I2cstart
    I2cwbyte Jcl1562bwrite
    I2cwbyte Jclmask
    I2cwbyte Dacwrite
    I2cstop
    I2cstart
    I2cwbyte Jcl1562bread
    I2crbyte Jclinput , Nack
    I2cstop
    Jclinput = Jclinput And Jclmask
    If Jclinput = Jclmask Then
      Exit For
    End If
  Next Dacval
  Locate 1 , 1
  Lcd Dacval ; "    "
Loop

End
В данном примере производится операция ИЛИ: Dacwrite = Dacval Or Jcl1562bcompon, так что оба бита - бит выбора источника опорного напряжения для компаратора B и бит защелки - устанавливаются в 1.
Jlcmask используется, чтобы установить бит 0 на порту JLC1562B в единицу, чтобы он работал как вход.
В цикле Do Loop включается светодиод, в течение следующих шагов значение Dacval возрастает от 0 до своего максимума 63 (0..63 * 0.0625 = приблизительно 4 вольта). После каждого увеличения считывается значение компаратора. Если при этом оно изменилось (стало 1), считается что компаратор сработал. Тогда цикл прерывается, а значение Dacval выводится на ЖКИ.

АЦП/ЦАП Philips PCF8591

PCF8591 имеет на борту один 8-битный ЦАП и четыре 8-битных АЦП. Чтобы они заработали, вывод EXT нужно подключить к земле. Тогда на выводе OSC появится нечто, похожее на тактовые импульсы 1 МГц:



Вы можете также использовать внешний тактовый генератор (0.75 - 1.25 МГц), подключив его к выводу OSC. Тогда вывод чипа EXT нужно подключить к плюсу питания.

PCF8591 управляется тремя незамысловатыми байтами:
- байт адреса
- байт команды
- байт значения ЦАП
Байт управление выглядит следующим образом:

7 6 5 4 3 2 1 0 (позиция бита)
0 a p p 0 i c c (управляющие биты)

Где:
a = 1 для включения аналогового выхода (но также нужно включить, если планируется использование АЦП)
pp = конфигурация АЦП, нужно выставить 00 для 4 независимых входов A0, A1, A2, A3. Другие значения изучите в даташите самостоятельно!
i = 1 для включения автоувеличения каналов АЦП. Увеличивает значение сс после каждого чтения.
cc = номер канала АЦП, 0, 1, 2 или 3.

Соберём следующую схему:



Для проверки ЦАП введём следующую программу:
i2c-pcf8591-dac-triangle.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10

Config Pind.6 = Output

Const Pcf8591write = &H90
Const Pcf8591read = &H91

Const Pcf8591dacconfig = &B01000000
'                           |
'                           -------- включаем аналоговый выход
'                                    (также нужно и для АЦП)
Dim Dacout As Byte

Set Portd.6
Waitms 1000
Reset Portd.6
Waitms 1000

Do

  Set Portd.6
  For Dacout = 1 To 255 Step 1
    I2cstart
    I2cwbyte Pcf8591write
    I2cwbyte Pcf8591dacconfig
    I2cwbyte Dacout
    I2cstop
  Next Dacout
  Reset Portd.6
  For Dacout = 255 To 1 Step -1
    I2cstart
    I2cwbyte Pcf8591write
    I2cwbyte Pcf8591dacconfig
    I2cwbyte Dacout
    I2cstop
  Next Dacout

Loop

End
В константе Pcf8591dacconfig установлен 6 бит, включающий аналоговый выход.
В цикле Do Loop, For Next цикл меняет значение Dacout от 1 до 255 и отсылает Dacout как третий управляющий байт.
Затем For Next цикл меняет значение Dacout в обратном порядке, от 255 до 1.


На выводе 15 (Aout) можно наблюдать треугольный сигнал:



Изменение Config I2cdelay с 10 до 1 ускоряет процесс:



Конечно, все эти наши приколы ограничены частотой в 100 КГц, это максимальная частота, поддерживаемая PCF8591.
Но тем не менее, программу можно переделать, чтобы получать более высокие частоты:
i2c-pcf8591-triangle-faster.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10

Config Pind.6 = Output

Const Pcf8591write = &H90
Const Pcf8591read = &H91

Const Pcf8591dacconfig = &B01000000

Dim Dacout As Byte

Set Portd.6
Waitms 1000
Reset Portd.6
Waitms 1000

I2cstart
I2cwbyte Pcf8591write
I2cwbyte Pcf8591dacconfig

Do

  For Dacout = 1 To 255 Step 1
    I2cwbyte Dacout
  Next Dacout
  For Dacout = 255 To 1 Step -1
    I2cwbyte Dacout
  Next Dacout

Loop

End
Теперь первые два управляющий байта отсылаются перед циклом Do Loop.
В самом цикле лишь происходит запись значения Dacout в чип PCF8591.


Обратите внимание: в этой программе нет команды I2cstop! В реальной же программе такой поворот событий маловероятен, потому что наверняка будут происходить какие то другие процессы с шиной I2C.

Посмотрим на выходной сигнал теперь:



Но мы опять ускорим программу, заменив, как и обешано, I2cdelay на 1:



И по идее можно добиться ещё большего ускорения, установив в схему кварц на 10 МГц, благополучно не сказав об этом Bascom. Тогда результат будет таким:



Разумеется, грамотный читатель никогда не будет делать таких ужасных вещей при разработке коммерческих продуктов. Но домашнее применение подразумевает попирание всех правил, но и подразумевает возможные последствия.

Температурный сенсор National Semiconductor LM76

LM76 - это одна из кучи микросхем для измерения температуры, с I2C интерфейсом. У большинства крупных производителей такой чип непременно есть в "портфолио". LM76 не предполагает высокой точности, и поэтому позорно называется "оценщик температуры". На самом деле она дает точность измерений около одного градуса Цельсия. В общем, почитайте даташит на досуге.
В этом чипе 6 регистров. Регистр, из которого нужно читать температуру, выбирается по умолчанию после включения питания. Подробнее об этих регистрах я расскажу позже. LM76 меряет и запоминает температуру с точностью 0.0625oC. Температура хранится как 16-битное слово, считывается как два байта:



D0-D2 не определены. D15 - бит знака, само значение температуры лежит в D3-D14. Температура хранится в обычном двоичном представлении. Это значит, что положительные числа хранятся как есть, с битом знака равным 0. Отрицательные числа хранят 1 в бите знака, сам модуль числа записан в инвертированном виде и увеличен на 1. Если вы предполагаете использовать датчик для измерения только плюсовых температур, забудьте про эти махинации со знаками и просто считывайте биты D3-D14. Однако если вы из мест не столь отдаленных, вас наверняка заинтересует метод считывания отрицательных значений. Для этого я приготовил вам следующий код:
...
Dim Tempint as Word
Dim Tempbytelo as Byte
Dim Tempbytehi as Byte
...
'считаем два байта из lm76. Это просто, как два байта переслать =)
...
Tempint = Makeint(Tempbytelo, Tempbytehi)
Tempsign = Tempbytehi And &B1000000
If Tempsign = &B10000000 Then
  Tempint = Not Tempint
  Tempint = Tempint + 1
End If
Shift Tempint, Right, 3
...
Makeint объединяет принятые два байта в одно 16-битное слово и кладет в переменную Tempint. Затем, путем откусывания самого старшего (левого) бита, извлекается знак числа. Если бит равен 1, нам нужно инвертировать все биты в Tempint (убрав тем самым бит знака) и увеличить результат на 1. Наконец, все биты в Tempint нужно сдвинуть вправо на 3 разряда, убрав тем самым бесполезные биты D0-D2. Теперь в Tempint хранится модуль температуры с точностью 0.0625oC, а в Tempsign лежит знак температуры. Все проще некуда.
Посему соберем следующую интересную схему:



И введём следующую интересную программу:
i2c-lm76.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 100
Config Pind.6 = Output

Const Lm76write = &H90
Const Lm76read = &H91

Dim Tempint As Integer At $80
Dim Tempbytelo As Byte At $80 Overlay
Dim Tempbytehi As Byte At $81 Overlay
Dim Templong As Long

Cls

Do
  Set Portd.6
  I2cstart
  I2cwbyte Lm76write
  'указатель на регистр температуры.
  'хоть и установлен по умолчанию, он здесь просто для примера.
  I2cwbyte 0
  I2cstart
  I2cwbyte Lm76read
  I2crbyte Tempbytehi , Ack
  I2crbyte Tempbytelo , Nack
  I2cstop
  Cls
  Lcd Tempbytehi ; " " ; Tempbytelo
  Lowerline
  Shift Tempint , Right , 3
  Templong = Tempint * 65
  Lcd Tempint ; " " ; Templong
  Reset Portd.0.6
  Wait 1
Loop

End
В этом примере игнорируется знак температуры. Поэтому нужно привести еще один, более продвинутый пример, который будет показывать нам температуру сразу в градусах Цельсия:
i2c-lm76-fusing.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 100
Config Pind.6 = Output

Const Lm76write = &H90
Const Lm76read = &H91
Const Lm76resolution = 0.0625

Dim Tempint As Word
Dim Tempbytelo As Byte
Dim Tempbytehi As Byte
Dim Temperature As Single
Dim Tempstring As String * 4
Dim Tempsign As Byte
Dim Flashnumber As Byte
Dim Flashloop As Byte
Dim Flashtime As Word

Do
  Set Portd.6
  Wait 1
  Cls
  I2cstart
  I2cwbyte Lm76read
  I2crbyte Tempbytehi , Ack
  I2crbyte Tempbytelo , Nack
  I2cstop
  Lcd Tempbytehi ; " " ; Tempbytelo ; " "
  Tempint = Makeint(tempbytelo , Tempbytehi)
  Tempsign = Tempbytehi And 128
  If Tempsign = 128 Then
    Tempint = Not Tempint
    Tempint = Tempint + 1
   End If
  Shift Tempint , Right , 3
  Lcd Tempint ; " "
  Temperature = Tempint * Lm76resolution
  Lowerline
  Lcd Temperature ; " "
  Tempstring = Fusing(temperature , "##.#")
  If Tempsign = 128 Then Lcd "-"
  Lcd Tempstring
  Reset Portd.6
  Wait 1
Loop

End
Эта программа использует операции с плавающей точкой. Для этого подгружаются специальные библиотеки. Хоть программа и очень маленькая, она займет 99% памяти в вашем AT90S2313 (а в ATTINY2313 даже не влезет)!
При работе с маленькими контроллерами следует избегать операций с плавающими точками, где это возможно. Следующая программа покажет, как сделать это, используя переменные типа Integer. Она также будет работать намного быстрее:
i2c-lm76-usingintegers.bas
$regfile = "2313def.dat"
$crystal = 4000000

Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 100
Config Pind.6 = Output

Const Lm76write = &H90
Const Lm76read = &H91
Const Lm76resolution = 625

Dim Tempint As Word
Dim Tempbytelo As Byte
Dim Tempbytehi As Byte
Dim Tempall As Long
Dim Temprem As Long
Dim Temptemp As Long
Dim Tempdeg As Long
Dim Tempsign As Byte
Dim Tempdigit As Integer

Do
  Cls
  Set Portd.6
  I2cstart
  I2cwbyte Lm76read
  I2crbyte Tempbytehi , Ack
  I2crbyte Tempbytelo , Nack
  I2cstop
  Lcd Tempbytehi ; Tempbytelo ; " "
  Tempint = Makeint(tempbytelo , Tempbytehi)
  Tempsign = Tempbytehi And 128
  If Tempsign = 128 Then
    Tempint = Not Tempint
    Tempint = Tempint + 1
   End If
  Shift Tempint , Right , 3
  Lcd Tempint ; " "
  Tempall = Tempint * Lm76resolution
  Lcd Tempall ; " "
  Tempdeg = Tempall / 10000
  Temptemp = Tempdeg * 10000
  Temprem = Tempall - Temptemp
  Lowerline
  Lcd Tempdeg ; " " ; Temprem ; " "

  Tempdigit = Temprem / 1000
  Temptemp = Tempdigit * 1000
  Temprem = Temprem - Temptemp

  If Temprem > 499 Then Tempdigit = Tempdigit + 1
  If Tempdigit > 9 Then
    Tempdigit = 0
    Tempdeg = Tempdeg + 1
  End If

  If Tempsign = 128 Then Lcd "-"
  Lcd Tempdeg ; "." ; Tempdigit

Loop

End
В этой программе больше строк, но, как ни странно, она занимает всего 85% в памяти контроллера, оставляя место для некоторых других функций.

Управление графическим дисплеем Philips LPH7653

Дисплей Philips LPH7653 вероятно был разработан для мобильных телефонов ранних поколений. Он имеет разрешение 97x35 пикселей.
Информацию по нему можно найти тут.
Прочитайте это описание (хоть оно и на английском, просто посмотрите картинки)! Наверняка вам придется подбирать напряжения питания и регулировки контрастности, чтобы суметь что-то прочитать на нем. Светодиодную подсветку можно питать стандартным напряжением +5 вольт.

LP7653 - чисто графический дисплей. Он не имеет знакогенератора, это значит, что вам придется самим рисовать символы, если вы хотите выводить текст. Но это не так сложно, как может показаться.
Дисплей имеет ширину в 97 пикселей, однако судя по всему, видеопамять у него рассчитана на 101 пиксель. Каждая строка из 8 пикселей имеет индивидуальную адресацию:



В программном плане это выглядит так:
I2cstart
I2cwbyte lph7653writeaddress '(7Ahex)
I2cwbyte linenumber          '(60, 61, 62, 63 or 64hex для строк 0, 1, 2, 3 и 4)
I2cwbyte pixelcolumn         '(00...60hex для столбцов 0...97)
I2cwbyte pixelbyte           '(младший бит - сверху)
I2cwbyte pixelbyte           '(pixelcolumn/linenumber идёт pixelcolumn 64hex)
I2cwbyte ...
I2cstop
Для очистки экрана вы можете записать 5x101 нулевых байтов начиная с первой строки и первого столбца. Если вы планируете использовать дисплей только для вывода текста, разумно было бы использовать его как четырёхстрочный. Тогда вы можете использовать символы высотой в 8 пикселей. Вы можете выводить стандартные символы 7x5 как символы 7x6. Таким образом, нижний ряд пикселей будет пустым, создавая отступ между строками текста. Пятая строка доступна для вывода, но отображаться будут только верхние три ряда пикселей (35 - (4 x 8)).
Для проверки соберём следующую схему:


И введём следующую программу:
i2c-lph7653-75.bas для символов 7x5
или i2c-lph7653-75prop.bas для символов 7x6
(листинги здесь не приводятся, так как там много скучных и неинтересных строк)

Программа i2c-lph7653-76prop.bas использует символы 7x6, поэтому каждая строка Data хранит 6 байтов.



Если вы хотите использовать дисплей для вывода графики, вам нужно представить изображение в виде 8-битных столбцов пикселей. Это немного отличается от метода вывода на дисплей Toshiba T6963, для которого в Bascom есть утилита конвертирования изображений.
Если вы просто хотите "слить" изображение на дисплей, лучше всего привести его к разрешению 101x35 и выводить видимую часть 97x32 начиная с левого верхнего края. Конечно, вы можете выводить и меньшие изображения, но тогда внимательно следите за тем, как меняется адресация.
Для преобразования изображений в блок данных есть небольшая утилита на Delphi. Она считывает черно-белое изображение BMP и выводит в виде текста, который вы можете скопировать и вставить в программу. Исходные коды прилагаются!
Вот вам пример работы:



Загружаем первый битмап в утилиту и видим следующее:



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

Управление синтезатором частоты Linear Technology LT6904

LT6904 - это чип от Linear Technology, выполняющий функцию синтезатора частоты. Его собрат LT6903 имеет SPI интерфейс, но LT6904 использует I2C. Еще есть чипы LT1799, LT6900, LT6902 и LT6905, в которых частота задается внешним переменным резистором.
LT6904 управляется следующим путем (согласно даташиту):





где OCT - четырёхбитное и DAC - десятибитные слова. Также еще два бита применяются для конфигурирования выходов. Убедитесь, что вы отключили неиспользуемый выход (читайте даташит!) В итоге мы отсылаем 16 бит в таком порядке:
Oct3, Oct2, Oct1, Oct0, DAC9, DAC8...DAC0, Cfg1, Cfg0.
Соберём такую схему:



Обратите внимание, даташит утверждает, что напряжение питание может быть от 2.7 до 5.5 вольт. Однако была замечена неприятная вещь - при питании 5 вольт, выход сигнала LT6904 "пропадает". Такая особенность поведения пропала после понижения питания до 3.3 вольта.
Загрузим для самостоятельного изучения следующую программу:
i2c-lt6904.bas Автор оригинала не предоставил код. Пусть это будет на его совести!

У нас возникает небольшая проблема: Bascom говорит, что программа займет 162% программной памяти. Очевидно, что эта программа жирновата для AT90S2313. Пора обратиться к более мощным контроллерам. Об этом будет рассказано в следующих главах.

 



DECADALAB ALFA