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

news2024/11/17 4:53:29

目录

  • 前言
  • 1、AHT20模块的简单了解(为了更好的理解程序语句)
  • 2、了解DMA相关中断标志位以及I2C相对应的DMA通道
  • 3、本篇文章提及程序中相关的变量定义提前了解
  • 4、DMA+IIC数据发送、接收流程及本文所用程序中相关的函数作用提前了解
    • 4.1 DMA+IIC数据发送、接收流程
    • 4.2、本文所用程序中相关的函数作用提前了解
  • 5、实验一 利用IIC+DMA完成数据发送的过程
    • 5.1  本实验相关函数如下
    • 5.2  实验一整合总程序
  • 6、实验二 利用IIC+DMA完成数据接收的过程
    • 6.1  本实验相关函数如下
    • 6.2  实验二整合总程序
  • 7、实验三 利用IIC+DMA完成数据发送以及数据的接收的过程
  • 8、结束语

前言

  这几天在学习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、结束语

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

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

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

相关文章

qt判断当前日期是不是当月的最后一天

思路就是&#xff1a;将当前日期1&#xff0c;如果月份发生变化&#xff0c;则证明是最后一天 找一个dateTimeEdit&#xff0c;然后选择个日期&#xff0c;如下&#xff1a; 加入测试代码&#xff1a; QDateTime time ui->dateTimeEdit->dateTime();QDateTime nextWeek…

Debezium日常分享系列之:Debezium 2.3.0.Final发布

Debezium日常分享系列之&#xff1a;Debezium 2.3.0.Final发布 一、重大改变二、PostgreSQL / MySQL 安全连接更改三、JDBC 存储编码更改四、新功能和改进五、Kubernetes 的 Debezium Server Operator六、新的通知子系统七、新的可扩展信号子系统八、JMX 信号和通知集成九、新的…

如何查看dll文件内导出函数名称

一 使用VS自带工具 进入VS开发环境&#xff0c;然后Tools -> Visual studio 2017 Command Prompt&#xff0c;打开兼容工具命令提示符&#xff0c; 如果工具 目录下没有命令行提示&#xff0c;可以从开始菜单找到VS的命令行提示符。 cd到dll所在目录&#xff0c;输入命令…

联邦学习综述

《Advances and Open Problems in Federated Learning》 选题&#xff1a;Published 10 December 2019-Computer Science-Found. Trends Mach. Learn. 联邦学习定义 联邦学习是一种机器学习设置&#xff0c;其中多个客户端在中央服务器或服务提供商的协调下协作解决机器学习…

VR会议:远程带看功能,专为沉浸式云洽谈而生

随着科技的不断发展&#xff0c;VR技术已经成为当今市场上较为热门的新型技术之一了&#xff0c;而VR会议远程带看功能&#xff0c;更是为用户提供更加真实、自然的沉浸式体验。 随着5G技术的发展&#xff0c;传统的图文、视频这种展示形式已经无法满足消费者对信息真实性的需求…

存档&改造【04】二维码操作入口设置细节自动刷新设置后的交互式网格内容的隐藏

因为数据库中没有数据无法查看设置效果&#xff0c;于是自己创建了个测试数据表&#xff0c;用来给demo测试 -- 二维码操作入口设置 create table JM_QR_CODE(QR_CODE_ID NUMBER generated as identity primary key,SYSTEM_ID NUMBER(20) not null,IS_ENAB…

中国数字化转型浪潮下,移动钱包成为全球App标配

中国&#xff0c;一个数字化转型极为快速的国家&#xff0c;在电子钱包和移动支付方面处于世界领先地位。 在中国&#xff0c;几乎一半的店内购物都是通过手机进行的&#xff0c;这远高于其他发达市场的水平&#xff08;德国25%&#xff0c;美国24%&#xff09;。移动支付&…

AI情感陪伴 | 数字伙伴为你而在,解锁情感健康的新时代

当我们审视当今社会的变革和进步时&#xff0c;不难发现新一代年轻人正面临着前所未有的多重挑战。其中就涵盖了社交关系的复杂性、学业上的巨大压力、激烈的就业竞争&#xff0c;以及紧迫的环境问题等等。这些挑战不仅深刻地影响着他们的生活品质&#xff0c;还可能引发严重的…

AUTOSAR CAN通信全过程

1、CAN通信概略图和基本概念介绍 应用层APPL、RTE&#xff08;实时运行环境 Runtime Environment&#xff09; 交互层&#xff1a; IPDU multiplexer&#xff1a;协议数据单元复用模块 COM&#xff1a;COMMUNICATION 通信模块 DCM: 诊断通信管理模块&#xff08;Diagnostic C…

多项目并行管理:优化协调策略提高效率

多项目同时进行已然是大部分项目管理者面临的现状了。相比于单项目管理&#xff0c;多项目管理可能会出现项目资源分配不均&#xff0c;项目进度监控难以及沟通协作复杂等问题。 可以通过一款灵活高效得项目管理工具&#xff0c;来帮助您进行多项目管理&#xff0c;比如 Zoho …

SSL证书如何提升网站的安全性和信任度

在今天的数字时代&#xff0c;随着网络攻击的日益增多&#xff0c;确保网站和用户数据的安全变得至关重要。SSL&#xff08;Secure Sockets Layer&#xff09;证书是一个有效的安全解决方案&#xff0c;可以为您的网站提供加密通信通道&#xff0c;并提升其安全性和信任度。下面…

南昌新颜|AIGC艺术之光,历史的魅力与未来的探索

在这个本来稀松平常的周一&#xff0c;我们坐在电脑前&#xff0c;回顾着刚刚过去的两天&#xff0c;仿佛经历了“反方向的钟”。前两天的上班日子&#xff0c;究竟是周几呢&#xff1f;答案不再是简单的星期几&#xff0c;而是“周负二”和“周负一”。因为&#xff0c;我们把…

三、WebGPU Uniforms

三、WebGPU Uniforms Uniform有点像着色器的全局变量。你可以在执行着色器之前设置它们的值&#xff0c;着色器的每次迭代都会有这些值。你可以在下一次请求GPU执行着色器时将它们设置为其他值。我们将再次从第一篇文章中的三角形示例开始&#xff0c;并对其进行修改以使用一些…

Go 字符串操作实战

1. 引言 在现代编程中&#xff0c;字符串处理是不可或缺的一部分。无论是简单的用户界面&#xff0c;还是复杂的数据处理&#xff0c;字符串都扮演着关键的角色。Go语言&#xff0c;作为一个现代的、性能优越的编程语言&#xff0c;为字符串处理提供了一系列强大的工具和功能。…

通过后台系统添加一段div,在div中写一个<style></style>标签来修改div外面的元素的深层元素的样式

先看图 btn元素就是通过后台系统加上的元素,现在需要通过在btn里面写一个style标签来修改grid-nine里面的head元素的高度.开始想通过style来修改,但是不知道怎么去获取这个div外面的元素,想通过js方法去修改,写了script标签加了js代码,但不生效,后面问了才知道,这个项目是vue打…

深度学习笔记之优化算法(六)RMSprop算法的简单认识

深度学习笔记之优化算法——RMSProp算法的简单认识 引言回顾&#xff1a;AdaGrad算法AdaGrad算法与动量法的优化方式区别AdaGrad算法的缺陷 RMProp算法关于AdaGrad问题的优化方式RMSProp的算法过程描述 RMSProp示例代码 引言 上一节对 AdaGrad \text{AdaGrad} AdaGrad算法进行…

重载和重写的区别

方法重载&#xff1a; &#xff08;1&#xff09;在同一个类中 &#xff08;2&#xff09;方法名必须相同 &#xff08;3&#xff09;形参列表必须不同&#xff08;形参类型或个数或顺序&#xff0c;至少有一样不同&#xff0c;参数名无要求&#xff09; &#xff08;4&…

基于SpringBoot的校园社团信息管理系统

目录 前言 一、技术栈 二、系统功能介绍 学生管理 社长管理 社团信息管理 社团新闻管理 社团添加 社团活动 加入社团 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术…

Linux---进程(1)

操作系统 传统的计算机系统资源分为硬件资源和软件资源。硬件资源包括中央处理器&#xff0c;存储器&#xff0c;输入设备&#xff0c;输出设备等物理设备&#xff1b;软件资源是以文件形式保存在存储器上的成熟和数据等信息。 操作系统就是计算机系统资源的管理者。 如果你的计…

GEO生信数据挖掘(七)差异基因分析

上节&#xff0c;我们使用结核病基因数据&#xff0c;做了一个数据预处理的实操案例。例子中结核类型&#xff0c;包括结核&#xff0c;潜隐进展&#xff0c;对照和潜隐&#xff0c;四个类别。本节延续上个数据&#xff0c;进行了差异分析。 差异分析 计算差异指标step12 加载…