STM32f103c8 gpio限速
STM32f103c8 gpio speed limit
我有这个简单的内联汇编代码:
__asm__ volatile (
".equ GPIOA_ODR, 0x4001080C \n\t" //GPIOA base address is 0x40010800 and ODR offset is 0x0C
//turns on PA8
"ldr r1, =(1 << 8) \n\t"
"ldr r2, =#GPIOA_ODR \n\t"
"str r1, [r2] \n\t"
//turn off PA8
"ldr r1, =0 \n\t"
"ldr r2, =#GPIOA_ODR \n\t"
"str r1, [r2] \n\t"
);
PA8只有2.4MHz的振荡频率,我想要36MHz的速度。我曾尝试使用定时器并达到了 36MHz 的速度,但由于一些限制,我想避免使用它们。
我不明白为什么 TIMER1 通道 1 (PA8) 可以配置为 36MHz 的开关速度,但是当我尝试在汇编中做同样的事情时,我在同一个引脚上只能达到 2.4MHz 的速度。
我也在使用 PinMode(PA8, OUTPUT);
设置 PIN
我已经尝试过此汇编代码的其他变体,但在 PA8 上最多只能达到 2.8MHz。我的问题是:在 STM32f103C8 上,GPIO 引脚上的开关速度是否不可能高于 2.4-2.8MHz?
(这是 Need Help Manipulating Registers in Inline Assembly (STM32F103 "BluePill") 之后的后续问题)
STM32F103C8 以 72 MHz 的最大时钟速度运行。因此 36 MHz 是可以在 GPIO 上生成的最大频率,因为需要一个单独的时钟周期来设置和清除引脚。这个频率只能用定时器来实现。
如果您尝试使用代码进行同样的操作,则至少需要三个说明:两家商店和一家分店。这些指令需要大约 6 个时钟周期来执行,因此会产生大约 12 Mhz 的最大频率。
为了在软件中实现这一点,您的代码应如下所示:
while (1) {
GPIOA->ODR = 1 << 8;
GPIOA->ODR = 0;
}
不需要汇编代码,因为编译器会提供最佳代码。它看起来像这样:
ldr r3, .L3
movs r1, #128
movs r2, #0
.L2:
str r1, [r3]
str r2, [r3]
b .L2
.L3:
.word 1207959572
更新
我已经在真实世界的设备上对其进行了测试,得到的频率为 8 MHz。我估计这三个指令需要 6 个时钟周期,但它似乎需要 9 个周期。
生成的代码或多或少符合预期:
7a: 60d9 str r1, [r3, #12]
7c: 60da str r2, [r3, #12]
7e: e7fc b.n 7a <main+0x7a>
范围清楚地表明所有三个指令花费相同的时间。
我可能完全离开这里,因为我没有为您的平台编写代码...
反正GPIO直接映射到内存或者寄存器的时代已经很远了。现代 MCU 具有通过接口(通常是内存映射寄存器)互连到 MCU CPU 内核的 GPIO 接口,您可以在其中查询 GPIO 命令而不是直接操作 GPIO 位。
计时器绕过此接口因此速度更快。但是,如果是这样的话,有一些方法可以提高 MCU 的 GPIO 轮询速度:
GPIOAPI时钟
API MCU CPU 内核和 GPIO 模块之间的(接口)通常由单独的时钟控制。如果设置为慢速,无论 MCU 时钟或 GPIO 功能如何,GPIO 也会变慢。
所以试着寻找它并尽可能地增强它。
GPIO 组
GPIO 引脚通常分组到共享相同 API 寄存器的端口。因此,通常可以以与处理单个引脚相同的速度一次处理同一组中的所有引脚。所以如果你 select 你小心使用你的引脚,你可以调整极化频率很多。
因此,如果可能,只使用单个组...计算所有引脚的操作,然后使用 GPIO api 到 set/clear/toggle 一次全部而不是一个一个地使用。
DMA
一些 MCU 允许内存和 GPIO 之间的 DMA,您可以绕过 GPIO API 并获得与定时器相似的速度。简单地创建内存缓冲区,所有位状态预先以一定的采样率预先计算,然后在 GPIO 上使用 DMA "play" 它,就像在声卡上播放 wav 文件一样...
未使用 GPIO
一些 MCU 根本不是为 GPIO 速度而构建的,而是为了更多的计算能力或不同的目的,在这种情况下,无论您做什么,都不会大大提高 GPIO 速度。在这种情况下,MCU 通常配备用于与外部存储器、IDE、LCD、SPI、USART 等不同硬件互连的接口
其中一些可以用来代替 GPIO,并且外部存储器的接口通常很快,并且即使 GPIO 太慢,也可以使用 DMA 实现快速传输速度......例如参见:VGA pixel grouping on STM32
只是为了比较,我习惯了 AVR32 UC3 MCU,它在 ~66MHz CPU 时钟上具有 ~5 MHz GPIO 切换频率(轮询)...但是通过使用接口,我什至可以拥有 33MHz 采样率。 ..
问题是这样的接口通常没有很多引脚可供使用,而且有时它们被共享或时间映射为总线,在这种情况下,您有时需要向硬件添加一些额外的东西(如二极管+电容器,或 LATCH 或 (DE)MUX ...) 以避免毛刺
我想回答我自己的问题,因为在这个问题上得到一个可靠的答案是令人沮丧的,而且在你进行真实世界测试之前,结果并不明显。 Old_timer 的评论被证明是最有帮助的。
void setup(){
#define FLASH_ACR (*(volatile uint32_t *)(0x40022000))
FLASH_ACR = 0b110010; //enable flash prefetch and wait states
pinMode(PA8, OUTPUT);
__asm__ volatile (
"ldr r0, =(0x4001080C) \n\t" //GPIOA ODR
"ldr r1, =(1<<8) \n\t" //turn on PA8
"ldr r2, =0 \n\t" //turn off PA8
".loop: \n\t"
"str r1, [r0] \n\t" // ON and OFF commands are unrolled (repeated) about 100 times
"str r2, [r0] \n\t" // inside the loop
"b .loop \n\t"
); }
使用 72MHz 的 MCU 运行,我使用上面的代码在 PA8 上获得了非常接近 18MHz 的切换速度。据我了解,使用立即值或 XOR 指令可以更快地切换引脚(以及您可能做的其他事情),这是因为某些指令或某些编码方法使用较少的时钟周期,从而提高性能。
如果你也看STM32f103 PDF:
您将在第 20 页的第 2.3.21 节中看到 "I/Os on APB2 with up to 18 MHz toggling speed." 所以我想如果文档中提到它,我会在那里达到极限。如果您还浏览了第 66 页,您会看到一个漂亮的 table 和 "I/O AC characteristics",它表明您可以达到 50MHz。
所以在达到将近 18MHz 后,我决定将电路板超频到 128MHz,并在 PA8 上实现了几乎 32MHz 切换速度,引脚上有 1.6VDC。现在我很满意,感谢所有评论和帮助的人。我在这方面还是个初学者,但我想我现在已经了解了很多。
我有这个简单的内联汇编代码:
__asm__ volatile (
".equ GPIOA_ODR, 0x4001080C \n\t" //GPIOA base address is 0x40010800 and ODR offset is 0x0C
//turns on PA8
"ldr r1, =(1 << 8) \n\t"
"ldr r2, =#GPIOA_ODR \n\t"
"str r1, [r2] \n\t"
//turn off PA8
"ldr r1, =0 \n\t"
"ldr r2, =#GPIOA_ODR \n\t"
"str r1, [r2] \n\t"
);
PA8只有2.4MHz的振荡频率,我想要36MHz的速度。我曾尝试使用定时器并达到了 36MHz 的速度,但由于一些限制,我想避免使用它们。
我不明白为什么 TIMER1 通道 1 (PA8) 可以配置为 36MHz 的开关速度,但是当我尝试在汇编中做同样的事情时,我在同一个引脚上只能达到 2.4MHz 的速度。
我也在使用 PinMode(PA8, OUTPUT);
我已经尝试过此汇编代码的其他变体,但在 PA8 上最多只能达到 2.8MHz。我的问题是:在 STM32f103C8 上,GPIO 引脚上的开关速度是否不可能高于 2.4-2.8MHz?
(这是 Need Help Manipulating Registers in Inline Assembly (STM32F103 "BluePill") 之后的后续问题)
STM32F103C8 以 72 MHz 的最大时钟速度运行。因此 36 MHz 是可以在 GPIO 上生成的最大频率,因为需要一个单独的时钟周期来设置和清除引脚。这个频率只能用定时器来实现。
如果您尝试使用代码进行同样的操作,则至少需要三个说明:两家商店和一家分店。这些指令需要大约 6 个时钟周期来执行,因此会产生大约 12 Mhz 的最大频率。
为了在软件中实现这一点,您的代码应如下所示:
while (1) {
GPIOA->ODR = 1 << 8;
GPIOA->ODR = 0;
}
不需要汇编代码,因为编译器会提供最佳代码。它看起来像这样:
ldr r3, .L3
movs r1, #128
movs r2, #0
.L2:
str r1, [r3]
str r2, [r3]
b .L2
.L3:
.word 1207959572
更新
我已经在真实世界的设备上对其进行了测试,得到的频率为 8 MHz。我估计这三个指令需要 6 个时钟周期,但它似乎需要 9 个周期。
生成的代码或多或少符合预期:
7a: 60d9 str r1, [r3, #12]
7c: 60da str r2, [r3, #12]
7e: e7fc b.n 7a <main+0x7a>
范围清楚地表明所有三个指令花费相同的时间。
我可能完全离开这里,因为我没有为您的平台编写代码...
反正GPIO直接映射到内存或者寄存器的时代已经很远了。现代 MCU 具有通过接口(通常是内存映射寄存器)互连到 MCU CPU 内核的 GPIO 接口,您可以在其中查询 GPIO 命令而不是直接操作 GPIO 位。
计时器绕过此接口因此速度更快。但是,如果是这样的话,有一些方法可以提高 MCU 的 GPIO 轮询速度:
GPIOAPI时钟
API MCU CPU 内核和 GPIO 模块之间的(接口)通常由单独的时钟控制。如果设置为慢速,无论 MCU 时钟或 GPIO 功能如何,GPIO 也会变慢。
所以试着寻找它并尽可能地增强它。
GPIO 组
GPIO 引脚通常分组到共享相同 API 寄存器的端口。因此,通常可以以与处理单个引脚相同的速度一次处理同一组中的所有引脚。所以如果你 select 你小心使用你的引脚,你可以调整极化频率很多。
因此,如果可能,只使用单个组...计算所有引脚的操作,然后使用 GPIO api 到 set/clear/toggle 一次全部而不是一个一个地使用。
DMA
一些 MCU 允许内存和 GPIO 之间的 DMA,您可以绕过 GPIO API 并获得与定时器相似的速度。简单地创建内存缓冲区,所有位状态预先以一定的采样率预先计算,然后在 GPIO 上使用 DMA "play" 它,就像在声卡上播放 wav 文件一样...
未使用 GPIO
一些 MCU 根本不是为 GPIO 速度而构建的,而是为了更多的计算能力或不同的目的,在这种情况下,无论您做什么,都不会大大提高 GPIO 速度。在这种情况下,MCU 通常配备用于与外部存储器、IDE、LCD、SPI、USART 等不同硬件互连的接口
其中一些可以用来代替 GPIO,并且外部存储器的接口通常很快,并且即使 GPIO 太慢,也可以使用 DMA 实现快速传输速度......例如参见:VGA pixel grouping on STM32
只是为了比较,我习惯了 AVR32 UC3 MCU,它在 ~66MHz CPU 时钟上具有 ~5 MHz GPIO 切换频率(轮询)...但是通过使用接口,我什至可以拥有 33MHz 采样率。 ..
问题是这样的接口通常没有很多引脚可供使用,而且有时它们被共享或时间映射为总线,在这种情况下,您有时需要向硬件添加一些额外的东西(如二极管+电容器,或 LATCH 或 (DE)MUX ...) 以避免毛刺
我想回答我自己的问题,因为在这个问题上得到一个可靠的答案是令人沮丧的,而且在你进行真实世界测试之前,结果并不明显。 Old_timer 的评论被证明是最有帮助的。
void setup(){
#define FLASH_ACR (*(volatile uint32_t *)(0x40022000))
FLASH_ACR = 0b110010; //enable flash prefetch and wait states
pinMode(PA8, OUTPUT);
__asm__ volatile (
"ldr r0, =(0x4001080C) \n\t" //GPIOA ODR
"ldr r1, =(1<<8) \n\t" //turn on PA8
"ldr r2, =0 \n\t" //turn off PA8
".loop: \n\t"
"str r1, [r0] \n\t" // ON and OFF commands are unrolled (repeated) about 100 times
"str r2, [r0] \n\t" // inside the loop
"b .loop \n\t"
); }
使用 72MHz 的 MCU 运行,我使用上面的代码在 PA8 上获得了非常接近 18MHz 的切换速度。据我了解,使用立即值或 XOR 指令可以更快地切换引脚(以及您可能做的其他事情),这是因为某些指令或某些编码方法使用较少的时钟周期,从而提高性能。
如果你也看STM32f103 PDF:
您将在第 20 页的第 2.3.21 节中看到 "I/Os on APB2 with up to 18 MHz toggling speed." 所以我想如果文档中提到它,我会在那里达到极限。如果您还浏览了第 66 页,您会看到一个漂亮的 table 和 "I/O AC characteristics",它表明您可以达到 50MHz。
所以在达到将近 18MHz 后,我决定将电路板超频到 128MHz,并在 PA8 上实现了几乎 32MHz 切换速度,引脚上有 1.6VDC。现在我很满意,感谢所有评论和帮助的人。我在这方面还是个初学者,但我想我现在已经了解了很多。