目录
前言
这几天在学习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、结束语
文章到这里就结束了,肯定有很多的不足之处,希望和大家一起改进,如果帮到大家了就点个赞吧!