Видео:STM32. CMSIS. Урок#06: I2C. Теория. Сканер I2C адресов. Отправка и прием данных. MemWrite, MemRead.Скачать
STM Урок 9. HAL. Шина I2C. Продолжаем работу с DS3231
Видео:STM32 Сканер I2C шины, ищем брак от АлиСкачать
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 милисекунд, затем весь процесс повторяется сначала.
Остальной код из бесконечного цикла, оставшийся из проекта по тестированию и подключения дисплея, мы пока не убираем.
Соберём код и прошьём контроллер. Посмотрим, как у нас всё это работает.
Отладочную плату, дисплей LCD 20×4 и модуль RTC DS3231 с микросхемой памяти можно приобрести здесь:
Видео:Установщик адресов Flash-i2cСкачать
Дружим STM32 с LCD дисплеем 1604 по I2C шине (библиотека HAL)
В этой статье я хотел бы рассказать о своем опыте подключения LCD дисплеев к микроконтроллеру STM32 с использованием библиотеки HAL по I2C шине.
Подключать буду дисплей 1602 и 2004. Они оба имеют припаянный I2C адаптер на основе чипа PCF8574T. Отладочной платой выступит Nucleo767ZI, а средой разработки – STM32CubeIDE 1.3.0.
Про принцип работы I2C шины подробно рассказывать не буду, советую заглянуть сюда и сюда.
Создаем проект, выбираем отладочную плату:
Указываем, что будем использовать I2C1. Также я подключу UART5 для общения с платой, это нужно для получения информации от платы об адресе дисплея.
В этом же окне можно посмотреть номера ножек, к которым подключается дисплей, в моем случае получилось так:
Для начала подключим всего один дисплей, я начну с 1602. Также я подключу известный бывалым ардуинщикам адаптер USB-UART CH340 для получения данных с платы.
Обратите внимание, адаптер подключается RX к TX и TX к RX, перемычка на адаптере стоит на 3.3В
Рассмотрим подробнее работу с микросхемой PCF8574T и дисплеем. Ниже приведена принципиальная схема модуля с дисплеем:
Читайте также: Зимние шины в барабинске
Микросхема 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, необходимо установить такую же в термите. Вызываем функцию в основном теле программы, прошиваем плату и коннектимся в термите к нашему микроконтроллеру:
Точками отображаются все адреса, с которых ответ не был получен. Адрес у моего дисплея 0х26, так как я запаял перемычку А0. Теперь подключим второй дисплей параллельно первому, и посмотрим, что выдаст программа:
Имеем два адреса: 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, то данные.
И последний штрих – нужна функция, которая будет отправлять сроку посимвольно. Тут все довольно просто:
Собрав все эти функции воедино можно написать:
Отлично, с дисплеем 1602 разобрались, теперь 2004. Разница между ними минимальная, даже этот код будет отлично работать. Все отличие сводится к организации адресов ячеек на дисплее. В обоих дисплеях память содержит 80 ячеек, в дисплее 1602 первые 16 ячеек отвечают за первую строчку, а за вторую строчку отвечают ячейки с 40 по 56. Остальные ячейки памяти на дисплей не выводятся, поэтому, если отправить на дисплей 17 символов, последний не перенесется на вторую строчку, а будет записан в ячейку памяти, не имеющую выхода на дисплей. Чуть более наглядно, память устроена так:
Для перевода строки я пользовался командой I2C_send(0b11000000,0);, она просто переходит к 40 ячейке. В дисплее 2004 все поинтереснее.
Первая строка — ячейки с 1 по 20
Вторая строка — ячейки с 40 по 60
Третья строка — ячейки с 21 по 40
Четвертая строка — ячейки с 60 по 80,
т.е. если отправить команду
Для организации переходов между строками необходимо переводить на нужную ячейку памяти курсор вручную, либо можно программно дополнить функцию. Я пока остановился на ручном варианте:
На этом пожалуй все с этими дисплеями, полезные ссылки, благодаря которым я смог во всем этом разобраться:
- Код во многом посмотрел вот тут
- Таблицы для конфигурации дисплея смотрел тут
- Порядок действий смотрел тут
🎬 Видео
Программирование МК STM32. УРОК 8. HAL. Шина I2C. Подключаем микросхему RTC DS3231Скачать
Урок 24. Узнаём адреса устройств на шине I2CСкачать
Что такое I2C ??? Подключаем GY-521 и Oled 96*16 к STM 32Скачать
Логический анализатор шины i2cСкачать
Введение в шину I2CСкачать
I2C. Краткая теория с примером. STM32 CubeIDE.Скачать
Подключение нескольких устройств, датчиков по I2C (АйТуСи) шинеСкачать
STM32: Интерфейсная шина I2C. Подключаем HDC1080.Скачать
Сканер I2C eeprom AT24C04Скачать
Проверка работоспособности шины I2CСкачать
Шина данных i2c - декодируем/синхронизируем с помощью осциллографа Lecroy!Скачать
MCP2515, контроллер CAN шины с интерфейсом SPIСкачать
Урок 9. Адреса модулей на шине I2C. Arduino (что такое I2C, адресация, как изменить адрес модуля)Скачать
Лекция 308. Шина I2CСкачать
STM32: Сканер I2C шини на stm32f103c8Скачать
Шина I2C.Скачать
Программирование МК STM32. Урок 172. CMSIS. STM32F1. I2C. Подключаем внешний EEPROMСкачать