目录
[3.2、 图示](#3.2、 图示)
[1. 配置I2C](#1. 配置I2C)
[2. 发送数据](#2. 发送数据)
[3. 接收数据](#3. 接收数据)
[4. 错误处理](#4. 错误处理)
1、什么是时序
计算机领域中的时序
在计算机领域,时序指的是计算机系统中各个事件和操作的执行顺序以及它们之间的时间关系 。
在通信中,时序指的是通信线上按照时间顺序发生的电平变化,以及这些变化对通信的意义。
1.1、非标准时序
概念:由一些模块的生产厂家自己规定的一些时序,没有统一的标准
例如,下图为模块DHT11的时序图
1.2、标准时序
一个系统或设备中,各个信号或事件之间的时间关系规范。 这些规范定义了信号的有效时间窗口、建立时间、保持时间、周期等等,以保证系统能够正确可靠地运行。 没有一个通用的"标准时序",不同的系统、不同的协议、不同的芯片都有各自的标准时序规范
2、STM32的标准时序
在stm32的芯片手册中我们可以找到常用的标准时序,例如:IIC,串口,SPI
本期主要介绍IIC
3、stm32------IIC
I2C(芯片间)总线接口连接微控制器和串行I2C总线。它提供多主机功能,控制所有I2C总线特定的 时序、协议、仲裁和定时。支持标准和快速两种模式,同时与SMBus 2.0兼容。 I2C模块有多种用途,包括CRC码的生成和校验、SMBus(系统管理总线---System Management Bus)和PMBus(电源管理总线---Power Management Bus)。 根据特定设备的需要,可以使用DMA以减轻CPU的负担。
------采自stm32f10x芯片手册原话
3.1、IIC介绍
IIC通信协议:IIC通信协议,也称为I2C(Inter-Integrated Circuit),是一种串行、半双工总线,主要用于近距离、低速的芯片(如传感器、存储器、显示屏等)之间的通信。
它由飞利浦(Philips)公司开发,用于简化PCB板上的连接和通信需求。
I2C协议采用两根双向的信号线进行通信:
串行数据线(SDA):用于传输数据的双向线路。
串行时钟线(SCL):用于同步数据传输的时钟信号线。
IIC是主从机制
可以实现多主多从,stm32大部分使用单主机,多从机模式
3.2、 图示
示意图
时序图
传输数据方式
3.3、IIC时序解析
在SCL高位期间;SDA的下降沿为开始条件,每一次SCL的拉高代表传输一个数据位;在SCL高位期间,SDA上升沿表示停止位。
数据和地址按8位/字节进行传输,高位在前。跟在起始条件后的1或2个字节是地址(7位模式为1 个字节,10位模式为2个字节)。地址只在主模式发送。
在一个字节传输的8个时钟后的第9个时钟期间,接收器必须回送一个应答位(ACK)给发送器。
3.4、IIC到底是什么
现在我们已经知道了IIC的时序,其实就是两条数据线,一根线的电平高低代表有无时钟,一根线的电平高低代表有无数据;
那么好,在我们学习配置stm32库函数的IIC之前,我们可不可以自己生成一个IIC通信接口呢??
当然可以,我们只需要合适的时间拉高拉低SCL与SDA的电平,不用stm32的IIC,也可以实现IIC通信
3.5、自己生成的IIC
iic.h
cpp
#ifndef __IIC_H
#define __IIC_H
#include "stm32f10x.h" // Device header
//SDA:PB8
//SCL:PB9
#define SCL_PIN GPIO_Pin_9
#define SDA_PIN GPIO_Pin_8
#define SCL_PIN_PORT GPIOB
#define SDA_PIN_PORT GPIOB
#define SCL_RCC RCC_APB2Periph_GPIOB
#define SDA_RCC RCC_APB2Periph_GPIOB
void IIC_Init(void);
void IIC_SendData(u8 addr, u8* data, int len);
#endif
iic.c
cpp
#include "stm32f10x.h" // Device header
#include "iic.h"
static void SCL_OUT(void)
{
GPIO_InitTypeDef gpio_struct;
gpio_struct.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_struct.GPIO_Pin = SCL_PIN;
gpio_struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SCL_PIN_PORT, &gpio_struct);
}
static void SDA_OUT(void)
{
GPIO_InitTypeDef gpio_struct;
gpio_struct.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_struct.GPIO_Pin = SDA_PIN;
gpio_struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SDA_PIN_PORT, &gpio_struct);
}
static void SDA_IN(void)
{
GPIO_InitTypeDef gpio_struct;
gpio_struct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
gpio_struct.GPIO_Pin = SDA_PIN;
GPIO_Init(SDA_PIN_PORT, &gpio_struct);
}
static void SCL(int x)
{
if(x)
GPIO_SetBits(SCL_PIN_PORT, SCL_PIN);
else
GPIO_ResetBits(SCL_PIN_PORT, SCL_PIN);
}
static void SDA(int x)
{
if(x)
GPIO_SetBits(SDA_PIN_PORT, SDA_PIN);
else
GPIO_ResetBits(SDA_PIN_PORT, SDA_PIN);
}
void IIC_Init(void)
{
//开时钟
RCC_APB2PeriphClockCmd(SCL_RCC, ENABLE);
RCC_APB2PeriphClockCmd(SDA_RCC, ENABLE);
//确定方向
SDA_OUT();
SCL_OUT();
//总线空闲
SDA(1);
SCL(1);
}
static void IIC_Send_8Bit(unsigned char data)
{
int i=0;
for(i=0; i<8; i++)
{
//先准备SDA
if(data & (0x1 << (7-i)))
//1
SDA(1);
else
SDA(0);
//再拉高拉低SCL
SCL(1);
SCL(0);
}
//ACK
SCL(1);
SCL(0);
}
void IIC_SendData(u8 addr, u8* data, int len)
{
//开始信号 SDA下降沿
SDA(1);
SCL(1);
SDA(0);
SCL(0);
//从机地址 读写位
IIC_Send_8Bit(addr);
//数据
int i = 0;
for(i=0; i<len; i++)
IIC_Send_8Bit(data[i]);
//停止信号 SDA上升沿
SDA(0);
SCL(1);
SDA(1);
}
在该项目中,我们实现了作为主机通过IIC发送数据的功能
既然发送的主机有了,那是不是需要一个接受的从机呢??
我们采用oled屏来接受IIC通信传输的数据
oled.h
cpp
#ifndef __OLED_H
#define __OLED_H
void OLED_Init(void);
void OLED_Clear(void);
#endif
oled.c
cpp
#include "oled.h"
#include "iic.h"
#include "stm32f10x.h" // Device header
static void OLED_SendCmd(u8 *cmd, int len)
{
u8 cmd_arr[10] = "";
cmd_arr[0] = 0x00; //发命令
memcpy(cmd_arr+1, cmd, len);
IIC_SendData(0x78, cmd_arr, len+1);
}
static void OLED_SendData(u8 *data, int len)
{
u8 data_arr[20] = "";
data_arr[0] = 0x40; //发数据
memcpy(data_arr+1, data, len);
IIC_SendData(0x78, data_arr, len+1);
}
void OLED_Clear(void)
{
int i, j;
u8 data[10] = "";
for(i=0; i<8; i++)
{
//指定行
data[0] = 0xB0+i;
OLED_SendCmd(data, 1);
//指定列
data[0] = 0x00;
data[1] = 0x10;
OLED_SendCmd(data, 2);
//发数据
data[0] = 0x00;
for(j=0; j<128; j++)
OLED_SendData(data, 1);
}
}
void OLED_Init(void)
{
IIC_Init();
u8 data[20] = "";
data[0] = 0xAE;//关屏
OLED_SendCmd(data, 1);
data[0] = 0x8D;//设置电荷泵
data[1] = 0x14;
OLED_SendCmd(data, 2);
data[0] = 0xAF;//开屏
OLED_SendCmd(data, 1);
}
咱们只是做一个验证;就不去设计oled屏幕具体显示什么了;
当我们只运行这个代码,在main中调用OLEN_Init;屏幕是雪花屏;如果在main中调用OLED_Clear(向从机输入全为0的数据);oled屏幕全黑; ------即传输了全为0的数据!
4、配置stm32------IIC
我们在自己生成IIC的过程中,其实需要记住时序图;之后对应着时序图去拉高拉低电平;很是麻烦,那有没有库函数可以帮我们实现这些繁琐的步骤呢??
接下来咱们就开始配置stm32的IIC;抵用库函数为我们构建IIC通信
1. 配置I2C
首先老规矩,找到结构图,根据结构体配置需要的寄存器------调用对应的库函数;
在STM32标准库中,需要配置I2C的时钟、地址、模式等参数。可以使用以下函数来配置I2C:
I2C_Init()
:初始化I2CI2C_SetClockSpeed()
:设置I2C的时钟速度I2C_SetAddress()
:设置I2C的地址I2C_SetMode()
:设置I2C的模式
2. 发送数据
使用以下函数来发送数据:
I2C_Master_Transmit()
:发送数据到从设备I2C_Master_Receive()
:从从设备接收数据
3. 接收数据
使用以下函数来接收数据:
I2C_Master_Receive()
:从从设备接收数据
4. 错误处理
在发送和接收数据时,需要检查返回值,确保操作成功。可以使用以下函数来检查错误:
I2C_Error()
:检查I2C错误
4.1、示例
my_iic.h
cpp
#ifndef __IIC_H
#define __IIC_H
#include "stm32f10x.h"
void My_IIC_Init(void);
void my_send1(u8 addr, u8 data);
void my_send2(u8 addr, u8 data1,u8 data2);
#endif
my_iic.c
cpp
#include "stm32f10x.h" // Device header
#include "my_iic.h"
#define I2C_ADDRESS 0x78
void my_send1(u8 addr, u8 data)
{
I2C_GenerateSTART(I2C2,ENABLE);
while(SUCCESS != I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C2,I2C_ADDRESS,I2C_Direction_Transmitter);
while(SUCCESS != I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C2,addr);
while(SUCCESS != I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING))
I2C_SendData(I2C2,data);
while(SUCCESS != I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C2,ENABLE);
}
void my_send2(u8 addr, u8 data1,u8 data2)
{
I2C_GenerateSTART(I2C2,ENABLE);
while(SUCCESS != I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C2,I2C_ADDRESS,I2C_Direction_Transmitter);
while(SUCCESS != I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C2,addr);
while(SUCCESS != I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING))
I2C_SendData(I2C2,data1);
while(SUCCESS != I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING))
I2C_SendData(I2C2,data2);
while(SUCCESS != I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C2,ENABLE);
}
void My_IIC_Init(void)
{
// 1. 时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); // 使能I2C2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIOB时钟
// 2. GPIO配置 (SCL在PB10,SDA在PB11)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 3. I2C初始化
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 标准模式
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 自己的I2C地址 (可根据需要修改)
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 50000; // 50kHz
I2C_Init(I2C1, &I2C_InitStructure);
// 4. I2C使能
I2C_Cmd(I2C1, ENABLE);
}