STM32H750寄存器操作(硬件I2C)

1.前言

最近使用的屏幕触控IC是GT911,这不巧了吗前面用另一款3.5寸tft也是这个IC,本来我是不想写I2C的但是拿着例程跑一下去实验,实在是蚌埠住了。首先是程序用的软件模拟I2C,其次程序还在主函数里加了20ms的延迟,程序里说是:触摸屏扫描间隔不能小于10ms,建议设置为20ms。我真是服了,最后我试了一下实际效果只能说真卡。实际效果甚至不如我用STC32G主频30Mhz跑起来的效果流畅。最后没办法,我想了想还是得修改硬件把硬件I2C跑起来。

说回H750的I2C,我实际使用起来,它的硬件结构与F4,F1系列有非常大的不同。F1,F4的I2C硬件构成是一摸一样的。而到了H7,ST官方几乎是把I2C重新设计了,不过官方手册写的非常完善了,把整个I2C从初始化到发生接受单个和多个字节的流程都讲了一遍。写起来难度其实不怎么大。

2.理论

2.1 时钟配置

首先我们看看手册上对初始化的描述

首先要配置I2C的时钟,这块是整个程序中最复杂的部分,我就简单介绍一下吧。首先我们的I2C时钟有4个来源,默认状态下使用APB1总线的时钟。那么我们的时钟源是120Mhz,在I2C内部有一个分频器可以再次分频。之后我们按照公式计算出SCL高低电平的长度即可控制周期了,手册上给出了经典的数值,我这里带大家过一下如何计算。

以100Khz为例,我们主时钟频率为48Mhz,我们的分频系数是0xb即11,这里注意一下我们投入的所有数值都是要+1才是实际值,那么实际分频系数是12,我们最后I2C频率为4Mhz,一个周期为250ns。然后配置低电平持续0x13即19即20个周期,也就是20x250ns=5us,这就是这一栏5us的来源。

同理计算高电平持续时间0xf即15即维持16个周期,也就是16x250=4us。具体的计算方式和原因还是比较复杂的,有兴趣的读者可以翻阅数据手册,要是大家想简单搞就用cubemx配置即可。

2.2 发送地址

首先的最开始发送地址阶段我们要配置地址,选择7位或10位,之后是配置传输方向和发送的字节数,配置完成后启动就可以了。

2.3 发送数据

发送过程手册上给出了文字描述和流程图,我们来具体看一下。

首先是初始化I2C,没啥好说的,之后我们要配置AutoEND

通常通过I2C写入寄存器会有restart信号,那么我们需要将Autoend置零;否则单纯写入连续写入给Autoend置一即可,置一之后在发送完最后一位数据后芯片会自动发送stop信号。然后按顺序配置好地址,给start置位这一串数据就可以正常发出去了。

再往下我们分为了两条路,左边这一侧是通讯异常状态,即地址不匹配或是没有收到ACK信号,我们就会进入这一分支,芯片会自动结束通讯。

我们重点看一看右侧,这一侧是我们正常的通讯流程

当我们地址发送出去后,我们需要等待状态寄存器的TXIS置位,这一位是啥?

这一位是表示数据已经发送出去,TXDR寄存器的数据正常发送了。

数据发送出去后,我们可以再次对TXDR寄存器进行写入。如果我们没有把所有数据全部发送,则循环这一过程。

当我们所有数据全部发送出去了

由于我们开启了Autoend,流程会自动发送停止位。

2.4 接受数据

过程比较类似,注意好传输方向即可,具体后面我结合具体程序来介绍

3.寄存器介绍

整个I2C寄存器比较少,我挑几个来介绍一下

首先是CR1寄存器的第0,7位

第7位是错误中断,大家如果需要可以开启,防止I2C进入错误情况。然后是第0位,使能外设,不多讲了。

然后是CR2寄存器,基本所有位都要用到

首先是0~9位是地址位,这里要格外注意,我们的七位地址是1~7位

第10位是读写方向,按照实际需要配置即可

第11位是寻址模式,正常我们是7位寻址模式

13、14位是起始和停止位生成,置1发送对应的信号,这两个我们一会是要用到的信号,其他的倒没有。

然后是16~23位是字节数,这就是我们一个通讯流程中需要发送的字节数目,正常写入即可。

之后是25位,当Autoend置0时SCL会延长并等待我们重新发送起始位或停止位。而当Autoend置1时发送完数据会自动发送停止位。

然后我们看看timg寄存器,按照前文提到的方式进行配置,注意位置和+1的情况,其他的没啥特别的地。

然后我们看看ISR寄存器

其中1,2位是我们用的最多的,是关于发送和接受的状态,详细的可以看看手册的描述

然后是5、6位,第6位是传输完成标志位。当TC=1意味着我们这一流程发送完毕可以发送停止位了,这一位我们用的也比较多。

第5位则是因为由于我们会使用Autoend,所以通过这一位可以判断我们所有过程是否全部结束,MCU对于总线的控制权是否释放。

顺带我们看一下数据寄存器,H7有一个典型特点就是数据寄存器分离了,发送就是单独的32位数据寄存器,接收也是单独的寄存器,这一点还是相当不错的。

4.程序

程序我先放上来

cpp 复制代码
#include "i2c1.h"

/*
注意,I2C一定要加上超时的设置,否则当IIC总线出错时,
没有超时检测可能造成MCU卡死在这里。
*/
void init_i2c1(void)
{
	RCC->APB1LENR|=1<<21;		//使能时钟
	I2C1->CR1&=~(1<<0);			//关闭I2C通信准备配置
	
	//初始化端口
	GPIOB->MODER&=~(3<<16);
	GPIOB->MODER&=~(3<<18);
	GPIOB->MODER|=2<<16;
	GPIOB->MODER|=2<<18;
	
	GPIOB->OSPEEDR|=1<<16;
	GPIOB->OSPEEDR|=1<<18;
	
	//!!!一定要设置成开漏输出模式!!!
	GPIOB->OTYPER|=1<<8;
	GPIOB->OTYPER|=1<<9;
	
	GPIOB->AFR[1]|=4<<0;
	GPIOB->AFR[1]|=4<<4;
	
	//设置I2C主频与通信速率
	//I2C挂在APB1上频率为120Mhz
	I2C1->TIMINGR=0;
	I2C1->TIMINGR|=3<<28;	//设置预分配系数为4 分频后时钟频率为30Mhz
	#if I2C_SPEED==100
	//H:5us+L:5us
	I2C1->TIMINGR|=144<<0;//SCLL
	I2C1->TIMINGR|=144<<8;//SCLH
	
	I2C1->TIMINGR|=6<<16;	//SDADEL=2
	I2C1->TIMINGR|=1<<20;	//SCLDEL=1
	#elif I2C_SPEED==400
	//H:1us+L:1.5us
	I2C1->TIMINGR|=37<<0;//SCLL
	I2C1->TIMINGR|=25<<8;//SCLH
	
	//(SDADEL + 1)*33.333 ns >100 ns
	I2C1->TIMINGR|=3<<16;	//SDADEL=2
	I2C1->TIMINGR|=1<<20;	//SCLDEL=1
	#elif I2C_SPEED==1000
	//H:0.4us+L:0.6us
	I2C1->TIMINGR|=13<<0;//SCLL
	I2C1->TIMINGR|=5<<8;//SCLH
	
	//(SDADEL + 1)*33.333 ns > 50 ns
	I2C1->TIMINGR|=2<<16;	//SDADEL=1
	I2C1->TIMINGR|=1<<20;	//SCLDEL=0
	#endif
	
	I2C1->CR2&=~(1<<11);	//7位地址模式
	I2C1->CR1|=1<<7;			//开启错误中断
	
	I2C1->CR1|=1<<0;	//开启I2C通讯
}

void i2c1_WriteOneChar(unsigned char i2c_address,unsigned char i2cin)
{
	I2C1->CR2&=~(0xff<<16);//清空字节长度
	I2C1->CR2|=1<<16;//写入字节长度
	I2C1->CR2|=1<<25;			//所有字节传输完毕后发送stop信号
	
	I2C1->CR2&=~(0x3FF);	//清空地址信息
	I2C1->CR2|=i2c_address<<1;//写入地址
	I2C1->CR2&=~(1<<10);		//主模式为写方向
	
	I2C1->CR2|=1<<13;			//发送起始信号
	
	while((I2C1->ISR&(1<<1))==0);//等待发送缓冲区为空
	I2C1->TXDR=i2cin;			//写入要传输的数据
	
	while((I2C1->ISR&(1<<5))==0);//等待停止位发送完毕
}

unsigned char i2c1_ReadOneChar(unsigned char i2c_address)
{
	unsigned char i2creadtemp;
	I2C1->CR2|=1<<16;			//写入一个字节
	I2C1->CR2|=1<<25;			//所有字节传输完毕后发送stop信号
	
	I2C1->CR2&=~(0x3FF);	//清空地址信息
	I2C1->CR2|=i2c_address<<1;//写入地址
	I2C1->CR2|=1<<10;			//主模式为读方向
	
	I2C1->CR2|=1<<13;			//发送起始信号
	
	while((I2C1->ISR&(1<<2))==0);//等待接收缓冲区为空
	i2creadtemp=I2C1->RXDR;
	while((I2C1->ISR&(1<<6))==0);//等待所有数据数据传输完毕
	
	return i2creadtemp;
}

void i2c1_MultiWrite(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
	unsigned char i2cwei=length;
	
	I2C1->CR2&=~(0xff<<16);//清空字节长度
	I2C1->CR2|=length<<16;//写入字节长度
	I2C1->CR2|=1<<25;			//所有字节传输完毕后发送stop信号
	
	I2C1->CR2&=~(0x3FF);	//清空地址信息
	I2C1->CR2|=i2c_address<<1;//写入地址
	I2C1->CR2&=~(1<<10);		//主模式为写方向
	
	I2C1->CR2|=1<<13;			//发送起始信号
	
	for(i2cwei=0;i2cwei<length;i2cwei++)
	{
		while((I2C1->ISR&(1<<1))==0);//等待发送缓冲区为空
		I2C1->TXDR=*(i2cdata+i2cwei);			//写入要传输的数据
	}
	while((I2C1->ISR&(1<<5))==0);//等待停止位发送完毕
}

void i2c1_MultiRead(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
	unsigned char i2cwei;
	I2C1->CR2|=length<<16;			//写入字节数
	I2C1->CR2|=1<<25;			//所有字节传输完毕后发送stop信号
	
	I2C1->CR2&=~(0x3FF);	//清空地址信息
	I2C1->CR2|=i2c_address<<1;//写入地址
	I2C1->CR2|=1<<10;			//主模式为读方向
	
	I2C1->CR2|=1<<13;			//发送起始信号
	
	for(i2cwei=0;i2cwei<length;i2cwei++)
	{
		while((I2C1->ISR&(1<<2))==0);//等待接收缓冲区为空
		i2cdata[i2cwei]=I2C1->RXDR;
	}
	while((I2C1->ISR&(1<<6))==0);//等待所有数据数据传输完毕
}

void i2c1_WriteRegist(unsigned char i2c_address,unsigned char *regaddress,unsigned char registlength,unsigned char* i2cdata,unsigned char datalength)
{
	unsigned char i2cwei;
	
	I2C1->CR2&=~(0xff<<16);//清空字节长度
	I2C1->CR2|=registlength<<16;//写入字节长度
	I2C1->CR2&=~(1<<25);			//所有字节传输完毕后发送restart信号
	
	I2C1->CR2&=~(0x3FF);	//清空地址信息
	I2C1->CR2|=i2c_address<<1;//写入地址
	I2C1->CR2&=~(1<<10);		//主模式为写方向
	
	I2C1->CR2|=1<<13;			//发送起始信号
	
	for(i2cwei=0;i2cwei<registlength;i2cwei++)
	{
		while((I2C1->ISR&(1<<1))==0);//等待发送缓冲区为空
		I2C1->TXDR=*(regaddress+i2cwei);			//写入要传输的数据
	}
	
	I2C1->CR2&=~(0xff<<16);//清空字节长度
	I2C1->CR2|=datalength<<16;//写入字节长度
	
	I2C1->CR2&=~(0x3FF);	//清空地址信息
	I2C1->CR2|=i2c_address<<1;//写入地址
	I2C1->CR2&=~(1<<10);		//主模式为写方向
	
	I2C1->CR2|=1<<13;			//发送起始信号
	
	for(i2cwei=0;i2cwei<datalength;i2cwei++)
	{
		while((I2C1->ISR&(1<<1))==0);//等待发送缓冲区为空
		I2C1->TXDR=*(i2cdata+i2cwei);			//写入要传输的数据
	}
	while((I2C1->ISR&(1<<6))==0);//等待所有数据传输完毕
	I2C1->CR2|=1<<14;	//发送停止位
	while((I2C1->ISR&(1<<5))==0);//等待停止位发送完毕
}

void i2c1_ReadRegist(unsigned char i2c_address,unsigned char *regaddress,unsigned char registlength,unsigned char* i2cdata,unsigned char datalength)
{
	unsigned char i2cwei;
	
	I2C1->CR2&=~(0xff<<16);//清空字节长度
	I2C1->CR2|=registlength<<16;//写入字节长度
	I2C1->CR2&=~(1<<25);			//所有字节传输完毕后发送restart信号
	
	I2C1->CR2&=~(0x3FF);	//清空地址信息
	I2C1->CR2|=i2c_address<<1;//写入地址
	I2C1->CR2&=~(1<<10);		//主模式为写方向
	
	I2C1->CR2|=1<<13;			//发送起始信号
	
	for(i2cwei=0;i2cwei<registlength;i2cwei++)
	{
		while((I2C1->ISR&(1<<1))==0);//等待发送缓冲区为空
		I2C1->TXDR=*(regaddress+i2cwei);			//写入要传输的数据
	}
	while((I2C1->ISR&(1<<6))==0);//等待所有数据数据传输完毕
	
	
	I2C1->CR2&=~(0xff<<16);//清空字节长度
	I2C1->CR2|=datalength<<16;			//写入字节数
	
	I2C1->CR2&=~(0x3FF);	//清空地址信息
	I2C1->CR2|=i2c_address<<1;//写入地址
	I2C1->CR2|=1<<10;			//主模式为读方向
	
	I2C1->CR2|=1<<13;			//发送起始信号
	
	for(i2cwei=0;i2cwei<datalength;i2cwei++)
	{
		while((I2C1->ISR&(1<<2))==0);//等待接收缓冲区为空
		i2cdata[i2cwei]=I2C1->RXDR;
	}
	while((I2C1->ISR&(1<<6))==0);//等待所有数据数据传输完毕
	I2C1->CR2|=1<<14;	//发送停止位
	while((I2C1->ISR&(1<<5))==0);//等待停止位发送完毕
}

首先是初始化,把时钟开启和配置管脚,这个不多说了

之后是配置通讯速率,我这里用的是4分频,数据是我通过计算配合实验得出来的。

最后是配置好地址模式并开启I2C

然后是发送过程,过程几乎和手册上描述的一致,配置好字节长度和地址信息。由于是多字节写入,所以我们使用的是Autoend模式,发送完最后一个字节自动发送停止位。然后注意好传输方向就可以了。

之后我们具体的发送过程我们按照手册要求,检测发送缓冲区,为空后依次写入数据即可。

由于我们是自动发送停止位,所以我们要检测停止位是否发送出去了。

我们再看一下读取过程,过程非常类似,注意好基础配置即可

然后我们再看看读寄存器流程,这是最复杂的过程。前半段没什么区别,主要在这里Autoend要为0,发送完这一段数据后,我们要准备发送restart信号。

后半段我们要注意两点,其一是重新配置方向,设置为读取方向。其二是由于我们没有开启Autoend模式,所以停止位需要由我们自己主动发送。

5.测试

首先我们先来测试连续发送,这里我先发送四个字节

是随便写的四个字节,从机地址为0x11

波形如下

速率为400Khz

然后是读取

6.结语

最开始开发的时候,我以为H7的I2C结构和F4非常相似,但是很显然ST官方把I2C重构了,以往令人诟病的bug基本算消失了。手册上写的也非常全面了,按照手册上的时序正常配置基本没有什么问题,官方的流程图给的也非常清晰了,踩坑也比较少了,这一点还是要给ST官方点赞的。那么OK,还是老样子,我们下一篇文章见。

相关推荐
秀秀更健康8 小时前
stm32: 系统时钟如何配置为72Mhz
stm32·单片机·嵌入式硬件
归零鸟13 小时前
WD Elements移动硬盘能识别出盘但不能出盘的修复记录
stm32·单片机·嵌入式硬件
追兮兮14 小时前
MCUQuickStart v1.1.0发布,一键生成Keil工程+RTOS模板
stm32·单片机·嵌入式硬件·freertos·gd32·keil5
rit843249915 小时前
STM32移植NES模拟器指南
stm32·单片机·嵌入式硬件
都在酒里15 小时前
STM32 I2C通信协议详解——标准库函数实现(通讯协议总结一)
stm32·嵌入式硬件·i2c
fengfuyao98515 小时前
STM32 HAL库实现串口DMA接收不定长数据
stm32·单片机·嵌入式硬件
yuan1999715 小时前
STM32直流无刷电机六拍方波控制器程序
stm32·单片机·嵌入式硬件
番茄灭世神16 小时前
PN学堂GD32教程第21篇——WiFiIOT
c语言·stm32·单片机·嵌入式·gd32
2zcode18 小时前
基于STM32的直流电机串级PID伺服控制系统设计与实现
stm32·单片机·嵌入式硬件·直流电机
都在酒里18 小时前
STM32低功耗休眠详解——睡眠、停止与待机模式实战,综合应用(三)
stm32·单片机·嵌入式硬件