cancel
Showing results for 
Search instead for 
Did you mean: 

I2C 16x2 Led Display. Problems in driving it. Please help.

FabioCC
Associate II

Good morning to everyone.
I met problems while trying to drive a 16x2 I2C Lcd display with STM32F446RE Nucleo boards.
I tried 2 different displays (Freenove and Arceli brand). It gives the same error.
I tried 2 different STM32F446RE boards. It gives the same error.
First line, all characters full black squares, second line empty.
I made the right connections (5Vdc on the Vcc pin, GND on GND pin, SDA on PB7, SCL on PB6.
I placed a "Led check" on PA5 Led, to check if the program was "passing trough" the instruction body. The Led toggles as requested.
What is not working?
Please help.
And thanks in advance for who can help.
This is the code:

/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

#include "stm32f4xx_hal.h"
#include <string.h>

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

 

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* I2C LCD Address */
#define LCD_ADDRESS 0x27

/* Commands for LCD */
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

/* Flags for display entry mode */
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

/* Flags for display on/off control */
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

/* Flags for display/cursor shift */
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

/* Flags for function set */
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

/* Backlight control */
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00

/* En and Rs pin definitions */
#define En 0x04 // Enable bit
#define Rw 0x02 // Read/Write bit
#define Rs 0x01 // Register select bit

/* Function prototypes */
void LCD_init(void);
void LCD_send(uint8_t value, uint8_t mode);
void LCD_write(uint8_t value);
void LCD_pulseEnable(uint8_t data);
void LCD_sendString(char *str);
void LCD_clear(void);
void LCD_setCursor(uint8_t col, uint8_t row);

/* Global I2C handle */
extern I2C_HandleTypeDef hi2c1;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void LCD_init(void) {
// 4 bit initialization
HAL_Delay(50); // wait for >40ms
LCD_send(0x30, 0);
HAL_Delay(5); // wait for >4.1ms
LCD_send(0x30, 0);
HAL_Delay(1); // wait for >100us
LCD_send(0x30, 0);
HAL_Delay(10);
LCD_send(0x20, 0); // 4bit mode

LCD_send(LCD_FUNCTIONSET | LCD_2LINE | LCD_5x8DOTS, 0); // Function set: 4-bit, 2 line, 5x8 dots
LCD_send(LCD_DISPLAYCONTROL | LCD_DISPLAYON, 0); // Display on
LCD_clear(); // Clear display
LCD_send(LCD_ENTRYMODESET | LCD_ENTRYLEFT, 0); // Entry mode set: increment cursor, no shift
}

void LCD_send(uint8_t value, uint8_t mode) {
uint8_t highnib = value & 0xF0;
uint8_t lownib = (value << 4) & 0xF0;
LCD_write((highnib) | (mode << 2) | LCD_BACKLIGHT);
LCD_pulseEnable(highnib | (mode << 2));
LCD_write((lownib) | (mode << 2) | LCD_BACKLIGHT);
LCD_pulseEnable(lownib | (mode << 2));
}

void LCD_write(uint8_t value) {
HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDRESS, &value, 1, HAL_MAX_DELAY);
}

void LCD_pulseEnable(uint8_t data) {
LCD_write(data | En); // En high
HAL_Delay(1); // enable pulse must be >450ns
LCD_write(data & ~En); // En low
HAL_Delay(1); // commands need > 37us to settle
}

void LCD_sendString(char *str) {
while (*str) {
LCD_send((uint8_t)(*str), Rs);
str++;
}
}

void LCD_clear(void) {
LCD_send(LCD_CLEARDISPLAY, 0);
HAL_Delay(2); // This command takes a long time!
}

void LCD_setCursor(uint8_t col, uint8_t row) {
int row_offsets[] = {0x00, 0x40, 0x14, 0x54};
if (row > 1) {
row = 1; // Only 2 lines on 16x2 LCD
}
LCD_send(LCD_SETDDRAMADDR | (col + row_offsets[row]), 0);
}
/* USER CODE END 0 */

/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* Configure the system clock */
SystemClock_Config();

/* USER CODE BEGIN SysInit */

/* USER CODE END SysInit */

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
// Initialize I2C1 here if not done in your main setup
LCD_init(); // Initialize the LCD

LCD_setCursor(0, 0); // First line
LCD_sendString("Hello");

LCD_setCursor(0, 1); // Second line
LCD_sendString("world");

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* Toggle LED */
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);

/* Delay for 500 milliseconds */
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}


/* Placeholder for system clock configuration */
/*void SystemClock_Config(void) {
// Implementation depends on your specific setup
}*/

/* Placeholder for I2C initialization */
/*void MX_I2C1_Init(void) {
// Implementation depends on your specific setup
}*/


/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);

/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 16;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}

/** Initializes the CPU, AHB and APB buses clocks
*/
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)
{
Error_Handler();
}
}

/**
* @brief I2C1 Initialization Function
* @PAram None
* @retval None
*/
static void MX_I2C1_Init(void)
{

/* USER CODE BEGIN I2C1_Init 0 */

/* USER CODE END I2C1_Init 0 */

/* USER CODE BEGIN I2C1_Init 1 */

/* USER CODE END I2C1_Init 1 */
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
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;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN I2C1_Init 2 */

/* USER CODE END I2C1_Init 2 */

}

/**
* @brief USART2 Initialization Function
* @PAram None
* @retval None
*/
static void MX_USART2_UART_Init(void)
{

/* USER CODE BEGIN USART2_Init 0 */

/* USER CODE END USART2_Init 0 */

/* USER CODE BEGIN USART2_Init 1 */

/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */

/* USER CODE END USART2_Init 2 */

}

/**
* @brief GPIO Initialization Function
* @PAram None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

/*Configure GPIO pin : B1_Pin */
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

/*Configure GPIO pin : LD2_Pin */
GPIO_InitStruct.Pin = LD2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @PAram file: pointer to the source file name
* @PAram line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

7 REPLIES 7
Techn
Senior III

Try with 3.3v instead of 5v. Chek if the pull up is installed for i2c lines. 

If you feel a post has answered your question, please click "Accept as Solution".
Danish1
Lead III

First line full of black squares, second line blank is a typical ”power-on” state for LCD displays. Not a specific error state.

Something I’m not clear on is whether you’re using the LCD in 4-bit mode or I2C mode. Many LCDs may be used in 8-bit, 4-bit, I2C or SPI. Which one being set at startup/reset. But I’ve never been aware of changing between modes as and when you choose.

For me, if I have the pins I’d stick with or 4-bit (or 8-bit). That’s so much quicker than I2C.

Please see the Posting Tips for how to properly post source code:

https://community.st.com/t5/community-guidelines/how-to-write-your-question-to-maximize-your-chances-to-find-a/ta-p/575228

 

Have you used an oscilloscope and/or logic analyser to see what's actually happening on the wires?

 

You should check the return codes from HAL_I2C_... functions - they will tell you if I2C comms is failing.

 

Are you sure you have the I2C address correct?

xisco
Associate III

Hello,

 

I2C address of this display is 0x27 (8bits)

You need to move this address 1 bit left.

0010 0111

0100 1110

Try 0x4E address and will work.

 

 

 

 

xisco
Associate III

Clarification,

This display is not I2C. May work on 4 or 8 bits

0x27 is the I2C address of the adapter for this display (4 bits) to I2C bus

 

Regards

Danish1
Lead III

There's a lot that hasn't been said.

Firstly, the original poster was @FabioCC , but @xisco  claims to know quit a bit about the system. I have has a quick look in the internet and "Arceli LCD" brings up https://www.amazon.co.uk/ARCELI-Display-Modules-Compatible-Raspberry/dp/B0CK1JBMGS  - is that the board you're trying to interface? If not, please provide a link. Whatever, a data-sheet on the board would be much more helpful than "sample code".

Your LED flashing tells me that the stm32 has completed talking to the I2C device without hitting Error_Handler(). Nothing more, nothing less.

The I2C protocol allows a master to tell if a slave is present at the specified address. But you don't look at the value returned by HAL_I2C_Master_Transmit to see if the value you have in LCD_ADDRESS* is correct and that you have wired things up right.

*There's a "gotcha" in I2C addressing, because the least-significant bit defines whether you're reading or writing. Some software interfaces expect addresses coded by bits 7 to 1, others by bits 6 to 0.


@Danish1 wrote:

*There's a "gotcha" in I2C addressing, because the least-significant bit defines whether you're reading or writing. Some software interfaces expect addresses coded by bits 7 to 1, others by bits 6 to 0.


Indeed - see: 

https://www.avrfreaks.net/s/topic/a5C3l000000UZ4wEAG/t148636?comment=P-1418813

and follow the link to:

https://www.avrfreaks.net/s/topic/a5C3l000000UXMPEA4/t142031?comment=P-1150738#:~:text=The%20discrepancy%20between%20talking%20of%20I2C%20addresses%20as%207%20or%208%20bits%20is%20very%20common%20indeed%20in%20software%2C%20tutorials%2C%20articles%2C%20and%20device%20datasheets

 

#I2CAddress