STM32--DMA

news2024/12/22 18:01:26

文章目录

  • DMA简介
    • DMA特性
  • DMA框图
  • DMA基本结构
  • DMA请求
  • 数据宽度对齐
  • DMA数据转运工程
  • DMA+ADC多通道

DMA简介

直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。

DMA特性

拥有12个独立可配置通道:DMA1(7个通道),DMA2(5个通道)

STM32F103C8T6 DMA资源:DMA1(7个通道)

在这里插入图片描述

每个通道都直接连接专用的硬件DMA请求,也就是硬件触发,也支持软件触发

对于有多个请求的同时,可以利用软件编程设置优先级的先后顺序,优先级相等的情况下由硬件决定

对于DMA来说,需要传输的源头和传输的目的地,传输的大小称为传输宽度,一般有字节(8bit)、半字(16bit)、字(32bit)。

在这里插入图片描述

每个通道都有3个事件标志(DMA半传输、 DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。
在这里插入图片描述

DMA框图

在这里插入图片描述
先看左上角,DMA与Cortex™-M3核心共享系统数据总线,通过总数据库,可以直接执行存储器的数据传输(Flash、SRAM、外设寄存器);当DMA与Cortex™-M3同时访问相同的目标时,会通过一个仲裁器,来给它们两个循环调度访问目标,而CPU最终会得到至少一半的带宽,也就访问权限。

接着往下看,AHB从设备连接着AHB总线,可以控制总裁器和通道的选择。相当于AHB放长线,放出一个设备来管理DMA。

总裁器是可以根据通道请求的优先级来启动外设/存储器的访问。
可以通过软件编程设置4个不同的等级:最高优先级、高优先级、中等优先级、低优先级。
相同等级的情况下,则较低编号的通道比较高编号的通道有较高的优
先权。举个例子,通道2优先于通道4。

右边都是一些存储器和外设寄存器,通过DMA的请求,就可以直接对数据进行传输了。
在这里插入图片描述
这是存储器对应的起始地址和作用。

DMA基本结构

在这里插入图片描述

DMA的处理:在发生一个事件后,外设向DMA控制器发送一个请求信号。 DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时, DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求, DMA控制器同时撤销应答信号。如果有更多的请求时,外设可以启动下一个周期。

我们首先会从要传输的外设寄存器或者是储存器获取它的起始地址和传输的数据宽度,然后再存数据到外设寄存器或者存储器指示的存储器地址,并确定数据宽度;

首先这里要明确DMA的数据转运是用复制去传输,也就是原位置的数据是不会改变的。地址自增是考虑到一般给出的地址只是首元素地址或者说是起始地址,要对下一个数据进行传输,就需要对地址进行增加,才能获取到下一个数据

传输计数器是来统计要传输多少个数据宽度的,一开始先赋予传输计数器一个初始值
在没有启动自动重装器的情况下,这个初始值会逐渐递减,当计数器的值变为0时,就停止传输。
如果启动了自动重装器,当初始值逐渐递减为0时,会重新恢复到初始值,而对应的传输地址,也会恢复到初始地址的状态;

M2M是决定使用软件触发或者硬件触发的寄存器

DMA请求

在这里插入图片描述
这是DMA1的请求映像,对于DMA请求来说,不同的外设是有要求不同对应通道的,没有由我们所决定哪个硬件外设对应哪条通道;而软件触发的则每条通道都允许。

数据宽度对齐

在这里插入图片描述

大小端讲解连接入口

这里是小端存储。
对以上的总结:
当两端宽度相等时:那么传输数据不变;
当源端宽度小于目标宽度时:目标宽度高位补0;
当源端宽度大于目标宽度时:对源端宽度进行截断,保留低位的数据

DMA数据转运工程

OLED代码连接入口

连接方式:
在这里插入图片描述
在这里插入图片描述
通过DMA的数据搬运,将一个数组中的内容搬运到另一个数组中,并且原数组会随时间不断增加,DMA也不断的进行数据搬运;

DMA.c

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{
    MyDMA_Size=Size;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
    //DMA初始化
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_BufferSize=Size; //指定通道缓冲区大小
    DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC; //指定外设是源或者目标
    DMA_InitStructure.DMA_M2M=DMA_M2M_Enable; //指定是否为软件触发运送
    DMA_InitStructure.DMA_MemoryBaseAddr=AddrB; //指定内存基地址
    DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte; //内存数据宽度
    DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable; //指定内存地址是否自增
    DMA_InitStructure.DMA_Mode=DMA_Mode_Normal; //指定DMA通道工作模式
    DMA_InitStructure.DMA_PeripheralBaseAddr=AddrA; //指定外设基地址
    DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte; //外设数据宽度
    DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable; //指定内存地址是否自增
    DMA_InitStructure.DMA_Priority=DMA_Priority_Medium; //指定DMA通道优先级
    DMA_Init(DMA1_Channel1,&DMA_InitStructure);

    //DMA是否启动
    DMA_Cmd(DMA1_Channel1,DISABLE);
}
//数据转运函数
void MyDMA_Transfer()
{
    DMA_Cmd(DMA1_Channel1,DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size); //设计DMA通道数据单元数
    DMA_Cmd(DMA1_Channel1,ENABLE);
    
    //检查DMA通道上的标志位,运送完成标志
    while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET); 
    //需要手动清除标志位
    DMA_ClearFlag(DMA1_FLAG_TC1);
}

DMA.h

#ifndef __DMA_H__
#define __DMA_H__

void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size);
void MyDMA_Transfer();

#endif

size表示传输的个数,大小由源端数据宽度决定,地址自增将会使数组下标进行移动;
规定软件触发不能启动自动重装器,也就是DMA的模式
数据转运函数:由于需要不断的数据转运,而没有启动自动重装器,所以需要我们自己编程来进行重装,也就是将Size恢复为初始值,地址会随着Size的变化进行变化。
手动将Size重装之前,需要先对运行控制禁用,全部操作设置完才可以启用运行控制

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "DMA.h"
#include "OLED.h"

uint8_t DataA[]={0x01,0x02,0x03,0x04};
uint8_t DataB[]={0,0,0,0};
int main()
{
	OLED_Init();
	MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);

    OLED_ShowString(1,1,"DataA");
    OLED_ShowString(3,1,"DataB");
    OLED_ShowHexNum(1,8,(uint32_t)DataA,8);
    OLED_ShowHexNum(3,8,(uint32_t)DataB,8);

    while(1)
    {
        DataA[0]++;
        DataA[1]++;
        DataA[2]++;
        DataA[3]++;
        OLED_ShowHexNum(1,8,(uint32_t)DataA,8);
        OLED_ShowHexNum(2,1,DataA[0],2);
        OLED_ShowHexNum(2,4,DataA[1],2);
        OLED_ShowHexNum(2,7,DataA[2],2);
        OLED_ShowHexNum(2,10,DataA[3],2);
        OLED_ShowHexNum(4,1,DataB[0],2);
        OLED_ShowHexNum(4,4,DataB[1],2);
        OLED_ShowHexNum(4,7,DataB[2],2);
        OLED_ShowHexNum(4,10,DataB[3],2);
        
        Delay_ms(1000);
        
        MyDMA_Transfer();
        
        OLED_ShowHexNum(2,1,DataA[0],2);
        OLED_ShowHexNum(2,4,DataA[1],2);
        OLED_ShowHexNum(2,7,DataA[2],2);
        OLED_ShowHexNum(2,10,DataA[3],2);
        OLED_ShowHexNum(4,1,DataB[0],2);
        OLED_ShowHexNum(4,4,DataB[1],2);
        OLED_ShowHexNum(4,7,DataB[2],2);
        OLED_ShowHexNum(4,10,DataB[3],2);
        
        Delay_ms(1000);
    }
}

DMA+ADC多通道

接线方式:
在这里插入图片描述
在这里插入图片描述
ADC的规则通道会将多通道结果放在规则通道寄存器上,每当扫描到一个通道,结果就放在ADC_DR上,结果会覆盖掉上一个储存的数据,DMA就会将结果转运到DMA存储器地址上。所以对于ADC_DR不用实现地址自增,而DMA储存器需要实现地址自增。

AD.c

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

void AD_Init()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
    
    //配置ADC时钟
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);  // 72M/6=12MHz

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; //模拟输入
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
   
    
    //为所选ADC规则通道配置其序列器对应等级和采样时间
    ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);
    
    
    //ADC结构体成员
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_ContinuousConvMode=ENABLE; //指定通道模式为连续转换或者单转换
    ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //数据对齐方式
    ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //启动规则通道模拟电压到数字转换的外部触发器
    ADC_InitStructure.ADC_Mode=ADC_Mode_Independent; //配置ADC为独立模式或者双模式
    ADC_InitStructure.ADC_NbrOfChannel=4;
    ADC_InitStructure.ADC_ScanConvMode=ENABLE; //选择是否为扫描模式
    ADC_Init(ADC1,&ADC_InitStructure);
    
    //DMA结构体成员
    DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_BufferSize = 4;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    
    //ADC运行控制
    DMA_Cmd(DMA1_Channel1,ENABLE);
    ADC_DMACmd(ADC1,ENABLE);
    ADC_Cmd(ADC1,ENABLE);
    
    
    //重置所选ADC校准寄存器
    ADC_ResetCalibration(ADC1);
    //获取ADC复位状态,复位后为0
    while(ADC_GetResetCalibrationStatus(ADC1));
    //开始校准
    ADC_StartCalibration(ADC1);
    //获取ADC所选标准位状态,校准需要时间,校准好后置0
    while(ADC_GetCalibrationStatus(ADC1));

    //启动ADC软件转换
    ADC_SoftwareStartConvCmd(ADC1,ENABLE);

}



AD.h

#ifndef __AD_H__
#define __AD_H__

extern uint16_t AD_Value[4];

void AD_Init();


#endif

这里采用的是连续转换+扫描模式。这样就可以直接实现数据转换的自动化。
这里需要加上ADC_DMACmd函数,因为上面DMA请求中通道一的硬件触发有3个选择,这里表示选择ADC1.
ADC为软件触发方式,DMA为硬件触发方式。

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"


int main()
{
	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)
    {
        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);
    }
}

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

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

相关文章

Goland 注释时自动在注释符号后添加空格

不得不说 JetBrains 旗下的 IDE 都好用,而且对于注释这块,使用 Ctrl / 进行注释的时候,大多会在每个注释符号后统一添加一个空格,比如 PyCharm 和 RubeMine 等。 # PyCharm # print("hello world") # RubyMine # req…

基于web网上订餐系统的设计与实现(论文+源码)_kaic

目录 1绪论 1.1课题研究背景 1.2研究现状 1.3主要内容 1.4本文结构 2网上订餐系统需求分析 2.1系统业务流程分析 2.2消费者用户业务流程分析 2.3商户业务流程分析 2.4管理员用户流程分析消费者用户用例分析 2.5系统用例分析 3网上订餐系统设计 3.1功能概述 3.2订单管理模块概要…

MySQL安装、配置和启动关闭

1. 概述 本文主要内容: MySQL下载;MySQL的安装;配置环境变量;登录MySQL服务器;查询系统数据库;启动和关闭服务; 2. 安装、配置、启动与关闭服务 2.1. MySQL下载 在MySQL官网就可以下载。 …

aardio简单日历实例

import console; io.open()//aardio简单日历实例getMonthDays function(year,month){var startDate year"/"month"/""1"; //当月1日var endDate time(startDate).addmonth(1).addday(-1); //当月末return endDate.diffday(time(startDate))1…

数据结构 - 算法设计的基本要求

1、算法的描述: 自然语言:英语、中文流程图:传统流程图、NS流程图伪代码:类语言 - 类C语言程序代码:Java、C语言 2、算法的特性: 一个算法必须具备以下五个特性: 3、算法设计的要求 正确性可…

msvcp140.dll文件丢失的解决方法是什么?

在日常使用电脑的时候,有时候会遇到一些使用问题。比如,有一次遇到了这样一个问题。那就是,因为“msvcp140.dll”这个文件丢失,有些软件安装不了。今天把我在网上找了很久的解决方法分享给大家,希望也能帮到大家。 丢…

页面滑动到可视区域加载更多内容思维流程

页面滑动到可视区域加载更多内容思维流程

Slingshot | 细胞分化轨迹的这样做比较简单哦!~(二)

1写在前面 今天又值班了,你没有听错!! 🥲 又值班了!!!!😅 最近自己的确不太在状态,做事情有极强的拖延症,要振奋起来啦,man&#xff0…

编写Dockerfile制作自己的镜像并推送到私有仓库

说明:我将用到的私有仓库是Harbor,安装教程参考我的这一篇文章: 安装搭建私有仓库Harbor_Word_Smith_的博客-CSDN博客 一、案例1 1、要求 编写Dockerfile制作Web应用系统nginx镜像,生成镜像nginx:v1.1,并推送其到私…

Linux学习(一)虚拟机安装

1、简介 最近准备开始进行linux的学习,本文从头开始记录学习过程以及遇到困难处理办法,便于以后复习、指令复制等。 2、虚拟机安装 2.1 VMware虚拟机安装 安装包链接:ubuntu20.04 https://www.aliyundrive.com/s/ZN8kZFKvBRu 点击链接保存…

十一、Linux用户及用户组的权限信息如何查看?如何修改?什么是权限的数字序号?

目录: 1、认知权限信息 2、rwx? (1)总括: (2)r权限: (3)w权限: (4)x权限: 3、修改权限 (1&a…

电脑提示找不到d3dcompiler_47.dll的解决方案

在电脑上玩游戏或许工作中是我日常生活中的一大乐趣。然而,最近我遇到了一个问题,让我对我的游戏还有我的工作软件体验感到非常沮丧。这个问题就是d3dcompiler_47.dll文件的丢失。当我尝试启动一个新的游戏时,一个错误提示窗口出现在我的屏幕…

操作系统经典互斥问题哲学家就餐问题

问题描述 由Dijkstra提出并解决的哲学家就餐问题是典型的同步问题。该问题描述的是五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替的进行思考和进餐。平时,一个哲学家进行思考…

LeetCode 42题:接雨水

题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,…

c#扩展方法的使用

扩展方法可以向现有类型“添加”方法,无需创建新的派生类型、重新编译或以其他方式修改原始类型,用起来很方便,下面是我写的例子,为string这个常用的类型添加一个showmes方法,以下是扩展方法的代码: public…

React+Typescript 状态管理

好 本文 我们来说说状态管理 也就是我们的 state 我们直接顺便写一个组件 参考代码如下 import * as React from "react";interface IProps {title: string,age: number }interface IState {count:number }export default class hello extends React.Component<I…

【LeetCode-中等题】49. 字母异位词分组

题目 题解一:排序哈希表 思路:由于互为字母异位词的两个字符串包含的字母相同&#xff0c;因此对两个字符串分别进行排序之后得到的字符串一定是相同的&#xff0c;故可以将排序之后的字符串作为哈希表的键。 核心api: //将字符串转换为字符数组char[] ch str.toCharArray();…

【力扣】84. 柱状图中最大的矩形 <模拟、双指针、单调栈>

目录 【力扣】84. 柱状图中最大的矩形题解暴力求解双指针单调栈 【力扣】84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例…

从一到无穷大 #11 Is mmap shit or not?

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 前言mmap原理madvise / mlock / msync放弃使用mmap的理由InfluxdbsinglestoreRocksDB…