使用STM8L的硬件I2C(三)硬件I2C的中断读写处理
其他系列文章参见
(一)硬件I2C的简介
(二)硬件I2C的事件和检测
(四)硬件I2C的使用注意
1、I2C初始化
关于I2C初始化的流程,标准库中的注释写的非常清晰:
* 1. Enable peripheral clock using CLK_PeripheralClockConfig(CLK_Peripheral_I2Cx,
* ENABLE) function (Refer to the product datasheet for the available I2C
* peripherals)
*
* 2. Program the Mode, duty cycle , Own address, Ack, Speed and Acknowledged
* Address using the I2C_Init() function.
*
* 3. Optionally you can enable/configure the following parameters without
* re-initialization (i.e there is no need to call again I2C_Init() function):
* - Enable the acknowledge feature using I2C_AcknowledgeConfig() function
* - Enable the dual addressing mode using I2C_DualAddressCmd() function
* - Enable the general call using the I2C_GeneralCallCmd() function
* - Enable the clock stretching using I2C_StretchClockCmd() function
* - Enable the fast mode duty cycle using the I2C_FastModeDutyCycleConfig()
* function
* - Enable the PEC Calculation using I2C_CalculatePEC() function
* - For SMBus Mode:
* - Enable the Address Resolution Protocol (ARP) using I2C_ARPCmd() function
* - Configure the SMBusAlert pin using I2C_SMBusAlertConfig() function
*
* 4. Enable the interrupt using the function I2C_ITConfig() if you need
* to use interrupt mode.
*
* 5. When using the DMA mode
* - Configure the DMA using DMA_Init() function
* - Active the needed channel Request using I2C_DMACmd() or
* I2C_DMALastTransferCmd() function
* Note: When using DMA mode, I2C interrupts may be used at the same time to
* control the communication flow (Start/Stop/Ack... events and errors).
*
* 6. Enable the I2C using the I2C_Cmd() function.
*
* 7. Enable the DMA using the DMA_Cmd() function when using DMA mode in the
* transfers.
*
* Note: The external Pull-up resistors must be connected on SDA and SCL.
*
流程很标准,翻译如下:
- 使能I2C时钟 (CLK_PeripheralClockConfig函数)
- 初始化总线参数 (I2C_Init函数)
通信频率、I2C模式(Master/Slave/DMA)、Duty、Ack自动、地址类型(7/10) - 用其他设定函数重新设置或者enable选项(无需再次调用I2C_init()函数)
- 如果使用中断模式,开中断(I2C_ITConfig函数)
- 如果使用DMA,做DMA相关设置(DMA_Init/I2C_DMALastTransferCmd))
- 使能I2C(I2C_Cmd函数)
注意必须要要先使能I2C时钟,否则后续设定无效,直接会导致通信不正常
2、STM8L I2C的中断类型
I2C的硬件中断有3中类型,分别为Event、Buffer、Error。
Event是I2C协议自身状态的中断,如Start、Stop的发送、地址发送完成、数据发送、接收完成等。
Buffer实际上要依附于Event中断,提供缓冲区相关的中断。比如缓冲(数据寄存器)可写、缓冲(数据寄存器)可读等。
Error顾名思义,是错误相关的中断。
具体表如下:
3、I2C Master的Read与Write
I2C外设都走I2C协议,但是在协议设计上却不尽相同。
一般来说,用I2C这种协议的外设相对复杂,自身也会进行一些设定,
从而自身内部会存在多个寄存器以区分不同的设定以及数据存储,以便和外界沟通。
与这种I2C外设交互时,往往不能只发送外设地址,还需要指定目标寄存器。
而I2C协议上只规定发送外设地址,因此寄存器地址只能作为应用层数据承载。
(有些I2C设备很简单,不需要指定寄存器,只需指定外设地址就可以。比如BH1750)
对于Write来说,外设寄存器地址也是数据,
寄存器地址本身和要写入寄存器的数据并没有区别,
只要串行写入多个数据(寄存器地址+真正数据)就可以正常工作。
而对于Read来说,有所不同。
因为I2C只能指定外设地址,那目标寄存器的指定自然存在一个问题:
必须先告知I2C外设,要读的目标寄存器的地址是什么,而告知本身的实现需要一个写操作。
也就是说,作为Read,要先写数据给外设(告知寄存器地址),然后再真正去读。
但是这个读之中的先写和后读如何实施,不同的I2C外设处理也不尽相同,
需要参考外设的手册,因为这实际上是一个应用层协议的设计问题。
因此,通常情况下对于大多数I2C外设,它的Read流程会比Write流程操作要多一步,复杂一些。
以常用的温度压力传感器博世BMP280为例,它的I2C Read流程如下:
- 先对外设地址做Write,发送要读取的BMP280寄存器地址
- 发送ReStart表示写结束
- 紧跟Restart发起对外设地址的Read(寄存器地址还要再送一遍)
可以看出,它的先写后读之间是用一个RepeatStart/ReStart做衔接区分的。
(根据BMP280手册,这个衔接可以用Stop或者Restart,但是不能用Stop+Start)
而它的Write,如下图:就简单的多,把寄存器地址和数据直接串行写就可以。
3.1 I2C Master Write
还是以BMP280为例,介绍使用中断方式进行读写流程的处理。
写流程很简单:
先回顾一下标准库的抽象出的I2C写流程:
再来看具体的实现流程:
写函数:
- 开中断
- 开Ack自动回应
- 生成Start
- 循环等待完成标志(同步写)(异步写可以在主循环中判断完成标志或者直接callback)
中断处理:
-
中断到来,用I2C_GetLastEvent()获取当前总线状态
-
根据总线状态,执行写地址、写数据、生成Stop、关中断的各种操作,具体如下:
EV5:送Slave地址(Write) EV6/EV8:每次发送1字节数据,发送完最后一个字节关中断(I2C_IT_BUF) EV8_2:发送Stop,关中断(I2C_IT_ERR和I2C_IT_EVT)
-
置标志位(表示写完成)
3.2 I2C Master Read
相对而言,Read要比Write复杂多了。
一方面是因为上面提到的外设寄存器地址指定的问题,另一方面更复杂的是NAck和Stop的发送规则。
后者是模拟方式根本不用考虑的的问题。
但是作为硬件处理,又加上中断,处理速度快,Nack和Stop的发送时机变得非常重要,该发Nack的时候
发送了Ack会导致整个流程都不正常。STD标准库为说明这个问题,简直是长篇大论。
基本上分为:1字节读取、2字节读取,处理方式各不相同,且中断模式和Polling也不相同。
先回顾一下标准库的抽象出的I2C读流程:
再来看具体的实现流程:(Nack和Stop设置的问题另外再说明)
读函数:
- 开中断
- 开Ack自动回应
- 生成Start
- 循环等待完成标志(同步读)(异步读可以在主循环中判断完成标志或者直接callback)
中断处理:
-
中断到来,用I2C_GetLastEvent()获取当前总线状态
-
根据总线状态,执行写地址、读数据、生成Ack/Nack/Stop的各种操作,具体如下
// 先写部分(送寄存器地址): EV5:送Slave地址(Write) EV6/EV8:写寄存器地址(作为数据) EV8_2:发送ReStart(即没有发送STOP的情况下再发一次Start) // 后读部分(读寄存器地址): EV5:送Slave地址(Read) EV6:如果仅需读取字节数n=1,则直接关Ack并发送Stop,n>=2,则不做动作 EV7:先读数据,然后判断剩余字节n,如果n=1,则关Ack并发送Stop,如n=0,则关中断
-
数据写完成后,置标志位(表示写完成)
(Ack设置为自动回复,所以无需显示执行发送Ack)
需要注意:EV6实际有2个定义,分别代表Master写就绪和Master读就绪,
都称为EV6,但实际不会冲突,因为它们是出现在不同的模式种。
EV6(Write):I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED
EV6(Read):I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED
4、代码示例
以下代码在BMP280+STM8L152C6T6上运行正常。
4.1 I2C读写缓冲区和状态定义
#define BUFFERSIZE (32) //BMP280最多一次性会读取24字节,所以缓冲区要>=24字节
#pragma pack(1)
struct i2c_dev {
uint8_t addr;
uint8_t state;
uint8_t tx_idx;
uint8_t rx_idx;
uint8_t tx_len;
uint8_t rx_len;
uint8_t TxBuffer[BUFFERSIZE];
uint8_t RxBuffer[BUFFERSIZE];
};
#pragma pack()
enum {
BMP280_I2C_RESET = 0x00,
BMP280_I2C_WRITE_REG,
BMP280_I2C_WRITE_REG_END,
BMP280_I2C_READ_REG1,
BMP280_I2C_READ_REG2,
BMP280_I2C_READ_REG_END
};
4.1 I2C初始化
int8_t i2c_init(void)
{
/* I2C clock Enable*/
CLK_PeripheralClockConfig(CLK_Peripheral_I2C1, ENABLE);
I2C_DeInit(I2C1);
/* Initialize I2C peripheral */
I2C_Init(I2C1, 100000, 0xA1,
I2C_Mode_I2C, I2C_DutyCycle_2,
I2C_Ack_Enable, I2C_AcknowledgedAddress_7bit);
/* Enable Buffer and Event Interrupt*/
I2C_ITConfig(I2C1, (I2C_IT_TypeDef)(I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF) , ENABLE);
/* Enable I2C */
I2C_Cmd(I2C1, ENABLE);
}
4.2 I2C写函数
int8_t i2c_reg_write(uint8_t i2c_addr, uint8_t reg_addr, uint8_t *reg_data, uint16_t length)
{
uint8_t count = 0;
// 初始化
reset_dev(&_i2c_dev);
// 指定Slave目标地址
_i2c_dev.addr = i2c_addr;
_i2c_dev.state = BMP280_I2C_WRITE_REG;
// Write数据从用户缓冲复制到I2C缓冲
// 第一字节数据是寄存器地址
_i2c_dev.TxBuffer[0] = reg_addr;
memcpy(_i2c_dev.TxBuffer+1, reg_data, length);
_i2c_dev.tx_len = length + 1;
_i2c_dev.tx_idx = 0;
// 打开Ack、开中断
I2C_AcknowledgeConfig(I2C1, ENABLE);
I2C_ITConfig(I2C1, (I2C_IT_TypeDef)(I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF) , ENABLE);
// Start发送、流程开始
I2C_GenerateSTART(I2C1, ENABLE);
// 写完成同步处理,带简单超时判断。如果异步处理可去除这部分代码
while (_i2c_dev.state != BMP280_I2C_WRITE_REG_END) {
delay_ms(1);
if ( count++ >= 300 ) {
break;
}
}
return 0;
}
4.3 I2C读函数
int8_t i2c_reg_read(uint8_t i2c_addr, uint8_t reg_addr, uint8_t *reg_data, uint16_t length)
{
uint8_t count = 0;
// 初始化
reset_dev(&_i2c_dev);
// 指定Slave目标地址
_i2c_dev.addr = i2c_addr;
_i2c_dev.state = BMP280_I2C_READ_REG1;
// 写缓冲初始化:先写部分的数据是Slave地址
_i2c_dev.TxBuffer[0] = reg_addr;
_i2c_dev.tx_len = 1;
_i2c_dev.tx_idx = 0;
// 读缓冲初始化
memcpy(_i2c_dev.RxBuffer, 0, sizeof(_i2c_dev.RxBuffer));
_i2c_dev.rx_len = length;
_i2c_dev.rx_idx = 0;
// 打开Ack、开中断
I2C_AcknowledgeConfig(I2C1, ENABLE);
I2C_ITConfig(I2C1, (I2C_IT_TypeDef)(I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF) , ENABLE);
// Start发送、流程开始
I2C_GenerateSTART(I2C1, ENABLE);
// Read完成同步处理,带简单超时判断。如果异步处理可去除这部分代码
while (_i2c_dev.state != BMP280_I2C_READ_REG_END){
delay_ms(1);
if ( count++ >= 300 ) {
break;
}
}
// 读出的数据复制到用户缓冲
memcpy(reg_data, _i2c_dev.RxBuffer, length);
return 0;
}
4.4 中断处理函数
INTERRUPT_HANDLER(I2C1_IRQHandler, 29)
{
uint16_t state = 0;
uint8_t direct = 0;
state = I2C_GetLastEvent(I2C1);
if( _i2c_dev.state == BMP280_I2C_WRITE_REG ) { // 写处理
switch (state) {
/* EV5 */
case I2C_EVENT_MASTER_MODE_SELECT :
/* Send slave Address for write */
I2C_Send7bitAddress(I2C1, _i2c_dev.addr << 1, I2C_Direction_Transmitter);
break;
/* EV6 */
case I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED:
/* EV8 */
case I2C_EVENT_MASTER_BYTE_TRANSMITTING:
if (_i2c_dev.tx_len - _i2c_dev.tx_idx > 0) {
/* Send the first Data */
I2C_SendData(I2C1, _i2c_dev.TxBuffer[_i2c_dev.tx_idx]);
_i2c_dev.tx_idx++;
} else {
I2C_ITConfig(I2C1, I2C_IT_BUF, DISABLE);
}
break;
/* EV8_2 */
case I2C_EVENT_MASTER_BYTE_TRANSMITTED:
/* Send STOP condition */
I2C_GenerateSTOP(I2C1, ENABLE);
I2C_ITConfig(I2C1, (I2C_IT_TypeDef)(I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF) , DISABLE);
_i2c_dev.state = BMP280_I2C_WRITE_REG_END;
break;
default:
break;
}
} else if( _i2c_dev.state == BMP280_I2C_READ_REG1 || _i2c_dev.state == BMP280_I2C_READ_REG2 ) { // 读处理
switch (state) {
/* EV5 */
case I2C_EVENT_MASTER_MODE_SELECT :
/* Send slave Address for write */
direct = (_i2c_dev.state == BMP280_I2C_READ_REG1 ? I2C_Direction_Transmitter : I2C_Direction_Receiver);
I2C_Send7bitAddress(I2C1, (_i2c_dev.addr << 1), direct);
break;
/* EV6 */
case I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED:
/* EV8 */
case I2C_EVENT_MASTER_BYTE_TRANSMITTING:
if (_i2c_dev.tx_len - _i2c_dev.tx_idx > 0) {
I2C_SendData(I2C1, _i2c_dev.TxBuffer[_i2c_dev.tx_idx]);
_i2c_dev.tx_idx++;
if( _i2c_dev.tx_len == _i2c_dev.tx_idx ) {
_i2c_dev.state = BMP280_I2C_READ_REG2;
}
}
break;
/* EV8_2 */
case I2C_EVENT_MASTER_BYTE_TRANSMITTED:
/* Send RESTART condition */
I2C_GenerateSTART(I2C1, ENABLE);
break;
/* EV6 */
case I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED:
if( _i2c_dev.rx_len == 1 ) {
I2C_AcknowledgeConfig(I2C1, DISABLE);
I2C_GenerateSTOP(I2C1, ENABLE);
}
break;
/* EV7 */
case I2C_EVENT_MASTER_BYTE_RECEIVED:
_i2c_dev.RxBuffer[_i2c_dev.rx_idx++] = I2C_ReceiveData(I2C1);
if( _i2c_dev.rx_len - _i2c_dev.rx_idx == 1 ) {
I2C_AcknowledgeConfig(I2C1, DISABLE);
I2C_GenerateSTOP(I2C1, ENABLE);
}
if(_i2c_dev.rx_len - _i2c_dev.rx_idx <= 0 ) {
I2C_ITConfig(I2C1, (I2C_IT_TypeDef)(I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF) , DISABLE);
_i2c_dev.state = BMP280_I2C_READ_REG_END;
}
break;
default:
break;
}
}
}
5、逻辑分析仪对照
Write例:
BMP初始化时需要软重置bmp280,是向0xE0寄存器发送0xB6。如下图:
Read例1:
可以从BMP280获取它的芯片ID(BMP280/BME280),需要读取其0xD0寄存器,如下图:
读出结果是0x58表示是BMP280。
能很清楚的看出:先写后读,中间过渡用RepeatStart。
Read例2:
最后,读取BMP280的温度值和气压值,
需要连续读取其从0xF7开始的6字节数据(3字节气压,3字节温度的原始值),如下图:
能很清楚的看出:先写后读,中间过渡用RepeatStart。