文章目录
- 需求
- 一、DMA(直接存储器存取)
- 二、实现流程
- 1.时钟使能
- 2.设置外设寄存器地址
- 3.设置存储器地址
- 4.设置要传输的数据量
- 5.设置通道优先级
- 6.设置传输方向
- 7.使通道和ADC转换
- 三、数据处理
- 四、需求实现
- 总结
需求
通过DMA实现光照强度和烟雾浓度的多通道采集
一、DMA(直接存储器存取)
作用:
把外设的寄存器里面数据直接传输到存储器中
把存储器里面数据直接传输到外设的寄存器中
把存储器里面数据直接传输到存储器中
绕开了CPU。
二、实现流程
1、时钟使能
2、设置外设寄存器地址
3、设置存储器地址
4、设置要传输的数据量
5、设置通道优先级
6、设置传输方向:外设到存储器,还是存储器到存储器
循环模式
外设和存储器的增量模式:外设和存储器的地址是否向后偏移
外设和存储器的数据宽度:一个数据的大小(8位,16位,32位)
7、使通道和ADC转换
1.时钟使能
DMA有很多通道,本次配置的为DMA1通道
可见DMA1在AHB外设时钟使能寄存器的第0位。
RCC->AHBENR |= 0x1<<0;
2.设置外设寄存器地址
由于该ADC1的规则组通道数据寄存器在DR上,所以直接赋给就行。
DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR);//设置外设寄存器地址
3.设置存储器地址
存储器地址需要自己设置一个变量,我这里为了方便后续取中位数,定义了个结构体数组,存放烟雾和光照两种变量,每种最多能存放10个。
DMA1_Channel1->CMAR = (uint32_t)adcvalue;//设置存储器地址
结构体及定义
typedef struct{
uint16_t light;
uint16_t mq2;
}ADCARR;
ADCARR adcvalue[10]={0};
4.设置要传输的数据量
这个根据需求来设置,想要一次传多少数据就填多少。
我这里为了获取每组10个数据,共20个量。
所以这里我填写的是20。
DMA1_Channel1->CNDTR = 20;//3、设置要传输的数据量
5.设置通道优先级
由于此时就1个通道,所以填那个都无所谓。
DMA1_Channel1->CCR |= 0x3<<12;//4、设置通道优先级 最高
6.设置传输方向
外设到存储器,还是存储器到存储器
打开循环模式(循环读)
外设和存储器的增量模式:外设和存储器的地址是否向后偏移
外设和存储器的数据宽度:一个数据的大小(8位,16位,32位)
全在CCR寄存器上一位一位配就行了
//5、设置传输方向: //外设到存储器,还是存储器到存储器
DMA1_Channel1->CCR &= ~(0x1<<14);//选择外设和存储器的之间的传输
DMA1_Channel1->CCR &= ~(0x1<<4);//从外设读
//外设和存储器的数据宽度:一个数据的大小(8位,16位,32位)
DMA1_Channel1->CCR &= ~(0x3<<10);//存储器数据宽度
DMA1_Channel1->CCR |= 0x1<<10;
DMA1_Channel1->CCR &= ~(0x3<<8);//外设寄存器数据宽度
DMA1_Channel1->CCR |= 0x1<<8;
//外设和存储器的增量模式:外设和存储器的地址是否向后偏移
DMA1_Channel1->CCR |= 0x1<<7;//存储器的指针增量打开
DMA1_Channel1->CCR &= ~(0x1<<6);//外设的指针增量关闭
DMA1_Channel1->CCR |= 0x1<<5;//循环模式打开
7.使通道和ADC转换
使能一下CRR第0位
DMA1_Channel1->CCR |= 0x1<<0;
将ADC的扫描和循环开启,最后开启ADC1的转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
此时ADC1就会不停转换,并将光照强度参数和烟雾浓度参数通过DMA从DR寄存器发送到我们定义的变量(存储器)中。
三、数据处理
为了使获取的数据更加精准,我定义了一个新的函数用来求光照和烟雾获取的每10个参数中间的平均数。
代码如下:
void Get_Smoke_Light_MidValue()
{
uint16_t Mid_Light_Value[10]={0};
uint16_t Mid_Smoke_Value[10]={0};
uint16_t i=0,j=0,temp=0;
for(i=0;i<10;i++){
Mid_Light_Value[i] = adcvalue[i].light;
}
for(i=0;i<10;i++){
Mid_Smoke_Value[i] = adcvalue[i].mq2;
}
for(i=0;i<10-1;i++)//光照
{
for(j=0;j<9-i;j++)
{
if(Mid_Light_Value[j]<Mid_Light_Value[j+1]){
temp = Mid_Light_Value[j];
Mid_Light_Value[j] = Mid_Light_Value[j+1];
Mid_Light_Value[j+1] = temp;
}
}
}
temp=0;
for(i=0;i<10-1;i++)//烟雾
{
for(j=0;j<9-i;j++)
{
if(Mid_Smoke_Value[j]<Mid_Smoke_Value[j+1]){
temp = Mid_Smoke_Value[j];
Mid_Smoke_Value[j] = Mid_Smoke_Value[j+1];
Mid_Smoke_Value[j+1] = temp;
}
}
}
adcData.light = (Mid_Light_Value[4]+Mid_Light_Value[5])/2;
adcData.mq2 = (Mid_Smoke_Value[4]+Mid_Smoke_Value[5])/2;
printf("Light = %d\r\n",adcData.light);
printf("Smoke = %d\r\n",adcData.mq2);
return;
}
四、需求实现
main.c
#include "stm32f10x.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "string.h"
#include "pwm.h"
#include "adc.h"
int main()
{
NVIC_SetPriorityGrouping(5);//两位抢占两位次级
Usart1_Config();
SysTick_Config(72000);
RGBpwm_Config();
uint8_t cai_count=0;
uint16_t cont=0;
Adc_Config();
while(1)
{
if(ledcnt[0]>=ledcnt[1]){//过去3s
ledcnt[0]=0;
Get_Smoke_Light_MidValue();
}
}
return 0;
}
adc.c
#include "ADC.h"
ADCARR adcvalue[10]={0};
ADCARR adcData={0};
//库函数
void Adc_Config(void)
{
//开时钟ADC1和PC,PA
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC|RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//配置DMA
RCC->AHBENR |= 0x1<<0;//开启DMA时钟
DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR);//设置外设寄存器地址
DMA1_Channel1->CMAR = (uint32_t)adcvalue;//设置存储器地址
DMA1_Channel1->CNDTR = 20;//3、设置要传输的数据量
DMA1_Channel1->CCR |= 0x3<<12;//4、设置通道优先级 最高
//5、设置传输方向: //外设到存储器,还是存储器到存储器
DMA1_Channel1->CCR &= ~(0x1<<14);//选择外设和存储器的之间的传输
DMA1_Channel1->CCR &= ~(0x1<<4);//从外设读
//外设和存储器的数据宽度:一个数据的大小(8位,16位,32位)
DMA1_Channel1->CCR &= ~(0x3<<10);//存储器数据宽度
DMA1_Channel1->CCR |= 0x1<<10;
DMA1_Channel1->CCR &= ~(0x3<<8);//外设寄存器数据宽度
DMA1_Channel1->CCR |= 0x1<<8;
//外设和存储器的增量模式:外设和存储器的地址是否向后偏移
DMA1_Channel1->CCR |= 0x1<<7;//存储器的指针增量打开
DMA1_Channel1->CCR &= ~(0x1<<6);//外设的指针增量关闭
DMA1_Channel1->CCR |= 0x1<<5;//循环模式打开
//6、使能所使用的通道
DMA1_Channel1->CCR |= 0x1<<0;
//配置GPIO口
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PA5,光敏
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//PC1,烟雾
GPIO_Init(GPIOC, &GPIO_InitStructure);
//配置ADC1
ADC_InitTypeDef ADC_InitStruct={0};
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//ADC独立模式
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//选择软件SWSTART位触发
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;//连续模式
ADC_InitStruct.ADC_ScanConvMode = ENABLE;//开启扫描
ADC_InitStruct.ADC_NbrOfChannel = 2;
ADC_Init(ADC1,&ADC_InitStruct);
//配置通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_5,1, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_11,2, ADC_SampleTime_239Cycles5);
ADC_Cmd(ADC1, ENABLE);
ADC1->CR2 |= 0x1<<8;//开启ADC的DMA请求
//校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
void Get_Smoke_Light_MidValue()
{
uint16_t Mid_Light_Value[10]={0};
uint16_t Mid_Smoke_Value[10]={0};
uint16_t i=0,j=0,temp=0;
for(i=0;i<10;i++){
Mid_Light_Value[i] = adcvalue[i].light;
}
for(i=0;i<10;i++){
Mid_Smoke_Value[i] = adcvalue[i].mq2;
}
for(i=0;i<10-1;i++)//光照
{
for(j=0;j<9-i;j++)
{
if(Mid_Light_Value[j]<Mid_Light_Value[j+1]){
temp = Mid_Light_Value[j];
Mid_Light_Value[j] = Mid_Light_Value[j+1];
Mid_Light_Value[j+1] = temp;
}
}
}
temp=0;
for(i=0;i<10-1;i++)//烟雾
{
for(j=0;j<9-i;j++)
{
if(Mid_Smoke_Value[j]<Mid_Smoke_Value[j+1]){
temp = Mid_Smoke_Value[j];
Mid_Smoke_Value[j] = Mid_Smoke_Value[j+1];
Mid_Smoke_Value[j+1] = temp;
}
}
}
adcData.light = (Mid_Light_Value[4]+Mid_Light_Value[5])/2;
adcData.mq2 = (Mid_Smoke_Value[4]+Mid_Smoke_Value[5])/2;
printf("Light = %d\r\n",adcData.light);
printf("Smoke = %d\r\n",adcData.mq2);
return;
}
adc.h
#ifndef _ADC_H_
#define _ADC_H_
#include "stm32f10x.h"
#include "stdio.h"
typedef struct{
uint16_t light;
uint16_t mq2;
}ADCARR;
void Get_Smoke_Light_MidValue();
void Adc_Config(void);
#endif
总结
1.先开时钟,配置DMA。
2.打开ADC1的两个通道(烟雾和光照),开启扫描和循环。
3.使能DMA并开启ADC转换。
4.数据处理及主函数调用。