【裸机开发】I2C 通信接口(三)—— I2C 底层驱动接口实现

news2024/12/26 15:16:39

目录

一、I2C 初始化

二、产生开始 / 停止信号

1、开始信号

2、重复开始信号

3、停止信号

三、向总线上发送数据(阻塞模式)

四、从总线上读取数据(阻塞模式)

五、整合:数据读写统一调用接口


一、I2C 初始化

初始化步骤如下:

  • 时钟源配置。PERCLK_CLK_ROOT 的时钟源是 66MHz
  • 关闭 I2C。在配置 I2C 相关寄存器时,我们需要先将 I2C 关闭
  • 分频。分频的目的是控制 I2C 的传输速率(如果要控制 I2C 的速率为 100K,可以640 分频)
  • 开启 I2C 

/* i2c 初始化 */
void i2c_init()
{
    // 时钟源的初始化放到了时钟初始化(CCM_init)

    // 关闭I2C
    I2C1_I2CR &= ~(1 << 7);
    // 设置分频值(分频值为 640)
    I2C1_IFDR = 0x15;
    // 打开I2C
    I2C1_I2CR |= (1 << 7);
}

 

二、产生开始 / 停止信号

1、开始信号

当总线空闲时,总线上的设备可以发送开始信号来占用总线。如果存在多个设备同时占用总线,此时就会发生冲裁。

  • 判断总线是否空闲
  • 设为主机模式,设为发送模式
  • 发送要通信的设备地址和通信方向
/* 产生开始信号 */
i2c_status_t i2c_master_start(unsigned char address, i2c_direction direction)
{
    /* 总线是否被占用 */
    if (is_bus_busy())
    {
        return Status_I2C_Busy;
    }

    /* 
     * 发送开始信号
     * bit 5: 1 主机
     * bit 4: 1 发送
     */
    I2C1_I2CR |= ((1 << 4) | (1 << 5));

    /* 发送通信地址和通信方向 */
    I2C1_I2DR = (((unsigned int)address << 1) | (direction == I2C_Read ? 1 : 0));

    return Status_I2C_Success;
}

2、重复开始信号

当某个设备在通信时,可能会需要临时改变通信方向,此时就可以发送一个重复开始信号来改变通信方向。发送重复开始信号要求总线空闲,且当前设备是主机,只要有一者不满足,就会发送失败

  • 判断主机是否空闲,且当前设备是否为主机模式
  • 发送一个重复开始信号
  • 发送通信地址和通信方向
/* 重复产生开始信号(可能是修改通信方向、通信设备的地址) */
i2c_status_t i2c_master_repeated_start(unsigned char address, i2c_direction direction)
{
    /* 
     * 总线是否被占用
     *  - 如果是其他设备占用总线,直接返回
     *  - 如果是当前设备占用总线,发送一个重复的开始信号
     */
    if (is_bus_busy() && is_master() == 0)
    {
        return Status_I2C_Busy;
    }

    /* 产生一个重复开始信号 */
    I2C1_I2CR |= ((1 << 2) | (1 << 4));

    /* 发送通信地址和通信方向 */
    I2C1_I2DR = (((unsigned int)address << 1U) | (direction & 0x01));

    return Status_I2C_Success;
}

 

3、停止信号

当主机打算继续通信时,可以发送一个停止信号来结束通信;如果从机不想继续通信,可以选择不回应主机表示想结束通信。

  • 产生一个停止信号
  • 等待总线被释放
/* 产生停止信号 */
i2c_status_t i2c_master_stop()
{
    unsigned short timeout = UINT16_MAX;

    /* 
     * 产生一个停止信号 
     * bit 3: 0 产生一个ACK
     * bit 4: 0 接收(要产生一个ACK,当前设备必须为接收模式)
     * bit 5: 0 从机
     */
    I2C1_I2CR &= ~((1 << 3) | (1 << 4) | (1 << 5));

    /* 等待总线被释放 */
    while (is_bus_busy() && (timeout--));

    // 等待超时
    if (timeout == 0)       
    {
        return Status_I2C_Timeout;
    }
    
    return Status_I2C_Success;
}

三、向总线上发送数据(阻塞模式)

只有当上一次的数据全部发送完,才能开始下一次通信。其本质是,每次传输 8 bit(需要 8 个时钟周期),当第 9 个时钟周期的边沿触发时,我们认为数据传输完毕。

  • 等待 DR 寄存器准备就绪
  • 清除中断、仲裁标志位
  • 状态设为发送状态
  • while 循环逐字节发送
    • 每发送一个字节,都要将数据保存到 DR 寄存器
    • 等待传输完成(传输完成时会触发中断)
    • 清除中断标志位
    • 判断仲裁是否失败(可能存在多主机占用总线的情况)
    • 是否收到 NACK(从机是否想继续通信)
    • 如果遇到其他问题,直接跳出while循环
/* 
 * @description: 阻塞式发送 
 * @param - sendbuf: 保存要发送的数据(允许多字节)
 * @param - len: 发送数据的长度
 * @param - stopflag: 下一次是否还要继续发送
 */
i2c_status_t i2c_master_write_blocking(unsigned char* sendbuf, unsigned int len, unsigned int stopflag)
{
    i2c_status_t i2c_stat = Status_I2C_Success;
    
    /* 等待DR寄存器准备就绪 */
    while (((I2C1_I2SR >> 7) & 0x01) == 0);

    /* 清除中断标志位 */
    clear_intpending_flag();

    /* 状态设为发送 */
    I2C1_I2CR |= (1 << 4);

    /* 逐字节发送 */
    while (len--)
    {
        // 将要发送的数据保存到寄存器
        I2C1_I2DR = *(sendbuf++);

        // 等待传输完成(传输完成时会触发中断)
        while (((I2C1_I2SR >> 1) & 0x01) == 0);

        // 清除中断标志位
        clear_intpending_flag();

        // 仲裁是否失败
        if(((I2C1_I2SR >> 4) & 0x01))
        {
            i2c_stat = Status_I2C_Lost_Arbitration;
        }

        // 是否收到 NACK
        if (((I2C1_I2SR >> 0) & 0x01))
        {
            i2c_stat = Status_I2C_NAK;
        }

        if (i2c_stat != Status_I2C_Success)
        {
            break;
        }
    }

    // 从机不想收了 或者 主机不想发了
    if ((i2c_stat == Status_I2C_NAK) || stopflag)
    {
        clear_intpending_flag();    
        i2c_stat = i2c_master_stop();     
    }
    
    return i2c_stat;
}

/* 清除中断标志位 */
void clear_intpending_flag()
{
    I2C1_I2SR &= ~(1 << 1);
}

四、从总线上读取数据(阻塞模式)

在DR寄存器准备就绪以后,要将当前状态设为接收,逐字节接收(需要考虑接收缓冲区的大小),一旦接收缓冲区大小不足以接收后续的数据,需要提前发送停止信号告诉对方,不要再继续发了。

接收缓冲区大小 = 0,表示本次接收继续,下一次接收无法进行,直接发送停止信号

接收缓冲区大小 = 1,表示下一次接收继续,但是后续接收无法继续,发送 NACK

  • 等待 DR 寄存器准备就绪
  • 清除中断标志位
  • 状态设为接收
  • 循环读取
    • 等待数据传输完毕(对方将一个字节的数据全部发送到总线上时会触发中断)
    • 清除标志位
    • 如果接收缓冲区大小为 0,直接发送停止信号
    • 如果接收缓冲区大小为 1,发送 NACK
    • 读取 DR 寄存器数据
/*
 * @description		: 阻塞式读取
 * @param - recvbuf	: 读取到数据
 * @param - len 	: 要读取的数据大小(单位:字节)
 */
i2c_status_t i2c_master_read_blocking(unsigned char* recvbuf, unsigned int len)
{
    i2c_status_t result = Status_I2C_Success;
    volatile unsigned char dummy = 0;

    /* 使用这个变量的目的是避免编译报错 */
    dummy++;

    /* 等待DR寄存器准备就绪 */
    while (((I2C1_I2SR >> 1) & 0x01) == 0);

    /* 清除中断标志位 */
    clear_intpending_flag();

    /* 状态设为接收 */
    I2C1_I2CR &= ~((1 << 3) | (1 << 4));
    /* 后续只想再接收一个字节的数据 */
    if (len == 1)
    {
        I2C1_I2CR |= (1 << 3);
    }

    /* 假读 */
    dummy = I2C1_I2DR;
    
    while (len--)
    {
        /* 有一个字节的数据被传输到总线上,中断触发 */
        while (((I2C1_I2SR >> 1) & 0x01) == 0);

        /* 清除中断标志位 */
        clear_intpending_flag();

        /* 下一次没有足够的空间去读取 */
        if (len == 0)
        {
            result = i2c_master_stop();
        }

        // 后续只能再接收一个字节的数据,告诉对方不要再发数据了
        if (len == 1)
        {
            I2C1_I2CR |= (1 << 3);
        }

        /* 从寄存器中读取 */
        *(recvbuf++) = I2C1_I2DR & 0xFF;
    }

    return result;
}

/* 清除中断标志位 */
void clear_intpending_flag()
{
    I2C1_I2SR &= ~(1 << 1);
}

五、整合:数据读写统一调用接口

实际上我们真正要调用的接口,只需要这一个即可,上述内容其实都是在为这个接口做铺垫。当数据开始传输的时候,

  • 等待 DR 寄存器准备就绪
  • 清除中断、仲裁标志位
  • 判断是否为读时序,如果是读时序,需要临时修改通信方向(因为要先发送寄存器地址让对方知道自己要读取哪个寄存器)
  • 发送开始信号(发送设备地址和通信方向)
  • 等待信号发送完毕
  • 发送寄存器地址(考虑到寄存器地址可能不止一个字节)
  • 发送 / 读取数据

这里需要分别参考读时序和写时序的通信过程:I2C 通信时序

写时序:​​​​​​​

读时序:

typedef struct 
{
    unsigned char slaveAddress;         // 要通信的设备地址
    i2c_direction direction;            // 传输方向
    unsigned char reg;                  // 要读写的寄存器地址
    unsigned int reg_size;              // 寄存器地址大小(单位: 字节, 一般是1个字节,也有可能大于1个字节)
    unsigned char stopFlag;             // 下一次是否要停止发送
    unsigned char* buf;                 // 缓冲区
    unsigned int buf_size;              // 缓冲区大小
} i2c_transfer_t;

/* 数据传输(发送 / 接收) */
i2c_status_t i2c_master_transfer(i2c_transfer_t* xfer)
{
    i2c_direction direction = xfer->direction;

    /* 传输之前清除所有的标志位(仲裁、中断挂起) */
    I2C1_I2SR &= ~((1 << 1) | (1 << 4));

    /* DR 寄存器是否准备就绪 */
    while (!((I2C1_I2SR >> 7) & 0x01));
    
    /* 如果是读时序,需要先临时修改通信方向 */
    if ((xfer->reg_size > 0) && (xfer->direction == I2C_Read))
    {
        direction = I2C_Write;
    }

    /* 1、发送开始信号 */
    i2c_status_t ret = i2c_master_start(xfer->slaveAddress, direction);
    if (ret != Status_I2C_Success)
    {
        return ret;
    }

    /* 等待传输完成 */
    while (!((I2C1_I2SR >> 1) & 0x01));
    /* 检查传输是否发生错误 */
    if ((ret = i2c_check_and_clear_error()) != Status_I2C_Success)
    {
        i2c_master_stop();
        return ret;
    }
    
    /* 2、发送寄存器地址 */
    if (xfer->reg_size > 0)
    {
        do
        {
            clear_intpending_flag();
            xfer->reg_size --;

            /* 写入地址 */
            I2C1_I2DR = ((xfer->reg) >> (xfer->reg_size * 8));      // 先传输高字节

            /* 等待传输完成 */
            while (((I2C1_I2SR >> 1) & 0x01) == 0);
            /* 检查传输是否发生错误 */
            if ((ret = i2c_check_and_clear_error()) != Status_I2C_Success)
            {
                i2c_master_stop();
                return ret;
            }
        } while (xfer->reg_size > 0);
        
        /* 如果是读时序,需要先发送重复开始信号;如果是写时序,直接发送数据 */
        if (xfer->direction == I2C_Read)
        {
            clear_intpending_flag();

            /* 发送重复开始信号 */
            ret = i2c_master_repeated_start(xfer->slaveAddress, I2C_Read);
            if (ret != Status_I2C_Success)
            {
                i2c_master_stop();
                return ret;
            }
            /* 等待传输完成 */
            while (((I2C1_I2SR >> 1) & 0x01) == 0);
            /* 检查传输是否发生错误 */
            if ((ret = i2c_check_and_clear_error()) != Status_I2C_Success)
            {
                i2c_master_stop();
                return ret;
            }
        }
    }
    
    /* 到此,准备工作完毕 */
    /* 发送数据 */
    if (xfer->direction == I2C_Write)
    {
        ret = i2c_master_write_blocking(xfer->buf, xfer->buf_size, xfer->stopFlag);
    }
    
    if (xfer->direction == I2C_Read)
    {
        ret = i2c_master_read_blocking(xfer->buf, xfer->buf_size);
    }

    return ret;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/741003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot驾校管理系统

框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 …

springboot+dynamic-datasource实现多数据源动态切换,非@DS注解方式

springbootdynamic-datasource实现多数据源动态切换&#xff0c;非注解 一、前言二、方案思路三、代码实现 一、前言 最近在分析SaaS平台多租户的功能&#xff0c;必然涉及数据库部分的功能&#xff0c;多租户的设计方案要考虑租户隔离数据和租户共享数据&#xff0c;共享数据…

concrt140.dll怎么修复?哪种修复方法更值得推荐

运行软件的时候&#xff0c;计算提示找不到concrt140.dll&#xff0c;第一次遇到的小伙伴肯定不知道是什么意思。concrt140.dll是Microsoft Visual C Redistributable for Visual Studio 2015的一部分。它是一个动态链接库文件&#xff0c;包含在Windows操作系统中。这个文件主…

【Python】瓶装液位检测系统

文章目录 概要效果图整体架构流程技术细节 概要 本代码是一个简单的GUI应用程序&#xff0c;用于瓶装液位检测系统。 效果图 整体架构流程 整体架构流程如下&#xff1a; 创建GUI窗口和必要的部件&#xff1a; 创建一个主窗口&#xff08;root&#xff09;作为GUI应用程序的…

分享一套开源充电桩云平台(v2.5.1)-- 支持二轮(电动自行车)、四轮(电动汽车)

开源充电桩云平台&#xff08;v2.5.1&#xff09; 支持二轮(电动自行车)、四轮(电动汽车) 后台体验地址 二轮后台体验地址&#xff0c;star star &#xff1a; 点我访问 四轮后台体验地址&#xff0c;star star &#xff1a; 点我访问 用户端二维码 公众号二维码 小程序二维…

攻防世界-WEB2

代码审计 首先进行代码审计 <?php $miwen"a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";function encode($str){$_ostrrev($str);// echo $_o;for($_00;$_0<strlen($_o);$_0){$_csubstr($_o,$_0,1);$__ord($_c)1;$_cchr($__);$_$_.$_c; …

2023- itwangyang - mac mysql 终端启动命令

在mac上使用mysql终端进行操作时&#xff0c;需要先启动mysql服务。以下是启动mysql服务的命令&#xff1a; sudo /usr/local/mysql/support-files/mysql.server start执行该命令后&#xff0c;会出现一些提示信息&#xff0c;等待一段时间后mysql服务就启动成功了。 接下来&…

【Java】面向对象基础 之 继承

一、继承 在前面的章节中&#xff0c;我们已经定义了Person类&#xff1a; class Person {private String name;private int age;public String getName() {...}public void setName(String name) {...}public int getAge() {...}public void setAge(int age) {...} }现在&am…

git报错:remote: Access denied (URL 403)

git报错&#xff1a;remote: Access denied fatal: unable to access ‘ https:/ /gitee. cohe requested URL 403 大概的原因&#xff0c;是之前更改了 可能因为我之前在git bash中配过ssh&#xff0c;系统已经将指向git的用户设置了别的位置&#xff0c;所以…

DAY44:动态规划(四)整数拆分(递归+DP递推都可以做,注意区别和理解)

文章目录 343.整数拆分思路1&#xff1a;递归法&#xff08;最直观的想法&#xff09;递归思路普通递归写法注意点&#xff1a;max的嵌套普通递归存在的问题 记忆化搜索递归写法时间复杂度 递归解法总结 思路2&#xff1a;动态规划&#xff08;注意递推的理解&#xff09;确认D…

Transformer 模型详解

Transformer模型 https://blog.csdn.net/m0_67084346/article/details/128138486 https://blog.csdn.net/benzhujie1245com/article/details/117173090 2017 年&#xff0c;Google 在论文 Attention is All you need 中提出了 Transformer 模型&#xff0c;其使用 Self-Atten…

一个SpringMVC的小项目

一个图书管理小项目&#xff1a; 定义对应的表结构&#xff0c;为了学习所以才添加大量的 SQL 规则&#xff0c;要记得针对货币的处理方案 create table if not exists tbl_books( id bigint primary key auto_increment,book_name varchar(32) not null,book_price numeric(8…

专业的PDF文件压缩工具推荐,让你的PDF文件轻松压缩

​在参加专业的比赛时&#xff0c;就需要用到pdf文件&#xff0c;如果pdf文件过大操作和分享起来就特别不方便&#xff0c;其实可以使用专业的pdf文件压缩工具来处理。今天就分享一款pdf在线压缩工具&#xff0c;通过浏览器就可以快速完成pdf压缩&#xff08;https://www.yasuo…

SQL22 统计每个学校的答过题的用户的平均答题数

SELECT university,COUNT(qt.question_id)/COUNT(distinct(qt.device_id)) avg_answer_cnt FROM question_practice_detail qt LEFT JOIN user_profile ut ON qt.device_idut.device_id GROUP BY university

使用Word轻松实现PDF转Word

以前WPS还能通过每天打卡白嫖会员&#xff0c;最近不行了&#xff0c;害&#xff0c;羊毛没了 现在重新回归Word&#xff0c;利用Word就可以将PDF转化为Word 一、通过Word新建一个Word文档并打开 二、点击 文件 —> 打开 三、浏览&#xff0c;找到要转的PDF 四、点击确定&…

基础篇--初识STM32

初识STM32 STM32是什么 ST&#xff1a;意法半导体 M&#xff1a;MCU/MPU32:32位 ST累计推出了&#xff1a;5大类、18个系列、1000多个型号的Cortex内核微控制器 STM32芯片分类 ST中文社区网&#xff1a;https://www.stmcu.org.cn/ ST官网&#xff1a;https://www.st.com …

4.5Java EEMyBatis缓存机制

一、 一级缓存 MyBatis的一级缓存级别 MyBatis的一级缓存是SqlSession级别的缓存。如果同一个SqlSession对象多次执行完全相同的SQL语句时&#xff0c;在第一次执行完成后&#xff0c;MyBatis会将查询结果写入到一级缓存中&#xff0c;此后&#xff0c;如果程序没有执行插入、…

Mysql (insert,update操作)

1.创建表&#xff1a; 创建员工表employee&#xff0c;字段如下&#xff1a; id&#xff08;员工编号&#xff09;&#xff0c;name&#xff08;员工名字&#xff09;&#xff0c;gender&#xff08;员工性别&#xff09;&#xff0c;salary&#xff08;员工薪资&#xff09; …

脚踏Java知识点

对上节Java的基础语法续讲 三元运算符和if语句格式的区别 语法格式&#xff1a; 三元运算符的语法格式是&#xff1a;(condition) ? expression1 : expression2&#xff1b; if语句的语法格式是&#xff1a; if (condition) { // 执行 expression1 } else { // 执行 express…

Stage模型HarmonyOS服务卡片开发整体说明

服务卡片&#xff08;以下简称“卡片”&#xff09;是一种界面展示形式&#xff0c;可以将应用的重要信息或操作前置到卡片&#xff0c;以达到服务直达、减少体验层级的目的。卡片常用于嵌入到其他应用&#xff08;当前卡片使用方只支持系统应用&#xff0c;如桌面&#xff09;…