• 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设备地址
    
    • 1

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

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

    在这里插入图片描述
    在这里插入图片描述
      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相关数据接收搬运后发生的中断
    
    • 1
    • 2

    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开始测量指令
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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,如果这里不使能的话后期需要多次使能,所以我就在这里使能了,各位根据自己实际情况进行使能。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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);	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    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);	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    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 所指示的通道 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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;    //清除复位忙碌标志
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    /*
     *  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;    //清除复位忙碌标志
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211

    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;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    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完成进入中断后清除繁忙标志位
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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;    //清除复位忙碌标志
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    /*
     * 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;    //清除复位忙碌标志
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    /*
     * 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;    //清除复位忙碌标志
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255

    8、结束语

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

  • 相关阅读:
    基于QT实现的SSL协议的安全报文发送接收设计
    5张图告诉你:同样是职场人,差距怎么这么大?
    WebSocket实战之二协议分析
    09-基础篇-一步一步写MakeFile
    优惠券秒杀的优化
    37_ue4进阶末日生存游戏开发[显示物品列表]
    超声功率放大器在MEMS超声测试中的应用
    谷粒商城 高级篇 (十九) --------- 消息队列
    完美PDF打印:PDFPrinting.NET Crack
    【算法集训专题攻克篇】第二十篇之二叉搜索树
  • 原文地址:https://blog.csdn.net/weixin_45911959/article/details/133717957