2.1 变量定义

  • Cortex 微控制器编程中常使用特定类型的变量定义,以增强代码的可读性和可移植性。
    • Cortex 变量类型与标准 C 中变量类型的对应关系:
      Cortex类型
      标准C类型
      uint8_t
      unsigned char
      uint16_t
      unsigned short int
      uint32_t
      unsigned int
      int8_t
      signed char
      int16_t
      signed short int
      int32_t
      signed int
  • 这些类型如 uint8_t 通常在标准库的 stdint.h 文件中通过 typedef 重定义。

2.2 #include 语句

  • #include 指令常用来包含某个头文件。
  • 头文件分类:
    • 系统头文件:以 < > 为标识,例如 #include <stdio.h>。编译器在编译系统安装目录的库文件夹中搜寻。
    • 自定义头文件:以 " " 为标识,例如 #include "config.h"。编译器首先在用户的文件目录中搜寻,找不到则去系统库文件目录搜寻。自定义头文件一般需要在编译器中设置包含目录。

2.3 预处理语句

  • 预处理语句以 # 开头,是给编译器传达指示。
  • 常见用途:
    • 预处理宏定义 (可用于定义全局变量的别名或常量): #define GPIOH_MODER *(unsigned int*)(GPIOH_BASE + 0x00)
    • 取消宏定义 (#undef):用于解除一个已有的宏定义,以避免命名冲突。 #undef MAX_NUM

2.4 条件编译语句

  • 结构:
    • 作用:如果“标识符”已经被定义,则编译程序段1;否则编译程序段2。
    • 防止重复包含:在头文件中常使用 #ifndef#define#endif 结构,以防止同一个头文件被多次包含。
    notion image

    2.5 extern 外部声明

    • 在 C 语言中,extern 关键字表明变量或函数是定义在其他文件中的。
    • 用于在一个文件中声明已在其他文件中定义的全局变量或函数。

      2.6 位运算操作

      • C 语言提供以下6个位运算操作符:& (按位与), | (按位或), ^ (按位异或), ~ (按位非), << (左移), >> (右移)。
      • 示例: GPIOH_MODER &= ~(0x03 << 20); // 将GPIOH_MODER的第20、21位清零 GPIOH_MODER |= (0x01 << 20); // 将GPIOH_MODER的第20位设置为1

      2.7 __IO 关键字

      • __IO (或 _IO) 关键字通常被定义为 volatile
      • volatile 变量的含义:允许程序之外的硬件或软件修改其内容,确保当前读取到的值是最新的数据值,防止编译器优化。
      • 示例: __IO uint32_t tmp;tmp = GPIOx->LCKR; // 确保从实际地址读取

      2.8 结构体与外设定义❗❗

      结构体变量的地址对应关系

      在 Cortex-M 微控制器编程中,尤其是当我们需要直接访问外设寄存器时,通常会使用 C 语言的结构体来组织这些寄存器。这种做法的优点在于:
      1. 相关的设置参数放置在一个结构体中
        1. 将一个外设的所有相关寄存器(例如一个 GPIO 端口的所有配置寄存器)集中到一个结构体里,使得代码更具可读性和组织性
      1. 对外设操作时,将相关寄存器地址映射到结构体的成员
        1. 通过将结构体实例(或指向结构体的指针)的地址设置为外设寄存器的起始地址,我们可以通过访问结构体成员的方式来间接访问这些寄存器,而不是使用裸的内存地址。
      实际的寄存器的地址也是连续排列的,我们定义的结构体在被赋予了正确的首地址之后就可以正确的访问这一组寄存器了。
      notion image
      1. 结构体变量地址对应这个结构体的首地址
        1. 这是 C 语言的一个基本特性。结构体在内存中是连续存放的,其首地址就是第一个成员的地址。

      详细讲解

      让我们以上图中的 GPIO_TypeDef 结构体为例进行说明:
      以及对应的 GPIO 寄存器映射表:
      偏移
      寄存器
      0x00
      GPIOA_MODER
      0x04
      GPIOX_OTYPER
      0x08
      GPIOX_OSPEEDER
      0x0C
      GPIOA_PUPDR
      ...
      ...
      【地址对应关系的核心】
      编译器会按照结构体成员的定义顺序,在内存中为它们分配连续的存储空间。每个成员的地址是相对于结构体首地址的偏移量加上结构体首地址。
      1. _IO uint32_t MODER; (偏移 0x00):
          • MODER 是结构体的第一个成员。
          • uint32_t 表示它占用 4 个字节 (32 位)。
          • 因此,MODER 成员的地址偏移量是 0x00。它对应着外设寄存器 GPIOx_MODER 的基地址(例如,对于 GPIOA,就是 0x40020000 + 0x00)。
      1. _IO uint32_t OTYPER; (偏移 0x04):
          • OTYPER 是结构体的第二个成员。
          • 紧跟在 MODER 之后。由于 MODER 占用了 4 个字节 (从 0x00 到 0x03),所以 OTYPER 的地址偏移量就是 0x04。
          • 它对应着外设寄存器 GPIOx_OTYPER 的地址(例如,对于 GPIOA,就是 0x40020000 + 0x04)。
      1. _IO uint32_t OSPEEDR; (偏移 0x08):
          • 类似地,OSPEEDR 的地址偏移量是 0x08,对应 GPIOx_OSPEEDER
      1. _IO uint32_t PUPDR; (偏移 0x0C):
          • PUPDR 的地址偏移量是 0x0C,对应 GPIOx_PUPDR
      【如何映射到实际外设寄存器】
      为了让程序能够通过结构体来访问实际的硬件寄存器,我们需要定义一个指向该结构体类型的指针,并将其赋值为外设寄存器的基地址。
      例如,对于 GPIOB 端口,其基地址是 0x400204001(真实地址)
      我们可以这样定义并使用:
      通过这种方式,GPIOB->MODER 实际上访问的就是地址 0x40020400 + 0x00 处的 32 位寄存器,GPIOB->OTYPER 访问的就是地址 0x40020400 + 0x04 处的 32 位寄存器,以此类推。这大大提高了代码的可读性和可维护性,使得操作寄存器就像操作普通变量一样直观。
      Loading...
      Z_cosy
      Z_cosy
      浙江大学电气工程学院本科生
      公告
      🎉Welcome to Z-cosy🎉
      -- 食用指南 ---
      目前只有课程笔记以及电控学习笔记
      陆续会整理更多内容!