历史上的今天
今天是:2025年04月11日(星期五)
2019年04月11日 | 写STM32 的I2c库函数
2019-04-11 来源:eefocus
I2c协议:
I2c是一种双向串行通讯标准,常用于嵌入式系统中。利用I2c总线可以利用有限的I/O接口来扩展多功能的外围设备。主要由SCL(时钟线)和SDA(数据线组成)。I2c总线上可以连接多个带有I2c接口的设备,每个设备都有自己唯一的地址。设备地址一般看该设备对应的手册。当总线空闲的时候SDA线和SCL线都为高电平,如果SCL处于高电平时SDL产生下降沿则认为起始位,如果SCL处于高电平SDA产生上升沿时则为停止位。

主发送从接收:
主要讲的是Stm32配置I2c协议成主发送从接收模式,我们之前看到的都是调用STM32的I2c的官方库函数来配置I2c,今天呢我们是自己配置寄存器来写一个I2c的库函数。
第一步:开启时钟外设看STM32对应的手册来配置相应的寄存器。我写的是STM32f303所以看对应手册开启时钟外设寄存器为RRC_APB1ENR寄存器。再次强调对应的芯片不同手册里面要配置的寄存器也不一样,一切要按照对应的手册写,这里只是提供一个参考。
///Reinitialize the I2C peripheral registers to their default reset values
void I2c::Initialize(uint8_t channel)
{
base = ( I2C_TypeDef * ) ( I2C1_BASE + ( ( channel - 1 ) * baseRegOffset ) );
if (_I2c1 == channel)
{
// I2C 1 clock enable
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
}
else if (_I2c2 == channel)
{
//I2C 2 clock enable
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
}
}
第二步:配置时钟频率(一般模式,快速模式,高速模式)。主要配置以下几个寄存器。把以下几个寄存器配置好了I2c的时序也相应的会产生。
///set I2C master Clock frequency
void I2c::ConfigClk(uint8_t SCLL, uint8_t SCLH, uint8_t PECSC, uint8_t SDADEL, uint8_t SCLDEL)
{
Disable();
//I2CCLK timing setting.
base->TIMINGR &= ~I2C_TIMINGR_SCLL_Msk;
base->TIMINGR &= ~I2C_TIMINGR_SCLH_Msk;
base->TIMINGR &= ~I2C_TIMINGR_PRESC_Msk;
base->TIMINGR &= ~I2C_TIMINGR_SDADEL_Msk;
base->TIMINGR &= ~I2C_TIMINGR_SCLDEL_Msk;
base->TIMINGR |= SCLL << I2C_TIMINGR_SCLL_Pos;
base->TIMINGR |= SCLH << I2C_TIMINGR_SCLH_Pos;
base->TIMINGR |= PECSC << I2C_TIMINGR_PRESC_Pos;
base->TIMINGR |= SDADEL << I2C_TIMINGR_SDADEL_Pos;
base->TIMINGR |= SCLDEL << I2C_TIMINGR_SCLDEL_Pos;
Enable();
}
设置频率时我这里给了一个例子可以参考,往相应的寄存器输入值,这个值也是在手册里面找的。不能自己乱写哈。
///Set clockspeed
///Mode of timings settings
///Standard mode Fast-mode Fast-mode Plus
void I2c::I2cClockSpeed(uint8_t speed)
{
//Standard mode100 kHz
if (100 == speed)
{
ConfigClk(0x13, 0xf, 1, 0x2, 0x4);
}
//Fastmode mode 400kHz
if (400 == speed)
{
ConfigClk(0x9, 0x3, 0, 0x1, 0x3);
}
//Fastmode Plus
if (500 == speed)
{
ConfigClk(0x6, 0x3, 0, 0x0, 0x01);
}
}
第三步:初始化对应的Gpio引脚,在这里要注意的是一般I2c都配置成开漏输出就可以了。
///Reinitialize the I2C peripheral registers to their default reset values
///I2Cx: where x can be 1, 2 to select the I2C peripheral
void I2c::ConfigPins(I2cPinConfig pinConfig)
{
Gpio sclPin;
Gpio sdaPin;
sclPin.Initialize(pinConfig.sclCh, pinConfig.sclPin);
sclPin.ConfigAltFunc(pinConfig.sclAltNum);
sclPin.ConfigMode(Gpio::_Alt);
sclPin.ConfigSpeed(Gpio::_HighSpeed);
sdaPin.Initialize(pinConfig.sdaCh, pinConfig.sdaPin);
sdaPin.ConfigAltFunc(pinConfig.sdaAltNum);
sdaPin.ConfigMode(Gpio::_Alt);
sdaPin.ConfigSpeed(Gpio::_HighSpeed);
sclPin.ConfigOutputType(Gpio::_OpenDrain);
sdaPin.ConfigOutputType(Gpio::_OpenDrain);
sclPin.ConfigInputType(Gpio::_NoPull);
sdaPin.ConfigInputType(Gpio::_NoPull);
}
第四步:我们可以看对应手册进行读写了。先看I2c作为主模式下是如何写的。我们可以看到写的流程图及产生以下代码:

写过程:
///sends one byte
void I2c::WriteByte(const uint8_t txData, uint8_t address)
{
//Configure slave address and configure direction to write
base->CR2 = ( base->CR2 & ~( I2C_CR2_SADD_Msk | I2C_CR2_RD_WRN ) ) | address;
//Set number of bytes to be write
base->CR2 = ( base->CR2 & ~I2C_CR2_NBYTES_Msk ) | ( 1 << I2C_CR2_NBYTES_Pos );
//Allow 12C module to send STOP automatically after all bytes are transferred
base->CR2 |= I2C_CR2_AUTOEND;
//Send a start condition to begin writing data
base->CR2 |= I2C_CR2_START;
while ( !( base->ISR & I2C_ISR_TXIS ) ) {}
base->TXDR = txData;
}
读过程:
///Recevice one byte
uint8_t I2c::ReadByte(uint8_t address)
{
uint8_t rxData;
//Configure slave address and configure direction to read
base->CR2 = (base->CR2 & ~I2C_CR2_SADD_Msk) | address | I2C_CR2_RD_WRN;
//Set number of bytes to be read
base->CR2 = (base->CR2 & ~I2C_CR2_NBYTES_Msk) | 1 << I2C_CR2_NBYTES_Pos;
//Allow 12C module to send STOP automatically after all bytes are transferred
base->CR2 |= I2C_CR2_AUTOEND;
//Send a start condition to begin receiving data
base->CR2 |= I2C_CR2_START;
while (0 == base->ISR & I2C_ISR_RXNE) {}
rxData = base->RXDR;
return rxData;
}
分析一下以上代码,结合上面的流程图就是先确定寻址找到从机地址确定读写方向,设置要读写的字节。我这里
给的例子是读写的一个字节所以我就给了一个1到CR2_NBYTES该寄存器里面。配置一个停止信号、一个start起始
信号和配置一个从机设备地址。有人看到这里可能会问ACK,NACK不要我们自己写吗,是的不用,我们主机只负
责发送一个start信号和一个停止信号给从机设备,从机设备硬件会自己给出应答包。最后我会放一张用逻辑分析仪
分析I2c读写的Eeprom过程图片方便更好理解。

当看到逻辑分析仪scl线和sda线由高电平被拉低就是代表通讯开始,从机接收成功会对应发送ACK。所以ACK应答不需要主机写。配置I2c的库函数到这里感觉一切要以对应的手册为基准。大概我对于I2c的理解就是这些了。下次我们写一下I2c读取Eeprom该注意的地方
下一篇:STM32寄存器与库函数
史海拾趣
|
我做的毕业设计是基于AT89S52单片机的信号发生器,要求实现通过键盘选择不同频率不同波形的输出。具体要求: 1、实现1HZ,2HZ、5HZ、10HZ、20HZ、50HZ、100HZ、200HZ、500HZ、1KHZ、2KHZ、5KHZ、10KHZ、20KHZ、50KHZ、100KHZ、200KHZ、500KHZ的四 ...… 查看全部问答> |
|
昨天去了顺德,才600米,客户说网桥不好,ping出现丢包,我去一看才知道: 距离不到1公里左右,灵敏度到-60dbm,信号非常好,但是客户说丢包,ping楼顶也是不行。 我只有过去,一看差点晕倒,雷人 网桥后接tplink的交换机,45元那种; 从2楼到1 ...… 查看全部问答> |
|
最近我用的一款单片机...ST的UPSD3254A,也就是款和51指令都兼容,只不过加了很多额外功能的单片机 现在用的是40M的晶振,本来...单片机X1,X2脚出来,接个晶振再接2个27P的瓷片电容对地就万事了,可有块板上,在晶振X1脚边上又引出根线接了2uH的 ...… 查看全部问答> |
|
用8255A的A端口接8位二进制输入,B端口和C端口各接8只发光二极管显示二进制数。编写一段程序,把A端口的读入数据送B端口显示,而C端口的各位则采用置0/置1的方式显示A端口的值。 不知道怎么写啊,先谢谢各位了,在8086环境下。… 查看全部问答> |
|
strmiids.lib、dmoguids.lib库找不到? 环境:EVC4+SP4 PB5.0 问题:使用Directshow,包含头文件, 这些头文件使用到了库文件strmiids.lib、dmoguids.lib,提示错误是canno open file srmiids.lib、dmoguids.lib, 我看了哈SDK,是 ...… 查看全部问答> |
|
以下这个控制LED渐明渐暗的程序,现在的现象是:第一个灯渐明渐暗后,紧接着第二个灯开始渐明渐暗了,而我想要的效果是:当第一个灯亮完熄灭后,停留数秒一段时间,才渐渐点亮第二个灯,我尝试在几个位置加上delay延时,但没效果,有时还会出现闪烁 ...… 查看全部问答> |




