Урок 173. CMSIS. STM32F1. I2C. Переходник для LCD 16×2
В прошлом уроке, используя багаж наших знаний по аппаратной реализации шины I2C в контроллере STM32F1, мы поработали с данной шиной и на запись и на чтение, подключив к ней микросхему памяти EEPROM AT24C32. При этом мы уже использовали возможности библиотеки CMSIS.
Но, думаю, что для более глубокого понимания работы данной шины и её программирования при помощи средств библиотеки CMSIS одного урока будет недостаточно. Поэтому на данном уроке мы попробуем также подключить к данной шине контроллера символьный дисплей на контроллере HD44780 разрешением 16 x 2 символа.
С таким дисплеем мы уже много работали, также много работали и с переходником, а также постоянно продолжаем работать, поэтому рассказывать о дисплее, а также о переходнике и как они работают, не требуется.
Поэтому предлагаю сразу же перейти к практической части, чтобы не терять лишнее время.
Начнём со схемы подключения дисплея, которая у нас остаётся точно такая же, как и была в уроке 151, в котором мы также работали с таким дисплеем, используя библиотеку LL, только для обеспечения лучшего контакта по шине, мы подключим переходник без макетной платы, а также отдельный источник питания на 5 вольт для дисплея мы тоже не будем использовать. А воспользуемся мы выводом 5 вольт прямо из нашего ST-Link, за что отдельное спасибо посетителю ресурса, который этот метод подсказал
Проект был сделан из проекта прошлого занятия с именем CMSIS_I2C_EEPROM и был назван CMSIS_I2C_LCD1602.
Из проекта урока 151 с именем LL_I2C_LCD1602 скопируем в соответствующие папки нашего нового проекта файлы lcd.h, lcd.c, i2c_user.h и i2c_user.c и откроем его в Keil.
Удалим файл led.c из дерева проекта, а затем физически из папки проекта, также из папки удалим файл led.h.
Откроем файл main.c, исправим подключение файла led.h на lcd.h, а заодно подключим также и файл i2c_user.c, а также стандартную библиотеку stdio.h
#include "lcd.h"
#include "i2c_user.h"
#include <stdio.h>
В дерево проекта добавим файлы lcd.c и i2c_user.c

Из файла main.c удалим вот эти макросы
#define I2C_OWNADDRESS1_7BIT 0x00004000U
#define I2C_MODE_I2C 0x00000000U
#define SLAVE_OWN_ADDRESS 0xA0
#define I2C_REQUEST_WRITE 0x00
#define I2C_REQUEST_READ 0x01
Вот эти объявления также удалим
__IO uint8_t tim2_count = 0;
extern uint8_t R1,R2,R3,R4;
extern uint16_t num_gl;
uint8_t rd_value[20] = {0};
uint8_t wr_value[20] = {0x14,0x13,0x12,0x11,0x10,
0x0F,0x0E,0x0D,0x0C,0x0B,
0x0A,0x09,0x08,0x07,0x06,
0x05,0x04,0x03,0x02,0x01};
Функции AT24C_WriteBytes и AT24C_ReadBytes, TIM2_Init, TIM2_IRQHandler и GPIO_Init удалим вместе с телами.
Удалим вызовы следующих функций в main()
TIM2_Init();
GPIO_Init();
Также оттуда удалим вот это
TIM_EnableIT_UPDATE(TIM2);
TIM_EnableCounter(TIM2);
AT24C_WriteBytes (0x008A, wr_value, 20);
//AT24C_ReadBytes (0x008A, rd_value, 20);
for(i=0;i<20;i++)
{
ledprint(wr_value[i]);
//ledprint(rd_value[i]);
delay_ms(1000);
}
Функцию I2C_Init перенесём вместе со всем её телом в файл i2c_user.c, удалив из её заголовка спецификатор static, а затем создадим для неё прототип в заголовочном файле i2c_user.h
|
1 2 |
void I2C_SendByteByADDR(I2C_TypeDef * i2c, uint8_t c,uint8_t addr); void I2C_Init(void); |
В данном заголовочном файле также удалим подключение данных библиотек
#include «stm32f1xx.h»
#include «stm32f1xx_ll_i2c.h»
Вместо них подключим другую
|
1 |
#include "stm32f10x.h" |
В файле i2c_user.c добавим следующие макросы
|
1 2 3 4 |
#include "i2c_user.h" //------------------------------------------------ #define I2C_OWNADDRESS1_7BIT 0x00004000U #define I2C_MODE_I2C 0x00000000U |
Добавим также глобальную переменную
|
1 2 3 4 |
#define I2C_REQUEST_READ 0x01 //------------------------------------------------ __IO uint32_t tmpreg1; //------------------------------------------------ |
В функции I2C_Init исправим имя переменной вот здесь
tmpreg1 = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_I2C1EN);
Также в этой же функции поменяем вот здесь порядок, так как сначала надо включить тактирование порта, а уже затем только настраивать его ножки, иначе работать шина не будет
|
1 2 3 4 5 6 |
void I2C_Init(void) { //I2C1 GPIO SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPBEN); SET_BIT(GPIOB->CRL, GPIO_CRL_CNF7_1 | GPIO_CRL_CNF6_1 | GPIO_CRL_CNF7_0 | GPIO_CRL_CNF6_0 |\ GPIO_CRL_MODE7_1 | GPIO_CRL_MODE6_1 | GPIO_CRL_MODE7_0 | GPIO_CRL_MODE6_0); |
В функции I2C_SendByteByADDR исправим тело в соответствии с требованиями библиотеки CMSIS. Здесь у нас практически то же самое, что в функции записи в микросхему EEPROM, только здесь мы отправляем в шину один байт
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void I2C_SendByteByADDR(I2C_TypeDef * i2c, uint8_t c,uint8_t addr) { //Disable Pos CLEAR_BIT(i2c->CR1, I2C_CR1_POS); MODIFY_REG(i2c->CR1, I2C_CR1_ACK, I2C_CR1_ACK); SET_BIT(i2c->CR1, I2C_CR1_START); while (!READ_BIT(i2c->SR1, I2C_SR1_SB)){}; (void) i2c->SR1; //I2C_Write_Byte(addr); MODIFY_REG(i2c->DR, I2C_DR_DR, addr | I2C_REQUEST_WRITE); while (!READ_BIT(i2c->SR1, I2C_SR1_ADDR)){}; (void) i2c->SR1; (void) i2c->SR2; //I2C_Write_Byte(c); MODIFY_REG(i2c->DR, I2C_DR_DR,c); while (!READ_BIT(i2c->SR1, I2C_SR1_TXE)){}; //I2C_StopCondition(); SET_BIT(i2c->CR1, I2C_CR1_STOP); } //------------------------------------------------ |
В файле lcd.h удалим подключение следующих библиотек
#include «stm32f1xx.h»
#include «stm32f1xx_ll_utils.h»
Вместо них подключим вот эту
#include "stm32f10x.h"
Перейдём в файл lcd.c и добавим прототип функции задержки
|
1 2 3 4 |
#include "i2c_user.h" //------------------------------------------------ void delay_ms(uint32_t ms); //------------------------------------------------ |
Вместо вызова задержки LL_mDelay напишем delay_ms во всех встречающихся местах.
Добавим буфер на 1 байт
|
1 2 |
char str1[100]; uint8_t buf[1]={0}; |
После функции DelayMicro добавим задержку в наносекундах
|
1 2 3 4 5 6 7 |
//------------------------------------------------ __STATIC_INLINE void DelayNano(__IO uint32_t nanos) { nanos = nanos * (SystemCoreClock / 1000000) / 9000; while (nanos--); } //------------------------------------------------ |
В функции sendhalfbyte вместо данной задержки
DelayMicro(1);
используем вот эту
|
1 |
DelayNano(200); |
А вот эту задержку в конце тела удалим вообще
DelayMicro(50);
В функции LCD_ini включим запись вот здесь
|
1 2 |
LCD_WriteByteI2CLCD(0); setwrite();//запись |
А в конце тела удалим
setled();//подсветка
setwrite();//запись
Вот это удалим
sendbyte(0x0C,0);//дисплей включаем (D=1), курсоры никакие не нужны
Вместо этого просто отключим дисплей
sendbyte(0x08,0);//дисплей пока выключаем
А включим его вот здесь
|
1 2 3 |
sendbyte(0x06,0);// пишем влево delay_ms(1); sendbyte(0x0C,0);//дисплей включаем (D=1), курсоры никакие не нужны |
Из функции LCD_SetPos удалим два ненужных кейса
case 2:
sendbyte((0x14+x)|0x80,0);
break;
case 3:
sendbyte((0x54+x)|0x80,0);
break;
В функции main() файла main.c вызовем инициализацию дисплея и попробуем вывести 2 строки на дисплей
|
1 2 3 4 5 |
I2C_Init(); LCD_ini(); LCD_String("String 1"); LCD_SetPos(5,1); LCD_String("String 2"); |
Соберём проект, прошьём контроллер и посмотрим результат работы кода

Всё отображается. Отлично.
Добавим в main() локальный строковый массив
|
1 2 |
uint16_t i; char str1[10]; |
Добавим задержку на пару секунд, очистим надпись во второй строке дисплея и в бесконечном цикле организуем счётчик, который будет отображать постоянно увеличивающуюся цифру в данном месте
|
1 2 3 4 5 6 7 8 9 10 11 |
LCD_String("String 2"); delay_ms(2000); LCD_SetPos(5,1); LCD_String(" "); while(1) { i++; sprintf(str1,"%5d",i); LCD_SetPos(5,1); LCD_String(str1); delay_ms(500); |
Соберём код, прошьём контроллер и посмотрим, как работает наш счётчик

Всё работает.
Итак, на данном уроке мы подключили дисплей 16×2 посредством переходника, работающего по шине I2C, используя при этом возможности библиотеки CMSIS.
Всем спасибо за внимание!
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)




Один только вопрос, данный дисплей как правило с 5 вольтовой логикой, а STM32 с 3,3 вольтовой. Получается переходник I2C работает с 3,3 или как?
open-drain port
Данный дисплей работает и от 3,3в , надо только подкрутить резистор на плате переходника. Конечно будет тускло, но так мне больше нравится. Если хотите обычный режим(5в), то запитайте переходник от 5в, а данные получайте от 3,3в. Работает. Автору урока огромное спасибо, хотелось бы урок CMSIS. STM32F1. I2C. Переходник для LCD 16×2 на DMA.Именно DMA+CMSIS.