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