STM32F407寄存器操作(DMA+I2C)

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站上,让更多人用到吧。那么还是老样子,有问题评论区留言,我们下篇文章见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值