cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F407: I2C1 not working with LCD1602 display. Bare metal

AAnth.1
Senior

Hi

my first attempt to program a LCD1602 via I2C1 on my STM32F407 discovery board failed miserably. I do not get the start condition generated, which means I never get the Start bit (SB) set in my status register. It seems that I am missing something fundamentally in my setup. Intention: STM32F407 is master, LCD is slave. The I2C module on my LCD seems to be the PCF8574, with address 0x27 (A0, A1 and A2 are logic high).

Any sharp eyes around that can spot my mistake? Thank you.

EDIT:

My code get stuck in line 42, where it never leaves the while loop. 

My code:

#include "stm32f4xx.h"
 
// I2C1 using PB6 and PB7 as AF.
// PB6: I2C1_SCL; PB7: I2C1_SDA
// I2C driver used in LCD 1602: PCF8574
int main(void)
{
	// 1. Setting up GPIO to I2C
 
	// 1.0 Sets PB6 and PB7 to Alternate Function
	GPIOB->MODER |= (2<<GPIO_MODER_MODER6_Pos);		// Sets PB6 to AF
	GPIOB->MODER |= (2<<GPIO_MODER_MODER7_Pos);		// Sets PB7 to AF
 
	// 1.1 PB6 and PB7 open drain
	GPIOB->OTYPER |= ((1<<GPIO_OTYPER_OT6_Pos) | (1<<GPIO_OTYPER_OT7_Pos));
	// 3. PB6 and PB7 no Pull up/down
	GPIOB->PUPDR &= ~((3U<<GPIO_PUPDR_PUPD6_Pos) | (3U<<GPIO_PUPDR_PUPD7_Pos));
 
	// 1.2 PB6 and PB7 to AF4
	GPIOB->AFR[0] |= (4U<<GPIO_AFRL_AFSEL6_Pos);
	GPIOB->AFR[0] |= (4U<<GPIO_AFRL_AFSEL7_Pos);
 
	// 2. Setting up I2C1
 
	// 2.0 Enable APB1 Bus
	RCC->APB1ENR |= (1<<RCC_APB1ENR_I2C1EN_Pos);	// Enables I2C1 on APB1 line
 
	// 1. Step: Configure the mode (SM, FM, FM+)
	// 2. Step: Configure the speed (100kHz, 400kHz etc.)
	I2C1->CR2 |= (16U<<I2C_CR2_FREQ_Pos);	// HSI is used. 16MHz
	I2C1->CCR &= ~(1<<I2C_CCR_FS_Pos);		// Standard mode SM
	I2C1->CCR |= (80U<<I2C_CCR_CCR_Pos);		// CCR=Thigh/TPCLK1=0.5*(1/100kHz)/(1/16MHz)=80
	// 4. Step: Enable ACK
	I2C1->CR1 |= (1<<I2C_CR1_ACK_Pos);				// Enable acknowledge
	// 5. Step: Configure rise time for I2C pins.
	I2C1->TRISE |= (17<<I2C_TRISE_TRISE_Pos);
 
	I2C1->CR1 |= (1<<I2C_CR1_PE_Pos);
	// Sending data
	// Generate start condition
	I2C1->CR1 |= (1<<I2C_CR1_START_Pos);	// Generate repeated start condition
	while(!(I2C1->SR1 & I2C_SR1_SB_Pos));	// Wait until Start bit is set
	uint8_t tempAddress = 0;
	uint8_t slaveAddress = 39;				// 0x27 = 39dec = 0b0010 0111
	tempAddress = (slaveAddress<<1);		// Make space on the 0th bit for R/W
	tempAddress &= ~(1<<0);					// Clear 0th bit for write
	I2C1->DR |= tempAddress;
 
	while(!(I2C1->SR1 & I2C_SR1_ADDR_Pos));
 
	while(!(I2C1->SR1 & I2C_SR1_TXE_Pos));
 
	I2C1->DR = 100;
 
	while(!(I2C1->SR1 & I2C_SR1_TXE_Pos));
 
	while(!(I2C1->SR1 & I2C_SR1_BTF_Pos));
 
	I2C1->CR1 |= (1<<I2C_CR1_STOP_Pos);
 
	I2C1->CR1 &= ~(1<<I2C_CR1_PE_Pos);		// Turn off I2C1
 
 
 
	for(;;);
}

1 ACCEPTED SOLUTION

Accepted Solutions

Oh wait. I am wrong on this one. On the 9th bit, an ACK is considered a logic low on the SDA line. So in fact, the address phase is acknowledged. And so is the data transfer of the 0x64 on the data line. On the 9th bit, SDA is logic low as well.

So it seems that my I2C is working as intended. That's great. 🙂

Now I just need to understand how to control the LCD with that. 🙂

Thanks for your support 🙂

Cheers,

View solution in original post

8 REPLIES 8

You have to enable GPIOB clock in RCC first.

Also, you want to put delay between enabling clock and accessing given peripheral, read errata.

JW

Then there is this thing:

> I2C1->DR |= tempAddress;

where you surely don't want to do |= as reading DR has consequences,

There may be similar logical and sequencing issues, you might need to debug. Try to follow the RM.

The I2C module is rather tricky, and unless there's some specific requirement e.g. for speed , you may be better off bit-banging the master.

JW

TDK
Guru

> while(!(I2C1->SR1 & I2C_SR1_SB_Pos));

You're using I2C_SR1_SB_Pos where you should be using I2C_SR1_SB. This mistake occurs on a number of lines both before and after line 42.

If you feel a post has answered your question, please click "Accept as Solution".
AAnth.1
Senior

Hi Jan

Thank you for your answer. I enabled the GPIOB clock in RCC now and implemented a delay after each peripheral bus enable. See below code. Also, what I noticed during debugging is, that the start condition is not generated at all, even when setting the bit. See the attached figure. So clearly, when the start condition is not generated, the status bit will never be set. I believe I messed something up in my I2C configuration, or what could be the reason that I cannot set the start generation bit?

Thank you,

0693W000001sAFcQAM.png

#include "stm32f4xx.h"
 
// I2C1 using PB6 and PB7 as AF.
// PB6: I2C1_SCL; PB7: I2C1_SDA
// I2C driver used in LCD 1602: PCF8574
int main(void)
{
	// 1. Setting up GPIO to I2C
	RCC->AHB1ENR |= (1<<RCC_AHB1ENR_GPIOBEN_Pos);
	uint32_t ctr = 0;
	for(;ctr<500000;ctr++)
	{
 
	}
	ctr = 0;
	// 1.0 Sets PB6 and PB7 to Alternate Function
	GPIOB->MODER |= (2<<GPIO_MODER_MODER6_Pos);		// Sets PB6 to AF
	GPIOB->MODER |= (2<<GPIO_MODER_MODER7_Pos);		// Sets PB7 to AF
 
	// 1.1 PB6 and PB7 open drain
	GPIOB->OTYPER |= ((1<<GPIO_OTYPER_OT6_Pos) | (1<<GPIO_OTYPER_OT7_Pos));
	// 3. PB6 and PB7 no Pull up/down
	GPIOB->PUPDR &= ~((3U<<GPIO_PUPDR_PUPD6_Pos) | (3U<<GPIO_PUPDR_PUPD7_Pos));
 
	// 1.2 PB6 and PB7 to AF4
	GPIOB->AFR[0] |= (4U<<GPIO_AFRL_AFSEL6_Pos);
	GPIOB->AFR[0] |= (4U<<GPIO_AFRL_AFSEL7_Pos);
 
	// 2. Setting up I2C1
 
	// 2.0 Enable APB1 Bus
	RCC->APB1ENR |= (1<<RCC_APB1ENR_I2C1EN_Pos);	// Enables I2C1 on APB1 line
	for(;ctr<500000;ctr++)
	{
 
	}
	// 1. Step: Configure the mode (SM, FM, FM+)
	// 2. Step: Configure the speed (100kHz, 400kHz etc.)
	I2C1->CR2 |= (16U<<I2C_CR2_FREQ_Pos);	// HSI is used. 16MHz
	I2C1->CCR &= ~(1<<I2C_CCR_FS_Pos);		// Standard mode SM
	I2C1->CCR |= (80U<<I2C_CCR_CCR_Pos);		// CCR=Thigh/TPCLK1=0.5*(1/100kHz)/(1/16MHz)=80
	// 4. Step: Enable ACK
	I2C1->CR1 |= (1<<I2C_CR1_ACK_Pos);				// Enable acknowledge
	// 5. Step: Configure rise time for I2C pins.
	I2C1->TRISE |= (17<<I2C_TRISE_TRISE_Pos);
 
	I2C1->CR1 |= (1<<I2C_CR1_PE_Pos);
	// Sending data
	// Generate start condition
	I2C1->CR1 |= (1<<I2C_CR1_START_Pos);	// Generate repeated start condition
	while(!(I2C1->SR1 & I2C_SR1_SB_Pos));	// Wait until Start bit is set
	uint8_t tempAddress = 0;
	uint8_t slaveAddress = 39;				// 0x27 = 39dec = 0b0010 0111
	tempAddress = (slaveAddress<<1);		// Make space on the 0th bit for R/W
	tempAddress &= ~(1<<0);					// Clear 0th bit for write
	I2C1->DR |= tempAddress;
 
	while(!(I2C1->SR1 & I2C_SR1_ADDR_Pos));
 
	while(!(I2C1->SR1 & I2C_SR1_TXE_Pos));
 
	I2C1->DR = 100;
 
	while(!(I2C1->SR1 & I2C_SR1_TXE_Pos));
 
	while(!(I2C1->SR1 & I2C_SR1_BTF_Pos));
 
	I2C1->CR1 |= (1<<I2C_CR1_STOP_Pos);
 
	I2C1->CR1 &= ~(1<<I2C_CR1_PE_Pos);		// Turn off I2C1
 
 
 
	for(;;);
}

Thank you TDK. This helped solving one problem. I am now leaving the while loop after a true condition as I should be. But I still cannot seem to get the I2C working. I hooked up my logic analyzer while the breakpoint is set to writing the data register, and I seem to have a fundamental problem here. Although I intended to set up the SCLK frequency to be 100kHz for SM, I seem to have a SCLK of 200kHz, where Thigh is 2,5us instead of 5us. This is mystic to me! :\ I am using HSI with 16MHz and set 80 into the CCR register. 16MHz/100kHz = 160 as a full period. Half of that is Thigh, thus Thigh=160/2=80. It seems like I am running twice as high as I should be.

0693W000001sAJAQA2.png

#include "stm32f4xx.h"
 
// I2C1 using PB6 and PB7 as AF.
// PB6: I2C1_SCL; PB7: I2C1_SDA
// I2C driver used in LCD 1602: PCF8574
int main(void)
{
	// 1. Setting up GPIO to I2C
	RCC->AHB1ENR |= (1<<RCC_AHB1ENR_GPIOBEN_Pos);
	uint32_t ctr = 0;
	for(;ctr<500000;ctr++)
	{
 
	}
	ctr = 0;
	// 1.0 Sets PB6 and PB7 to Alternate Function
	GPIOB->MODER |= (2<<GPIO_MODER_MODER6_Pos);		// Sets PB6 to AF
	GPIOB->MODER |= (2<<GPIO_MODER_MODER7_Pos);		// Sets PB7 to AF
 
	// 1.1 PB6 and PB7 open drain
	GPIOB->OTYPER |= ((1<<GPIO_OTYPER_OT6_Pos) | (1<<GPIO_OTYPER_OT7_Pos));
	// 3. PB6 and PB7 no Pull up/down
	GPIOB->PUPDR &= ~((3U<<GPIO_PUPDR_PUPD6_Pos) | (3U<<GPIO_PUPDR_PUPD7_Pos));
 
	// 1.2 PB6 and PB7 to AF4
	GPIOB->AFR[0] |= (4U<<GPIO_AFRL_AFSEL6_Pos);
	GPIOB->AFR[0] |= (4U<<GPIO_AFRL_AFSEL7_Pos);
 
	// 2. Setting up I2C1
 
	// 2.0 Enable APB1 Bus
	RCC->APB1ENR |= (1<<RCC_APB1ENR_I2C1EN_Pos);	// Enables I2C1 on APB1 line
	for(;ctr<500000;ctr++)
	{
 
	}
	// 1. Step: Configure the mode (SM, FM, FM+)
	// 2. Step: Configure the speed (100kHz, 400kHz etc.)
	I2C1->CR2 |= (16U<<I2C_CR2_FREQ_Pos);	// HSI is used. 16MHz
	I2C1->CCR &= ~(1<<I2C_CCR_FS_Pos);		// Standard mode SM
	I2C1->CCR |= (80U<<I2C_CCR_CCR_Pos);		// CCR=Thigh/TPCLK1=0.5*(1/100kHz)/(1/16MHz)=80
	// 4. Step: Enable ACK
	I2C1->CR1 |= (1<<I2C_CR1_ACK_Pos);				// Enable acknowledge
	// 5. Step: Configure rise time for I2C pins.
	I2C1->TRISE |= (17<<I2C_TRISE_TRISE_Pos);
 
	I2C1->CR1 |= (1<<I2C_CR1_PE_Pos);
	// Sending data
	// Generate start condition
	I2C1->CR1 |= (1<<I2C_CR1_START_Pos);	// Generate repeated start condition
	while(!(I2C1->SR1 & I2C_SR1_SB));	// Wait until Start bit is set
	uint8_t tempAddress = 0;
	uint8_t slaveAddress = 39;				// 0x27 = 39dec = 0b0010 0111
	tempAddress = (slaveAddress<<1);		// Make space on the 0th bit for R/W
	tempAddress &= ~(1<<0);					// Clear 0th bit for write
	I2C1->DR = tempAddress;
 
	while(!(I2C1->SR1 & I2C_SR1_ADDR));
	uint32_t temp1 = I2C1->SR1;
	uint32_t temp2 = I2C1->SR2;
 
	while(!(I2C1->SR1 & I2C_SR1_TXE));
 
	I2C1->DR = 100;
 
	while(!(I2C1->SR1 & I2C_SR1_TXE));
 
	while(!(I2C1->SR1 & I2C_SR1_BTF));
 
	I2C1->CR1 |= (1<<I2C_CR1_STOP_Pos);
 
	I2C1->CR1 &= ~(1<<I2C_CR1_PE_Pos);		// Turn off I2C1
 
 
 
	for(;;);
}

So double CCR. Read the CCR register description to find out your mistake.
If you feel a post has answered your question, please click "Accept as Solution".

Hi TDK

sorry for being a pain in the a**. I need to clarify that I probably messed up my project before so the measurements cannot be trusted. I noticed that when I tried to clean my project which failed to do so, and I needed to restart my computer. With that being done, I managed to run the below code and I can measure the signals on my logic analyzer. I first send the address followed by 100 (0x64). However, perhaps I am misreading the graph, but I fail to see the acknowledge by the slave after the address is send. Shouldn't in the 9th clock cycle be the acknowledge bit pulled high?

0693W000001sAROQA2.png

/**
 ******************************************************************************
 * @file           : main.c
 * @author         : Auto-generated by STM32CubeIDE
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
 * All rights reserved.</center></h2>
 *
 * This software component is licensed by ST under BSD 3-Clause license,
 * the "License"; You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at:
 *                        opensource.org/licenses/BSD-3-Clause
 *
 ******************************************************************************
 */
 
#if !defined(__SOFT_FP__) && defined(__ARM_FP)
  #warning "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."
#endif
 
#include "stm32f4xx.h"
 
// I2C1 using PB6 and PB7 as AF.
// PB6: I2C1_SCL; PB7: I2C1_SDA
// I2C driver used in LCD 1602: PCF8574
int main(void)
{
	// 1. Setting up GPIO to I2C
	RCC->AHB1ENR |= (1<<RCC_AHB1ENR_GPIOBEN_Pos);
	uint32_t ctr = 0;
	for(;ctr<500000;ctr++)
	{
 
	}
	ctr = 0;
	// 1.0 Sets PB6 and PB7 to Alternate Function
	GPIOB->MODER |= (2<<GPIO_MODER_MODER6_Pos);		// Sets PB6 to AF
	GPIOB->MODER |= (2<<GPIO_MODER_MODER7_Pos);		// Sets PB7 to AF
 
	// 1.1 PB6 and PB7 open drain
	GPIOB->OTYPER |= ((1<<GPIO_OTYPER_OT6_Pos) | (1<<GPIO_OTYPER_OT7_Pos));
	// 3. PB6 and PB7 no Pull up/down
	GPIOB->PUPDR &= ~((3U<<GPIO_PUPDR_PUPD6_Pos) | (3U<<GPIO_PUPDR_PUPD7_Pos));
 
	// 1.2 PB6 and PB7 to AF4
	GPIOB->AFR[0] |= (4U<<GPIO_AFRL_AFSEL6_Pos);
	GPIOB->AFR[0] |= (4U<<GPIO_AFRL_AFSEL7_Pos);
 
	// 2. Setting up I2C1
 
	// 2.0 Enable APB1 Bus
	RCC->APB1ENR |= (1<<RCC_APB1ENR_I2C1EN_Pos);	// Enables I2C1 on APB1 line
	for(;ctr<500000;ctr++)
	{
 
	}
	// 1. Step: Configure the mode (SM, FM, FM+)
	// 2. Step: Configure the speed (100kHz, 400kHz etc.)
	I2C1->CR2 |= (16U<<I2C_CR2_FREQ_Pos);	// HSI is used. 16MHz
	I2C1->CCR &= ~(1<<I2C_CCR_FS_Pos);		// Standard mode SM
	I2C1->CCR |= (80U<<I2C_CCR_CCR_Pos);		// CCR=Thigh/TPCLK1=0.5*(1/100kHz)/(1/16MHz)=80
	// 4. Step: Enable ACK
	I2C1->CR1 |= (1<<I2C_CR1_ACK_Pos);				// Enable acknowledge
	// 5. Step: Configure rise time for I2C pins.
	I2C1->TRISE |= (17<<I2C_TRISE_TRISE_Pos);
 
	I2C1->CR1 |= (1<<I2C_CR1_PE_Pos);
 
 
 
 
	for(;;)
	{
		for(;ctr<500000;ctr++)
		{
		}
		ctr = 0;
		// Sending data
		// Generate start condition
		I2C1->CR1 |= (1<<I2C_CR1_START_Pos);	// Generate repeated start condition
		while(!(I2C1->SR1 & I2C_SR1_SB));	// Wait until Start bit is set
		uint8_t tempAddress = 0;
		uint8_t slaveAddress = 39;				// 0x27 = 39dec = 0b0010 0111
		tempAddress = (slaveAddress<<1);		// Make space on the 0th bit for R/W
		tempAddress &= ~(1<<0);					// Clear 0th bit for write
		I2C1->DR = tempAddress;
 
		while(!(I2C1->SR1 & I2C_SR1_ADDR));
		uint32_t temp1 = 0;
		temp1 = I2C1->SR1;
		temp1 = I2C1->SR2;
		(void)temp1;
 
		while(!(I2C1->SR1 & I2C_SR1_TXE));
 
		I2C1->DR = 100;
 
		while(!(I2C1->SR1 & I2C_SR1_TXE));
 
		while(!(I2C1->SR1 & I2C_SR1_BTF));
 
		 I2C1->CR1 |= (1<<I2C_CR1_STOP_Pos);
 
//		I2C1->CR1 &= ~(1<<I2C_CR1_PE_Pos);		// Turn off I2C1
 
	}
}

Oh wait. I am wrong on this one. On the 9th bit, an ACK is considered a logic low on the SDA line. So in fact, the address phase is acknowledged. And so is the data transfer of the 0x64 on the data line. On the 9th bit, SDA is logic low as well.

So it seems that my I2C is working as intended. That's great. 🙂

Now I just need to understand how to control the LCD with that. 🙂

Thanks for your support 🙂

Cheers,