|
在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;
/ 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); |
强制类型转换不起作用
STM32 VSCode 扩展插件问题
在 CubeIDE 中为不同 RAM 区域定义带初始值的全局变量
当程序里有 while (1) 死循环时,main 函数还需要 return 0 吗?
编译器在结构体中插入了并不存在的 16 位变量?
第三方库尺寸评估
给变量赋值二进制数值无效
STM32CubeIDE 构建后运行脚本与路径中包含引号
int 与 float 之间的转换
有关STM32H743ZGT6 驱动RTL8201F-VB芯片驱动的问题,有奖寻求帮助
微信公众号
手机版
解答
在开始我的回答之前,笔者建议你学习一下 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)0xE0001000:这是一个普通的十六进制数字,代表寄存器的物理地址。** :这是 C 语言的强制类型转换。它告诉编译器:“请不要把0xE0001000当成一个普通的数字,请把它当成一个**指针** ,这个指针指向一个 32 位的无符号整数(uint32_t`)”。volatile关键字:这个词翻译为“易变的”。硬件寄存器的值随时可能被外设(比如内核状态)改变,而不是只受你写的代码控制。加上volatile是在警告编译器:“不要对这个地址进行任何缓存优化! 每次用到它时,必须老老实实去内存地址里重新读取最新的值。”其实按照我自己的学习来看,你看到对volatile关键字的这种解释,可能会感觉非常不能理解,这是正常的,实在理解不了记住就行,因为我真正理解这个关键字的作用,是在我学习了 ARM32 的汇编之后,亲手写了一段类似的代码然后编译,再反编译去对比使用或者不使用这个关键字所翻译出来的汇编指令的差别。*(解引用)* :指针只是地址,加上`` 就代表获取这个地址里面装的具体内容** 。最后,为什么用
|=修改它?当你写下
DWT_CTRL |= (1<<0);时,经过宏替换,编译器实际看到的是:(*(volatile uint32_t *)0xE0001000) |= (1<<0);(1<<0)是把数字 1 左移 0 位,代表第 0 位是 1,其他位是 0。|=是“按位或并赋值”操作。先说
|,这个符号是位运算符,表示按位或。从逻辑上说,这个 “或” 就是指只要有一个为真,那么结果就为真,只有全为假,那么结果就为假。而在 C 语言中,0 为假,非 0 或者说 1 为真。举个例子,假设有两个 8 位的二进制数,A = 0101 0000,B = 0000 0011。如果我们将它们进行按位或操作(A | B),计算过程就是将它们上下对齐,每一位单独做 “或” 运算,如下所示:你可以清除地看到,
A原本为 1 的位,结果里依然是 1;B原本为 1 的位,结果里也是 1。这两个数完美地 “合并” 在了一起,相互之间没有产生干扰。为什么要用
|=而不是直接=呢?这叫 Read-Modify-Write(读-改-写) 操作。一个寄存器里通常有 32 个位,分别控制不同的功能。我们只想把第 0 位(使能位)变成 1,而不想破坏其他 31 个位原本的状态。使用|=可以完美做到只修改特定位,而不影响其他位的配置。而你的
DWT_CTRL |= (1<<0)逻辑上完全等价于DWT_CTRL = DWT_CTRL | (1<<0)。解答
在开始我的回答之前,笔者建议你学习一下 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)0xE0001000:这是一个普通的十六进制数字,代表寄存器的物理地址。** :这是 C 语言的强制类型转换。它告诉编译器:“请不要把0xE0001000当成一个普通的数字,请把它当成一个**指针** ,这个指针指向一个 32 位的无符号整数(uint32_t`)”。volatile关键字:这个词翻译为“易变的”。硬件寄存器的值随时可能被外设(比如内核状态)改变,而不是只受你写的代码控制。加上volatile是在警告编译器:“不要对这个地址进行任何缓存优化! 每次用到它时,必须老老实实去内存地址里重新读取最新的值。”其实按照我自己的学习来看,你看到对volatile关键字的这种解释,可能会感觉非常不能理解,这是正常的,实在理解不了记住就行,因为我真正理解这个关键字的作用,是在我学习了 ARM32 的汇编之后,亲手写了一段类似的代码然后编译,再反编译去对比使用或者不使用这个关键字所翻译出来的汇编指令的差别。*(解引用)* :指针只是地址,加上`` 就代表获取这个地址里面装的具体内容** 。最后,为什么用
|=修改它?当你写下
DWT_CTRL |= (1<<0);时,经过宏替换,编译器实际看到的是:(*(volatile uint32_t *)0xE0001000) |= (1<<0);(1<<0)是把数字 1 左移 0 位,代表第 0 位是 1,其他位是 0。|=是“按位或并赋值”操作。先说
|,这个符号是位运算符,表示按位或。从逻辑上说,这个 “或” 就是指只要有一个为真,那么结果就为真,只有全为假,那么结果就为假。而在 C 语言中,0 为假,非 0 或者说 1 为真。举个例子,假设有两个 8 位的二进制数,A = 0101 0000,B = 0000 0011。如果我们将它们进行按位或操作(A | B),计算过程就是将它们上下对齐,每一位单独做 “或” 运算,如下所示:你可以清除地看到,
A原本为 1 的位,结果里依然是 1;B原本为 1 的位,结果里也是 1。这两个数完美地 “合并” 在了一起,相互之间没有产生干扰。为什么要用
|=而不是直接=呢?这叫 Read-Modify-Write(读-改-写) 操作。一个寄存器里通常有 32 个位,分别控制不同的功能。我们只想把第 0 位(使能位)变成 1,而不想破坏其他 31 个位原本的状态。使用|=可以完美做到只修改特定位,而不影响其他位的配置。而你的
DWT_CTRL |= (1<<0)逻辑上完全等价于DWT_CTRL = DWT_CTRL | (1<<0)。先用绝对地址定义寄存器,然后就可以直接寻址方式访问寄存器了
解答
在开始我的回答之前,笔者建议你学习一下 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)0xE0001000:这是一个普通的十六进制数字,代表寄存器的物理地址。** :这是 C 语言的强制类型转换。它告诉编译器:“请不要把0xE0001000当成一个普通的数字,请把它当成一个**指针** ,这个指针指向一个 32 位的无符号整数(uint32_t`)”。volatile关键字:这个词翻译为“易变的”。硬件寄存器的值随时可能被外设(比如内核状态)改变,而不是只受你写的代码控制。加上volatile是在警告编译器:“不要对这个地址进行任何缓存优化! 每次用到它时,必须老老实实去内存地址里重新读取最新的值。”其实按照我自己的学习来看,你看到对volatile关键字的这种解释,可能会感觉非常不能理解,这是正常的,实在理解不了记住就行,因为我真正理解这个关键字的作用,是在我学习了 ARM32 的汇编之后,亲手写了一段类似的代码然后编译,再反编译去对比使用或者不使用这个关键字所翻译出来的汇编指令的差别。*(解引用)* :指针只是地址,加上`` 就代表获取这个地址里面装的具体内容** 。最后,为什么用
|=修改它?当你写下
DWT_CTRL |= (1<<0);时,经过宏替换,编译器实际看到的是:(*(volatile uint32_t *)0xE0001000) |= (1<<0);(1<<0)是把数字 1 左移 0 位,代表第 0 位是 1,其他位是 0。|=是“按位或并赋值”操作。先说
|,这个符号是位运算符,表示按位或。从逻辑上说,这个 “或” 就是指只要有一个为真,那么结果就为真,只有全为假,那么结果就为假。而在 C 语言中,0 为假,非 0 或者说 1 为真。举个例子,假设有两个 8 位的二进制数,A = 0101 0000,B = 0000 0011。如果我们将它们进行按位或操作(A | B),计算过程就是将它们上下对齐,每一位单独做 “或” 运算,如下所示:你可以清除地看到,
A原本为 1 的位,结果里依然是 1;B原本为 1 的位,结果里也是 1。这两个数完美地 “合并” 在了一起,相互之间没有产生干扰。为什么要用
|=而不是直接=呢?这叫 Read-Modify-Write(读-改-写) 操作。一个寄存器里通常有 32 个位,分别控制不同的功能。我们只想把第 0 位(使能位)变成 1,而不想破坏其他 31 个位原本的状态。使用|=可以完美做到只修改特定位,而不影响其他位的配置。而你的
DWT_CTRL |= (1<<0)逻辑上完全等价于DWT_CTRL = DWT_CTRL | (1<<0)。定义的时候,直接定义寄存器地址,这样不是更明确,更易读么