技巧小结:根据寄存器手册写常用外设的驱动程序

news2025/6/9 3:11:30

需求:根据STM32F103寄存器手册写DMA模块的驱动程序

一、分析标准库函数的写法:

  1. 各个外设的寄存器地址定义在stm32f10x.h文件中:此文件由芯片厂家提供;
  2. 内核的有关定义则定义在core_cm3.h文件中:ARM提供;
1、查看外设区域多级划分的基地址
一级地址划分

Flash地址、SRAM地址、外设地址、内核地址
查看数据手册的存储映像章节
在这里插入图片描述

在这里插入图片描述

二级地址划分

外设地址分为AHBAPB2APB1三个区域。

具体的对于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 0800GPIOA外设寄存器结束地址为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,那么只需将GPIOABSRR寄存器的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_ISRDMA_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_PeripheralDataSize
                                        or 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-memory
                                              data 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,如果将bit41,等价于
    #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,如果将bit61,等价于
    #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,如果将bit71,等价于
    #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,如果将bit51,等价于
    #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,如果将bit141,等价于
    #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;

三、总结

要分层写程序
  • 底层写一层结构体,确保能够直接访问寄存器
  • 用户层写一层结构体,只是为了方便使用和配置参数(尤其是按位使用的寄存器),最终还是要落到底层函数的调用和寄存器赋值

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

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

相关文章

设计模式(代理设计模式)

代理模式解释清楚&#xff0c;所以如果想对一个类进行功能上增强而又不改变原来的代码情况下&#xff0c;那么只需要让这个类代理类就是我们的顺丰&#xff0c;对吧?并行增强就可以了。具体增强什么?在哪方面增强由代理类进行决定。 代码实现就是使用代理对象代理相关的逻辑…

从代码学习深度强化学习 - 初探强化学习 PyTorch版

文章目录 前言强化学习的概念强化学习的环境强化学习中的数据强化学习的独特性总结前言 本文将带你初步了解强化学习 (Reinforcement Learning, RL) 的基本概念,并通过 PyTorch 实现一些简单的强化学习算法。强化学习是一种让智能体 (agent) 通过与环境 (environment) 的交互…

ELK日志管理框架介绍

在小铃铛的毕业设计中涉及到了ELK日志管理框架&#xff0c;在调研期间发现在中文中没有很好的对ELK框架进行介绍的文章&#xff0c;因此拟在本文中进行较为详细的实现的介绍。 理论知识 ELK 框架介绍 ELK 是一个流行的开源日志管理解决方案堆栈&#xff0c;由三个核心组件组…

【Linux】sed 命令详解及使用样例:流式文本编辑器

【Linux】sed 命令详解及使用样例&#xff1a;流式文本编辑器 引言 sed 是 Linux/Unix 系统中一个强大的流式文本编辑器&#xff0c;名称来源于 “Stream EDitor”&#xff08;流编辑器&#xff09;。它允许用户在不打开文件的情况下对文本进行筛选和转换&#xff0c;是命令行…

机器学习:聚类算法及实战案例

本文目录&#xff1a; 一、聚类算法介绍二、分类&#xff08;一&#xff09;根据聚类颗粒度分类&#xff08;二&#xff09;根据实现方法分类 三、聚类流程四、K值的确定—肘部法&#xff08;一&#xff09;SSE-误差平方和&#xff08;二&#xff09;肘部法确定 K 值 五、代码重…

【p2p、分布式,区块链笔记 MESH】 论文阅读 Thread/OpenThread Low-Power Wireless Multihop Net

paperauthorThread/OpenThread: A Compromise in Low-Power Wireless Multihop Network Architecture for the Internet of ThingsHyung-Sin Kim, Sam Kumar, and David E. Culler 目录 引言RPL 标准设计目标与架构设计选择与特性shortcomIngs of RPL设计选择的反面影响sImulta…

moon游戏服务器-demo运行

下载地址 https://github.com/sniper00/MoonDemo redis安装 Redis-x64-3.0.504.msi 服务器配置文件 D:\gitee\moon_server_demo\serverconf.lua 貌似不修改也可以的&#xff0c;redis不要设置密码 windows编译 安装VS2022 Community 下载premake5.exe放MoonDemo\server\moon 双…

Qt学习及使用_第1部分_认识Qt---学习目的及技术准备

前言 学以致用,通过QT框架的学习,一边实践,一边探索编程的方方面面. 参考书:<Qt 6 C开发指南>(以下称"本书") 标识说明:概念用粗体倾斜.重点内容用(加粗黑体)---重点内容(红字)---重点内容(加粗红字), 本书原话内容用深蓝色标识,比较重要的内容用加粗倾…

湖北理元理律师事务所:债务咨询中的心理支持技术应用

债务危机往往伴随心理崩溃。世界卫生组织研究显示&#xff0c;长期债务压力下抑郁症发病率提升2.3倍。湖北理元理律师事务所将心理干预技术融入法律咨询&#xff0c;构建“法律方案心理支持”的双轨服务模型。 一、债务压力下的心理危机图谱 通过对服务对象的追踪发现&#x…

阿里云域名怎么绑定

阿里云服务器绑定域名全攻略&#xff1a;一步步轻松实现网站“零”障碍上线&#xff01; 域名&#xff0c;您网站在云端的“身份证”&#xff01; 在数字化浪潮中&#xff0c;拥有一个属于自己的网站或应用&#xff0c;是个人展示、企业运营不可或缺的一环。而云服务器&#x…

能上Nature封面的idea!强化学习+卡尔曼滤波

2025深度学习发论文&模型涨点之——强化学习卡尔曼滤波 强化学习&#xff08;Reinforcement Learning, RL&#xff09;与卡尔曼滤波&#xff08;Kalman Filtering, KF&#xff09;的交叉研究已成为智能控制与状态估计领域的重要前沿方向。 强化学习通过试错机制优化决策策…

Markdown基础(1.2w字)

1. Markdown基础 这次就没目录了&#xff0c;因为md格式太乱了写示例&#xff0c;展示那些都太乱了&#xff0c;导致目录很乱。 &#xff08;我是XX&#xff0c;出现了很多错误&#xff0c;有错误和我说&#xff09; 1.1 Markdown简介 Markdown是一种轻量级的标记语言&#…

LabVIEW与PLC液压泵测控系统

针对液压泵性能测试场景&#xff0c;采用LabVIEW与西门子 PLC 控制系统&#xff0c;构建高精度、高可靠性的智能测控系统。通过选用西门子 PLC、NI 数据采集卡、施耐德变频电机等&#xff0c;结合LabVIEW 强大的数据处理与界面开发能力&#xff0c;实现液压泵压力、流量、转速等…

【HarmonyOS5】UIAbility组件生命周期详解:从创建到销毁的全景解析

⭐本期内容&#xff1a;【HarmonyOS5】UIAbility组件生命周期详解&#xff1a;从创建到销毁的全景解析 &#x1f3c6;系列专栏&#xff1a;鸿蒙HarmonyOS&#xff1a;探索未来智能生态新纪元 文章目录 前言生命周期全景图详细状态解析与最佳实践&#x1f3ac; Create状态&#…

c++ 静态成员变量

Student.h头文件内容&#xff1a; #pragma once #include <string> using namespace std;class Student { public:string name;int score;static int totalScore; // 静态局部变量声明Student(string name, int score);~Student();void print() const; };Student.cpp源文…

数据分析之OLTP vs OLAP

数据处理系统主要有两种基本方法&#xff1a;一种注重数据操作(增删查改)&#xff0c;另一种注重商业智能数据分析。 这两种系统是&#xff1a; 联机事务处理&#xff08;OLTP&#xff09; 联机分析处理&#xff08;OLAP&#xff09; Power BI专为与OLAP系统兼容而构建&…

dvwa5——File Upload

LOW 在dvwa里建一个testd2.php文件&#xff0c;写入一句话木马&#xff0c;密码password antsword连接 直接上传testd2.php文件&#xff0c;上传成功 MEDIUM 查看源码&#xff0c;发现这一关只能提交jpg和png格式的文件 把testd2.php的后缀改成jpg&#xff0c;上传时用bp抓包…

【优选算法】C++滑动窗口

1、长度最小的子数组 思路&#xff1a; class Solution { public:int minSubArrayLen(int target, vector<int>& nums) {// 滑动窗口// 1.left0,right0// 2.进窗口( nums[right])// 3.判断// 出窗口// (4.更新结果)// 总和大于等于 target 的长度最小的 子数组…

关于GitHub action云编译openwrt

特别声明:此教程仅你有成功离线编译的经验后,使用下列教程更佳 不建议没有任何成功经验的人进行云编译 1、准备工作 使用GitHub云编译模板 GitHub - jxjxcw/build_openwrt: 利用Actions在线云编译openwrt固件,适合官方源码,lede,lienol和immortalwrt源码,支持X86,电…

sql入门语句-案例

Sql入门 数据库、数据表、数据的关系介绍 数据库 用于存储和管理数据的仓库 一个库中可以包含多个数据表 数据表 数据库最重要的组成部分之一 它由纵向的列和横向的行组成(类似excel表格) 可以指定列名、数据类型、约束等 一个表中可以存储多条数据 数据 想要永久化存储…