STM32F407寄存器操作(DMA+I2C)

news2024/11/27 5:24:16

1.前言

因为后面需要用到大量基础通讯传输的问题,于是今天折腾了一下DMA传输I2C与SPI的效果,其实我先是把DMA+SPI搞出来了。但是考虑到网上对于STM32的I2C微词颇多,基础的协议都没有调试出来,更遑论DMA控制了,前面调不出来我想找资料都找不到,还是先把I2C发出来吧,稍晚再发SPI的吧。

2.理论

2.1 I2C控制器端

这是手册上对于控制器端设置的描述,我们的关注点首先是第二行,DMAEN位的设置问题,在7位地址发送出来前就要进行置为,这一点我们一会能在程序里看到,这基本是发送时需要注意的点。

然后是接收,最后一两行写的比较清楚。我们需要用到传输完成中断,这里手册上描述的略有歧义,这里的中断是DMA侧的,而非I2C的中断。

2.2 传输时DMA配置

配置方式和普通的存储器到外设的配置方式大同小异,这里大家有不懂的可以详细看看

2.3 发送时DMA配置

与发送时的配置类似,这里大家也稍微看看

3.程序

本次实验里,我设置I2C是主模式,从模式对单片机端用的非常少,然后是通讯速率,我设置的是100Khz,我这里以I2C1作为介绍了。此外我没有写传输单个数据的程序,因为考虑到DMA是大量数据下的优化方式,并且单个数据的传输配置非常麻烦,所以我就没写了。

额外提一嘴,因为我对于407的DMA和I2C用的非常多了,下面的内容我不会再一个接着一个寄存器讲了,大家有不懂的可以看看我前面的文章,尤其是我上一篇对于ST的I2C控制器的完整介绍,本次实验也是在那个程序上修改得出的。

3.1 I2C控制器初始化

首先我先把程序放出来

/*
注意,I2C一定要加上超时的设置,否则当IIC总线出错时,
没有超时检测可能造成MCU卡死在这里。
*/
void init_i2c(void)
{
	RCC->APB1ENR|=1<<21;	//使能时钟
	
	//初始化端口
	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主频与通信速率
	I2C1->CR2|=0x2A;
	I2C1->TRISE=0x2B;
	I2C1->CCR|=0xD2;
	
	I2C1->CR1&=~(1<<1);	//I2C模式
	I2C1->CR1|=1<<7;		//禁止时钟延长
	I2C1->CR2|=1<<8;		//开启错误中断	
	
	#if I2C_DMA_TX_EN==1||I2C_DMA_RX_EN==1
	I2C1->CR2|=1<<11;	//开启I2C发送DMA
	I2C1->CR2|=1<<12;	//设置DMA传输最后一位关闭应答
	#endif
	
	I2C1->CR1|=0x01;			//开启I2C通信
}

可以看到和前面的区别不大,我把改动的点拿出来说一下。主要是在下面这两个设置,I2C1的CR2寄存器里的11和12位置。

	#if I2C_DMA_TX_EN==1||I2C_DMA_RX_EN==1
	I2C1->CR2|=1<<11;	//开启I2C发送DMA
	I2C1->CR2|=1<<12;	//设置DMA传输最后一位关闭应答
	#endif

 这两位是啥呢?

首先是11位,这是DMA使能的开关,不必多说了,想用DMA是必开的。

然后是12位,什么是最后一次DMA传输?我们重新看一下上面理论里面对这个的描述

当LAST置1时,会硬件在最后一位置NACK,当然,这是在接收模式下。记得不用DMA时I2C的传输方式不?我们需要手动控制芯片是否输出ACK,并且需要在最后一位到来前把ACK位清空。有了这个控制位,我们再也不要判断了!!并且可以在进入传输第一个数据时就可以置位,芯片会自行进行应答配置!!(我的天,这才是该有的舒适感!)

3.2 传输时DMA设置

首先我们确定DMA的通道,这次实验里我发送是用DMA1的数据流7的通道1作为发送端

程序如下

//初始化DMA1 组7 通道1
//IIC1_TX
void init_DMA1_S7C1(unsigned char *IICData,unsigned short IICWEI)
{	
	DMA1_Stream7 ->CR   = 0;//禁止数据流 ,才能写寄存器 
	
	//外设地址寄存器
	//将所需寄存器的地址放入PAR寄存器
	DMA1_Stream7 ->PAR  = (unsigned int)(&I2C1->DR);
	
	//数据流地址寄存器
	//M1AR仅在双通道模式下有用
	//将数据所在地址给M0AR寄存器
	DMA1_Stream7 ->M0AR = (unsigned int)(IICData);
	
	DMA1_Stream7 ->NDTR = IICWEI;			// 一次传输数量
	DMA1_Stream7 ->FCR  = 0x21;		//FIFO所有配置失效
	DMA1_Stream7 ->CR |= 1<< 6;		//储存器到外设模式
	
	//循环模式:
	//当NDTR寄存器减到0时自动重装
	//单次模式(普通模式):
	//NDTR减到0后停止DMA
	DMA1_Stream7 ->CR &=~(1<<8);	//非循环模式
	DMA1_Stream7 ->CR &=~(3<<11);	//外设数据长度:8位
	DMA1_Stream7 ->CR &=~(3<<13);	//存储器数据长度:8位
	
	DMA1_Stream7 ->CR &= ~(1<<9); //外设非增量模式
	DMA1_Stream7 ->CR |= 1<<10;   //存储器增量模式,指针增加,可用于传输数组
	DMA1_Stream7 ->CR |= 1<<16;   //中等优先级
	
	//突发传输
	//DMA占用CPU总线时间,此时CPU无法工作
	//一个节拍:传输多少次32位变量
	//应用场景:从ram里读出字节
	DMA1_Stream7 ->CR &= ~(3<<21);   //外设突发单次传输
	DMA1_Stream7 ->CR &= ~(3<<23);   //存储器突发单次传输
	
	DMA1_Stream7 ->CR |= 1<<25;   //通道1
	DMA1_Stream7 ->CR |= 1<<0;    //使能数据流
}

和普通存储器到寄存器没什么区别,大家略微看看,没什么特殊设置的。

3.3 接收时DMA设置

接收的通道是DMA1数据流5通道1

程序如下

先是DMA初始化

//初始化DMA1 组5 通道1
//IIC1_RX
void init_DMA1_S5C1(unsigned char *IICData,unsigned short IICWEI)
{	
	DMA1_Stream5 ->CR   = 0;//禁止数据流 ,才能写寄存器 
	
	DMA1_Stream5 ->CR |= 1<<4;		//开启传输完成中断
	DMA1->LIFCR	|=0x3d<<6;	//清空组5所有中断标志位
	
	//外设地址寄存器
	//将所需寄存器的地址放入PAR寄存器
	DMA1_Stream5 ->PAR  = (unsigned int)(&I2C1->DR);
	
	//数据流地址寄存器
	//M1AR仅在双通道模式下有用
	//将数据所在地址给M0AR寄存器
	DMA1_Stream5 ->M0AR = (unsigned int)(IICData);
	
	DMA1_Stream5 ->NDTR = IICWEI;			// 一次传输数量
	DMA1_Stream5 ->FCR  = 0x21;		//FIFO所有配置失效
	DMA1_Stream5 ->CR &= ~(3<<6);		//外设到存储器模式
	
	//循环模式:
	//当NDTR寄存器减到0时自动重装
	//单次模式(普通模式):
	//NDTR减到0后停止DMA
	DMA1_Stream5 ->CR &=~(1<<8);	//非循环模式
	DMA1_Stream5 ->CR &=~(3<<11);	//外设数据长度:8位
	DMA1_Stream5 ->CR &=~(3<<13);	//存储器数据长度:8位
	
	DMA1_Stream5 ->CR &= ~(1<<9); //外设非增量模式
	DMA1_Stream5 ->CR |= 1<<10;   //存储器增量模式,指针增加,可用于传输数组
	DMA1_Stream5 ->CR |= 1<<16;   //中等优先级
	
	//突发传输
	//DMA占用CPU总线时间,此时CPU无法工作
	//一个节拍:传输多少次32位变量
	//应用场景:从ram里读出字节
	DMA1_Stream5 ->CR &= ~(3<<21);   //外设突发单次传输
	DMA1_Stream5 ->CR &= ~(3<<23);   //存储器突发单次传输
	
	DMA1_Stream5 ->CR |= 1<<25;   //通道1
	DMA1_Stream5 ->CR |= 1<<0;    //使能数据流
	
	DMA1S5_InitInterrupt();
	DMA1S5_Subpriority();
}

中断服务函数如下

void DMA1_Stream5_IRQHandler(void)
{
	if(DMA1->HISR&(1<<27))
	{
		DMA1->HIFCR|=(1<<27);
	}
	
	if(DMA1->HISR&(1<<11))
	{
		I2C1->CR1|=1<<9;
		DMA1->HIFCR|=(1<<11);
	}
}

我们先从初始化程序来看,唯一要变的是启用中断,记得开启传输完成中断就行。这里的中断还是比较好用的,如果大家写过串口的,那个需要用到空闲中断,这里就是普通的传输完成中断,没什么特别的。

此外我们在开启中断后需要对所有状态位进行清空,否则开启中断后会默认置位了,程序会卡bug并且需要先开启中断再清空。

然后是中断服务函数,因为前面我用DAC,而DAC是相同数据流,不过是通道7。所以服务函数里面我有两个,大家如果没有需要可以只保留这一段。这里为什么会对I2C进行设置?

 

我们看看手册,第9位是停止位,给这位置1可以在接收完成后补上停止信号。

3.4 发送程序

这是单次发送多个数据的程序

void i2c_MultiWrite(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
	unsigned int wei=0;
	
	I2C1->CR1|=1<<8;							//发出起始信号
	while((I2C1->SR1&(1<<0))==0);	//等待起始信号发送完毕
	
	I2CEV(5);
	#if ADDRESSBIT==7
	I2C1->DR=(i2c_address<<1);	//写入地址
	#else
	I2C1->DR=i2c_address;				//写入地址
	#endif
	while((I2C1->SR1&(1<<1))==0);	//等待地址发送完毕
	
	I2CEV(6);
	for(wei=0;wei<=length;wei++)
	{
		while((I2C1->SR1&(1<<7))==0);	//等待数据发送完毕
		#if I2C_DMA_TX_EN==0
		I2C1->DR=i2cdata[wei];				//EV8
		#endif
	}
	I2C1->CR1|=1<<9;							//发送停止位
}

讲一下核心区别

其他的基本不用变,核心变化在这里,原本是CPU控制发送数据,这里可以不用了,当TxE(即SR1的第7位)为1时会产生一个DMA请求,DMA直接搬一个数据到DR寄存器了。我们只要保证数据没用发生覆盖和移除就行,这也就是只保留上一句的原因:保证数据完整地发出去了。

3.5 接收程序

这是接收多个数据的函数(注意不是收寄存器的!!收寄存器需要发送,我后面会写)

void i2c_MultiRead(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
	unsigned int wei=0;
	
	I2C1->CR1|=1<<8;							//发出起始信号
	while((I2C1->SR1&(1<<0))==0);	//等待起始信号发送完毕
	
	I2CEV(5);
	#if ADDRESSBIT==7
	I2C1->DR=(i2c_address<<1)+1;	//写入地址
	#else
	I2C1->DR=i2c_address;				//写入地址
	#endif
	while((I2C1->SR1&(1<<1))==0);	//等待地址发送完毕
	
	I2CEV(6);
	for(wei=0;wei<=length;wei++)
	{
		if(length!=1){I2C1->CR1|=1<<10;}						//多重数据接收时由主机应答
		#if I2C_DMA_RX_EN==0
		if(wei==length-1)
		{
			I2C1->CR1&=~(1<<10);					//最后一位无需应答
			I2C1->CR1|=1<<9;							//提前写入停止位
		}
		while((I2C1->SR1&(1<<6))==0);		//等待数据接收完毕
		i2cdata[wei]=I2C1->DR;				//EV8
		#endif
	}
}

与发送同理

不过需要的内容更少了,我们甚至不需要等待RxE,我们只需要提前把主机应答的信号打开,最后一个NACK也是自动产生的。(简直不要太幸福!!)

4.测试

4.1 所有程序

首先是完整程序

i2c.c

#include "iic.h"

/*
注意,I2C一定要加上超时的设置,否则当IIC总线出错时,
没有超时检测可能造成MCU卡死在这里。
*/
void init_i2c(void)
{
	RCC->APB1ENR|=1<<21;	//使能时钟
	
	//初始化端口
	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主频与通信速率
	I2C1->CR2|=0x2A;
	I2C1->TRISE=0x2B;
	I2C1->CCR|=0xD2;
	
	I2C1->CR1&=~(1<<1);	//I2C模式
	I2C1->CR1|=1<<7;		//禁止时钟延长
	I2C1->CR2|=1<<8;		//开启错误中断	
	
	#if I2C_DMA_TX_EN==1||I2C_DMA_RX_EN==1
	I2C1->CR2|=1<<11;	//开启I2C发送DMA
	I2C1->CR2|=1<<12;	//设置DMA传输最后一位关闭应答
	#endif
	
	I2C1->CR1|=0x01;			//开启I2C通信
}


void I2CEV(unsigned char EVin)
{
	unsigned char EVTemp;
	switch(EVin)
	{
		case 5:
			EVTemp=I2C1->SR1;	//读取sr1寄存器
			break;
		
		case 6:
			EVTemp=I2C1->SR1;	//读取sr1寄存器
			EVTemp=I2C1->SR2;	//读取sr2寄存器
		break;
		
		case 8:
			;
		break;
	}
}

void i2c_WriteOneChar(unsigned char i2c_address,unsigned char i2cin)
{
	unsigned int wait;
	
	I2C1->CR1|=1<<8;							//发出起始信号
	
	//等待起始信号发送完毕
	for(wait=0;(I2C1->SR1&(1<<0))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	
	I2CEV(5);
	
	
	#if ADDRESSBIT==7
	I2C1->DR=i2c_address<<1;			//写入地址
	#else
	I2C1->DR=i2c_address;				//写入地址
	#endif
	
	//等待地址发送完毕
	for(wait=0;(I2C1->SR1&(1<<1))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	
	I2CEV(6);	
	I2C1->DR=i2cin;								//EV8
	//等待数据发送完毕
	for(wait=0;(I2C1->SR1&(1<<7))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	
	I2C1->CR1|=1<<9;							//写入停止位
}

unsigned char i2c_ReadOneChar(unsigned char i2c_address)
{
	unsigned char readtemp;
	unsigned char addresstemp;
		
	I2C1->CR1|=1<<8;							//发出起始信号
	while((I2C1->SR1&(1<<0))==0);	//等待起始信号发送完毕
	
	I2CEV(5);
	
	#if ADDRESSBIT==7
	I2C1->DR=(i2c_address<<1)+1;	//写入地址
	#else
	I2C1->DR=i2c_address;				//写入地址
	#endif
	
	while((I2C1->SR1&(1<<1))==0);	//等待地址发送完毕
	
	I2CEV(6);
	readtemp=I2C1->DR;						//EV7
	while((I2C1->SR1&(1<<6))==0);	//等待数据接收完毕
	
	I2C1->CR1|=1<<9;							//写入停止位
	
	return readtemp;
}

void i2c_MultiWrite(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
	unsigned int wei=0;
	
	I2C1->CR1|=1<<8;							//发出起始信号
	while((I2C1->SR1&(1<<0))==0);	//等待起始信号发送完毕
	
	I2CEV(5);
	#if ADDRESSBIT==7
	I2C1->DR=(i2c_address<<1);	//写入地址
	#else
	I2C1->DR=i2c_address;				//写入地址
	#endif
	while((I2C1->SR1&(1<<1))==0);	//等待地址发送完毕
	
	I2CEV(6);
	for(wei=0;wei<=length;wei++)
	{
		while((I2C1->SR1&(1<<7))==0);	//等待数据发送完毕
		#if I2C_DMA_TX_EN==0
		I2C1->DR=i2cdata[wei];				//EV8
		#endif
	}
	I2C1->CR1|=1<<9;							//发送停止位
}

void i2c_MultiRead(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
	unsigned int wei=0;
	
	I2C1->CR1|=1<<8;							//发出起始信号
	while((I2C1->SR1&(1<<0))==0);	//等待起始信号发送完毕
	
	I2CEV(5);
	#if ADDRESSBIT==7
	I2C1->DR=(i2c_address<<1)+1;	//写入地址
	#else
	I2C1->DR=i2c_address;				//写入地址
	#endif
	while((I2C1->SR1&(1<<1))==0);	//等待地址发送完毕
	
	I2CEV(6);
	for(wei=0;wei<=length;wei++)
	{
		if(length!=1){I2C1->CR1|=1<<10;}						//多重数据接收时由主机应答
		#if I2C_DMA_RX_EN==0
		if(wei==length-1)
		{
			I2C1->CR1&=~(1<<10);					//最后一位无需应答
			I2C1->CR1|=1<<9;							//提前写入停止位
		}
		while((I2C1->SR1&(1<<6))==0);		//等待数据接收完毕
		i2cdata[wei]=I2C1->DR;				//EV8
		#endif
	}
}

void i2c_ReadRegist(unsigned char i2c_address,unsigned char *regaddress,unsigned char registlength,unsigned char* i2cdata,unsigned char datalength)
{
	
	unsigned int wait;
	unsigned int wei=0;
	
	//发送
	
	I2C1->CR1|=1<<8;							//发出起始信号
	//等待起始信号发送完毕
	for(wait=0;(I2C1->SR1&(1<<0))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	
	I2CEV(5);											//EV5
	
	#if ADDRESSBIT==7
	I2C1->DR=i2c_address<<1;			//写入地址
	#else
	I2C1->DR=i2c_address;				//写入地址
	#endif
	
	//等待地址发送完毕
	for(wait=0;(I2C1->SR1&(1<<1))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	
	I2CEV(6);	
	for(wei=0;wei<registlength;wei++)
	{
		#if I2C_DMA_TX_EN==1
		#else
		I2C1->DR=regaddress[wei];				//EV8
		#endif
		//等待数据发送完毕	
		for(wait=0;(I2C1->SR1&(1<<7))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	}
	
	
	//接收
	I2C1->CR1|=1<<8;							//发出起始信号
	//等待起始信号发送完毕
	for(wait=0;(I2C1->SR1&(1<<0))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	
	I2CEV(5);
	
	#if ADDRESSBIT==7
	I2C1->DR=(i2c_address<<1)+1;	//写入地址
	#else
	I2C1->DR=i2c_address;				//写入地址
	#endif
	//等待地址发送完毕
	for(wait=0;(I2C1->SR1&(1<<1))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	
	I2CEV(6);

	for(wei=0;wei<datalength;wei++)
	{
		if(datalength!=1){I2C1->CR1|=1<<10;}						//多重数据接收时由主机应答
		#if I2C_DMA_RX_EN==0
		if(wei==datalength-1)
		{
			I2C1->CR1&=~(1<<10);					//最后一位无需应答
			I2C1->CR1|=1<<9;							//提前写入停止位
		}
		//等待数据接收完毕
		for(wait=0;(I2C1->SR1&(1<<6))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
		i2cdata[wei]=I2C1->DR;				//EV8
		#endif
	}
}

void i2c_WriteRegist(unsigned char i2c_address,unsigned char *i2cdata,unsigned char datalenght)
{
	unsigned int wait;
	unsigned char wei=0;
	//发送
	
	I2C1->CR1|=1<<8;							//发出起始信号
	//等待起始信号发送完毕
	for(wait=0;(I2C1->SR1&(1<<0))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	
	I2CEV(5);											//EV5
	
	#if ADDRESSBIT==7
	I2C1->DR=i2c_address<<1;			//写入地址
	#else
	I2C1->DR=i2c_address;				//写入地址
	#endif
	
	//等待地址发送完毕
	for(wait=0;(I2C1->SR1&(1<<1))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	
	I2CEV(6);
	for(wei=0;wei<datalenght;wei++)
	{
		I2C1->DR=i2cdata[wei];						//EV8
		//等待数据发送完毕	
		for(wait=0;(I2C1->SR1&(1<<7))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
	}
	
	I2C1->CR1|=1<<9;							//写入停止位
}

i2c.h 

#ifndef __IIC_H
#define __IIC_H

#include "stm32f4xx.h"

//I2C数据长度
#define ADDRESSBIT	7
//I2C等待时间
#define WAITTIME	5000
//I2C传输是否开启DMA功能
#define I2C_DMA_TX_EN	1
//I2C接收是否开启DMA功能
#define I2C_DMA_RX_EN	1

void init_i2c(void);
void i2c_WriteOneChar(unsigned char i2c_address,unsigned char i2cin);
unsigned char i2c_ReadOneChar(unsigned char i2c_address);
void i2c_MultiWrite(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length);
void i2c_MultiRead(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length);
void i2c_ReadRegist(unsigned char i2c_address,unsigned char *regaddress,unsigned char registlength,unsigned char* i2cdata,unsigned char datalength);

#endif

DMA和中断 

//初始化DMA1 组7 通道1
//IIC1_TX
void init_DMA1_S7C1(unsigned char *IICData,unsigned short IICWEI)
{	
	DMA1_Stream7 ->CR   = 0;//禁止数据流 ,才能写寄存器 
	
	//外设地址寄存器
	//将所需寄存器的地址放入PAR寄存器
	DMA1_Stream7 ->PAR  = (unsigned int)(&I2C1->DR);
	
	//数据流地址寄存器
	//M1AR仅在双通道模式下有用
	//将数据所在地址给M0AR寄存器
	DMA1_Stream7 ->M0AR = (unsigned int)(IICData);
	
	DMA1_Stream7 ->NDTR = IICWEI;			// 一次传输数量
	DMA1_Stream7 ->FCR  = 0x21;		//FIFO所有配置失效
	DMA1_Stream7 ->CR |= 1<< 6;		//储存器到外设模式
	
	//循环模式:
	//当NDTR寄存器减到0时自动重装
	//单次模式(普通模式):
	//NDTR减到0后停止DMA
	DMA1_Stream7 ->CR &=~(1<<8);	//非循环模式
	DMA1_Stream7 ->CR &=~(3<<11);	//外设数据长度:8位
	DMA1_Stream7 ->CR &=~(3<<13);	//存储器数据长度:8位
	
	DMA1_Stream7 ->CR &= ~(1<<9); //外设非增量模式
	DMA1_Stream7 ->CR |= 1<<10;   //存储器增量模式,指针增加,可用于传输数组
	DMA1_Stream7 ->CR |= 1<<16;   //中等优先级
	
	//突发传输
	//DMA占用CPU总线时间,此时CPU无法工作
	//一个节拍:传输多少次32位变量
	//应用场景:从ram里读出字节
	DMA1_Stream7 ->CR &= ~(3<<21);   //外设突发单次传输
	DMA1_Stream7 ->CR &= ~(3<<23);   //存储器突发单次传输
	
	DMA1_Stream7 ->CR |= 1<<25;   //通道1
	DMA1_Stream7 ->CR |= 1<<0;    //使能数据流
}

//初始化DMA1 组5 通道1
//IIC1_RX
void init_DMA1_S5C1(unsigned char *IICData,unsigned short IICWEI)
{	
	DMA1_Stream5 ->CR   = 0;//禁止数据流 ,才能写寄存器 
	
	DMA1_Stream5 ->CR |= 1<<4;		//开启传输完成中断
	DMA1->LIFCR	|=0x3d<<6;	//清空组5所有中断标志位
	
	//外设地址寄存器
	//将所需寄存器的地址放入PAR寄存器
	DMA1_Stream5 ->PAR  = (unsigned int)(&I2C1->DR);
	
	//数据流地址寄存器
	//M1AR仅在双通道模式下有用
	//将数据所在地址给M0AR寄存器
	DMA1_Stream5 ->M0AR = (unsigned int)(IICData);
	
	DMA1_Stream5 ->NDTR = IICWEI;			// 一次传输数量
	DMA1_Stream5 ->FCR  = 0x21;		//FIFO所有配置失效
	DMA1_Stream5 ->CR &= ~(3<<6);		//外设到存储器模式
	
	//循环模式:
	//当NDTR寄存器减到0时自动重装
	//单次模式(普通模式):
	//NDTR减到0后停止DMA
	DMA1_Stream5 ->CR &=~(1<<8);	//非循环模式
	DMA1_Stream5 ->CR &=~(3<<11);	//外设数据长度:8位
	DMA1_Stream5 ->CR &=~(3<<13);	//存储器数据长度:8位
	
	DMA1_Stream5 ->CR &= ~(1<<9); //外设非增量模式
	DMA1_Stream5 ->CR |= 1<<10;   //存储器增量模式,指针增加,可用于传输数组
	DMA1_Stream5 ->CR |= 1<<16;   //中等优先级
	
	//突发传输
	//DMA占用CPU总线时间,此时CPU无法工作
	//一个节拍:传输多少次32位变量
	//应用场景:从ram里读出字节
	DMA1_Stream5 ->CR &= ~(3<<21);   //外设突发单次传输
	DMA1_Stream5 ->CR &= ~(3<<23);   //存储器突发单次传输
	
	DMA1_Stream5 ->CR |= 1<<25;   //通道1
	DMA1_Stream5 ->CR |= 1<<0;    //使能数据流
	
	DMA1S5_InitInterrupt();
	DMA1S5_Subpriority();
}

void DMA1_Stream5_IRQHandler(void)
{
	if(DMA1->HISR&(1<<27))
	{
		DMA1->HIFCR|=(1<<27);
	}
	
	if(DMA1->HISR&(1<<11))
	{
		I2C1->CR1|=1<<9;
		DMA1->HIFCR|=(1<<11);
	}
}


4.2 多数据发送

首先是发送,初始化IIC,然后配置DMA,最后写好传输函数,这里MultiWrite里面后面两个参数与上面DMA里面的参数保持一致就行。

unsigned char iic_regist[2]={0x20,0x08};	
init_i2c();//硬件IIC初始化
init_DMA1_S7C1(iic_regist,2);
i2c_MultiWrite(0x01,iic_data,2);//开启传输

我们发送两个数据0x20 0x08测试一下

波形非常漂亮,波形数据和程序里是一样的。

4.3 多数据接收

与发送类似

unsigned char iic_data[2];	
init_i2c();//硬件IIC初始化
init_DMA1_S5C1(iic_data,3);//配置DMA
i2c_MultiRead(0x01,iic_data,3);//开启传输

4.4 寄存器接收

两者一结合就行了

unsigned char iic_data[2];
unsigned char iic_regist[2]={0x20,0x08};	
init_DMA1_S7C1(iic_regist,2);
init_DMA1_S5C1(iic_data,3);
i2c_ReadRegist(0x01,iic_regist,2,iic_data,3);

没毛病

5.结语

今天搞的也算是了却一个心结了,这样的I2C完全碾压了模拟I2C。网上关于ST的I2C几乎处于以讹传讹的阶段,没有人研究过I2C底层操作。其实加上DMA后I2C更好操作了,传完地址后几乎不需要研究传输的过程,全都是芯片的事了。这两天有点事,稍晚我会把SPI发出来,争取早点做成视频传到B站上,让更多人用到吧。那么还是老样子,有问题评论区留言,我们下篇文章见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2187222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

排序算法之——归并排序,计数排序

文章目录 前言一、归并排序1. 归并排序的思想2. 归并排序时间复杂度及空间复杂度3. 归并排序代码实现1&#xff09;递归版本2&#xff09;非递归版本 二、计数排序1. 计数排序的思想2. 计数排序的时间复杂度及空间复杂度3. 计数排序代码实现 总结&#xff08;排序算法稳定性&am…

计算机毕业设计 基于Python的无人超市管理系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

深度学习基础—残差网络ResNets

1.残差网络结构 当网络训练的很深很深的时候&#xff0c;效果是否会很好&#xff1f;在这篇论文中&#xff0c;作者给出了答案&#xff1a;Deep Residual Learning for Image Recognitionhttps://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/He_Deep_Residual_…

使用html写一个能发起请求的登录界面

目录 head部分 内联样式部分 body部分 login-form类的div myModal类的div id script部分 总的代码 界面与操作演示 <!DOCTYPE html> <html lang"en"> <!DOCTYPE html> 这是文档类型声明&#xff0c;告诉浏览器这是一个 HTML文档。 <…

jmeter学习(1)线程组与发送请求

1、线程组 执行顺序 &#xff1a;setUp线程组 > 线程组 > tearDown线程组 2、 发送请求 可以发送http、java、dubbo 请求等 下面讲解发送http 1&#xff09;Http请求默认值 作用范围是该线程组下的所有HTTP请求&#xff0c;如果http请求设置的与默认值冲突&#xff0…

前端规范工程-3:CSS规范(Stylelint)

样式规范工具&#xff08;StyleLint&#xff09; Stylelint 是一个灵活且强大的工具&#xff0c;适用于保持 CSS 代码的质量和一致性。结合其他工具&#xff08;如 Prettier 和 ESLint&#xff09;&#xff0c;可以更全面地保障前端代码的整洁性和可维护性。 目录 样式规范工具…

oracle virtualBox 拖动文件到虚拟机内报错

DnD: Error: Drag and drop to guest not possible -- either the guest OS does not support this, or the.... 首先将拖放的双向选项打开 打开CD驱动器 根据操作系统是32还是64安装对应的安装包&#xff0c;amd64为64位系统&#xff0c;x86为32位系统 安装后重启即可向虚拟机…

【C语言】数组练习

【C语言】数组练习 练习1&#xff1a;多个字符从两端移动&#xff0c;向中间汇聚练习2、二分查找 练习1&#xff1a;多个字符从两端移动&#xff0c;向中间汇聚 编写代码&#xff0c;演示多个字符从两端移动&#xff0c;向中间汇聚 练习2、二分查找 在⼀个升序的数组中查找指…

sql语句牛客练习

文章目录 1. SQL21 浙江大学用户题目回答情况① 错误② 正确 2. SQL22 统计每个学校的答过题的用户的平均答题数① 错误② 正确 3. SQL23 统计每个学校各难度的用户平均刷题数4. SQL25 查找山东大学或者性别为男生的信息① 错误② 正确 5. SQL26 计算25岁以上和以下的用户数量①…

Wireshark 解析QQ、微信的通信协议|TCP|UDP

写在前面 QQ&#xff0c;微信这样的聊天软件。我们一般称为im&#xff0c;Instant Messaging&#xff0c;即时通讯系统。那大家会不会有疑问&#xff0c;自己聊天内容会不会被黑客或者不法分子知道&#xff1f;这种体量的im是基于tcp还是udp呢&#xff1f;这篇文章我们就来探索…

基于Springboot的在线订餐系统设计与实现(论文+源码)_kaic

摘 要 当今世界&#xff0c;互联网以及和互联网有关的行业都在不断的发展&#xff0c;也在持续走进人们的生活&#xff0c;在此趋势下人们对于通过互联网解决生活问题的需求愈来愈多&#xff0c;本文考虑到了这些情况后做出了该订餐系统。 本系统选择了MySQL作为主要存储单元…

搭建k8s集群服务(kubeadm方式)

准备工作 操作系统版本&#xff1a;CentOS Linux release 7.9.2009 (Core) 虚拟机硬件配置&#xff1a;2核8G内存&#xff08;最低2G&#xff09;&#xff0c;硬盘最低25G&#xff1b; linux内核版本&#xff08;3.10版本尝试失败&#xff09;&#xff1a;5.4.268-1.el7.elr…

每日读则推(三)

n.(事件的)发生地点,(活动的)场所 n.雄性大园丁鸟 n.多细枝的,苗条的 v.放大,扩大(声音);增强,加强 Male great bowerbirds build twiggy concert venues that amplify their raucous songs and n.园丁鸟 …

讲职场:不要经常说消极的话

1、不要经常说消极的话&#xff0c;不要接触让自己力量消失的人 习惯性用强大的语言加持自己&#xff0c;才能立起来 2、只要你下决心钻研一门技术&#xff0c;你就全身心扑在上面&#xff0c;把每一个细节研究透&#xff0c;只有这样&#xff0c;你才能在学会之后&#xff0…

投资精明之选,国内外低代码平台性价比排行榜

本文介绍了国内外10大低代码平台的特点及性价比&#xff0c;包括ZohoCreator、OutSystems等&#xff0c;强调低代码平台通过简化开发过程&#xff0c;提高应用开发效率和质量&#xff0c;适合不同规模企业。选择时考虑企业需求和预算&#xff0c;建议试用后再决策。 一、Zoho C…

Apache安装后无法启动的问题“不能再本地计算机启动apache”

首先安装 参考这位博主的小白下载和安装Apache的教程&#xff08;保姆级&#xff09; 遇到的问题 在启动的时候遇到问题 说apache不能在本地计算机启动 解决方法 1. 路径检查 首先&#xff01;&#xff01;&#xff01; 请仔细检查你的httpd.conf文件中的Apache路径是否…

基于Springboot+Vue的汉服交易小程序的设计与实现(含源码+数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

Git版本控制工具--关于命令

Git版本控制工具 学习前言 在项目开发中&#xff0c;总是需要多个人同时对一个项目进行修改&#xff0c;如何高效快速地进行修改&#xff0c;且控制各自修改的版本不会和他人的进行重叠&#xff0c;这就需要用到Git分布式版本控制器了 作用 解决了一致性&#xff0c;并发性…

如何解决 Photoshop 中的“暂存盘已满”错误

好久没有用Photoshop了&#xff0c;今天想自己修个图&#xff0c;就启动了一下PS&#xff0c;结果出现一个对话框“不能初始化Photoshop&#xff0c;因为暂存盘已经满”。我一直存在C盘焦虑&#xff0c;常年C盘显示都是红色的。上网一查&#xff0c;发现PS启动时暂存盘的空间是…

YOLOv11改进 | Neck篇 | YOLOv11引入Gold-YOLO

1. Gold-YOLO介绍 1.1 摘要: 在过去的几年中,YOLO 系列模型已成为实时目标检测领域的领先方法。 许多研究通过修改架构、增加数据和设计新的损失,将基线提升到更高的水平。 然而,我们发现以前的模型仍然存在信息融合问题,尽管特征金字塔网络(FPN)和路径聚合网络(PANet)…