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校验和
- 读取一位 (函数)
- 循环8次(读取一位)
- 循环5次第二步的函数
- 检验和 = 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的回应
}