STM中的I2C

news2024/9/20 8:04:07

常见的几种通信接口

I2C总线定义

定义

I2C - Inter-Integrated Circuit:两线式 串行总线:说明处理器和外设之间只需两根信号线,分别是SCL时钟控制信号线和SDA数据线

SCL(serial clock line)

时钟控制信号线,永远只能由CPU控制,用于实现数据的同步,就四个字:低放高取

  • SCL为低电平的时候将数据放到SDA数据线上
  • SCL为高电平的时候从数据线SDA上获取数据

SDA

数据线,用于传输数据,双方都可以控制
  • 如果处理器给外设发送数据,SDA由处理器控制
  • 如果外设给处理器发送数据,SDA由外设控制
注意:SCL和SDA必须要分别连接一个上拉电阻,所以他们默认的电平都是高电平

架构

    I2C总线基于主从架构 

  •     其中一个设备作为主设备(master)

            负责发起通信控制总线时序

  •     其它的设备为从设备(slave)

            负责响应和数据传输 

速率

  •     标准模式 - 100kbps
  •     快速模式 - 400kbps
  •     高速模式 - 3.4Mbps 

串行

由于数据线就一根SDA,必然是串行,又由于有时钟控制信号线SCL,所以数据传输是一个时钟周期传输一个bit位,I2C数据传输从高位开始,I2C数据传输一次传输一个字节,如果传输多个字节,需要分拆着来传
传输特点:
  • 一位一周期
  • 一次一字节
  • 传输从高位
  • 速度看时钟(SCL)
  • 时钟看外设

I2C总线的应用领域

I2C总线协议相关概念 

  • START信号:又称起始信号,此信号永远只能由CPU发起,表示CPU开始要访问外设

                              时序为:SCL为高电平,SDA由高电平向低电平跳变产生START信号

  • STOP信号:又称结束信号,此信号永远只能由CPU发起,表示CPU结束对总线的访问

                              时序为:SCL为高电平,SDA由低电平向高电平跳变产生STOP信号

  •  R/W读写位:用于表示CPU到底是向外设写入数据还是从外设读取数据

                             有效位数为1个bit位,CPU读取数据:R/W=1,CPU向外设写入数据:R/W=0

  • 设备地址:用于表示外设在总线上的唯一性,也就是同一个I2C总线上,不同的外设具有唯一的一个设备地址,也就是如果CPU要想访问某个外设,CPU只需向总线上发送这个外设的设备地址即可设备地址的有效位数为7位或者10位(极其少见),设备地址不包含读写位!
  • 设备地址由原理图和芯片手册共同来决定: 

 

I2C总线数据传输的流程(协议)

设备地址

  • 读设备地址=设备地址<<1|R/W=1
  • 写设备地址=设备地址<<1|R/W=0
问:为何需要读或者写设备地址呢?
答:I2C总线协议规定,数据传输一次一个字节(8位),而设备地址本身7位不够1字节,正巧R/W为一个bit位,所以报团取暖凑够一字节,将来CPU要想访问某个外设只需发送读或者写设备地址,即可找到这个外 设也可以告诉外设到底读还是写!一箭双雕!

ACK信号

又称应答信号,表示双方数据传输的反馈,有效位数为1个bit位,低电平有效

片内寄存器

切记:任何I2C外设芯片内部都集成了一堆的寄存器,此类寄存器又称片内寄存器
  • 这些寄存器同样也有地址,地址编号从0x00开始
  • 虽然这些寄存器都有唯一的地址,但是CPU不能直接以指针的形式访问, 必须要严格按照,读写时序进行访问

结论

CPU访问I2C外设本质就是访问I2C外设内部的寄存器!

所以I2C外设本身只需关注三点:
  • I2C外设片内寄存器的特性
  • I2C外设片内寄存器的基地址
  • I2C外设片内寄存器的读写时序

以CPU访问MMA8653三轴加速度传感器为例

读取单字节数据

写入单字节数据

读取多字节数据

写入多字节数据

 总结

AT24C02的访问操作

以CPU访问AT24C02存储器为例


写数据

 

读数据

时序图

AT24C02 

概况

AT24C02是一个2K位串行EEPROM, 内部含有256个8位字节的存储单元,掉电数据不丢失
AT24C02的存储容量分成32页,每页8Byte,共256Byte
AT24C02寻址范围为00~FF,共256个寻址单位

 硬件设计

SCL和SDA均接有上拉电阻,连接到STM32的PB6和PB7管脚上

 时序细节

IIC代码编写 

STM32中无直接调用IIC的底层库,需要手撸代码实现IIC的数据收发

初始化

void IIC_Init(void)
{
	// 1.打开SCL/SDA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	// 2.配置SCL - 推挽输出, 50MHz
	GPIO_InitTypeDef GPIO_Config;
	GPIO_Config.GPIO_Pin = IIC_SCL_PIN;
	GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
	GPIO_Init(IIC_SCL_PORT, &GPIO_Config);
	
	// 3.配置SDA - 推挽输出, 50MHz
	GPIO_Config.GPIO_Pin = IIC_SDA_PIN;
	GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
	GPIO_Init(IIC_SDA_PORT, &GPIO_Config);
	
	// 4.拉高SCL/SDA 
	IIC_SDA = 1;
	IIC_SCL = 1;
}

配置SDA为推挽输出, 50MHz

// 配置SDA为推挽输出, 50MHz
static void SDA_OUT(void){
	GPIO_InitTypeDef GPIO_Config;
	GPIO_Config.GPIO_Pin = IIC_SDA_PIN;
	GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
	GPIO_Init(IIC_SDA_PORT, &GPIO_Config);
}

配置SDA为上拉输入

// 配置SDA为上拉输入
static void  SDA_IN(void){
	GPIO_InitTypeDef GPIO_Config;
	GPIO_Config.GPIO_Pin = IIC_SDA_PIN;
	GPIO_Config.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 
	GPIO_Init(IIC_SDA_PORT, &GPIO_Config);
}

起始和终止条件

 开始步骤
  •     1.配置SDA为输出模式 
  •     2.配置SDA/SCL为高电平 
  •     3.保持至少4.7us
  •     4.拉低SDA
  •     5.保持至少4us 

    ------>已经完成发送开始信号

void  IIC_Start(void){
	SDA_OUT();// 配置SDA为输出模式 
	IIC_SCL = 1; // 时钟线拉高
	IIC_SDA = 1; // 数据线拉高 
	delay_us(6); // 延时6us, >=4.7us 
	IIC_SDA = 0; // 拉低SDA
	delay_us(6); // 延时6us, >= 4us
	//-------->发送了开始信号 
	IIC_SCL = 0; // 将SCL拉低,便于下一次数据数据
}
终止步骤
  •     1.配置SDA为输出模式
  •     2.配置SCL为高电平,SDA为低电平
  •     3.保持至少4us
  •     4.拉高SDA
  •     5.保持至少4.7us 
void IIC_Stop(void){
	SDA_OUT(); // 配置SDA为输出模式 
	IIC_SDA = 0; // 数据线拉低 
	IIC_SCL = 1; // 时钟线拉高 
	delay_us(6); // 延时6us 
	IIC_SDA = 1; // 数据线拉高 
	delay_us(6); // 延时>4.7us 
}

处理ack

CPU等待ack
  • CPU等待外设发送ack信号 
  • 返回值 - 判断是否收到了ack
  • 收到ack ,0; 没收到ack, 返回1
u8 IIC_Wait_Ack(void){
	
	u32 tempTime = 0; // 等待的次数 
	// 将时钟拉低, 方便外设放入数据 
	IIC_SCL = 0; 
	delay_us(6); // 保持6us 
	SDA_IN(); // 配置输入模式 
	
	// 将时钟线拉高, 为了让CPU来读取数据 
	IIC_SCL = 1;
	delay_us(6); 
	
	// 如何判断SDA的高低电平呢? - READ_SDA 
	// 如果外设发送了ack, 发送低电平, SDA = 0
	// 如果外设没发送ack, 上拉输入,   SDA = 1
	
	while(READ_SDA){
		tempTime++;
		if(tempTime > 250){
			// 没有收到ack,结束传输 
			IIC_Stop(); 
			return 1;// 没收到ack 
		}
	}
	IIC_SCL = 0; //准备下一次数据传输
	return 0; // 收到ack
}
发送ack信号
void  IIC_Ack(void){
	IIC_SCL = 0; // 将SCL拉低
	SDA_OUT(); // 配置为输出模式 
	
	IIC_SDA = 0; // 将低电平放到SDA, 发送ack
	delay_us(6); // 保持低电平的周期 
	
	IIC_SCL = 1; // 将时钟线拉高, 让外设在此时读取SDA数据 
	delay_us(6); 
	
	IIC_SCL = 0; // 拉低准备下一次数据传输
}
发送nack信号
void  IIC_NAck(void){
	IIC_SCL = 0; // 将SCL拉低
	SDA_OUT(); // 配置为输出模式 
	
	IIC_SDA = 1; // 将高电平放到SDA, 发送nack
	delay_us(6); // 保持低电平的周期 
	
	IIC_SCL = 1; // 将时钟线拉高, 让外设在此时读取SDA数据 
	delay_us(6); 
	
	IIC_SCL = 0; // 拉低准备下一次数据传输
}

CPU收发数据 

CPU发送单字节
void IIC_Send_Byte(u8 TxData){
	u8 i;
	SDA_OUT(); // 配置为输出模式
	IIC_SCL = 0; // 为了将数据放到SDA上 
	for(i = 0; i < 8; i++){
		if(TxData & 0x80)
			IIC_SDA = 1;
		else 
			IIC_SDA = 0;
		
		TxData <<= 1;
		
		delay_us(6); // 低电平的时钟周期 
		
		IIC_SCL = 1; // 拉高,让外设读
		delay_us(6); 
		
		IIC_SCL = 0; 
	}
}
CPU读取单字节
// 返回读取到的数据 
// 参数 :
// 		1, 回复ack信号; 
//		0, 回复nack信号;
u8 IIC_Read_Byte(u8 ack){
	u8 i = 0, data = 0; // data保存读取到的数据 
	SDA_IN(); // 配置为输入模式 
	for(i = 0; i < 8; i++){
		IIC_SCL = 0; // 拉低SCL为了让外设放入数据 
		delay_us(6);
		
		IIC_SCL = 1; // 拉高为了获取数据
		data |= READ_SDA << (7 - i);
		delay_us(6); 
	}
	
	// 回复ack/nack
	if(!ack)
		IIC_NAck();
	else
		IIC_Ack();
	
	return data; // 返回读取到的数据 
}

 AT24C02代码

// @file at24c02.c
#include "at24c02.h"
#include "iic.h"
#include "systick.h"
#include "stdio.h" // printf

void AT24C02_Init(void){
	IIC_Init(); 
}

// 参数:要读取的寄存器的地址
// 返回值 : 返回读取到的数据
// AT24C02_ID
u8   AT24C02_ReadByte(u16 ReadAddr){
	u8 temp;
	// 1.发送开始信号
	IIC_Start();
	// 2.发送写设备地址
	IIC_Send_Byte(AT24C02_ID << 1 | 0);
	// 3.等待ack
	IIC_Wait_Ack();
	// 4.发送要读取的寄存器地址
	IIC_Send_Byte(ReadAddr);
	// 5.等待ack
	IIC_Wait_Ack();
	// 6.发送开始信号
	IIC_Start();
	// 7.发送读设备地址
	IIC_Send_Byte(AT24C02_ID << 1 | 1);
	// 8.等待ack
	IIC_Wait_Ack();
	// 9.读取外设数据 + 回复nack
	temp = IIC_Read_Byte(0);
	// 10.发送结束信号
	IIC_Stop();
	
	return temp;
}
// 功能 : 发送单字节数据
// 参数 : 
//		WriteAddr : 要写入的寄存器地址 
//		data : 要写入的数据
void AT24C02_WriteByte(u16 WriteAddr, u8 data){
	// 1.发送开始信号
	IIC_Start();
	// 2.发送写设备地址
	IIC_Send_Byte(AT24C02_ID << 1 | 0);
	// 3.等待ack
	IIC_Wait_Ack();
	// 4.发送要写入的寄存器地址
	IIC_Send_Byte(WriteAddr);
	// 5.等待ack
	IIC_Wait_Ack();
	// 6.发送要写入的数据
	IIC_Send_Byte(data);
	// 7.等待ack
	IIC_Wait_Ack();
	// 8.发送结束信号 
	IIC_Stop();
}
// 功能 : 读取多字节
// 参数 :
//	ReadAddr : 要读取的寄存器的首地址
//  pBuffer : 要读取数据存储的首地址
//	Len : 要读取的数据个数 
//  char buf[1024]; char* pBuffer = buf;
//  11 12 13 14  寄存器地址
void AT24C02_ReadBlockData(u16 ReadAddr, u8* pBuffer, u16 Len){
	while(Len){
		*pBuffer++ =  AT24C02_ReadByte(ReadAddr++);
		Len--;
	}
}
// 功能 : 写入多字节
// 参数 :
//	WriteAddr : 要写入的寄存器的首地址
//  pBuffer : 要写入数据存储的首地址
//	Len : 要写入的数据个数 
//  char buf[1024]; char* pBuffer = buf; 
//  buf数组 : xx xx xx xx
//  11 12 13 14  寄存器地址
void AT24C02_WriteBlockData(u16 WriteAddr, u8* pBuffer, u16 Len){
	while(Len){
		AT24C02_WriteByte(WriteAddr, *pBuffer);
		WriteAddr++;
		pBuffer++;
		Len--;
	}
	delay_us(20);
}
	

// 测试函数 : 后续进行命令匹配使用 	
void AT24C02_ReadOne(void){
	// 读取地址0x00寄存器数据 
	printf("READ DATA : %#X\n", AT24C02_ReadByte(0x00));
}
void AT24C02_WriteOne(void){
	// 将数据0XAA写入到地址0x00寄存器中 
	AT24C02_WriteByte(0X00, 0XAA);
}
void AT24C02_ReadMul(void){
	u8 data[5] = {0};
	// 从地址0x00开始连续读取5个数据到data数组中 
	AT24C02_ReadBlockData(0x00, data, 5);
	// 打印输出 
	u8 i;
	for(i = 0; i < 5; i++)
		printf("ADDR[%d], DATA[%#x]\n", i, data[i]);
}
void AT24C02_WriteMul(void){
	u8 Data[5] = {1, 2, 3, 4, 5};
	// 将5个数据分别写入到地址0 1 2 3 4寄存器中 
	AT24C02_WriteBlockData(0x00, Data, 5);
}

实验结果

 通过串口工具向内部写入单字节,读取单字节

通过串口工具向内部写入多字节,读取多字节

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

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

相关文章

makefile文件基本语法

一、makefile文件基本介绍 Makefile 文件是 make 工具使用的配置文件&#xff0c;它定义了如何自动化构建项目的规则和命令。Makefile 文件的主要作用是指定如何编译和链接程序&#xff0c;以及管理文件之间的依赖关系&#xff0c;从而实现高效的构建过程。 1.1 Makefile 的基…

【FreeRTOS】队列实验-分发数据给多个任务(赛车游戏)

目录 0 前言1 队列实验_分发数据给多个任务(赛车游戏)2 赛车游戏2.1 game.c2.2 注册队列2.3显示汽车2.4隐藏汽车2.5 CarTask2.6 car_game2.7 MX_FREERTOS_Init 3 总结 0 前言 学习视频&#xff1a; 【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS&#xff08;FreeRTOS教…

如何用Python实现山东省旅游数据爬虫与K-means满意度分析

&#x1f393; 作者&#xff1a;计算机毕设小月哥 | 软件开发专家 &#x1f5a5;️ 简介&#xff1a;8年计算机软件程序开发经验。精通Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等技术栈。 &#x1f6e0;️ 专业服务 &#x1f6e0;️ 需求定制化开发源码提…

AI一键视频多语言配音/翻译工具:打造无缝多语言视频体验

在全球化的今天,视频内容的传播不再受限于地域和语言。然而,如何高效地将视频内容翻译成多种语言并保持其自然度和流畅性,一直是业界面临的挑战。为了解决这一难题,我们推出了一款智能视频多语言AI配音和翻译工具——Linly Dubbing。该工具基于YouDub-webui的灵感进行了创新…

开源:cuda studio云原生一站机器学习、深度学习、大模型AI平台

文章目录 1、 cuda studio云原生一站机器学习、深度学习、大模型AI平台2、网址 1、 cuda studio云原生一站机器学习、深度学习、大模型AI平台 cube studio开源云原生一站式机器学习/深度学习/大模型AI平台&#xff0c;支持sso登录&#xff0c;多租户&#xff0c;大数据平台对接…

3级线性反馈移位寄存器在C3=1时可有4种线性反馈函数,设其初始状态为(a1,a2,a3)=(1,0,1),求各线性反馈函数的输出序列及周期

标题是题目 题解 1.补充知识 2.分析四种情况&#xff1a; 结合我所给的反馈数公式以及a31&#xff0c;可以得到反馈函数为: fC1*a3⊕C2*a2⊕C3*a1C1*a3⊕C2*a2⊕a1 附&#xff1a;别把初始状态为&#xff08;a1,a2,a3&#xff09;(1,0,1)带入&#xff0c;因为a1,a2,a3的值…

小程序学习day11-生命周期函数、组件所在页面的生命周期、自定义组件的插槽、自定义组件的父子通信

40、自定义组件&#xff08;续&#xff09;&#xff08;续&#xff09; &#xff08;10&#xff09;生命周期函数 1&#xff09;小程序里的全部生命周期函数 ①created&#xff08;在组件刚被创建时执行&#xff09;&#xff08;被创建&#xff0c;但未被放入页面&#xff09…

【AD9361 数字基带】多片基带内FPGA补偿 I/Q Rotation

I/Q 旋转 Rotation 在许多多通道射频系统中&#xff0c;如 AD-FMCOMMS5&#xff0c;甚至在 AD-FMCOMMS2、AD-FMCOMMS3 上&#xff0c;都需要测量或校正两个复数 &#xff08;I/Q&#xff09; RF 信号之间的相位差。 从纯粹的数学描述来看&#xff0c;单个正弦波没有相位&…

NNG简介和使用总结

先认识下ZeroMQ 参考&#xff1a;ZeroMQ详解 - 南哥的天下 - 博客园 (cnblogs.com) ZeroMQ&#xff08;简称ZMQ&#xff09;是一个基于消息队列的多线程网络库&#xff0c;其对套接字类型、连接处理、帧、甚至路由的底层细节进行抽象&#xff0c;提供跨越多种传输协议的套接字。…

RK3568开发笔记-buildroot系统scp拷贝文件报错dbclient no such file or directory

目录 ​​​​​​​ 前言 一、问题分析 什么是 Dropbear Dropbear 的优点 二、解决办法 总结 前言 在使用RK3588开发板进行系统开发时,很多开发者会选择使用Buildroot来构建自己的定制化系统。在开发过程中,通常需要通过scp(Secure Copy Protocol)命令将文件从本地计…

IDEA工具设置默认使用maven的settings.xml文件

第一步&#xff1a;打开idea工具&#xff0c;选中 File ——> New Projects Setup ——> Settings for New Projects 第二步&#xff1a;先设置下自动构建项目这个选项 第三步&#xff1a;选中 Build Tools ——> Maven&#xff0c;让后就可以设置自己安转的maven和se…

xlsx表格-A列的值需要从C列中匹配到然后输出C列旁边D列的值,怎么写公式?

公式&#xff1a; IFERROR(VLOOKUP(A1, C:D, 2, FALSE), "") 解释&#xff1a; 在VLOOKUP函数中&#xff0c;2表示要返回的列的索引。具体来说&#xff0c;VLOOKUP函数的语法如下&#xff1a; VLOOKUP(lookup_value, table_array, col_index_num, [range_lookup])…

功能测试和性能测试区别简析,软件测试公司如何开展有效测试?

软件功能测试旨在验证软件是否按照需求和设计规范正常运行&#xff0c;软件性能测试则是用来评估软件在特定负载条件下的行为和响应时间&#xff0c;确保软件在高并发和高需求的环境中能够稳定运行。 虽然两者都属于软件测试的重要组成部分&#xff0c;但它们的目的和重点却有…

零基础5分钟上手谷歌云GCP - 服务器自动扩展

简介 欢迎来到小李哥谷歌云GCP云计算知识学习系列&#xff0c;适用于任何无云计算或者谷歌云技术背景的开发者&#xff0c;让大家零基础5分钟通过这篇文章就能完全学会谷歌云一个经典的服务开发架构方案。 我将每天介绍一个基于全球三大云计算平台&#xff08;AWS, Azure, GC…

改编版猜数字小游戏,猜错了就黑屏(整蛊版本)

1. 前情提要 在前一篇博客中&#xff0c;我们了解到了如何获得随机数&#xff0c;并且通过运算可以规定所获得的这个随机数的范围在多少数值之间 那么接下来我们就需要去具体去实现猜数字游戏的各种布置 2. 布置主菜单 玩一个游戏&#xff0c;最开始的界面都会是一个主菜单…

iPhone13手机照片被误删,有什么方法可以恢复吗?

在日常使用手机时&#xff0c;我们可能因为误操作、手机崩溃、或者其他原因&#xff0c;导致iPhone13手机中的照片丢失。遇到这种情况&#xff0c;手机误删照片如何恢复&#xff1f;在本文中&#xff0c;我们将分享3个妙招&#xff0c;帮助您恢复iPhone13上误删的照片。 一、通…

2024年第二季度SSD出货量下滑18.4%,降至6750万部,但容量增长4.1%至90.6EB

2024年第二季度SSD Exabytes实现连续季度增长 仅企业级PCIe SSD有所增长&#xff1a;尽管所有其他类别均出现下滑&#xff0c;但企业级PCIe SSD的增长是由其所有终端市场需求增加所驱动的。总体SSD出货量&#xff1a;总体SSD出货量环比下降18.4%&#xff0c;降至6750万部&…

Leetcode JAVA刷刷站(76)最小覆盖子串

一、题目概述 二、思路方向 为了解决这个问题&#xff0c;我们可以使用滑动窗口的方法。滑动窗口是数组/字符串问题中常用的一个技巧&#xff0c;特别是用于寻找子数组或子字符串的问题。 这里的关键是&#xff0c;我们需要知道字符串t中每个字符的出现次数&#xff0c;并在遍…

【Python】函数高阶【上】

本篇文章将讲解函数高阶部分&#xff1a; &#xff08;1&#xff09;函数的嵌套 &#xff08;2&#xff09;闭包 &#xff08;3&#xff09;装饰器 1、函数的嵌套 Python是以函数为作用域&#xff0c;在作用域中定义的相关数据只能被当前作用域或子作用域使用。 &#xf…

(QT-UI)十四、在时间轴上绘制一段段时间片

本系列预计实现 ①刻度上方文字显示&#xff0c; ②时间轴拖动效果&#xff0c; ③时间轴刻度缩放&#xff0c; ④时间轴和其他控件联动显示&#xff0c; ⑤鼠标放置到时间轴&#xff0c;显示具体时间。 ⑥通过定时器&#xff0c;实时更新时间轴 ⑦时间轴上绘制时间片 完…