目录
前言
上一期我们学习了DMA直接存储器存取的理论知识点(上一期:stm32入门-----DMA直接存储器存取(上——理论篇)-CSDN博客),那么本期我们就来进行关于DMA的项目实践,本期有两个项目,分别是DMA实现数组的转运和DMA实现AD多通道数据转运。(视频:[8-2] DMA数据转运&DMA+AD多通道_哔哩哔哩_bilibili)
本期的代码我已上传至百度网盘,可自行下载。
链接: https://pan.baidu.com/s/1gOml_COgxA9tSxwo_tHFOQ?pwd=0721
提取码: 0721
DMA相关函数
下面这些就是DMA的功能函数,这里我会一一讲解。
1.初始化
下面三个函数是对结构体初始化的函数。
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);//恢复缺省配置
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct); //定义好结构体后初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct); //使用默认结构体初始化
下面看个示例:
//配置结构体
DMA_InitTypeDef DMA_initstruct;
//起始外设的三个参数
DMA_initstruct.DMA_PeripheralBaseAddr = addr_A; //起始数据的地址
DMA_initstruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte ; //数据的宽度,这里选择8位,即一个字节
DMA_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //选择地址是否自增,这里是数组的转运选择自增
//目标存储器的三个参数
DMA_initstruct.DMA_MemoryBaseAddr = addr_B;
DMA_initstruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte ;//选择目标存储器的数据宽度,同样是一个字节宽度
DMA_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //选择地址是否自增,同样也是选择自增
DMA_initstruct.DMA_DIR = DMA_DIR_PeripheralSRC; //选择传输方向,这里选择外设到存储器的方向A->B
DMA_initstruct.DMA_BufferSize = size; //设置传输的次数,其取值范围是0~65535
DMA_initstruct.DMA_Mode= DMA_Mode_Normal ; //自动重装的选择,如果选择了软件触发方式是不能选择自动重装的,所以我们这边软件触发就选择普通模式
DMA_initstruct.DMA_M2M = DMA_M2M_Enable; //选择触发方式,我们这里是数组的复制,存储器到存储器的内容,所以选择最快的软件触发
DMA_initstruct.DMA_Priority = DMA_Priority_Medium; //优先级,如果是多个通道的话就要去选择好,我们这里只有一个通道所以随便选
DMA_Init(DMA1_Channel1, &DMA_initstruct); //这里我们既选择了DMA几,也选择了通道
2.中断相关函数
下面这个函数是开启DMA中断使能的函数,我们看到这个config都大概联系到中断之类的。
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
下面这两个是DMA中断相关的函数,获取中断状态标志位,还有就是清除中断标志位。
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
void DMA_ClearITPendingBit(uint32_t DMAy_IT);
3.DMA使能
下面是DMA使能的函数,前面学了这么多看到cmd的都知道是开启使能的操作了。
DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
4.设置计数器的值
下面这个函数是用来去设置计数器的值的函数,如果我们没有选择自动重装,那么如果想再次开启DMA的时候就需要去对搬运计数器进行装值。
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
下面这个是获取当前计数器值的函数,是用来查看计数的次数的,如果你想去看还有多少没有转运就可以用这个看看,不过没有特定需求的话一般不用到。
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
5.DMA标志位相关函数
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);//获取DMA转运标志位状态
void DMA_ClearFlag(uint32_t DMAy_FLAG); //清除DMA转运标志位
项目实践
1.DMA转运存储器之间的数据
实验现象:
DMA转运存储器数据
电路连接图:
工程文件:
DMA.c文件代码:
#include "stm32f10x.h" // Device header
uint16_t count;
void MyDMA_init(uint32_t addr_A, uint32_t addr_B, uint16_t size) {
count = size; //先存一份在全局变量,有大用
//开启DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//配置结构体
DMA_InitTypeDef DMA_initstruct;
//起始外设的三个参数
DMA_initstruct.DMA_PeripheralBaseAddr = addr_A; //起始数据的地址
DMA_initstruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte ; //数据的宽度,这里选择8位,即一个字节
DMA_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //选择地址是否自增,这里是数组的转运选择自增
//目标存储器的三个参数
DMA_initstruct.DMA_MemoryBaseAddr = addr_B;
DMA_initstruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte ;//选择目标存储器的数据宽度,同样是一个字节宽度
DMA_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //选择地址是否自增,同样也是选择自增
DMA_initstruct.DMA_DIR = DMA_DIR_PeripheralSRC; //选择传输方向,这里选择外设到存储器的方向A->B
DMA_initstruct.DMA_BufferSize = size; //设置传输的次数,其取值范围是0~65535
DMA_initstruct.DMA_Mode= DMA_Mode_Normal ; //自动重装的选择,如果选择了软件触发方式是不能选择自动重装的,所以我们这边软件触发就选择普通模式
DMA_initstruct.DMA_M2M = DMA_M2M_Enable; //选择触发方式,我们这里是数组的复制,存储器到存储器的内容,所以选择最快的软件触发
DMA_initstruct.DMA_Priority = DMA_Priority_Medium; //优先级,如果是多个通道的话就要去选择好,我们这里只有一个通道所以随便选
DMA_Init(DMA1_Channel1, &DMA_initstruct); //这里我们既选择了DMA几,也选择了通道
//开启DMA
DMA_Cmd(DMA1_Channel1, DISABLE);
}
//给传输计数器重新赋值
void MyDMA_transfer() {
//根据DMA的要求DMA是必须在停止工作的时候才能去设置参数
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, count); //对计数器重新赋值
DMA_Cmd(DMA1_Channel1, ENABLE);//开启DMA
//等待转运完成标志位
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//如果是Reset状态就说明没有转运完成,所以就继续在循环里面等着
//清除标志位,是需要手动清除的
DMA_ClearFlag(DMA1_FLAG_TC1);
}
DMA.h文件代码:
#ifndef __DMA_H
#define __DMA_H
void MyDMA_init(uint32_t addr_A, uint32_t addr_B, uint16_t size);
void MyDMA_transfer();
#endif // !__DMA_H
main.c文件代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "DMA.h"
uint8_t A[] = { 0x01,0x02,0x03,0x04 };
uint8_t B[]={0,0,0,0};
uint8_t i;
int main(void)
{
OLED_Init();
OLED_ShowString(1, 1, "arr_A:");
OLED_ShowString(3, 1, "arr_B:");
OLED_ShowHexNum(1, 7, (uint32_t)A, 8);
OLED_ShowHexNum(3, 7, (uint32_t)B, 8);
MyDMA_init((uint32_t)A, (uint32_t)B, 4);
while (1)
{
for (i = 0;i < 4;i++)
A[i]++;
for (i = 0;i < 4;i++)
OLED_ShowHexNum(2, 3 * i + 1, A[i], 2);
for (i = 0;i < 4;i++)
OLED_ShowHexNum(4, 3 * i + 1, B[i], 2);
Delay_ms(1000);
MyDMA_transfer();
for (i = 0;i < 4;i++)
OLED_ShowHexNum(2, 3 * i + 1, A[i], 2);
for (i = 0;i < 4;i++)
OLED_ShowHexNum(4, 3 * i + 1, B[i], 2);
Delay_ms(1000);
}
}
2.DMA转运ADC转换的数据
实验现象:
DMA转运ADC转换结果
电路连接图:
工程文件:
DMA.c文件代码:
#include "stm32f10x.h" // Device header
uint16_t AD_value[4];
void AD_init() {
//1.开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//选择时钟分频
//2.配置GPIO
GPIO_InitTypeDef GPIO_initstruct;
GPIO_initstruct.GPIO_Mode=GPIO_Mode_AIN; //选择模拟输入 ,是ADC专属模式,其他是无效的
GPIO_initstruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_initstruct);
//扫描的通道, 规则组列表中的通道,序列写入通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_28Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_28Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_28Cycles5);
// 3.ADC结构体初始化
ADC_InitTypeDef ADC_initstruct;
ADC_initstruct.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,左右可选,这里选择右对齐
ADC_initstruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//选择外部触发源,这里选择none是用软件触发
ADC_initstruct.ADC_Mode = ADC_Mode_Independent;//选择ADC的模式,独立和双模式选择,这里选择独立模式
//选择模式
ADC_initstruct.ADC_ContinuousConvMode = ENABLE; //连续转换
ADC_initstruct.ADC_ScanConvMode = ENABLE; // 扫描模式,这里选择扫描模式,多个序列
ADC_initstruct.ADC_NbrOfChannel = 4;//通道数目,有4个通道和序列
ADC_Init(ADC1, &ADC_initstruct);
//配置结构体
DMA_InitTypeDef DMA_initstruct;
//起始外设的三个参数,ADC
DMA_initstruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //起始数据的地址
DMA_initstruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_HalfWord ; //数据的宽度
DMA_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //选择地址是否自增,因为ADC地址是固定的,不能改变
//目标存储器的三个参数,数组
DMA_initstruct.DMA_MemoryBaseAddr = (uint32_t)AD_value;
DMA_initstruct.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//选择目标存储器的数据宽度
DMA_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //选择地址是否自增
DMA_initstruct.DMA_DIR = DMA_DIR_PeripheralSRC; //选择传输方向,这里选择外设到存储器的方向A->B
DMA_initstruct.DMA_BufferSize = 4; //设置传输的次数,其取值范围是0~65535
DMA_initstruct.DMA_Mode= DMA_Mode_Circular ; //自动重装的选择,如果选择了软件触发方式是不能选择自动重装的,所以我们这边软件触发就选择普通模式
DMA_initstruct.DMA_M2M = DMA_M2M_Disable; //选择触发方式,我们这里是ADC 转换,是需要硬件触发
DMA_initstruct.DMA_Priority = DMA_Priority_Medium; //优先级,如果是多个通道的话就要去选择好,我们这里只有一个通道所以随便选
DMA_Init(DMA1_Channel1, &DMA_initstruct); //这里必须选择DMA1的通道1,因为ADC1只接在DMA1的通道1上,其他的都不行
//开启DMA
DMA_Cmd(DMA1_Channel1, ENABLE);
//开启ADC到DMA的触发信号
ADC_DMACmd(ADC1, ENABLE);
//4.开启ADC电源
ADC_Cmd(ADC1, ENABLE);
//5.校准
ADC_ResetCalibration(ADC1); //复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET); //获取复位校准状态,如果复位完成了之后就标志位就会清理
ADC_StartCalibration(ADC1); //开启校准
while (ADC_GetCalibrationStatus(ADC1) == SET);//如果标志位清零的话说明校准完成,不然就一直等待校准标志位
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//选择软件触发,开启ADC软件触发
}
//上面设置的单次转换,所以这里要去开启ADC软件触发转换
//实际上DMA的速度是比ADC快很多的,所以ADC都没转换好的时候DMA会一直处于等待的状态,这里是选择先开启DMA再开启ADC的,顺序很重要
void AD_getvalue() {
//根据DMA的要求DMA是必须在停止工作的时候才能去设置参数
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, 4); //对计数器重新赋值
DMA_Cmd(DMA1_Channel1, ENABLE);//开启DMA
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//选择软件触发,开启ADC软件触发
//等待转运完成标志位
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//如果是Reset状态就说明没有转运完成,所以就继续在循环里面等着
//清除标志位,是需要手动清除的
DMA_ClearFlag(DMA1_FLAG_TC1);
}
DMA.h文件代码:
#ifndef __AD_h
#define __AD_h
extern uint16_t AD_value[4];
void AD_init();
void AD_getvalue();
#endif // !__AD_h#define __AD_h
main.c文件代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
#include "LED.h"
int main(void)
{
OLED_Init();
AD_init();
OLED_ShowString(1, 1, "AD0:");
OLED_ShowString(2, 1, "AD1:");
OLED_ShowString(3, 1, "AD2:");
OLED_ShowString(4, 1, "AD3:");
while (1) {
//AD_getvalue(); //如果是单次转换模式这里就需要这个在死循环里面
OLED_ShowNum(1, 5, AD_value[0], 4);
OLED_ShowNum(2, 5, AD_value[1], 4);
OLED_ShowNum(3, 5, AD_value[2], 4);
OLED_ShowNum(4, 5, AD_value[3], 4);
Delay_ms(100);
}
}
以上就是本期的全部内容了,我们下次见!
今日壁纸: