×
单片机 > 单片机程序设计 > 详情

写STM32 的I2c库函数

发布时间:2020-06-18 发布时间:
|

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  I2c  库函数 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
STM32 USB HID 键盘