写STM32 的I2c库函数

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该注意的地方。奋斗




猜你喜欢

转载自blog.csdn.net/weixin_39770778/article/details/78911360