I2C的物理层
(1) 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线
中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线
(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之
间的访问。
(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空
闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用
总线。
(6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式
下可达 3.4Mbit/s,但目前大多 I 2C 设备尚不支持高速模式。
(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。
I2C的协议层
I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地
址广播等环节。
I2C的通信过程
主机的 I2C 接口产生的传输起始信号(S),这时连接到 I2C 总线上的所有从机都会接收
到这个信号。
起始信号产生后,所有从机就开始等待主机紧接下来 广播 的从机地址信号(SLAVE_ADDRESS)。
在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这
个设备就被选中了,没被选中的设备将会忽略之后的数据信号。
根据 I2C 协议,这个从机地址可以是 7 位或 10 位。
在地址位之后,是传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主
机传输至从机,即主机向从机写数据。该位为 1 时,则相反,即主机由从机读数据。
从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,
只有接收到应答信号后,主机才能继续发送或接收数据。
写数据
主机开始正式向从机传输数据(DATA),数据包的大小为 8 位,主机每发送完一个字节数
据,都要等待从机的应答信号(ACK),重复这个过程。当数据传输结束时,主机向从机发送
一个停止传输信号§,表示不再传输数据。
读数据
接收到应答信号后,从机开始向主机返回数据(DATA),数据包大小也为 8 位,从机每发送完一个数
据,都会等待主机的应答信号(ACK)。主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。
读和写
主机通过 SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备
内部的寄存器或存储器地址(注意区分它与 SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。
通讯的起始和停止信号
应答响应
软件I2C
直接控制 STM32的两个 GPIO 引脚,分别用作 SCL及 SDA,按照上述信号的时序要求,直接
像控制 LED 灯那样控制引脚的输出(若是接收数据时则读取 SDA 电平),就可以实现 I2C 通讯。
硬件I2C
STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议
要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。
基于I2C的温度采集
使用cube MX创建工程
其余的配置和正常配置差不多
向工程树中,添加两个文件
AHT20.h
#ifndef INC_AHT20_H_
#define INC_AHT20_H_
#include "i2c.h"
//****************************************
// 定义 AHT20 命令
//****************************************
#define AHT20_Write 0x00 //读取
#define AHT20_Read 0x01 //写入
//****************************************
// 定义 AHT20 地址
//****************************************
#define AHT20_SLAVE_ADDRESS 0x70 /* I2C从机地址 */
//****************************************
// 定义 AHT20 命令
//****************************************
#define AHT20_INIT_COMD 0xBE //初始化 寄存器地址
#define AHT20_SoftReset 0xBA //软复位 单指令
#define AHT20_TrigMeasure_COMD 0xAC //触发测量 寄存器地址
// 存储AHT20传感器信息的结构体
typedef struct m_AHT20
{
uint8_t alive; // 0-器件不存在; 1-器件存在
uint8_t flag; // 读取/计算错误标志位。0-读取/计算数据正常; 1-读取/计算设备失败
uint32_t HT[2]; // 湿度、温度 原始传感器的值(20Bit).
float RH; // 湿度,转换单位后的实际值,标准单位%
float Temp; // 温度,转换单位后的实际值,标准单位°C
}AHT20_StructureTypedef;
extern AHT20_StructureTypedef Humiture;
uint8_t AHT20_Init(void);
void AHT20_Start_Init(void);
uint8_t AHT20_ReadHT(uint32_t *HT);
uint8_t StandardUnitCon(AHT20_StructureTypedef *aht);
uint8_t AHT20_Get_Value(AHT20_StructureTypedef *p);
#endif /* INC_AHT20_H_ */
AHT20.c
#include "AHT20.h"
AHT20_StructureTypedef Humiture;
/**
* @brief 读AHT20 设备状态字
* @param void
* @retval uint8_t 设备状态字
*/
static uint8_t AHT20_ReadStatusCmd(void)
{
uint8_t tmp = 0;
HAL_I2C_Master_Receive(&hi2c1, AHT20_SLAVE_ADDRESS, &tmp, 1, 0xFFFF);
return tmp;
}
/**
* @brief 读AHT20 设备状态字 中的Bit3: 校准使能位
* @param void
* @retval uint8_t 校准使能位:1 - 已校准; 0 - 未校准
*/
static uint8_t AHT20_ReadCalEnableCmd(void)
{
uint8_t tmp = 0;
tmp = AHT20_ReadStatusCmd();
return (tmp>>3)&0x01;
}
/**
* @brief 读AHT20 设备状态字 中的Bit7: 忙标志
* @param void
* @retval uint8_t 忙标志:1 - 设备忙; 0 - 设备空闲
*/
static uint8_t AHT20_ReadBusyCmd(void)
{
uint8_t tmp = 0;
tmp = AHT20_ReadStatusCmd();
return (tmp>>7)&0x01;
}
/**
* @brief AHT20 芯片初始化命令
* @param void
* @retval void
*/
static void AHT20_IcInitCmd(void)
{
uint8_t tmp = AHT20_INIT_COMD;
HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, &tmp, 1, 0xFFFF);
}
/**
* @brief AHT20 软复位命令
* @param void
* @retval void
*/
static void AHT20_SoftResetCmd(void)
{
uint8_t tmp = AHT20_SoftReset;
HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, &tmp, 1, 0xFFFF);
}
/**
* @brief AHT20 触发测量命令
* @param void
* @retval void
*/
static void AHT20_TrigMeasureCmd(void)
{
static uint8_t tmp[3] = {AHT20_TrigMeasure_COMD, 0x33, 0x00};
HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, tmp, 3, 0xFFFF);
}
/**
* @brief AHT20 设备初始化
* @param void
* @retval uint8_t:0 - 初始化AHT20设备成功; 1 - 初始化AHT20失败,可能设备不存在或器件已损坏
*/
uint8_t AHT20_Init(void)
{
uint8_t rcnt = 2+1;//软复位命令 重试次数,2次
uint8_t icnt = 2+1;//初始化命令 重试次数,2次
while(--rcnt)
{
icnt = 2+1;
HAL_Delay(40);//上电后要等待40ms
// 读取温湿度之前,首先检查[校准使能位]是否为1
while((!AHT20_ReadCalEnableCmd()) && (--icnt))// 2次重试机会
{
HAL_Delay(1);
// 如果不为1,要发送初始化命令
AHT20_IcInitCmd();
HAL_Delay(40);//这个时间手册没说,按上电时间算40ms
}
if(icnt)//[校准使能位]为1,校准正常
{
break;//退出rcnt循环
}
else//[校准使能位]为0,校准错误
{
AHT20_SoftResetCmd();//软复位AHT20器件,重试
HAL_Delay(20);//这个时间手册标明不超过20ms.
}
}
if(rcnt)
{
return 0;// AHT20设备初始化正常
}
else
{
return 1;// AHT20设备初始化失败
}
}
/**
* @brief AHT20 寄存器复位
* @param void
* @retval void
*/
static void AHT20_Register_Reset(uint8_t addr)
{
uint8_t iic_tx[3] = {0}, iic_rx[3] = {0};
iic_tx[0] = addr;
HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, iic_tx, 3, 0xFFFF);
HAL_Delay(5);
HAL_I2C_Master_Receive(&hi2c1, AHT20_SLAVE_ADDRESS, iic_rx, 3, 0xFFFF);
HAL_Delay(10);
iic_tx[0] = 0xB0 | addr;
iic_tx[1] = iic_rx[1];
iic_tx[2] = iic_rx[2];
HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, iic_tx, 3, 0xFFFF);
HAL_Delay(10);
}
/**
* @brief AHT20 设备开始初始化
* @param void
* @retval void
*/
void AHT20_Start_Init(void)
{
static uint8_t temp[3] = {0x1B, 0x1C, 0x1E}, i;
for(i = 0; i < 3; i++)
{
AHT20_Register_Reset(temp[i]);
}
}
/**
* @brief AHT20 设备读取 相对湿度和温度(原始数据20Bit)
* @param uint32_t *HT:存储20Bit原始数据的uint32数组
* @retval uint8_t:0-读取数据正常; 1-读取设备失败,设备一直处于忙状态,不能获取数据
*/
uint8_t AHT20_ReadHT(uint32_t *HT)
{
uint8_t cnt=3+1;//忙标志 重试次数,3次
uint8_t tmp[6];
uint32_t RetuData = 0;
// 发送触发测量命令
AHT20_TrigMeasureCmd();
do{
HAL_Delay(75);//等待75ms待测量完成,忙标志Bit7为0
}while(AHT20_ReadBusyCmd() && (--cnt));//重试3次
if(cnt)//设备闲,可以读温湿度数据
{
HAL_Delay(5);
// 读温湿度数据
HAL_I2C_Master_Receive(&hi2c1, AHT20_SLAVE_ADDRESS, tmp, 6, 0XFFFF);
// 计算相对湿度RH。原始值,未计算为标准单位%。
RetuData = 0;
RetuData = (RetuData|tmp[1]) << 8;
RetuData = (RetuData|tmp[2]) << 8;
RetuData = (RetuData|tmp[3]);
RetuData = RetuData >> 4;
HT[0] = RetuData;
// 计算温度T。原始值,未计算为标准单位°C。
RetuData = 0;
RetuData = (RetuData|tmp[3]) << 8;
RetuData = (RetuData|tmp[4]) << 8;
RetuData = (RetuData|tmp[5]);
RetuData = RetuData&0xfffff;
HT[1] = RetuData;
return 0;
}
else//设备忙,返回读取失败
{
return 1;
}
}
/**
* @brief AHT20 温湿度信号转换(由20Bit原始数据,转换为标准单位RH=%,T=°C)
* @param struct m_AHT20* aht:存储AHT20传感器信息的结构体
* @retval uint8_t:0-计算数据正常; 1-计算数据失败,计算值超出元件手册规格范围
*/
uint8_t StandardUnitCon(AHT20_StructureTypedef *aht)
{
aht->RH = (double)aht->HT[0] *100 / 1048576;//2^20=1048576 //原式:(double)aht->HT[0] / 1048576 *100,为了浮点精度改为现在的
aht->Temp = (double)aht->HT[1] *200 / 1048576 -50;
//限幅,RH=0~100%; Temp=-40~85°C
if((aht->RH >=0)&&(aht->RH <=100) && (aht->Temp >=-40)&&(aht->Temp <=85))
{
aht->flag = 0;
return 0;//测量数据正常
}
else
{
aht->flag = 1;
return 1;//测量数据超出范围,错误
}
}
/**
* @brief AHT20 温湿度信号转换(由20Bit原始数据,转换为标准单位RH=%,T=°C)
* @param struct m_AHT20* aht:存储AHT20传感器信息的结构体
* @retval uint8_t:0-计算数据正常; 1-计算数据失败,计算值超出元件手册规格范围
*/
uint8_t AHT20_Get_Value(AHT20_StructureTypedef *p)
{
uint8_t temp = 0;
temp = AHT20_ReadHT(p->HT);
if(temp == 0)
{
temp = StandardUnitCon(p);
}
return temp;
}
修改main.c为
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
if(AHT20_Init() != 0)
{
Humiture.alive = 0;
printf("AHT20 Initialization failed\r\n");
}
else
{
Humiture.alive = 1;
printf("AHT20 Initialization succeeded\r\n");
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
AHT20_Get_Value(&Humiture);
printf("environment temperature is : %.2f;\r\nenvironment humidity is : %.2f",Humiture.Temp, Humiture.RH);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
注意这里的printif需要重定向,如果不了解的可以用HAL的transmit.
还有就是在做这个温湿度采集的时候,需要添加.c和.h文件到工程下。我最开始的时候是直接在文件资源管理器中去添加的,但是一直都不能引用头文件里的东西。更新工程、添加新的头文件路径、用绝对路径寻找都不行,最后在AHT20.c文件中找到一个函数右键打开AHT20.h发现里面居然是空白的,明明我在编辑的时候就放了代码进去的,在文件管理器里面打开也有内容。然后添加代码进文件才行。
OLED和点阵屏显
SPI
SPI(Serial Peripheral Interface)就是串行外围设备接口。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚。SPI 是一个环形总线结构,由 ss(cs)、sck、sdi、sdo 构成,时序主要是在 sck 的控制下,两个双向移位寄存器进行数据交换。
上升沿发送、下降沿接收、高位先发送。
上升沿到来的时候,sdo 上的电平将被发送到从设备的寄存器中。
下降沿到来的时候,sdi 上的电平将被接收到主设备的寄存器中。
SS( Slave Select):从设备选择信号线,常称为片选信号线。
SCK (Serial Clock):时钟信号线,用于通讯数据同步。
MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。
MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。
SPI的通信过程
MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。
OLED显示屏显示数据
首先配置SPI
分配GPIO
生成工程以后进行代码移植
向项目里添加文件led,c
向这个文件夹里添加头文件
然后修改led.h为
修改led.c
向头文件里添加代码
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
OLED_Init(); //OLED³õʼ»¯
OLED_Clear();
OLED_WR_Byte(0x2E,OLED_CMD);
OLED_WR_Byte(0x26,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0x07,OLED_CMD);
OLED_WR_Byte(0x07,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0xFF,OLED_CMD);
OLED_WR_Byte(0x2F,OLED_CMD);
OLED_ShowChar(0,16,'&',1,12);
OLED_ShowString(0,16,"632007030116",12);
/* OLED_ShowNum(0,16,ss[0],1,24);
OLED_ShowNum(8,16,ss[1],1,24);
OLED_ShowNum(16,16,ss[2],1,24);
OLED_ShowNum(24,16,ss[3],1,24);
OLED_ShowNum(32,16,ss[4],1,24);
OLED_ShowNum(40,16,ss[5],1,24);*/
OLED_ShowChinese(0,0,0,16);
OLED_ShowChinese(16,0,1,16);
OLED_ShowChinese(32,0,2,16);
OLED_Refresh_Gram();
while(1)
{
}
}
OLED_Showchinese里写入的是取模软件生成的,取模软件直接网上搜索或者找淘宝店家要一个就行
这里这个我不知道那个中景源给的代码咋回事,我明明以及调了显示方式为正常显示,但是显示的数字依旧是反相,对其他的地址修改就能正常,比如说是白底显示就能正常。但是修改显示方式就是不行。还有就是这个keil经常不更新hex文件,改完源文件以后生成的hex文件居然和之前是一模一样的。烧录也是起码要连续烧录三遍,OLED屏幕的才会发生变化。
;
/* OLED_ShowNum(0,16,ss[0],1,24);
OLED_ShowNum(8,16,ss[1],1,24);
OLED_ShowNum(16,16,ss[2],1,24);
OLED_ShowNum(24,16,ss[3],1,24);
OLED_ShowNum(32,16,ss[4],1,24);
OLED_ShowNum(40,16,ss[5],1,24);*/
OLED_ShowChinese(0,0,0,16);
OLED_ShowChinese(16,0,1,16);
OLED_ShowChinese(32,0,2,16);
OLED_Refresh_Gram();
while(1)
{
}
}
OLED_Showchinese里写入的是取模软件生成的,取模软件直接网上搜索或者找淘宝店家要一个就行
这里这个我不知道那个中景源给的代码咋回事,我明明以及调了显示方式为正常显示,但是显示的数字依旧是反相,对其他的地址修改就能正常,比如说是白底显示就能正常。但是修改显示方式就是不行。还有就是这个keil经常不更新hex文件,改完源文件以后生成的hex文件居然和之前是一模一样的。烧录也是起码要连续烧录三遍,OLED屏幕的才会发生变化。
![在这里插入图片描述](https://img-blog.csdnimg.cn/dfbab94c86f746d184bacd0ea70b7ba8.gif)