首先介绍我的DMA使用场景“电阻ADC转化”。
从外界读取ADC的值,然后让DMA进行搬运至自己定义的数组当中,之后读取这个数据就可以了。
先介绍一个我的ADC结构体,也就是句柄(到时候对ADC的所有控制,就是操作这个句柄)
/**
* @brief ADC handle Structure definition
*/
typedef struct __ADC_HandleTypeDef
{
ADC_TypeDef *Instance; /*! < ADC实例 > */
ADC_InitTypeDef Init; /*! < 配置ADC初始化参数 > */
DMA_HandleTypeDef *DMA_Handle; /*! < 直接内存访问(DMA)操作的句柄结构 > */
HAL_LockTypeDef Lock; /*! < 锁的状态 > */
__IO uint32_t State; /*! < 表示ADC状态 > */
__IO uint32_t ErrorCode; /*! < 存储ADC错误代码的32位寄存器 > */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
void (* ConvCpltCallback)(struct __ADC_HandleTypeDef *hadc); /*!< ADC conversion complete callback */
void (* ConvHalfCpltCallback)(struct __ADC_HandleTypeDef *hadc); /*!< ADC conversion DMA half-transfer callback */
void (* LevelOutOfWindowCallback)(struct __ADC_HandleTypeDef *hadc); /*!< ADC analog watchdog 1 callback */
void (* ErrorCallback)(struct __ADC_HandleTypeDef *hadc); /*!< ADC error callback */
void (* InjectedConvCpltCallback)(struct __ADC_HandleTypeDef *hadc); /*!< ADC group injected conversion complete callback */ /*!< ADC end of sampling callback */
void (* MspInitCallback)(struct __ADC_HandleTypeDef *hadc); /*!< ADC Msp Init callback */
void (* MspDeInitCallback)(struct __ADC_HandleTypeDef *hadc); /*!< ADC Msp DeInit callback */
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
}ADC_HandleTypeDef;
上面的代码中是ADC的结构体句柄,我简单写了注释(为什么简单?因为你别管,咱们重点是DMA)。
除了下面的这个元素(这TM是重点,下面仔细介绍,叫我懂王就好)
DMA_HandleTypeDef *DMA_Handle; /*! < 直接内存访问(DMA)操作的句柄结构 > */
如果你要问我,下面的感觉像函数一样的东西为什么没有中文注释,那我可以回答你:
那玩意我也不懂,所以你也可以不用管,完全不影响咱们的DMA讲解!懂了吗!
懂了就好,不用谢,叫我 “装-B-王”;
---------------------------------------------------------我去你的分割线---------------------------------------------
现在我们来看我说的DMA的结构体
typedef struct __DMA_HandleTypeDef
{
DMA_Channel_TypeDef *Instance; /*! < 指向 DMA 通道的寄存器基址 > */
DMA_InitTypeDef Init; /*! < 初始化 DMA 通道的配置参数 > */
HAL_LockTypeDef Lock; /*! < 锁定 DMA 通道 > */
HAL_DMA_StateTypeDef State; /*! < 当前 DMA 传输的状态 > */
void *Parent; /*! < 指向父对象的状态 > */
void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*! < 回调函数指针,当 DMA 传输完成时会被调用 > */
void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*! < 回调函数指针,当 DMA 传输完成一半时会被调用 > */
void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma); /*! < 回调函数指针,当 DMA 传输发生错误时会被调用 > */
void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma); /*! < 回调函数指针,当 DMA 传输被中止时会被调用 > */
__IO uint32_t ErrorCode; /*! < 存储 DMA 传输的错误代码 > */
DMA_TypeDef *DmaBaseAddress; /*! < 指向 DMA 控制器的基址 > */
uint32_t ChannelIndex; /*! < 表示 DMA 通道的索引 > */
} DMA_HandleTypeDef;
这个结构体就是需要你配置的东西,就是DMA的句柄,到时候所有的操作就是要操作这个结构体的对象(不是男女对象,想什么呢,注意点),对象不用我教你怎么定义了吧(结构体名 xx),别定义什么稀奇古怪的名字,求你了。
现在来详细介绍每一个元素:
(我靠,怎么就2个元素,其他的被我吃了,其他的不用配,最后直接初始化,tm的最后报错我把电脑吃了)
DMA_Channel_TypeDef *Instance; 这个元素就是(DMA1 还是 DMA2)
DMA_InitTypeDef Init; (一个结构体),下面是他的细分(去他的细分,模糊求生模式)
typedef struct
{
uint32_t Direction; /*!< Specifies if the data will be transferred from memory to peripheral,
from memory to memory or from peripheral to memory.
This parameter can be a value of @ref DMA_Data_transfer_direction */
uint32_t PeriphInc; /*!< Specifies whether the Peripheral address register should be incremented or not.
This parameter can be a value of @ref DMA_Peripheral_incremented_mode */
uint32_t MemInc; /*!< Specifies whether the memory address register should be incremented or not.
This parameter can be a value of @ref DMA_Memory_incremented_mode */
uint32_t PeriphDataAlignment; /*!< Specifies the Peripheral data width.
This parameter can be a value of @ref DMA_Peripheral_data_size */
uint32_t MemDataAlignment; /*!< Specifies the Memory data width.
This parameter can be a value of @ref DMA_Memory_data_size */
uint32_t Mode; /*!< Specifies the operation mode of the DMAy Channelx.
This parameter can be a value of @ref DMA_mode
@note The circular buffer mode cannot be used if the memory-to-memory
data transfer is configured on the selected Channel */
uint32_t Priority; /*!< Specifies the software priority for the DMAy Channelx.
This parameter can be a value of @ref DMA_Priority_level */
}DMA_InitTypeDef;
你肯定想说:我靠,怎么那么多英文,老Z要看中文,不要急,下面我就给你讲中文,Hold住
__HAL_RCC_DMA1_CLK_ENABLE(); //开启DMA1的时钟
dma1_handle.Instance = DMA1_Channel1; //使用的DMA通道1
dma1_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; //外存到内设
dma1_handle.Init.PeriphInc = DMA_PINC_DISABLE; //外设地址寄存器不递增--固定
dma1_handle.Init.MemInc = DMA_MINC_ENABLE; //内存地址寄存器自动递增,不递增的话若有新数据来就会直接在这个基础上直接覆盖
dma1_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //外设数据宽度--半字dma1_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; //内存数据宽度--半字
dma1_handle.Init.Mode = DMA_NORMAL ; //正常模式
dma1_handle.Init.Priority = DMA_PRIORITY_MEDIUM; //优先级--中等
HAL_DMA_Init(&dma1_handle);
__HAL_LINKDMA(&adc1_handle,DMA_Handle,dma1_handle); //ADC-DMA关联函数(三个参数分别为:adc句柄,adc结构体内dma名称,dma句柄)
上面的代码就是配置的具体过程,你只看结构体里面就是一坨大便,咱们具体来讲。
1. 要先开启时钟(时钟是什么破玩意?),就是钱,没钱咋改装车,你说是吧。
2.instance,就是选择DMA的通道,那么通道可以随便选择吗?你选1,选2,选到天王老子都没关系,但别重复哈。
3.Direction的情况:外设到内存,内存到外设(你就看这两种情况就行了)
4.PeriphInc:就是外设的地址变不变呢?大哥,你变了你还怎么读这个地方的ADC值,隔山打牛读吗,上面的DISABLE是啥意思不用我说了。
5.MemInc:是我内存的地址,也就是说我的数据来了放在哪里呢?如果你不ENABLE,好比你现在有100个水缸,一个水缸已经满了,你还要一直往里面注水(就是传数据),溢出来了大哥,你不自增我算你是条汉子
6.PeriphDataAlignment和MemDataAlignment,这两个就是数字大小,字节还是字,你自己选,但是要一致。
7.Mode:正常模式和循环模式,正常的情况DMA只搬一次,你打死他不会再搬,如果你的ADC一直在变,你想让他一直搬,那你就要用连续模式
8.Priority:这个随便你怎么弄,优先级而已
配置以后, HAL_DMA_Init(&dma1_handle);
这个语句才是真的初始化成功了,你创的不算,你是老大还是编译器是老大。
然后: __HAL_DMA_ENABLE(&dma1_handle); //只是使能DMA(没有运输)
这句话的意思是,我的DMA电源开了,还没开始搬哈,一个语句你还想干两件事,想得美