本帖最后由 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();
}
I2Czz.7z
(1.16 MB, 下载次数: 0)
|