iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等
第三十三章Cortex-M4 DMA实验
本章节最终所完成的实验例程存放路径为“iTOP-STM32MP157开发板网盘资料汇总\06_Cortex-M4实验例程\09_DMA.zip”。
33.1 DMA 简介
DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。
我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?
因此,转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理,
DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。
DMA定义:
DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。
DMA传输方式:
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:
1)外设到内存
2)内存到外设
3)内存到内存
4)外设到外设
DMA传输参数
我们知道,数据传输,首先需要的是1数据的源地址、2 数据传输位置的目标地址、3 传递数据多少的数据传输量、4进行多少次传输的传输模式 DMA所需要的核心参数,便是这四个。
当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时达到传输终点,结束DMA传输,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。
33.2 STM32MP157 DMA 资源介绍
STM32MP157内部有3个DMA控制器:
1个高速主DMA(MDMA)和2个双口DMA(DMA1和 DMA2)。M4 和 A7 可以共享 MDMA,而 DMA1 和 DMA2 只能由 A7 或者 M4 中某一个使用。 由于我们本章节实验是通过M4核心来实现的,所以我们在这里只是关注DMA1和DMA2。STM32MP157 DMA对应的功能框图如下
Programming port 代表 DMA 控制器的从机编程接口,通过该接口可以对 DMA 的相关控制寄存器进行设置,从而配置 DMA,实现不同的功能。
Peripheral port 和Memory port 分别代表 DMA 控制器的外设接口和DMA 控制器的存储器接口,用于访问相关外设和用于访问外部存储器。
DMA控制器的FIFO区,可以实现存储器接口到外设接口之间的数据长度非对齐传输。每个数据流(总共 8 个数据流)都有一个独立的 FIFO(先进先出存储器缓冲区),FIFO 用于临时存放要传输的数据,可通过 DMA_SxFCR 寄存器来控制控制 FIFO 的阈值,如果数据存储量达到阈值时,FIFO 中的数据将传输到目标中。
Arbitrer代表DMA 控制器的仲裁器,用于仲裁数据流 0~7 的请求优先级,保证数据有序传输。
左侧的From DMAMUX输入为 DMA 控制器数据流 0~7 的通道请求信号,每个数据流有多达 116 个通道请求可以选择。
DMA 请求映射表,如下图所示:
33.3实验目的
1)学习使用STM32CubeIED配置DMA
2)STM32CubeIED的熟练
实验要求:UART4 以 DMA 方式发送数据,打开串口调试助手,可以收到 DMA 发送的内容。
33.4实验步骤
33.4.1建立DMA工程
首先我们打开STM32CubeIDE软件,进入软件界面之后,我们点击File属性,选择NEW下的STM32 Project的选项,如下图所示:
然后我们会进入下图所示界面:在Part Number选择框输入STM32MP157A,然后在右边的选择界面选择STM32MP157AAA,然后点击Next选项
在Project Name框中输入工程名字DMA,然后点击Finish选项即可,如下图所示:
等待工程创建完毕,会询问我们是否要安装OpenSTLinux ,由于我们是在windows环境下,所以我们不需要安装,点击NO即可
至此我们的工程创建完毕,进入工程界面如下图所示界面:
33.4.1.1串口引脚的功能配置
首先我们在下面的搜索框之中输入我们要配置的引脚,我们在这里以PB2为例进行搜索,输入名称之后,对应的引脚在工程中会闪烁,如下图所示:
然后我们使用鼠标左键点击对应的引脚会弹出PB2的复用功能选择,我们在这里选择复用为UART4_RX功能,如下图所示:
用同样的方法对PG11进行搜索,然后我们使用鼠标左键点击对应的引脚会弹出PG11的复用功能选择,我们在这里选择复用为UART4_TX功能,如下图所示:
配置完成之后打开左侧菜单的 System CoreàGPIO 进入 GPIO 模式配置界面:如下图所示:
点击对应的引脚配置之后会弹出右下方的管脚配置界面,如上图所示:
在下方会列出要配置选项的具体说明和我们要进行的配置。
选项 GPIO Pull-up/Pull-down 用来设置 IO 口是上拉/下拉/没有上下拉。本实验我们设置为上拉(Pull-up)。
选项 GPIO mode用来设置 GPIO 口的模式,这里默认为alternate function,也就是复用功能。
选项 User Label 是用来设置初始化的 IO 口 Pin 值为我们自定义的宏,这里我们填写为 UART_RX。按照如上要求设置后的界面如下(由于PG11的配置相同,只是最后的Label值不同,也在下方列了出来):
而PG11和PB2的设置相似,只是多了出了一个选项:选项 Mzximum ouput speed 用来设置 IO 口输出速度为低速(Low)/中速(Medium)/高速 (Hign)/快速(Very High)。我们设置为高速Very High 。
设置完成之后在左侧菜单找到Connectivity下的UART4,打开之后首先选中Cortex-M4,然后配置Mode为Asynchronous(也就是异步通信模式),配置完成之后如下如图所示:
然后进入NVIC Settings功能设置界面,使能UART4 global interrupt,配置完成如下图所示:
33.4.1.2 DMA配置
配置完成之后继续打开左侧菜单的 System Core,选择DMA,进入模式配置界面:如下图所示:我们配置 M4 内核使用 DMA2(默认配置就已经配置了),从 DMA 配置处可以看到,而 DMA1给A7 内核(Cortex-A7 non secure)使用的,DMA和DMA2 可以单独给 M4 或者 A7 内核使用,配置完成如下:
在相同界面下,来到Configuration配置界面,选择DMA2,点击下方的add按钮,如下图所示:
将串口的收发引脚加入DMA请求,数据流(Stream)和传输方向(Direction)不需要去进行修改,软件已经自动为我们配置好,而Priority需要设置为中速(Medium),配置完成如下图所示:
33.4.1.3时钟配置
NVIC配置完成之后在左侧菜单找到System Core下的RCC,选择打开 HSE(也就是使用外部高速时钟),并选择Crystal/Ceramic Resonator 晶体/陶瓷谐振器选项。
选择HSE(高速外部时钟)之后,打开时钟设置界面,首先将PLL3 Source Mux 设置为HSE,然后设置MCU Clock Mux为PLL3P,设置完成之后在对应的功能框之中手动输入 209 以后按下回车键,STM32CubeMX 插件会自动计算分频和倍频系数,然后在APB1DIV、APB2DIV、APB3DIV处输入 2,因为 APB1、APB2、APB3时钟最大值只能为 104.5MHz,所以我们要手动设置分频值,设置完成之后如下图所示:
至此我们的基本设置就完成了,最后需要在Project Manage下的Code Generator选项下勾选 Generate peripheral initialization as a pair of ".c/.h' files per peripheral 选项,这样可以独立生成对应外设的初始化.h 和.c 文件(方便配置的查看),如下图所示:
33.4.2工程的生成与完善
在上述的步骤完成之后,按下键盘的“Ctrl+S”组合键保存保存 UART.ioc 文件,系统开始生成初始化代码,工程生成之后如下图所示:
然后我们进行工程的完善,以及添加对应的逻辑代码。
33.4.2.1 uart.h文件的完善
首先打开uart.h文件,文件存放位置如下图所示:
进入uart.h文件后在/* USER CODE BEGIN Private defines */和/* USER CODE END Private defines */之间添加定义
#define rx_max 1024
extern uint8_t rx_flag;
extern uint16_t rx_len;
extern uint8_t rx_buf[rx_max];
其中rx_max代表接收数据量的最大值,rx_flag代表接收完成的标志,rx_len代表接收的数据长度,rx_buf代表接收数据缓冲区。添加完成如下图所示:
33.4.2.2 uart.c文件的完善
首先打开uart.c文件,文件存放位置如下图所示:
进入uart.c文件后在/* USER CODE BEGIN 0 */和/* USER CODE END 0 */之间添加内容如下:
uint8_t rx_flag = 0;
uint16_t rx_len = 0;
uint8_t rx_buf[rx_max] = {0};
添加完成之后如下图所示:
33.4.2.3 stm32mp1xx_it.c文件的完善
首先打开 stm32mp1xx_it.c文件,文件存放位置如下图所示:
进入 stm32mp1xx_it.c文件后在/* USER CODE BEGIN Includes */和/* USER CODE END Includes */之间添加内容如下:
#include "usart.h"
添加完成如下图所示:
然后在 /*USER CODE BEGIN UART4_IRQn 0*/和/* USER CODE END UART4_IRQn 0*/ 之间添加内容如下:
uint32_t temp;
if((__HAL_UART_GET_FLAG(&huart4,UART_FLAG_IDLE) != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart4);
HAL_UART_DMAStop(&huart4);
temp = huart4.hdmarx->Instance->NDTR;
rx_len = rx_max - temp;
rx_flag=1;
HAL_UART_Receive_DMA(&huart4,rx_buf,rx_max);
}
添加完成如下图所示:
打开main.c文件,在 /* USER CODE BEGIN 2 */和/* USER CODE END 2 */之间添加以下内容:
HAL_UART_Receive_DMA(&huart4, rx_buf, rx_max);
__HAL_UART_ENABLE_IT(&huart4,UART_IT_IDLE);
使能DMA串口接收并使能串口中断,添加完成如下图所示:
在/* USER CODE BEGIN 4 */和/* USER CODE END 4 */之间添加以下内容:
if(rx_flag)
{
rx_flag=0;
HAL_UART_Transmit_DMA(&huart4, rx_buf, rx_len);
}
添加部分的内容,表示串口接收数据完成之后,再将接收到的数据发送出去,以实现串口数据的回显,方便观察。
33.4.4工程的编译
在完成以上步骤之后我们点击工具栏的小锤子进行编译,编译图标如下图所示:
编译完成会在下方的终端中显示打印信息,如下图所示:
如果报错,需要自己根据错误的提示信息来进行问题的寻找和改正。
本例程实现的最终功能是,发送一个字符串,会将发送的字符串打印出来,如下图所示:
33.4.5工程的调试
由于STM32MP157的裸机部分和一般的单片机有些区别,他没有内部的存储,所以只能在程序编译成功之后,通过debug的方式来进行调试(将程序放在内存之中),调试过程如下:
首先,点击菜单栏中的小甲虫Debug调试按钮,弹出以下界面,
在弹出来的界面,按步骤,选择响应的属性(该步骤为Jlink的步骤,如果是STLink,调试探头选择对应的即可)。如下图所示:
选择完成之后,点击右下角的Debug按钮,点击之后,会进行再一次的编译,编译完成之后会弹出如下内容(作者用的是J-LinK),这里弹出的是J-link关于设备的选择,不同调试器的弹窗可能会不同
在弹出来的界面中,选择Accept接受,会弹出以下内容,继续点击下方的OK。
之后会来到设备选择界面,我们选择Cortex-M4,如下图所示:
选择Cortex-M4之后,点击右下角的OK,会弹出以下界面,选择右下角Switch.
然后会弹出一个新的页面,选择菜单栏的 resume按钮开始调试。
连接好串口之后,打开串口调试助手,当我们发送一串字符时,开发板会返还我们相同的字符。
如果想关闭调试,则点击菜单栏的终止按钮即可。