请选择 进入手机版 | 继续访问电脑版

你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32入门系列-使用C语言封装寄存器

[复制链接]
STMCU小助手 发布时间:2022-7-2 14:00
  前面介绍了存储器映射、寄存器和寄存器映射,这些都是为了介绍使用 C语言封装寄存器做铺垫。这里我们通过一个实例来对 C 语言封装寄存器进行介绍。
    具体实例:控制 GPIOC 端口的第 0 管脚输出一个低电平。首先我们需要知道GPIOC 端口外设是挂接在哪个总线上的,然后根据总线基地址和本身的偏移地址得到 GPIOC 外设基地址,最后通过这个外设基地址得到里面各种寄存器基地址。
总线和外设基地址封装
    根据寄存器的概念,我们可以使用 C 语言中的宏定义对寄存器进行定义。具体代码如下:
    //定义外设基地址
    #define PERIPH_BASE ((unsigned int)0x40000000) 1)
    //定义 APB2 总线基地址
    #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) 2)
    //定义 GPIOC 外设基地址
    #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800) 3)
    //定义寄存器基地址 这里以 GPIOC 为例
    #define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00) 4)
    #define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04)
    #define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08)
    #define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C)
    #define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10)
    #define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14)
    #define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18)
    上述代码中我们在后面备注了数字,下面对其进行简单介绍下其功能:
  • 定义外设的基地址,这个地址也是 Block2 的基地址。
  • 定义 APB2 总线基地址,因为 Block2 的第一个总线是 APB1,而 APB2 总线地址只需要加上对应的地址偏移量即可。
  • 定义 GPIO 外设基地址,因为 GPIOC 是挂接在 APB2 总线上的,所以找到对应的端口地址偏移量即可知道 GPIOC 端口基地址。
  • 定义 GPIO 外设寄存器基地址,这里以 GPIOC 端口为例,因GPIOC_CRL是 GPIOC 外设的第一个寄存器,所以基地址就是 GPIOC 地址,其他寄存器地址只需要在 GPIOC 基地址上加上相应的偏移量即可。

    6 V+ y+ a* c3 M$ l( J
    我们得到了寄存器具体的地址,那么就可以使用 C 语言指针来操作读写。例如我们需要 GPIOC0 输出一个低电平或者高电平,可以使用下面语句来操作。
    //控制 GPIOC 第 0 管脚输出一个低电平
    GPIOC_BSRR = (0x01<<(16+0));
    //控制 GPIOC 第 0 管脚输出一个高电平
    GPIOC_BSRR = (0x01<<0);
    我们知道 GPIOC_BSRR 的值是这个寄存器的地址,但是编译器不知道它是地址,而是把它当做立即数,所以我们必须要强制转换为(unsigned int *)指针类型才可以对其操作,这一点特别要注意。然后再在前面加上一个“*”作取指针操作,表示对该地址内内容进行写,读操作也同样使用“*”取指针操作。如下:
    unsigned int temp;
    temp =GPIOC_IDR;
    将寄存器内的数据保存在变量 temp 中,使用到变量时一定要进行定义。
寄存器封装
    通过前面讲解,我们已经可以对寄存器进行操作,但是还稍有不足,因为STM32的GPIO比较多, 我们不可能每使用一个GPIO都做前面一样的一大堆定义。根据GPIO寄存器的特点,我们知道不论GPIOA还是GPIOB等都拥有一组功能相同的寄存器,如GPIOA_ODR/GPIOB_ODR/GPIOC_ODR等等,它们只是地址不一样。
    为了更方便地访问寄存器,我们引入C语言中的结构体对寄存器进行封装,具体代码如下:
    typedef unsigned int uint32_t; /*无符号 32 位变量*/
    typedef unsigned short int uint16_t; /*无符号 16 位变量*/
    /* GPIO 寄存器列表 */
    typedef struct
    {
        uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
        uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
        uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */
        uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */
        uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
        uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
        uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */
    }GPIO_TypeDef;
    这段代码用 typedef 关键字声明了名为GPIO_TypeDef的结构体类型,结构体内有7 个成员变量,变量名正好对应寄存器的名字。C 语言的语法规定,结构体内变量的存储空间是连续的,其中32位的变量占用4个字节,16 位的变量占用2个字节。
    于是,我们定义的GPIO_TypeDef,假如这个结构体的首地址为0x4001 1000(这也是第一个成员变量 CRL的地址),那么结构体中第二个成员变量CRH的地址即为0x4001 1000 +0x04,加上的这个0x04,正是代表CRH所占用的4个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给出。
    这样的地址偏移与STM32 GPIO外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存器了,比如我们还是将GPIOC0输出低电平,具体代码如下:
    GPIO_TypeDef * GPIOx; //定义一个GPIO_TypeDef型结构体指针GPIOx
    GPIOx = GPIOC_BASE; //把指针地址设置为宏 GPIOC_BASE 地址
    GPIOx->BSRR =(1<<(16+0)); //通过指针访问并修改 GPIOC_BSRR 寄存器
    这段代码先用GPIO_TypeDef类型定义一个结构体指针GPIOx,并让指针指向GPIOC基地址GPIOC_BASE,地址确定下来,然后根据C语言访问结构体的内容,用GPIOx->BSRR写寄存器。为了操作更简便灵活,我们直接使用宏定义好GPIO_TypeDef类型的指针,而且指针指向各个GPIO端口的首地址,使用时我们直接用该宏访问寄存器即可。具体代码如下:
    #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
    #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
    #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
    #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
    #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
    #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
    #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
    GPIOC->BSRR = (1<<(16+0));
    我们这里仅仅以GPIO这个外设为例,给大家讲解了如何使用C语言对寄存器封装,对于其他的外设也是使用同样方法。其实到了后面的实验程序的编写,我们都是使用ST公司提供的固件库,他们把STM32所有外设都已经封装好了,我们只需要调用即可。 我们这里分析这个封装过程只是想让大家更加清楚理解如何使用C来封装寄存器的。

6 j1 T. S+ P$ K- n/ M
收藏 评论0 发布时间:2022-7-2 14:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版