qqxjyxjy清风:TSM32F407ZGT6_1602IIC调不通,一到LCD_Init();就死机。请AI帮忙检查一下问题出在哪里。我编的主函数如下:
主函数main(void):
#include “stm32f4xx.h”
#include “./tim/bsp_advance_tim.h”
#include “./usart/bsp_debug_usart.h”
#include “./rcc/bsp_clkconfig.h”
#include “./systick/bsp_SysTick.h”
#include <stdint.h>
#include stm32f4xx.h"
#include “i2c_1602.h”
//#include “delay.h” // ÐèÒªÑÓʱº¯Êý¿â
// º¯ÊýÉùÃ÷
void RCC_Configuration(void);
void GPIO_Configuration(void);
void SysTick_Configuration(void);
int main(void)
{
// ³õʼ»¯ÏµÍ³Ê±ÖÓ
RCC_Configuration();
SysTick_Init();
// ³õʼ»¯SysTick¶¨Ê±Æ÷ÓÃÓÚÑÓʱ
// SysTick_Configuration();
// ³õʼ»¯I2C
I2C_Configuration();
Delay_ms(2000);
// ³õʼ»¯LCD
LCD_Init();
// ÔÚµÚÒ»ÐÐÏÔʾ"STM32F407"
LCD_SetCursor(0, 0);
LCD_PrintString("STM32F407");
// ÔÚµÚ¶þÐÐÏÔʾ"I2C 1602 LCD"
LCD_SetCursor(1, 0);
LCD_PrintString("I2C 1602 LCD");
while (1)
{
// Ö÷Ñ»•£ºÃ¿2ÃëÇл»ÏÔʾÄÚÈÝ
Delay_ms(2000);
LCD_Clear();
LCD_SetCursor(0, 0);
LCD_PrintString("Standard Lib");
LCD_SetCursor(1, 0);
LCD_PrintString("I2C Example");
Delay_ms(2000);
LCD_Clear();
LCD_SetCursor(0, 0);
LCD_PrintString("STM32F407");
LCD_SetCursor(1, 0);
LCD_PrintString("I2C 1602 LCD");
}
}
void RCC_Configuration(void)
{
// ÆôÓÃÍⲿ¸ßËÙʱÖÓ
RCC_HSEConfig(RCC_HSE_ON);
// µÈ´ýHSEÎȶ¨
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
// ÅäÖÃPLL
RCC_PLLConfig(RCC_PLLSource_HSE, 8, 336, 2, 7); // 168MHzϵͳʱÖÓ
RCC_PLLCmd(ENABLE);
// µÈ´ýPLLÎȶ¨
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
// ÅäÖÃϵͳʱÖÓԴΪPLL
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// ÅäÖÃAHBʱÖÓ (HCLK = SYSCLK)
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// ÅäÖÃAPB1ʱÖÓ (PCLK1 = HCLK/4)
RCC_PCLK1Config(RCC_HCLK_Div4);
// ÅäÖÃAPB2ʱÖÓ (PCLK2 = HCLK/2)
RCC_PCLK2Config(RCC_HCLK_Div2);
}
//#ifndef __SYSTICK_H
//#define __SYSTICK_H
//#include “stm32f4xx.h”
//void SysTick_Init(void);
//void Delay_us(__IO uint32_t nTime);
//#define delay_ms(x) Delay_us(100*x) //µ¥Î»ms
模块bsp_SysTick.h":
//#endif /* __SYSTICK_H */
#ifndef __SYSTICK_H
#define __SYSTICK_H
#include “stm32f4xx.h”
void SysTick_Init(void);
void Delay_us(__IO u32 nTime);
#define Delay_ms(x) Delay_us(100*x) //µ¥Î»ms
#endif /* __SYSTICK_H */
模块bsp_SysTick.c":
#include “./systick/bsp_SysTick.h”
static __IO u32 TimingDelay;
/**
- @brief Æô¶¯ÏµÍ³µÎ´ð¶¨Ê±Æ÷ SysTick
- @param ÎÞ
- @retval ÎÞ
/
void SysTick_Init(void)
{
/ SystemFrequency / 1000 1msÖжÏÒ»´Î- SystemFrequency / 100000 10usÖжÏÒ»´Î
- SystemFrequency / 1000000 1usÖжÏÒ»´Î
/
if (SysTick_Config(SystemCoreClock / 100000))
{
/ Capture error */
while (1);
}
}
/**
-
@brief usÑÓʱ³ÌÐò,10usΪһ¸öµ¥Î»
-
@param
-
@arg nTime: Delay_us( 1 ) ÔòʵÏÖµÄÑÓʱΪ 1 * 10us = 10us
-
@retval ÎÞ
*/
void Delay_us(__IO u32 nTime)
{
TimingDelay = nTime;while(TimingDelay != 0);
}
/**
- @brief »ñÈ¡½ÚÅijÌÐò
- @param ÎÞ
- @retval ÎÞ
- @attention ÔÚ SysTick ÖжϺ¯Êý SysTick_Handler()µ÷ÓÃ
*/
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay–;
}
}
/***********************END OF FILE/
模块i2c_1602.h:
ifndef __I2C_1602_H
#define __I2C_1602_H
#include “stm32f4xx.h”
// ¶¨ÒåI2C½Ó¿Ú£¬¿É¸ù¾Ýʵ¼ÊÓ²¼þÐÞ¸Ä
#define I2Cx I2C1
#define I2Cx_CLK RCC_APB1Periph_I2C1
#define I2Cx_SCL_GPIO_CLK RCC_AHB1Periph_GPIOB
#define I2Cx_SDA_GPIO_CLK RCC_AHB1Periph_GPIOB
#define I2Cx_GPIO_PORT GPIOB
#define I2Cx_SCL_PIN GPIO_Pin_5
#define I2Cx_SDA_PIN GPIO_Pin_7
#define I2Cx_SCL_SOURCE GPIO_PinSource5
#define I2Cx_SDA_SOURCE GPIO_PinSource7
#define I2Cx_AF GPIO_AF_I2C1
// LCD I2CµØÖ•£¨¸ù¾Ýʵ¼ÊÄ£¿éÐ޸ģ¬Í¨³£Îª0x4E»ò0x40£©
#define LCD_ADDR 0x3F << 1 // ×óÒÆ1룬ÒòΪI2CÐÒéÒªÇó
// Ö¸ÁÒå
#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
// ±ê־붨Òå
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00
// º¯ÊýÉùÃ÷
void I2C_Configuration(void);
void LCD_Init(void);
void LCD_Clear(void);
void LCD_SetCursor(uint8_t row, uint8_t col);
void LCD_PrintChar(uint8_t ch);
void LCD_PrintString(char *str);
void LCD_CreateChar(uint8_t location, uint8_t *charmap);
void LCD_SendCommand(uint8_t cmd);
void LCD_SendData(uint8_t data);
#endif /* __I2C_1602_H */
模块i2c_1602.c:
#include “i2c_1602.h”
#include “./systick/bsp_SysTick.h”
/**
-
@brief ÅäÖÃI2CÍâÉè
-
@param ÎÞ
-
@retval ÎÞ
*/
void I2C_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;// ʹÄÜI2CºÍGPIOʱÖÓ
RCC_APB1PeriphClockCmd(I2Cx_CLK, ENABLE);
RCC_AHB1PeriphClockCmd(I2Cx_SCL_GPIO_CLK | I2Cx_SDA_GPIO_CLK, ENABLE);// ÅäÖÃI2CÒý½ÅΪ¸´Óù¦ÄÜ
GPIO_InitStructure.GPIO_Pin = I2Cx_SCL_PIN | I2Cx_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed =GPIO_Low_Speed;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; // ¿ªÂ©Êä³ö
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // ÉÏÀ
GPIO_Init(I2Cx_GPIO_PORT, &GPIO_InitStructure);// Á¬½ÓÒý½Åµ½I2CÍâÉè
GPIO_PinAFConfig(I2Cx_GPIO_PORT, I2Cx_SCL_SOURCE, I2Cx_AF);
GPIO_PinAFConfig(I2Cx_GPIO_PORT, I2Cx_SDA_SOURCE, I2Cx_AF);// ÅäÖÃI2C
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // Ö÷»úģʽ²»ÐèÒª×Ô¼ºµÄµØÖ•
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 100000; // 100kHzI2C_Init(I2Cx, &I2C_InitStructure);
// ʹÄÜI2C
I2C_Cmd(I2Cx, ENABLE);
}
/**
-
@brief ÏòI2CÉ豸•¢ËÍÊý¾Ý
-
@param addr: É豸µØÖ•
-
@param data: Òª•¢Ë͵ÄÊý¾Ý
-
@retval ÎÞ
*/
static void I2C_WriteByte(uint8_t addr, uint8_t data)
{
// µÈ´ýI2C¿ÕÏÐ
while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));// •¢ËÍÆðʼÐźÅ
I2C_GenerateSTART(I2Cx, ENABLE);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));// •¢ËÍÉ豸µØÖ•
I2C_Send7bitAddress(I2Cx, addr, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));// •¢ËÍÊý¾Ý
I2C_SendData(I2Cx, data);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));// •¢ËÍÍ£Ö¹ÐźÅ
I2C_GenerateSTOP(I2Cx, ENABLE);
}
/**
-
@brief ÏòLCD•¢ËÍÃüÁî
-
@param cmd: ÃüÁî×Ö½Ú
-
@retval ÎÞ
*/
void LCD_SendCommand(uint8_t cmd)
{
uint8_t data_u, data_l;
uint8_t data_u_tx, data_l_tx;data_u = (cmd & 0xF0);
data_l = ((cmd << 4) & 0xF0);// ¸ßËÄλÊý¾Ý + ¿ØÖÆÎ» (RS=0, RW=0, EN=1)
data_u_tx = data_u | 0x0C;
// ¸ßËÄλÊý¾Ý + ¿ØÖÆÎ» (RS=0, RW=0, EN=0)
data_l_tx = data_u | 0x08;I2C_WriteByte(LCD_ADDR, data_u_tx);
Delay_ms(1);
I2C_WriteByte(LCD_ADDR, data_l_tx);
Delay_ms(1);// µÍËÄλÊý¾Ý + ¿ØÖÆÎ» (RS=0, RW=0, EN=1)
data_u_tx = data_l | 0x0C;
// µÍËÄλÊý¾Ý + ¿ØÖÆÎ» (RS=0, RW=0, EN=0)
data_l_tx = data_l | 0x08;I2C_WriteByte(LCD_ADDR, data_u_tx);
Delay_ms(1);
I2C_WriteByte(LCD_ADDR, data_l_tx);
Delay_ms(1);
}
/**
-
@brief ÏòLCD•¢ËÍÊý¾Ý
-
@param data: Êý¾Ý×Ö½Ú
-
@retval ÎÞ
*/
void LCD_SendData(uint8_t data)
{
uint8_t data_u, data_l;
uint8_t data_u_tx, data_l_tx;data_u = (data & 0xF0);
data_l = ((data << 4) & 0xF0);// ¸ßËÄλÊý¾Ý + ¿ØÖÆÎ» (RS=1, RW=0, EN=1)
data_u_tx = data_u | 0x0D;
// ¸ßËÄλÊý¾Ý + ¿ØÖÆÎ» (RS=1, RW=0, EN=0)
data_l_tx = data_u | 0x09;I2C_WriteByte(LCD_ADDR, data_u_tx);
Delay_ms(1);
I2C_WriteByte(LCD_ADDR, data_l_tx);
Delay_ms(1);// µÍËÄλÊý¾Ý + ¿ØÖÆÎ» (RS=1, RW=0, EN=1)
data_u_tx = data_l | 0x0D;
// µÍËÄλÊý¾Ý + ¿ØÖÆÎ» (RS=1, RW=0, EN=0)
data_l_tx = data_l | 0x09;I2C_WriteByte(LCD_ADDR, data_u_tx);
Delay_ms(1);
I2C_WriteByte(LCD_ADDR, data_l_tx);
Delay_ms(1);
}
/**
-
@brief ³õʼ»¯LCD
-
@param ÎÞ
-
@retval ÎÞ
*/
void LCD_Init(void)
{
// ³õʼ»¯ÑÓ³Ù
Delay_ms(50);// ³õʼ»¯ÐòÁÐ
LCD_SendCommand(0x02); // »Øµ½³õʼλÖÃ
LCD_SendCommand(0x28); // 4λģʽ£¬2ÐÐÏÔʾ£¬5x8µãÕó
LCD_SendCommand(0x0C); // ÏÔʾ¿ª£¬¹â±ê¹Ø£¬ÉÁ˸¹Ø
LCD_SendCommand(0x06); // ¹â±ê×Ô¶¯ÓÒÒÆ£¬ÆÁÄ»²»Òƶ¯
LCD_SendCommand(0x01); // ÇåÆÁ
Delay_ms(2);
}
/**
- @brief ÇåÆÁ
- @param ÎÞ
- @retval ÎÞ
*/
void LCD_Clear(void)
{
LCD_SendCommand(LCD_CLEARDISPLAY);
Delay_ms(2);
}
/**
-
@brief ÉèÖùâ±êλÖÃ
-
@param row: ÐкÅ(0»ò1)
-
@param col: ÁкÅ(0-15)
-
@retval ÎÞ
*/
void LCD_SetCursor(uint8_t row, uint8_t col)
{
uint8_t address;if (row == 0)
address = 0x00 + col;
else
address = 0x40 + col;LCD_SendCommand(LCD_SETDDRAMADDR | address);
}
/**
- @brief ÏÔʾµ¥¸ö×Ö•û
- @param ch: ÒªÏÔʾµÄ×Ö•û
- @retval ÎÞ
*/
void LCD_PrintChar(uint8_t ch)
{
LCD_SendData(ch);
}
/**
- @brief ÏÔʾ×Ö•û´®
- @param str: ÒªÏÔʾµÄ×Ö•û´®Ö¸Õë
- @retval ÎÞ
*/
void LCD_PrintString(char *str)
{
while (*str)
{
LCD_PrintChar(*str++);
}
}
/**
- @brief ´´½¨×Ô¶¨Òå×Ö•û
- @param location: λÖÃ(0-7)
- @param charmap: ×Ö•ûµãÕóÊý¾Ý(8×Ö½Ú)
- @retval ÎÞ
*/
void LCD_CreateChar(uint8_t location, uint8_t *charmap)
{
uint8_t i; // ÌáǰÉùÃ÷Ñ»•±äÁ¿
location &= 0x07; // È•±£Î»ÖÃÔÚ0-7•¶Î§ÄÚ
LCD_SendCommand(LCD_SETCGRAMADDR | (location << 3));
for (i = 0; i < 8; i++)
{
LCD_SendData(charmap[i]);
}
}
模块bsp_clkconfig.h
#ifndef __CLKCONFIG_H
#define __CLKCONFIG_H
#include “stm32f4xx.h”
void HSE_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q);
void HSI_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q);
void MCO1_GPIO_Config(void);
void MCO2_GPIO_Config(void);
#endif /* __CLKCONFIG_H */
模块bsp_clkconfig.c:
#include “./rcc/bsp_clkconfig.h”
#include “stm32f4xx_rcc.h”
/*
- ʹÓÃHSEʱ£¬ÉèÖÃϵͳʱÖӵIJ½Öè
- 1¡¢¿ªÆôHSE £¬²¢µÈ´ý HSE Îȶ¨
- 2¡¢ÉèÖà AHB¡¢APB2¡¢APB1µÄÔ¤•ÖÆµÒò×Ó
- 3¡¢ÉèÖÃPLLµÄʱÖÓÀ´Ô´
- ÉèÖÃVCOÊäÈëʱÖÓ •ÖÆµÒò×Ó m
- ÉèÖÃVCOÊä³öʱÖÓ ±¶ÆµÒò×Ó n
- ÉèÖÃPLLCLKʱÖÓ•ÖÆµÒò×Ó p
- ÉèÖÃOTG FS,SDIO,RNGʱÖÓ•ÖÆµÒò×Ó q
- 4¡¢¿ªÆôPLL£¬²¢µÈ´ýPLLÎȶ¨
- 5¡¢°ÑPLLCKÇл»ÎªÏµÍ³Ê±ÖÓSYSCLK
- 6¡¢¶ÁȡʱÖÓÇл»×´Ì¬Î»£¬È•±£PLLCLK±»Ñ¡ÎªÏµÍ³Ê±ÖÓ
*/
/*
-
m: VCOÊäÈëʱÖÓ •ÖÆµÒò×Ó£¬È¡Öµ2~63
-
n: VCOÊä³öʱÖÓ ±¶ÆµÒò×Ó£¬È¡Öµ192~432
-
p: PLLCLKʱÖÓ•ÖÆµÒò×Ó £¬È¡Öµ2£¬4£¬6£¬8
-
q: OTG FS,SDIO,RNGʱÖÓ•ÖÆµÒò×Ó£¬È¡Öµ4~15
-
º¯Êýµ÷ÓþÙÀý£¬Ê¹ÓÃHSEÉèÖÃʱÖÓ
-
SYSCLK=HCLK=168M,PCLK2=HCLK/2=84M,PCLK1=HCLK/4=42M
-
HSE_SetSysClock(25, 336, 2, 7);
-
HSE×÷ΪʱÖÓÀ´Ô´£¬¾¹ýPLL±¶Æµ×÷ΪϵͳʱÖÓ£¬ÕâÊÇͨ³£µÄ×ö•¨
-
ϵͳʱÖÓ³¬Æµµ½216MˬһÏÂ
-
HSE_SetSysClock(25, 432, 2, 9);
*/
void HSE_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q)
{
__IO uint32_t HSEStartUpStatus = 0;
// ʹÄÜHSE£¬¿ªÆôÍⲿ¾§Õñ£¬Ò°»ðF407ʹÓà HSE=25M
RCC_HSEConfig(RCC_HSE_ON);
// µÈ´ýHSEÆô¶¯Îȶ¨
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if (HSEStartUpStatus == SUCCESS)
{
// µ÷ѹÆ÷µçѹÊä³ö¼¶±ðÅäÖÃΪ1£¬ÒÔ±ãÔÚÆ÷¼þΪ×î´óƵÂÊ
// ¹¤×÷ʱʹÐÔÄܺ͹¦ºÄʵÏÖÆ½ºâ
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;
// HCLK = SYSCLK / 1
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// PCLK2 = HCLK / 2
RCC_PCLK2Config(RCC_HCLK_Div2);
// PCLK1 = HCLK / 4
RCC_PCLK1Config(RCC_HCLK_Div4);
// Èç¹ûÒª³¬Æµ¾ÍµÃÔÚÕâÀïÏÂÊÖÀ²
// ÉèÖÃPLLÀ´Ô´Ê±ÖÓ£¬ÉèÖÃVCO•ÖÆµÒò×Óm£¬ÉèÖÃVCO±¶ÆµÒò×Ón£¬
// ÉèÖÃϵͳʱÖÓ•ÖÆµÒò×Óp£¬ÉèÖÃOTG FS,SDIO,RNG•ÖÆµÒò×Óq
RCC_PLLConfig(RCC_PLLSource_HSE, m, n, p, q);
// ʹÄÜPLL
RCC_PLLCmd(ENABLE);
// µÈ´ý PLLÎȶ¨
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/-----------------------------------------------------/
// µ±PLLÎȶ¨Ö®ºó£¬°ÑPLLʱÖÓÇл»ÎªÏµÍ³Ê±ÖÓSYSCLK
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// ¶ÁȡʱÖÓÇл»×´Ì¬Î»£¬È•±£PLLCLK±»Ñ¡ÎªÏµÍ³Ê±ÖÓ
while (RCC_GetSYSCLKSource() != 0x08)
{
}
}
else
{ // HSEÆô¶¯³ö´í´¦Àí
while (1)
{
}
}
}
/*
- ʹÓÃHSIʱ£¬ÉèÖÃϵͳʱÖӵIJ½Öè
- 1¡¢¿ªÆôHSI £¬²¢µÈ´ý HSI Îȶ¨
- 2¡¢ÉèÖà AHB¡¢APB2¡¢APB1µÄÔ¤•ÖÆµÒò×Ó
- 3¡¢ÉèÖÃPLLµÄʱÖÓÀ´Ô´
- ÉèÖÃVCOÊäÈëʱÖÓ •ÖÆµÒò×Ó m
- ÉèÖÃVCOÊä³öʱÖÓ ±¶ÆµÒò×Ó n
- ÉèÖÃSYSCLKʱÖÓ•ÖÆµÒò×Ó p
- ÉèÖÃOTG FS,SDIO,RNGʱÖÓ•ÖÆµÒò×Ó q
- 4¡¢¿ªÆôPLL£¬²¢µÈ´ýPLLÎȶ¨
- 5¡¢°ÑPLLCKÇл»ÎªÏµÍ³Ê±ÖÓSYSCLK
- 6¡¢¶ÁȡʱÖÓÇл»×´Ì¬Î»£¬È•±£PLLCLK±»Ñ¡ÎªÏµÍ³Ê±ÖÓ
*/
/*
-
m: VCOÊäÈëʱÖÓ •ÖÆµÒò×Ó£¬È¡Öµ2~63
-
n: VCOÊä³öʱÖÓ ±¶ÆµÒò×Ó£¬È¡Öµ192~432
-
p: PLLCLKʱÖÓ•ÖÆµÒò×Ó £¬È¡Öµ2£¬4£¬6£¬8
-
q: OTG FS,SDIO,RNGʱÖÓ•ÖÆµÒò×Ó£¬È¡Öµ4~15
-
º¯Êýµ÷ÓþÙÀý£¬Ê¹ÓÃHSIÉèÖÃʱÖÓ
-
SYSCLK=HCLK=168M,PCLK2=HCLK/2=84M,PCLK1=HCLK/4=42M
-
HSI_SetSysClock(16, 336, 2, 7);
-
HSE×÷ΪʱÖÓÀ´Ô´£¬¾¹ýPLL±¶Æµ×÷ΪϵͳʱÖÓ£¬ÕâÊÇͨ³£µÄ×ö•¨
-
ϵͳʱÖÓ³¬Æµµ½216MˬһÏÂ
-
HSI_SetSysClock(16, 432, 2, 9);
*/
void HSI_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q)
{
__IO uint32_t HSIStartUpStatus = 0;
// °ÑRCCÍâÉè³õʼ»¯³É¸´Î»×´Ì¬
RCC_DeInit();
//ʹÄÜHSI, HSI=16M
RCC_HSICmd(ENABLE);
// µÈ´ý HSIÎȶ¨
while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET)
{
}
// ¶ÁÈ¡ HSI ¾ÍÐ÷״̬
HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;
// Ö»ÓÐ HSI¾ÍÐ÷Ö®ºóÔò¼ÌÐøÍùÏÂÖ´ÐÐ
if (HSIStartUpStatus == RCC_CR_HSIRDY)
{
// µ÷ѹÆ÷µçѹÊä³ö¼¶±ðÅäÖÃΪ1£¬ÒÔ±ãÔÚÆ÷¼þΪ×î´óƵÂÊ
// ¹¤×÷ʱʹÐÔÄܺ͹¦ºÄʵÏÖÆ½ºâ
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;
// HCLK = SYSCLK / 1
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// PCLK2 = HCLK / 2
RCC_PCLK2Config(RCC_HCLK_Div2);
// PCLK1 = HCLK / 4
RCC_PCLK1Config(RCC_HCLK_Div4);
// Èç¹ûÒª³¬Æµ¾ÍµÃÔÚÕâÀïÏÂÊÖÀ²
// ÉèÖÃPLLÀ´Ô´Ê±ÖÓ£¬ÉèÖÃVCO•ÖÆµÒò×Óm£¬ÉèÖÃVCO±¶ÆµÒò×Ón£¬
// ÉèÖÃϵͳʱÖÓ•ÖÆµÒò×Óp£¬ÉèÖÃOTG FS,SDIO,RNG•ÖÆµÒò×Óq
RCC_PLLConfig(RCC_PLLSource_HSI, m, n, p, q);
// ʹÄÜPLL
RCC_PLLCmd(ENABLE);
// µÈ´ý PLLÎȶ¨
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/-----------------------------------------------------/
//¿ªÆô OVER-RIDEģʽ£¬ÒÔÄÜ´ïµ½¸ü¸ßƵÂÊ(F429²ÅÓиù¦ÄÜ)
// PWR->CR |= PWR_CR_ODEN;
// while((PWR->CSR & PWR_CSR_ODRDY) == 0)
// {
// }
// PWR->CR |= PWR_CR_ODSWEN;
// while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
// {
// }
// ÅäÖÃFLASHԤȡָ,Ö¸Á´æ,Êý¾Ý»º´æºÍµÈ´ý״̬
FLASH->ACR = FLASH_ACR_PRFTEN
| FLASH_ACR_ICEN
|FLASH_ACR_DCEN
|FLASH_ACR_LATENCY_5WS;
/-----------------------------------------------------/
// µ±PLLÎȶ¨Ö®ºó£¬°ÑPLLʱÖÓÇл»ÎªÏµÍ³Ê±ÖÓSYSCLK
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// ¶ÁȡʱÖÓÇл»×´Ì¬Î»£¬È•±£PLLCLK±»Ñ¡ÎªÏµÍ³Ê±ÖÓ
while (RCC_GetSYSCLKSource() != 0x08)
{
}
}
else
{ // HSIÆô¶¯³ö´í´¦Àí
while (1)
{
}
}
}
// MCO1 PA8 GPIO ³õʼ»¯
void MCO1_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// MCO1 GPIO ÅäÖÃ
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
// MCO2 PC9 GPIO ³õʼ»¯
void MCO2_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
// MCO2 GPIO ÅäÖÃ
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
AI: 主要问题分析:
- I2C引脚配置问题
// 你的配置
#define I2Cx_SCL_PIN GPIO_Pin_5
#define I2Cx_SDA_PIN GPIO_Pin_7
// STM32F407 I2C1的标准引脚是:
// SCL - PB6 或 PB8
// SDA - PB7 或 PB9
建议修改:
#define I2Cx_SCL_PIN GPIO_Pin_6 // 改为标准引脚
#define I2Cx_SDA_PIN GPIO_Pin_7 // 这个正确
#define I2Cx_SCL_SOURCE GPIO_PinSource6
#define I2Cx_SDA_SOURCE GPIO_PinSource7
2. I2C地址问题
#define LCD_ADDR 0x3F << 1 // 左移1位
大多数I2C LCD模块地址是0x27或0x3F,不需要左移1位。
建议修改:
#define LCD_ADDR 0x3F // 去掉左移
// 或者在写入时处理地址:
// I2C_Send7bitAddress(I2Cx, LCD_ADDR << 1, I2C_Direction_Transmitter);
3. GPIO配置问题
c
复制
下载
GPIO_InitStructure.GPIO_Speed = GPIO_Low_Speed; // 速度太低
I2C通信需要适当的速度。
建议修改:
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
4. 时序问题
LCD初始化需要足够的延时:
void LCD_Init(void)
{
Delay_ms(50); // 确保有足够长的延时
// 4位模式初始化序列
LCD_SendCommand(0x03);
Delay_ms(5);
LCD_SendCommand(0x03);
Delay_ms(1);
LCD_SendCommand(0x03);
Delay_ms(1);
LCD_SendCommand(0x02); // 切换到4位模式
LCD_SendCommand(0x28); // 4位模式,2行,5x8点阵
// ... 其余命令
}
调试建议:
-
添加调试信息
在main函数中添加串口调试:
int main(void)
{
Debug_USART_Config();
printf(“程序启动…\r\n”);RCC_Configuration();
SysTick_Init();printf(“初始化I2C…\r\n”);
I2C_Configuration();printf(“初始化LCD…\r\n”);
LCD_Init(); // 在这里死机printf(“LCD初始化成功\r\n”);
// …
} -
检查硬件连接
• 确认I2C引脚连接正确
• 检查LCD的VCC、GND、SDA、SCL连接
• 确认背光控制线连接(如果有) -
简化测试代码
先测试最基本的I2C通信:
c
复制
下载
void test_I2C(void)
{
// 尝试发送一个简单的命令
uint8_t test_data = 0x00;
I2C_WriteByte(LCD_ADDR, test_data);// 检查I2C状态
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)) {
printf(“I2C应答失败\r\n”);
}
} -
修改I2C写入函数
static void I2C_WriteByte(uint8_t addr, uint8_t data)
{
// 等待I2C空闲
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
// 生成起始条件
I2C_GenerateSTART(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
// 发送地址(写模式)
I2C_Send7bitAddress(I2Cx, addr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送数据
I2C_SendData(I2Cx, data);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 生成停止条件
I2C_GenerateSTOP(I2Cx, ENABLE);
}
请先检查硬件连接和引脚配置,然后添加调试信息定位具体死机位置。如果还有问题,可以提供更多调试信息。
qqxjyxjy清风:按上述代码LCD_Init()函数任然死机。
AI: 回复
从您提供的代码来看,STM32F407通过I2C驱动1602 LCD在初始化时卡死的问题,可能有以下几个关键原因需要排查:
-
I2C硬件连接检查
• 确认PB6(SCL)和PB7(SDA)的物理连接正确
• 检查I2C地址0x3F是否与您的LCD模块匹配(常见还有0x27)
• 测量上拉电阻(通常4.7K欧姆)是否正常工作 -
时钟配置优化 在RCC_Configuration()中建议添加以下保护措施:
C复制
• 1
• 2
• 3
• 4
// 在PLL配置后添加时钟安全检查
if (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) != SET) {
while(1); // 时钟初始化失败
} -
SysTick定时器问题 您的SysTick配置为10us中断一次,但Delay_ms依赖于这个时基。建议:
C复制
• 1
• 2
// 修改bsp_SysTick.c中的初始化
SysTick_Config(SystemCoreClock / 1000); // 改为1ms时基 -
LCD初始化序列加强 修改LCD_Init()函数:
C21
void LCD_Init(void)
{
Delay_ms(50); // 延长初始等待时间// 4位初始化序列
LCD_SendCommand(0x03);
Delay_ms(5);
LCD_SendCommand(0x03);
Delay_ms(1);
LCD_SendCommand(0x03);
Delay_ms(1);// 切换4位模式
LCD_SendCommand(0x02);
Delay_ms(1);// 后续正常初始化
LCD_SendCommand(0x28);
Delay_ms(1);
// …其余保持不变
} -
I2C通信诊断 在I2C_WriteByte()函数中添加错误处理:
static void I2C_WriteByte(uint8_t addr, uint8_t data)
{
uint32_t timeout = 100000; // 超时计数器while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) && timeout–);
if(timeout == 0) { /* 处理超时 */ }// 每个步骤都添加类似的超时检测
// …
} -
电压电平检查
• 确认STM32和LCD模块的供电电压匹配(通常都是3.3V)
• 检查I2C线路上的电压幅值是否足够
建议的调试步骤: -
先用逻辑分析仪抓取I2C信号,观察是否有起始信号
-
单独测试I2C写函数是否能完成单字节传输
-
逐步缩短初始化流程,找到具体的卡死点
-
尝试降低I2C速度为50KHz测试
如果仍有问题,可以提供以下信息进一步诊断:
• 实际硬件连接照片
• 逻辑分析仪捕获的波形图
• 使用的具体LCD模块型号
• 供电电压测量值
这种问题通常是由硬件连接、时序配合或初始化序列不完整导致的,需要耐心逐步排查。
void I2C_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
// 使能I2C和GPIO时钟
RCC_APB1PeriphClockCmd(I2Cx_CLK, ENABLE);
RCC_AHB1PeriphClockCmd(I2Cx_SCL_GPIO_CLK | I2Cx_SDA_GPIO_CLK, ENABLE);
// 配置I2C引脚为复用功能
GPIO_InitStructure.GPIO_Pin = I2Cx_SCL_PIN | I2Cx_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed =GPIO_Low_Speed;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; // 开漏输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉
GPIO_Init(I2Cx_GPIO_PORT, &GPIO_InitStructure);
// 连接引脚到I2C外设
GPIO_PinAFConfig(I2Cx_GPIO_PORT, I2Cx_SCL_SOURCE, I2Cx_AF);
GPIO_PinAFConfig(I2Cx_GPIO_PORT, I2Cx_SDA_SOURCE, I2Cx_AF);
// 配置I2C
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 主机模式不需要自己的地址
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 50000; // 100kHz
I2C_Init(I2Cx, &I2C_InitStructure);
// 使能I2C
I2C_Cmd(I2Cx, ENABLE);
}
qqxjyxjy清风:再按上述程序运行,仍然在LCD_Init()函数处死机。
AI: 以下是针对 STM32F407ZGT6 的 1602 IIC 液晶显示器驱动程序。这个驱动使用 STM32 的硬件 I2C 接口来与 1602 液晶屏通信,包含了初始化、显示字符、清屏等基本功能。
STM32F407ZGT6的1602 IIC液晶显示器驱动程序
V1
创建时间:14:19
使用说明:
- 首先需要确保你的 STM32F407ZGT6 已经正确配置了 I2C1 外设,上述代码使用的是 I2C1。
- 根据你的硬件连接,可能需要修改i2c_1602.h中的LCD_ADDR宏定义,1602 IIC 液晶的地址通常是 0x4E 或 0x40,可以通过 I2C 扫描工具确定。
- 驱动程序功能说明:
o LCD_Init():初始化 LCD 显示器
o LCD_Clear():清屏操作
o LCD_SetCursor(row, col):设置光标位置,row 为 0 或 1(表示第 1 行或第 2 行),col 为 0-15(列位置)
o LCD_PrintString(str):显示字符串
o LCD_PrintChar©:显示单个字符
o LCD_CreateChar():创建自定义字符 - 在使用前,需要确保已经正确初始化了 I2C 外设,示例代码中的MX_I2C1_Init()提供了基本的初始化配置。
- 如果你的 I2C 引脚不是默认的,可以在HAL_I2C_MspInit()函数中修改 GPIO 配置。
请根据你的实际硬件连接情况调整 I2C 引脚和设备地址,确保能够正常通信。
qqxjyxjy清风:能用标准库编写吗?我对HAL库不太熟悉。
AI:好的,我可以为你提供一个使用 STM32 标准外设库(SPL)编写的 1602 IIC 液晶驱动程序,不依赖 HAL 库。这个驱动程序直接操作寄存器,适合不熟悉 HAL 库的开发者使用。
STM32F407ZGT6的1602 IIC液晶驱动程序(标准库版本)
V1
创建时间:14:21
使用说明:
- 硬件连接:
o 该驱动默认使用 I2C1,引脚为 PB6(SCL)和 PB7(SDA)
o 如果需要使用其他引脚,可以修改i2c_1602.h中的引脚定义 - 地址设置:
o 1602 IIC 液晶的地址通常是 0x4E 或 0x40,你可能需要根据实际模块修改LCD_ADDR宏定义
o 地址已经左移了 1 位,因为 I2C 协议要求地址格式如此 - 延时函数:
o 代码中使用了delay_ms()函数,你需要提供一个简单的延时函数库
o 可以使用 SysTick 定时器实现毫秒级延时 - 功能函数:
o LCD_Init():初始化 LCD
o LCD_Clear():清屏
o LCD_SetCursor(row, col):设置光标位置(row:0-1, col:0-15)
o LCD_PrintChar(ch):显示单个字符
o LCD_PrintString(str):显示字符串 - 使用步骤:
o 初始化系统时钟
o 初始化 I2C
o 初始化 LCD
o 调用显示函数输出内容
如果你的 1602 LCD 模块无法正常工作,可以先检查 I2C 地址是否正确,然后检查接线是否正确,最后检查系统时钟配置是否正确。
qqxjyxjy清风:按照提示修改地址后,再次运行,故障依旧。测得SCL电压为0.02V,SDA电压为4.2V.
AI: 从你提供的数据来看,SCL(时钟线)电压为 0.02V,SDA(数据线)电压为 4.2V,这种情况可能存在一些异常:
在正常的 I2C 通信总线中,SCL 和 SDA 通常会通过上拉电阻保持在高电平(一般接近电源电压,如 3.3V 或 5V)。当有设备拉低时,电压会降低。
可能的情况分析:
- SCL 线被某一设备持续拉低,导致电压异常偏低
- 通信总线存在故障,如短路或元件损坏
- 上拉电阻出现问题,导致 SCL 无法正常保持高电平
- 某个设备出现故障,持续拉低 SCL 线
建议检查相关电路连接、上拉电阻以及总线上的各个设备,找出导致 SCL 电压异常的原因。
编辑分享
拔掉液晶显示1602,SCL(时钟线)电压为 0.02V,SDA(数据线)电压为 3.25V,故障与液晶显示板无关。
既然拔掉 1602 液晶后,SCL 电压仍然保持在 0.02V 的异常低电平,说明故障点不在液晶显示板本身,可从以下方向进一步排查:
- 其他 I2C 设备故障
检查总线上连接的其他 I2C 设备(如传感器、模块等),可能某一设备内部短路或异常,导致持续拉低 SCL 线。可尝试逐一断开其他 I2C 设备,观察 SCL 电压是否恢复正常(正常应接近电源电压,如 3.3V 或 5V)。 - 主控芯片问题
若断开所有外部 I2C 设备后,SCL 仍为低电平,可能是主控芯片(如单片机)的 SCL 引脚内部短路或输出异常,导致持续输出低电平。 - 线路或上拉电阻故障
o 检查 SCL 线路是否与 GND 短路(可用万用表导通档测试)。
o 确认 SCL 线上的上拉电阻是否损坏或未正确连接(正常应通过上拉电阻接电源)。
通过逐步排除法,先断开所有外部设备排查主控问题,再逐个接入设备定位故障源,可更高效找到问题所在。
qqxjyxjy清风:拔掉液晶显示1602,SCL(时钟线)电压为 0.02V,SDA(数据线)电压为 3.25V,故障与液晶显示板无关。
AI: 既然拔掉 1602 液晶后,SCL 电压仍然保持在 0.02V 的异常低电平,说明故障点不在液晶显示板本身,可从以下方向进一步排查:
- 其他 I2C 设备故障
检查总线上连接的其他 I2C 设备(如传感器、模块等),可能某一设备内部短路或异常,导致持续拉低 SCL 线。可尝试逐一断开其他 I2C 设备,观察 SCL 电压是否恢复正常(正常应接近电源电压,如 3.3V 或 5V)。 - 主控芯片问题
若断开所有外部 I2C 设备后,SCL 仍为低电平,可能是主控芯片(如单片机)的 SCL 引脚内部短路或输出异常,导致持续输出低电平。 - 线路或上拉电阻故障
o 检查 SCL 线路是否与 GND 短路(可用万用表导通档测试)。
o 确认 SCL 线上的上拉电阻是否损坏或未正确连接(正常应通过上拉电阻接电源)。
通过逐步排除法,先断开所有外部设备排查主控问题,再逐个接入设备定位故障源,可更高效找到问题所在。
qqxjyxjy清风:调试时已过了 I2C_Configuration()初始化函数,SCL 电压仍然保持在 0.02V 的异常低电平,首当怀疑这个函数的问题。其它前面的函数几乎与PB6(SCL)和 PB7(SDA)无关。说明故障点在I2C_Configuration()初始化函数过程之中。
AI: 既然故障点锁定在I2C_Configuration()初始化函数过程中,说明 I2C 初始化配置未正常完成,导致 SCL 线持续被拉低。可从以下几个方向排查:
- I2C 模式与引脚配置错误
检查初始化函数中是否正确配置了 SCL/SDA 对应的 GPIO 引脚模式(应设置为复用开漏输出),若误配置为推挽输出且持续输出低电平,会导致 SCL 被强制拉低。
c
运行
// 错误示例(推挽输出可能导致持续低电平)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 正确配置(复用开漏才能实现线与逻辑)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
-
I2C 时钟频率配置异常
若初始化时配置的时钟频率过高(超出硬件支持范围),可能导致 I2C 模块启动失败并锁死 SCL 线。需确认时钟分频参数是否合理:
c
运行
// 例如:APB1时钟为36MHz时,配置100kHz标准模式
I2C_InitStructure.I2C_ClockSpeed = 100000;
I2C_InitStructure.I2C_Prescaler = 180; // 分频参数需与时钟频率匹配 -
I2C 外设使能失败
检查初始化流程中是否遗漏了 I2C 外设时钟使能或 GPIO 时钟使能:
c
运行
// 需确保先使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 假设SCL/SDA接GPIOB -
I2C 模块硬件故障
若上述配置均正确,可能是 MCU 的 I2C 外设硬件损坏,可尝试切换到其他 I2C 接口(如从 I2C1 换为 I2C2)重新测试,以排除硬件故障。
建议在初始化函数中分步添加调试断点,观察每一步配置后 SCL 电压的变化,准确定位哪一步操作导致了电压异常。
qqxjyxjy清风:将I2C_InitStructure.I2C_ClockSpeed = 100000;改为 I2C_InitStructure.I2C_ClockSpeed = 50000;后PB6(SCL)和PB7(SDA)同为低电平。看来这单片机体质堪忧.
AI: 从现象来看,调整 I2C 时钟频率后 SCL 和 SDA 同时被拉低,确实可能指向硬件层面的问题,可按以下思路进一步验证:
- 最小系统测试
剥离所有外部 I2C 设备,仅保留 MCU 最小系统,重新初始化 I2C 后测量引脚电压。若仍持续低电平,基本可确认是 MCU 的 I2C 外设损坏或内部电路异常。 - GPIO 独立测试
暂时跳过 I2C 初始化,直接将 PB6、PB7 配置为普通 GPIO 输出模式,手动控制高低电平:
c
运行
// 测试代码示例
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7); // 输出高电平
// 延时后测量电压
GPIO_ResetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7); // 输出低电平
若 GPIO 能正常输出高低电平,说明引脚物理层面没问题,问题仍集中在 I2C 外设模块。
3. 更换 I2C 接口
若 MCU 有多个 I2C 外设(如 I2C2),可尝试切换到备用接口,重新编写初始化代码测试。若备用接口能正常工作,则进一步印证原 I2C 接口损坏。
4. 降低驱动强度
部分 MCU 可配置 GPIO 驱动强度,尝试将对应引脚设置为低驱动模式,可能会缓解某些硬件异常导致的锁死现象。
如果上述测试均无法恢复 I2C 正常工作,大概率是 MCU 内部 I2C 模块损坏,这种情况下确实需要考虑更换芯片了。在更换前,也可尝试重新烧录不同版本的固件或复位芯片,排除软件配置残留的影响。
已剥离所有外部 I2C 设备。
在已剥离所有外部 I2C 设备的情况下,仍出现 SCL/SDA 异常低电平,可按以下步骤进一步定位:
- 测试 GPIO 基础功能
如前所述,将 PB6、PB7 配置为普通推挽输出,手动切换高低电平并测量电压:
o 若能正常输出 3.3V(或电源电压)和 0V,说明引脚物理通路正常,问题锁定在 I2C 外设模块。
o 若始终为低电平,可能是引脚内部短路或 GPIO 模块损坏。 - 简化 I2C 初始化流程
编写最精简的初始化代码,仅包含时钟使能和基础配置:
c
运行
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// 配置为复用开漏
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; // 降低速率
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 仅初始化基本参数
I2C_DeInit(I2C1);
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 不设置从地址
I2C_InitStructure.I2C_Ack = I2C_Ack_Disable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 10000; // 极低频率测试
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
初始化后立即测量电压,若仍为低电平,说明 I2C 外设初始化过程中出现异常锁死。
3. 检测外设复位状态
尝试在初始化前对 I2C 外设进行强制复位:
c
运行
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE);
delay_ms(10); // 短暂延时
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, DISABLE);
若复位后仍无改善,硬件损坏的可能性极高。
4. 更换芯片验证
若有备用 MCU,可更换后使用相同代码测试。若新芯片能正常初始化(SCL/SDA 电压接近电源电压),则可确认原芯片 I2C 模块损坏。
这种情况下,若软件层面已排除所有可能的配置错误,基本可以判定是 MCU 硬件问题,建议更换芯片后再进行测试。
编辑分享
qqxjyxjy清风:好像应将此句RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); 改为 RCC_AHB1PeriphClockCmd(I2Cx_SCL_GPIO_CLK | I2Cx_SDA_GPIO_CLK, ENABLE);
#include “stm32f4xx.h”
#include “i2c_1602.h”
//#include “delay.h” // 需要延时函数库
// 函数声明
void RCC_Configuration(void);
void GPIO_Configuration(void);
void SysTick_Configuration(void);
int main(void)
{
// 初始化系统时钟
RCC_Configuration();
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | //RCC_APB2Periph_AFIO, ENABLE); // 将此注释掉词句改为下句
RCC_AHB1PeriphClockCmd(I2Cx_SCL_GPIO_CLK | I2Cx_SDA_GPIO_CLK, ENABLE);
// 配置为复用开漏
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; // 降低速率
GPIO_Init(GPIOB, &GPIO_InitStructure); // 仅初始化基本参数
I2C_DeInit(I2C1);
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 不设置从地址
I2C_InitStructure.I2C_Ack = I2C_Ack_Disable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 10000; // 极低频率测试
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
// SysTick_Init(); //
// 初始化SysTick定时器用于延时
// // SysTick_Configuration(); // //
// 初始化I2C // I2C_Configuration();
// Delay_ms(2000); //
}
AI: 从代码来看,你针对 STM32F4 系列调整了 I2C 初始化的部分配置,特别是时钟使能部分改用了RCC_AHB1PeriphClockCmd,这符合 F4 系列的时钟树特性(GPIO 时钟挂在 AHB1 总线上)。不过有几个细节可以进一步完善:
-
AI: 宏定义补充
代码中使用了I2Cx_SCL_GPIO_CLK和I2Cx_SDA_GPIO_CLK,需要确保这些宏已正确定义,例如:
c
运行
#define I2Cx_SCL_GPIO_CLK RCC_AHB1Periph_GPIOB
#define I2Cx_SDA_GPIO_CLK RCC_AHB1Periph_GPIOB
#define I2Cx I2C1 -
复用功能映射
STM32F4 的 GPIO 复用需要显式配置复用映射,否则引脚可能无法正确关联到 I2C 外设:
c
运行
// 配置PB6和PB7复用为I2C1
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1); // SCL
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1); // SDA -
GPIO 速度参数
F4 系列的 GPIO 速度定义与 F1 不同,应使用GPIO_Speed_50MHz而非GPIO_Speed_2MHz(F4 没有 2MHz 选项):
c
运行
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; -
初始化流程整理
建议将 I2C 初始化封装为独立函数,保持 main 函数简洁:
STM32F4 I2C初始化代码
V1
创建时间:09-12 13:17
修改后建议分步测试:
- 只保留 I2C 初始化,不连接任何外设,测量 SCL/SDA 电压是否恢复正常(应接近 3.3V)
- 若电压正常,逐步添加 LCD 初始化代码
- 若仍异常,可尝试:
o 更换 I2C 引脚(如 PB8/PB9 对应 I2C1 的备选引脚)
o 检查芯片是否损坏(可尝试其他外设功能验证 MCU 基本工作状态)
这种逐步验证的方式能更精准地定位问题根源。
qqxjyxjy清风:我将主程序中的函数管1602 II初始化部分注释掉后,PB6和PB7电压为5V,接上了1602 IIC液晶显示板,问题出在注释掉部分。
qqxjyxjy清风;#include “stm32f4xx.h” #include “i2c_1602.h” //#include “delay.h” // 需要延时函数库 // 函数声明 void RCC_Configuration(void); void GPIO_Configuration(void); void SysTick_Configuration(void); int main(void) { // 初始化系统时钟 RCC_Configuration(); //GPIO_InitTypeDef GPIO_InitStructure; //I2C_InitTypeDef I2C_InitStructure; // //RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); ////RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); // RCC_AHB1PeriphClockCmd(I2Cx_SCL_GPIO_CLK | I2Cx_SDA_GPIO_CLK, ENABLE); //// 配置为复用开漏 //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; // 降低速率 //GPIO_Init(GPIOB, &GPIO_InitStructure); //// 仅初始化基本参数 //I2C_DeInit(I2C1); //I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 不设置从地址 //I2C_InitStructure.I2C_Ack = I2C_Ack_Disable; //I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //I2C_InitStructure.I2C_ClockSpeed = 100000; // 极低频率测试 //I2C_Init(I2C1, &I2C_InitStructure); //I2C_Cmd(I2C1, ENABLE); // SysTick_Init(); // // // 初始化SysTick定时器用于延时 // // SysTick_Configuration(); // // // 初始化I2C // I2C_Configuration(); // Delay_ms(2000); // // 初始化LCD // LCD_Init(); // // // 在第一行显示"STM32F407" // LCD_SetCursor(0, 0); // LCD_PrintString(“STM32F407”); // // // 在第二行显示"I2C 1602 LCD" // LCD_SetCursor(1, 0); // LCD_PrintString(“I2C 1602 LCD”); while (1) { // 主循环:每2秒切换显示内容 // Delay_ms(2000); // // LCD_Clear(); // LCD_SetCursor(0, 0); // LCD_PrintString(“Standard Lib”); // LCD_SetCursor(1, 0); // LCD_PrintString(“I2C Example”); // // Delay_ms(2000); // LCD_Clear(); // LCD_SetCursor(0, 0); // LCD_PrintString(“STM32F407”); // LCD_SetCursor(1, 0); // LCD_PrintString(“I2C 1602 LCD”); } } void RCC_Configuration(void) { // 启用外部高速时钟 RCC_HSEConfig(RCC_HSE_ON); // 等待HSE稳定 while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); // 配置PLL RCC_PLLConfig(RCC_PLLSource_HSE, 8, 336, 2, 7); // 168MHz系统时钟 RCC_PLLCmd(ENABLE); // 等待PLL稳定 while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); // 配置系统时钟源为PLL RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 配置AHB时钟 (HCLK = SYSCLK) RCC_HCLKConfig(RCC_SYSCLK_Div1); // 配置APB1时钟 (PCLK1 = HCLK/4) RCC_PCLK1Config(RCC_HCLK_Div4); // 配置APB2时钟 (PCLK2 = HCLK/2) RCC_PCLK2Config(RCC_HCLK_Div2); }
AI: 从现象来看,当注释掉 I2C 相关代码后,PB6 和 PB7 电压恢复正常(5V),说明问题确实出在 I2C 初始化配置部分。结合 STM32F4 的特性,主要问题可能在以下几点:
- GPIO 模式配置错误
之前代码中使用了GPIO_Mode_AF(仅复用功能),但 I2C 需要复用开漏输出(GPIO_Mode_AF_OD)才能实现线与逻辑,否则引脚可能被异常拉低。 - 缺少复用功能映射
未调用GPIO_PinAFConfig将 PB6/PB7 映射到 I2C1 外设,导致引脚功能未正确关联。 - 内部上拉电阻未启用
若电路中没有外部上拉电阻,需开启 GPIO 内部上拉,否则引脚可能处于悬浮状态。
以下是修正后的 I2C 初始化代码,可替换注释部分进行测试:
修正后的STM32F4 I2C初始化代码
V1
创建时间:09-12 22:00
关键修正点说明:
- 增加了GPIO_PinAFConfig配置,确保引脚正确映射到 I2C1 功能
- 将GPIO_Mode_AF改为GPIO_Mode_AF_OD(复用开漏),这是 I2C 通信的必要配置
- 启用内部上拉电阻(GPIO_PuPd_UP),避免引脚悬浮
- 调整 I2C 时钟为 50kHz,降低速率提高稳定性
- 使能应答功能(I2C_Ack_Enable),符合 1602 液晶的通信要求
测试建议:
- 先不接 1602 液晶,烧录代码后测量 PB6/PB7 电压,应接近电源电压(3.3V 或 5V,取决于系统)
- 若电压正常,再连接 1602 液晶测试通信
- 若仍有问题,可尝试在 I2C 初始化后添加 100ms 延时,再调用 LCD 初始化函数
这些修改针对 I2C 通信的底层硬件配置,解决了因模式错误导致的引脚被异常拉低问题。
// 取消注释并替换为以下代码
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
// 宏定义补充(需添加)
#define I2Cx_SCL_GPIO_CLK RCC_AHB1Periph_GPIOB
#define I2Cx_SDA_GPIO_CLK RCC_AHB1Periph_GPIOB
// 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_AHB1PeriphClockCmd(I2Cx_SCL_GPIO_CLK | I2Cx_SDA_GPIO_CLK, ENABLE);
// 配置复用映射(关键步骤)
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1); // PB6 -> I2C1_SCL
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1); // PB7 -> I2C1_SDA
// 配置GPIO为复用开漏模式并启用内部上拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏(关键修正)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // F4系列正确速率
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 启用内部上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 初始化I2C参数
I2C_DeInit(I2C1);
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 主机模式无需地址
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // 使能应答(适合1602通信)
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 50000; // 50kHz低速更稳定
I2C_Init(I2C1, &I2C_InitStructure);
// 使能I2C外设
I2C_Cmd(I2C1, ENABLE);
qqxjyxjy清风:将I2C_InitStructure.I2C_ClockSpeed = 100000;改为 I2C_InitStructure.I2C_ClockSpeed = 50000;后PB6(SCL)和PB7(SDA)同为低电平。看来这单片机体质堪忧。
qqxjyxjy清风:GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; 这句出错,STM32F407ZG没有 GPIO_Mode_AF_OD定义。
AI: 在 STM32F4 系列的标准库中,GPIO 模式的定义与 F1 系列不同,确实没有GPIO_Mode_AF_OD这个宏。F4 系列将模式和输出类型分开配置,需要通过两个参数组合实现复用开漏功能:
- 模式配置为复用功能(GPIO_Mode_AF)
- 输出类型配置为开漏(GPIO_OType_OD)
以下是针对 F4 系列修正后的 GPIO 配置代码:
STM32F4系列复用开漏模式配置
V1
创建时间:09-12 22:06
关键差异说明:
• STM32F1 系列:模式宏直接包含输出类型(如GPIO_Mode_AF_OD)
• STM32F4 系列:模式(Mode)和输出类型(OType)是分开的两个配置项
完整 I2C 初始化流程补充:
确保同时保留复用映射和 I2C 参数配置:
c
运行
// 复用映射配置(必须保留)
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1); // PB6 -> I2C1_SCL
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1); // PB7 -> I2C1_SDA
// I2C参数配置(保持之前的正确设置)
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
// … 其他I2C配置参数
这种配置方式符合 F4 系列的标准库规范,能够正确实现 I2C 通信所需的复用开漏功能,避免引脚被异常拉低。修改后重新测试,应能看到 SCL/SDA 电压恢复正常。
qqxjyxjy清风:修改后,PB6和PB7的电压均为4.2V,我再把1602 IIC驱动加上试试。
AI: 很好,电压恢复到 4.2V 说明 I2C 引脚的基础配置已经正常了。在添加 1602 IIC 驱动时,建议注意以下几点,以确保通信顺利:
-
确认 1602 的 I2C 地址
大多数 I2C 转接板的默认地址是0x4E或0x48(7 位地址),可在i2c_1602.h中定义:
c
运行
#define LCD_ADDR 0x4E // 或0x48,根据实际硬件调整 -
初始化时序延迟
在 I2C 初始化后,给 LCD 留出足够的上电时间:
c
运行
// I2C初始化完成后
Delay_ms(50); // 延时确保LCD稳定
LCD_Init(); // 再调用LCD初始化 -
I2C 写函数兼容性
确保i2c_1602.c中的 I2C 通信函数与 F4 的 I2C 库匹配,特别是发送函数:
c
运行
// 示例:向LCD发送数据的I2C函数
void I2C_WriteByte(uint8_t addr, uint8_t data)
{
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);
}
- 调试建议
o 先测试简单功能(如清屏、设置光标),再逐步添加显示字符串功能
o 若显示异常,可通过逻辑分析仪抓取 I2C 时序,或用串口打印调试信息
o 若出现电压再次拉低,可能是 LCD 模块或接线问题,可单独测量模块的 I2C 引脚是否短路
按照这个流程添加驱动后,正常情况下 1602 应该能正确显示内容了。如果遇到具体错误,可根据现象进一步排查时序或地址问题。
qqxjyxjy清风:运行到LCD_lnit()初始化函数死机,初始化函数LCD_lnit()如下:
void LCD_Init(void)
{
// 初始化延迟
Delay_ms(50);
// 初始化序列
LCD_SendCommand(0x02); // 回到初始位置
LCD_SendCommand(0x28); // 4位模式,2行显示,5x8点阵
LCD_SendCommand(0x0C); // 显示开,光标关,闪烁关
LCD_SendCommand(0x06); // 光标自动右移,屏幕不移动
LCD_SendCommand(0x01); // 清屏
Delay_ms(2);
}
AI: 从现象来看,调用LCD_Init()后 SCL 被拉低、SDA 保持高电平,说明 I2C 通信在发送 LCD 初始化指令时 “卡住” 了,问题大概率出在I2C 写指令的时序逻辑或LCD 模块的通信响应上,可按以下步骤排查:
一、优先排查:I2C 写指令函数(LCD_SendCommand)的时序问题
LCD_Init()的核心是通过LCD_SendCommand向 1602 发送指令,若该函数的 I2C 时序不完整(比如缺少 START/STOP 信号、未等待 ACK 应答),会导致 SCL 被持续拉低。需重点检查LCD_SendCommand的实现,确保符合 I2C 主机发送数据的标准流程:
标准的 I2C 发送指令函数模板(适用于 STM32F4):
c
运行
// 假设1602的I2C地址为0x4E(7位地址,需左移1位+写标志,即0x9C)
#define LCD_I2C_ADDR 0x4E << 1 // 关键:I2C发送地址时需左移1位,最低位为读写标志(0=写)
// 向LCD发送指令(cmd)
void LCD_SendCommand(uint8_t cmd)
{
// 1. 发送I2C START信号
I2C_GenerateSTART(I2C1, ENABLE);
// 等待START发送完成(事件:EV5)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 2. 发送1602的I2C地址(写模式)
I2C_Send7bitAddress(I2C1, LCD_I2C_ADDR, I2C_Direction_Transmitter);
// 等待地址被ACK(事件:EV6)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 3. 发送指令(1602的I2C转接板通常需加“控制位”,比如0x00=指令,0x01=数据)
// 注:不同1602转接板的控制位可能不同,常见为“高4位=数据/指令,低4位=使能”,需确认硬件
I2C_SendData(I2C1, 0x00); // 控制位:0x00表示发送的是“指令”(关键!若漏写会导致1602不响应)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 等待数据发送完成
I2C_SendData(I2C1, cmd); // 发送实际指令(如0x02、0x28等)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 等待指令发送完成
// 4. 发送I2C STOP信号(必须!否则I2C总线会保持忙状态,SCL被拉低)
I2C_GenerateSTOP(I2C1, ENABLE);
Delay_us(50); // 短暂延时,确保STOP信号稳定
}
最容易出错的 3 个点:
- I2C 地址未左移:7 位地址必须左移 1 位(LCD_I2C_ADDR = 0x4E << 1),最低位用于区分读写(0 = 写,1 = 读),若直接用 0x4E 发送,1602 会无法识别地址,导致无 ACK,SCL 卡死。
- 缺少 “控制位”:1602 的 I2C 转接板(如 PCF8574 芯片)通常需要先发送 1 字节 “控制位”(告知 1602 是指令还是数据),若漏写控制位,1602 不会响应,I2C 会因等待 ACK 超时卡死。
- 未发送 STOP 信号:每发送 1 次指令 / 数据后,必须发送 STOP 信号释放 I2C 总线,否则 SCL 会被持续拉低。
二、其次排查:1602 I2C 转接板的硬件匹配问题
若LCD_SendCommand时序没问题,需确认 1602 转接板的硬件参数是否与代码匹配: - 确认 1602 的 I2C 地址(最常见错误)
不同品牌的 1602 I2C 转接板地址可能不同,常见地址为0x48(0x90 左移后)或0x4E(0x9C 左移后)。若地址错误,1602 不会应答,导致 I2C 主机卡在 “等待 ACK” 步骤,SCL 拉低。
• 排查方法:用 “I2C 地址扫描函数” 遍历总线,确认 1602 的实际地址:
c
运行
// I2C地址扫描函数(遍历0x00~0xFF,打印有响应的地址)
void I2C_Scan(void)
{
uint8_t addr;
printf(“I2C Scan Start…\r\n”);
for(addr=0; addr<0xFF; addr++)
{
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
Delay_us(100); // 短暂等待应答
// 若地址被ACK(EV6事件触发),说明该地址有设备
if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
printf("Found I2C Device at Addr: 0x%02X\r\n", addr >> 1); // 右移1位得到7位地址
}
I2C_GenerateSTOP(I2C1, ENABLE);
Delay_ms(10);
}
printf("I2C Scan End\r\n");
}
在main函数中先调用I2C_Scan(),通过串口打印确认 1602 的实际地址,再修改LCD_I2C_ADDR。
2. 确认 1602 转接板的 “控制位” 格式
1602 的 I2C 转接板(如基于 PCF8574)的控制位定义不同,常见有两种:
• 格式 1:控制字节 = 0x00(指令)、0x01(数据)(如上述代码);
• 格式 2:控制字节 = 0x80(指令)、0xC0(数据)(部分转接板需拉高 “使能位”)。
若控制位错误,1602 无法识别指令,导致通信卡死。
• 排查方法:查阅 1602 转接板的 datasheet,或尝试更换控制位(如把0x00改为0x80)。
三、最后排查:I2C 外设的基础配置(防止遗漏)
若上述两步无问题,需再次确认 I2C 初始化的基础配置,避免因底层配置导致通信异常:
- I2C 时钟频率匹配:代码中I2C_ClockSpeed设为 50000(50kHz),确保 1602 转接板支持该速率(大多数支持 100kHz 以下);
- ACK 使能:I2C 初始化时需开启 ACK(I2C_InitStructure.I2C_Ack = I2C_Ack_Enable),若关闭 ACK,主机不会等待从机应答,可能导致时序混乱;
- 延时足够:LCD_Init()中的Delay_ms(50)和Delay_ms(2)需确保实际延时有效(比如基于 SysTick 的Delay_ms是否正确初始化),若延时过短,1602 未准备好会不响应。
排查流程总结 - 先验证LCD_SendCommand的时序(重点:START / 地址 / 控制位 / STOP 是否完整);
- 用I2C_Scan()确认 1602 的实际 I2C 地址,修正LCD_I2C_ADDR;
- 确认 1602 转接板的控制位格式,调整I2C_SendData(I2C1, 0x00)中的控制字节;
- 若仍死机,用逻辑分析仪抓取 I2C 总线波形,观察是否有 START/STOP 信号、地址是否被 ACK。
按这个流程排查后,通常能解决 “初始化 LCD 时 SCL 拉低死机” 的问题,核心是确保 I2C 通信的每一步时序都完整且与 1602 硬件匹配。
这是程序中的void LCD_SendCommand(uint8_t cmd) { uint8_t data_u, data_l; uint8_t data_u_tx, data_l_tx; data_u = (cmd & 0xF0); data_l = ((cmd << 4) & 0xF0); // 高四位数据 + 控制位 (RS=0, RW=0, EN=1) data_u_tx = data_u | 0x0C; // 高四位数据 + 控制位 (RS=0, RW=0, EN=0) data_l_tx = data_u | 0x08; I2C_WriteByte(LCD_ADDR, data_u_tx); Delay_ms(1); I2C_WriteByte(LCD_ADDR, data_l_tx); Delay_ms(1); // 低四位数据 + 控制位 (RS=0, RW=0, EN=1) data_u_tx = data_l | 0x0C; // 低四位数据 + 控制位 (RS=0, RW=0, EN=0) data_l_tx = data_l | 0x08; I2C_WriteByte(LCD_ADDR, data_u_tx); Delay_ms(1); I2C_WriteByte(LCD_ADDR, data_l_tx); Delay_ms(1); } 函数。
从你提供的LCD_SendCommand函数来看,代码逻辑是基于4 位通信模式的 1602 I2C 转接板(通过 PCF8574 扩展,控制位包含 RS/RW/EN),但存在两个关键问题可能导致 SCL 拉低死机,需针对性修正:
一、核心问题 1:I2C_WriteByte函数未发送STOP 信号,导致 I2C 总线卡死
函数中每次调用I2C_WriteByte(LCD_ADDR, …)后,若该函数仅发送了 START + 地址 + 数据,却未发送STOP 信号,会导致 I2C 总线始终处于 “忙” 状态,SCL 被持续拉低。这是当前死机的最主要原因。
修正方案:确保I2C_WriteByte包含完整的 I2C 时序(START→地址→数据→STOP)
c
运行
// 完整的I2C写字节函数(适用于STM32F4,需包含STOP信号)
#define LCD_I2C_ADDR 0x4E << 1 // 替换为你的1602实际地址(左移1位,写模式)
void I2C_WriteByte(uint8_t addr, uint8_t data)
{
// 1. 发送START信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 等待START完成(EV5)
// 2. 发送1602的I2C地址(写模式)
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 等待ACK(EV6)
// 3. 发送数据(控制位+4位指令)
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 等待数据发送完成(EV8_2)
// 4. 发送STOP信号(关键!释放I2C总线,避免SCL拉低)
I2C_GenerateSTOP(I2C1, ENABLE);
Delay_us(50); // 短暂延时,确保STOP信号稳定
}
关键说明:
• 原I2C_WriteByte若缺少I2C_GenerateSTOP,每次写操作后总线不会释放,后续操作会因总线忙而卡死,表现为 SCL 持续低电平。
• 地址必须左移 1 位(0x4E << 1):I2C 协议中,7 位地址需左移 1 位,最低位表示 “读写”(0 = 写,1 = 读),直接用 7 位地址发送会导致 1602 无法应答。
二、次要问题 2:1602 控制位(EN 引脚)的时序可能不匹配
你的代码中,控制位定义为:
• 0x0C:对应二进制00001100 → 推测EN=1(使能有效)、RS=0(指令模式)、RW=0(写模式);
• 0x08:对应二进制00001000 → 推测EN=0(使能无效)、RS=0、RW=0。
这符合多数 PCF8574 转接板的控制逻辑,但需确认EN 引脚的电平变化是否能触发 1602 的指令接收(1602 需要 EN 从 1→0 的 “下降沿” 来锁存指令)。当前代码中EN=1(0x0C)和EN=0(0x08)的延时仅 1ms,通常足够,但需注意:
• 若 1602 仍不响应,可适当延长Delay_ms(1)为Delay_ms(2),确保指令被稳定锁存;
• 若控制位定义错误(比如 EN 引脚对应其他位),需查阅转接板的硬件手册(如 PCF8574 的引脚与 1602 的连接关系),修正0x0C和0x08的值。
三、排查步骤(按优先级)
- 优先修正I2C_WriteByte函数,确保包含 STOP 信号和地址左移,这是解决 SCL 拉低的核心;
- 调用I2C_Scan()函数(参考上一轮回复),确认 1602 的实际 I2C 地址,修正LCD_I2C_ADDR(比如若扫描到地址0x90,则LCD_I2C_ADDR=0x90);
- 若修正后仍死机,在LCD_SendCommand的每一步I2C_WriteByte后,用串口打印调试信息(如 “发送高四位完成”),定位具体哪一步卡死;
- 若 1602 仍无显示,用万用表测量转接板的 VCC 和 GND 是否正常(确保供电 5V 或 3.3V 匹配),排除硬件供电问题。
修正后的完整LCD_SendCommand+I2C_WriteByte示例
c
运行
// 1. 先定义正确的I2C地址(需通过I2C_Scan确认)
#define LCD_I2C_ADDR 0x90 // 假设扫描到的地址为0x90(已左移1位,写模式)
// 2. 完整的I2C写字节函数(含STOP)
void I2C_WriteByte(uint8_t addr, uint8_t data)
{
// START
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送地址
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送数据
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// STOP(关键)
I2C_GenerateSTOP(I2C1, ENABLE);
Delay_us(50);
}
// 3. 修正后的LCD发送指令函数
void LCD_SendCommand(uint8_t cmd)
{
uint8_t data_u, data_l;
uint8_t data_u_tx, data_l_tx;
data_u = (cmd & 0xF0); // 提取指令高4位
data_l = ((cmd << 4) & 0xF0); // 提取指令低4位(左移4位,与高4位对齐)
// 高4位:EN=1(有效),RS=0,RW=0
data_u_tx = data_u | 0x0C;
I2C_WriteByte(LCD_I2C_ADDR, data_u_tx);
Delay_ms(2); // 延长延时,确保EN=1稳定
// 高4位:EN=0(无效,下降沿锁存)
data_l_tx = data_u | 0x08;
I2C_WriteByte(LCD_I2C_ADDR, data_l_tx);
Delay_ms(2);
// 低4位:EN=1(有效)
data_u_tx = data_l | 0x0C;
I2C_WriteByte(LCD_I2C_ADDR, data_u_tx);
Delay_ms(2);
// 低4位:EN=0(无效,下降沿锁存)
data_l_tx = data_l | 0x08;
I2C_WriteByte(LCD_I2C_ADDR, data_l_tx);
Delay_ms(2);
}
按上述修正后,重新测试LCD_Init(),SCL 应不会再持续拉低,1602 大概率能正常初始化并显示内容。若仍有问题,可重点排查 I2C 地址是否匹配、转接板硬件连接是否正确。
qqxjyxjy清风: 现已能进入了LED初始化函数,在其子在函数 while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));语句中死机。
AI: 卡在while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));,本质是I2C 主机发送地址后,未收到从机(1602)的 ACK 应答,导致程序无限等待。结合之前的排查,核心原因集中在「地址不匹配」「硬件连接 / 供电」「总线时序」三类问题,按优先级排查如下:
一、最核心原因:1602 的 I2C 地址错误(90% 概率)
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED事件的触发条件是:主机发送 7 位地址后,从机返回 ACK(低电平)。若地址错误,从机不会发送 ACK,程序会卡死在该循环。
排查 & 解决步骤:
-
明确 1602 I2C 地址的格式差异
你的代码中addr参数需满足:7 位地址左移 1 位 + 最低位 0(写模式)(即addr = 7位地址 << 1)。
常见 1602 I2C 转接板(PCF8574 芯片)的 7 位地址为0x27或0x3F,对应写地址为:
o 0x27 << 1 = 0x4E
o 0x3F << 1 = 0x7E
若你当前用的LCD_ADDR是0x27或0x3F(未左移),或地址值错误(如0x48),必然收不到 ACK。 -
用「I2C 地址扫描函数」精准确认地址
直接通过代码扫描总线,打印 1602 的实际地址(无需猜测),代码如下:
c
运行
// 在main函数中,I2C初始化后调用该函数
void I2C_Scan_Devices(void)
{
uint8_t dev_addr;
printf(“I2C Bus Scan Start…\r\n”);for(dev_addr = 0x02; dev_addr < 0xFF; dev_addr += 2) // 只扫写地址(最低位0)
{
// 等待总线空闲
while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));// 发送START I2C_GenerateSTART(I2Cx, ENABLE); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)); // 尝试发送当前地址 I2C_Send7bitAddress(I2Cx, dev_addr, I2C_Direction_Transmitter); Delay_us(100); // 给从机反应时间 // 检查是否收到ACK(若收到,说明该地址有设备) if (I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { printf("Found I2C Device: Write Addr = 0x%02X (7bit Addr = 0x%02X)\r\n", dev_addr, dev_addr >> 1); // 右移1位得到7位地址 // 发送STOP释放总线 I2C_GenerateSTOP(I2Cx, ENABLE); return; // 找到后退出扫描 } // 未收到ACK,发送STOP I2C_GenerateSTOP(I2Cx, ENABLE); Delay_ms(5);
}
printf(“No I2C Device Found!\r\n”);
}
o 需确保串口调试(printf)已正常工作,通过串口助手查看扫描结果;
o 扫描到的Write Addr(如0x4E)就是LCD_SendCommand中应传入的addr值。
二、次核心原因:1602 硬件连接 / 供电异常(8% 概率)
若地址正确仍无 ACK,需排查硬件层面是否让 1602 无法响应:
-
供电电压不匹配
• 多数 1602 液晶屏(非宽压版)需5V 供电,若误接 3.3V,会导致 1602 工作异常,无法发送 ACK;
• 检查转接板的 VCC 引脚:若标注 “VCC 5V”,必须接 5V(可从 STM32 开发板的 5V 引脚取电),GND 需与 STM32 共地(否则会有电平差,通信失败)。 -
SDA/SCL 引脚接反
• 若 PB6(SCL)和 PB7(SDA)接反,1602 无法解析地址信号,自然不会发送 ACK;
• 确认转接板的 SDA/SCL 引脚:通常转接板标注 “SDA”“SCL”,需与 STM32 的 PB7(SDA)、PB6(SCL)一一对应,不可交叉。 -
外部上拉电阻缺失(部分情况)
• STM32 内部上拉(GPIO_PuPd_UP)的驱动能力较弱,若 1602 与 STM32 的连线较长(>10cm),可能导致信号衰减,1602 收不到地址;
• 临时解决方案:在 PB6(SCL)、PB7(SDA)引脚分别串联一个4.7kΩ~10kΩ的上拉电阻到 3.3V(或 5V,需与 GPIO 电平匹配),增强信号驱动能力。
三、低概率原因:I2C 时序 / 配置异常(2% 概率)
若地址和硬件都没问题,需确认 I2C 初始化配置是否兼容 1602 的通信要求: -
I2C 时钟频率过高
• 1602 I2C 转接板(PCF8574)的最大支持速率通常为100kHz(标准模式),若代码中I2C_ClockSpeed设为 400kHz(快速模式),可能导致从机无法同步,收不到地址;
• 确认 I2C 初始化代码:
c
运行
I2C_InitStructure.I2C_ClockSpeed = 100000; // 必须设为100kHz及以下
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 标准模式下用2即可 -
I2C_OWNAddress1 配置错误
• 主机模式下,I2C_OWNAddress1(自身地址)可设为 0x00,但需确保未启用 “从机模式”;
• 确认初始化代码:
c
运行
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 主机无需自身地址
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // 必须启用ACK(否则主机不检测从机应答)
排查流程总结(按顺序执行)
- 调用I2C_Scan_Devices(),通过串口确认 1602 的实际写地址,替换代码中的LCD_ADDR;
- 检查 1602 供电(确保 5V)、GND 共地、SDA/SCL 引脚未接反;
- 若仍无 ACK,添加外部上拉电阻(4.7kΩ)到 SDA/SCL 引脚;
- 确认 I2C 时钟频率≤100kHz、I2C_Ack已启用。
按此流程操作后,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED事件会正常触发,程序不会再卡死,1602 初始化也能顺利完成。
qqxjyxjy清风:谢谢了,辛苦你了,今天太晚了,明天接着调。
AI: 不客气!熬夜调试容易遗漏细节,好好休息一晚,明天思路会更清晰~
如果明天扫描地址、排查硬件后还有小问题,比如地址确认了但仍无响应,或者显示乱码,都可以再一起梳理。重点先放在「地址匹配」和「硬件接线」上,这两步解决了,后续的显示问题会更易定位。
祝你明天调试顺利!
待续。