STM32F103标准库硬件IIC+DMA连续数据发送、接收

前言

  这几天在学习IIC通信读写数据,学习了软件IIC实现、硬件IIC实现I2C通信后,发现无论是软件I2C还是硬件I2C都比较占用CPU资源,虽然硬件IIC不需要像软件IIC一样需要靠程序不断地拉高、拉低SCL、SDA管脚,可是硬件IIC依旧需要CPU不断地运行相关的指令实现完整的IIC功能。对于该弊端的处理方法,使用CPU小助手——DMA配合I2C使用,可以极大程度的减轻CPU负担。
  对于我来说,为了学习这块儿内容,我在网上挣扎了好几天,实话说,没找到多少对我有实质性帮助的文章,因为我的目的是为了利用STM32的标准库完成这项工作,但是网上出现的比较多的是HAL库版本以及LL库版本的IIC+DMA硬件读写程序,因为HAL库封装的比较完整,不需要用户像标准库那样了解的比较通透,我看的是云里雾里,反正就是没看明白。经过反复的挣扎,终于搞出了想要的效果,写这篇文章主要是为了给自己留一个记录和笔记,方便自己以后查阅,如果同时这篇文章能够帮到大家那最好不过。因为大多是自己摸索的,该文章也许有很多错误的描述和地方,如果您看到有误的地方,可以私信或者评论,我尽快改正,谢谢。
  这篇文章为了实现什么? 这篇文章的主要实现内容是实现了利用IIC+DMA(STM32标准库)对AHT20温湿度传感器发送AC指令(即开始测量命令)、获取AHT20传感器的测量结果的程序。本篇文章想要展现的是DMA配合硬件IIC方法,即如何使用硬件IIC和DMA相配合完成数据发送、数据接收以及如何利用DMA和I2C完成数据的发送后再利用DMA配合IIC完成数据接收,所以本篇文章的程序对于AHT20模块来说不算完整,即本篇文章所设计的程序中,未设置传感器校准、软复位等操作(没有这些操作有可能会有无法使用AHT20模块或者无法获得正确传感器数据的情况),但AHT20的主要功能已实现,各位知道即可。
  学习这篇文章的内容需要提前知道哪些知识? 想要学懂这些东西需要一定程度的了解硬件IIC的工作过程并了解DMA的工作机理,也需要知道一些关于中断的知识。如果在学习该文章前能独立的完成利用硬件IIC读写数据的程序以及能写出其他的DMA数据转运的程序那么在本篇文章中你将轻松很多。

1、AHT20模块的简单了解(为了更好的理解程序语句)

  这部分内容主要是让大家知道我在程序中发的部分指令是什么意思,不至于看代码一头雾水,如果了解该模块可以直接跳过。
  对于这部分内容,大家简单了解AHT20的设备地址、需要控制AHT20开始测量的指令是什么、测量完成的数据会以几个字节的形式返回,每个字节对应于什么意思(即是状态表示字节,还是温湿度表示字节)即可。

AHT20的设备地址为0x38,在接下来的程序中我将定义为 AHT20_ADDRESS

#define AHT20_ADDRESS (0x38 << 1)  // AHT20设备地址

在这里插入图片描述
  因为该篇文章没有对AHT20进行初始化,所以只发送开始测量指令还有接收数据,对于要发送的指令,我们将其放在数组中(发送开始测量需要连续发送三个字节数据,分别是0xAC、0x33、0x00),到时候由DMA将其发送到I2C1的DR寄存器中,并由硬件自动发送给外设。(此外,程序并没有对下图中序号3中所说的 Bit[7] 状态位进行判断,所以程序不严谨,大家后期自己完善)

static uint8_t AC[3] = {0xAC,0x33,0x00};  // AHT20开始测量指令

在这里插入图片描述
在这里插入图片描述
  AHT20的温湿度数据(在收到测量信号了之后75ms)将连续获得6个字节的数据,其中获得的第一个数据是AHT20的状态,咱们这篇文章不讨论,状态之后的5个字节的数据是我们需要了解的,分别是湿度数据和温度数据。
在这里插入图片描述
  最后得到的温湿度数据转换公式为下图中所示,了解即可
在这里插入图片描述

2、了解DMA相关中断标志位以及I2C相对应的DMA通道

  因为在这个程序中,我需要在DMA接受完数据后对其进行一定的处理(将温度、湿度分开存放),我将该过程放在了DMA接收完成后产生的中断中处理;此外,在DMA发送完成后,我在其中断中执行关闭硬件IIC操作(AHT20需要这个步骤,没有该步骤,即使等待再多事件,AHT20也不会开始数据测量)。各位学习完成后可以更改本文的代码,上述操作去掉中断也是可以实现的,就算是课后习题吧。
  所以给位就先看一下DMA中断相关内容吧,在本文中,我们只用到DMA传输完成中断,即下图中的(TCIF事件标志位),传输完成和传输错误标志位未使用,各位可以学习了解一下,后期更改代码,使得自己的代码更加健壮。
在这里插入图片描述
  STM32F103有两个IIC接口,I2C1和I2C2的DMA请求均传入DMA1,其中I2C1的请求分别接入DMA1的通道6和通道7,对于单片机来说,DMA1的通道6负责I2C1的数据发送,DMA1的通道7负责I2C1的数据接收。
在这里插入图片描述

在这篇文章的程序中,我们将使用STM32F103单片机的 PB6 和 PB7 管脚,他俩分别对应于I2C1的 SCL 和 SDA

  相关中断函数名称 DMA1_Channel6_IRQHandler 、DMA1_Channel7_IRQHandler

void DMA1_Channel6_IRQHandler(void)  // DMA1通道6中断函数,即DMA完成I2C1相关数据发送搬运后发生的中断
void DMA1_Channel7_IRQHandler(void)  // DMA1通道7中断函数,即DMA完成I2C1相关数据接收搬运后发生的中断

3、本篇文章提及程序中相关的变量定义提前了解

变量名称作用备注
uint8_t  Send_AC_Flag帮助程序判断是否发送AC指令True为可以发送
uint8_t   DMA_BusyFlag检查DMA是否繁忙True为繁忙状态
uint8_t   AHT20_Data[6]该数组是储存AHT20测量得到的数据数组注意该数组需要定义为 uint8_t ,否则会出错,因为DMA是以字节的形式(是字节、字或者半字由自己定义,这里需要以字节的形式传输)转移数据
uint32_t   wenshidu[2]该数组是处理得到的温湿度数据该数组需要定义为 uint32_t,因为温湿度数据都有20位大小,定义太小了会使得数据接收不完整
static uint8_t  AC[3]AHT20测量指令数组
#define AHT20_ADDRESS (0x38 << 1)  // AHT20设备地址

uint8_t Send_AC_Flag = 1;  // 是否发送AC指令标志
uint8_t DMA_BusyFlag = 0;  // DMA是否繁忙标志

uint8_t AHT20_Data[6] = {0};  // 接收AHT20测量数据数组
uint32_t wenshidu[2] = {0x00};  // 对AHT20_Data[6]数组进行数据处理后得到的湿度、温度数据数组

static uint8_t AC[3] = {0xAC,0x33,0x00};  // AHT20开始测量指令

4、DMA+IIC数据发送、接收流程及本文所用程序中相关的函数作用提前了解

4.1 DMA+IIC数据发送、接收流程

   对于IIC+DMA来说,同时说发送和接收比较绕,难以理解,大家本来对这里都不是很了解,那么本篇文章是按照这样来的:我们先看比较简单的利用IIC+DMA完成数据发送的过程(即发送AC数组中的指令),了解了这个以后再看稍微难一点的数据接收过程,之后再将两个过程融合到一起,完成DMA既参与I2C1数据发送时的数据搬运工作,又参与数据接收时的搬运工作。
  对于数据搬运或者接收,前期的初始化步骤是必须的,也是大致相同的,因为这个是这三部学习中都需要的,我就在这一节完成讲解了,也就是下方【4.2、本文所用程序中相关的函数作用提前了解】中提及的相关函数。

在本篇的程序中,我们将以这样的流程完成对代码的理解 (不需要一下子把三个看完、可以看一个实验跟着完成一个实验)
① 利用IIC+DMA完成数据发送的过程  在该过程中,我们大概是这样的,先初始化相关的GPIO口配置、I2C配置、DMA配置、NVIC相关配置,然后将 I2C1 对 DMA 的计数器打开(I2C_DMACmd(I2C1,ENABLE);),之后我们需要先利用硬件IIC进行前期地址+读写位的发送,之后就可以利用DMA搬运数据交给DR寄存器了,并由I2C1硬件的移位寄存器进行数据的发送,等待发送完成数据,DMA会向CPU申请一个中断,我们在该中断中将I2C1的通信停止,之后,我们再利用普通的IIC读取数据。
② 利用IIC+DMA完成数据接收的过程  在该过程中,我们大概是这样的,依旧先初始化相关的GPIO口配置、I2C配置、DMA配置、NVIC相关配置,然后将 I2C1 对 DMA 的计数器打开(I2C_DMACmd(I2C1,ENABLE);),之后我们需要先利用硬件IIC进行前期地址+读写位的发送,之后就可以利用DMA从DR寄存器搬运数据并写入到AHT20_Data[6]数组中了,等待数据接收完成,DMA依旧会向CPU申请一个中断,我们在该中断中对数据进行相应的处理,处理完成的数据放入wenshidu[2]数组中。
③ 利用IIC+DMA完成数据发送+接收的过程  在该过程中,大概是这样,依旧先初始化相关的GPIO口配置、I2C配置、DMA配置、NVIC相关配置,然后将 I2C1 对 DMA 的计数器打开(I2C_DMACmd(I2C1,ENABLE);),之后我们需要先利用硬件IIC进行前期地址+读写位的发送,利用DMA搬运数据交给DR寄存器,并由I2C1硬件的移位寄存器进行数据的发送,等待发送完成数据,DMA会向CPU申请一个中断,我们在该中断中将I2C1的通信停止,之后等待响应的时间(数据手册上是75ms,我们停止80ms)。后面的就类似于实验②了,利用硬件IIC进行前期地址+读写位的发送后就可以利用DMA从DR寄存器搬运数据并写入到AHT20_Data[6]数组中了,等待数据接收完成,DMA依旧会向CPU申请一个中断,我们在该中断中对数据进行相应的处理,处理完成的数据放入wenshidu[2]数组中。

4.2、本文所用程序中相关的函数作用提前了解

AHT20_I2C_InitConfig(void)   初始化STM32F103的GPIO管脚、I2C1等相关配置
对于代码:
        I2C1->CR1 |= 0x8000; // 手动清除清BUSY
        I2C1->CR1 &= ~0x8000;
  由于很多时候,由于STM32硬件自身的原因,可能会使得I2C总线一直处于繁忙状态,这是一种错误的表示,如果初始化IIC时不将I2C的BUSY位清零,我们的程序有很大的几率将卡死。
  相关解释及处理方法见 STM32之I2C_FLAG_BUSY置位解决办法 以及 stm32f1硬件I2C busy问题 ,有兴趣的可以了解一下,我这里就简单的在初始化时手动处理了

void AHT20_I2C_InitConfig(void)
{
	delay_init();
	/******* 启用GPIO配置和时钟  *********/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

	
	/*********** GPIO口相关配置 **********/
	GPIO_InitTypeDef GPIO_InitStructer;
	GPIO_InitStructer.GPIO_Mode = GPIO_Mode_AF_OD;  // 注意这里要将GPIO口设置为复用开漏模式
	GPIO_InitStructer.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructer.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructer);
	
	/*********** I2C外设配置 **********/
	I2C_DeInit(I2C1);
	
	I2C1->CR1 |= 0x8000;  // 手动清除清BUSY
	I2C1->CR1 &= ~0x8000;
	
	I2C_InitTypeDef I2C1_InitStructer;
	I2C1_InitStructer.I2C_Ack = I2C_Ack_Enable;
	I2C1_InitStructer.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C1_InitStructer.I2C_ClockSpeed = I2C_SPEED; // 太高容易卡死
	I2C1_InitStructer.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C1_InitStructer.I2C_Mode = I2C_Mode_I2C;
	I2C1_InitStructer.I2C_OwnAddress1 = MCU_I2C1_ADDRESS;
	
	I2C_Cmd(I2C1,ENABLE);
	I2C_Init(I2C1,&I2C1_InitStructer);
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能DMA传输,因为DMA在I2C发送数据和接收数据的时候都需要使能DMA1,如果这里不使能的话后期需要多次使能,所以我就在这里使能了,各位根据自己实际情况进行使能。
}

MY_DMA_Transmit_InitConfig(void)  I2C1 发送数据时需要初始化的DMA函数,这里将会初始化DMA1的通道6

 注意这里数据的传输方向: DMA_InitStructer.DMA_DIR = DMA_DIR_PeripheralDST;   // 发送数据到外设,I2C1的DR寄存器作为数据接收方

重点:为什么我要传的数据是3个,即数组 AC 中的数据,但是下面  DMA_InitStructer.DMA_BufferSize = 4;  为什么设置为4个数据?
这里我不详细解释,给大家附上一篇文章链接,大家去自己寻找答案:【STM32】IIC使用中DMA传输时 发送数据总少一个的问题

  因为要使用到 DMA 完成从AC数组转移数据到I2C1的DR寄存器后产生的中断实现停止I2C1硬件通信功能(上方有解释,在【2、了解DMA相关中断标志位以及I2C相对应的DMA通道】这一部分中),所以这里就把NVIC的相关模块也进行相关的配置,各位根据自己实际情况进行配置。

void MY_DMA_Transmit_InitConfig(void)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能DMA传输
	DMA_DeInit(DMA1_Channel6);	
	
	DMA_InitTypeDef DMA_InitStructer;
	DMA_InitStructer.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;   // 注意这里取地址不要忘记加上 & 符号
	DMA_InitStructer.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructer.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  // DR寄存器位置固定,就那一个,所以不能使得指针传输完移动
	
	DMA_InitStructer.DMA_MemoryBaseAddr = (uint32_t)AC;
	DMA_InitStructer.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructer.DMA_MemoryInc = DMA_MemoryInc_Enable;  // AC数组这边的指针需要不断自增,这样才能将数据轮流发送出去,而不是只发数组中的第一个
	
	DMA_InitStructer.DMA_Mode = DMA_Mode_Normal;   // DMA设定为正常模式
	DMA_InitStructer.DMA_DIR = DMA_DIR_PeripheralDST;  // 发送数据到外设
	DMA_InitStructer.DMA_M2M = DMA_M2M_Disable;  // 注意不适用软件触发
	
	DMA_InitStructer.DMA_BufferSize = 4;
	DMA_InitStructer.DMA_Priority = DMA_Priority_VeryHigh;
	DMA_Init(DMA1_Channel6,&DMA_InitStructer);
	 
	NVIC_InitTypeDef NVIC_InitStructure;
	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;  //DMA中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器
	
	/* 启用DMA Channel6完全传输中断 */  
	DMA_ITConfig(DMA1_Channel6, DMA_IT_TC, ENABLE);	
}

MY_DMA_Receive_InitConfig(void)  I2C1 接收数据时需要初始化的DMA函数,这里将会初始化DMA1的通道7

 注意这里数据的传输方向: DMA_InitStructer.DMA_DIR = DMA_DIR_PeripheralSRC;   // 从外设接收数据,I2C1的DR寄存器作为数据发送方(I2C1的DR寄存器内数据由AHT20发送来的)

DMA_InitStructer.DMA_BufferSize = 6;  这里接收的时候正常,不需要像发送的时候DMA的值要比发送的数据多一个,AHT20会传回6个字节的数据,不同的外设返回的数据长度不一样,大家根据自己的外设进行配置。

   不知道大家有没有注意到优先级配置问题,为了放置卡死,我选择了将DMA+I2C1数据传输的优先级大于DMA+I2C1数据接收的优先级,这里大家可以根据自己的需求设计,不过根据官方所说,建议DMA+I2C1使用时,优先级设置高一点。

   同理,这里也对其要用的NVIC进行相关配置

void MY_DMA_Receive_InitConfig(void)
{
	DMA_DeInit(DMA1_Channel7);	
	
	DMA_InitTypeDef DMA_Receive_InitStructer;
	DMA_Receive_InitStructer.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;   // 注意这里取地址不要忘记加上 & 符号
	DMA_Receive_InitStructer.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_Receive_InitStructer.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  // DR寄存器位置固定,就那一个,所以不能使得指针传输完移动
	
	DMA_Receive_InitStructer.DMA_MemoryBaseAddr = (uint32_t)AHT20_Data;
	DMA_Receive_InitStructer.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
	DMA_Receive_InitStructer.DMA_MemoryInc = DMA_MemoryInc_Enable;  // AHT20_Data 这边需要指针不断加1,这样才能不覆盖接收到的数据
	
	DMA_Receive_InitStructer.DMA_Mode = DMA_Mode_Normal;   // DMA设定为正常模式
	DMA_Receive_InitStructer.DMA_DIR = DMA_DIR_PeripheralSRC;   // 从外设接收数据,I2C1的DR寄存器作为数据发送方(I2C1的DR寄存器内数据由AHT20发送来的)
	DMA_Receive_InitStructer.DMA_M2M = DMA_M2M_Disable;  // 注意不适用软件触发
	
	DMA_Receive_InitStructer.DMA_BufferSize = 6;
	DMA_Receive_InitStructer.DMA_Priority = DMA_Priority_High;
	DMA_Init(DMA1_Channel7,&DMA_Receive_InitStructer);
	 
	NVIC_InitTypeDef NVIC_InitStructure;
	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;  //DMA中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器
	
	/* 启用DMA Channel7完全传输中断 */  
	DMA_ITConfig(DMA1_Channel7, DMA_IT_TC, ENABLE);	
}

AHT20_DMA_Transfer(void)  这个函数在IIC发送数据的步骤中使用;在硬件IIC完成开始指令以及发送完AHT20的地址+写指令的后使用,可以完成DMA将AC数组中的数据搬运到 I2C1 的DR寄存器
AHT20_DMA_Recevie(void)  这个函数在IIC接收数据的步骤中使用;在硬件IIC完成开始指令以及发送完AHT20的地址+读指令的后使用,可以完成DMA将 I2C1 的DR寄存器中的数据搬运到 AHT20_Data[6] 数组中

//开启一次DMA传输
void AHT20_DMA_Transfer(void)
{ 
	DMA_Cmd(DMA1_Channel6, DISABLE ); 						  //关闭 DMA 所指示的通道 	
 	DMA_SetCurrDataCounter(DMA1_Channel6, 4);	//DMA通道的DMA缓存的大小
 	DMA_Cmd(DMA1_Channel6, ENABLE);  								//使能 DMA 所指示的通道 
}

//开启一次DMA传输接收数据
void AHT20_DMA_Recevie(void)
{ 
	DMA_Cmd(DMA1_Channel7, DISABLE ); 						  //关闭 DMA 所指示的通道 	
 	DMA_SetCurrDataCounter(DMA1_Channel7, 6);	      //DMA通道的DMA缓存的大小
 	DMA_Cmd(DMA1_Channel7, ENABLE);  								//使能 DMA 所指示的通道 
}

5、实验一 利用IIC+DMA完成数据发送的过程

5.1  本实验相关函数如下

AHT20_SendAC(void)  这个函数是 IIC+DMA 在 I2C 完成数据发送的时候用的。
  在该函数中,先判断系统是否允许发送AC指令,因为在初始化的时候,我们给 Send_AC_Flag 的赋值为 1,所以这里会进入函数,该标志位会在DMA完成将数据由AC搬运到DR寄存器后置 0 ,和在利用普通硬件 IIC 完成数据接收后置1(即允许下一次发送AC指令);判断完可以发送AC指令后,需要判断DMA是否为忙碌状态,只有在DMA空闲的时候才可以使用DMA完成数据搬运,在DMA开始搬运数据的时候将该标志位置1,后续在DMA完成数据搬运后的中断中我们清除该标志位,在本程序中该标志位清除也表示数据发送成功。(接下来的内容建议先看完AHT20_SendAC(void)函数后回看)
   在进入程序后,会先判断 I2C1 总线是否处于忙碌状态,如果是的话就等 I2C1 完成他手上的工作,不过我这里为了省事就直接加入了一个死循环 while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); 容易卡死在这,后期大家做相应处理,这里就不管了。
   接下来就是利用硬件 IIC 常见的开头了,先开启 I2C1,然后在发送器件地址+读写指令,待收到器件的回应后利用 AHT20_DMA_Transfer(); 函数完成开始测量指令的发送,之后的事情就交由到中断中执行了。

番外:既然DMA可以完成数据的搬运了,DR寄存器也可以完成自动的将数据移到移位寄存器中,那为什么还非要利用I2C_Send7bitAddress(I2C1, AHT20_ADDRESS, I2C_Direction_Transmitter); 这个函数完成数据的发送,为什么不将AC数组写成 uint8_t   AC[4] = {0x70,0xAC,0x33,0x00} 这样,然后将I2C_Send7bitAddress(I2C1, AHT20_ADDRESS, I2C_Direction_Transmitter); 这条语句给换掉呢?
其实我刚开始也是这样想的,并且尝试了,很不幸,失败了,后来我翻看参考手册,其中是这样说的:
在这里插入图片描述  所以我认为 IIC 向 DMA 申请的传输数据标志位只有 EV8,而不处理 EV6 (IIC发送数据状态下)
在这里插入图片描述

void AHT20_SendAC(void)
{
	if(Send_AC_Flag)
	{
		if(DMA_BusyFlag == 0)
		{
			while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
			
			I2C_GenerateSTART(I2C1, ENABLE);//开启I2C1
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));/*EV5,主模式*/

			I2C_Send7bitAddress(I2C1, AHT20_ADDRESS, I2C_Direction_Transmitter);//器件地址
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
			
			AHT20_DMA_Transfer();
			
			Send_AC_Flag = 0;    // 是否发送AC指令标志位,发送完成AC指令,清除标志位,等待接收数据程序完成数据接收后可以重新拉高该标志位
			DMA_BusyFlag = 1;   // DMA繁忙标志位置位,等待DMA完成进入中断后清除繁忙标志位
		}
		else
		{
			return;
		}
	}
	else
	{
		return;
	}
}

AHT20_Measure1(void)   这个函数就是普通的硬件 IIC 接收数据的函数了,我们在这里将接收到的数据放在AHT20_Data[6] 数组中,并将处理好的数据放在wenshidu[2]数组中,这里不细讲,大家自己理解。

void AHT20_Measure1(void)
{
	uint32_t TempData = 0;   // 这里需要提前设置两个参数接收温湿度数据,不可以直接放在wenshidu[2]数组中
	uint32_t HumiData = 0;

	if(Send_AC_Flag == 0)
	{
		delay_ms(80);  // 这里延时的80ms后期要进行修改,太占用cpu资源
		
		I2C_GenerateSTART(I2C1,ENABLE);
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);  // EV5事件  // EV5事件
		
		I2C_Send7bitAddress(I2C1,AHT20_ADDRESS,I2C_Direction_Receiver);
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);  // EV6事件
		
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[0] = I2C_ReceiveData(I2C1);
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[1] = I2C_ReceiveData(I2C1);
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[2] = I2C_ReceiveData(I2C1);
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[3] = I2C_ReceiveData(I2C1);
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[4] = I2C_ReceiveData(I2C1);
		
		I2C_AcknowledgeConfig(I2C1,DISABLE);  // 主机发送不再接收信号
		I2C_GenerateSTOP(I2C1,ENABLE);        // 截至信号
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[5] = I2C_ReceiveData(I2C1);

		I2C_AcknowledgeConfig(I2C1,ENABLE);
		
		HumiData = HumiData | AHT20_Data[1];
		HumiData = (HumiData << 8) | AHT20_Data[2];
		HumiData = (HumiData << 4) | (AHT20_Data[3] >> 4);
		
		TempData = TempData | (0x0F & AHT20_Data[3]);
		TempData = (TempData << 8) | AHT20_Data[4];
		TempData = (TempData << 8) | AHT20_Data[5];
		
		wenshidu[0] = HumiData;
		wenshidu[1] = TempData;
		Send_AC_Flag = 1;
	}
	else
		return;
}

AHT20_DMA_I2C_Init2(void)   这个函数就是将之前的几个函数融合到一起的函数了,在主程序中调用一次该函数便能获得一次测量数据。

void AHT20_DMA_I2C_Init1(void)
{
	delay_init();
	AHT20_I2C_InitConfig();
	
	MY_DMA_Transmit_InitConfig();
	I2C_DMACmd(I2C1,ENABLE);
	
	AHT20_SendAC();
	AHT20_Measure1();
}

DMA1_Channel6_IRQHandler(void)   这个函数是在 IIC 发送数据过程中,DMA完成数据搬运后产生的中断,我们需要在这个函数中关闭IIC总线并清除DMA忙碌标志位

void DMA1_Channel6_IRQHandler(void)
{
	if(DMA_GetFlagStatus(DMA1_FLAG_TC6))
	{
		DMA_ClearFlag(DMA1_FLAG_TC6);
		
		delay_us(10); // 这里如果有一个ms级别的延时,直接将程序卡死,无法测量数据
		I2C_GenerateSTOP(I2C1, ENABLE);      //关闭I2C1总线
		I2C_AcknowledgeConfig(I2C1,ENABLE);
		
		DMA_BusyFlag = 0;    //清除复位忙碌标志
	}
}

5.2  实验一整合总程序

/*
 * AHT20_DMA.h 程序
 */
#ifndef __AHT20_DMA_H
#define __AHT20_DMA_H
#include "stm32f10x.h"                  // Device header
#define MCU_I2C1_ADDRESS  0x60          //单片机作为从机时的地址,该处地址的前五位不能是11110
#define I2C_SPEED         50000

extern uint8_t Send_AC_Flag;	//是否发送AC指令标志位
extern uint8_t DMA_BusyFlag;	//DMA忙碌标志位

extern uint8_t AHT20_Data[6];
extern uint32_t wenshidu[2];

void AHT20_DMA_I2C_Init1(void);

#endif
/*
 *  AHT20_DMA.C 程序
 */
#include "AHT20_DMA.h"
#include "delay.h"

#define AHT20_ADDRESS (0x38 << 1)  // AHT20设备地址

uint8_t Send_AC_Flag = 1;  // 是否发送AC指令标志
uint8_t DMA_BusyFlag = 0;  // DMA是否繁忙标志

uint8_t AHT20_Data[6] = {0};  // 接收AHT20测量数据数组
uint32_t wenshidu[2] = {0x00};  // 对AHT20_Data[6]数组进行数据处理后得到的湿度、温度数据数组

static uint8_t AC[3] = {0xAC,0x33,0x00};  // AHT20开始测量指令


void AHT20_I2C_InitConfig(void)
{
	delay_init();
	/******* 启用GPIO配置和时钟  *********/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

	
	/*********** GPIO口相关配置 **********/
	GPIO_InitTypeDef GPIO_InitStructer;
	GPIO_InitStructer.GPIO_Mode = GPIO_Mode_AF_OD;  // 注意这里要将GPIO口设置为复用开漏模式
	GPIO_InitStructer.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructer.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructer);
	
	/*********** I2C外设配置 **********/
	I2C_DeInit(I2C1);
	
	I2C1->CR1 |= 0x8000;  // 手动清除清BUSY
	I2C1->CR1 &= ~0x8000;
	
	I2C_InitTypeDef I2C1_InitStructer;
	I2C1_InitStructer.I2C_Ack = I2C_Ack_Enable;
	I2C1_InitStructer.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C1_InitStructer.I2C_ClockSpeed = I2C_SPEED; // 太高容易卡死
	I2C1_InitStructer.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C1_InitStructer.I2C_Mode = I2C_Mode_I2C;
	I2C1_InitStructer.I2C_OwnAddress1 = MCU_I2C1_ADDRESS;
	
	I2C_Cmd(I2C1,ENABLE);
	I2C_Init(I2C1,&I2C1_InitStructer);
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能DMA传输,因为DMA在I2C发送数据和接收数据的时候都需要使能DMA1,如果这里不使能的话后期需要多次使能,所以我就在这里使能了,各位根据自己实际情况进行使能。
}

	
void MY_DMA_Transmit_InitConfig(void)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能DMA传输
	DMA_DeInit(DMA1_Channel6);	
	
	DMA_InitTypeDef DMA_InitStructer;
	DMA_InitStructer.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;   // 注意这里取地址不要忘记加上 & 符号
	DMA_InitStructer.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructer.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  // DR寄存器位置固定,就那一个,所以不能使得指针传输完移动
	
	DMA_InitStructer.DMA_MemoryBaseAddr = (uint32_t)AC;
	DMA_InitStructer.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructer.DMA_MemoryInc = DMA_MemoryInc_Enable;  // AC数组这边的指针需要不断自增,这样才能将数据轮流发送出去,而不是只发数组中的第一个
	
	DMA_InitStructer.DMA_Mode = DMA_Mode_Normal;   // DMA设定为正常模式
	DMA_InitStructer.DMA_DIR = DMA_DIR_PeripheralDST;  // 发送数据到外设
	DMA_InitStructer.DMA_M2M = DMA_M2M_Disable;  // 注意不适用软件触发
	
	DMA_InitStructer.DMA_BufferSize = 4;
	DMA_InitStructer.DMA_Priority = DMA_Priority_VeryHigh;
	DMA_Init(DMA1_Channel6,&DMA_InitStructer);
	 
	NVIC_InitTypeDef NVIC_InitStructure;
	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;  //DMA中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器
	
	/* 启用DMA Channel6完全传输中断 */  
	DMA_ITConfig(DMA1_Channel6, DMA_IT_TC, ENABLE);	
}
 

//开启一次DMA传输
void AHT20_DMA_Transfer(void)
{ 
	DMA_Cmd(DMA1_Channel6, DISABLE ); 						  //关闭 DMA 所指示的通道 	
 	DMA_SetCurrDataCounter(DMA1_Channel6, 4);	//DMA通道的DMA缓存的大小
 	DMA_Cmd(DMA1_Channel6, ENABLE);  								//使能 DMA 所指示的通道 
}


void AHT20_SendAC(void)
{
	if(Send_AC_Flag)
	{
		if(DMA_BusyFlag == 0)
		{
			while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
			
			I2C_GenerateSTART(I2C1, ENABLE);//开启I2C1
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));/*EV5,主模式*/

			I2C_Send7bitAddress(I2C1, AHT20_ADDRESS, I2C_Direction_Transmitter);//器件地址
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
			
			AHT20_DMA_Transfer();
			
			Send_AC_Flag = 0;    // 是否发送AC指令标志位,发送完成AC指令,清除标志位,等待接收数据程序完成数据接收后可以重新拉高该标志位
			DMA_BusyFlag = 1;   // DMA繁忙标志位置位,等待DMA完成进入中断后清除繁忙标志位
		}
		else
		{
			return;
		}
	}
	else
	{
		return;
	}
}


void AHT20_Measure1(void)
{
	uint32_t TempData = 0;   // 这里需要提前设置两个参数接收温湿度数据,不可以直接放在wenshidu[2]数组中
	uint32_t HumiData = 0;

	if(Send_AC_Flag == 0)
	{
		delay_ms(80);  // 这里延时的80ms后期要进行修改,太占用cpu资源
		
		I2C_GenerateSTART(I2C1,ENABLE);
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);  // EV5事件  // EV5事件
		
		I2C_Send7bitAddress(I2C1,AHT20_ADDRESS,I2C_Direction_Receiver);
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);  // EV6事件
		
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[0] = I2C_ReceiveData(I2C1);
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[1] = I2C_ReceiveData(I2C1);
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[2] = I2C_ReceiveData(I2C1);
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[3] = I2C_ReceiveData(I2C1);
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[4] = I2C_ReceiveData(I2C1);
		
		I2C_AcknowledgeConfig(I2C1,DISABLE);  // 主机发送不再接收信号
		I2C_GenerateSTOP(I2C1,ENABLE);        // 截至信号
		
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);  // EV7事件
		AHT20_Data[5] = I2C_ReceiveData(I2C1);

		I2C_AcknowledgeConfig(I2C1,ENABLE);
		
		HumiData = HumiData | AHT20_Data[1];
		HumiData = (HumiData << 8) | AHT20_Data[2];
		HumiData = (HumiData << 4) | (AHT20_Data[3] >> 4);
		
		TempData = TempData | (0x0F & AHT20_Data[3]);
		TempData = (TempData << 8) | AHT20_Data[4];
		TempData = (TempData << 8) | AHT20_Data[5];
		
		wenshidu[0] = HumiData;
		wenshidu[1] = TempData;
		Send_AC_Flag = 1;
	}
	else
		return;
}


void AHT20_DMA_I2C_Init1(void)
{
	delay_init();
	AHT20_I2C_InitConfig();
	
	MY_DMA_Transmit_InitConfig();
	I2C_DMACmd(I2C1,ENABLE);
	
	AHT20_SendAC();
	AHT20_Measure1();
}


void DMA1_Channel6_IRQHandler(void)
{
	if(DMA_GetFlagStatus(DMA1_FLAG_TC6))
	{
		DMA_ClearFlag(DMA1_FLAG_TC6);
		
		delay_us(10); // 这里如果有一个ms级别的延时,直接将程序卡死,无法测量数据
		I2C_GenerateSTOP(I2C1, ENABLE);      //关闭I2C1总线
		I2C_AcknowledgeConfig(I2C1,ENABLE);
		
		DMA_BusyFlag = 0;    //清除复位忙碌标志
	}
}

6、实验二 利用IIC+DMA完成数据接收的过程

  IIC的数据接收不同于IIC的数据发送,IIC通信在单片机为主接收的时候,单片机需要根据不同的需求发送不同的应答位,如第一个实验中的普通硬件IIC接收的时候,在接收最后一个数据之前,单片机设置了 I2C_AcknowledgeConfig(I2C1,DISABLE);提前告知IIC的硬件,接下来的一个数据是我想要接收的最后一个数据,等外设发完就不要再让他发了,也就是软件中的将SDA位拉高操作。
  因为DMA+IIC操作是一个连续完整的过程,我们中间无法参与,所以必须告知 DMA,等他把数据快搬运完成的时候,提前告诉单片机的硬件IIC一声:【老哥,再搬一个数据我就不干了,别再让外设发了。】这样,DMA相当于我们在实验一中的 I2C_AcknowledgeConfig(I2C1,DISABLE);这一步操作也帮我们干了,但是前提是你得提醒DMA记得跟硬件IIC说。
  那么如何提醒DMA记得告知外设不要在发数据了呢?这里需要一个新的函数:I2C_DMALastTransferCmd(I2C1,ENABLE);在开启DMA搬运数据之前,调用该函数就相当于提前告知了DMA要记得通知硬件IIC发送 NACK,也就是不再接收数据的应答位。
  接下来附一张单片机IIC主接收的序列图在这里插入图片描述

6.1  本实验相关函数如下

I2C1_SendAC(void)   这个函数是利用硬件IIC完成发送AC指令,其中没有DMA参与其中

void I2C1_SendAC(void)
{
	if(Send_AC_Flag)
	{
		if(DMA_BusyFlag == 0)
		{
			while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
			
			I2C_GenerateSTART(I2C1, ENABLE);  //开启I2C1
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /*EV5,主模式*/

			I2C_Send7bitAddress(I2C1, AHT20_ADDRESS, I2C_Direction_Transmitter);//器件地址
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));  // EV6事件

			I2C_SendData(I2C1,0xAC);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);; // EV8事件
			I2C_SendData(I2C1,0x33);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);  // EV8事件
			I2C_SendData(I2C1,0x00);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);  // EV8_2事件
			I2C_GenerateSTOP(I2C1,ENABLE);  //关闭I2C1
			
			Send_AC_Flag = 0;    // 是否发送AC指令标志位,发送完成AC指令,清除标志位,等待接收数据程序完成数据接收后可以重新拉高该标志位
		}
		else
		{
			return;
		}
	}
	else
	{
		return;
	}
}

I2C1_SendAC(void)   这个函数是和实验一中的类似,但是多了一条 I2C_DMALastTransferCmd(I2C1,ENABLE); 即上方讲的提前告知DMA即将接收完数据后告知硬件 IIC 发送 NACK 应答位。

void AHT20_Measure2(void)
{
	if(!Send_AC_Flag)
	{
		if(DMA_BusyFlag == 0)
		{
			while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
			
			I2C_GenerateSTART(I2C1,ENABLE);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);  // EV5事件
			
			I2C_Send7bitAddress(I2C1,AHT20_ADDRESS,I2C_Direction_Receiver);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);  // EV6事件
			
			I2C_DMALastTransferCmd(I2C1,ENABLE);  //这里不可少
			
			AHT20_DMA_Recevie();
			DMA_BusyFlag = 1;  // DMA繁忙标志位置位,等待DMA完成进入中断后清除繁忙标志位
		}
	}
}

AHT20_DMA_I2C_Init2(void)   这个函数就是将之前的几个函数融合到一起的函数了,在主程序中调用一次该函数便能获得一次测量数据。

void AHT20_DMA_I2C_Init2(void)
{
	delay_init();
	AHT20_I2C_InitConfig();
	
	MY_DMA_Receive_InitConfig();
	I2C_DMACmd(I2C1,ENABLE);
	
	I2C1_SendAC();
	AHT20_Measure2();
}

DMA1_Channel7_IRQHandler(void)   这个函数是和实验一中的DMA中断一样,只不过我们需要在这个中断中执行的代码多一点

void DMA1_Channel7_IRQHandler(void)
{
	if(DMA_GetFlagStatus(DMA1_FLAG_TC7))
	{
		DMA_ClearFlag(DMA1_FLAG_TC7);
	
		I2C_GenerateSTOP(I2C1, ENABLE);      //关闭I2C1总线
		delay_us(10);
		I2C_AcknowledgeConfig(I2C1,ENABLE);
		
		uint32_t TempData = 0;
		uint32_t HumiData = 0;
		
		HumiData = HumiData | AHT20_Data[1];
		HumiData = (HumiData << 8) | AHT20_Data[2];
		HumiData = (HumiData << 4) | (AHT20_Data[3] >> 4);
	
		TempData = TempData | (0x0F & AHT20_Data[3]);
		TempData = (TempData << 8) | AHT20_Data[4];
		TempData = (TempData << 8) | AHT20_Data[5];
		
		wenshidu[0] = HumiData;
		wenshidu[1] = TempData;
		
		Send_AC_Flag = 1;    //数据接收完成,可以开始下一轮测量
		DMA_BusyFlag = 0;    //清除复位忙碌标志
	}
}

6.2  实验二整合总程序

/*
 * AHT20_DMA.h 程序
 */
#ifndef __AHT20_DMA_H
#define __AHT20_DMA_H
#include "stm32f10x.h"                  // Device header
#define MCU_I2C1_ADDRESS  0x60          //单片机作为从机时的地址,该处地址的前五位不能是11110
#define I2C_SPEED         50000

extern uint8_t Send_AC_Flag;	//是否发送AC指令标志位
extern uint8_t DMA_BusyFlag;	//DMA忙碌标志位

extern uint8_t AHT20_Data[6];
extern uint32_t wenshidu[2];

void AHT20_DMA_I2C_Init2(void);

#endif
/*
 * AHT20_DMA.c 程序
 */
#include "AHT20_DMA.h"
#include "delay.h"

#define AHT20_ADDRESS (0x38 << 1)  // AHT20设备地址

uint8_t Send_AC_Flag = 1;  // 是否发送AC指令标志
uint8_t DMA_BusyFlag = 0;  // DMA是否繁忙标志

uint8_t AHT20_Data[6] = {0};  // 接收AHT20测量数据数组
uint32_t wenshidu[2] = {0x00};  // 对AHT20_Data[6]数组进行数据处理后得到的湿度、温度数据数组

static uint8_t AC[3] = {0xAC,0x33,0x00};  // AHT20开始测量指令


void AHT20_I2C_InitConfig(void)
{
	delay_init();
	/******* 启用GPIO配置和时钟  *********/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

	
	/*********** GPIO口相关配置 **********/
	GPIO_InitTypeDef GPIO_InitStructer;
	GPIO_InitStructer.GPIO_Mode = GPIO_Mode_AF_OD;  // 注意这里要将GPIO口设置为复用开漏模式
	GPIO_InitStructer.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructer.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructer);
	
	/*********** I2C外设配置 **********/
	I2C_DeInit(I2C1);
	
	I2C1->CR1 |= 0x8000;  // 手动清除清BUSY
	I2C1->CR1 &= ~0x8000;
	
	I2C_InitTypeDef I2C1_InitStructer;
	I2C1_InitStructer.I2C_Ack = I2C_Ack_Enable;
	I2C1_InitStructer.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C1_InitStructer.I2C_ClockSpeed = I2C_SPEED; // 太高容易卡死
	I2C1_InitStructer.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C1_InitStructer.I2C_Mode = I2C_Mode_I2C;
	I2C1_InitStructer.I2C_OwnAddress1 = MCU_I2C1_ADDRESS;
	
	I2C_Cmd(I2C1,ENABLE);
	I2C_Init(I2C1,&I2C1_InitStructer);
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能DMA传输,因为DMA在I2C发送数据和接收数据的时候都需要使能DMA1,如果这里不使能的话后期需要多次使能,所以我就在这里使能了,各位根据自己实际情况进行使能。
}

	
void MY_DMA_Receive_InitConfig(void)
{
	DMA_DeInit(DMA1_Channel7);	
	
	DMA_InitTypeDef DMA_Receive_InitStructer;
	DMA_Receive_InitStructer.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;   // 注意这里取地址不要忘记加上 & 符号
	DMA_Receive_InitStructer.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_Receive_InitStructer.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  // DR寄存器位置固定,就那一个,所以不能使得指针传输完移动
	
	DMA_Receive_InitStructer.DMA_MemoryBaseAddr = (uint32_t)AHT20_Data;
	DMA_Receive_InitStructer.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
	DMA_Receive_InitStructer.DMA_MemoryInc = DMA_MemoryInc_Enable;  // AHT20_Data 这边需要指针不断加1,这样才能不覆盖接收到的数据
	
	DMA_Receive_InitStructer.DMA_Mode = DMA_Mode_Normal;   // DMA设定为正常模式
	DMA_Receive_InitStructer.DMA_DIR = DMA_DIR_PeripheralSRC;   // 从外设接收数据,I2C1的DR寄存器作为数据发送方(I2C1的DR寄存器内数据由AHT20发送来的)
	DMA_Receive_InitStructer.DMA_M2M = DMA_M2M_Disable;  // 注意不适用软件触发
	
	DMA_Receive_InitStructer.DMA_BufferSize = 6;
	DMA_Receive_InitStructer.DMA_Priority = DMA_Priority_High;
	DMA_Init(DMA1_Channel7,&DMA_Receive_InitStructer);
	 
	NVIC_InitTypeDef NVIC_InitStructure;
	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;  //DMA中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器
	
	/* 启用DMA Channel7完全传输中断 */  
	DMA_ITConfig(DMA1_Channel7, DMA_IT_TC, ENABLE);	
}

//开启一次DMA传输接收数据
void AHT20_DMA_Recevie(void)
{ 
	DMA_Cmd(DMA1_Channel7, DISABLE ); 						  //关闭 DMA 所指示的通道 	
 	DMA_SetCurrDataCounter(DMA1_Channel7, 6);	      //DMA通道的DMA缓存的大小
 	DMA_Cmd(DMA1_Channel7, ENABLE);  								//使能 DMA 所指示的通道 
}



void I2C1_SendAC(void)
{
	if(Send_AC_Flag)
	{
		if(DMA_BusyFlag == 0)
		{
			while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
			
			I2C_GenerateSTART(I2C1, ENABLE);  //开启I2C1
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /*EV5,主模式*/

			I2C_Send7bitAddress(I2C1, AHT20_ADDRESS, I2C_Direction_Transmitter);//器件地址
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));  // EV6事件

			I2C_SendData(I2C1,0xAC);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);; // EV8事件
			I2C_SendData(I2C1,0x33);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);  // EV8事件
			I2C_SendData(I2C1,0x00);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);  // EV8_2事件
			I2C_GenerateSTOP(I2C1,ENABLE);  //关闭I2C1
			
			Send_AC_Flag = 0;    // 是否发送AC指令标志位,发送完成AC指令,清除标志位,等待接收数据程序完成数据接收后可以重新拉高该标志位
		}
		else
		{
			return;
		}
	}
	else
	{
		return;
	}
}



void AHT20_Measure2(void)
{
	if(!Send_AC_Flag)
	{
		if(DMA_BusyFlag == 0)
		{
			while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
			
			I2C_GenerateSTART(I2C1,ENABLE);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);  // EV5事件
			
			I2C_Send7bitAddress(I2C1,AHT20_ADDRESS,I2C_Direction_Receiver);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);  // EV6事件
			
			I2C_DMALastTransferCmd(I2C1,ENABLE);  //这里不可少
			
			AHT20_DMA_Recevie();
			DMA_BusyFlag = 1;  // DMA繁忙标志位置位,等待DMA完成进入中断后清除繁忙标志位
		}
	}
}


void AHT20_DMA_I2C_Init2(void)
{
	delay_init();
	AHT20_I2C_InitConfig();
	
	MY_DMA_Receive_InitConfig();
	I2C_DMACmd(I2C1,ENABLE);
	
	I2C1_SendAC();
	AHT20_Measure2();
}


void DMA1_Channel7_IRQHandler(void)
{
	if(DMA_GetFlagStatus(DMA1_FLAG_TC7))
	{
		DMA_ClearFlag(DMA1_FLAG_TC7);
	
		I2C_GenerateSTOP(I2C1, ENABLE);      //关闭I2C1总线
		delay_us(10);
		I2C_AcknowledgeConfig(I2C1,ENABLE);
		
		uint32_t TempData = 0;
		uint32_t HumiData = 0;
		
		HumiData = HumiData | AHT20_Data[1];
		HumiData = (HumiData << 8) | AHT20_Data[2];
		HumiData = (HumiData << 4) | (AHT20_Data[3] >> 4);
	
		TempData = TempData | (0x0F & AHT20_Data[3]);
		TempData = (TempData << 8) | AHT20_Data[4];
		TempData = (TempData << 8) | AHT20_Data[5];
		
		wenshidu[0] = HumiData;
		wenshidu[1] = TempData;
		
		Send_AC_Flag = 1;    //数据接收完成,可以开始下一轮测量
		DMA_BusyFlag = 0;    //清除复位忙碌标志
	}
}

7、实验三 利用IIC+DMA完成数据发送以及数据的接收的过程

   学会了实验一和实验二,其实实验三是比较简单的,就是实验一和实验二的缝合作品,我这里直接提供完整的程序了。(注意这里我将上方两个实验的部分函数名称改了)

/*
 * AHT20_DMA.h 程序
 */
#ifndef __AHT20_DMA_H
#define __AHT20_DMA_H
#include "stm32f10x.h"                  // Device header
#define MCU_I2C1_ADDRESS  0x60          //单片机作为从机时的地址,该处地址的前五位不能是11110
#define I2C_SPEED         50000

extern uint8_t Send_AC_Flag;	//是否发送AC指令标志位
extern uint8_t DMA_BusyFlag;	//DMA忙碌标志位

extern uint8_t AHT20_Data[6];
extern uint32_t wenshidu[2];

void AHT20_DMA_I2C_Init(void);

#endif
/*
 * AHT20_DMA.h 程序
 */
 #include "AHT20_DMA.h"
#include "delay.h"

#define AHT20_ADDRESS (0x38 << 1)

uint8_t Send_AC_Flag = 1;
uint8_t DMA_BusyFlag = 0;

uint8_t AHT20_Data[6] = {0};
uint32_t wenshidu[2] = {0x00};

static uint8_t AC[3] = {0xAC,0x33,0x00};  // AHT20开始测量指令

void AHT20_I2C_InitConfig(void)
{
	delay_init();
	/******* 启用GPIO配置和时钟  *********/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

	
	/*********** GPIO口相关配置 **********/
	GPIO_InitTypeDef GPIO_InitStructer;
	GPIO_InitStructer.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStructer.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructer.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructer);
	
	/*********** I2C外设配置 **********/
	I2C_DeInit(I2C1);
	
	I2C1->CR1 |= 0x8000;  // 手动清除清BUSY
	I2C1->CR1 &= ~0x8000;
	
	I2C_InitTypeDef I2C1_InitStructer;
	I2C1_InitStructer.I2C_Ack = I2C_Ack_Enable;
	I2C1_InitStructer.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C1_InitStructer.I2C_ClockSpeed = I2C_SPEED; // 太高容易卡死
	I2C1_InitStructer.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C1_InitStructer.I2C_Mode = I2C_Mode_I2C;
	I2C1_InitStructer.I2C_OwnAddress1 = MCU_I2C1_ADDRESS;
	
	I2C_Cmd(I2C1,ENABLE);
	I2C_Init(I2C1,&I2C1_InitStructer);
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能DMA传输
}
	
void MY_DMA_Transmit_InitConfig(void)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能DMA传输
	DMA_DeInit(DMA1_Channel6);	
	
	DMA_InitTypeDef DMA_InitStructer;
	DMA_InitStructer.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;   // 注意这里取地址不要忘记加上 & 符号
	DMA_InitStructer.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructer.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	
	DMA_InitStructer.DMA_MemoryBaseAddr = (uint32_t)AC;
	DMA_InitStructer.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructer.DMA_MemoryInc = DMA_MemoryInc_Enable;
	
	DMA_InitStructer.DMA_Mode = DMA_Mode_Normal;   // DMA设定为正常模式
	DMA_InitStructer.DMA_DIR = DMA_DIR_PeripheralDST;  // 发送数据到外设
	DMA_InitStructer.DMA_M2M = DMA_M2M_Disable;  // 注意不适用软件触发
	
	DMA_InitStructer.DMA_BufferSize = 4;
	DMA_InitStructer.DMA_Priority = DMA_Priority_VeryHigh;
	DMA_Init(DMA1_Channel6,&DMA_InitStructer);
	 
	NVIC_InitTypeDef NVIC_InitStructure;
	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;  //DMA中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器
	
	/* 启用DMA Channelx完全传输中断 */  
	DMA_ITConfig(DMA1_Channel6, DMA_IT_TC, ENABLE);	
}

void MY_DMA_Receive_InitConfig(void)
{
	DMA_DeInit(DMA1_Channel7);	
	
	DMA_InitTypeDef DMA_Receive_InitStructer;
	DMA_Receive_InitStructer.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;   // 注意这里取地址不要忘记加上 & 符号
	DMA_Receive_InitStructer.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_Receive_InitStructer.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	
	DMA_Receive_InitStructer.DMA_MemoryBaseAddr = (uint32_t)AHT20_Data;
	DMA_Receive_InitStructer.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
	DMA_Receive_InitStructer.DMA_MemoryInc = DMA_MemoryInc_Enable;
	
	DMA_Receive_InitStructer.DMA_Mode = DMA_Mode_Normal;   // DMA设定为正常模式
	DMA_Receive_InitStructer.DMA_DIR = DMA_DIR_PeripheralSRC;  // 从外设接收数据
	DMA_Receive_InitStructer.DMA_M2M = DMA_M2M_Disable;  // 注意不适用软件触发
	
	DMA_Receive_InitStructer.DMA_BufferSize = 6;
	DMA_Receive_InitStructer.DMA_Priority = DMA_Priority_High;
	DMA_Init(DMA1_Channel7,&DMA_Receive_InitStructer);
	 
	NVIC_InitTypeDef NVIC_InitStructure;
	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;  //DMA中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器
	
	/* 启用DMA Channelx完全传输中断 */  
	DMA_ITConfig(DMA1_Channel7, DMA_IT_TC, ENABLE);	
}


//开启一次DMA传输
void AHT20_DMA_Transfer(void)
{ 
	DMA_Cmd(DMA1_Channel6, DISABLE ); 						  //关闭 DMA 所指示的通道 	
 	DMA_SetCurrDataCounter(DMA1_Channel6, 4);	//DMA通道的DMA缓存的大小
 	DMA_Cmd(DMA1_Channel6, ENABLE);  								//使能 DMA 所指示的通道 
}

//开启一次DMA传输接收数据
void AHT20_DMA_Recevie(void)
{ 
	DMA_Cmd(DMA1_Channel7, DISABLE ); 						  //关闭 DMA 所指示的通道 	
 	DMA_SetCurrDataCounter(DMA1_Channel7, 6);	      //DMA通道的DMA缓存的大小
 	DMA_Cmd(DMA1_Channel7, ENABLE);  								//使能 DMA 所指示的通道 
}

void AHT20_DMA_SendAC(void)
{
	if(Send_AC_Flag)
	{
		if(DMA_BusyFlag == 0)
		{
			while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
			
			I2C_GenerateSTART(I2C1, ENABLE);//开启I2C1
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));/*EV5,主模式*/

			I2C_Send7bitAddress(I2C1, AHT20_ADDRESS, I2C_Direction_Transmitter);//器件地址
			while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
			
			AHT20_DMA_Transfer();
			
			Send_AC_Flag = 0;    // 是否发送AC指令标志位,发送完成AC指令,清除标志位,等待接收数据程序完成数据接收后可以重新拉高该标志位
			DMA_BusyFlag = 1;   // DMA繁忙标志位置位,等待DMA完成进入中断后清除繁忙标志位
		}
		else
		{
			return;
		}
	}
	else
	{
		return;
	}
}

void AHT20_DMA_Measure(void)
{
	if(!Send_AC_Flag)
	{
		if(DMA_BusyFlag == 0)
		{
			while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
			
			I2C_GenerateSTART(I2C1,ENABLE);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);  // EV5事件
			
			I2C_Send7bitAddress(I2C1,AHT20_ADDRESS,I2C_Direction_Receiver);
			while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);   // EV6事件
					
			
			I2C_DMALastTransferCmd(I2C1,ENABLE);
			
			AHT20_DMA_Recevie();
			DMA_BusyFlag = 1;  // DMA繁忙标志位置位,等待DMA完成进入中断后清除繁忙标志位
		}
	}
}


void AHT20_DMA_I2C_Init(void)
{
	delay_init();
	AHT20_I2C_InitConfig();
	
	MY_DMA_Receive_InitConfig();
	MY_DMA_Transmit_InitConfig();
	I2C_DMACmd(I2C1,ENABLE);
		
	AHT20_DMA_SendAC();

	delay_ms(100);
	
	I2C_DMACmd(I2C1,ENABLE);
	AHT20_DMA_Measure();
}



void DMA1_Channel6_IRQHandler(void)
{
	if(DMA_GetFlagStatus(DMA1_FLAG_TC6))
	{
		DMA_ClearFlag(DMA1_FLAG_TC6);
		
		delay_us(10); // 这里如果有一个ms级别的延时,直接将程序卡死,无法测量数据
		I2C_GenerateSTOP(I2C1, ENABLE);      //关闭I2C1总线
		
		I2C_AcknowledgeConfig(I2C1,ENABLE);
		
		I2C_DMACmd(I2C1,DISABLE);
		
		DMA_BusyFlag = 0;    //清除复位忙碌标志
	}
}

void DMA1_Channel7_IRQHandler(void)
{
	if(DMA_GetFlagStatus(DMA1_FLAG_TC7))
	{
		DMA_ClearFlag(DMA1_FLAG_TC7);
	
		I2C_GenerateSTOP(I2C1, ENABLE);      //关闭I2C1总线
		delay_us(10);
		I2C_AcknowledgeConfig(I2C1,ENABLE);
		I2C_DMACmd(I2C1,DISABLE);
		
		uint32_t TempData = 0;
		uint32_t HumiData = 0;
		
		HumiData = HumiData | AHT20_Data[1];
		HumiData = (HumiData << 8) | AHT20_Data[2];
		HumiData = (HumiData << 4) | (AHT20_Data[3] >> 4);
	
		TempData = TempData | (0x0F & AHT20_Data[3]);
		TempData = (TempData << 8) | AHT20_Data[4];
		TempData = (TempData << 8) | AHT20_Data[5];
		
		wenshidu[0] = HumiData;
		wenshidu[1] = TempData;
		
		Send_AC_Flag = 1;    //数据接收完成,可以开始下一轮测量
		DMA_BusyFlag = 0;    //清除复位忙碌标志
	}
}

8、结束语

   文章到这里就结束了,肯定有很多的不足之处,希望和大家一起改进,如果帮到大家了就点个赞吧!

gd32是一款中国国产的微控制器系列,其中包括了i2cdma两个功能模块。 i2c,全称为Inter-Integrated Circuit,是一种串行通信协议,用于在微控制器和外部设备之间进行通信。gd32系列提供了i2c模块,使得它可以与其他i2c兼容的设备进行交互。通过gd32的i2c模块,我们可以实现多个设备之间的数据传输和通信。gd32 i2c模块支持主从模式,可以作为主设备来控制其他从设备,也可以作为从设备被其他主设备所控制。使用i2c,我们可以连接各种外设,如传感器、存储器等,从而实现数据的读写和交换。gd32的i2c模块提供了一些寄存器和配置选项,使得用户可以根据自己的需求进行设置和操作。 dma,全称为Direct Memory Access,是一种用于数据传输的技术。通常情况下,当微控制器需要进行数据传输时,需要通过CPU进行中断处理,这样会占用CPU的很多时间和资源。而dma模块的作用就是用来解放CPU,实现直接的数据传输。gd32的dma模块通过配置源地址、目的地址和传输数据的长度等参数,可以实现大容量数据的快速传输。当dma传输完成后,会触发一个中断信号,从而通知CPU。 综上所述,gd32的i2cdma功能模块能够提供可靠、高效的数据通信和传输方式。用户可以根据需要来配置和使用这两个功能模块,从而实现各种各样的应用,如外设控制、数据传输、通信等。gd32的i2cdma模块的灵活性和易用性,使得它成为了很多嵌入式系统的理想选择。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值