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

FreeRTOS中为什么要以这种宏定义方式访问指定地址的值

[复制链接]
patch1582 提问时间:2026-4-27 10:16 / 未解决

在FreeRTOS中需要启用 ARM-Cortex 内核 CYCNT 寄存器。要实现这个功能,必须将地址为0xE0001000的 DWT_CTRL 寄存器的使能位置 1。 我不理解的是为什么要使用下面这种宏定义(第 1 行),把寄存器的值赋值给宏DWT_CTRL,然后像第 34 行那样去修改它?

define DWT_CTRL ((volatile uint32_t)0xE0001000)

int main(void) { / USER CODE BEGIN 1 / TaskHandle_t task1_handle; TaskHandle_t task2_handle;

BaseType_t status;

/ USER CODE END 1 /

/ MCU Configuration--------------------------------------------------------/

/ Reset of all peripherals, Initializes the Flash interface and the Systick. / HAL_Init();

/ USER CODE BEGIN Init /

/ USER CODE END Init /

/ Configure the system clock / SystemClock_Config();

/ USER CODE BEGIN SysInit /

/ USER CODE END SysInit /

/ Initialize all configured peripherals / MX_GPIO_Init(); / USER CODE BEGIN 2 /

//Enable the CYCCNT

DWT_CTRL |= (1<<0);

收藏 评论5 发布时间:2026-4-27 10:16

举报

5个回答
cove 回答时间:2026-4-27 22:29:23

解答

在开始我的回答之前,笔者建议你学习一下 Markdown 的语法。如你所见,我的回答比之你的提问,可读性会好上不少。

Markdown 的语法也十分的简单,看一遍也就几分钟的事情,十分钟就能够熟练使用了。

为什么我们要这样操作寄存器?

首先第一点,你的用词不太准确

宏(#define)的本质是文本替换 ,它是在编译的第一阶段(预编译期)完成的。在这里我们并不是“赋值”给宏 DWT_CTRL,而是告诉编译器:“在后面的代码中,只要看到 DWT_CTRL 这个词,就把它原封不动地替换成后面那一长串代码”。这也是为什么宏定义中会使用很多小括号 () 的原因,编译器不会对宏进行语法检查,而 “赋值” 是会进行语法检查的。

其次,理解内存映射(Memory-Mapped I/O)

STM32 是一款 32 位的 MCU,理论上最大内存地址空间达到 $2^{32}$ 字节(即 4GB)。为了方便操作硬件,芯片设计者做了一个很优雅的设计——将外设的寄存器映射到内部地址空间中。换句话说,外设寄存器也能有自己的“内存地址”,你只需要像操作普通内存一样操作这个地址,就可以控制相应的硬件外设DWT_CTRL 寄存器的地址 0xE0001000 就是这么来的。

第三,这句宏定义的真正含义

#define DWT_CTRL (*(volatile uint32_t *)0xE0001000)

  1. 0xE0001000 :这是一个普通的十六进制数字,代表寄存器的物理地址。
  2. *`(volatile uint32_t )** :这是 C 语言的强制类型转换。它告诉编译器:“请不要把0xE0001000当成一个普通的数字,请把它当成一个**指针** ,这个指针指向一个 32 位的无符号整数(uint32_t`)”。
  3. volatile 关键字:这个词翻译为“易变的”。硬件寄存器的值随时可能被外设(比如内核状态)改变,而不是只受你写的代码控制。加上volatile 是在警告编译器:“不要对这个地址进行任何缓存优化! 每次用到它时,必须老老实实去内存地址里重新读取最新的值。”其实按照我自己的学习来看,你看到对 volatile 关键字的这种解释,可能会感觉非常不能理解,这是正常的,实在理解不了记住就行,因为我真正理解这个关键字的作用,是在我学习了 ARM32 的汇编之后,亲手写了一段类似的代码然后编译,再反编译去对比使用或者不使用这个关键字所翻译出来的汇编指令的差别。
  4. 最外层的 *(解引用)* :指针只是地址,加上`` 就代表获取这个地址里面装的具体内容** 。

最后,为什么用 |= 修改它?

当你写下 DWT_CTRL |= (1<<0); 时,经过宏替换,编译器实际看到的是:

(*(volatile uint32_t *)0xE0001000) |= (1<<0);

  • (1<<0) 是把数字 1 左移 0 位,代表第 0 位是 1,其他位是 0。
  • |= 是“按位或并赋值”操作。

先说 |,这个符号是位运算符,表示按位或。从逻辑上说,这个 “或” 就是指只要有一个为真,那么结果就为真,只有全为假,那么结果就为假。而在 C 语言中,0 为假,非 0 或者说 1 为真。举个例子,假设有两个 8 位的二进制数,A = 0101 0000B = 0000 0011。如果我们将它们进行按位或操作(A | B),计算过程就是将它们上下对齐,每一位单独做 “或” 运算,如下所示:

  0101 0000  (A)
| 0000 0011  (B)
-------------
  0101 0011  (结果)

你可以清除地看到,A 原本为 1 的位,结果里依然是 1;B 原本为 1 的位,结果里也是 1。这两个数完美地 “合并” 在了一起,相互之间没有产生干扰。

为什么要用 |= 而不是直接 = 呢?这叫 Read-Modify-Write(读-改-写) 操作。一个寄存器里通常有 32 个位,分别控制不同的功能。我们只想把第 0 位(使能位)变成 1,而不想破坏其他 31 个位原本的状态。使用 |= 可以完美做到只修改特定位,而不影响其他位的配置。

而你的 DWT_CTRL |= (1<<0) 逻辑上完全等价于 DWT_CTRL = DWT_CTRL | (1<<0)

cove 回答时间:2026-4-27 22:33:08

解答

在开始我的回答之前,笔者建议你学习一下 Markdown 的语法。如你所见,我的回答比之你的提问,可读性会好上不少。

Markdown 的语法也十分的简单,看一遍也就几分钟的事情,十分钟就能够熟练使用了。

为什么我们要这样操作寄存器?

首先第一点,你的用词不太准确

宏(#define)的本质是文本替换 ,它是在编译的第一阶段(预编译期)完成的。在这里我们并不是“赋值”给宏 DWT_CTRL,而是告诉编译器:“在后面的代码中,只要看到 DWT_CTRL 这个词,就把它原封不动地替换成后面那一长串代码”。这也是为什么宏定义中会使用很多小括号 () 的原因,编译器不会对宏进行语法检查,而 “赋值” 是会进行语法检查的。

其次,理解内存映射(Memory-Mapped I/O)

STM32 是一款 32 位的 MCU,理论上最大内存地址空间达到 $2^{32}$ 字节(即 4GB)。为了方便操作硬件,芯片设计者做了一个很优雅的设计——将外设的寄存器映射到内部地址空间中。换句话说,外设寄存器也能有自己的“内存地址”,你只需要像操作普通内存一样操作这个地址,就可以控制相应的硬件外设DWT_CTRL 寄存器的地址 0xE0001000 就是这么来的。

第三,这句宏定义的真正含义

#define DWT_CTRL (*(volatile uint32_t *)0xE0001000)

  1. 0xE0001000 :这是一个普通的十六进制数字,代表寄存器的物理地址。
  2. *`(volatile uint32_t )** :这是 C 语言的强制类型转换。它告诉编译器:“请不要把0xE0001000当成一个普通的数字,请把它当成一个**指针** ,这个指针指向一个 32 位的无符号整数(uint32_t`)”。
  3. volatile 关键字:这个词翻译为“易变的”。硬件寄存器的值随时可能被外设(比如内核状态)改变,而不是只受你写的代码控制。加上volatile 是在警告编译器:“不要对这个地址进行任何缓存优化! 每次用到它时,必须老老实实去内存地址里重新读取最新的值。”其实按照我自己的学习来看,你看到对 volatile 关键字的这种解释,可能会感觉非常不能理解,这是正常的,实在理解不了记住就行,因为我真正理解这个关键字的作用,是在我学习了 ARM32 的汇编之后,亲手写了一段类似的代码然后编译,再反编译去对比使用或者不使用这个关键字所翻译出来的汇编指令的差别。
  4. 最外层的 *(解引用)* :指针只是地址,加上`` 就代表获取这个地址里面装的具体内容** 。

最后,为什么用 |= 修改它?

当你写下 DWT_CTRL |= (1<<0); 时,经过宏替换,编译器实际看到的是:

(*(volatile uint32_t *)0xE0001000) |= (1<<0);

  • (1<<0) 是把数字 1 左移 0 位,代表第 0 位是 1,其他位是 0。
  • |= 是“按位或并赋值”操作。

先说 |,这个符号是位运算符,表示按位或。从逻辑上说,这个 “或” 就是指只要有一个为真,那么结果就为真,只有全为假,那么结果就为假。而在 C 语言中,0 为假,非 0 或者说 1 为真。举个例子,假设有两个 8 位的二进制数,A = 0101 0000B = 0000 0011。如果我们将它们进行按位或操作(A | B),计算过程就是将它们上下对齐,每一位单独做 “或” 运算,如下所示:

  0101 0000  (A)
| 0000 0011  (B)
-------------
  0101 0011  (结果)

你可以清除地看到,A 原本为 1 的位,结果里依然是 1;B 原本为 1 的位,结果里也是 1。这两个数完美地 “合并” 在了一起,相互之间没有产生干扰。

为什么要用 |= 而不是直接 = 呢?这叫 Read-Modify-Write(读-改-写) 操作。一个寄存器里通常有 32 个位,分别控制不同的功能。我们只想把第 0 位(使能位)变成 1,而不想破坏其他 31 个位原本的状态。使用 |= 可以完美做到只修改特定位,而不影响其他位的配置。

而你的 DWT_CTRL |= (1<<0) 逻辑上完全等价于 DWT_CTRL = DWT_CTRL | (1<<0)

zhoupxa 回答时间:2026-4-27 22:49:26

先用绝对地址定义寄存器,然后就可以直接寻址方式访问寄存器了

cove 回答时间:2026-4-28 12:54:15

解答

在开始我的回答之前,笔者建议你学习一下 Markdown 的语法。如你所见,我的回答比之你的提问,可读性会好上不少。

Markdown 的语法也十分的简单,看一遍也就几分钟的事情,十分钟就能够熟练使用了。

为什么我们要这样操作寄存器?

首先第一点,你的用词不太准确

宏(#define)的本质是文本替换 ,它是在编译的第一阶段(预编译期)完成的。在这里我们并不是“赋值”给宏 DWT_CTRL,而是告诉编译器:“在后面的代码中,只要看到 DWT_CTRL 这个词,就把它原封不动地替换成后面那一长串代码”。这也是为什么宏定义中会使用很多小括号 () 的原因,编译器不会对宏进行语法检查,而 “赋值” 是会进行语法检查的。

其次,理解内存映射(Memory-Mapped I/O)

STM32 是一款 32 位的 MCU,理论上最大内存地址空间达到 $2^{32}$ 字节(即 4GB)。为了方便操作硬件,芯片设计者做了一个很优雅的设计——将外设的寄存器映射到内部地址空间中。换句话说,外设寄存器也能有自己的“内存地址”,你只需要像操作普通内存一样操作这个地址,就可以控制相应的硬件外设DWT_CTRL 寄存器的地址 0xE0001000 就是这么来的。

第三,这句宏定义的真正含义

#define DWT_CTRL (*(volatile uint32_t *)0xE0001000)

  1. 0xE0001000 :这是一个普通的十六进制数字,代表寄存器的物理地址。
  2. *`(volatile uint32_t )** :这是 C 语言的强制类型转换。它告诉编译器:“请不要把0xE0001000当成一个普通的数字,请把它当成一个**指针** ,这个指针指向一个 32 位的无符号整数(uint32_t`)”。
  3. volatile 关键字:这个词翻译为“易变的”。硬件寄存器的值随时可能被外设(比如内核状态)改变,而不是只受你写的代码控制。加上volatile 是在警告编译器:“不要对这个地址进行任何缓存优化! 每次用到它时,必须老老实实去内存地址里重新读取最新的值。”其实按照我自己的学习来看,你看到对 volatile 关键字的这种解释,可能会感觉非常不能理解,这是正常的,实在理解不了记住就行,因为我真正理解这个关键字的作用,是在我学习了 ARM32 的汇编之后,亲手写了一段类似的代码然后编译,再反编译去对比使用或者不使用这个关键字所翻译出来的汇编指令的差别。
  4. 最外层的 *(解引用)* :指针只是地址,加上`` 就代表获取这个地址里面装的具体内容** 。

最后,为什么用 |= 修改它?

当你写下 DWT_CTRL |= (1<<0); 时,经过宏替换,编译器实际看到的是:

(*(volatile uint32_t *)0xE0001000) |= (1<<0);

  • (1<<0) 是把数字 1 左移 0 位,代表第 0 位是 1,其他位是 0。
  • |= 是“按位或并赋值”操作。

先说 |,这个符号是位运算符,表示按位或。从逻辑上说,这个 “或” 就是指只要有一个为真,那么结果就为真,只有全为假,那么结果就为假。而在 C 语言中,0 为假,非 0 或者说 1 为真。举个例子,假设有两个 8 位的二进制数,A = 0101 0000B = 0000 0011。如果我们将它们进行按位或操作(A | B),计算过程就是将它们上下对齐,每一位单独做 “或” 运算,如下所示:

  0101 0000  (A)
| 0000 0011  (B)
-------------
  0101 0011  (结果)

你可以清除地看到,A 原本为 1 的位,结果里依然是 1;B 原本为 1 的位,结果里也是 1。这两个数完美地 “合并” 在了一起,相互之间没有产生干扰。

为什么要用 |= 而不是直接 = 呢?这叫 Read-Modify-Write(读-改-写) 操作。一个寄存器里通常有 32 个位,分别控制不同的功能。我们只想把第 0 位(使能位)变成 1,而不想破坏其他 31 个位原本的状态。使用 |= 可以完美做到只修改特定位,而不影响其他位的配置。

而你的 DWT_CTRL |= (1<<0) 逻辑上完全等价于 DWT_CTRL = DWT_CTRL | (1<<0)

patch1582 回答时间:2026-4-29 15:42:49

zhoupxa 发表于 2026-4-27 22:49
先用绝对地址定义寄存器,然后就可以直接寻址方式访问寄存器了

定义的时候,直接定义寄存器地址,这样不是更明确,更易读么

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版