• STM32的寄存器操作


    STM32最基本的,最底层的,就是对寄存器的直接操作。通过操作特定寄存器的特定位,来实现相对应的功能。

    本文通过GPIO点亮LED来演示。

    GPIO

    查阅数据手册,了解相关内容。

    启动代码

    旧版的keil,在建立工程时会提示自动导入启动代码。

    但是新版在建立工程后,会弹出运行时环境配置。选择相应的启动代码。

    为了选择这个startup,需要先选择CMSIS里的CORE内核模块。

    之后生成了3个启动代码。

    旧版只有1个启动代码startup_stm32f10x_hd.s,没有多余的文件,咋回事?

    在回答这个问题之前,我们来了解下STM32的启动文件。

    在使用 STM32CubeMX 开发 STM32时,往往会看到一个.s 的文件,这个文件就是 STM32 的启动文件,里面包含多个功能,例如堆栈大小配置、STM32 复位后初始化和建立中断服务入口地址等功能。

    在支持包中,可以找到相应的启动文件。

    启动代码在这里:

    F:\czpBuiltin\keil5 mdk\Arm\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Source\ARM

    这里有很多种启动代码,到底应该怎么选呢?

    startup_stm32f10x_cl.s 互联型的STM32F105xx,STM32F107xx
    startup_stm32f10x_hd.s 大容量的STM32F101xx,STM32F102xx,STM32F103xx
    startup_stm32f10x_hd_vl.s 超值型大容量的STM32F100xx
    startup_stm32f10x_ld.s 小容量的STM32F101xx,STM32F102xx,STM32F103xx
    startup_stm32f10x_ld_vl.s 超值型小容量的STM32F100xx
    startup_stm32f10x_md.s 中容量的STM32F101xx,STM32F102xx,STM32F103xx
    startup_stm32f10x_md_vl.s 超值型中容量的STM32F100xx
    startup_stm32f10x_xl.s

    超大容量FLASH在512K到1024K字节的STM32F101xx,STM32F102xx,STM32F103xx

    ROM容量为:

    16~32K就是LD

    64K~128K的就是MD

    256~512K的就是HD

    我这里用的是F103C8,64K的flash,所以用的是md,startup_stm32f10x_md.s。

    现尝试将这个启动文件单独复制到工程中。

    代码实现

    编译后有一个error:

    .\Objects\regled.axf: Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f10x_md.o). 

    没定义的符号SystemInit,也就是说,该启动文件没有初始化的实现。其中关键的是没有开启时钟,所以无法工作。

    来看看这个符号在哪?启动文件中有这一段:

    这是复位之后的处理代码,先系统初始化,然后再跳转到main函数中执行。

    两种解决方法,要么把带有SystemInit的文件导入进来,要么自己定义一个,可以不写实现,保持为空,也可以直接在这里面开启时钟。

    1. /*************************************************************************************************
    2. *PB8-PB15,这8个端口依次控制着8颗LED的正极
    3. *
    4. *GPIOB的起始地址为:0x40010C00
    5. *
    6. *1、
    7. *GPIOB_CRH进行模式和输出配置,8个端口均为推挽输出,所以该寄存机的值应为0x11111111
    8. *该寄存器的偏移地址为04h,所以GPIOB_CRH寄存器的地址为:0x40010C04
    9. *
    10. *2、
    11. *我们先点亮第这8颗LED,所以接下来要先配置输出寄存器GPIOB_ODR,偏移量0x0C,所以地址为:0x40010C08
    12. *要赋予的值为:Ox0000FF00
    13. *
    14. *************************************************************************************************/
    15. void SystemInit(void);
    16. int main(void)
    17. {
    18. unsigned int *ledCfg = (unsigned int *)0x40010C04;
    19. unsigned int *ledData = (unsigned int *)0x40010C0C;
    20. *ledCfg = 0x33333333;
    21. *ledData = 0x00005500;
    22. while(1);
    23. }
    24. void SystemInit()
    25. {
    26. //开启GPIOB时钟,置RCC_APB2ENR寄存器的第3位为1,赋值0x00000008
    27. //RCC基地址为0x40021000,该寄存器的偏移量为18H,则地址为:0x40021018
    28. unsigned int *RCC_APB2ENR = (unsigned int *)0x40021018;
    29. *RCC_APB2ENR = 0x00000008;
    30. }

    注意,因为手册上没有写明GPIOB_ODR的偏移量,所以,按照我的想法,它的地址是按照4个字节来递增的,上一个偏移量是08H,下一个偏移量是10H,所以,我在想,08和10之间不可能隔了4个数呀?咋回事?难道是和上一个寄存器共用一个地址?结果,代码就错了。

    这里犯了思维定势的错误。08和10都是16进制的数,那么08H和10H之间不就正好差了8个字节吗?08H和10H之间的不就是0CH吗?我还在疑惑,C换成十进制是12,怎么可能在10H之前呢?所以,千万别搞混了十进制和十六进制。

    另外,还可以直接通过复位或者置位寄存器来控制电平高低。操作方法一样,不赘述。

    再补充一点,以上代码可以通过宏定义(比如重复格式代码宏定义,基地址宏定义,基地址+偏移量方式宏定义等等)、位操作或者拆分头文件等方式来优化,不赘述。

    归根结底

    通过操作寄存器来实现对应的功能,是最底层的方式,需要我们通过查阅数据手册,找到对应寄存器的地址,并且了解对应寄存器相应位所表示的含义,从而实现相应功能。

    要学会通过串口来调试程序。 

  • 相关阅读:
    FPGA project : usrt_rs232
    强化学习 | Python强化学习
    在 Android 上测试 Kotlin 协程
    Fabric.js 讲解官方demo:Stickman
    RabbitMQ面试题(四十四道)
    1-3 Linux ifconfig 命令使用
    react ant design Upload 多文件上传 beforeUpload 会调用很多次,怎么只获取一次
    安卓机型固件系统分区的基础组成 手机启动规律初步常识 各分区的基本含义与说明
    悄然而至,低/无代码的出现真能撬动传统开发的蛋糕?
    VMware Workstation虚拟机安装 CentOS 7.9 后ping ip地址出现错误:Network is unreachable
  • 原文地址:https://blog.csdn.net/qq_28576837/article/details/126394302