Сканер i2c шины hal

STM Урок 9. HAL. Шина I2C. Продолжаем работу с DS3231

HAL. Шина I2C. Продолжаем работу с DS3231

На прошлом занятии мы как следует ознакомились с шиной I2C, а также с микросхемой часов реального времени DS3231, создали и настроили проект в Cube MX и Keil.

На данном занятии мы продолжим работать с тем же проектом и уже займёмся непосредственно кодом.

В файл main.h мы также добавим переменную для данных

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

Екстерналим ее в i2c.c, также напишем там переменную для строки и напишем там две функции для работы с шиной.

extern uint8_t aTxBuffer[8];

void I2C_WriteBuffer(I2C_HandleTypeDef hi, uint8_t DEV_ADDR, uint8_t sizebuf)

while(HAL_I2C_Master_Transmit(&hi, (uint16_t)DEV_ADDR,(uint8_t*) &aTxBuffer, (uint16_t)sizebuf, (uint32_t)1000)!= HAL_OK)

if (HAL_I2C_GetError(&hi) != HAL_I2C_ERROR_AF)

void I2C_ReadBuffer(I2C_HandleTypeDef hi, uint8_t DEV_ADDR, uint8_t sizebuf)

while(HAL_I2C_Master_Receive(&hi, (uint16_t)DEV_ADDR, (uint8_t*) &aTxBuffer, (uint16_t)sizebuf, (uint32_t)1000)!= HAL_OK)

if (HAL_I2C_GetError(&hi) != HAL_I2C_ERROR_AF)

Первая функция будет для передачи данных в шину, а вторая – для приема.

Входные параметры в первой функции:

I2C_HandleTypeDef hi – идентификатор шины I2C.

DEV_ADDR – адрес устройства

sizebuf – количество байт, которые мы будем передавать.

Дальше идёт цикл, из которого мы выйдем тогда, когда данные передадутся и мы получим статус HAL_OK.

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

Затем мы проверяем передачу на ошибку, и в случае, если она будет, то выведем текст ошибки на дисплей.

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

Тело функции также аналогично телу предыдущей функции, только библиотечная фунция там уже применена HAL_I2C_Master_Receive.

Создадим для этих двух функций прототипы в файле i2c.h

void I2C_WriteBuffer(I2C_HandleTypeDef hi, uint8_t DEV_ADDR, uint8_t sizebuf);
void I2C_ReadBuffer(I2C_HandleTypeDef hi, uint8_t DEV_ADDR, uint8_t sizebuf);

Убираем весь код по дисплею кроме инициализации из главной функции.

Начинаем работать с шиной в главной функции main()

Сначала удалим код вывода тестовых строк на экран дисплея. Оставим только вот это

/* USER CODE BEGIN 2 */
LCD_ini();
LCD_Clear();

/* USER CODE END 2 */

Дальше уже начнем работать с функциями.

Пишем код в бесконечный цикл

while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)

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

Далее мы передадим адрес устройства и адрес первого регистра микросхемы.

Пишем дальше в бесконечный цикл

Читайте также: Летние шины 195 65 r15 в саратове

while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)

А затем мы уже считаем все 7 регистров в буфер из RTC

Так как данные мы приняли в двоично-десятичном коде, то мы не можем их нормально показать без преобразования. Поэтому напишем две функции преобразования в файле RTC.c

uint8_t RTC_ConvertFromDec(uint8_t c)

uint8_t RTC_ConvertFromBinDec(uint8_t c)

Также пропишем им прототипы в одноименном хедере.

uint8_t RTC_ConvertFromDec(uint8_t c); //перевод двоично-десятичного числа в десятичное

uint8_t RTC_ConvertFromBinDec(uint8_t c); //перевод десятичного числа в двоично-десятичное

date = RTC_ConvertFromDec(date); //Преобразуем в десятичный формат

LCD_SendChar((char) ((date/10)%10) + 0x30);

LCD_SendChar((char) (date%10) + 0x30);

month = RTC_ConvertFromDec(month); //Преобразуем в десятичный формат

LCD_SendChar((char) ((month/10)%10) + 0x30);

LCD_SendChar((char) (month%10) + 0x30);

year = RTC_ConvertFromDec(year); //Преобразуем в десятичный формат

LCD_SendChar((char) ((year/10)%10) + 0x30);

LCD_SendChar((char) (year%10) + 0x30);

day = RTC_ConvertFromDec(day); //Преобразуем в десятичный формат

LCD_SendChar((char) (day%10) + 0x30);

hour = RTC_ConvertFromDec(hour); //Преобразуем в десятичный формат

LCD_SendChar((char) ((hour/10)%10) + 0x30);

LCD_SendChar((char) (hour%10) + 0x30);

min = RTC_ConvertFromDec(min); //Преобразуем в десятичный формат

LCD_SendChar((char) ((min/10)%10) + 0x30);

LCD_SendChar((char) (min%10) + 0x30);

sec = RTC_ConvertFromDec(sec); //Преобразуем в десятичный формат

LCD_SendChar((char) ((sec/10)%10) + 0x30);

LCD_SendChar((char) (sec%10) + 0x30);

LCD_SendChar((char) ((i/100)%10) + 0x30);

LCD_SendChar((char) ((i/10)%10) + 0x30);

LCD_SendChar((char) (i%10) + 0x30);

LCD_SendChar((char) (((i+500)/100)%10) + 0x30);

LCD_SendChar((char) (((i+500)/10)%10) + 0x30);

LCD_SendChar((char) ((i+500)%10) + 0x30);

LCD_SendChar((char) (((i+750)/100)%10) + 0x30);

LCD_SendChar((char) (((i+750)/10)%10) + 0x30);

LCD_SendChar((char) ((i+750)%10) + 0x30);

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

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

Соберём код и прошьём контроллер. Посмотрим, как у нас всё это работает.

Сканер i2c шины hal

Отладочную плату, дисплей LCD 20×4 и модуль RTC DS3231 с микросхемой памяти можно приобрести здесь:

Дружим STM32 с LCD дисплеем 1604 по I2C шине (библиотека HAL)

В этой статье я хотел бы рассказать о своем опыте подключения LCD дисплеев к микроконтроллеру STM32 с использованием библиотеки HAL по I2C шине.

Сканер i2c шины hal

Подключать буду дисплей 1602 и 2004. Они оба имеют припаянный I2C адаптер на основе чипа PCF8574T. Отладочной платой выступит Nucleo767ZI, а средой разработки – STM32CubeIDE 1.3.0.

Про принцип работы I2C шины подробно рассказывать не буду, советую заглянуть сюда и сюда.

Создаем проект, выбираем отладочную плату:

Сканер i2c шины hal

Указываем, что будем использовать I2C1. Также я подключу UART5 для общения с платой, это нужно для получения информации от платы об адресе дисплея.

Сканер i2c шины hal

Сканер i2c шины hal

В этом же окне можно посмотреть номера ножек, к которым подключается дисплей, в моем случае получилось так:

Сканер i2c шины hal

Для начала подключим всего один дисплей, я начну с 1602. Также я подключу известный бывалым ардуинщикам адаптер USB-UART CH340 для получения данных с платы.

Сканер i2c шины hal

Обратите внимание, адаптер подключается RX к TX и TX к RX, перемычка на адаптере стоит на 3.3В

Сканер i2c шины hal

Рассмотрим подробнее работу с микросхемой PCF8574T и дисплеем. Ниже приведена принципиальная схема модуля с дисплеем:

Читайте также: Зимние шины в барабинске

Сканер i2c шины hal

Микросхема PCF8574T по функционалу схожа с регистром сдвига 74hc595 – она получает по I2C интерфейсу байт и присваивает своим выводам (P0-P7) значения соответствующего бита.

Рассмотрим какие выводы микросхемы соединены с дисплеем и за что отвечают:

  • Вывод Р0 микросхемы соединен с выводом RS дисплея, отвечающего за то, принимает дисплей данные (1) или инструкции по работе дисплея (0);
  • Вывод Р1 соединен с R\W, если 1 – запись данных в дисплей, 0 – считывание;
  • Вывод Р2 соединен с CS – вывод, по изменению состояния которого идет считывание;
  • Вывод Р3 – управление подсветкой;
  • Выводы Р4 — Р7 служат для передачи данных дисплею.

К одной I2C шине может быть подключено несколько устройств одновременно. Для того, чтобы можно было обращаться к конкретному устройству, каждое из них имеет свой адрес, для начала выясним его. Если контакты А1, А2 и А3 на плате адаптера не запаяны, то адрес будет скорее всего 0х27, но лучше проверить. Для этого напишем небольшую функцию, которая покажет адреса всех устройств, которые подключены к I2C шине:

Данная функция опрашивает все адреса от 0 до 127 и если с этого адреса поступил ответ, она отправляет номер этого адреса в 16-тиричной форме в UART.

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

Сканер i2c шины hal

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

Сканер i2c шины hal

Имеем два адреса: 0х26 (дисплей 1602) и 0х27 (дисплей 2004). Теперь о том, как работать с дисплеем. Микроконтроллер посылает байт адреса, а все устройства, подключенные к шине, сверяют его со своим. Если он совпадает, то модуль начинает общение с микроконтроллером. В первую очередь нужно настроить дисплей: откуда будет идти отсчет символов и в какую сторону, как будет вести себя курсор и т.п. После этого уже можно будет передавать дисплею информацию для вывода. Особенность в том, что мы можем использовать только 4 бита для передачи информации, т.е. данные необходимо разбивать на две части. Данные хранятся в старших битах (4-7), а младшие биты используются для указания того, будет ли включена подсветка (3 бит), приходят ли данные для вывода или же настройки работы дисплея (вывод RS, 0 бит), и 2 бит, по изменению которого происходит считывание, т.е чтобы отправить 1 байт данных необходимо отправить 4 байта – 1й байт будет содержать 4 бита информации, 2й бит в состояние 1, 2й байт это повторение 1-го, только уже 2й бит в состояние 0. 3й и 4й байт аналогично, только там содержится вторая половина данных. Звучит немного непонятно, покажу на примере:

Разберем все по порядку. В начале идут переменные, хранящие в себе адрес дисплея, и биты настроек, которые необходимо отправлять каждый раз вместе с данными. В функции отправки мы в первую очередь проверяем, есть ли по записанному адресу модуль. В случае получения сообщения HAL_OK начинаем формировать байты для отправки. В начале байт, который мы будем отправлять, необходимо разделить на две части, оба из них записать в старшие биты. Допустим, мы хотим, чтобы дисплей отобразил символ ‘s’, в двоичной системе это 1110011 (калькулятор). С помощью логической операции & мы записываем в переменную up = 01110000, т.е. записываем только старшие биты. Младшие биты в начале сдвигаются влево на 4 символа, а потом записываются в переменную lo = 00110000. Дальше мы формируем массив из 4 байт, которые содержат информацию о символе, который необходимо вывести. Теперь к существующим байтам приписываем биты конфигурации (0-3 биты). После этого отправляем байт адреса и 4 байта информации на дисплей с помощью функции HAL_I2C_Master_Transmit();

Читайте также: Шины для мазда сх 5 зимние размер

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

Эти команды поместим перед началом бесконечного цикла, чтобы настройки отправлялись единожды перед началом работы (как void setup у ардуинки). Функция I2C_send помимо байта требует указать, будут отправляться настройки дисплея или же данные. Если второй аргумент функции 0, то настройки, а если 1, то данные.

И последний штрих – нужна функция, которая будет отправлять сроку посимвольно. Тут все довольно просто:

Собрав все эти функции воедино можно написать:

Сканер i2c шины hal

Отлично, с дисплеем 1602 разобрались, теперь 2004. Разница между ними минимальная, даже этот код будет отлично работать. Все отличие сводится к организации адресов ячеек на дисплее. В обоих дисплеях память содержит 80 ячеек, в дисплее 1602 первые 16 ячеек отвечают за первую строчку, а за вторую строчку отвечают ячейки с 40 по 56. Остальные ячейки памяти на дисплей не выводятся, поэтому, если отправить на дисплей 17 символов, последний не перенесется на вторую строчку, а будет записан в ячейку памяти, не имеющую выхода на дисплей. Чуть более наглядно, память устроена так:

Сканер i2c шины hal

Для перевода строки я пользовался командой I2C_send(0b11000000,0);, она просто переходит к 40 ячейке. В дисплее 2004 все поинтереснее.

Первая строка — ячейки с 1 по 20
Вторая строка — ячейки с 40 по 60
Третья строка — ячейки с 21 по 40
Четвертая строка — ячейки с 60 по 80,
т.е. если отправить команду

Сканер i2c шины hal

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

Сканер i2c шины hal

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

  1. Код во многом посмотрел вот тут
  2. Таблицы для конфигурации дисплея смотрел тут
  3. Порядок действий смотрел тут
Поделиться или сохранить к себе:
Технарь знаток