Contact me: hankecnc@gmail.com

破坏上拉电阻。 #697

推推 grbl 3年前 (2023-01-23) 228次浏览

关闭
bdurbrow 打开了这个问题 2019 年 7 月 28 日 · 7条评论
关闭

破坏上拉电阻。#697

bdurbrow 打开了这个问题 2019 年 7 月 28 日 · 7条评论

注释

破坏上拉电阻。 #697
黑眉 评论了 2019 年 7 月 28 日  

编辑第一部分无效。在阅读回复时,为了清楚起见,我将它留在这里,但它确实应该被忽略。

用于设置 IO 端口位的方法会破坏端口上其他位的任何上拉电阻设置。
使用单个语句同时设置多个位会导致生成修改意外位的机器代码。

假设端口 A 上的位 7 被用作输入,带有上拉电阻;位 2 和位 4 用作输出,初始化如下:

DDRA = (1<<2) | (1<<4);
PORTA = (1<<7);

好的,现在一些外部硬件将引脚 A7 拉低。

然后,当它保持低电平时,设置 A2 和 A4 的位值(高或低无关紧要……但我会显示高的情况)。
PORTA |= ((1<<2) | (1<<4));

这导致读取端口的机器代码;按位或使用 ((1<<2) | (1<<4)) 的值进行运算;并将其写回。即使它是一个用于存储要打开的位的变量,并且该变量中只有一个位打开,也会发生这种情况。

问题是第 7 位的值被读取为低值,写回该值会导致禁用该引脚上的上拉电阻。

为避免这种情况,需要单独写入引脚的位:

PORTA |= (1<<2);
PORTA |= (1<<4);

编译为:

  sbi 34-0x20,2
  sbi 34-0x20,4

对于变量,必须使用 if 语句:

  if(q & (1<<2)) PORTA |= 1<<2; else PORTA &=~ (1<<2);
  if(q & (1<<4)) PORTA |= 1<<4; else PORTA &=~ (1<<4);

请注意,使用 ?: 运算符然后使用运算符的结果值会生成具有此问题的机器代码:

uint8_t foo(uint8_t q)
{
    return (q & (1<<3))?(PORTA |= 1<<3):(PORTA &=~ (1<<3));
}

编译为:

foo:
  sbrs r24,3
  rjmp .L2
  in r24,34-0x20
  ori r24,lo8(8)
  out 34-0x20,r24
  ret
.L2:
  in r24,34-0x20
  andi r24,lo8(-9)
  out 34-0x20,r24
  ret

请注意生成的单独的 in、ori 和 out 指令。

编辑认为其余的仍然有效:

此外,还发现了一个相关问题:

STEP_PULSE_DELAY 可能会破坏也在主 ISR(TIMER1_COMPA_vect)触发和延迟 ISR(TIMER0_COMPA_vect)触发之间改变状态的端口上的任何位。

  1. 主 ISR 将整个端口的值存储到 st.step_bits 中:
    st.step_bits = (STEP_PORT & ~STEP_MASK) | st.step_outbits; // Store out_bits to prevent overwriting.
  2. 发生了一些其他事件(ISR 在 sei() 调用后触发?)并且未分配给步进输出的端口位之一改变了状态。
  3. 延迟的 ISR(TIMER0_COMPA_vect) 触发,并从 st.step_bits 中读取相关位的旧值。
  4. 这些位随后被 ISR 写入而被破坏。
    STEP_PORT = st.step_bits; // Begin step pulse.

…我认为这样做的正确方法是不存储整个端口值,而只存储应该脉冲的步进位。此外,必须注意使用的代码解析为 sbi 和 cbi 指令,这样端口上的任何上拉电阻都不会意外启用或禁用。

  1. 在主 ISR(TIMER1_COMPA_vect)中:
    st.step_bits = st.step_outbits; // Store out_bits to for delay.
  2. 在延迟的 ISR(TIMER0_COMPA_vect)中:
      if(st.step_bits & (1<<STEP_BIT(X_AXIS))) STEP_PORT |= (1<<STEP_BIT(X_AXIS)); else STEP_PORT &= ~(1<<STEP_BIT(X_AXIS));
      if(st.step_bits & (1<<STEP_BIT(Y_AXIS))) STEP_PORT |= (1<<STEP_BIT(Y_AXIS)); else STEP_PORT &= ~(1<<STEP_BIT(Y_AXIS));
      if(st.step_bits & (1<<STEP_BIT(Z_AXIS))) STEP_PORT |= (1<<STEP_BIT(Z_AXIS)); else STEP_PORT &= ~(1<<STEP_BIT(Z_AXIS));

对于非延迟情况,显然,只需像这样设置位:

      if(st.step_outbits & (1<<STEP_BIT(X_AXIS))) STEP_PORT |= (1<<STEP_BIT(X_AXIS)); else STEP_PORT &= ~(1<<STEP_BIT(X_AXIS));
      if(st.step_outbits & (1<<STEP_BIT(Y_AXIS))) STEP_PORT |= (1<<STEP_BIT(Y_AXIS)); else STEP_PORT &= ~(1<<STEP_BIT(Y_AXIS));
      if(st.step_outbits & (1<<STEP_BIT(Z_AXIS))) STEP_PORT |= (1<<STEP_BIT(Z_AXIS)); else STEP_PORT &= ~(1<<STEP_BIT(Z_AXIS));

根据 godbolt.org 上的编译器资源管理器,这会产生如下内容:

sbrs r24,3
  rjmp .L2
  sbi 34-0x20,3
  rjmp .L3
.L2:
  cbi 34-0x20,3

…这不应该破坏任何位。

FWIW,我正在努力修补我的 grbl-mega 分支来解决这个问题。

破坏上拉电阻。 #697
危机 评论了 2019 年 7 月 28 日 通过电子邮件
破坏上拉电阻。 #697
作者

在 AVR 上,它们确实共享相同的地址。PORTA 就是 PORTA,阅读或写作。

我只是坚持这个:

PORTA |= 0x03;

在 Compiler Explorer 中,得到了这个:

in r25,34-0x20
ori r25,lo8(3)
out 34-0x20,r25

请注意,读取和写入 (34-0x20) 的地址是相同的。

此外,
uint8_t a=PORTA;
生成
in r25,34-0x20
…相同的地址。

来自 ATMega2560 的数据表(它与 ATMega328p 上的设置相同,但我正在研究 grbl 的 arduino mega 端口……所以这就是我手边的东西)

每个端口引脚由三个寄存器位组成:DDxn、PORTxn 和 PINxn。

DDxn 位在 DDRx I/O 地址访问,PORTxn 位在 PORTx I/O 地址访问,PINxn 位在 PINx I/O 地址访问。

DDRx 寄存器中的 DDxn 位选择该引脚的方向。如果 DDxn 写为逻辑 1,则 Pxn 配置为输出引脚。如果 DDxn 写入逻辑零,则 Pxn 配置为输入引脚。
当引脚配置为输入引脚时,如果 PORTxn 写入逻辑 1,则上拉电阻被激活。要关闭上拉电阻,必须将 PORTxn 写入逻辑零或必须将引脚配置为输出引脚。当复位条件激活时,端口引脚处于三态,即使没有时钟在运行。
如果在引脚配置为输出引脚时将 PORTxn 写入逻辑 1,则端口引脚被驱动为高电平(一)。如果在引脚配置为输出引脚时将 PORTxn 写入逻辑零,则端口引脚被驱动为低电平(零)。

破坏上拉电阻。 #697
危机 评论了 2019 年 7 月 28 日 通过电子邮件
破坏上拉电阻。 #697
作者

呸!好吧,这就是我在早餐前发布的内容。?
在我体内加入一些食物和咖啡因重新阅读数据表表明您对第一部分的看法是正确的。

我仍然认为第二部分是有效的……读取 PORTx 寄存器然后从不同的 ISR 将其写回应该引入竞争条件……

破坏上拉电阻。 #697
危机 评论了 2019 年 7 月 28 日 通过电子邮件
破坏上拉电阻。 #697
作者

您制作的 foo 示例是错误的。

我知道我之前的解释是错误的。现在,如果我很密集,请原谅我,但是……我不太明白 foo 是如何以某种方式不合格对端口变量进行操作的?

鉴于:

uint8_t foo(uint8_t q)
{
    return (q & (1<<3))?(PORTA |= 1<<3):(PORTA &=~ (1<<3));
}

q 不需要不稳定;因为没有其他线程/ISR 可以访问它。

FWIW,喂这个:

#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,34-0x20
  andi r24,lo8(-9)
  out 34-0x20,r24
  ret

我认为它在 volatile 函数参数情况下(将 q 从 r24 移动到堆栈)中做的事情的原因是 q 可以 – 原则上至少 – 可以在其他地方访问……即使它恰好是那个这里没有代码使该堆栈位置在其他地方可用。


延迟脉冲是出于速度原因而产生的。由于该寄存器可以
预先分配给专用寄存器,因此可以将 isr 设置为裸,以便
它只需要 5 个 CPU 周期。

那……似乎并没有真正发生?

打开步进脉冲延迟的实际编译的.elf 的反汇编:


0000501a <__vector_21>:
  // initiated after the STEP_PULSE_DELAY time period has elapsed. The ISR TIMER2_OVF interrupt
  // will then trigger after the appropriate settings.pulse_microseconds, as in normal operation.
  // The new timing between direction, step pulse, and step complete events are setup in the
  // st_wake_up() routine.
  ISR(TIMER0_COMPA_vect)
  {
    501a:	1f 92       	push	r1
    501c:	0f 92       	push	r0
    501e:	0f b6       	in	r0, 0x3f	; 63
    5020:	0f 92       	push	r0
    5022:	11 24       	eor	r1, r1
    5024:	0b b6       	in	r0, 0x3b	; 59
    5026:	0f 92       	push	r0
    5028:	8f 93       	push	r24
    502a:	ef 93       	push	r30
    502c:	ff 93       	push	r31
    #ifdef DEFAULTS_RAMPS_BOARD
      STEP_PORT(0) = st.step_bits[0]; // Begin step pulse.
    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       	out	0x11, r24	; 17
      STEP_PORT(1) = st.step_bits[1]; // Begin step pulse.
    5036:	85 85       	ldd	r24, Z+13	; 0x0d
    5038:	81 bb       	out	0x11, r24	; 17
      STEP_PORT(2) = st.step_bits[2]; // Begin step pulse.
    503a:	86 85       	ldd	r24, Z+14	; 0x0e
    503c:	80 93 0b 01 	sts	0x010B, r24	; 0x80010b <__TEXT_REGION_LENGTH__+0x70010b>
    #endif // Ramps Board
  }
    5040:	ff 91       	pop	r31
    5042:	ef 91       	pop	r30
    5044:	8f 91       	pop	r24
    5046:	0f 90       	pop	r0
    5048:	0b be       	out	0x3b, r0	; 59
    504a:	0f 90       	pop	r0
    504c:	0f be       	out	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 版本在这里永远不会改变;因为芯片上没有空间可以改变 ISR 之间的引脚状态。

?

破坏上拉电阻。 #697
危机 评论了 2019 年 7 月 29 日 通过电子邮件
喜欢 (0)