AI似人,不是神

#VibeCoding·九月创作之星挑战赛#

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; // 100kHz

    I2C_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: 主要问题分析:

  1. 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点阵
// ... 其余命令

}
调试建议:

  1. 添加调试信息
    在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”);
    // …
    }

  2. 检查硬件连接
    • 确认I2C引脚连接正确
    • 检查LCD的VCC、GND、SDA、SCL连接
    • 确认背光控制线连接(如果有)

  3. 简化测试代码
    先测试最基本的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”);
    }
    }

  4. 修改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在初始化时卡死的问题,可能有以下几个关键原因需要排查:

  1. I2C硬件连接检查
    • 确认PB6(SCL)和PB7(SDA)的物理连接正确
    • 检查I2C地址0x3F是否与您的LCD模块匹配(常见还有0x27)
    • 测量上拉电阻(通常4.7K欧姆)是否正常工作

  2. 时钟配置优化 在RCC_Configuration()中建议添加以下保护措施:
    C复制
    • 1
    • 2
    • 3
    • 4
    // 在PLL配置后添加时钟安全检查
    if (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) != SET) {
    while(1); // 时钟初始化失败
    }

  3. SysTick定时器问题 您的SysTick配置为10us中断一次,但Delay_ms依赖于这个时基。建议:
    C复制
    • 1
    • 2
    // 修改bsp_SysTick.c中的初始化
    SysTick_Config(SystemCoreClock / 1000); // 改为1ms时基

  4. 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);
    // …其余保持不变
    }

  5. 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) { /* 处理超时 */ }

    // 每个步骤都添加类似的超时检测
    // …
    }

  6. 电压电平检查
    • 确认STM32和LCD模块的供电电压匹配(通常都是3.3V)
    • 检查I2C线路上的电压幅值是否足够
    建议的调试步骤:

  7. 先用逻辑分析仪抓取I2C信号,观察是否有起始信号

  8. 单独测试I2C写函数是否能完成单字节传输

  9. 逐步缩短初始化流程,找到具体的卡死点

  10. 尝试降低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

使用说明:

  1. 首先需要确保你的 STM32F407ZGT6 已经正确配置了 I2C1 外设,上述代码使用的是 I2C1。
  2. 根据你的硬件连接,可能需要修改i2c_1602.h中的LCD_ADDR宏定义,1602 IIC 液晶的地址通常是 0x4E 或 0x40,可以通过 I2C 扫描工具确定。
  3. 驱动程序功能说明:
    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():创建自定义字符
  4. 在使用前,需要确保已经正确初始化了 I2C 外设,示例代码中的MX_I2C1_Init()提供了基本的初始化配置。
  5. 如果你的 I2C 引脚不是默认的,可以在HAL_I2C_MspInit()函数中修改 GPIO 配置。

请根据你的实际硬件连接情况调整 I2C 引脚和设备地址,确保能够正常通信。

qqxjyxjy清风:能用标准库编写吗?我对HAL库不太熟悉。

AI:好的,我可以为你提供一个使用 STM32 标准外设库(SPL)编写的 1602 IIC 液晶驱动程序,不依赖 HAL 库。这个驱动程序直接操作寄存器,适合不熟悉 HAL 库的开发者使用。

STM32F407ZGT6的1602 IIC液晶驱动程序(标准库版本)
V1
创建时间:14:21

使用说明:

  1. 硬件连接:
    o 该驱动默认使用 I2C1,引脚为 PB6(SCL)和 PB7(SDA)
    o 如果需要使用其他引脚,可以修改i2c_1602.h中的引脚定义
  2. 地址设置:
    o 1602 IIC 液晶的地址通常是 0x4E 或 0x40,你可能需要根据实际模块修改LCD_ADDR宏定义
    o 地址已经左移了 1 位,因为 I2C 协议要求地址格式如此
  3. 延时函数:
    o 代码中使用了delay_ms()函数,你需要提供一个简单的延时函数库
    o 可以使用 SysTick 定时器实现毫秒级延时
  4. 功能函数:
    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):显示字符串
  5. 使用步骤:
    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)。当有设备拉低时,电压会降低。

可能的情况分析:

  1. SCL 线被某一设备持续拉低,导致电压异常偏低
  2. 通信总线存在故障,如短路或元件损坏
  3. 上拉电阻出现问题,导致 SCL 无法正常保持高电平
  4. 某个设备出现故障,持续拉低 SCL 线

建议检查相关电路连接、上拉电阻以及总线上的各个设备,找出导致 SCL 电压异常的原因。
编辑分享
拔掉液晶显示1602,SCL(时钟线)电压为 0.02V,SDA(数据线)电压为 3.25V,故障与液晶显示板无关。
既然拔掉 1602 液晶后,SCL 电压仍然保持在 0.02V 的异常低电平,说明故障点不在液晶显示板本身,可从以下方向进一步排查:

  1. 其他 I2C 设备故障
    检查总线上连接的其他 I2C 设备(如传感器、模块等),可能某一设备内部短路或异常,导致持续拉低 SCL 线。可尝试逐一断开其他 I2C 设备,观察 SCL 电压是否恢复正常(正常应接近电源电压,如 3.3V 或 5V)。
  2. 主控芯片问题
    若断开所有外部 I2C 设备后,SCL 仍为低电平,可能是主控芯片(如单片机)的 SCL 引脚内部短路或输出异常,导致持续输出低电平。
  3. 线路或上拉电阻故障
    o 检查 SCL 线路是否与 GND 短路(可用万用表导通档测试)。
    o 确认 SCL 线上的上拉电阻是否损坏或未正确连接(正常应通过上拉电阻接电源)。

通过逐步排除法,先断开所有外部设备排查主控问题,再逐个接入设备定位故障源,可更高效找到问题所在。

qqxjyxjy清风:拔掉液晶显示1602,SCL(时钟线)电压为 0.02V,SDA(数据线)电压为 3.25V,故障与液晶显示板无关。

AI: 既然拔掉 1602 液晶后,SCL 电压仍然保持在 0.02V 的异常低电平,说明故障点不在液晶显示板本身,可从以下方向进一步排查:

  1. 其他 I2C 设备故障
    检查总线上连接的其他 I2C 设备(如传感器、模块等),可能某一设备内部短路或异常,导致持续拉低 SCL 线。可尝试逐一断开其他 I2C 设备,观察 SCL 电压是否恢复正常(正常应接近电源电压,如 3.3V 或 5V)。
  2. 主控芯片问题
    若断开所有外部 I2C 设备后,SCL 仍为低电平,可能是主控芯片(如单片机)的 SCL 引脚内部短路或输出异常,导致持续输出低电平。
  3. 线路或上拉电阻故障
    o 检查 SCL 线路是否与 GND 短路(可用万用表导通档测试)。
    o 确认 SCL 线上的上拉电阻是否损坏或未正确连接(正常应通过上拉电阻接电源)。

通过逐步排除法,先断开所有外部设备排查主控问题,再逐个接入设备定位故障源,可更高效找到问题所在。

qqxjyxjy清风:调试时已过了 I2C_Configuration()初始化函数,SCL 电压仍然保持在 0.02V 的异常低电平,首当怀疑这个函数的问题。其它前面的函数几乎与PB6(SCL)和 PB7(SDA)无关。说明故障点在I2C_Configuration()初始化函数过程之中。

AI: 既然故障点锁定在I2C_Configuration()初始化函数过程中,说明 I2C 初始化配置未正常完成,导致 SCL 线持续被拉低。可从以下几个方向排查:

  1. I2C 模式与引脚配置错误
    检查初始化函数中是否正确配置了 SCL/SDA 对应的 GPIO 引脚模式(应设置为复用开漏输出),若误配置为推挽输出且持续输出低电平,会导致 SCL 被强制拉低。
    c
    运行
    // 错误示例(推挽输出可能导致持续低电平)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

// 正确配置(复用开漏才能实现线与逻辑)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;

  1. I2C 时钟频率配置异常
    若初始化时配置的时钟频率过高(超出硬件支持范围),可能导致 I2C 模块启动失败并锁死 SCL 线。需确认时钟分频参数是否合理:
    c
    运行
    // 例如:APB1时钟为36MHz时,配置100kHz标准模式
    I2C_InitStructure.I2C_ClockSpeed = 100000;
    I2C_InitStructure.I2C_Prescaler = 180; // 分频参数需与时钟频率匹配

  2. I2C 外设使能失败
    检查初始化流程中是否遗漏了 I2C 外设时钟使能或 GPIO 时钟使能:
    c
    运行
    // 需确保先使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 假设SCL/SDA接GPIOB

  3. 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 同时被拉低,确实可能指向硬件层面的问题,可按以下思路进一步验证:

  1. 最小系统测试
    剥离所有外部 I2C 设备,仅保留 MCU 最小系统,重新初始化 I2C 后测量引脚电压。若仍持续低电平,基本可确认是 MCU 的 I2C 外设损坏或内部电路异常。
  2. 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 异常低电平,可按以下步骤进一步定位:

  1. 测试 GPIO 基础功能
    如前所述,将 PB6、PB7 配置为普通推挽输出,手动切换高低电平并测量电压:
    o 若能正常输出 3.3V(或电源电压)和 0V,说明引脚物理通路正常,问题锁定在 I2C 外设模块。
    o 若始终为低电平,可能是引脚内部短路或 GPIO 模块损坏。
  2. 简化 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 总线上)。不过有几个细节可以进一步完善:

  1. 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

  2. 复用功能映射
    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

  3. GPIO 速度参数
    F4 系列的 GPIO 速度定义与 F1 不同,应使用GPIO_Speed_50MHz而非GPIO_Speed_2MHz(F4 没有 2MHz 选项):
    c
    运行
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  4. 初始化流程整理
    建议将 I2C 初始化封装为独立函数,保持 main 函数简洁:

STM32F4 I2C初始化代码
V1
创建时间:09-12 13:17

修改后建议分步测试:

  1. 只保留 I2C 初始化,不连接任何外设,测量 SCL/SDA 电压是否恢复正常(应接近 3.3V)
  2. 若电压正常,逐步添加 LCD 初始化代码
  3. 若仍异常,可尝试:
    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 的特性,主要问题可能在以下几点:

  1. GPIO 模式配置错误
    之前代码中使用了GPIO_Mode_AF(仅复用功能),但 I2C 需要复用开漏输出(GPIO_Mode_AF_OD)才能实现线与逻辑,否则引脚可能被异常拉低。
  2. 缺少复用功能映射
    未调用GPIO_PinAFConfig将 PB6/PB7 映射到 I2C1 外设,导致引脚功能未正确关联。
  3. 内部上拉电阻未启用
    若电路中没有外部上拉电阻,需开启 GPIO 内部上拉,否则引脚可能处于悬浮状态。

以下是修正后的 I2C 初始化代码,可替换注释部分进行测试:

修正后的STM32F4 I2C初始化代码
V1
创建时间:09-12 22:00

关键修正点说明:

  1. 增加了GPIO_PinAFConfig配置,确保引脚正确映射到 I2C1 功能
  2. 将GPIO_Mode_AF改为GPIO_Mode_AF_OD(复用开漏),这是 I2C 通信的必要配置
  3. 启用内部上拉电阻(GPIO_PuPd_UP),避免引脚悬浮
  4. 调整 I2C 时钟为 50kHz,降低速率提高稳定性
  5. 使能应答功能(I2C_Ack_Enable),符合 1602 液晶的通信要求

测试建议:

  1. 先不接 1602 液晶,烧录代码后测量 PB6/PB7 电压,应接近电源电压(3.3V 或 5V,取决于系统)
  2. 若电压正常,再连接 1602 液晶测试通信
  3. 若仍有问题,可尝试在 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 系列将模式和输出类型分开配置,需要通过两个参数组合实现复用开漏功能:

  1. 模式配置为复用功能(GPIO_Mode_AF)
  2. 输出类型配置为开漏(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 驱动时,建议注意以下几点,以确保通信顺利:

  1. 确认 1602 的 I2C 地址
    大多数 I2C 转接板的默认地址是0x4E或0x48(7 位地址),可在i2c_1602.h中定义:
    c
    运行
    #define LCD_ADDR 0x4E // 或0x48,根据实际硬件调整

  2. 初始化时序延迟
    在 I2C 初始化后,给 LCD 留出足够的上电时间:
    c
    运行
    // I2C初始化完成后
    Delay_ms(50); // 延时确保LCD稳定
    LCD_Init(); // 再调用LCD初始化

  3. 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);
}

  1. 调试建议
    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 个点:

  1. I2C 地址未左移:7 位地址必须左移 1 位(LCD_I2C_ADDR = 0x4E << 1),最低位用于区分读写(0 = 写,1 = 读),若直接用 0x4E 发送,1602 会无法识别地址,导致无 ACK,SCL 卡死。
  2. 缺少 “控制位”:1602 的 I2C 转接板(如 PCF8574 芯片)通常需要先发送 1 字节 “控制位”(告知 1602 是指令还是数据),若漏写控制位,1602 不会响应,I2C 会因等待 ACK 超时卡死。
  3. 未发送 STOP 信号:每发送 1 次指令 / 数据后,必须发送 STOP 信号释放 I2C 总线,否则 SCL 会被持续拉低。
    二、其次排查:1602 I2C 转接板的硬件匹配问题
    若LCD_SendCommand时序没问题,需确认 1602 转接板的硬件参数是否与代码匹配:
  4. 确认 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 初始化的基础配置,避免因底层配置导致通信异常:

  1. I2C 时钟频率匹配:代码中I2C_ClockSpeed设为 50000(50kHz),确保 1602 转接板支持该速率(大多数支持 100kHz 以下);
  2. ACK 使能:I2C 初始化时需开启 ACK(I2C_InitStructure.I2C_Ack = I2C_Ack_Enable),若关闭 ACK,主机不会等待从机应答,可能导致时序混乱;
  3. 延时足够:LCD_Init()中的Delay_ms(50)和Delay_ms(2)需确保实际延时有效(比如基于 SysTick 的Delay_ms是否正确初始化),若延时过短,1602 未准备好会不响应。
    排查流程总结
  4. 先验证LCD_SendCommand的时序(重点:START / 地址 / 控制位 / STOP 是否完整);
  5. 用I2C_Scan()确认 1602 的实际 I2C 地址,修正LCD_I2C_ADDR;
  6. 确认 1602 转接板的控制位格式,调整I2C_SendData(I2C1, 0x00)中的控制字节;
  7. 若仍死机,用逻辑分析仪抓取 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的值。
三、排查步骤(按优先级)

  1. 优先修正I2C_WriteByte函数,确保包含 STOP 信号和地址左移,这是解决 SCL 拉低的核心;
  2. 调用I2C_Scan()函数(参考上一轮回复),确认 1602 的实际 I2C 地址,修正LCD_I2C_ADDR(比如若扫描到地址0x90,则LCD_I2C_ADDR=0x90);
  3. 若修正后仍死机,在LCD_SendCommand的每一步I2C_WriteByte后,用串口打印调试信息(如 “发送高四位完成”),定位具体哪一步卡死;
  4. 若 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,程序会卡死在该循环。
排查 & 解决步骤:

  1. 明确 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。

  2. 用「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 无法响应:

  1. 供电电压不匹配
    • 多数 1602 液晶屏(非宽压版)需5V 供电,若误接 3.3V,会导致 1602 工作异常,无法发送 ACK;
    • 检查转接板的 VCC 引脚:若标注 “VCC 5V”,必须接 5V(可从 STM32 开发板的 5V 引脚取电),GND 需与 STM32 共地(否则会有电平差,通信失败)。

  2. SDA/SCL 引脚接反
    • 若 PB6(SCL)和 PB7(SDA)接反,1602 无法解析地址信号,自然不会发送 ACK;
    • 确认转接板的 SDA/SCL 引脚:通常转接板标注 “SDA”“SCL”,需与 STM32 的 PB7(SDA)、PB6(SCL)一一对应,不可交叉。

  3. 外部上拉电阻缺失(部分情况)
    • STM32 内部上拉(GPIO_PuPd_UP)的驱动能力较弱,若 1602 与 STM32 的连线较长(>10cm),可能导致信号衰减,1602 收不到地址;
    • 临时解决方案:在 PB6(SCL)、PB7(SDA)引脚分别串联一个4.7kΩ~10kΩ的上拉电阻到 3.3V(或 5V,需与 GPIO 电平匹配),增强信号驱动能力。
    三、低概率原因:I2C 时序 / 配置异常(2% 概率)
    若地址和硬件都没问题,需确认 I2C 初始化配置是否兼容 1602 的通信要求:

  4. I2C 时钟频率过高
    • 1602 I2C 转接板(PCF8574)的最大支持速率通常为100kHz(标准模式),若代码中I2C_ClockSpeed设为 400kHz(快速模式),可能导致从机无法同步,收不到地址;
    • 确认 I2C 初始化代码:
    c
    运行
    I2C_InitStructure.I2C_ClockSpeed = 100000; // 必须设为100kHz及以下
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 标准模式下用2即可

  5. I2C_OWNAddress1 配置错误
    • 主机模式下,I2C_OWNAddress1(自身地址)可设为 0x00,但需确保未启用 “从机模式”;
    • 确认初始化代码:
    c
    运行
    I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 主机无需自身地址
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // 必须启用ACK(否则主机不检测从机应答)

排查流程总结(按顺序执行)

  1. 调用I2C_Scan_Devices(),通过串口确认 1602 的实际写地址,替换代码中的LCD_ADDR;
  2. 检查 1602 供电(确保 5V)、GND 共地、SDA/SCL 引脚未接反;
  3. 若仍无 ACK,添加外部上拉电阻(4.7kΩ)到 SDA/SCL 引脚;
  4. 确认 I2C 时钟频率≤100kHz、I2C_Ack已启用。

按此流程操作后,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED事件会正常触发,程序不会再卡死,1602 初始化也能顺利完成。

qqxjyxjy清风:谢谢了,辛苦你了,今天太晚了,明天接着调。

AI: 不客气!熬夜调试容易遗漏细节,好好休息一晚,明天思路会更清晰~

如果明天扫描地址、排查硬件后还有小问题,比如地址确认了但仍无响应,或者显示乱码,都可以再一起梳理。重点先放在「地址匹配」和「硬件接线」上,这两步解决了,后续的显示问题会更易定位。

祝你明天调试顺利!

待续。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值