3

我正在为 AVR Atmega328p 微控制器编写代码。微控制器应该读取编码器并根据编码器的旋转增加或减少 r23。不幸的是,此时,无论我转动编码器的方向如何,输出只会减少直到达到 0,然后从 255 开始。

我的代码相当简单,并且基于结合了编码器先前状态和当前状态的表查找值。如果前一个状态和当前状态没有结合起来创建一个有效的回合,则会返回一个错误并且代码什么也不做。如果发生有效的状态更改,则通过 r24 将 1 或 -1 添加到 r23。

我让微控制器读取编码器没有问题,但我不知道如何防止 r23 溢出。我的问题是当我达到 255 并加 1 时,寄存器溢出并变为 0。我不希望寄存器变为 0;我希望它保持在 255,直到我沿相反方向旋转编码器。我对 0 也有同样的问题。如果寄存器为 0 并且我添加 -1,我不希望寄存器转到 255,我希望它保持在 0 直到我沿相反方向旋转它。

我对跳出框框思考没有问题。如果您有解决方案或想法,请随时发布。

;**** A P P L I C A T I O N   N O T E  *************************************
;*
;* Title:       
;* Version:     
;* Last updated:    
;* Target:  AVR Dragon  
;*
;*
;* DESCRIPTION
;*
;*.device   ATmega328P @ 1M  clock speed
;* 
;* This is a simple program to test an optical encoder
;***************************************************************************

.include "m328Pdef.inc"
.org 0x0000
    jmp RESET   ;Reset Handle

.org 0x0008
    jmp Interrupt1 ; PCINT1 Handler

enc_states:
.db 0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0 

RESET:

;Setup stack pointer
    ldi temp, low(RAMEND)   
    out SPL, temp
    ldi temp, high(RAMEND)
    out SPH, temp

//Set Port B pins to output
    ser     temp            ; Set Register Rd <-- 0xff (output)
    out     DDRB,temp       ; set all PORTB bits as output

//Clear analog input pins and enable pull ups on Pin 0 and 1 (Port C)
    clr     temp
    out     DDRC, temp      ;all pins input
    ldi     temp, (1<<PC1)|(1<<PC0)
    out     PORTC,temp      ;Enable pullups on Pin 0 and 1

//Set Port D pins to output
    ser     temp            ; Set Register Rd <-- 0xff
    out     DDRD,temp       ; set all PORTD bits as output

//Enable encoder pins interrupt sources (Encoder 1)
    ldi     temp, (1<<PCINT9)|(1<<PCINT8)
    sts     PCMSK1, temp

//Enable encoder pins interrupt sources (Encoder 2)
//  ldi     temp, (1<<PCINT11)|(1<<PCINT10)
//  sts     PCMSK1, temp

//Enable pin change interrupts
    ldi     temp, (1<<PCIE1)
    sts     PCICR, temp

//Enable global interrupts
    sei

//Lookup table initial value
    ldi     ZL, 0x00   ;lookup table index and initial state

.def temp   = r16
    clr     r25
    clr     r24
    clr     r23

loop:
    out PORTB, r23
    jmp loop

Interrupt1:
// Push SREG, etc
    in      r25, PORTC  ;encoder value from PORTC

    ldi   ZH, High(enc_states*2)   ; setup Z pointer hi
    ldi   ZL, Low (enc_states*2)   ; setup Z pointer lo
    rol     r22     ;remember previous state and shift left twice
    rol     r22
    cbr     r25, 0xFC  ;clear encoder bits 7:2
    mov     r21,r25
    or      r25, r22   ;combine encoder bits with old bits
    cbr     r25, 0xF0  ;clear bits 7:4 for table lookup
    mov     r22, r25   ;save table lookup value
    mov     ZL, r25    ;load index value into table
    lpm     r24, z      ;get result
    add     r23,r24

// Pop SREG, etc.
    reti
4

2 回答 2

3

通过利用进位标志可以非常简单地完成“饱和”,例如:

  mov __tmp_reg__, r23
  add __tmp_reg__, r24   ; do the addition

  brcs saturated          ; if the carry flag is set we have an overflow and should discard the result

    mov r23, __tmp_reg__ ; there was no overflow so we store the result

  saturated:          

用上面的代码替换add r23,r24ISR 末尾的 ,你应该没问题。(显然,您可能需要更改__tmp_reg__一些可以用作临时存储的寄存器。)

考虑到r24可能是正数、负数或零,所有情况都可以通过稍微扩展上述原则来正确处理:

  mov __tmp_reg__, r23

  tst r24 

  breq doreturn  ; if r24 == 0 we have nothing to do and may just return

  brmi subtract  ; if r24 is 'negative' we need to subtract

    add __tmp_reg__, r24  ; if r24 is not negative we just add
    rjmp store            ; and go to where the result may be stored

  subtract:

    neg r24  ; r24 := -r24
    sub __tmp_reg__, r24   ; do the subtraction

  store:

  brcs doreturn          ; if the carry flag is set we have an overflow and should discard the result

    mov r23, __tmp_reg__ ; there was no overflow so we store the result

  doreturn:

仔细查看您的代码,在我看来,当 Z 指针的计算完成时,其中还有另一个“故障”:

ldi   ZH, High(enc_states*2)   ; setup Z pointer hi
ldi   ZL, Low (enc_states*2)   ; setup Z pointer lo

mov     ZL, r25    ;load index value into table

看起来有问题:Z地址的下部只是被忽略并被索引值覆盖。Low (enc_states*2)如果碰巧没有,这将造成麻烦0;您可能需要做add ZL, r25and adc ZH, __zero_reg__(16 位加法)而不是mov ZL, r25.

另一个可能会降低您日常工作复杂性的想法:

增量旋转编码器的输出可以解释为某种同步串行数据:一个输出(比如“A”)代表“时钟”信号,而另一个输出(“B”)代表“数据”信号。

您可以自由选择哪个输出用于哪个信号,以及您选择的“时钟”极性。那么算法就相当简单了:

  1. (去抖动和)检测“时钟”信号上的上升(或下降,您可以选择)转换
  2. 一旦检测到“时钟”边沿,只需读取“数据”信号的电平 - 这里不需要去抖动
  3. 然后读取的单个“数据”位直接指示编码器刚刚进入的方向:“0”或“1”,一个方向或另一个方向。

在伪代码中,这可能看起来像:

bit lastClockState;

void readEncoder() {

    bit currentClockState = readClockPin();

    if ( lastClockState == 1 && currentClockState == 0 ) {
        // A (falling) edge was detected...

        // Get the direction of the rotation:
        bit direction = readDataPin();

        if ( direction == 1 ) {
            value++;
        } else {
            value--;
        }
    }

    lastClockState = currentClockState; // Update the lastClockState for the next iteration

}

例如,让它每 10 毫秒执行一次,您已经“免费”拥有了一些简约的去弹跳。

(顺便说一句,总结许多其他人之前吸取的教训:不要尝试使用一些外部/引脚更改中断来检测由任何机械开关或机械编码器产生的(非去抖动)信号转换。机械元件的弹跳特性将确保这永远不会按预期工作。)

于 2013-03-06T13:24:55.640 回答
2

您只需要为边界情况添加一些测试。多年来我没有进行任何组装,这段代码可能与您的架构无关,但它类似于:

    lpm     r24, z      ;get result
    cmp     r23, 0
    je      rot_lo      ; if val is 0
    cmp     r23, 255
    je      rot_hi      ; if val is 255
    jmp     rot_add

rot_lo:
    cmp     r24, 0
    jl      rot_ret     ; don't add if delta less than 0
    jmp     rot_add

rot_hi:
    cmp     r24, 0
    jg      rot_ret     ; don't add if delta greater than 0
    jmp     rot_add     ; (or just fall through here)

rot_add:
    add     r23, r24

rot_ret:
    reti
于 2013-03-05T22:26:58.543 回答