基于STM32F103C8T6和CubeMX(HAL库)移植江科大软件IIC

I2C通信核心原理

  1. 物理层特性

    • 双线制:仅需SCL(时钟线)和SDA(数据线)两根线

    • 开漏输出:所有设备都需配置为开漏模式,依赖外部上拉电阻(通常4.7kΩ)

    • 多主从架构:支持多个主设备和从设备

    • 地址寻址:7位或10位从机地址机制

  2. 协议层要点

    • 起始条件:SCL高电平时SDA由高→低

    • 停止条件:SCL高电平时SDA由低→高

    • 数据有效性:SDA在SCL高电平期间需保持稳定

    • 应答机制:每个字节传输后接收方必须发送ACK(低电平)

时序图(来源江科大)

 

代码解释(后面有完整的.c和.h文件)

1. 基础GPIO操作

// SCL控制(PB4)
void MyI2C_W_SCL(uint8_t BitValue) {
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, (GPIO_PinState)BitValue);
    Delay_us(5); // 关键时序延时
}

// SDA控制(PB5)
void MyI2C_W_SDA(uint8_t BitValue) {
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, (GPIO_PinState)BitValue);
    Delay_us(5);
}

// SDA读取
uint8_t MyI2C_R_SDA(void) {
    Delay_us(5);
    return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5);
}
  • 精确的微秒级延时保证协议时序

  • 所有GPIO操作必须为开漏模式(GPIO_MODE_OUTPUT_OD

  • 读SDA前需先释放总线(输出高电平)

2. 通信控制信号

/* 起始信号 */
void MyI2C_Start(void) {
    MyI2C_W_SDA(1);
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(0);  // 下降沿触发起始
    MyI2C_W_SCL(0);  // 准备数据传输
}

/* 停止信号 */
void MyI2C_Stop(void) {
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(1);  // 上升沿触发停止
}

时序要求

信号类型SCL状态SDA状态
起始信号高电平高->低
停止信号高电平低->高

3. 数据收发实现

/* 发送单个字节 */
void MyI2C_SendByte(uint8_t Byte) {
    for(uint8_t i=0; i<8; i++) {
        MyI2C_W_SDA(Byte & (0x80 >> i)); // 高位先行
        MyI2C_W_SCL(1);                  // 上升沿锁存数据
        MyI2C_W_SCL(0);
    }
    MyI2C_W_SDA(1); // 释放总线等待ACK
}

/* 接收单个字节 */
uint8_t MyI2C_ReceiveByte(void) {
    uint8_t Byte = 0;
    MyI2C_W_SDA(1); // 主机释放总线
    for(uint8_t i=0; i<8; i++) {
        MyI2C_W_SCL(1);
        if(MyI2C_R_SDA()) Byte |= (0x80 >> i);
        MyI2C_W_SCL(0);
    }
    return Byte;
}

发送端:      [BIT7] [BIT6] ... [BIT0] [ACK]
接收端:SCL ↑      → 锁存数据      → 检测ACK

4. 应答机制

/* 发送应答 */
void MyI2C_SendAck(uint8_t AckBit) {
    MyI2C_W_SDA(AckBit);
    MyI2C_W_SCL(1);
    MyI2C_W_SCL(0);
}

/* 接收应答 */
uint8_t MyI2C_ReceiveAck(void) {
    MyI2C_W_SDA(1); // 主机释放总线
    MyI2C_W_SCL(1);
    uint8_t ack = MyI2C_R_SDA();
    MyI2C_W_SCL(0);
    return ack; // 0=ACK, 1=NACK
}

应答规则

  • ACK:SDA低电平(0)

  • NACK:SDA高电平(1)

完整代码

.h文件

#include "MYIIC.h"
void Delay_us(uint32_t us) {
    uint32_t ticks = us * (SystemCoreClock / 1000000) / 5; // 根据主频调整
    while(ticks--);
}

// 修改所有GPIO操作函数的引脚定义
void MyI2C_W_SCL(uint8_t BitValue) {
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, (GPIO_PinState)BitValue); // PB4为SCL
    Delay_us(5);
}

void MyI2C_W_SDA(uint8_t BitValue) {
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, (GPIO_PinState)BitValue); // PB5为SDA
    Delay_us(5);
}

uint8_t MyI2C_R_SDA(void) {
    Delay_us(5);
    uint8_t BitValue = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5);        // 读取PB5
    return BitValue;
}

/* I2C初始化 */
void MyI2C_Init(void) {
    __HAL_RCC_GPIOB_CLK_ENABLE();
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出
    GPIO_InitStruct.Pull = GPIO_NOPULL;         // 外部上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4 | GPIO_PIN_5, GPIO_PIN_SET);
}

/* 起始信号 */
void MyI2C_Start(void)
{
    MyI2C_W_SDA(1);
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(0);
}

/* 停止信号 */
void MyI2C_Stop(void)
{
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(1);
}

/* 发送一个字节 */
void MyI2C_SendByte(uint8_t Byte) {
    for (uint8_t i = 0; i < 8; i++) {
        MyI2C_W_SDA(Byte & (0x80 >> i));
        MyI2C_W_SCL(1);
        MyI2C_W_SCL(0);
    }
    // 发送完字节后,释放SDA线
    MyI2C_W_SDA(1); // 设置为高电平(开漏模式)
}

/* 接收一个字节 */
uint8_t MyI2C_ReceiveByte(void)
{
    uint8_t Byte = 0x00;
    MyI2C_W_SDA(1); // 释放SDA总线
    for (uint8_t i = 0; i < 8; i++)
    {
        MyI2C_W_SCL(1);
        if (MyI2C_R_SDA()) Byte |= (0x80 >> i);
        MyI2C_W_SCL(0);
    }
    return Byte;
}

/* 发送应答 */
void MyI2C_SendAck(uint8_t AckBit)
{
    MyI2C_W_SDA(AckBit);
    MyI2C_W_SCL(1);
    MyI2C_W_SCL(0);
}

/* 接收应答 */
uint8_t MyI2C_ReceiveAck(void) {
    MyI2C_W_SDA(1); // 释放SDA线
    MyI2C_W_SCL(1);
    uint8_t AckBit = MyI2C_R_SDA(); // 读取ACK
    MyI2C_W_SCL(0);
    return AckBit; // 0=ACK, 1=NACK
}

void IIC_write(uchar addr, uchar dat) {
    MyI2C_Start();
    MyI2C_SendByte(w_addr);   //地址根据从设备修改
    MyI2C_ReceiveAck();
    MyI2C_SendByte(addr);
    MyI2C_ReceiveAck();
    MyI2C_SendByte(dat);
    MyI2C_ReceiveAck();
    MyI2C_Stop();
    HAL_Delay(10); // 延长等待时间至10ms
}


uint8_t IIC_read(uchar addr) {
    uint8_t Data;
    MyI2C_Start();
    MyI2C_SendByte(w_addr);    //地址根据从设备修改
    MyI2C_ReceiveAck();
    MyI2C_SendByte(addr);  
    MyI2C_ReceiveAck();
    
    MyI2C_Start(); // 重复起始信号
    MyI2C_SendByte(r_addr);    //地址根据从设备修改
    MyI2C_ReceiveAck();
    Data = MyI2C_ReceiveByte();
    MyI2C_SendAck(1); // 发送NACK(1表示不应答)
    MyI2C_Stop();
    return Data;
}

.c文件

#ifndef _MYIIC_H_
#define _MYIIC_H_

#include "main.h"

void Delay_us(uint32_t us);
void MyI2C_W_SCL(uint8_t BitValue);
void MyI2C_W_SDA(uint8_t BitValue);
uint8_t MyI2C_R_SDA(void);
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);

#define w_addr 0xa0       //从机地址线
#define r_addr 0xa1       //从机地址线

void IIC_write(uchar addr,uchar dat);
uint8_t IIC_read(uchar addr);

#endif 

 这里注意0xa0和0xa1,这个是根据自己传感器的地址线而填的,我的是stm32自带的AT2402

main.c(主函数)

  MyI2C_Init();
  IIC_write(1,50);
  char t[30];
  sprintf(t,"  r:%d   ",IIC_read(1));
  OLED_ShowString(2,2,t);

 现象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值