Урок 9
HAL. Шина I2C. Продолжаем работу с DS3231
На прошлом занятии мы как следует ознакомились с шиной I2C, а также с микросхемой часов реального времени DS3231, создали и настроили проект в Cube MX и Keil.
На данном занятии мы продолжим работать с тем же проектом и уже займёмся непосредственно кодом.
В файл main.h мы также добавим переменную для данных
#include «RTC.h»
//——————————————
uint8_t aTxBuffer[8];
В неё мы будем принимать значения сразу всех 7 регистров микросхемы и, предварительно заполнив в ней все ячейки, туда же и отправлять, когда будем устанавливать время в часах.
Екстерналим ее в i2c.c, также напишем там переменную для строки и напишем там две функции для работы с шиной.
#include «i2c.h»
//——————————————
extern uint8_t aTxBuffer[8];
//——————————————
char str[100];
//——————————————
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)
{
sprintf(str, «Buffer error»);
LCD_SetPos(8, 0);
LCD_String(str);
}
}
}
//——————————————
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)
{
sprintf(str, «Buffer error»);
LCD_SetPos(8, 0);
LCD_String(str);
}
}
}
Первая функция будет для передачи данных в шину, а вторая — для приема.
Входные параметры в первой функции:
I2C_HandleTypeDef hi — идентификатор шины I2C.
DEV_ADDR — адрес устройства
sizebuf — количество байт, которые мы будем передавать.
Дальше идёт цикл, из которого мы выйдем тогда, когда данные передадутся и мы получим статус HAL_OK.
Статус нам уже будет возвращать стандартная функция передачи данных в I2C из библиотеки HAL HAL_I2C_Master_Transmit. В данную функцию мы передаём практически те же параметры, некоторые из них только явно преобразованы несколько в другой тип, а также передаём таймаут в милисекундах. Это не значит, что функция будет именно столько времени выполняться. При успешном выполнении мы будем из функции возвращаться мгновенно. А вот если что-то пойдёт не так, то будем ждать именно столько времени, а затем всё равно выйдем, правда с ошибкой.
Затем мы проверяем передачу на ошибку, и в случае, если она будет, то выведем текст ошибки на дисплей.
Во второй функции абсолютно такие же входные параметры, только там мы уже будем читать байты и отправлять их в тот же буфер.
Тело функции также аналогично телу предыдущей функции, только библиотечная фунция там уже применена HAL_I2C_Master_Receive.
Создадим для этих двух функций прототипы в файле i2c.h
#include «lcd.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 (1)
{
LCD_SetPos(0,1);
aTxBuffer[0]=0;
I2C_WriteBuffer(hi2c1,(uint16_t)0xD0,1);
while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
{
}
Здесь мы сначала установим позицию строки в дисплее, инициализируем буфер, вернее не весь, а только его самую первую ячейку.
Далее мы передадим адрес устройства и адрес первого регистра микросхемы.
Пишем дальше в бесконечный цикл
while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
{
}
I2C_ReadBuffer(hi2c1,(uint16_t)0xD0,7);
А затем мы уже считаем все 7 регистров в буфер из RTC
Так как данные мы приняли в двоично-десятичном коде, то мы не можем их нормально показать без преобразования. Поэтому напишем две функции преобразования в файле RTC.c
#include «RTC.h»
uint8_t RTC_ConvertFromDec(uint8_t c)
{
uint8_t ch = ((c>>4)*10+(0x0F&c));
return ch;
}
uint8_t RTC_ConvertFromBinDec(uint8_t c)
{
uint8_t ch = ((c/10)<<4)|(c%10);
return ch;
}
Также пропишем им прототипы в одноименном хедере.
#ifndef RTC_H_
#define RTC_H_
#include «stm32f4xx_hal.h»
uint8_t RTC_ConvertFromDec(uint8_t c); //перевод двоично-десятичного числа в десятичное
uint8_t RTC_ConvertFromBinDec(uint8_t c); //перевод десятичного числа в двоично-десятичное
#endif /* RTC_H_ */
Добавим переменные
/* USER CODE BEGIN 1 */
uint32_t i=0;
uint8_t sec=0,min=0,hour=0,day=0,date=0,month=0,year=0;
/* USER CODE END 1 */
Дальше рутина.
I2C_ReadBuffer(hi2c1,(uint16_t)0xD0,7);
date=aTxBuffer[4];
date = RTC_ConvertFromDec(date); //Преобразуем в десятичный формат
LCD_SendChar((char) ((date/10)%10) + 0x30);
LCD_SendChar((char) (date%10) + 0x30);
LCD_SendChar(':');
month=aTxBuffer[5];
month = RTC_ConvertFromDec(month); //Преобразуем в десятичный формат
LCD_SendChar((char) ((month/10)%10) + 0x30);
LCD_SendChar((char) (month%10) + 0x30);
LCD_SendChar(':');
year=aTxBuffer[6];
year = RTC_ConvertFromDec(year); //Преобразуем в десятичный формат
LCD_SendChar((char) ((year/10)%10) + 0x30);
LCD_SendChar((char) (year%10) + 0x30);
LCD_SendChar(':');
day=aTxBuffer[3];
day = RTC_ConvertFromDec(day); //Преобразуем в десятичный формат
LCD_SendChar((char) (day%10) + 0x30);
LCD_SendChar(':');
hour=aTxBuffer[2];
hour = RTC_ConvertFromDec(hour); //Преобразуем в десятичный формат
LCD_SendChar((char) ((hour/10)%10) + 0x30);
LCD_SendChar((char) (hour%10) + 0x30);
LCD_SendChar(':');
min=aTxBuffer[1];
min = RTC_ConvertFromDec(min); //Преобразуем в десятичный формат
LCD_SendChar((char) ((min/10)%10) + 0x30);
LCD_SendChar((char) (min%10) + 0x30);
LCD_SendChar(':');
sec=aTxBuffer[0];
sec = RTC_ConvertFromDec(sec); //Преобразуем в десятичный формат
LCD_SendChar((char) ((sec/10)%10) + 0x30);
LCD_SendChar((char) (sec%10) + 0x30);
LCD_SetPos(0,0);
LCD_SendChar((char) ((i/100)%10) + 0x30);
LCD_SendChar((char) ((i/10)%10) + 0x30);
LCD_SendChar((char) (i%10) + 0x30);
LCD_SetPos(6,2);
LCD_SendChar((char) (((i+500)/100)%10) + 0x30);
LCD_SendChar((char) (((i+500)/10)%10) + 0x30);
LCD_SendChar((char) ((i+500)%10) + 0x30);
LCD_SetPos(9,3);
LCD_SendChar((char) (((i+750)/100)%10) + 0x30);
LCD_SendChar((char) (((i+750)/10)%10) + 0x30);
LCD_SendChar((char) ((i+750)%10) + 0x30);
HAL_Delay(100);
i++;
/* USER CODE END WHILE */
В этом длинном коде мы для всех регистров микросхемы по очереди сначала из соответствующей ячейки буфера, в который мы данные регистры считали, сначала берём показание в переменную, затем преобразовываем в обычный десятичный вид, Затем десятки и единицы превращаем в символы в соответствии с таблицей ascii, и затем выводим на дисплей. Потом в конце применяем задержку 100 милисекунд, затем весь процесс повторяется сначала.
Остальной код из бесконечного цикла, оставшийся из проекта по тестированию и подключения дисплея, мы пока не убираем.
Соберём код и прошьём контроллер. Посмотрим, как у нас всё это работает.
Предыдущий урок Программирование МК STM32 Следующий урок
Техническая документация:
Техническая документация на микросхему RTC DS3231
Отладочную плату, дисплей LCD 20×4 и модуль RTC DS3231 с микросхемой памяти можно приобрести здесь:
Модуль RTC DS3231 с микросхемой памяти (3 шт)
Модуль RTC DS3231 с микросхемой памяти (1 шт) — так дороже
Смотреть ВИДЕОУРОК
Здравствуйте. Очень помогают ваши уроки. Подскажите пожалуйста вот что, в своём проекте использую eeprom в этом модуле часов, так как stm32f103c8 её не имеет.
Зогвоздка в том что при записи или чтении почти всё время за редким исключением контролер виснет. Но это происходит только при записи или чтении при срабатывании таймера например, но всё прекрасно работает если запись или чтение происходит при нажатии кнопки.
Вот модуль записи ичтения
void eeprom_write_menu(uint8_t memory_adres,uint8_t znach_mem ){
zapis_eep=1;
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
HAL_I2C_Mem_Write(&hi2c1, (uint16_t) 0xAE, memory_adres,I2C_MEMADD_SIZE_16BIT,&znach_mem, 1, 100);
HAL_Delay(10);
HAL_I2C_Mem_Read(&hi2c1,(uint16_t) 0xAE, 0x31 ,I2C_MEMADD_SIZE_16BIT,(uint8_t *)&mywrite, 28, 100);
HAL_Delay(10);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
zapis_eep=0;
}
На могли бы Вы дополнить урок примером для записи и чтения встроенной в модуль DS3231 епром.
Вот моя программа может подскажите где я ошибся?
https://cloud.mail.ru/public/9NV9/NVj61HFA3
Здравствуйте! Могли бы вы помочь советом?
В подключаемом файле i2c.c вылетает ошибка по UARTу
#include «i2c.h»
//——————————————
extern uint8_t aTxBuffer[8];
//——————————————
char str[100];
//——————————————
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)
{
sprintf(str, «Buffer error»); ошибка HAL_UART_Transmit(&huart1, str, 8, 1000);
}
}
}
Вот весь проект целиком. Заранее спасибо!
https://yadi.sk/d/Fyga3hbo3R85wY
так и будет, перепишите это условие:
if (HAL_I2C_GetError(&hi) != HAL_I2C_ERROR_AF) на это
if (HAL_I2C_GetError(&hi) == HAL_I2C_ERROR_AF)
Добрый день! Прекрасные уроки и практически всё понятно, но у меня вопрос по поводу ошибки при написании программного кода. Опыта в написании программы на СИ у меня мало, так как работал раньше на Бейсике. В хедере файла 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);
пишет вот такую ошибку ../Inc/i2c.h(8): error: #20: identifier «I2C_HandleTypeDef» is undefined.
Пробовал подключать stm32f4xx_hal_i2c.h, предполагая, что программа не видит эту библиотеку, но это всё бесполезно. Короче Кейл не видит функции I2C_HandleTypeDef, хоть убейся. Как можно исправить это.
Как правило помогает удаление куба с вычисткой всех библиотек. Скорей всего что-то не догрузилось при обновах.
Я сейчас пробую на другом копме проверить проблему с этим кодом. У меня Кейл версии 4, а на другом я поставил версию 5. Может в нём дело, да и Куб там по новой установлен. Проверю отпишу.
Добрый вечер! Хотел сразу, после проверки программного кода на Кейл 5, отписать результат работы. Результат оказался точно таким же как в Кейл 4. Куб удалил полностью и установил заново, помимо ещё и обновил его новым патчом за 2018 г. Прежде чем отписать результат, я ещё раз внимательно проделал последовательно все действия при написании кода. Я всё надеялся, что где то допустил ошибку при написании кода или указал не все пути рабочих библиотек HAL. При компиляции по прежнему всё та же ошибка error: #20: identifier «I2C_HandleTypeDef» is undefined в хедере i2c.h, к тому же до компиляции в этом же хедере да и в других местах где есть включение #include «stm32f4xx_hal.h», подчёркивает и пишет на него fatal error: «stm32f4xx_hal.h» file not found. Пробовал я даже все библиотеки из Drive, куда устанавливает их Куб при инициализации программы, прописывать напрямую в папку inc, а в Src все си, но толку никакого. Если навести наведении на HandleTypeDef и кликнуть по всплывающему меню то перекидывает в файл stm32f4xx_hal_i2c.h, где находятся типы структур. Получается что всё необходимое подключено, но как будто ничего чего не видит в этих файлах. В общем надо продвигаться дальше в учёбе, а я застрял на этом уроке из за этой ошибки.
Здравствуйте!
Оно и не удивительно. Столько времени уже прошло с момента урока. И кейл и куб стали другими и многие проекты приходится перерабатывать. Спасибо за интерес!
у меня такаяже беда, почему при сборке пишет C:/Project/STM32F103RBT6-XT33-BMP180/Inc/i2c.h:14:21: error: unknown type name 'I2C_HandleTypeDef'
пишу в System Workbench for stm32
если вы решили эту проблему поделитесь пожалуйста решением
Может помочь переустановка Cube MX и библиотек, а также не помешает обновление System Workbench. Затем удалить из проекта все папки кроме Inc и Src и заново пересобрать проект в Cube MX. А в SWB также проект удалить и добавить заново.
привет народный поток.
почему бы вам не использовать внутренний rtc?
Каковы ваши взгляды на внутренний rtc?
Является ли внутренний rtc хорошим (безопасным) или ds3231?
Iam sory.I use google translate.
Я уже раз 100 отвечал на данный вопрос.
Это плохой модуль вследствие отсутствия в нём энергонезависимости от основного контроллера. К нему не подключается отдельная питающая батарейка.
thank you narodstream
Здравствуйте. И у меня аналогичная проблема с I2C_InitTypeDef. Что же делать, narodstream? Просим помочь разобраться. Заранее спасибо.
Проблему с I2C_InitTypeDef можно решить собрав, функции работы с i2c в main.c
Проблему с I2C_InitTypeDef, по совету Вячеслава, я решил переносом функций работы с i2c из i2c.с в main.c.(проверено на xNucleo F030R8).
а как сами часы установить?
uint8_t sec=10, min=20, hour=15, day=7, date=23, month=9, year=18;
Не срабатывает.
А вот начальная установка часов:
/* USER CODE BEGIN WHILE */
//Начальная установка часов*****************************************************
// инициализируем буфер
aTxBuffer[0]=0x00;
aTxBuffer[1]=0x00; //секунды(0-59)
aTxBuffer[2]=0x20;//минуты(0-59)
aTxBuffer[3]=0x15;//часы(1-12+AM/PM или 00-23)
aTxBuffer[4]=0x03; //день недели(1-7)
aTxBuffer[5]=0x17; //дата(01-31)
aTxBuffer[6]=0x10;//месяц(01-12)
aTxBuffer[7]=0x18; //год(00-99)
//передадим в функцию I2C-адрес DS3231(0xD0) и адрес первого регистра DS3231(0x00)
I2C_WriteBuffer(hi2c1,(uint16_t)0xD0,1);
//подождем готовность микросхемы
while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
{
}
//передадим в DS3231 подряд 7 байт из буфера
I2C_WriteBuffer(hi2c1,(uint16_t)0xD0,7);
//******************************************************************************
while (1)
Доброго времени суток)
Вместо часов на экране выходит только две строки прямоугольных символов. Уж и код несколько раз переписывал, и коммутацию сто раз проверил, даже перепаял контакты — все так же( Может ли быть дело в кубе? Скачал специально версию 16 года чтоб не париться с совместимостью.
Добрый день.
Не могу дальше двигаться. выдает 2 ошибки.
LCD_RTC\LCD_RTC.axf: Error: L6200E: Symbol aTxBuffer multiply defined (by stm32f4xx_it.o and main.o).
LCD_RTC\LCD_RTC.axf: Error: L6200E: Symbol aTxBuffer multiply defined (by stm32f4xx_hal_msp.o and main.o).
перенес переменную в main.с!!! вроде заработало. прочитал об ошибке на просторах паутины. но все равно не понял сути.
Я запустил эти часы реального времени на регистрах правда в кубе использовал генератор кода LL библиотека.