Внешняя eeprom. Микроконтроллеры STM32: работа с внешним EEPROM. Прерывание по снижению уровня питания

Внешняя eeprom. Микроконтроллеры STM32: работа с внешним EEPROM. Прерывание по снижению уровня питания

13.03.2019

NetGuard представляет собой фаервол для блокировки рекламы в интернете на телефоне андроид. Для полноценной работы не требуются права суперпользователя (рут права).

Приложение NetGuard бесплатно, просто в использовании и не перегружена лишними элементами. Блокировщик рекламы для андроид NetGuard без root прав и значально позиционировался только как фаервол для Android с тонкой настройкой каждого приложения, благодаря набору правил и условий. В последних версиях появилась блокировка рекламных объявлений, но так как Google негативно относится к таким утилитам, разработчики загрузили приложение на github . Там же опубликован исходный код, доступны актуальные и предыдущие сборки. Версия с Google Play содержит только фаервол.

В одно касание приложению разрешается или запрещается использовать мобильную и\или беспроводную сеть. Ограничение на передачу данных Wi-Fi актуально, если точка доступа лимитирована по трафику. Предусмотрен журнал заблокированного и фильтр сетевого трафика; уведомление о том, когда приложение связывается с удаленным сервером; 6 тем графического оформления с выбором светлого или темного цвета фона и т.д.

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

Основные преимущества:

  1. Нет рекламы.
  2. Фаервол и блокировка рекламы без использования root прав (доступ суперпользователя).
  3. Выбор типа сети и запрет на использование интернета для системных и сторонних приложений.
  4. Журнал трафика.
  5. Смена графического оформления.

Основные недостатки:

  1. Некоторые опции покупаются за отдельную плату, в то числе смена оформления.
  2. Местами некорректный перевод и возможные сбои отображения.

Вывод

При использовании мобильного интернета, до 35% трафика съедает реклама, а при просмотре новостных сайтов показатель поднимается до 79%. Не каждый браузер обладает опцией эффективного блокирования рекламы, или для полноценной работы требуются права суперпользователя. Поэтому не удивительно, что пользователи Android ищут способы блокировки рекламы без рута. NetGuard решит эту проблему, а так же заменит вам фаервол.

Статья была Вам полезной?
Поставьте оценку - поддержите проект!

NetGuard - неплохой фаервол для Android, который позволяет заблокировать доступ в интернет каждой установленной программе. Не так давно в появилась свежая функция - блокировка рекламы с помощью фильтрации трафика без . Как активировать блокировку рекламы в этом фаерволе - читайте в нашей небольшой статье-инструкции.

Для начала стоит отметить, что функция блокировки рекламы в NetGuard доступна в версии приложения с официальной страницы проекта на GitHub. Вариация в Google Play не имеет таких возможностей, так как в каталог больше не пропускают блокировщики рекламы. На Трешбоксе в названии отмечены те версии NetGuard, которые содержат блокировку рекламных объявлений.

Инструкция по активации блокировки рекламы в NetGuard

Итак. Приложение скачано и установлено. Открываем NetGuard и заходим в настройки, не активируя фаервол.


Ищем пункт «Фильтр трафика» и активируем его тумблером. Программа предупредит о возможном расходе заряда аккумулятора, но он незначительный.


После этого ищем в меню пункт «Скачать hosts-файл» и нажимаем на него. NetGuard автоматически скачает hosts-файл с большинством адресов, с которых обычно загружается реклама. Загрузка трафика с них будет заблокирована. Альтернативные файлы hosts можно найти на GitHub , скачать оттуда и импортировать в приложение.


Далее возвращаемся в главное меню приложения и активируем его работу тумблером в левом верхнем углу. Программа начнет работать, когда в верхнем баре появится значок ключа (VPN), а индикатор рядом с тумблером примет форму иконки NetGuard, а не песочных часов. После активации желательно подождать 5–10 минут - за это время обновятся все DNS-адреса.


Позже проверяем работу блокировщика. Открываем тестовую страницу на официальном сайте NetGuard. Если тест выдает “Ad Blocking Works”, то это значит, что блокировка работает.

Слева - до блокировки, справа - после


Чтобы наглядно убедиться в этом, переходим на какой-нибудь популярный сайт с большим количеством рекламы, например - zaycev.net.

Также смотрите нашу видео-инструкцию по активации блокировки рекламы в NetGuard :

Когда нужно длительное время хранить какие-нибудь рабочие данные, не боясь их потерять, используют штатную, встроенную в микроконтроллер, EEPROM память. Обычно размер этой памяти не велик и его хватает только для хранения каких-то небольших по объему данных, например пользовательских настроек или т.п. А если нужно хранить данные размером десяток килобайт, то понятно что встроенной памятью не обойтись и нужно подключать внешнее устройство хранения. И тут как нельзя лучше подходят внешние микросхемы EEPROM. Например микросхемы из серии 24LCxx от компании Microchip. Эти микросхемы поддерживают подключение по протоколу I2C и умеют хранить от 512 байт до 128 килобайт данных. К тому же, старшие модели могут работать в связке из себе подобных, таким образом размер памяти может быть увеличен за счет присвоения микросхемам памяти индивидуального адреса на I2C шине. В младших же моделях, все микросхемы имеют фиксированный адрес 1010.


В номенклатуре Microchip серии 24LC числовое значение после буквенного индекса обозначает объем памяти в килобитах . Так, подопытная микросхема 24LC08 имеет на борту 8 килобит пространства под хранение данных (или 1 килобайт).

Подключение микросхемы

Данная микросхема выпускается в различных корпусах: DIP, SOIC, TSOP, DFN. Для каждого восьминогового типа корпуса сохраняется распиновка контактов.

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

Назначение выводов микросхемы смотрим ниже:

A0, A1, A2 - в данной микросхеме не используются, в старших моделях они служат для присвоения микросхеме индивидуального адреса на I2C шине.

SDA – линия данных

SCL – линия тактовых импульсов

WP – защита от записи. Если на данный вывод подан логический 0, то запись в память разрешена. Если подать логическую единицу, то возможно только чтение из памяти.

Vcc – питание микросхемы. Напряжение может быть в пределах от 2.5 вольта до 5.5 вольта.

Vss – земля.

К микроконтроллеру микросхема подключаются следуя традициям протокола I2C, тоесть сигнальные линии подтягиваются к шине питания через резисторы номиналом 4,7к. Неиспользуемые выводы A0, A1, A2 можно посадить на землю (IC1 в примере Attiny2313)


Организация памяти

Для того чтобы понять принцип работы с микросхемами памяти, нужно разобраться как происходит адресация внутри микросхемы. В подопытной микросхеме 24LC08 все пространство памяти поделено на 4 блока по 256 байт в каждом блоке. Каждый блок имеет свой адрес.


При обращении к микросхеме ведущее устройство (микроконтроллер) отправляет адрес устройства (он у нас фиксированный 1010) и адрес блока с которым нужно работать.

Затем отправляется адрес ячейки в которую нужно записать/прочитать данные. Что нужно сделать с данными - прочитать или записать - зависит от бита в конце посылки. Разберем на примерах.

Запись данных

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

  1. Отослать стартовый бит
  2. Отослать адрес микросхемы + адрес блока памяти. В конце посылки должен стоять 0 (бит записи)
  3. Отослать адрес ячейки памяти в которую будет производится запись
  4. Отослать байт данных
  5. Отослать стоповый бит

К примеру запишем один байт &hFF в первую ячейку памяти первого блока (адрес блока &b000 , адрес ячейки &h00 ).


$regfile = "2313def.dat"
$crystal = 1000000


Config Sda = Portb . 7 "I2C Data
Config Scl = Portb . 6 "I2C Clock

Wait 1

"работа с микросхемой
I2cstart "даем сигнал старт i2c шине
I2cwbyte & B10100000
I2cwbyte & H00 "отправляем адрес ячейки
I2cwbyte & HFF "отправляем байт, который нужно записать
I2cstop "останавливаем работу i2c

End

Постраничная запись

Для увеличения скорости записи данных существует метод постраничной записи. Одна страница - это область из 16 байт (один столбец на картинке выше). При постраничной записи адрес записываемой ячейки увеличивается автоматически, поэтому
не нужно каждый раз вручную прописывать адрес. Для записи одной страницы отправляем адрес первой ячейки и затем 16 раз отправляем необходимые данные, причем если отправить 17 байт, то последний байт перезапишет первый и т.д. Для примера запишем первую страницу первого блока. Адрес первой ячейки
&h00 .


$regfile = "2313def.dat"
$crystal = 1000000

"конфигурируем scl и sda пины
Config Sda = Portb . 7 "I2C Data
Config Scl = Portb . 6 "I2C Clock

Wait 1

"работа с микросхемой
I2cstart "даем сигнал старт i2c шине
I2cwbyte & B10100000 "отправляем адрес микросхемы и адрес блока
I2cwbyte & H00 "отправляем адрес первой ячейки

I2cwbyte & HF0 "отправляем 1 байт
I2cwbyte & HF1 "отправляем 2 байт
I2cwbyte & HF2 "отправляем 3 байт
I2cwbyte & HF3 "отправляем 4 байт
I2cwbyte & HF4 "отправляем 5 байт
I2cwbyte & HF5 "отправляем 6 байт
I2cwbyte & HF6 "отправляем 7 байт
I2cwbyte & HF7 "отправляем 8 байт
I2cwbyte & HF8 "отправляем 9 байт
I2cwbyte & HF9 "отправляем 10 байт
I2cwbyte & HFA "отправляем 11 байт
I2cwbyte & HFB "отправляем 12 байт
I2cwbyte & HFC "отправляем 13 байт
I2cwbyte & HFD "отправляем 14 байт
I2cwbyte & HFE "отправляем 15 байт
I2cwbyte & HFF "отправляем 16 байт

I2cstop "останавливаем работу i2c

End


Здесь записывается вся первая страница числами от 240 (в шестнадцатеричной системе F0) до 255 (FF).

Чтение данных

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

  1. Сконфигурировать интерфейс I2C
  2. Отправить стартовый бит
  3. Отправить адрес микросхемы + адрес блока памяти откуда нужно читать
  4. Отправить адрес ячейки памяти
  5. Снова отправить стартовый бит
  6. Отправить адрес микросхемы и адрес блока памяти с битом «чтение»
  7. Получить байт
  8. Отправить стоповый бит

Чтение первой ячейки первого блока будет выглядеть так:


$regfile = "2313def.dat"
$crystal = 1000000

Dim A As Byte "переменная для хранения прочитанного байта

"конфигурируем scl и sda пины
Config Sda = Portb . 7 "I2C Data
Config Scl = Portb . 6 "I2C Clock

Wait 1

"работа с микросхемой

I2cstart "даем сигнал старт i2c шине
I2cwbyte & B10100000 "отправляем адрес микросхемы и адрес блока
I2cwbyte & H00 "отправляем адрес ячейки

Одна из проблем с микроконтроллерами STM32 заключается в том, что большинство из них не имеют встроенного EEPROM . Исключением являются только микроконтроллеры серий STM32L0 и STM32L1 с ультра низким энергопотреблением. Это довольно странно после работы с AVR, где EEPROM есть у всех микроконтроллеров. Существует несколько решений, но в рамках этой заметки мы рассмотрим самое очевидное — использование внешнего EEPROM на примере чипа с I2C-интерфейсом 24LC64.

Цифра 64 в названии говорит о том, что устройство имеет 64 килобит памяти, или 8 Кбайт. Есть аналогичные чипы от разных производителей и с разными объемами памяти — я видел от 128 байт (например, M24C01 от компании ST) до 256 Кбайт (AT24CM02 производства Atmel). В плане распиновки и интерфейса все они абсолютно взаимозаменяемы. Далее я буду говорить о 24LC64, производимом компанией Microchip, так как сам использовал именно его.

Распиновка 24LC64 выглядит так (даташит ):

VSS и VCC, понятно, представляют собой минус и плюс питания, а SDA и SCL — это I2C шина. Устройство имеет I2C-адрес 0b1010zyx, где значения x, y и z определяются тем, к чему подключены пины A0, A1 и A2. Если пин подключен к земле, соответствующий бит адреса равен нулю, если же к плюсу — то единице. Таким образом, устройство может иметь адрес от 0 x 50 до 0 x 57. Наконец, пин WP — это write protection. Если пин подключен к земле, разрешено как чтение, так и запись. Если же пин подключен к плюсу, устройство доступно только на чтение.

Создаем новый проект в STM32CubeMX для вашей отладочной платы. Лично я все также использую плату Nucleo-F411RE, но если вы используете другую, отличия в проекте должны быть минимальными. В Peripherals включаем устройство I2C1, выбрав в выпадающем списке «I2C» вместо «Disable». Также мы будем передавать что-то компьютеру, поэтому включаем устройство USART2, как делали это в заметке Микроконтроллеры STM32: обмен данными по UART . Чтобы плату можно было использовать с Arduino-шилдами, несущими какие-то I2C-устройства, пины I2C1 нужно переназначить на PB9 и PB8 (по умолчанию будут назначены другие: PB7 и PB6). В итоге должна получиться такая картинка:

Затем генерируем код и подправляем Makefile, как обычно. Я лично просто взял Makefile из исходников к заметке про UART и дописал недостающие файлы в C_SOURCES, а именно:

$(FIRMWARE)/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c \
$(FIRMWARE)/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c \

Для подключения 24LC64 к плате Nucleo я воспользовался Proto Shield от Arduino. Пришлось прорезать в нем отверстие Dremel’ем, чтобы иметь доступ к кнопкам на отладочной плате. В итоге у меня получился вот такой сэндвич:

Кто-то из читателей мог обратить внимание на то, что в I2C-шине должны использоваться резисторы, подтягивающие SCL и SDA к плюсу, но в этом проекте мы их не используем. Дело в том, что для моей платы и использованного в ней микроконтроллера STM32CubeMX автоматически включает встроенные подтягивающие резисторы на соответствующих пинах.

Убедиться в этом несложно, посмотрев код процедуры HAL_I2C_MspInit в файле./Src/stm32f4xx_hal_msp.c:

// ...
/**I2C1 GPIO Configuration
PB8 ------> I2C1_SCL
PB9 ------> I2C1_SDA
*/

GPIO_InitStruct.Pin = GPIO_PIN_8| GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
HAL_GPIO_Init(GPIOB, & GPIO_InitStruct) ;
// ...

Правда, не так очевидно, почему процедура HAL_I2C_MspInit вообще откуда-то вызывается. Ответ можно найти в файле stm32f4xx_hal_i2c.c где эта процедура объявляется с атрибутом __weak и вызывается из процедуры HAL_I2C_Init. Атрибут __weak работает таким образом, что при сборке не-weak процедура из кода нашего проекта подменяет собой weak-процедуру из HAL, за счет чего она и будет вызвана.

Заметьте, однако, что встроенные подтягивающие резисторы доступны не во всех микроконтроллерах STM32. Насколько мне известно, для серии STM32F1 это работать не будет, и придется все-таки использовать внешние подтягивающие резисторы.

Наконец, рассмотрим основной код прошивки:

void init() {
const char wmsg = "Some data" ;
char rmsg[ sizeof (wmsg) ] ;
// HAL expects address to be shifted one bit to the left
uint16_t devAddr = (0x50 << 1 ) ;
uint16_t memAddr = 0x0100 ;
HAL_StatusTypeDef status;

// Hint: try to comment this line
HAL_I2C_Mem_Write(& hi2c1, devAddr, memAddr, I2C_MEMADD_SIZE_16BIT,
(uint8_t * ) wmsg, sizeof (wmsg) , HAL_MAX_DELAY) ;

for (;; ) { // wait...
status = HAL_I2C_IsDeviceReady(& hi2c1, devAddr, 1 ,
HAL_MAX_DELAY) ;
if (status == HAL_OK)
break ;
}

HAL_I2C_Mem_Read(& hi2c1, devAddr, memAddr, I2C_MEMADD_SIZE_16BIT,
(uint8_t * ) rmsg, sizeof (rmsg) , HAL_MAX_DELAY) ;

if (memcmp (rmsg, wmsg, sizeof (rmsg) ) == 0 ) {
const char result = "Test passed!\r \n " ;

HAL_MAX_DELAY) ;
} else {
const char result = "Test failed:(\r \n " ;
HAL_UART_Transmit(& huart2, (uint8_t * ) result, sizeof (result) - 1 ,
HAL_MAX_DELAY) ;
}
}

Для работы с внешней памятью в HAL предусмотрены специальные процедуры HAL_I2C_Mem_Read и HAL_I2C_Mem_Write. Заметьте, что эти процедуры работают с I2C адресами, сдвинутыми на 1 бит влево. Связано это с тем, что в протоколе I2C семибитный адрес устройства и бит операции (чтение или запись) передаются в одном байте. Использование «сдвинутых» адресов позволяет выполнять чуть меньше ассемблерных инструкций, что нередко бывает важно в разработке встраиваемых систем. Еще стоит обратить внимание на то, что перед чтением с устройства мы должны дождаться его готовности с помощью процедуры HAL_I2C_IsDeviceReady. Наконец, здесь я забил на коды возврата большинства использованных процедур, чего в боевом коде, пожалуй, делать не стоит.

Fun fact! Вдумчивый читатель, конечно же, обратил внимание на тот факт, что адрес памяти имеет тип uint16_t . Спрашивается, как можно адресовать им более 64 Кбайт памяти, например, те же 256 Кбайт у AT24CM02? Само собой разумеется, никак. Чипы, имеющие более 64 Кбайт памяти, начинают использовать для адресации младшие биты I2С-адреса устройства. То есть, с точки зрения нашего кода, они будут выглядеть, как 2 или более отдельных I2C-устройства. Соответствующие пины, определяющие адрес устройства, при этом являются NC, то есть, ни к чему не подключаются.

При работе с EEPROM нужно учитывать еще пару важных моментов:

  • 24LC64 и его родственники хранят данные в страницах по 32 байта. За один вызов HAL_I2C_Mem_Write вы можете записать только одну страницу, причем нужно учитывать не только размер данных, но и их выравнивание. Если требутеся записать больше одной страницы, процедуру нужно вызывать в цикле. На чтение, насколько я смог выяснить, таких ограничений нет — читать можно сколько угодно и по любым смещениям;
  • Запись в EEPROM может быть прервана в любой момент (сел аккумулятор, пользователь выдернул кабель питания, …). Поэтому в боевом коде стоит хранить вместе с данными их контрольную сумму. В случае, если будет записана только часть данных, это позволит обнаружить проблему и использовать хотя бы параметры по умолчанию, а не какой-то мусор.

Полную версию исходников к этой заметке вы найдете на GitHub . В качестве небольшого домашнего задания можете модифицировать прошивку так, чтобы она дампила все содержимое EEPROM и передавала его по UART. Код форматирования бинарных данных в стиле того, как это делает hexdump, можно взять из поста Перехват сетевого трафика при помощи библиотеки libpcap (процедура print_data_hex). В итоге должен получиться приятный такой отладочный инструмент — простенький, но со вкусом.

А доводилось ли вам работать с внешней памятью, и если да, то с какой именно?

Дополнение: Также вас могут заинтересовать посты

Для того чтобы полностью разобраться с Two-Wire Interface (TWI) , пишем с нуля в AVR STUDIO процедуры инициализации, чтения и записи. Подробно останавливаемся на каждом шаге и разбираемся. Затем промоделируем все в Proteus.

I. Кратко теория

Аппаратный модуль TWI и протокол I2C

В микроконтроллеры серии MEGA входит модуль TWI, с помощью которого мы будем работать с шиной I2C. Модуль TWI по сути является посредником между нашей программой и подключаемым устройством (память I2C EEPROM, например).

Структура модуля TWI в микроконтроллерах AVR

Шина I2C состоит из двух проводов:

  • SCL (Serial Clock Line) – линия последовательной передачи тактовых импульсов.
  • SDA (Serial Data Line) – линия последовательной передачи данных.

К этой шине мы можем одновременно подключить до 128 микросхем.

Подключения к шине TWI

Наш контроллер будем называть ведущим, а все подключаемые микросхемы ведомыми. Каждый ведомый имеет определенный адрес, зашитый на заводе в ходе изготовления (кстати, граница в 128 микросхем как раз и определяется диапазоном возможных адресов). С помощью этого адреса мы и можем работать с каждый ведомым индивидуально, хотя все они подключены к одной шине одинаковым образом. Из нашей программы мы управляем модулем TWI, а этот модуль берет на себя дергание ножками SCL и SDA.

Как видно на блок-схеме, TWI условно состоит из четырех блоков. Вначале нам нужно будет настроить Генератор скорости связи, и мы сразу забудем про него. Основная работа – с Блоком управления.

Блок шинного интерфейса управляется Блоком управления, т.е. непосредственно с ним мы контактировать не будем. А Блок сравнения адреса нужен, чтобы задать адрес на шине I2C, если тока наш контроллер будет подчиненным устройством(ведомым) от другого какого-то контроллера или будет приемником от другого ведущего (как в статье Налаживаем обмен данными между двумя контроллерами по шине I²C на моем блоге). В этой статье он нам не нужен. Если хотите, почитайте про него в любом даташите контроллера серии MEGA.

Итак, наша задача сейчас разобраться с регистрами, входящими в Генератор скорости связи и Блок управления:

 Регистр скорости связи TWBR

 Регистр управления TWCR

 Регистр статуса(состояния) TWSR

 Регистр данных TWDR

И все, разобравшись всего лишь с 4-мя регистрами, мы сможем полноценно работать с внешней памятью EEPROM и вообще, обмениваться данными по I2C с любым другим устройством.

Генератор скорости связи и Регистр скорости связи TWBR

Блок генератора скорости связи управляет линией SCL, а именно периодом тактовых импульсов. Управлять линией SCL может только ведущий. Период SCL управляется путем установки регистра скорости связи TWI (TWBR) и бит предделителя в регистре статуса TWI (TWSR).

Частота SCL генерируется по следующей формуле:

F SCL = F ЦПУ /,

  • TWBR — значение в регистре скорости связи TWI;
  • TWPS — значение бит предделителя в регистре статуса TWI (TWSR).
  • F цпу – тактовая частота ведущего
  • F SCL – частота тактовых импульсов линии SCL

Настройка TWBR нужна, т.к. ведомая микросхема обучена обмениваться данными на определенной частоте. Например, в Proteus, введите в поиск I2CMEM, увидите обилие микросхем памяти, в основном у них указаны частоты 100 и 400Khz.

Ну вот, подставляя в формулу частоты F ЦПУ и F SCL , мы сможем найти оптимальное значение для регистра TWBR.

TWPS – это 2-битное число , первый бит – это разряд TWPS0, второй – TWPS1 в регистре статуса TWSR. Задавая Предделитель скорости связи 4 TWPS , мы можем понижать значение TWBR (т.к. TWBR – это байт, максимальное значение 255). Но обычно это не требуется, поэтому TWPS обычно задается 0 и 4 TWPS = 1. Гораздо чаще, наоборот, мы упираемся в нижний диапазон регистра TWBR. Если TWI работает в ведущем режиме, то значение TWBR должно быть не менее 10. Если значение TWBR меньше 10, то ведущее устройство шины может генерировать некорректные сигналы на линиях SDA и SCL во время передачи байта.

Частота ЦПУ, МГц TWBR TWPS Частота SCL, КГц
16.0 12 0 400
16.0 72 0 100
14.4 10 0 400
14.4 64 0 100
12.0 52 0 100
8.0 32 0 100
4.0 12 0 100
3.6 10 0 100

Ну вот, настройка TWI в этом и заключается:

 задание значения предделителя ( в регистре статуса TWSR)

Регистр состояния TWI — TWSR

Разряд 7 6 5 4 3 2 1 0
TWS7 TWS6 TWS5 TWS4 TWS3 TWPS1 TWPS0
Чтение/запись Чт. Чт. Чт. Чт. Чт. Чт. Чт./Зп. Чт./Зп.
Исх. значение 1 1 1 1 1 0 0 0

 задание скорости связи (TWBR, Регистр скорости связи).

Регистр скорости связи шины TWI — TWBR

Разряд 7 6 5 4 3 2 1 0
TWBR7 TWBR6 TWBR5 TWBR4 TWBR3 TWBR2 TWBR1 TWBR0
Чтение/запись Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп.
Исх. значение 0 0 0 0 0 0 0 0

Блок управления

Регистр TWCR предназначен для управления работой TWI. Он используется для разрешения работы TWI, для инициации сеанса связи ведущего путем генерации условия СТАРТ на шине, для генерации подтверждения приема, для генерации условия СТОП и для останова шины во время записи в регистр TWDR. Он также сигнализирует о попытке ошибочной записи в регистр TWDR, когда доступ к нему был запрещен.

Регистр управления шиной TWI — TWCR

Разряд 7 6 5 4 3 2 1 0
TWINT TWEA TWSTA TWSTO TWWC TWEN TWIE
Чтение/запись Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт. Чт./Зп. Чт. Чт./Зп.
Исх. значение 0 0 0 0 0 0 0 0

 Разряд 7 — TWINT: Флаг прерывания TWI

Этот флаг устанавливается аппаратно, если TWI завершает текущее задание (к примеру, передачу, принятие данных) и ожидает реакции программы. Линия SCL остается в низком состоянии, пока установлен флаг TWINT. Флаг TWINT сбрасывается программно путем записи в него логической 1. Очистка данного флага приводит к возобновлению работы TWI, т.е. программный сброс данного флага необходимо выполнить после завершения опроса регистров статуса TWSR и данных TWDR.

 Разряд 6 — TWEA: Бит разрешения подтверждения

Бит TWEA управляет генерацией импульса подтверждения. Как видно в таблице, по умолчанию он сброшен. Останавливаться на нем не буду, он в данной статье не пригодится.

 Разряд 5 — TWSTA: Бит условия СТАРТ

Мы должны установить данный бит при необходимости стать ведущим на шине I2C. TWI аппаратно проверяет доступность шины и генерирует условие СТАРТ, если шина свободна. Мы проверяем это условие (по регистру статуса, будет далее) и если шина свободна, то мы можем начинать с ней работать. Иначе нужно будет подождать, пока шина освободится.

 Разряд 4 — TWSTO: Бит условия СТОП

Установка бита TWSTO в режиме ведущего приводит к генерации условия СТОП на шине I2C. Если на шине выполняется условие СТОП, то бит TWSTO сбрасывается автоматически и шина освобождается.

 Разряд 3 — TWWC: Флаг ошибочной записи

Бит TWWC устанавливается при попытке записи в регистр данных TWDR, когда TWINT имеет низкий уровень. Флаг сбрасывается при записи регистра TWDR, когда TWINT = 1.

 Разряд 2 — TWEN: Бит разрешения работы TWI

Бит TWEN разрешает работу TWI и активизирует интерфейс TWI. Если бит TWEN установлен, то TWI берет на себя функции управления линиями ввода-вывода SCL и SDA. Если данный бит равен нулю, то TWI отключается и все передачи прекращаются независимо от состояния работы.

 Разряд 1 — Резервный бит

 Разряд 0 — TWIE: Разрешение прерывания TWI

Если в данный бит записана лог. 1 и установлен бит I в регистре SREG (прерывания разрешены глобально), то разрешается прерывание от модуля TWI (ISR (TWI_vect) ) при любом изменении регистра статуса.

Регистр состояния TWI – TWSR

Разряд 7 6 5 4 3 2 1 0
TWS7 TWS6 TWS5 TWS4 TWS3 TWPS1 TWPS0
Чтение/запись Чт. Чт. Чт. Чт. Чт. Чт. Чт./Зп. Чт./Зп.
Исх. значение 1 1 1 1 1 0 0 0

 Разряды 7..3 — TWS: Состояние TWI

Данные 5 бит отражают состояние логики блока TWI и шины I2C. Диапазон кодов состояния 0b0000 0000 – 0b1111 1000, или с 0x00 до 0xF8. Привожу их ниже:

// TWSR values (not bits) // (taken from avr-libc twi.h - thank you Marek Michalkiewicz) // Master (Ведущий) #define TW_START 0x08 //условие СТАРТ #define TW_REP_START 0x10 //условие ПОВТОРНЫЙ СТАРТ (повтор условия начала передачи) // Master Transmitter (Ведущий в роли передающего) #define TW_MT_SLA_ACK 0x18 //Ведущий отправил адрес и бит записи. Ведомый подтвердил свой адрес #define TW_MT_SLA_NACK 0x20 //Ведущий отправил адрес и бит записи. Нет подтверждения приема (ведомый с таким адресом не найден) #define TW_MT_DATA_ACK 0x28 //Ведущий передал данные и ведомый подтвердил прием. #define TW_MT_DATA_NACK 0x30 //Ведущий передал данные. Нет подтверждения приема #define TW_MT_ARB_LOST 0x38 //Потеря приоритета // Master Receiver (Ведущий в роли принимающего) #define TW_MR_ARB_LOST 0x38 //Потеря приоритета #define TW_MR_SLA_ACK 0x40 // Ведущий отправил адрес и бит чтения. Ведомый подтвердил свой адрес #define TW_MR_SLA_NACK 0x48 //Ведущий отправил адрес и бит чтения. Нет подтверждения приема (ведомый с таким адресом не найден) #define TW_MR_DATA_ACK 0x50 //Ведущий принял данные и передал подтверждение #define TW_MR_DATA_NACK 0x58 //Ведущий принял данные и не передал подтверждение // Slave Transmitter (Ведомый в роли передающего) #define TW_ST_SLA_ACK 0xA8 //Получение адреса и бита чтения, возвращение подтверждения #define TW_ST_ARB_LOST_SLA_ACK 0xB0 //Потеря приоритета, получение адреса и бита чтения, возвращение подтверждения #define TW_ST_DATA_ACK 0xB8 //Данные переданы, подтверждение от ведущего принято #define TW_ST_DATA_NACK 0xC0 //Данные переданы. Нет подтверждения приема от ведущего. #define TW_ST_LAST_DATA 0xC8 //Последний переданный байт данных, получение подтверждения // Slave Receiver (Ведомый в роли принимающего) #define TW_SR_SLA_ACK 0x60 //Получение адреса и бита записи, возвращение подтверждения #define TW_SR_ARB_LOST_SLA_ACK 0x68 //Потеря приоритета, получение адреса и бита записи, возвращение подтверждения #define TW_SR_GCALL_ACK 0x70 //Прием общего запроса, возвращение подтверждения #define TW_SR_ARB_LOST_GCALL_ACK 0x78 //Потеря приоритета, прием общего запроса, возвращение подтверждения #define TW_SR_DATA_ACK 0x80 // Прием данных, возвращение подтверждения #define TW_SR_DATA_NACK 0x88 //Данные переданы без подтверждения. #define TW_SR_GCALL_DATA_ACK 0x90 //Прием данных при общем запросе, возвращение подтверждения #define TW_SR_GCALL_DATA_NACK 0x98 // Прием данных при общем запросе, без подтверждения #define TW_SR_STOP 0xA0 //Условие СТОП // Misc (Ошибки интерфейса) #define TW_NO_INFO 0xF8 //Информация о состоянии отсутствует #define TW_BUS_ERROR 0x00 //Ошибка шины

Если мы работаем с пассивным ведомым (т.е. это не другой контроллер AVR, а микросхема памяти или например, часы RTC), то коды состояний из разделов Slave Transmitter (Ведомый в роли передающего) и Slave Receiver (Ведомый в роли принимающего) нам не понадобятся, поскольку единственная «разумная» микросхема у нас – это Ведущий (наш контроллер).

Проверяя регистр статуса после каждой выполненной операции с шиной, мы можем контролировать обмен информацией. Например, будем точно знать – записались ли данные во внешнюю память или нет.

Регистр данных шины TWI — TWDR

Разряд 7 6 5 4 3 2 1 0
TWD7 TWD6 TWD5 TWD4 TWD3 TWD2 TWD1 TWD0
Чтение/запись Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп.
Исх. значение 1 1 1 1 1 1 1 1

В режиме передатчика регистр TWDR содержит байт для передачи. В режиме приемника регистр TWDR содержит последний принятый байт. Будьте внимательны, после аппаратной установки флага TWINT, регистр TWDR не содержит ничего определенного.

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

II. Программа

Все функции (инициализация TWI, чтение, запись внешней памяти) я вынесу в отдельные файлы, как это и принято делать, i2c_eeprom.c и i2c_eeprom.h.

#define false 0 #define true 1 //#define slaveF_SCL 100000 //100 Khz #define slaveF_SCL 400000 //400 Khz #define slaveAddressConst 0b1010 //Постоянная часть адреса ведомого устройства #define slaveAddressVar 0b000 //Переменная часть адреса ведомого устройства //Разряды направления передачи данных #define READFLAG 1 //Чтение #define WRITEFLAG 0 //Запись void eeInit(void); //Начальная настройка TWI uint8_t eeWriteByte(uint16_t address,uint8_t data); //Запись байта в модуль памяти EEPROM uint8_t eeReadByte(uint16_t address); //Чтение байта из модуля памяти EEPROM // TWSR values (not bits) // (taken from avr-libc twi.h - thank you Marek Michalkiewicz) // Master #define TW_START 0x08 #define TW_REP_START 0x10 // Master Transmitter #define TW_MT_SLA_ACK 0x18 #define TW_MT_SLA_NACK 0x20 #define TW_MT_DATA_ACK 0x28 #define TW_MT_DATA_NACK 0x30 #define TW_MT_ARB_LOST 0x38 // Master Receiver #define TW_MR_ARB_LOST 0x38 #define TW_MR_SLA_ACK 0x40 #define TW_MR_SLA_NACK 0x48 #define TW_MR_DATA_ACK 0x50 #define TW_MR_DATA_NACK 0x58 // Slave Transmitter #define TW_ST_SLA_ACK 0xA8 #define TW_ST_ARB_LOST_SLA_ACK 0xB0 #define TW_ST_DATA_ACK 0xB8 #define TW_ST_DATA_NACK 0xC0 #define TW_ST_LAST_DATA 0xC8 // Slave Receiver #define TW_SR_SLA_ACK 0x60 #define TW_SR_ARB_LOST_SLA_ACK 0x68 #define TW_SR_GCALL_ACK 0x70 #define TW_SR_ARB_LOST_GCALL_ACK 0x78 #define TW_SR_DATA_ACK 0x80 #define TW_SR_DATA_NACK 0x88 #define TW_SR_GCALL_DATA_ACK 0x90 #define TW_SR_GCALL_DATA_NACK 0x98 #define TW_SR_STOP 0xA0 // Misc #define TW_NO_INFO 0xF8 #define TW_BUS_ERROR 0x00

Чтобы было легче воспринимать нижеследующий код, приведу здесь теоретические примеры осциллограмм при обмене данными между Ведущим и Ведомым по шине I2C (взял в книжке , в более высоком разрешении найдете по адресу, указанном в конце статьи):

#include #include #include "i2c_eeprom.h" void eeInit(void) { /*Настраиваем Генератор скорости связи*/ TWBR = (F_CPU/slaveF_SCL - 16)/(2 * /* TWI_Prescaler= 4^TWPS */1); /* Если TWI работает в ведущем режиме, то значение TWBR должно быть не менее 10. Если значение TWBR меньше 10, то ведущее устройство шины может генерировать некорректные сигналы на линиях SDA и SCL во время передачи байта. */ if(TWBR < 10) TWBR = 10; /* Настройка предделителя в регистре статуса Блока управления. Очищаются биты TWPS0 и TWPS1 регистра статуса, устанавливая тем самым, значение предделителя = 1. */ TWSR &= (~((1<>8); //..и передаем его TWCR=(1<>8); TWCR=(1<

Ну вот, самые главные функции мы написали. На этой базе можно написать функции для чтения\записи массива байтов. Также можно добавить прерывание ISR(TWI_INT), которое будет срабатывать при каждом изменении регистра статуса. Я скажу только пару слов об этом, поскольку разобравшись в вышеизложенном, вам не составит труда реализовать их самому.

Итак, пару слов о чтении\записи массива байтов. Очень просто взять и вогнать в цикл функции eeReadByte\eeWriteByte. Это даже будет работать  . Но, посмотрите, TWI каждый раз будет формировать условие СТАРТ, устанавливать связь с ведомым, отсылать адрес чтения\записи… Словом, огромная потеря времени. Вы же не покупаете продукты в магазине по частям  , нет — вы складываете все продукты (байты) в кулек (в массив) и несете домой. Дак давайте также сложим все наши байты в кулек! Изменения в новой функции eeWriteBytes

будут следующими, часть кода затаскиваем в цикл:

/*****ЗАПИСЫВАЕМ БАЙТЫ ДАННЫХ********/ //Аналогично, как и передавали адрес, передаем байты данных for(i=0;i

В процедуре чтения изменения будут чуточку сложнее, поскольку между считыванием байтов данных должно быть подтверждение от ведущего, а после считывания последнего байта подтверждения быть не должно, далее идет условие завершения передачи (условие СТОП).

Про прерывание ISR(TWI_INT) говорить ничего не буду, просто приведу пример использования (обычно этого достаточно, сразу становится все понятно):

ISR(TWI) { switch(TWSR & 0xF8) { case TW_START: lcd_puts("TW_START\n"); break; case TW_REP_START: lcd_puts("TW_REP_START\n"); break; case TW_MT_SLA_ACK: lcd_puts("TW_MT_SLA_ACK\n"); break; case TW_MT_DATA_ACK: lcd_puts("TW_MT_DATA_ACK\n"); break; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //и т.д. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } }

Все, остается создать проект в AVR STUDIO:

В настройках проекта укажите какой-нибудь MEGA (atmega16 например), подключите файлы i2c_eeprom.c и i2c_eeprom.h.

#define F_CPU 16000000UL #include #include "util/delay.h" #include "i2c_eeprom.h" int main(void) { _delay_ms(300); /*Здесь приходится подождать, т.к. виртуальный осциллограф в Proteus включается с задержкой*/ uint8_t byte = 10; uint16_t address = 25; //Настраиваем TWI eeInit(); //Записываем байт данных 10 = 0xA0 по адресу 25 = 0x19 //Если успешно записалось, то возвратит true if(eeWriteByte(address, byte)) { //Очищаем переменную byte = 0; //Ждем 15 с, чтобы визуально различить осциллограммы _delay_ms(15); //Считываем байт информации, расположенной по адресу 25 = 0x19 byte = eeReadByte(address); //Для проверки выведем этот байт в порт ввода\вывода //(в Proteus отображается состояние ножек контроллера) PORTA = byte; } }

В программе я явно указал частоту тактирования моего контроллера 16 Mhz. Далее, в Proteus, мы выберем какую-нибудь микросхему внешней памяти I2C EEPROM. Не забудьте после этого сравнить настройки в i2c_eeprom.h с параметрами выбранной микросхемы (slaveF_SCL , slaveAddressConst – достоверную информацию всегда можно узнать из даташитов).

Итак, остается собрать проект и переходим к моделированию..

III. Моделируем работу с I2C EEPROM в Proteus

Добавляем на схему ATmega16, 2 резистора для подтяжки шины I2C (см. схему в начале статьи). Из вкладки виртуальные инструменты возьмите Осциллограф и I2C-Отладчик. Для выбора памяти введите в поиск I2CMEMS в окне выбора устройств:

Из всего списка я выбрал 24LC64 с объемом памяти 64 КБ и частотой работы с шиной I2C 400 КГц.

У микросхемы 8 ножек. Все, кроме питания и земли, отображены на скриншоте слева. С SCK и SDA мы знакомы, WP – Write Protect (защита от записи), A0:A2 используются для выбора переменной части адреса ведомой микросхемы (для чего нужны, уже писал где-то выше). Даташит на эту микросхему можете найти по запросу 24XX64 в гугле. Там можете проверить постоянную часть адреса и частоту, а также посмотреть, как использовать ножки WP, A0:A2.

В настройках контроллера укажите прошивку, частоту тактирования 16 МГц и не забудьте выставить CKSEL-фьюзы для кристалла. В настройках I2C-отладчика укажите, что тактовая частота импульсов шины 400 КГц.

Запускаем моделирование..

Видно, что в PORTA записалось число 0b0000’1010 или 0x0A или 10, т.е. переменная uint8_t byte содержит верное значение после чтения памяти 24LC64 по адресу 0х19. Давайте посмотрим подробнее, что делает наша программа, взглянем на лог I2C-отладчика:

Теперь можно опуститься еще на уровень ниже и посмотреть на осциллограмму:

Логика, думаю, понятна. Так можно просмотреть и всю осциллограмму, проверить работу I2C в реальной ситуации, а не в режиме симуляции.



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows