技巧小结:根据寄存器手册写常用外设的驱动程序
需求:根据STM32F103寄存器手册写DMA模块的驱动程序
一、分析标准库函数的写法:
- 各个外设的寄存器地址定义在
stm32f10x.h
文件中:此文件由芯片厂家提供; - 内核的有关定义则定义在
core_cm3.h
文件中:ARM
提供;
1、查看外设区域多级划分的基地址
一级地址划分
Flash
地址、SRAM
地址、外设地址、内核地址
查看数据手册的存储映像章节
:
二级地址划分
外设地址分为AHB
、APB2
、APB1
三个区域。
具体的对于APB1、APB2、AHB
的划分则体现在参考手册存储器和总线架构章节的存储器映像小结
:
- 除了
SDIO
外设的地址例外,AHB
总线上的外设其余的都是以0x4000 0000 + 0x20000
为基地址
APB2
总线上的外设都是以0x4000 0000 + 0x10000
为基地址
APB1
总线上的外设都是以0x4000 0000 + 0x0
为基地址
三级地址划分
外设各个模块的基地址,上图即数据手册中的存储器映像图
。 列出了所用STM32F10xxx
中内置外设的起始地址,比如
GPIOA
外设模块挂在APB2
总线,以APB2地址(0x4001 0000)
为基地址,偏移地址则是0x0800
,则得到GPIOA的基地址为0x4001 0000+0x0800=0x4001 0800
,GPIOA
外设寄存器结束地址为0x4000 0BFF
。
2、代码实现上述外设区域的多级地址划分
一级地址:Flash、SRAM、外设
/*!< FLASH base address in the alias region */
#define FLASH_BASE ((uint32_t)0x08000000)
/*!< SRAM base address in the alias region */
#define SRAM_BASE ((uint32_t)0x20000000)
/*外设区域的基地址,地址需要定义为uint32_t */
#define PERIPH_BASE ((uint32_t)0x40000000)
/*!< 并不是所有F103都具有FSMC外设,这里是为了兼容有此模块的芯片 */
#define FSMC_R_BASE ((uint32_t)0xA0000000)
- 总线划分成4个大的区域
二级地址:APB1、APB2、AHB
/*APB1外设区域的基地址,地址需要定义为uint32_t */
#define APB1PERIPH_BASE PERIPH_BASE
/*APB2外设区域的基地址,地址需要定义为uint32_t */
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
/*AHB外设区域的基地址,地址需要定义为uint32_t */
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
- 采用基地址+偏移地址的形式得到各个地址的具体值
三级地址:具体到某个模块的基地址,以GPIO为例
/*GPIOA挂在APB2外设区域,因此基于APB2PERIPH_BASE进行偏移 */
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
- 得到每个外设模块的基地址
3、访问外设模块具体的每个寄存器地址
有一种方式是每个寄存器的地址都进行宏定义,然后使用指针访问地址
,实现访问寄存器的功能,但这种调用时不够方便,因此标准库函数里采用了结构体的形式访问寄存器
:
3.1 查看GPIOA有哪些寄存器
参考手册的GPIO
和AFIO
寄存器地址映象,说明GPIOA~GPIOG
的寄存器完全一样。
- 具体的每个寄存器的功能则见
参考手册8.2GPIO寄存器描述和8.3复用功能I/O和调试配置(AFIO)
。每个寄存器都是32位,一般都需要按字节或者按位访问
。
3.2 代码中按格式定义GPIOA的寄存器结构体
按照手册中的寄存器地址声明结构体模板
#define __IO volatile
typedef struct
{__IO uint32_t CRL;__IO uint32_t CRH;__IO uint32_t IDR;__IO uint32_t ODR;__IO uint32_t BSRR;__IO uint32_t BRR;__IO uint32_t LCKR;
} GPIO_TypeDef;
3.3 代码中按格将GPIOA基地址强转成GPIOA的寄存器结构体类型
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
这里并没有实体化结构体,而是在直接访问地址,只是和直接指针访问地址有点区别,更简洁一些,比如
GPIOA.CRL = 0x1;
展开后表示
((GPIO_TypeDef *) GPIOA_BASE).CRL = 0x1;
-
对于结构体而言,
.运算
就是表示在结构体变量的首地址基础上偏移一个偏移地址,从而到达成员变量的存放地址,这个偏移地址值则和结构体类型里的成员顺序有关。 -
那么这里,
GPIOA_BASE
表示的是结构体类型指针,也就是这个地址后面的数据的访问按照GPIO_TypeDef结构体模板
的形式即依次是成员变量CRL、CRH、IDR、ODR、BSRR、BRR、LCKR
,.CRL
意味着偏移地址是0x1
(由结构体模板GPIO_TypeDef
决定),也就是GPIOA_BASE+0x1
,这是CRL
成员存放的地址。
两种访问方式的对比:
如果是直接的指针访问
#define GPIOA_CRL (GPIOA_BASE + 0x1)
#define GPIOA_CRH (GPIOA_BASE + 0x2)
#define GPIOA_IDR (GPIOA_BASE + 0x3)
#define GPIOA_ODR (GPIOA_BASE + 0x4)
#define GPIOA_BSRR (GPIOA_BASE + 0x5)
#define GPIOA_BRR (GPIOA_BASE + 0x6)
#define GPIOA_LCKR (GPIOA_BASE + 0x7)
*(uint32_t*)GPIOA_CRL = 0x1;
*(uint32_t*)GPIOA_CRH = 0x1;
*(uint32_t*)GPIOA_IDR = 0x1;
*(uint32_t*)GPIOA_ODR = 0x1;
*(uint32_t*)GPIOA_BSRR = 0x1;
*(uint32_t*)GPIOA_BRR = 0x1;
*(uint32_t*)GPIOA_LCKR = 0x1;
如果是结构体访问
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
GPIOA.CRL = 0x1;
GPIOA.CRH = 0x1;
GPIOA.IDR = 0x1;
GPIOA.ODR = 0x1;
GPIOA.BSRR = 0x1;
GPIOA.BRR = 0x1;
GPIOA.LCKR = 0x1;
而且一般IDE都有代码提示功能,会自动提示结构体有哪些成员,就不用再逐个查找具体的寄存器了,所以这种结构体访问方式更简洁。
4、位运算掩码
此外,一般还会宏定义一些掩码方便操作寄存器,类似于这种
1、宏定义
#define BIT1_MASK ((uint32_t)(1<<1))
#define BITn_MASK(n) ((uint32_t)(1<<n))
2、使用时
GPIOA.CRL |= BIT1_MASK ;把CRL寄存器的bit1位置的值置1
GPIOA.CRL &= (~BIT1_MASK) ;把CRL寄存器的bit1位置的值置0
GPIOA.CRL ^= BIT1_MASK ;把CRL寄存器的bit1位置的值翻转
按位运算的口诀:
1、或1则置1
(或运算就是有一个为1就为1,因此用来置1)
1、或0则不变
(1|0
的结果是1,0|0
的结果是0,因此此位保持不变)
2、与0则置0
(与运算就是有一个为0就为0,因此用来置0)
2、与1则不变
(1&0
的结果是1,0&0
的结果是0,因此此位保持不变)
3、异或1则翻转
(异或运算就是1碰到1变成1,0碰到1变成1,因此用来翻转)
3、异或0则不变
(异或运算就是1碰到0变成1,0碰到0变成0,因此保持不变)
异或运算^
为:相同为0,不同为1;
5、位运算掩码的使用
以控制GPIO电平输出的函数GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
为例:
/*** @brief Sets the selected data port bits.* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.* @param GPIO_Pin: specifies the port bits to be written.* This parameter can be any combination of GPIO_Pin_x where x can be (0..15).* @retval None*/
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{/* Check the parameters */assert_param(IS_GPIO_ALL_PERIPH(GPIOx));assert_param(IS_GPIO_PIN(GPIO_Pin));GPIOx->BSRR = GPIO_Pin;
}
分析:
(1)GPIOx
:指定是哪一组GPIO
- 为
GPIO_TypeDef
类型:GPIOA~GPIOG
; - 如果参数是
GPIOA
,也就是把GPIOA
的基地址传递进来;
typedef struct
{__IO uint32_t CRL; //端口配置低寄存器(GPIOx_CRL) (x=A..E)__IO uint32_t CRH; //端口配置高寄存器(GPIOx_CRH) (x=A..E)__IO uint32_t IDR; //端口输入数据寄存器(GPIOx_IDR) (x=A..E)__IO uint32_t ODR; //端口输出数据寄存器(GPIOx_ODR) (x=A..E)__IO uint32_t BSRR;//端口位设置/清除寄存器(GPIOx_BSRR) (x=A..E)__IO uint32_t BRR; //端口位清除寄存器(GPIOx_BRR) (x=A..E)__IO uint32_t LCKR;//端口配置锁定寄存器(GPIOx_LCKR) (x=A..E)
} GPIO_TypeDef;
(2)GPIO_Pin
:指定GPIO引脚号
为uint16_t
类型,此参数用于指定引脚号,组号和引脚号共同确定是哪个具体引脚。结合GPIO寄存器
的特点,此参数需要赋值给对应低位GPIO寄存器从而控制寄存器的某一位的值,因此其需要设计成掩码
,即:
GPIOx->BSRR = GPIO_Pin;对应的使用例子:GPIO_SetBits(GPIOA,GPIO_Pin_5); //PB.5 输出高内部展开也就是:GPIOA->BSRR = GPIO_Pin_5;
那么GPIO_Pin_5就是赋值掩码
,需要用户定义:
/*!< Pin 5 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020)
这个其实也就是选择bit5
,等价于
#define GPIO_Pin_5 ((uint16_t)(1<<5))
查看参考手册里BSRR寄存器
的描述:如果想将GPIOA的5脚即PA5
的输出电平设置为1
,那么只需将GPIOA
的BSRR
寄存器的BS5
位即bit5
位设置为1
,剩余的其他位写0
即对其他引脚不产生影响。而函数GPIO_SetBits(GPIOA,GPIO_Pin_5);
就达到了这个目的:
(1)指定GPIOA
(2)指定BSRR
寄存器
(3)指定引脚的掩码值GPIO_Pin_5
。
二、DMA外设的驱动程序
1、一级基地址
/*!< Peripheral base address in the alias region */
#define PERIPH_BASE ((uint32_t)0x40000000)
2、二级基地址
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
3、三级基地址
#define DMA1_BASE (AHBPERIPH_BASE + 0x0000)
#define DMA1_Channel1_BASE (AHBPERIPH_BASE + 0x0008)
#define DMA1_Channel2_BASE (AHBPERIPH_BASE + 0x001C)
#define DMA1_Channel3_BASE (AHBPERIPH_BASE + 0x0030)
#define DMA1_Channel4_BASE (AHBPERIPH_BASE + 0x0044)
#define DMA1_Channel5_BASE (AHBPERIPH_BASE + 0x0058)
#define DMA1_Channel6_BASE (AHBPERIPH_BASE + 0x006C)
#define DMA1_Channel7_BASE (AHBPERIPH_BASE + 0x0080)
4、外设的结构体定义
- 前面的
DMA_ISR
和DMA_IFCR
寄存器是DMA
模块的中断管理
,适用于所有DMA通道。 - 后面的
DMA_CCR1、DMA_CCR2、DMA_CCR3、DMA_CCR4、DMA_CCR5、DMA_CCR6、DMA_CCR7
则是对应到每个DMA通道
的控制寄存器。 - 因此为了使用方便,
定义结构体时将两者分开
。
typedef struct
{__IO uint32_t ISR; //DMA中断状态寄存器(DMA_ISR)__IO uint32_t IFCR; //DMA中断标志清除寄存器(DMA_IFCR)
} DMA_TypeDef;typedef struct
{__IO uint32_t CCR; //DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)__IO uint32_t CNDTR;//DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)__IO uint32_t CPAR;//DMA通道x外设地址寄存器(DMA_CPARx)(x = 1…7)__IO uint32_t CMAR;//DMA通道x存储器地址寄存器(DMA_CMARx)(x = 1…7)
} DMA_Channel_TypeDef;
5、外设基地址强转为结构体类型
#define DMA1 ((DMA_TypeDef *) DMA1_BASE)
#define DMA1_Channel1 ((DMA_Channel_TypeDef *) DMA1_Channel1_BASE)
#define DMA1_Channel2 ((DMA_Channel_TypeDef *) DMA1_Channel2_BASE)
#define DMA1_Channel3 ((DMA_Channel_TypeDef *) DMA1_Channel3_BASE)
#define DMA1_Channel4 ((DMA_Channel_TypeDef *) DMA1_Channel4_BASE)
#define DMA1_Channel5 ((DMA_Channel_TypeDef *) DMA1_Channel5_BASE)
#define DMA1_Channel6 ((DMA_Channel_TypeDef *) DMA1_Channel6_BASE)
#define DMA1_Channel7 ((DMA_Channel_TypeDef *) DMA1_Channel7_BASE)
6、外设寄存器的访问
DMA1_Channel1.CCR |= (uint32_t)(1<<4); //DIR:数据传输方向
DMA1_Channel1.CCR |= (uint32_t)(1<<5); //CIRC:循环模式
DMA1_Channel1.CCR &= (uint32_t)(~(1<<6)); //PINC:外设地址增量模式
DMA1_Channel1.CCR |= (uint32_t)(1<<7); //MINC:存储器地址增量模式
DMA1_Channel1.CCR |= (uint32_t)(1<<8)); //PSIZE[1:0]:外设数据宽度
DMA1_Channel1.CCR |= (uint32_t)(1<<9));
DMA1_Channel1.CCR |= (uint32_t)(1<<14); //MEM2MEM:存储器到存储器模式
...
- 由于
很多寄存器都是按位配置
,如果不嫌麻烦,可以按照这种形式访问寄存器。 - 为了访问方便,标准库函数里定义了
用户层的DMA配置结构体
,然后在通过定义的函数将这些参数组合成最终的uint32_t
值写入到DMA寄存器
中,更加方便。
7、DMA用户层结构体和寄存器掩码
7.1 DMA用户层结构体的定义
-
这一层
结构体DMA_InitTypeDef
实际上已经是底层的上一层了即用户层,定义这个结构体是为了方便用户使用,因为很多寄存器都是按位操作的。 -
那么如果想将用户层设置的参数传递到寄存器,显然需要写一个函数进行传递,这个函数就是
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
,这个函数里会组合结构体DMA_InitTypeDef
的所有成员变量,得到一个可以直接写入到DMA_CCRx
寄存器的uint32_t
数,然后写入DMA_CCRx寄存器
,完成参数的传递和写入。
typedef struct
{uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */uint32_t DMA_MemoryBaseAddr; /*!< Specifies the memory base address for DMAy Channelx. */uint32_t DMA_DIR; /*!< Specifies if the peripheral is the source or destination.This parameter can be a value of @ref DMA_data_transfer_direction */uint32_t DMA_BufferSize; /*!< Specifies the buffer size, in data unit, of the specified Channel. The data unit is equal to the configuration set in DMA_PeripheralDataSizeor DMA_MemoryDataSize members depending in the transfer direction. */uint32_t DMA_PeripheralInc; /*!< Specifies whether the Peripheral address register is incremented or not.This parameter can be a value of @ref DMA_peripheral_incremented_mode */uint32_t DMA_MemoryInc; /*!< Specifies whether the memory address register is incremented or not.This parameter can be a value of @ref DMA_memory_incremented_mode */uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.This parameter can be a value of @ref DMA_peripheral_data_size */uint32_t DMA_MemoryDataSize; /*!< Specifies the Memory data width.This parameter can be a value of @ref DMA_memory_data_size */uint32_t DMA_Mode; /*!< Specifies the operation mode of the DMAy Channelx.This parameter can be a value of @ref DMA_circular_normal_mode.@note: The circular buffer mode cannot be used if the memory-to-memorydata transfer is configured on the selected Channel */uint32_t DMA_Priority; /*!< Specifies the software priority for the DMAy Channelx.This parameter can be a value of @ref DMA_priority_level */uint32_t DMA_M2M; /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;
- 可以先定一个一个
DMA_InitTypeDef
类型的结构体变量,即
DMA_InitTypeDef ST_DMA_Init;
- 然后这个结构体传给
DMA_StructInit()
函数,即
DMA_StructInit(&ST_DMA_Init);
通过此函数设置结构体的值即DMA
参数配置,然后还要想办法把配置好的参数即结构体变量写入到DMA
寄存器中。
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct)
{
/*-------------- Reset DMA init structure parameters values ------------------*//* Initialize the DMA_PeripheralBaseAddr member */DMA_InitStruct->DMA_PeripheralBaseAddr = 0;/* Initialize the DMA_MemoryBaseAddr member */DMA_InitStruct->DMA_MemoryBaseAddr = 0;/* Initialize the DMA_DIR member */DMA_InitStruct->DMA_DIR = DMA_DIR_PeripheralSRC;/* Initialize the DMA_BufferSize member */DMA_InitStruct->DMA_BufferSize = 0;/* Initialize the DMA_PeripheralInc member */DMA_InitStruct->DMA_PeripheralInc = DMA_PeripheralInc_Disable;/* Initialize the DMA_MemoryInc member */DMA_InitStruct->DMA_MemoryInc = DMA_MemoryInc_Disable;/* Initialize the DMA_PeripheralDataSize member */DMA_InitStruct->DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;/* Initialize the DMA_MemoryDataSize member */DMA_InitStruct->DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;/* Initialize the DMA_Mode member */DMA_InitStruct->DMA_Mode = DMA_Mode_Normal;/* Initialize the DMA_Priority member */DMA_InitStruct->DMA_Priority = DMA_Priority_Low;/* Initialize the DMA_M2M member */DMA_InitStruct->DMA_M2M = DMA_M2M_Disable;
}
- 以上赋值号的右边是用户定义的宏定义,表示操作掩码或者目标值。
7.2 DMA用户层结构体的值传递给DMA寄存器
/*** @brief Initializes the DMAy Channelx according to the specified* parameters in the DMA_InitStruct.* @param DMAy_Channelx: where y can be 1 or 2 to select the DMA and * x can be 1 to 7 for DMA1 and 1 to 5 for DMA2 to select the DMA Channel.* @param DMA_InitStruct: pointer to a DMA_InitTypeDef structure that* contains the configuration information for the specified DMA Channel.* @retval None*/
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
{uint32_t tmpreg = 0;/* Check the parameters */assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));assert_param(IS_DMA_DIR(DMA_InitStruct->DMA_DIR));assert_param(IS_DMA_BUFFER_SIZE(DMA_InitStruct->DMA_BufferSize));assert_param(IS_DMA_PERIPHERAL_INC_STATE(DMA_InitStruct->DMA_PeripheralInc));assert_param(IS_DMA_MEMORY_INC_STATE(DMA_InitStruct->DMA_MemoryInc)); assert_param(IS_DMA_PERIPHERAL_DATA_SIZE(DMA_InitStruct->DMA_PeripheralDataSize));assert_param(IS_DMA_MEMORY_DATA_SIZE(DMA_InitStruct->DMA_MemoryDataSize));assert_param(IS_DMA_MODE(DMA_InitStruct->DMA_Mode));assert_param(IS_DMA_PRIORITY(DMA_InitStruct->DMA_Priority));assert_param(IS_DMA_M2M_STATE(DMA_InitStruct->DMA_M2M));/*--------------------------- DMAy Channelx CCR Configuration -----------------*//* Get the DMAy_Channelx CCR value */tmpreg = DMAy_Channelx->CCR; /* Clear MEM2MEM, PL, MSIZE, PSIZE, MINC, PINC, CIRC and DIR bits */tmpreg &= CCR_CLEAR_Mask;/* Configure DMAy Channelx: data transfer, data size, priority level and mode *//* Set DIR bit according to DMA_DIR value *//* Set CIRC bit according to DMA_Mode value *//* Set PINC bit according to DMA_PeripheralInc value *//* Set MINC bit according to DMA_MemoryInc value *//* Set PSIZE bits according to DMA_PeripheralDataSize value *//* Set MSIZE bits according to DMA_MemoryDataSize value *//* Set PL bits according to DMA_Priority value *//* Set the MEM2MEM bit according to DMA_M2M value */tmpreg |= DMA_InitStruct->DMA_DIR | DMA_InitStruct->DMA_Mode |DMA_InitStruct->DMA_PeripheralInc | DMA_InitStruct->DMA_MemoryInc |DMA_InitStruct->DMA_PeripheralDataSize | DMA_InitStruct->DMA_MemoryDataSize |DMA_InitStruct->DMA_Priority | DMA_InitStruct->DMA_M2M; //按位组合参数得到最终参数/* Write to DMAy Channelx CCR */DMAy_Channelx->CCR = tmpreg;/*--------------------------- DMAy Channelx CNDTR Configuration ---------------*//* Write to DMAy Channelx CNDTR */DMAy_Channelx->CNDTR = DMA_InitStruct->DMA_BufferSize;/*--------------------------- DMAy Channelx CPAR Configuration ----------------*//* Write to DMAy Channelx CPAR */DMAy_Channelx->CPAR = DMA_InitStruct->DMA_PeripheralBaseAddr;/*--------------------------- DMAy Channelx CMAR Configuration ----------------*//* Write to DMAy Channelx CMAR */DMAy_Channelx->CMAR = DMA_InitStruct->DMA_MemoryBaseAddr;
}
- 用这个函数
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
传递上面设置的结构体参数,由于这个寄存器都是按位设置,因此上面结构体参数实际上要按位组合后才能得到完整的寄存器数据,也就是在这个函数里进行了组合,组合的代码语句:
tmpreg |= DMA_InitStruct->DMA_DIR | DMA_InitStruct->DMA_Mode |DMA_InitStruct->DMA_PeripheralInc | DMA_InitStruct->DMA_MemoryInc |DMA_InitStruct->DMA_PeripheralDataSize | DMA_InitStruct->DMA_MemoryDataSize |DMA_InitStruct->DMA_Priority | DMA_InitStruct->DMA_M2M; //按位组合参数得到最终参数
- 然后将组合结果赋值给寄存器
DMA通道x
配置寄存器(DMA_CCRx)(x = 1…7)
/* Write to DMAy Channelx CCR */DMAy_Channelx->CCR = tmpreg;
7.3 外设寄存器掩码
(1)DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)
/** @defgroup DMA_data_transfer_direction * @{*/
#define DMA_DIR_PeripheralDST ((uint32_t)0x00000010)
#define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000)
- DIR:数据传输方向,对应
bit4
,如果将bit4
置1
,等价于
#define DMA_DIR_PeripheralDST ((uint32_t)(1<<4))
/** @defgroup DMA_peripheral_incremented_mode * @{*/
#define DMA_PeripheralInc_Enable ((uint32_t)0x00000040)
#define DMA_PeripheralInc_Disable ((uint32_t)0x00000000)
- PINC:外设地址增量模式 (Peripheral increment mode),对应
bit6
,如果将bit6
置1
,等价于
#define DMA_PeripheralInc_Enable ((uint32_t)(1<<6))
/** @defgroup DMA_memory_incremented_mode * @{*/
#define DMA_MemoryInc_Enable ((uint32_t)0x00000080)
#define DMA_MemoryInc_Disable ((uint32_t)0x00000000)
- MINC:存储器地址增量模式 (Memory increment mode),对应
bit7
,如果将bit7
置1
,等价于
#define DMA_MemoryInc_Enable ((uint32_t)(1<<7))
/** @defgroup DMA_peripheral_data_size * @{*/
#define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000)
#define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000100)
#define DMA_PeripheralDataSize_Word ((uint32_t)0x00000200)
- PSIZE[1:0]:外设数据宽度 (Peripheral size),对应
bit8-9
,如果将bit8-9
置位,这些位由软件设置和清除:
00:8位
01:16位
10:32位
11:保留
/** @defgroup DMA_memory_data_size * @{*/
#define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000)
#define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400)
#define DMA_MemoryDataSize_Word ((uint32_t)0x00000800)
- MSIZE[1:0]:存储器数据宽度 (Memory size),对应
bit10-11
,如果将bit10-11
置位,这些位由软件设置和清除:
00:8位
01:16位
10:32位
11:保留
/** @defgroup DMA_circular_normal_mode * @{*/
#define DMA_Mode_Circular ((uint32_t)0x00000020)
#define DMA_Mode_Normal ((uint32_t)0x00000000)
- CIRC:循环模式 (Circular mode),对应
bit5
,如果将bit5
置1
,等价于
#define DMA_Mode_Circular ((uint32_t)(1<<5))
/** @defgroup DMA_priority_level * @{*/
#define DMA_Priority_VeryHigh ((uint32_t)0x00003000)
#define DMA_Priority_High ((uint32_t)0x00002000)
#define DMA_Priority_Medium ((uint32_t)0x00001000)
#define DMA_Priority_Low ((uint32_t)0x00000000)
- PL[1:0]:通道优先级 (Channel priority level),对应
bit12-13
,如果将bit12-13
置位,这些位由软件设置和清除:
00:低
01:中
10:高
11:最高
/** @defgroup DMA_memory_to_memory * @{*/
#define DMA_M2M_Enable ((uint32_t)0x00004000)
#define DMA_M2M_Disable ((uint32_t)0x00000000)
- MEM2MEM:存储器到存储器模式 (Memory to memory mode),对应
bit14
,如果将bit14
置1
,等价于
#define DMA_Mode_Circular ((uint32_t)(1<<14))
(2)DMA通道x其他配置寄存器
这部分都是按字节访问,也就无需定义用户结构体(只不过是把要设置的值都放到了用户结构体里,统一管理比较方便,而不是像CCR那样按位拆分后再组合成一个最终的uint32_t),而是直接调用底层的DMA结构体进行访问即可
/*--------------------------- DMAy Channelx CNDTR Configuration ---------------*//* Write to DMAy Channelx CNDTR */DMAy_Channelx->CNDTR = DMA_InitStruct->DMA_BufferSize;/*--------------------------- DMAy Channelx CPAR Configuration ----------------*//* Write to DMAy Channelx CPAR */DMAy_Channelx->CPAR = DMA_InitStruct->DMA_PeripheralBaseAddr;/*--------------------------- DMAy Channelx CMAR Configuration ----------------*//* Write to DMAy Channelx CMAR */DMAy_Channelx->CMAR = DMA_InitStruct->DMA_MemoryBaseAddr;
三、总结
要分层写程序
- 底层写一层结构体,确保能够直接访问寄存器
- 用户层写一层结构体,
只是为了方便使用和配置参数(尤其是按位使用的寄存器)
,最终还是要落到底层函数的调用和寄存器赋值