好长时间没有发表学习心得了,这段时间都在忙着开题的事情,实在是罪过罪过。这周算是正式吧开题搞定了,先发一篇f7的学习笔记,看看周末有没有时间吧开题有关的内容po一下。
今天就学习DMA和USART部分内容吧。
一、硬件接口
首先从硬件上了解F7discovery板子的提供给user的串口。
大家可以看一下dis板子的背面。这个通用的Arduino Uno revision 3 connectors是板子提供给大家的可用的外部接口,这里的D1和D0对应USART的发送和接受。如此,就搞清楚硬件在哪里啦。
由于使用的是HAL库,中间的艰辛难以言表,话不多说,还是继续学习过程吧。
二、DMA用户手册阅读
2.1、简介及特征
在了解HAL库有关函数前,先对用户手册相关模块做了解是必须的过程,打开用户手册,来到直接存储器访问控制器 (DMA)部分。
直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输,可以将CPU从数据移动中解放出来。F7的DMA有以下主要特征(就摘录了一部分):
双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问;
每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求);
每个数据流有四个字深的 32 位先进先出存储器缓冲区 (FIFO),可用于 FIFO 模式或直接模式:
– FIFO 模式:可通过软件将阈值级别选取为 FIFO 大小的 1/4、1/2 或 3/4
– 直接模式:每个 DMA 请求会立即启动对存储器的传输。当在直接模式(禁止 FIFO)下将DMA 请求配置为以存储器到外设模式传输数据时,DMA 仅会将一个数据从存储器预加载到内部 FIFO,从而确保一旦外设触发 DMA 请求时则立即传输数据。
通过硬件可以将每个数据流配置为:
– 支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
– 在存储器端支持双缓冲的双缓冲区通道
可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设发起 DMA
请求
……
2.2、传输过程
相关特诊了解后,来学习一下DMA的传输过程,每个DMA传输包含三项操作,首先是从外设数据寄存器或者存储器单元中加载数据,接下来是将加载的数据存储到外设数据寄存器或存储器单元中,最后一步是事物数计数器递减一。
总的来说,一旦发生了事件,外设就向控制器请求传输,控制器根据通道优先级来处理传输。只要DMA 控制器访问外设,DMA 控制器就会向外设发送确认信号。外设获得 DMA 控
制器的确认信号后,便会立即释放其请求。一旦外设使请求失效,DMA 控制器就会释放确
认信号。如果有更多请求,外设可以启动下一个事务。
2.3、通道选择
上面有说道,每个数据流对应的是一个DMA传输请求,而每一个数据流可以选择八个请求(即八个通道)中的一个:
2.4、数据流
每个数据流是一个source到definition的单向传输链路,配置后可以执行两种事务,第一种是常规类型的事务:存储器到外设或者外设到存储器或者存储器到存储器。第二种是双缓冲区类型的事务。每次传输的数据量最多有65535。
传输方向使用 DMA_SxCR 寄存器中的 DIR[1:0] 位进行配置,有三种可能的传输方向:存储器到外设、外设到存储器或存储器到存储器。
2.5、可能的配置
三、DMA的HAL库手册阅读与实例解读
3.1、一般驱动Generic Driver
3.1.1、DMA Firmware driver registers structures
这里参考的实例是dma运用到usart发送的例子。
首先手册介绍的是DMA_InitTypeDef
给出一段实例,这个结构体包含以下变量:
Data Fields
uint32_t Channel
uint32_t Direction
uint32_t PeriphInc
uint32_t MemInc
uint32_t PeriphDataAlignment
uint32_t MemDataAlignment
uint32_t Mode
uint32_t Priority
uint32_t FIFOMode
uint32_t FIFOThreshold
uint32_t MemBurst
uint32_t PeriphBurst
相关变量的功能可以顾名思义理解,第一个channel是选择通道,一共有八中,例子里的实际上是DMA_CHANNEL_5;第二个deriction是传输方向,一共有三种,例子里的实际是DMA_MEMORY_TO_PERIPH即存储器到外设;第三个periphinc是为了配置外设地址寄存器是否要自动增长,对应的是用户手册8.3.7 指针递增的内容,这里配置为disable实际上就是指针保持常量,也即每次传递的数据都是同一destination。第四个类似是配置存储器,设为enable是为了每次传递的数据的source自动转到下一个;第五个PeriphDataAlignment
外设数据长度的调整,有三种分别是字节,字和半字,例子里面配置为字节。第六个类似是配置存储器;第七个配置传输模式,对应用户手册8.3.5 DMA 数据流的内容,配置为常规类型;第八个优先级配置是最低。后面几个没有使用到就不做介绍了。
HAL_DMA_Init(&hdma_tx);最后使用这个函数来完成初始化。
需要注意的是后面有这么一个函数,这个函数的目的是为了将dma的handle和uart的handle起一个结合的的作用,怎么说呢,就比如说dma的handle中有这么个
这里通过这个宏定义就可以实现这个指针和串口接受缓存的指针的连接。
接下来是__DMA_HandleTypeDef。
这个handle包括以下Data Fields
DMA_Stream_TypeDef * Instance
DMA_InitTypeDef Init
HAL_LockTypeDef Lock
__IO HAL_DMA_StateTypeDef State
void * Parent
void(* XferCpltCallback
void(* XferHalfCpltCallback
void(* XferM1CpltCallback
void(* XferErrorCallback
__IO uint32_t ErrorCode
我是这么理解的,在HAL库中,针对每一个要处理的对象都定义了对应的handle,比如这里的dma的handle以及后面要提到的usart的handle。下面来理解一下这个DMA的handle,第一个是instance实例,是寄存器基地址的配置,例子里这里设置为DMA2_Stream6;第二个就是上面那段介绍的初始化的结构体;第三个和第四个和第五个都是一些state的标志;需要注意的是下面的一些callback指针。首先要说明一个约定,STM32CubeMX使用的是新的HAL库,HAL库对中断及事件的处理采用的是所谓回调机制。也就是说中断程序的框架已做好,且不能修改。那么如何加入用户代码呢,就是通过所谓的回调函数来实现的。这里的一些callback就是回调函数,比如说XferCpltCallback,这个函数就是dma传输完成的的回调函数,就是说,如果用户重写了这个函数,那么在dma传输完成后,会自动调用用户的重写函数。
那么这个函数是在哪里的呢,就是在HAL库写的dma的中断中。
到这里大家应该了解到HAL库的一点区别了,以往在标准库中需要自己写的中断之类乱七八糟的东西,HAL库中都给封装好了,留给用户的就是回调函数,只需要在回调函数中加入自己的内容,就可以实现有关功能了。
3.1.2、DMA Firmware driver API description
那么,我们在了解了以上的知识后,要怎么使用库留给我们的API接口呢?
首先使能和配置外设使得他们连接到DMA数据流上面(在接下来的USART手册和例子阅读里面会有),然后对于相对应的数据流,需要配置以下内容:传输方向、源和目的地的数据格式、是否循环、normal或者perpheral flow control模式、还有优先级、是否增长等等上面提过的内容
在配置完成后,有两种操作来实现数据的传输。
第一种是polling,这里要使用HAL_DMA_Start()函数来开始传输数据。
第二种就是中断了。我们给出中断的例子:
首先使用HAL_NVIC_SetPriority()来配置优先级,其次通过HAL_NVIC_EnableIRQ() 来使能中断,然后使用HAL_DMA_Start_IT() 来开始传输,实际上,HAL库中的HAL_UART_Transmit_DMA等函数都调用了这个函数,所以根据相关使用dma的外设来调用其他函数来实现对HAL_DMA_Start_IT()的调用。
在中断函数中直接调用HAL库的中断处理函数,如果需要加入自己的功能,要重写回调函数。
四、usart用户手册阅读
这里不做介绍了,因为和F1和其他系列的串口在理解上差别不大。(我也没有去理解差别大的部分。。。。)
五、usart的HAL库手册阅读与实例解读
5.1、一般驱动Generic Driver
首先是USART_InitTypeDef
包含以下Data Fields
uint32_t BaudRate
uint32_t WordLength
uint32_t StopBits
uint32_t Parity
uint32_t Mode
uint32_t OverSampling
uint32_t CLKPolarity
uint32_t CLKPhase
uint32_t CLKLastBit
很好理解的应该,波特率、字长、停止位、优先级、模式、过采样等等。
下面给出一段例子:
就完成了配置。
同样需要注意的是对于USART也有对应的handle。
USART_HandleTypeDef
Data Fields
USART_TypeDef * Instance
USART_InitTypeDef Init
uint8_t * pTxBuffPtr
uint16_t TxXferSize
uint16_t TxXferCount
uint8_t * pRxBuffPtr
uint16_t RxXferSize
uint16_t RxXferCount
uint16_t Mask
DMA_HandleTypeDef * hdmatx
DMA_HandleTypeDef * hdmarx
HAL_LockTypeDef Lock
HAL_USART_StateTypeDef State
__IO uint32_t ErrorCode
可以看到这里有接收和发送缓存的指针、大小、计数等的定义,实际上也就是HAL库都帮我们完成了原本所需要自己编写的内容,还是一句话,只需要重写回调函数就可以了,是不是相比标准库要好用的多呢。
5.1.2、USART Firmware driver API description
使用方法做一个简单介绍,首先申明USART_HandleTypeDefhandle。接下来初始化这个handle通过这个函数HAL_USART_MspInit 。初始化的内容包括以下部分:时钟、管脚、中断、DMA等;这个函数是需要用户自己写的哦,如果使用了stm32cubemx这款设计工具,这个函数是会自动生成的。
配置管脚的过程:现使能时钟、再配置上拉。
配置中断的过程:先配置优先级、使能中断,类似上面介绍的DMA,同样的中断函数HAL库也都帮你写好了,只需要重写回调函数就可以了,给出一个例子: