注释
|
这没有意义,因为 PORTA 是端口的输出锁存器。您描述的行为以 pic 为例,因为它有一个端口寄存器,其中输出锁存器和输入读取寄存器共享相同的地址。在 avr 或 Arm 或 …. 当这个寄存器有不同的地址时,这个写-读-修改不存在。Britt <notifications@github.com> schrieb am So.,2019 年 7 月 28 日,12:32:
|
|
在 AVR 上,它们确实共享相同的地址。PORTA 就是 PORTA,阅读或写作。 我只是坚持这个:
在 Compiler Explorer 中,得到了这个:
请注意,读取和写入 (34-0x20) 的地址是相同的。 此外, 来自 ATMega2560 的数据表(它与 ATMega328p 上的设置相同,但我正在研究 grbl 的 arduino mega 端口……所以这就是我手边的东西)
|
|
在 AVR 上,读取端口 (PINx) 与写入端口 (PORTx) 具有不同的地址。存在只有一个地址用于读取和写入 gpio 端口的 MCU。具有不同地址的端口只是一个锁存器,永远不会受到外部引脚状态的影响。因此,您尝试解决的问题在 avr/arm MCU 上不存在。Britt <notifications@github.com> schrieb am So.,2019 年 7 月 28 日,13:43:
|
|
呸!好吧,这就是我在早餐前发布的内容。? 我仍然认为第二部分是有效的……读取 PORTx 寄存器然后从不同的 ISR 将其写回应该引入竞争条件…… |
|
您制作的 foo 示例是错误的。这里有一个很大的编码问题,因为端口变量必须并且被声明为易变的。foo 没有资格在端口变量上操作,并且进一步的 volatile 作为函数参数具有与 volatile 内部代码不同的行为,作为函数参数的 volatile 可以被缓存或别名,其中禁止普通 volatile 代码延迟脉冲是出于速度原因. 由于该寄存器可以预先分配给专用寄存器,因此可以将 isr 设置为裸,以便它只需要 5 个 CPU 周期。由于输出端口几乎是输出专用的,所以没关系。唯一的问题是 arduino uno/micro/… 和步进器启用引脚。如果该引脚是输入,则问题不存在,但存在 sw 解决方法。st_shutdown() 必须等到 TCCR0B 为 0,或者必须在超时后将其设置为 0。这仅适用于使用 avr328 的步进延迟的慢速机器。Britt <notifications@github.com> schrieb am So.,2019 年 7 月 28 日,15:35:
|
我知道我之前的解释是错误的。现在,如果我很密集,请原谅我,但是……我不太明白 foo 是如何以某种方式不合格对端口变量进行操作的? 鉴于:
q 不需要不稳定;因为没有其他线程/ISR 可以访问它。 FWIW,喂这个:
编译器资源管理器生成这个:
我认为它在 volatile 函数参数情况下(将 q 从 r24 移动到堆栈)中做的事情的原因是 q 可以 – 原则上至少 – 可以在其他地方访问……即使它恰好是那个这里没有代码使该堆栈位置在其他地方可用。
那……似乎并没有真正发生? 打开步进脉冲延迟的实际编译的.elf 的反汇编:
再次……如果我很密集,请原谅我,但是……我不明白延迟 ISR 中的加载和掩码操作如何对性能产生重大影响:主 ISR 缩短了约 5 条指令,并且延迟的 ISR 变长了 ~ 5 条指令(或者我从反汇编编译器的输出中收集的)。是的,这确实会影响延迟的准确性……但我认为它不会产生有害影响 – 通常,如果需要延迟,稍微长一点应该可以吗?
这是假设标准的引出线配置。FWIW,我认为,向前推进(grbl-mega;ARM 上的 grbl),以在步长延迟上稍有延迟为代价,使其在技术上是正确的是更可取的。我当然可以理解 ‘328 版本在这里永远不会改变;因为芯片上没有空间可以改变 ISR 之间的引脚状态。 ? |
|
内联 2019-07-28 20:28 GMT+02:00,布里特 <notifications@github.com>:
> 你做的 foo 例子是错误的。我知道我之前的解释是错误的。现在,如果我很密集,请原谅我,但是……我不太明白 foo 是如何以某种方式不合格对端口变量进行操作的?给出:“` uint8_t foo(uint8_t q) { return (q & (1<<3))?(PORTA |= 1<<3):(PORTA &=~ (1<<3)); } “` q 不需要是易变的;因为没有其他线程/ISR 可以访问它。FWIW,喂这个:
抱歉,这是我的错,我在想 foo 使用指针而不是 jusr args,抱歉。
“` #define __AVR_ATmega2560__ #include <avr/io.h> #include <stdint.h> uint8_t foo(volatile uint8_t q) { return (q & (1<<3))?(PORTA |= 1<<3 ):(PORTA &=~ (1<<3)); } uint8_t bar(uint8_t q) { return (q & (1<<3))?(PORTA |= 1<<3):(PORTA &=~ (1<<3)); “` 编译器资源管理器生成此:“` __SP_H__ = 0x3e __SP_L__ = 0x3d __tmp_reg__ = 0 foo: push r28 push r29 push __tmp_reg__ in r28,__SP_L__ in r29,__SP_H__ std Y+1,r24 ldd r24,Y+1 sbrs r24,3 rjmp .L2 in r24,34-0x20 ori r24,lo8(8) rjmp .L4 .L2: in r24,34-0x20 andi r24,lo8(-9) .L4: out 34-0x20,r24 pop __tmp_reg__ pop r29 pop r28 ret bar: sbrs r24,3 rjmp .L6 in r24,34-0x20 ori r24,lo8(8) out 34-0x20,r24 ret .L6: in r24,
只是为了比较生成的代码,这里是使用 fastio.h 并且没有进一步优化的 hal_WriteSteps 的代码和反编译,例如使用相同或仅两个不同的端口和输出字节而不是顺序位。/** * 写入步进脉冲 * @param值 */ inline void hal_WriteSteps(uint8_t value) { WRITE(X_STEP_PIN, value&(1<<X_AXIS)); 写入(Y_STEP_PIN,值&(1<<Y_AXIS));写入(Z_STEP_PIN,值&(1<<Z_AXIS));写入(U_STEP_PIN,值&(1<<U_AXIS));写入(V_STEP_PIN,值&(1<<V_AXIS));写入(W_STEP_PIN,值&(1<<W_AXIS));} 0000a030 <hal_WriteSteps>: a030: 80 ff sbrs r24, 0 a032: 02 c0 rjmp .+4 ; 0xa038 <hal_WriteSteps+0x8> a034: 10 9a sbi 0x02, 0 ; 2 a036: 01 c0 rjmp .+2 ; 0xa03a <hal_WriteSteps+0xa> a038: 10 98 cbi 0x02, 0 ; 2 a03a: 81 ff sbrs r24, 1 a03c: 02 c0 rjmp .+4 ; 0xa042 <hal_WriteSteps+0x12> a03e: 11 9a sbi 0x02, 1 ; 2 a040: 01 c0 rjmp .+2 ; 0xa044 <hal_WriteSteps+0x14> a042: 11 98 cbi 0x02, 1 ; 2 a044: 82 ff sbrs r24, 2 a046: 02 c0 rjmp .+4 ; 0xa04c <hal_WriteSteps+0x1c> a048: 12 9a sbi 0x02, 2 ; 2 a04a: 01 c0 rjmp .+2 ; 0xa04e <hal_WriteSteps+0x1e> a04c: 12 98 cbi 0x02, 2 ; 2 a04e: 83 ff sbrs r24, 3 a050: 02 c0 rjmp .+4 ; 0xa056 <hal_WriteSteps+0x26> a052: 14 9a sbi 0x02, 4 ; 2 a054: 01 c0 rjmp 。+2; 0xa058 <hal_WriteSteps+0x28> a056: 14 98 cbi 0x02, 4 ; 2 a058: 84 ff sbrs r24, 4 a05a: 02 c0 rjmp .+4 ; 0xa060 <hal_WriteSteps+0x30> a05c: 13 9a sbi 0x02, 3 ; 2 a05e: 01 c0 rjmp .+2 ; 0xa062 <hal_WriteSteps+0x32> a060: 13 98 cbi 0x02, 3 ; 2 a062: 85 ff sbrs r24, 5 a064: 02 c0 rjmp .+4 ; 0xa06a <hal_WriteSteps+0x3a> a066: 15 9a sbi 0x02, 5 ; 2 a068: 08 95 ret a06a: 15 98 cbi 0x02, 5 ; 2 a06c: 08 95 退役 84 ff sbrs r24, 4 a05a: 02 c0 rjmp .+4 ; 0xa060 <hal_WriteSteps+0x30> a05c: 13 9a sbi 0x02, 3 ; 2 a05e: 01 c0 rjmp .+2 ; 0xa062 <hal_WriteSteps+0x32> a060: 13 98 cbi 0x02, 3 ; 2 a062: 85 ff sbrs r24, 5 a064: 02 c0 rjmp .+4 ; 0xa06a <hal_WriteSteps+0x3a> a066: 15 9a sbi 0x02, 5 ; 2 a068: 08 95 ret a06a: 15 98 cbi 0x02, 5 ; 2 a06c: 08 95 退役 84 ff sbrs r24, 4 a05a: 02 c0 rjmp .+4 ; 0xa060 <hal_WriteSteps+0x30> a05c: 13 9a sbi 0x02, 3 ; 2 a05e: 01 c0 rjmp .+2 ; 0xa062 <hal_WriteSteps+0x32> a060: 13 98 cbi 0x02, 3 ; 2 a062: 85 ff sbrs r24, 5 a064: 02 c0 rjmp .+4 ; 0xa06a <hal_WriteSteps+0x3a> a066: 15 9a sbi 0x02, 5 ; 2 a068: 08 95 ret a06a: 15 98 cbi 0x02, 5 ; 2 a06c: 08 95 退役 01 c0 rjmp .+2 ; 0xa062 <hal_WriteSteps+0x32> a060: 13 98 cbi 0x02, 3 ; 2 a062: 85 ff sbrs r24, 5 a064: 02 c0 rjmp .+4 ; 0xa06a <hal_WriteSteps+0x3a> a066: 15 9a sbi 0x02, 5 ; 2 a068: 08 95 ret a06a: 15 98 cbi 0x02, 5 ; 2 a06c: 08 95 退役 01 c0 rjmp .+2 ; 0xa062 <hal_WriteSteps+0x32> a060: 13 98 cbi 0x02, 3 ; 2 a062: 85 ff sbrs r24, 5 a064: 02 c0 rjmp .+4 ; 0xa06a <hal_WriteSteps+0x3a> a066: 15 9a sbi 0x02, 5 ; 2 a068: 08 95 ret a06a: 15 98 cbi 0x02, 5 ; 2 a06c: 08 95 退役
_______________________________________________________________________________________
> 延迟脉冲是出于速度原因而产生的。由于该寄存器可以预先分配给专用寄存器,因此可以将 isr 设置为裸,以便它只需要 5 个 CPU 周期。那……似乎并没有真正发生?反汇编实际编译的 .elf 并打开步进脉冲延迟:“` 0000501a <__vector_21>:// 在 STEP_PULSE_DELAY 时间段结束后启动。与正常操作一样,ISR TIMER2_OVF 中断 // 将在适当的 settings.pulse_microseconds 之后触发。// 方向、步进脉冲和步进完成事件之间的新时序在 // st_wake_up() 例程中设置。ISR(TIMER0_COMPA_vect) { 501a: 1f 92 push r1 501c: 0f 92 push r0 501e: 0f b6 in r0, 0x3f ; 63 5020: 0f 92 推 r0 5022: 11 24 eor r1, r1 5024: 0b b6 in r0, 0x3b ; 59 5026: 0f 92 推 r0 5028: 8f 93 推 r24 502a: ef 93 推 r30 502c: ff 93 推 r31 #ifdef DEFAULTS_RAMPS_BOARD STEP_PORT(0) = st.step_bits[0]; //开始步进脉冲。502e: ee ef ldi r30, 0xFE; 254 5030:fc e0 ldi r31,0x0C;12 5032: 84 85 ldd r24, Z+12 ; 0x0c 5034:81 bb 输出 0x11,r24;17 STEP_PORT(1) = st.step_bits[1]; //开始步进脉冲。5036: 85 85 ldd r24, Z+13 ; 0x0d 5038:81 bb 输出 0x11,r24;17 STEP_PORT(2) = st.step_bits[2]; //开始步进脉冲。503a: 86 85 ldd r24, Z+14 ; 0x0e 503c: 80 93 0b 01 sts 0x010B, r24 ; 0x80010b <__TEXT_REGION_LENGTH__+0x70010b> #endif // 坡道板 } 5040:ff 91 pop r31 5042: ef 91 pop r30 5044: 8f 91 pop r24 5046: 0f 90 pop r0 5048: 0b 出来 0x3b, r0 ; 59 504a: 0f 90 pop r0 504c: 0f 出来 0x3f, r0 ; 63 504e: 0f 90 pop r0 5050: 1f 90 pop r1 5052: 18 95 reti “` 再次…请原谅我,如果我是密集的,但是…我不知道如何进行加载和屏蔽操作在延迟的 ISR 中对性能有重大影响:主 ISR 缩短了 ~ 5 条指令,而延迟的 ISR 变得更长了 ~ 5 条指令(或者我从反汇编编译器的输出中收集的)。是的,这确实会影响延迟的准确性……但我认为它不会产生有害影响 – 通常,如果需要延迟,稍微长一点应该可以吗?> 由于输出端口几乎是输出专用的,所以没关系。这是假设标准的引出线配置。FWIW,我认为,向前推进(grbl-mega;ARM 上的 grbl),以在步长延迟上稍有延迟为代价,使其在技术上是正确的是更可取的。我当然可以理解 ‘328 版本在这里永远不会改变;因为芯片上没有剩余空间来容纳 _could_ 改变 ISR 之间的引脚状态的东西。
当然是。如果使用设置脉冲延迟并且 $1=0 那么步进器可能永远不会因为引脚覆盖而被禁用。stop 和 之间的时序窗口是 160 个时钟周期,在一些 e-stop/halt/… 事件之后的 110 条指令,这很少见但可能。是的,dir/step 端口必须意识到这个问题,最好是将输入用于其余的或不改变的东西或硬件覆盖的引脚。我仍然使用这段代码,MCU_ISR(TIMER0_OVF_vect) { hal_WriteSteps((step_port_invert_mask)); TCCR0B = 0; // 禁用 Timer0 以防止在不需要时重新进入此中断。} MCU_ISR(TIMER0_COMPA_vect) { // if(settings.pulse_delay) hal_WriteSteps(st.step_bits); } 。,它生成的代码如下:00003978 <__vector_23>: 3978: 1f 92 push r1 397a: 0f 92 push r0 397c: 0f b6 in r0, 0x3f ; 63 397e: 0f 92 push r0 3980: 11 24 eor r1, r1 3982: 0b b6 in r0, 0x3b ; 59 3984: 0f 92 推 r0 3986: 2f 93 推 r18 3988: 3f 93 推 r19 398a: 4f 93 推 r20 398c: 5f 93 推 r21 398e: 6f 93 推 r22 3990: 7f 93 推 r23 3992: r24 9339 推 8f 9339 : 9f 93 推 r25 3996: af 93 推 r26 3998: bf 93 推 r27 399a: ef 93 推 r30 399c: ff 93 推 r31 399e: 80 91 49 06 lds r24,0x0649;0x800649 <step_port_invert_mask> 39a2: 0e 94 18 50 call 0xa030 ; 0xa030 <hal_WriteSteps> 39a6: 15 bc out 0x25, r1 ; 37 39a8: ff 91 pop r31 39aa: ef 91 pop r30 39ac: bf 91 pop r27 39ae: af 91 pop r26 39b0: 9f 91 pop r25 39b2: 8f 91 pop r24 39b4: 7f 91 pop r23 39b6: 6f 91 pop r22 39b6: 6f 91 pop r22 : 5f 91 pop r21 39ba: 4f 91 pop r20 39bc: 3f 91 pop r19 39be: 2f 91 pop r18 39c0: 0f 90 pop r0 39c2: 0b 输出 0x3b, r0 ; 59 39c4: 0f 90 pop r0 39c6: 0f 出 0x3f, r0 ; 63 39c8: 0f 90 pop r0 39ca: 1f 90 pop r1 39cc: 18 95 reti 没有错,但是,它消耗了不必要的 3us 时间。由于在主中断代码中触发了两个中断,因此浪费了 6us 的时间。
|


编辑第一部分无效。在阅读回复时,为了清楚起见,我将它留在这里,但它确实应该被忽略。
用于设置 IO 端口位的方法会破坏端口上其他位的任何上拉电阻设置。
使用单个语句同时设置多个位会导致生成修改意外位的机器代码。
假设端口 A 上的位 7 被用作输入,带有上拉电阻;位 2 和位 4 用作输出,初始化如下:
好的,现在一些外部硬件将引脚 A7 拉低。
然后,当它保持低电平时,设置 A2 和 A4 的位值(高或低无关紧要……但我会显示高的情况)。
PORTA |= ((1<<2) | (1<<4));这导致读取端口的机器代码;按位或使用 ((1<<2) | (1<<4)) 的值进行运算;并将其写回。即使它是一个用于存储要打开的位的变量,并且该变量中只有一个位打开,也会发生这种情况。
问题是第 7 位的值被读取为低值,写回该值会导致禁用该引脚上的上拉电阻。
为避免这种情况,需要单独写入引脚的位:
编译为:
对于变量,必须使用 if 语句:
请注意,使用 ?: 运算符然后使用运算符的结果值会生成具有此问题的机器代码:
编译为:
请注意生成的单独的 in、ori 和 out 指令。
编辑我认为其余的仍然有效:
此外,还发现了一个相关问题:
STEP_PULSE_DELAY 可能会破坏也在主 ISR(TIMER1_COMPA_vect)触发和延迟 ISR(TIMER0_COMPA_vect)触发之间改变状态的端口上的任何位。
st.step_bits = (STEP_PORT & ~STEP_MASK) | st.step_outbits; // Store out_bits to prevent overwriting.STEP_PORT = st.step_bits; // Begin step pulse.…我认为这样做的正确方法是不存储整个端口值,而只存储应该脉冲的步进位。此外,必须注意使用的代码解析为 sbi 和 cbi 指令,这样端口上的任何上拉电阻都不会意外启用或禁用。
st.step_bits = st.step_outbits; // Store out_bits to for delay.对于非延迟情况,显然,只需像这样设置位:
根据 godbolt.org 上的编译器资源管理器,这会产生如下内容:
…这不应该破坏任何位。
FWIW,我正在努力修补我的 grbl-mega 分支来解决这个问题。