IIC(芯片间)总线接口连接微控制器和串行IIC总线。它提供多主机功能,控制所有IIC总线特定的时序、协议、仲裁和定时。支持标准和快速两种模式,同时与SMBus 2.0兼容。
一、IIC功能
- 并行总线/IIC总线协议转换器
- 多主机功能:该模块既可做主设备也可做从设备
- 产生和检测7位/10位地址和广播呼叫
- 支持不同的通讯速度
- 状态标志
- 错误标志
- 2个中断向量
- 具单字节缓冲器的DMA
- 可配置的PEC(信息包错误检测)的产生或校验
- 兼容SMBus 2.0
- 兼容SMBus
具体支持哪些功能, 要看具体的产品。
1.1、IIC 主模式
1.1.1、起始条件
1.1.2、从地址的发送
- ADD10位被硬件置位,如果设置了ITEVFEN位,则产生一个中断,然后主设备等待读SR1寄存器,再将第二个地址字节写入DR寄存器
- ADDR位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。随后主设备等待一次读SR1寄存器,跟着读SR2寄存器
-
要进入发送器模式,主设备先送头字节 (11110xx0) ,然后送最低位为 ’0’ 的从地址。 ( 这里xx 代表 10 位地址中的最高 2 位。 )
-
要进入接收器模式,主设备先送头字节(11110xx0) ,然后送最低位为 ’1’ 的从地址。然后再重新发送一个开始条件,后面跟着头字节 (11110xx1)( 这里 xx 代表 10 位地址中的最高 2 位。 )
在7位地址模式时,只需送出一个地址字节。
- 一旦该地址字节被送出 ADDR位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。 随后主设备等待一次读SR1寄存器,跟着读SR2寄存器
-
要进入发送器模式,主设备发送从地址时置最低位为’0’。
-
要进入接收器模式,主设备发送从地址时置最低位为’1’。
1.1.3、主发送器
在发送了地址和清除了ADDR位后, 主设备通过内部移位寄存器将字节从DR寄存器发送到SDA线上。主设备等待,直到TxE被清除。
当收到应答脉冲时:TxE位被硬件置位,如果设置了INEVFEN和ITBUFEN位,则产生一个中断。
如果TxE被置位并且在上一次数据发送结束之前没有写新的数据字节到DR寄存器,则BTF(byte transfer finshied)被硬件置位,在清除BTF之前IIC接口将保持SCL为低电平;读出IIC_SR1之后再写入IIC_DR寄存器将清除BTF位。
1.1.4、关闭通信
二、IIC操作OLED(CPU主模式)
CubeMX主要配置图:
核心代码如下:
除了HAL相关的代码,其它代码都是从51IIC中摘出来的。显示的是小猪佩奇的一图片。
void iic_write_cmd(uint8_t cmd) {
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,
&cmd, 1, 0xff);
}
void iic_write_data(uint8_t data) {
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,
&data, 1, 0xff);
}
void oled_init() {
iic_write_cmd(0xAE);//--display off
iic_write_cmd(0x00);//---set low column address
iic_write_cmd(0x10);//---set high column address
iic_write_cmd(0x40);//--set start line address
iic_write_cmd(0xB0);//--set page address
iic_write_cmd(0x81); // contract control
iic_write_cmd(0xFF);//--128
iic_write_cmd(0xA1);//set segment remap
iic_write_cmd(0xA6);//--normal / reverse
iic_write_cmd(0xA8);//--set multiplex ratio(1 to 64)
iic_write_cmd(0x3F);//--1/32 duty
iic_write_cmd(0xC8);//Com scan direction
iic_write_cmd(0xD3);//-set display offset
iic_write_cmd(0x00);//
iic_write_cmd(0xD5);//set osc division
iic_write_cmd(0x80);//
iic_write_cmd(0xD8);//set area color mode off
iic_write_cmd(0x05);//
iic_write_cmd(0xD9);//Set Pre-Charge Period
iic_write_cmd(0xF1);//
iic_write_cmd(0xDA);//set com pin configuartion
iic_write_cmd(0x12);//
iic_write_cmd(0xDB);//set Vcomh
iic_write_cmd(0x30);//
iic_write_cmd(0x8D);//set charge pump enable
iic_write_cmd(0x14);//
iic_write_cmd(0xAF);//--turn on oled panel
}
void oled_clear() {
int i, j;
for(i = 0; i < 8; i ++) {
iic_write_cmd(0xB0+i);
iic_write_cmd(0x00);
iic_write_cmd(0x10);
for(j = 0; j < 128; j ++) {
iic_write_data(0);
}
}
}
void oled_image(unsigned char *image)
{
unsigned char i;
unsigned int j;
for(i=0;i<8;i++){
iic_write_cmd(0xB0 + i);
iic_write_cmd(0x00);
iic_write_cmd(0x10);
for(j = 128 * i; j<(128 * (i+1));j++){
iic_write_data(image[j]);
}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
oled_init();
// 3、addressing setting command table 第三行
iic_write_cmd(0x20);
iic_write_cmd(0x02);
oled_clear();
oled_image(peiqi);
while (1)
{
}
}
2.1、HAL_I2C_Mem_Write
这个方法是HAL提供的,里面封装了具体的操作步骤,不像51,没有IIC外设需要模拟。HAL_I2C_Mem_Write的主要是按照1.1、IIC主模式进行处理的。
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
if (hi2c->State == HAL_I2C_STATE_READY)
{
/* Wait until BUSY flag is reset */
if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK)
{
return HAL_BUSY;
}
__HAL_LOCK(hi2c);
if ((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE)
{
__HAL_I2C_ENABLE(hi2c);
}
CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_POS);
hi2c->State = HAL_I2C_STATE_BUSY_TX;
hi2c->Mode = HAL_I2C_MODE_MEM;
hi2c->ErrorCode = HAL_I2C_ERROR_NONE;
hi2c->pBuffPtr = pData;
hi2c->XferCount = Size;
hi2c->XferSize = hi2c->XferCount;
hi2c->XferOptions = I2C_NO_OPTION_FRAME;
// 发送从机地址,内存地址
if (I2C_RequestMemoryWrite(hi2c, DevAddress, MemAddress, MemAddSize, Timeout, tickstart) != HAL_OK)
{
return HAL_ERROR;
}
while (hi2c->XferSize > 0U)
{
// 判断Txe 是否被置1,代表数据寄存器为空
if (I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
{
if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
{
/* Generate Stop */
SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
}
return HAL_ERROR;
}
// 写数据
hi2c->Instance->DR = *hi2c->pBuffPtr;
// 指向下个数据地址
hi2c->pBuffPtr++;
// 更新大小和数量
hi2c->XferSize--;
hi2c->XferCount--;
// 两次判断断BTF 是否被置1且是否还有数据要发送
if ((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U))
{
/* Write data to DR */
hi2c->Instance->DR = *hi2c->pBuffPtr;
/* Increment Buffer pointer */
hi2c->pBuffPtr++;
/* Update counter */
hi2c->XferSize--;
hi2c->XferCount--;
}
}
// 判断数据是否发送完成
if (I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
{
if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
{
SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
}
return HAL_ERROR;
}
// 产生停止信号
SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
hi2c->State = HAL_I2C_STATE_READY;
hi2c->Mode = HAL_I2C_MODE_NONE;
__HAL_UNLOCK(hi2c);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
2.2.1、检测busy位
检测是SR2的busy位是否为1,为1时说明总线正在使用。这里有一个技巧,
- 如果是SR1中的标识,所有的宏都是0x00010000,开头
- 如果是SR2中的标识,所有的宏都是0x00100000,开头
这里使用的是I2C_FLAG_BUSY = 0x00100002, 右移动16位就是0x0002,就是busy位。
// 1 -----------------------------------------
if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) {
return HAL_BUSY;
}
#define __HAL_I2C_GET_FLAG(__HANDLE__, __FLAG__) ((((uint8_t)((__FLAG__) >> 16U)) == 0x01U) ? \
(((((__HANDLE__)->Instance->SR1) & ((__FLAG__) & I2C_FLAG_MASK)) == ((__FLAG__) & I2C_FLAG_MASK)) ? SET : RESET) : \
(((((__HANDLE__)->Instance->SR2) & ((__FLAG__) & I2C_FLAG_MASK)) == ((__FLAG__) & I2C_FLAG_MASK)) ? SET : RESET))
2.2.2、检测PE位是否开启(使能IIC外设)
if ((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE)
{
/* Enable I2C peripheral */
__HAL_I2C_ENABLE(hi2c);
}
2.2.3、清除POS标识位
CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_POS);
操作CR1寄存器中的POS位,
2.2.4、请示内存写(产生起始信号等)I2C_RequestMemoryWrite
下面代码的意思是开启IIC起始信号,写从机地址,写内存地址
static HAL_StatusTypeDef I2C_RequestMemoryWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart)
{
// 产生启动信号start
SET_BIT(hi2c->Instance->CR1, I2C_CR1_START);
// 启动信号已发送
if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK)
{
if (READ_BIT(hi2c->Instance->CR1, I2C_CR1_START) == I2C_CR1_START)
{
hi2c->ErrorCode = HAL_I2C_WRONG_START;
}
return HAL_TIMEOUT;
}
// 设置从机地址
hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress);
// 地址发送结束
if (I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK)
{
return HAL_ERROR;
}
// 依次读取SR1,SR2清除ADDR 位
__HAL_I2C_CLEAR_ADDRFLAG(hi2c);
// 等待数据寄存器为空,代表发送完成
if (I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK)
{
if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
{
SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
}
return HAL_ERROR;
}
// 只有8位,用LSB
if (MemAddSize == I2C_MEMADD_SIZE_8BIT)
{
// 发送数据
hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress);
}
else
{
// 如果是16位,先发送MSB,再发送LSB
hi2c->Instance->DR = I2C_MEM_ADD_MSB(MemAddress);
// 等待MSB发送完成
if (I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK)
{
if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
{
SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
}
return HAL_ERROR;
}
hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress);
}
return HAL_OK;
}
2.2.5、发送数据
在while循环内判断数据是否发送完全,一个字节的发,
while (hi2c->XferSize > 0U)
2.2.6、判断数据是否发送完
I2C_WaitOnBTFFlagUntilTimeout ---->
__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_TXE)---->
I2C_FLAG_TXE
操作的是SR1寄存器中的TxE
2.2.7、停止信号
// 8.产生停止信号
SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);