STM Урок 150. LL. I2C. Подключаем внешний EEPROM. Часть 2



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

 

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

Основная схема была взята из урока 148 по динамической индикации. Только ещё мы к данной схеме подключим модуль на часовой микросхеме DS1307 с необходимой нам микросхемой AT24C32, подведя от нашей макетной платы к нему общий провод, провод питания и провода контактов SDA и SCL, которые, забегая немного вперёд, будут соответствовать ножкам портов PB7 и PB6

 

Для лучшего мониторинга ситуации подключим также логический анализатор к ножкам шины

 

 

Проект мы создадим из проекта урока 148 с именем LL_LED_DYN и назовём его LL_I2C_EEPROM.

Откроем наш проект в Cube MX и включим модуль I2C1

 

 

Настройки оставляем все по умолчанию (режим Standard и частота 100000).

Ножки также оставляем по умолчанию

 

 

И, самое главное, не забываем нашу шину настроить к работе именно с библиотекой LL

 

 

Сгенерируем проект, откроем его в Keil, настроим программатор на autoreset, отключим оптимизацию, подключим к проекту файл библиотеки led.c.

Попробуем собрать проект, подключить схему и прошить контроллер. Если индикатор нормально отсчитывает наши циклы, то продолжим дальше.

Займёмся изучением того, как проходит инициализация шины I2C. Для этого перейдём в тело функции и увидим, что там сначала объявляется переменная типа структуры LL_I2C_InitTypeDef. Также создаётся и переменная типа структуры LL_GPIO_InitTypeDef для настройки ножек портов, задействованных в шине.

Далее ножки портов настраиваются для работы в альтернативном режиме и включается их тактирование.

Затем с помощью функции LL_I2C_DisableOwnAddress2 отключается режим двойной адресации путём сброса соответствующего бита в регистре OAR2

 

 

Потом с помощью функции LL_I2C_DisableGeneralCall отключается работа с широковещательными запросами

 

 

Далее при помощи функции LL_I2C_EnableClockStretching отключается увеличение времени в режиме SLAVE

 

 

Затем заполняются поля структуры I2C_InitStruct и вызывается функция LL_I2C_Init, в тело которой мы теперь и перейдём.

Там сначала проверяется заполненность структуры, а потом с помощью функции LL_I2C_Disable отключается модуль I2C

 

 

 

Далее с помощью функции LL_RCC_GetSystemClocksFreq заполняется структура rcc_clocks значениями различных частот тактирования

 

 

Потом при помощи функции LL_I2C_ConfigSpeed происходит настройка скорости работы шины. Во втором входном параметре данной функции используется поле вышезаполненной структуры, несущее в себе значение частоты PCLK1, которое является частотой тактирования шины APB1, на которой и находится наш модуль I2C1.

Перейдём в данную функцию и увидим, что здесь сначала при помощи функции __LL_I2C_FREQ_HZ_TO_MHZ частота PCLK1 переводится из герц в мегагерцы

 

 

Затем в битовое поле I2C_CR2_FREQ регистра CR2 заносится значение данной частоты

 

 

Далее в битовое поле TRISE одноименного регистра TRISE заносится значение времени нарастания фронта, которое сначала вычисляется при помощи макроса __LL_I2C_RISE_TIME, в параметрах которого передаются наша частота тактирования в мегагерцах, а также частота работы шины

 

 

Затем, если скорость работы шины установлена в значение больше 100 килогерц, то переменная clockconfig, впоследствии используемая для занесения значений в определенные биты регистра CCR, инициализируется следующим образом

 

 

А если будет использована стандартная скорость работы шины или менее, то данная переменная инициализируется уже вот так

 

 

И далее значениями данной переменной заполняются соответствующие биты и поля регистра CCR

 

 

 

Возвращаемся в функцию LL_I2C_Init, где затем у нас происходит заполнение адреса при помощи функции LL_I2C_SetOwnAddress1

 

 

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

Затем с помощью функции LL_I2C_SetMode устанавливается режим работы шины (I2C или SMBus) посредством занесения значений в соответствующие биты регистра CR1 (в нашем случае они все обнуляются)

 

 

Далее при помощи функции LL_I2C_Enable наш модуль включается посредством установки бита PE в регистре CR1

 

 

Затем с помощью функции LL_I2C_AcknowledgeNextData устанавливается бит ACK в регистре CR1, который разрешает модулю генерировать условие ACK после приёма байтов

 

 

Возвращаемся в функцию MX_I2C1_Init, где затем мы при помощи LL_I2C_SetOwnAddress2 устанавливаем значение второго адреса в 0 путём заполнения в регистре OAR2 соответствующего поля нулями

 

 

Вот и вся инициализация.

Теперь наша дальнейшая задача — написать код, который сможет общаться с микросхемой внешней памяти EEPROM по шине I2C, а именно записывать и читать данные по определённым адресам памяти.

Для этого мы объявим в файле main.c два глобальных массива. Один из них будет для хранения считанных из внешнего EEPROM значений, а другой будет состоять из заранее заданных 8-битных чисел для записи в микросхему

 

 

Добавим также некоторые макросы для работы с шиной, а именно адрес SLAVE микросхемы, и переменные для инициализации бита записи/чтения в адресе

 

 

Почему именно A0, а не AE, как мы задавали в случае использования модуля DS3231? А потому что адресные ножки A0:A2 у нас в модуле, который мы используем сейчас, притянуты к земле, а следовательно биты в адресе будут равны 0

 

 

Поэтому смотрите внимательно, какие у вас уровни на ножках 1-3 микросхемы. Лучше всего померить вольтметром.

Далее добавим функцию, которая будет записывать определённое количество байтов в память микросхемы из массива, начиная с определённого адреса памяти

 

 

Объявим в нашей функции локальную переменную

 

 

Сбросим бит POS в регистре CR1 с помощью специально-обученной этому функции

 

 

Включим генерирование условия ACK

 

 

Сгенерируем условие START

 

 

Дождёмся установки бита SB в регистре SR1 также при помощью специальной функции. Данный бит устанавливается, как мы уже знаем, в случае наличие на шине условия START

 

 

Считаем регистр SR1

 

 

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

 

 

Вызовем нашу функцию в функции main()

 

 

Писать и читать мы будем 20 байтов с адреса 4A.

Правда, пока мы ничего ещё не пишем и не читаем.

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

 

 

Мы видим, что у нас прекрасно сформировались на шине наши условия START и STOP.

 

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

 

 

Предыдущая часть Программирование МК STM32 Следующая часть

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

Модуль RTC DS3231 с микросхемой памяти (3 шт)

Модуль RTC DS3231 с микросхемой памяти (1 шт) — так дороже

Семисегментный чертырехразрядный индикатор красный с общим анодом 10 шт

Логический анализатор 16 каналов можно приобрести здесь

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM I2C. Подключаем внешний EEPROM

3 комментария на “STM Урок 150. LL. I2C. Подключаем внешний EEPROM. Часть 2
  1. Gorybych:

    Привет!
    Отличный разбор библиотек! Спасибо Вам!
    Добавьте способ обхода ошибки аналоговых фильтров из Errata на сотую серию п.2.13.7

  2. Приветствую! Огромное спасибо за ваши уроки! Очень помогают.
    Касательно ошибок аналогового фильтра есть решение. Собрал несколько проектов для тестирования ноутбучных батарей. Сперва на ардуино, потом на STM32 + HAL, сейчас заканчиваю на STM32 + LL.
    STM32 + HAL тоже глючит i2c. Там я этот вопрос решил просто. Если HAL_ERROR, то MX_I2C2_Init() и помогало.
    Решил следующий проект сделать на LL и те же грабли. Но!
    Во-первых, нужно было избавится от блокирующего while, чтобы отловить проблему. По быстрому сделал таймер на HAL_GetTick (), задавал лимит времени от 100мс до 500мс и отловил где именно происходит глюк. Шина в какой то момент не может отправить старт. Нога SCL все время прижата к нулю. Глубококопание привело к Errata. Сделал все по рекомендации, но шина отвисала не сразу, а через минуту-две. Уже хотел сделать принудительно в цикле повторять процедуру перезапуска, и проверять не отвисла ли.
    Потом на каком то форуме прочитал, что якобы регистры не любят когда бесконечно проверяют их состояние, не знаю факт или нет, но режил вместо таймера сделать тупо счетчик. И получилось! Шина больше не виснет. Двое суток непрерывного чтения ноутбучной батареи и не одного зависания!
    Таперь решение:
    Вместо например while(!LL_I2C_IsActiveFlag_SB(I2C2)){};
    пишем
    number_of_read_attempts = 1000;
    do {
    status = LL_I2C_IsActiveFlag_SB(I2C2);
    if( ! number_of_read_attempts )
    {
    return I2C_START_ERROR;
    }
    number_of_read_attempts —;
    } while (!status);

    и так в каждой проверке статуса какого либо флага. Из экспериментов выяснил, что некоторым флагам достаточно number_of_read_attempts = 100, а некоторым number_of_read_attempts = 300. Так что number_of_read_attempts=1000 это чтоб уж наверняка.
    Возможно, когда процессор занят другим делом, например перебирает счетчик, флаг спокойно устанавливается.
    Вобщем как то так.

  3. При глубококопании нарыл вот еще что.
    При использовании библиотеки HAL, в процедуре HAL_I2C_Init происходит вот что:
    /*Reset I2C*/
    hi2c->Instance->CR1 |= I2C_CR1_SWRST;
    hi2c->Instance->CR1 &= ~I2C_CR1_SWRST;
    А LL так не делает. Поэтому я добавил в MX_I2C2_Init следующий код:

    LL_I2C_EnableReset(I2C2);
    LL_I2C_DisableReset(I2C2);

    или можно так:

    LL_I2C_EnableReset(I2C2);
    while(!LL_I2C_IsResetEnabled(I2C2)){};
    LL_I2C_DisableReset(I2C2);
    while(LL_I2C_IsResetEnabled(I2C2)){};

    Или можно завернуть это в функцию, скажем LL_I2C2_Prepare().
    Вобщем после этого у меня начали читаться батареи Lenovo, которые иногда не читались

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*