I2C是一种多主从的串行通讯协议。STM32F1的I2C模块支持标速(最高100kHz)和高速(最高400kHz)两种工作模式。
一、I2C协议简介
标准的IIC接口有数据线SDA、时钟线SCL两条总线,只能工作于半双工模式,在设计中,对总线的负载电容有一定的要求,具体请查阅元件的技术手册确定。
IIC的通讯时序如图,通讯电平为正逻辑:
※数据发送的起始和终止信号为:SCL为高时,
起始:SDA下降沿 终止:SDA上升沿
※当总线空闲时,SDA和SCL均为高电平,总线需要外接上拉电阻,阻值5-10k。
※每次发送一字节(8bits)信号,MSB高位先发。
二、I2C通讯时序(使用时以具体被控芯片手册为准)
通讯时序(主机发送若干数据):
※主机引脚配置为open Drain,务必不要内部上拉、下拉
***①保持SCL=1,拉低SDA,产生起始信号。
***②拉低SCL,准备发送数据;
***③将数据高位Shift Out(SDA上),随后拉高SCL。在SCL=1期间SDA电平不能变动。
***④将数据依次输出
***⑤输出最后一位数据且SCL变为低电平后,主机接SDA引脚立即变换为高阻输入模式。
⑥主机向SCL输出一个时钟方波,在方波高位检测从机是否将SDA拉低(即发送ACK);若未收到表明发送未成功。
***⑦当主机检测到SDA被拉低时,表明从机准备好接收下一字节数据。随后主机拉高SCL,从机释放SDA,形成终止信号。若错误,主机也拉高SCL并记录错误。此时SCL=1,SDA=1。***
⑧准备下一次传输。
通讯时序(从机接收若干数据):
①从机引脚均配置为输入模式
***②检测开始信号,随后接收数据(可以通过SCL上升、下降沿触发方式检测校验)
③若接收没有出现错误,从机将SDA配置为开漏,在定义的数据长度(数据位+1ACK)的前一个时钟方波之后(时钟下降沿)拉低SDA发出ACK。若接受错误(例如一个时钟信号上升下降沿SDA数据不同)则不下拉。
④检测到SCL出现上升后,释放SDA。
⑤准备下一次传输。
※IIC数据以1个数据包(主机)+1位ACK(从机)格式传送:
发送地址的最后一位是数据方向位(R/W位),该位用“0”表示主机发送数据,“1”表示主机接收数据(R)。
※当STM32做主机时,在发送地址位后(10位地址模式为首序列最后一位)会根据方向位自动判断进入主发送器模式或主接收器模式**。**
三、STM32 LL库IIC驱动
STM32的I2C模块可以配置为从机输出、从机输入、主机输出、主机输入四种模式。
■主模式时,I2C接口启动数据传输并产生时钟信号。串行数据传输总是以起始条件开始并以停止条件结束。起始条件和停止条件都是在主模式下由软件控制产生。
■从模式时,I2C接口能识别它自己的地址(7位或10位)和广播呼叫地址。软件能够控制开启或禁止广播呼叫地址的识别。
※ SCL时钟信号、起始终止信号由主机输出 , 应答始终由从机应答 ;从机由主机发送地址选中(主机和从机的区别)。
1.IIC时钟配置:
在I2C_CR2寄存器中Freq[5:0]设定该模块的输入时钟( APH1旁路时钟输入,最高36MHz )。输入时钟的频率至少为:
● 标准模式下为:2MHz ● 快速模式下为:4MHz
若使用CubeMX进行模块的配置,则生成的模块初始化函数中自动设置
2.相关寄存器
①自身地址寄存器(I2C_OAR1):
STM32的IIC模块支持双地址模式,即 可以设置两个自身地址 :
自身地址寄存器2(I2C_OAR2):
*②时钟控制寄存器(I2C_CCR): * (CubeMX自动配置)
在I2C_CR2中设置完成模块输入时钟后对SCL输出时钟进行配置
③控制寄存器(I2C_CR1)
④控制寄存器(I2C_CR2)
⑤数据寄存器(I2C_DR)
⑥状态寄存器(I2C_SR1)
PEC校验的使用较为复杂,使用时请具体参看手册
对包序号的判断(第几个包?)从起始条件开始计算。
⑦状态寄存器(I2C_SR2)
3.传输时序(请参考传输时序进行开发)
** ①主发送器模式**
※7位地址最后一位为R/W位,10位帧头最后一位为R/W,此时设置为"0",由本机发送
** ②主接收器模式(主模式下起始、终止条件都由本机发出)**
※7位地址最后一位为R/W位,10位帧头最后一位为R/W,此时设置为"1",由本机发送
** ③从接收器模式**
※地址R/W位非本机发送,由硬件自动比对本机地址判断是否被选中。此时R/W位为"0"
** ④从发送器模式**
※地址R/W位非本机发送,由硬件自动比对本机地址判断是否被选中。此时R/W位为"0"
关于主从模式:
*** ■模块默认处于从模式***
*** ■当本机通过START位主动发送起始条件时,进入主模式***
*** ■当主模式最后一字节传输完成后,由本机置STOP位发送停止条件,进入从模式***
*** ■从模式下模块自动检测总线上的电平变化***
*** ■发送/接收模式的选择通过硬件自动判断R/W实现***
■注意发送/接收地址后,读ADDR才能进入主/从模式下一步的数据传输
LL库函数:
1、初始化结构体LL_I2C_InitTypeDef
typedef struct
{
uint32_t PeripheralMode;/*
选择模块工作模式,通过LL_I2C_SetMode()实现
@ref LL_I2C_MODE_I2C //I2C模式
LL_I2C_MODE_SMBUS_HOST //
LL_I2C_MODE_SMBUS_DEVICE //
LL_I2C_MODE_SMBUS_DEVICE_ARP //
*/
uint32_t ClockSpeed;/*
配置时钟频率(< 400kHz(高速);< 100kHz(标准));通过LL_I2C_SetClockPeriod()、LL_I2C_SetDutyCycle()、
LL_I2C_SetClockSpeedMode()、LL_I2C_ConfigSpeed()实现
//示例: 若需要100kHz时钟,则输入100000
*/
uint32_t DutyCycle;/*
设置高速模式(仅)下信号占空比;通过LL_I2C_SetDutyCycle()实现
@ref LL_I2C_DUTYCYCLE_2 //低:高=2
LL_I2C_DUTYCYCLE_16_9 //低:高=16:9
*/
uint32_t OwnAddress1;/*
设置自身的主地址,通过 LL_I2C_SetOwnAddress1()实现
//10位模式最大为0x3FF, 7位最大为0x7F
※该结构体及模块初始化函数不提供设置第二地址(副地址)的方式
*/
uint32_t TypeAcknowledge;/*
配置ACK使能;通过LL_I2C_AcknowledgeNextData()实现
@ref LL_I2C_ACK //在接收到一个字节后返回一个应答(匹配的地址或数据)
LL_I2C_NACK //无应答
*/
uint32_t OwnAddrSize;/*
设置自身地址长度;通过LL_I2C_SetOwnAddress1()实现
※在双地址模式下只能设置为7位长度
@ref LL_I2C_OWNADDRESS1_7BIT //7位长度
LL_I2C_OWNADDRESS1_10BIT //10位长度
*/
} LL_I2C_InitTypeDef;
2、工作模式设置
void LL_I2C_SetMode(I2C_TypeDef *I2Cx, uint32_t PeripheralMode);/*
设置模块工作模式
@reg CR1- >SMBUS、SMBTYPE、ENARP
@ref LL_I2C_MODE_I2C //I2C模式
LL_I2C_MODE_SMBUS_HOST //
LL_I2C_MODE_SMBUS_DEVICE //
LL_I2C_MODE_SMBUS_DEVICE_ARP //
*/
uint32_t LL_I2C_GetMode(I2C_TypeDef *I2Cx);
3、模块开启/关闭函数
void LL_I2C_Enable(I2C_TypeDef *I2Cx);/*
使能(开启)I2C模块
@reg CR1- >PE
*/
void LL_I2C_Disable(I2C_TypeDef *I2Cx);/*
禁用(关闭)I2C模块
@reg CR1- >PE
*/
uint32_t LL_I2C_IsEnabled(I2C_TypeDef *I2Cx);/*
检测I2C模块是否开启
@retval: 1//开启
0//关闭
*/
4.时钟延长控制(ClockStretching)
参考https://blog.csdn.net/happygaohualei/article/details/52864694
通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行。一般情况是从机在发送过程中将SCL接地,主机无法拉高电平后暂停传输,从机处理完成任务后释放SCL,传输继续。 大多数外设不支持时钟延长功能。
※STM32默认允许时钟延长
void LL_I2C_EnableClockStretching(I2C_TypeDef *I2Cx);/*
使能时钟延长。
@reg CR1- >NOSTRETCH
*/
void LL_I2C_DisableClockStretching(I2C_TypeDef *I2Cx);/*
禁用时钟延长
@reg CR1- >NOSTRETCH
*/
uint32_t LL_I2C_IsEnabledClockStretching(I2C_TypeDef *I2Cx);/*
检测是否启用时钟延长
@retval 1//启用
*/
5.自身地址设置
地址在从模式下由硬件自动比较
void LL_I2C_SetOwnAddress1(I2C_TypeDef *I2Cx, uint32_t OwnAddress1, uint32_t OwnAddrSize);/*
设置自身主地址,并设置地址模式(7位或10位)
@reg ADD0 ADD1_7 ADD8_9 ADDMODE
@param OWnAddress//自身主地址
OwnAddrSize//地址模式(7位或10位),@ref LL_I2C_OWNADDRESS1_7BIT
LL_I2C_OWNADDRESS1_10BIT
※若启用第二地址,设置为7位地址模式
*/
void LL_I2C_EnableOwnAddress2(I2C_TypeDef *I2Cx);/*
双地址模式(启用第二地址),启用后只支持7位地址模式
@reg OAR2- >ENDUAL
*/
void LL_I2C_SetOwnAddress2(I2C_TypeDef *I2Cx, uint32_t OwnAddress2);/*
设置第二地址(7位)
*/
void LL_I2C_DisableOwnAddress2(I2C_TypeDef *I2Cx);/*
禁用双地址模式
@reg OAR2- >ENDUAL
*/
6.广播呼叫控制(General Call)
当本机作为从机接收到地址0x00时,将被选中并发ACK(如果使能ACK),0x00即广播地址。
void LL_I2C_EnableGeneralCall(I2C_TypeDef *I2Cx);/*
使能广播呼叫。
@reg CR1- >ENGC
*/
void LL_I2C_DisableGeneralCall(I2C_TypeDef *I2Cx);/*
禁用广播呼叫.
@reg CR1- >ENGC
*/
uint32_t LL_I2C_IsEnabledGeneralCall(I2C_TypeDef *I2Cx);/*
检测是否启用了广播呼叫。
@retval 1//启用
*/
7.模块时钟配置
请在模块关闭的状态下配置*
void LL_I2C_SetClockPeriod(I2C_TypeDef *I2Cx, uint32_t ClockPeriod);/*
配置SCL时钟周期;
@reg CCR- >CCR
@note 具体配置参见MANUAL的CCR寄存器(前有)
*/
uint32_t LL_I2C_GetClockPeriod(I2C_TypeDef *I2Cx);/*
读取CCR- >CCR的值
*/
void LL_I2C_SetClockSpeedMode(I2C_TypeDef *I2Cx, uint32_t ClockSpeedMode);/*
设置速度模式(标准/高速)
@reg CCR- >FS
@ref LL_I2C_CLOCK_SPEED_STANDARD_MODE //标准模式(最高100kHz)
LL_I2C_CLOCK_SPEED_FAST_MODE //高速模式(最高400kHz)
@note ※高速模式下可设置信号占空比
*/
void LL_I2C_SetPeriphClock(I2C_TypeDef *I2Cx, uint32_t PeriphClock);/*
设置模块的输入时钟(e.g.36Mhz,84Mhz...);单位Hz,函数自动与10MHz对齐;
@reg CR2- >FREQ
*/
uint32_t LL_I2C_GetPeriphClock(I2C_TypeDef *I2Cx);/*
读取模块的输入时钟(由上一函数设置)
@reg CR2- >FREQ =@retval
*/
void LL_I2C_SetDutyCycle(I2C_TypeDef *I2Cx, uint32_t DutyCycle);/*
设置信号占空比(※仅高速模式)
@reg CCR- >DUTY
@ref LL_I2C_DUTYCYCLE_2 //低:高=2
LL_I2C_DUTYCYCLE_16_9 //低:高=16:9
*/
uint32_t LL_I2C_GetDutyCycle(I2C_TypeDef *I2Cx);/*
获取信号占空比(仅高速模式);
@reg CCR- >DUTY
*/
void LL_I2C_SetRiseTime(I2C_TypeDef *I2Cx, uint32_t RiseTime);/*
设置主模式下SCL的最大上升时间。
@reg TRISE- >TRISE[5:0]
@note 默认值0x02 计算方法请详细参看MANUAL
*/
uint32_t LL_I2C_GetRiseTime(I2C_TypeDef *I2Cx);/*
获取主模式下SCL的最大上升时间。
@retval = TRISE[5:0]
*/
时钟配置整合函数(优先使用):
void LL_I2C_ConfigSpeed(I2C_TypeDef I2Cx, uint32_t PeriphClock, uint32_t ClockSpeed,
uint32_t DutyCycle);/
配置输入时钟、时钟周期、占空比(高速模式下)
*/
8.SMbus部分
暂不补充
9.关键控制
①ACK控制(接收时)
void LL_I2C_AcknowledgeNextData(I2C_TypeDef I2Cx, uint32_t TypeAcknowledge);/
配置应答使能(是否接收到一个数据(地址或数据)后返回一个应答)
@reg CR1->ACK
@ref LL_I2C_ACK //返回应答
LL_I2C_NACK //不返回应答
*/
②生成起始条件与终止条件
void LL_I2C_GenerateStartCondition(I2C_TypeDef I2Cx);/
生成起始条件(S),可与SB配合检测起始条件的生成状况
@reg CR1->START
@note 起始条件发送后START位自动清除
*/
void LL_I2C_GenerateStopCondition(I2C_TypeDef I2Cx);/
发送终止条件(P)
@reg CR1->STOP
@note 终止条件发送后STOP位自动清除
*/
③读取/发送操作
uint8_t LL_I2C_ReceiveData8(I2C_TypeDef I2Cx);/
接收8位数据。
读取一次DR,获得8位数据
*/
void LL_I2C_TransmitData8(I2C_TypeDef I2Cx, uint8_t Data);/
发送8位数据。
向DR写入一个字节
*/
I2C中断
(摘自RM008)
从中可以看出,I2C模块的中断掩码非常少,一旦开启可靠性将大大降低,因此一般不使用中断功能
中断总共有两个入口,条件检测、中断配置类似UART和SPI
中断掩码控制:
void LL_I2C_EnableIT_BUF(I2C_TypeDef I2Cx);/
置位ITBUFEN
*/
void LL_I2C_DisableIT_BUF(I2C_TypeDef I2Cx);/
清零ITBUFEN
*/
void LL_I2C_EnableIT_ERR(I2C_TypeDef I2Cx);/
置位ITERREN
*/
void LL_I2C_DisableIT_ERR(I2C_TypeDef I2Cx);/
清零ITERREN
*/
void LL_I2C_EnableIT_EVT(I2C_TypeDef I2Cx);/
置位ITEVTEN
*/
void LL_I2C_DisableIT_EVT(I2C_TypeDef I2Cx);/
清零ITEVTEN
*/
I2C状态判断 ※在软件方式使用I2C时非常重要
读取状态位判断的格式为 LL_I2C_IsActiveFlag_XXX()
使用请参见前面的时序图。
DMA控制
I2C模块同样可以配置DMA传输,这通常使用在主模式下传输大量数据的情况。
但LL库使用DMA时容易发生通讯错误,因此不推荐使用。