STM32 HAL LCD SSD1306 I2C настройка экрана и контроллера для отображения информации.
Первая статья по работе с экраном LCD SSD1306 по шине I2C где я опишу как его настроить и конечно, что то отобразить простое. Работать будем с контроллером STM32F103C8.
Сам экран и его схема подключения по шине I2C для статьи.
Протокол обмена информацией контроллера с SSD1306.
Отправка данных по шине I2C.
Для управления LCD отправляется ему массив данных в котором первым идёт байт адрес, вторым байт управления, третьим байт команда или просто данные для отображения. Все байты отправляются старшим битом в перёд.
Байт адрес в котором отправляется адрес устройства, бит SAO(не используется должен быть 0) и бит(R/W) записи данных в устройство или чтения из него. Адрес устройства 0x1E смещённый в лево на два бита, байт адреса может принимать два значения 0x78(0b01111000) для записи в устройство данных или 0x79(0b01111001) чтения из него.
После байта адреса идёт байт управления.
Тут меняется только D/C# бит который определяет, что за байтом управления следует команда или данные для отображения, для команды у него будет значение 0x00 для данных 0x40.
Байт данных.
Содержит восьми битное число, может быть как командой экрана с данными или просто данные для отображение.
Два примера отправка одной команды и команды с её данными.
Пример отправки двух байт данных которые будут записаны в память экрана для отображения.
Описание команд SSD1306.
1.Основные команды.
2.Команды скроллинга.
3.Команды установки адреса.
4.Команды аппаратной конфигурации (относятся к разрешающей способности панели и ориентации графики).
5.Команды настройки интервалов времени и схемы драйверов.
Вывод изображения или работа с видеопамятью экрана.
Графическая область экрана разбита на страницы(PAGE) и колонки(COL), каждая страница содержит по восемь бит, всего страниц 8 и также 128 колонок вот и получается максимальное разрешение экрана 8*8x128=8192 пикселей. Запись в видеопамять осуществляется только по байтно то есть по восемь пикселей сразу в нужную страницу в нужной колонке.
Запись в видео память можно производить в трёх режимах это: Page addressing mode, Horizontal addressing mode и Vertical addressing mode.
Page addressing mode устанавливается PAGE(страница) и начальный COL(столбец) с которого будем выводить изображение, после записи в PAGE байта данных счётчик COL автоматически увеличивается на 1. Когда будут заполнены все столбцы то есть после записи в COL127 счётчик столбцов сбрасывает и начинает вывод изображения с COL0, и так по кругу. Страница не меняется, для вывода в другую страницу вы должны переустановить адреса PAGE и COL.
Horizontal addressing mode определяется прямоугольная область с начальным адресом и конечным адресом. После записи в PAGE байта данных счётчик COL автоматически увеличивается на 1, когда будет достигнут последний адрес COL счётчик сбрасывается на начально значение и увеличивается счетчик PAGE. Если счётчик PAGE достигает максимального значения то он сбрасывается и устанавливается начальное значение.
Вывод изображения будет будет происходить по кругу пока не переопределите новую прямоугольную область то есть должны установить новый начальный и конечный адреса.
Vertical addressing mode Похоже на Horizontal addressing mode только после записи байта данных автоматически увеличивается PAGE на 1, после достижения максимального установленного значения счётчик сбрасывается до начального значения и прибавляется на 1 COL. Когда COL дойдёт до максимального значения тоже сбрасывается и устанавливается начальное значение, и так по кругу.
Ниже пример вывода изображения символа ′>′ в режиме ′Horizontal addressing mode′. Тут мы определяем прямоугольную область, задаём начальный адрес PAGE0 COL2 конечный адрес PAGE1 COL10 после отправляем 18 байт данных изображения.
Программная часть.
Код программы для вывода символа ′>′, здесь вы можете поглядеть как: настраиваются выводы контроллера для I2C, настройка I2C для обмена информацией, происходит инициализация SSD1306 для работы с экраном, очистка экрана от мусора и конечно вывод самого символа ′>′.
Функция LCD_SSD1306_init(); настраивает SSD1306 для обмена информацией, функция должна выполнятся один раз после подачи питания на экран!Показать код программы.
/*ProgCont.ru*/
#include "main.h"
#define SSD1306_I2C_ADDRESS 0x78
#define SSD1306_I2C_CONTROL_BYTE_COMMAND 0x00
#define SSD1306_I2C_CONTROL_BYTE_DATA 0x40
#define SSD1306_I2C_TIMEOUT 100
uint8_t data[1024];//Массив для отправки команд и данных.
/*Массив для символа ′>′.*/
uint8_t data_symbol[] = {0b00000001, 0b00000010, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b00000000,
0b10000000, 0b01000000, 0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010,0b00000001};
I2C_HandleTypeDef hi2c1 = {0};
void SystemClock_Config(void);
void MX_GPIO_Init(void);
void MX_I2C1_Init(void);
void SendCommand(uint8_t* data, uint8_t size);
void SendData(uint8_t *data, uint16_t size);
void LCD_SSD1306_init(void);
int main(void){
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
LCD_SSD1306_init();
/*Готовим массив для очистки экрана.*/
for( uint16_t clear = 0; clear < 1024; clear++){
data[clear] = 0;
}
/*Очищаем экран.*/
SendData( data, 1024);
/*Устанавливаем начальный и конечный адрес COL.*/
data[0] = 0x21;
data[1] = 2;//Начальный адрес.
data[2] = 10;//Конечный адрес.
SendCommand(data, 3);
/*Устанавливаем начальный и конечный адрес PAGE.*/
data[0] = 0x22;
data[1] = 0;//Начальный адрес.
data[2] = 1;//Конечный адрес.
SendCommand(data, 3);
/*Выводим символ ′>′.*/
SendData( data_symbol, 18);
}
/*Отправка массива команды.*/
void SendCommand(uint8_t* data, uint8_t size){
HAL_I2C_Mem_Write(&hi2c1, SSD1306_I2C_ADDRESS, SSD1306_I2C_CONTROL_BYTE_COMMAND, 1, data, size, SSD1306_I2C_TIMEOUT);
}
/*Отправка массива данных для отображения*/
void SendData(uint8_t *data, uint16_t size){
HAL_I2C_Mem_Write(&hi2c1, SSD1306_I2C_ADDRESS, SSD1306_I2C_CONTROL_BYTE_DATA, 1, data, size, SSD1306_I2C_TIMEOUT);
}
/*Функция для иницилизации SSD1306.*/
void LCD_SSD1306_init(void){
// Set display off
data[0] = 0xAE;
SendCommand(data, 1);
// Set oscillator frequency
data[0] = 0xD5;
data[1] = 0x80;
SendCommand(data, 2);
// Enable charge pump regulator
data[0] = 0x8D;
data[1] = 0x14;
SendCommand(data, 2);
// Set display start line
data[0] = 0x40;
SendCommand(data, 1);
// Set segment remap
data[0] = 0xA1;
SendCommand(data, 1);
// Set COM output scan direction
data[0] = 0xC8;
SendCommand(data, 1);
// Set COM pins hardware configuration
data[0] = 0xDA;
data[1] = 0x12;
SendCommand(data, 2);
// Set MUX ratio
data[0] = 0xA8;
data[1] = 63;
SendCommand(data, 2);
// Set display offset
data[0] = 0xD3;
data[1] = 0;
SendCommand(data, 2);
// Set horizontal addressing mode
data[0] = 0x20;
data[1] = 0x00;
SendCommand(data, 2);
// Set column address
data[0] = 0x21;
data[1] = 0;
data[2] = 127;
SendCommand(data, 3);
// Set page address
data[0] = 0x22;
data[1] = 0;
data[2] = 7;
SendCommand(data, 3);
// Set contrast
data[0] = 0x81;
data[1] = 0x7F;
SendCommand(data, 2);
// Deactivate scroll
data[0] = 0x2e;
SendCommand(data, 1);
// Disable Zoom
data[0] = 0xd6;
data[1] = 0x00;
SendCommand(data, 2);
// Entire display on
data[0] = 0xA4;
SendCommand(data, 1);
//Set normal display
data[0] = 0xA6;//0xA7
SendCommand(data, 1);
// Set display on
data[0] = 0xAF;
SendCommand(data, 1);
}
/*Настройка I2C.*/
void MX_I2C1_Init(void){
__HAL_RCC_I2C1_CLK_ENABLE();
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
}
/*Настройка выводов контроллера для I2C.*/
void MX_GPIO_Init(void){
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef I2C_pin = {0};
I2C_pin.Pin = GPIO_PIN_6|GPIO_PIN_7;
I2C_pin.Mode = GPIO_MODE_AF_OD;
I2C_pin.Pull = GPIO_PULLUP;
I2C_pin.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &I2C_pin);
}
/*Настройка системного тактирования.*/
void SystemClock_Config(void){
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line){}
#endif /* USE_FULL_ASSERT */
Пример для имитации движения символа в право по экрану, смещение по COL.
Показать код программы.
/*ProgCont.ru*/
#include "main.h"
#define SSD1306_I2C_ADDRESS 0x78
#define SSD1306_I2C_CONTROL_BYTE_COMMAND 0x00
#define SSD1306_I2C_CONTROL_BYTE_DATA 0x40
#define SSD1306_I2C_TIMEOUT 100
uint8_t data[1024];//Массив для отправки команд и данных.
/*Массив для символа ′>′.*/
uint8_t data_symbol[] = {0b00000001, 0b00000010, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b00000000,
0b10000000, 0b01000000, 0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010,0b00000001};
I2C_HandleTypeDef hi2c1 = {0};
void SystemClock_Config(void);
void MX_GPIO_Init(void);
void MX_I2C1_Init(void);
void SendCommand(uint8_t* data, uint8_t size);
void SendData(uint8_t *data, uint16_t size);
void LCD_SSD1306_init(void);
int main(void){
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
LCD_SSD1306_init();
for( uint16_t clear = 0; clear < 1024; clear++){
data[clear] = 0;
}
SendData( data, 1024);//Очищаем экран.
while(1){
/*Цикл для смещения символа по COL.*/
for(uint8_t move = 0; move < 119; move ++){
/*Устанавливаем начальный и конечный адрес COL по move.*/
data[0] = 0x21;
data[1] = move;//Начальный адрес.
data[2] = move+8;//Конечный адрес.
SendCommand(data, 3);
//Устанавливаем начальный и конечный адрес PAGE.
data[0] = 0x22;
data[1] = 0;//Начальный адрес.
data[2] = 1;//Конечный адрес.
SendCommand(data, 3);
/*Выводим символ ′>′.*/
SendData( data_symbol, 18);
HAL_Delay(50);
/*Очищаем массив.*/
for( uint16_t clear = 0; clear < 19; clear++){
data[clear] = 0;
}
/*Очищаем нужную область для отрисовки нового символа.*/
SendData( data, 18);
}
}
}
/*Отправка массива команды.*/
void SendCommand(uint8_t* data, uint8_t size){
HAL_I2C_Mem_Write(&hi2c1, SSD1306_I2C_ADDRESS, SSD1306_I2C_CONTROL_BYTE_COMMAND, 1, data, size, SSD1306_I2C_TIMEOUT);
}
/*Отправка массива данных для отображения*/
void SendData(uint8_t *data, uint16_t size){
HAL_I2C_Mem_Write(&hi2c1, SSD1306_I2C_ADDRESS, SSD1306_I2C_CONTROL_BYTE_DATA, 1, data, size, SSD1306_I2C_TIMEOUT);
}
/*Функция для иницилизации SSD1306.*/
void LCD_SSD1306_init(void){
// Set display off
data[0] = 0xAE;
SendCommand(data, 1);
// Set oscillator frequency
data[0] = 0xD5;
data[1] = 0x80;
SendCommand(data, 2);
// Enable charge pump regulator
data[0] = 0x8D;
data[1] = 0x14;
SendCommand(data, 2);
// Set display start line
data[0] = 0x40;
SendCommand(data, 1);
// Set segment remap
data[0] = 0xA1;
SendCommand(data, 1);
// Set COM output scan direction
data[0] = 0xC8;
SendCommand(data, 1);
// Set COM pins hardware configuration
data[0] = 0xDA;
data[1] = 0x12;
SendCommand(data, 2);
// Set MUX ratio
data[0] = 0xA8;
data[1] = 63;
SendCommand(data, 2);
// Set display offset
data[0] = 0xD3;
data[1] = 0;
SendCommand(data, 2);
// Set horizontal addressing mode
data[0] = 0x20;
data[1] = 0x00;
SendCommand(data, 2);
// Set column address
data[0] = 0x21;
data[1] = 0;
data[2] = 127;
SendCommand(data, 3);
// Set page address
data[0] = 0x22;
data[1] = 0;
data[2] = 7;
SendCommand(data, 3);
// Set contrast
data[0] = 0x81;
data[1] = 0x7F;
SendCommand(data, 2);
// Deactivate scroll
data[0] = 0x2e;
SendCommand(data, 1);
// Disable Zoom
data[0] = 0xd6;
data[1] = 0x00;
SendCommand(data, 2);
// Entire display on
data[0] = 0xA4;
SendCommand(data, 1);
//Set normal display
data[0] = 0xA6;//0xA7
SendCommand(data, 1);
// Set display on
data[0] = 0xAF;
SendCommand(data, 1);
}
/*Настройка I2C.*/
void MX_I2C1_Init(void){
__HAL_RCC_I2C1_CLK_ENABLE();
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
}
/*Настройка выводов контроллера для I2C.*/
void MX_GPIO_Init(void){
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef I2C_pin = {0};
I2C_pin.Pin = GPIO_PIN_6|GPIO_PIN_7;
I2C_pin.Mode = GPIO_MODE_AF_OD;
I2C_pin.Pull = GPIO_PULLUP;
I2C_pin.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &I2C_pin);
}
/*Настройка системного тактирования.*/
void SystemClock_Config(void){
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line){}
#endif /* USE_FULL_ASSERT */