STM32(DMA、DHT11)

1、DMA(数据的搬运工)

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。

1.1 DMA作用

DMA的传输方式不需要CPU参与,可以直接控制传输

DMA给外部设备和内存开辟一条直接传输数据的通道

目的:给CPU节省资源,使CPU的工作效率提高。

1.2 DMA主要特性

中文手册(140页)、对此页进行讲解

1)同一个DMA模块可以有多个优先级请求:很高、高、中等、低

2)每个通道有3个事件标志: DMA半传输、DMA传输完成、 DMA传输出错

3)数据源 目标源 数据传输宽度对齐

4)传输数据 字节(8位) 半字(16位) 全字(32位 )

5)存储器<->存储器、外设<->存储器、外设<->外设

6)闪存(flash) 、SRAM、 APB 、AHB 、外设均可以作为源或者目标

7)搬移数据的最大长度为65535字节

1.3 DMA寄存器

DMA_CPARx :设置外设地址的寄存器

DMA_CMARx :设置存储器地址的寄存器

DMA_CCRx :设置数据传输方向

DMA_CNDTRx:设置传输的数据量

1.4 DMA的增量或者循环模式

1)增量: 外设搬移到存储器的时候 ,不希望覆盖上一个数据,会将内存设置为增量模式

2)循环: DMA不停循环的搬移数据,一组的数据传输完成时,计数寄存器将会自动地被恢复成配置该通道时设置的初值。

1.5 DMA中断

每个 DMA 通道都可以在 DMA 传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断

实验1: DMA-ADC串口发送

实验要求:使用DMA通过串口打印ADC采集的光照值

从外设-----》内存

半字(16位)也就是每次搬16位数据,而我们的ADC每次刚好是将数据存在16位的寄存器里面,因此不需要更改。

什么时候搬移?

再ADC转化完成之后再搬移(用DMA搬移)

怎么用?

HAL_StatusTypeDef HAL_ADC_Start_DMA (ADC_HandleTypeDef * hadc, uint32_t * pData, uint32_t Length)

功能:启动ADC开始转换,并通过DMA搬移转换结果。

参数:ADC_HandleTypeDef * hadc 句柄

uint32_t * pData 数据存放地址

uint32_t Length 数据长度

此时CPU并未参与ADC转换完成的数据读取工作,节省了CPU的资源。

实验2:DMA-ADC串口发送--按键中断

2.1 实验要求

在按键按下之后,使用DMA通过串口发送ADC采集的光照值(时钟设置成64MHz)

2.2 Cube MX 环境配置

在上一个项目基础上配置

2.3 代码编写

复制代码
void` `HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)`
`{`
	`if(GPIO_Pin == GPIO_PIN_8)`
	`{`
		`HAL_ADC_Start_DMA(&hadc1,(uint32_t` `*)&buf,1);`
	`}`
`}`
`int` `fputc(int ch ,FILE* p)`
`{`
	`while(!(USART1->ISR&(1<<7)));`
`	USART1->TDR=ch;`
	`return ch;`
`}`
`void` `HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)`
`{`
	`HAL_ADC_Stop_DMA(&hadc1);`
	`printf("%d\n",buf);`
`}`
`

2.4 再加上按键的ADC值

复制代码
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)`
`{`
`	if(GPIO_Pin == GPIO_PIN_8)`
`	{`
`		HAL_ADC_Start_DMA(&hadc1,(uint32_t *)buf,2);`
`	}`
`}`
`int fputc(int ch ,FILE* p)`
`{`
`	while(!(USART1->ISR&(1<<7)));`
`	USART1->TDR=ch;`
`	return ch;`
`}`
`void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)`
`{`
`	HAL_ADC_Stop_DMA(&hadc1);`
`	printf("key=%d light=%d\n",buf[0],buf[1]);`
`}`
`

3、DMA不定长接收

3.1 Cube MX 环境配置

3.2 可能使用到的函数

复制代码
找函数去HAL英文手册 HAL_UART_Generic Driver 587页`
`1)HAL_UART_Receive_DMA(UART_HandleTypeDef * huart,` 
`uint8_t` `* pData,` `uint16_t Size)` `//开启DMA通道并设定通道长度`
`2)__HAL_UART_ENABLE_IT(__HANDLE__,__INTERRUPT__)//开启串口空闲中断`
`3)__HAL_UART_GET_FLAG(__HANDLE__,__FLAG__)//获得串口空闲中断标志`
`4)__HAL_UART_CLEAR_FLAG(__HANDLE__,__FLAG__)` `//清除串口空闲中断`
`5)HAL_UART_DMAStop(UART_HandleTypeDef * huart)` `//关闭串口DMA通道`
`6)设定的传输长度-剩余传输数量(DMA_CNDTRx)=实际长度`
`7)HAL_UART_Transmit_DMA(UART_HandleTypeDef * huart,` `uint8_t` `* pData,` `uint16_t Size)`  `//使用DMA通道发送指定长度的字符到串口中`
`

3.3 代码编写

main.c

复制代码
uint8_t buf[128];`															`//接收缓冲区`
`uint8_t trans_buf[]="家玉是帅哥,猛哥是王子";`	`//发送内容`

  `/* USER CODE BEGIN WHILE */`
  `while` `(1)`
  `{`
		`HAL_UART_Transmit_DMA(&huart1,trans_buf,sizeof(trans_buf));`		`//DMA发送`
		`HAL_Delay(200);`
		`HAL_UART_Receive_DMA(&huart1,buf,128);`						`//接收DMA使能`
		`__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);`				`//开启串口空闲中断`
    `/* USER CODE END WHILE */`
`

串口中断函数(中断向量表跳转一次)

复制代码
extern` `uint8_t buf[128];`
`uint8_t len =` `0;`
`// 直接在串口中断函数编写代码,不采用回调函数`
`// 因为DMA自动搬数据,有中断后进入该函数,直接写即可`
`void` `USART1_IRQHandler(void)`	
`{`
  `/* USER CODE BEGIN USART1_IRQn 0 */`

  `/* USER CODE END USART1_IRQn 0 */`
  `HAL_UART_IRQHandler(&huart1);`
  `/* USER CODE BEGIN USART1_IRQn 1 */`
	`// 在中断线中判断IDLE状态`
    `// 1->空闲`
    `// param1->串口;`
    `// param2->Idle line detection interrupt->空闲线路检测中断`
	`if` `(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))`	
	`{`                               `// 1. 获得串口空闲中断标志	`
		`__HAL_UART_CLEAR_FLAG(&huart1,UART_CLEAR_IDLEF);`													 `// 2. 清除空闲中断标志`
		`HAL_UART_DMAStop(&huart1);`	 `// 3. 清除->空闲->停止DMA		`
`		len =` `128` `- hdma_usart1_rx.Instance->CNDTR;`
            `// 4. 设定的传输长度-剩余传输数量(DMA_CNDTRx)=实际长度`
		`HAL_UART_Transmit(&huart1, buf, len,` `100);`
                                    `// 5. 数据处理------发送`
		`HAL_UART_Receive_DMA(&huart1, buf,` `128);`
								     `// 6. 重新开启DMA`
	
	`}`
  `/* USER CODE END USART1_IRQn 1 */`
`}`

`

4、DHT11

查看说明书

DHT11说明书081206.pdf

DHT11是一款有已校准数字信号输出的温湿度传感器。 其精度湿度±5%RH, 温度±2℃,量程湿度20-90%RH, 温度0~50℃。

DHT11温湿度传感器类似于DS18B20采用一线制通信协议(单总线),所谓"一线制"顾名思义,设备与上位控制器通信使用1根线,这根线同时承担了时钟和数据线的角色。

硬件上的简单势必会带来软件上的复杂,像一线制通信协议,一般都是上位CPU先发开始、复位等电平信号,然后DHT11发送回应信号,然后再发送对应数据,上位CPU接收电平脉冲信号,连续接收固定的字节,然后再进行解析数据。

协议分析

主机先要发送一个至少18ms的低电平,在这个过程中,DHT11内部完成AD转换等操作,当主机拉高后,有20-40us时间,这个时间用于主机做输入输出切换,当主机释放总线控制权(此时主机为输入状态,总线被上拉电阻拉高),DHT11尝试将总线拉低,成功拉低后就开始准备发送数据了,再拉高一次就开始传输数据了。

40位:8湿度整 8湿度小 8温度整 8温度小 8校验和

  1. 读取一位 (函数)
  2. 循环8次(读取一位)
  3. 循环5次第二步的函数
  4. 检验和 = 8湿度整+8湿度小+8温度整+8温度小

查看原理图

代码分析

复制代码
//设置IO为输入模式`
`static` `void` `DHT11_IO_IN(void)`
`{`
`    GPIO_InitTypeDef GPIO_InitStruct =` `{0};`

`    GPIO_InitStruct.Pin = DHT11_GPIO_PIN;`
`    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;`
`    GPIO_InitStruct.Pull = GPIO_PULLUP;`
    `HAL_GPIO_Init(DHT11_GPIO_PORT,` `&GPIO_InitStruct);`
`}`
`//设置IO为输出模式`
`static` `void` `DHT11_IO_OUT(void)`
`{`
`    GPIO_InitTypeDef GPIO_InitStruct =` `{0};`

`    GPIO_InitStruct.Pin = DHT11_GPIO_PIN;`
`    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;`
`    GPIO_InitStruct.Pull = GPIO_PULLUP;`
`    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;`
    `HAL_GPIO_Init(DHT11_GPIO_PORT,` `&GPIO_InitStruct);`
`}`

`//复位DHT11      \\起始信号`
`void` `DHT11_Rst(void)`
`{`
    `DHT11_IO_OUT();` 	`//SET OUTPUT  转换成输出模式`
    `HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_RESET);` 	
     `//拉低DQ`
    `HAL_Delay(20);`    	`//拉低至少18ms`
    `HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET);`		      
     `//拉高DQ`
    `delay_us(30);`     	`//主机拉高20~40us`
`}`

`//等待DHT11的回应`
`//返回1:未检测到DHT11的存在`
`//返回0:存在`
`uint8_t` `DHT11_Check(void)`
`{`
    `uint8_t retry=0;`
    `DHT11_IO_IN();//SET INPUT`
    `while` `(DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us`
    `{`
`        retry++;`
        `delay_us(1);`
    `};`
    `if(retry>=100)return` `1;`
    `else retry=0;`
    `while` `(!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us`
    `{`
`        retry++;`
        `delay_us(1);`
    `};`
    `if(retry>=100)return` `1;`
    `return` `0;`
`}`

`//从DHT11读取一个位`
`//返回值:1/0`
`uint8_t` `DHT11_Read_Bit(void)`
`{`
    `uint8_t retry=0;`
    `while(DHT11_DQ_IN&&retry<100)//等待变为低电平`
    `{`
`        retry++;`
        `delay_us(1);`
    `}//延时100`
`    retry=0;`
    `while(!DHT11_DQ_IN&&retry<100)//等待变高电平`
    `{`
`        retry++;`
        `delay_us(1);`
    `}`
    `delay_us(40);//等待40us`
    `if(DHT11_DQ_IN)return` `1;`
    `else` `return` `0;`
`}`

`//从DHT11读取一个字节`
`//返回值:读到的数据`
`uint8_t` `DHT11_Read_Byte(void)`
`{`
    `uint8_t i,dat;`
`    dat=0;`
    `for` `(i=0; i<8; i++)`
    `{`
`        dat<<=1;`
`        dat|=DHT11_Read_Bit();`
    `}`
    `return dat;`
`}`

`//从DHT11读取一次数据`
`//temp:温度值(范围:0~50°)`
`//humi:湿度值(范围:20%~90%)`
`//返回值:HAL_OK,正常;1,读取失败`
`uint8_t` `DHT11_Read_Data(uint8_t` `*humiH,uint8_t` `*humiL,uint8_t` `*tempH,uint8_t` `*tempL)`
`{`
    `uint8_t buf[5];`
    `uint8_t i;`
    `DHT11_Rst();`
    `if(DHT11_Check()==0)`
    `{`
        `for(i=0; i<5; i++)` `//读取40位数据`
        `{`
`            buf[i]=DHT11_Read_Byte();`
        `}`
        `if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])`
        `{`
            `*humiH=buf[0];`     `//湿度高八位,整数位`
            `*humiL=buf[1];`      `//湿度低八位,小数位`
            `*tempH=buf[2];`     `//温度高八位,整数位`
            `*tempL=buf[3];`      `//温度低八位,小数位`
        `}`
    `}` `else`
        `return HAL_ERROR;`
    `return HAL_OK;`
`}`

`//初始化DHT11的IO口 DQ 同时检测DHT11的存在`
`//返回1:不存在`
`//返回0:存在`
`uint8_t` `FS_DHT11_Init(void)`
`{`
`    GPIO_InitTypeDef GPIO_InitStruct =` `{0};`

`    GPIO_InitStruct.Pin = DHT11_GPIO_PIN;`
`    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;`
`    GPIO_InitStruct.Pull = GPIO_PULLUP;`
`    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;`
    `HAL_GPIO_Init(DHT11_GPIO_PORT,` `&GPIO_InitStruct);`
    `HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET);`	
    `// 输出高电平`
    `DHT11_Rst();`  `//复位DHT11`
    `return` `DHT11_Check();//等待DHT11的回应`
`}`

`
相关推荐
生擒小朵拉7 分钟前
STM32添加库函数
java·javascript·stm32
云伴枫轻舞14 分钟前
我对 OTA 的理解随记,附GD32/STM32例程
stm32·单片机·嵌入式硬件
Aczone281 小时前
硬件(五) 存储、ARM 架构与指令系统
arm开发·嵌入式硬件·架构
LS·Cui1 小时前
单片机按键示例功能
单片机
【ql君】qlexcel2 小时前
MCU上电到运行的全过程
单片机·嵌入式硬件·mcu·启动过程
搞一搞汽车电子2 小时前
S32K3平台eMIOS 应用说明
开发语言·驱动开发·笔记·单片机·嵌入式硬件·汽车
pQAQqa2 小时前
FreeRTOS项目(2)摇杆按键检测
stm32·单片机·嵌入式硬件·freertos
小莞尔3 小时前
【51单片机】【protues仿真】基于51单片机停车场的车位管理系统
c语言·开发语言·单片机·嵌入式硬件·51单片机
一川月白7095 小时前
51单片机---硬件学习(跑马灯、数码管、外部中断、按键、蜂鸣器)
单片机·学习·51单片机·外部中断·蜂鸣器·数码管·跑马灯
月阳羊5 小时前
【硬件-笔试面试题-69】硬件/电子工程师,笔试面试题(知识点:电机驱动电路的反馈电路)
java·经验分享·嵌入式硬件·面试