硬汉嵌入式论坛

 找回密码
 立即注册
查看: 266|回复: 6
收起左侧

[技术讨论] STM32F4和OLED软件I2C问题:显示正常,但逻辑分析仪定时异常

[复制链接]

0

主题

4

回帖

4

积分

新手上路

积分
4
发表于 2025-4-30 11:46:15 | 显示全部楼层 |阅读模式
本帖最后由 chipdynkid 于 2025-4-30 11:52 编辑

我的软件I2C在STM32F4(控制一个SSD1306 OLED)上的实现遇到了一个令人困惑的问题。我的逻辑分析仪显示了一个短暂的SDA高脉冲在第9 SCL高相位,标记为NACK。令人惊讶的是,OLED仍然能够接收数据,并能够在ACK发生时显示。

gpio设置
[C] 纯文本查看 复制代码
__HAL_RCC_GPIOB_CLK_ENABLE();//PB6 - I2C_SCL_Pin;PB7 - I2C_SDA_Pin

/*Configure GPIO pins : PBPin PBPin */
GPIO_InitStruct.Pin = I2C_SCL_Pin|I2C_SDA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

software-i2c
[C] 纯文本查看 复制代码
/*
 * bsp_i2c_soft.c
 *
 * Created: 2025-04-29 15:02:23
 *  Author: user
 *  function:   软件I2C
 *
 */

#include "bsp_i2c_soft.h"
#include "delay.h"

/* 软件I2C外设初始化
 * 1. 初始化对应GPIO时钟
 * 2. 配置GPIO为开漏输出、上拉电阻
 */

static void i2c_delay(void)
{
    delay_us(5);
}

/* 产生I2C起始信号
 * 在SCL高电平期间,SDA出现下降沿
 */
void i2c_start(void)
{
    i2c_sda(1);
    i2c_scl(1);
    i2c_delay();
    i2c_sda(0);
    i2c_delay();
    i2c_scl(0);
    i2c_delay();
}

/* 产生I2C停止信号
 * 在SCL高电平期间,SDA出现上升沿
 */
void i2c_stop(void)
{
    i2c_sda(0);
    i2c_scl(0);
    i2c_delay();
    i2c_scl(1);
    i2c_delay();
    i2c_sda(1);
    i2c_delay();
}

/*产生ack信号
 * SCL高电平期间,SDA低电平
 */
void i2c_ack(void)
{
    i2c_sda(0);
    i2c_delay();
    i2c_scl(1);
    i2c_delay();
    i2c_scl(0);
    i2c_delay();
    i2c_sda(1);
    i2c_delay();
}

/* 产生nack信号
 * SCL高电平期间,SDA高电平
 */
void i2c_nack(void)
{
    i2c_sda(1);
    i2c_delay();
    i2c_scl(1);
    i2c_delay();
    i2c_scl(0);
    i2c_delay();
}

/* 等待应答信号(ACK)
 * 0 接收到应答
 * 1 未接收到应答
 */
uint8_t i2c_wait_ack(void)
{
    uint8_t ack = 0;
    uint16_t wait_time = 0;

    i2c_sda(1); // 释放SDA
    i2c_delay();
    i2c_scl(1); // 拉高SCL
    i2c_delay();

    while (i2c_read_sda())
    {
        wait_time++;
        if (wait_time > 1024) // 超时
        {
            i2c_stop(); // 产生停止信号
            ack = 1;    // 未接收到应答
            break;
        }
    }

    i2c_scl(0); // 拉低SCL
    i2c_delay();

    return ack;
}

/* 发送一个字节数据
 * @input: data 要发送的数据(uint8_t)
 * MSB 先发送,LSB 后发送
 */
void i2c_send_byte(uint8_t txd)
{
    uint8_t i = 0;

    for (i = 0; i < 8; i++)
    {
        i2c_sda((txd & 0x80) >> 7); // 发送最高位
        i2c_delay();
        i2c_scl(1); // 拉高SCL
        i2c_delay();
        i2c_scl(0); // 拉低SCL
        txd <<= 1;  // 左移一位
    }

    i2c_sda(1); // 释放SDA
    i2c_scl(0); // 拉低SCL
}

/* 接收一个字节数据
 * @input: ack 应答信号(0: 应答,1: 非应答)
 * @return: 接收到的数据(uint8_t)
 * MSB 先接收,LSB 后接收
 */
uint8_t i2c_read_byte(uint8_t ack)
{
    uint8_t i = 0;
    uint8_t rxd = 0;

    for (i = 0; i < 8; i++)
    {
        rxd <<= 1;  // 左移一位
        i2c_scl(1); // 拉高SCL
        i2c_delay();
        if (i2c_read_sda()) // 读取SDA
        {
            rxd |= 0x01; // 接收到1
        }
        i2c_scl(0); // 拉低SCL
        i2c_delay();
    }

    if (!ack) // 非应答
    {
        i2c_nack(); // 发送应答信号
    }
    else // 应答
    {
        i2c_ack(); // 发送非应答信号
    }

    return rxd;
}


oled-display
[C] 纯文本查看 复制代码
/*
 * bsp_0.96oled.c
 *
 * Created: 2025-04-29 15:02:23
 *  Author: user
 *  function:  4脚 0.96寸 oled显示
 *
 */

#include "bsp_096oled.h"
#include "oled_font.h"
#include "delay.h"

#ifdef OLED_SOFT_IIC
#include "bsp_i2c_soft.h"
#endif

// static uint8_t oled_page_buffer[128][8];

static void oled_i2c_write_command(uint8_t data)
{
    i2c_start();
    i2c_send_byte(0x78); // 写地址
    i2c_wait_ack();
    i2c_send_byte(0x00); // 写命令
    i2c_wait_ack();
    i2c_send_byte(data); // 写命令
    i2c_wait_ack();
    i2c_stop();
}

static void oled_i2c_write_data(uint8_t data)
{
    i2c_start();
    i2c_send_byte(0x78); // 写地址
    i2c_wait_ack();
    i2c_send_byte(0x40); // 写数据
    i2c_wait_ack();
    i2c_send_byte(data); // 写数据
    i2c_wait_ack();
    i2c_stop();
}

void oled_write_byte(uint8_t data, uint8_t mode)
{

#ifdef OLED_SOFT_IIC
    if (mode)
    {
        oled_i2c_write_data(data);
    }
    else
    {
        oled_i2c_write_command(data);
    }
#endif
}

void oled_set_pos(uint8_t x, uint8_t y)
{
    oled_write_byte(0xb0 + y, OLED_CMD);                 // 设置页地址
    oled_write_byte((x & 0x0f) | 0x00, OLED_CMD);        // 设置列地址低4位
    oled_write_byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD); // 设置列地址高4位
}

void oled_display_on(void)
{
    oled_write_byte(0x8d, OLED_CMD); // 电荷泵使能
    oled_write_byte(0x14, OLED_CMD); // 开启电荷泵
    oled_write_byte(0xaf, OLED_CMD); // 开启显示
}

void oled_display_off(void)
{
    oled_write_byte(0x8d, OLED_CMD); // 电荷泵使能
    oled_write_byte(0x10, OLED_CMD); // 关闭电荷泵
    oled_write_byte(0xae, OLED_CMD); // 关闭显示
}

void oled_clear(void)
{
    uint8_t i, j;
    for (i = 0; i < 8; i++)
    {
        oled_set_pos(0, i); // 设置页地址
        for (j = 0; j < 128; j++)
        {
            oled_write_byte(0x00, OLED_DATA); // 写数据
        }
    }
}

void oled_fill(void)
{
    uint8_t i, j;
    for (i = 0; i < 8; i++)
    {
        oled_set_pos(0, i); // 设置页地址
        for (j = 0; j < 128; j++)
        {
            oled_write_byte(0xff, OLED_DATA); // 写数据
        }
    }
}

/* oled显示字符
 * 1. input:x(0-127)、y(0-7)、chr(字符)、Char_Size(字符大小 16(8*16) / 12(12*8))
 * 2. 配置GPIO为开漏输出、上拉电阻
 */
void oled_show_char(uint8_t x, uint8_t y, uint8_t chr, uint8_t size)
{
    uint8_t c = 0, i = 0;
    c = chr - ' ';  // 得到偏移后的值 ,' '的ASCII码为32
    if (size == 16) // 16*8 字体
    {
        oled_set_pos(x, y);
        for (i = 0; i < 8; i++)
            oled_write_byte(f8X16[c][i], OLED_DATA); // 写数据
        oled_set_pos(x, y + 1);
        for (i = 0; i < 8; i++)
            oled_write_byte(f8X16[c][i + 8], OLED_DATA); // 写数据
    }
    else if (size == 12) // 12*8 字体
    {
        oled_set_pos(x, y);
        for (i = 0; i < 6; i++)
            oled_write_byte(f12x8[c][i], OLED_DATA); // 写数据
    }
}

void oled_init(void)
{
    delay_ms(200);
    oled_write_byte(0xAE, OLED_CMD); // 设置显示开启/关闭,0xAE关闭,0xAF开启

    oled_write_byte(0xD5, OLED_CMD); // 设置显示时钟分频比/振荡器频率
    oled_write_byte(0x80, OLED_CMD); // 0x00~0xFF

    oled_write_byte(0xA8, OLED_CMD); // 设置多路复用率
    oled_write_byte(0x3F, OLED_CMD); // 0x0E~0x3F

    oled_write_byte(0xD3, OLED_CMD); // 设置显示偏移
    oled_write_byte(0x00, OLED_CMD); // 0x00~0x7F

    oled_write_byte(0x40, OLED_CMD); // 设置显示开始行,0x40~0x7F

    oled_write_byte(0xA1, OLED_CMD); // 设置左右方向,0xA1正常,0xA0左右反置

    oled_write_byte(0xC8, OLED_CMD); // 设置上下方向,0xC8正常,0xC0上下反置

    oled_write_byte(0xDA, OLED_CMD); // 设置COM引脚硬件配置
    oled_write_byte(0x12, OLED_CMD);

    oled_write_byte(0x81, OLED_CMD); // 设置对比度
    oled_write_byte(0xCF, OLED_CMD); // 0x00~0xFF

    oled_write_byte(0xD9, OLED_CMD); // 设置预充电周期
    oled_write_byte(0xF1, OLED_CMD);

    oled_write_byte(0xDB, OLED_CMD); // 设置VCOMH取消选择级别
    oled_write_byte(0x30, OLED_CMD);

    oled_write_byte(0xA4, OLED_CMD); // 设置整个显示打开/关闭

    oled_write_byte(0xA6, OLED_CMD); // 设置正常/反色显示,0xA6正常,0xA7反色

    oled_write_byte(0x8D, OLED_CMD); // 设置充电泵
    oled_write_byte(0x14, OLED_CMD);

    oled_write_byte(0xAF, OLED_CMD); // 开启显示
    oled_clear();
}


Snipaste_2025-04-30_11-08-52.png

Snipaste_2025-04-30_11-49-35.png

I2Czz.7z (1.16 MB, 下载次数: 0)


回复

使用道具 举报

6

主题

241

回帖

259

积分

高级会员

积分
259
发表于 2025-4-30 16:46:20 | 显示全部楼层
i2c硬件都有虑毛刺功能,你看一下毛刺的时间,比如这个芯片支持的最高波特率是1Mhz,毛刺时间小于1us很多的就会被过滤掉不影响正常功能。
还有你的逻辑分析仪好像不支持设置电平级别,有可能是小振荡识别出来了,但MCU不一定能检测到。
回复

使用道具 举报

0

主题

4

回帖

4

积分

新手上路

积分
4
 楼主| 发表于 2025-4-30 19:08:13 | 显示全部楼层
感谢二楼的建议,为我后续实现硬件i2c显示oled,提供参考信息。

问题已解决,这个i2c_wait_ack适用于模拟i2c(sda为推挽输出引脚),即只能单主从的i2c通信。

这个是软件模拟i2c,问题在于一楼中的i2c_wait_ack函数,因为主机释放总线(sda引脚为开漏输出)所以输出DR一直为1.会导致超时,输出停止信号,出现NACK情况。
即在i2c_wait_ack的开头,将sda引脚配置为输入引脚,在获得ack信号,再将sda设置为开漏输出引脚。
微信图片_20250430185942.png
回复

使用道具 举报

10

主题

100

回帖

130

积分

初级会员

积分
130
发表于 2025-4-30 20:10:20 | 显示全部楼层
你需要的不是逻辑分析仪,而是示波器!
回复

使用道具 举报

0

主题

4

回帖

4

积分

新手上路

积分
4
 楼主| 发表于 2025-4-30 22:31:14 | 显示全部楼层
我似乎解决了这个问题。我检查出的问题在于software-i2c中的i2c_wait_ack函数,因为主机释放总线时输出DR总是1 (sda引脚是开路输出)。此时读取sda引脚的状态将一直是1导致超时,并输出停止信号,并发生NACK条件。
i2c_wait_ack函数里将sda引脚配置成输入模式。

1.png

[C] 纯文本查看 复制代码
uint8_t i2c_wait_ack(void)
{
    uint8_t ack = 0;
    uint16_t wait_time = 0;

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = I2C_SDA_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // i2c_sda(1); // 释放SDA
    i2c_delay();
    i2c_scl(1); // 拉高SCL
    i2c_delay();

    while (i2c_read_sda())
    {
        wait_time++;
        if (wait_time > 500) // 超时
        {
            i2c_stop(); // 产生停止信号
            ack = 1;    // 未接收到应答
            break;
        }
    }

    i2c_delay();
    i2c_scl(0); // 拉低SCL

    GPIO_InitStruct.Pin = I2C_SDA_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    return ack;
}
回复

使用道具 举报

0

主题

4

回帖

4

积分

新手上路

积分
4
 楼主| 发表于 2025-4-30 22:34:48 | 显示全部楼层
我的回复审核一直不通过,我检查问题在于https://stackoverflow.com/questi ... =createdasc#tab-top
回复

使用道具 举报

0

主题

4

回帖

4

积分

新手上路

积分
4
 楼主| 发表于 2025-4-30 22:36:37 | 显示全部楼层
fxyc87 发表于 2025-4-30 16:46
i2c硬件都有虑毛刺功能,你看一下毛刺的时间,比如这个芯片支持的最高波特率是1Mhz,毛刺时间小于1us很多的 ...

我似乎解决了这个问题。我检查出的问题在于software-i2c中的i2c_wait_ack函数,因为主机释放总线时输出DR总是1 (sda引脚是开路输出)。此时读取sda引脚的状态将一直是1导致超时,输出停止信号,并发生NACK条件。

解决方法:i2c_wait_ack函数里将sda引脚配置成输入模式。



[C] 纯文本查看 复制代码
uint8_t i2c_wait_ack(void)
{
    uint8_t ack = 0;
    uint16_t wait_time = 0;

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = I2C_SDA_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // i2c_sda(1); // 释放SDA
    i2c_delay();
    i2c_scl(1); // 拉高SCL
    i2c_delay();

    while (i2c_read_sda())
    {
        wait_time++;
        if (wait_time > 500) // 超时
        {
            i2c_stop(); // 产生停止信号
            ack = 1;    // 未接收到应答
            break;
        }
    }

    i2c_delay();
    i2c_scl(0); // 拉低SCL

    GPIO_InitStruct.Pin = I2C_SDA_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    return ack;
}
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2025-5-11 18:38 , Processed in 0.591419 second(s), 27 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表