'' ================================================================================================= '' '' File....... jm_dynamixel_2x0.spin '' Purpose.... Interface for Dynamixel actuators '' -- this version for 2.0 protocol '' -- send end of file for RAM/EEPROM constants and connections '' -- NOT COMPLETE, this is a work in progress '' Author..... Jon "JonnyMac" McPhalen '' Copyright (C) 2013-2019 Jon McPhalen '' -- see below for terms of use '' E-mail..... jon@jonmcphalen.com '' Started.... '' Updated.... 11 JUN 2019 '' -- added friendly constant names (sans '_0') '' -- corrected use of timeout timing '' -- removed requirement for FET buffer in TTL mode '' -- updated to support TTL and RS-485 interfaces '' '' ================================================================================================= {{ Reference: http://emanual.robotis.com/docs/en/dxl/protocol2/ }} con { fixed io pins } RX1 = 31 ' programming / terminal TX1 = 30 SDA1 = 29 ' eeprom / i2c SCL1 = 28 con #0, BR_9600, BR_57K, BR_115K, BR_1M, BR_2M, BR_3M, BR_4x5M ' legal baud rates BUF_SIZE = 128 ' power of 2 (2..512) BUF_MASK = BUF_SIZE - 1 TXE_US = 10 ' txe delay (for MAX485 chip) GLOBAL = $FE ' global device address var long cog ' cog flag/id long state ' idle, tx, rx long txlen ' bytes to tx long rxlen ' bytes rx'd long pinz ' txe, tx, and rx pins long txeticks long bitticks ' bit timing (ticks) long timeout ' timeout period (ticks) long txbufaddr ' hub address of txbuf long rxbufaddr ' hub address of rxbuf long txidx ' for tx_putval and tx_put byte txbuf[BUF_SIZE] ' transmit buffer byte rxbuf[BUF_SIZE] ' receive buffer byte rxerror ' error bits (last transaction) pub null ' This is not a top-level object pub start_ttl(sio, baud) '' Start Dynamixel object in TTL mode '' -- sio is serial i/o pin '' -- baud is legal dyanmixel baud rate '' * suggested max is 115_200 return startx(-1, sio, sio, baud) ' default baud rate pub start_485(txe, tx, rx, baud) '' Start Dynamixel object in RS-485 mdoe '' -- txe is transmit enable pin '' -- tx is transmit pin '' -- rx is receive pin '' -- baud is baud rate for coms (typically 57_600) '' * suggested max is 115_200 return startx(txe, tx, rx, baud) ' default baud rate pri startx(txe, tx, rx, baud) '' Start Dynamixel object '' -- txe is transmit enable pin for RS-485 (-1 for TTL mode) '' -- tx is transmit pin '' -- rx is receive pin (must match tx when using TTL) '' -- baud is baud rate for coms (typically 57_600) stop ' stop UART driver pinz.byte[0] := txe ' setup io pins pinz.byte[1] := tx pinz.byte[2] := rx txeticks := clkfreq / 1_000_000 * TXE_US ' set txe delay (in ticks) bitticks := clkfreq / baud ' set bit time for baud rate timeout := (clkfreq / 1000) >> 4 ' set for 2ms timeout txbufaddr := @txbuf ' calc buffer addresses rxbufaddr := @rxbuf cog := cognew(@fasthds, @state) + 1 ' start UART cog return cog pub stop '' Stops UART cog if (cog) ' running? cogstop(cog - 1) ' shut it down cog := 0 tx_clear ' clear buffers rx_clear pub set_baud(id, baudidx) '' Sets baud rate for index wr_byte(id, P_BAUD_RATE, baudidx) con { --------------- } { Shell Methods } { --------------- } pub rd_byte(id, addr) : value '' Reads byte from address in device #id if (read_data(id, addr, 1, @value) == id) ' read from device return value ' return if good read else return E_ID ' return error on timeout pub rd_word(id, addr) : value '' Reads word from address in device #id if (read_data(id, addr, 2, @value) == id) return value else return E_ID pub rd_long(id, addr) : value '' Reads long from address in device #id if (read_data(id, addr, 4, @value) == id) return value else return E_ID pub wr_byte(id, addr, value) : check '' Writes value.byte[0] (8 bits) to addr in device #id '' -- returns value on good write if ((value < $00) or (value > $FF)) return E_VALUE check := write_data(id, addr, 1, @value) if (check == id) return value else return check pub wr_word(id, addr, value) : check '' Writes value.word[0] (16 bits) to addr in device #id '' -- returns value on good write if ((value < $0000) or (value > $03FF)) return E_VALUE check := write_data(id, addr, 2, @value) if (check == id) return value else return check pub wr_long(id, addr, value) : check '' Writes 32-bit value to addr in device #id '' -- returns value on good write check := write_data(id, addr, 4, @value) if (check == id) return value else return check pub reg_byte(id, addr, value) : check '' Registers value.byte[0] for addr in device #id '' -- returns -1 if bad id '' -- returns -2 if bad value (system max only, check docs for register max) '' -- returns -3 if value out of range for register '' -- requires .action() to execute if ((value < $00) or (value > $FF)) return E_VALUE check := reg_write(id, addr, 1, @value) if (check == id) return value else return check pub reg_word(id, addr, value) : check '' Registers value.word[0] for addr in device #id '' -- returns -1 if bad id '' -- returns -2 if bad value (system max only, check docs for register max) '' -- returns -3 if value out of range for register '' -- requires .action() to execute if ((value < $0000) or (value > $03FF)) return E_VALUE check := reg_write(id, addr, 2, @value) if (check == id) return value else return check pub reg_long(id, addr, value) : check '' Registers value for addr in device #id '' -- returns -1 if bad id '' -- returns -2 if bad value (system max only, check docs for register max) '' -- returns -3 if value out of range for register '' -- requires .action() to execute check := reg_write(id, addr, 4, @value) if (check == id) return value else return check con { --------------------- } { Dynamixel Functions } { --------------------- } con { dynamixel instructions } INST_PING = $01 INST_READ = $02 INST_WRITE = $03 INST_REG_WRITE = $04 INST_ACTION = $05 INST_RESET = $06 INST_REBOOT = $08 ' not implemented INST_STATUS_RETURN = $55 INST_SYNC_READ = $82 ' not implemented INST_SYNC_WRITE = $83 ' not implemented INST_BULK_READ = $92 ' not implemented INST_BULK_WRITE = $93 ' not implemented dat Header byte $FF, $FF, $FD, $00 pub ping(id, p_model, p_swver) : cs '' Pings actuator at id '' -- returns id if present (global ping not supported) '' -- returns -1 if not present '' -- p_model is pointer to word (device model) '' -- p_swver is pointer to byte (sofware version) '' -- use rx_error() to check device status after successful ping if ((id < 0) or (id > $FC)) ' bad id? return E_ID tx_clear bytemove(@txbuf, @Header, 4) ' set header txbuf[04] := id ' set id txbuf[05] := $03 ' length of packet txbuf[07] := INST_PING ' instruction cs := checksum(@txbuf) ' calculate checksum bytemove(@txbuf[8], @cs, 2) ' set packet checksum tx_execute(10, M_TXRX) ' send message if (state == M_IDLE) ' no timeout? if (rxlen == 14) ' correct response length? if ((rxbuf[04] == id) and (rxbuf[07] == $55)) ' correct id in response cs := rxbuf[12] | (rxbuf[13] << 8) ' get checksum from response if (checksum(@rxbuf) == cs) ' good checksum rxerror := rxbuf[08] ' save any error bits if (p_model > 0) ' pointer for model #? word[p_model] := rxbuf[09] | (rxbuf[10] << 8) if (p_swver > 0) ' poiner for sw version? byte[p_swver] := rxbuf[11] return id ' return id for good ping return E_ID ' return error for bad ping pub read_data(id, addr, len, p_dest) : cs | idx '' Read n bytes from device #id '' -- addr is beginning address in device '' -- len is number of bytes to read '' -- p_dest is pointer to destination '' -- use rx_error() to check device status if ((id < 0) or (id > $FC)) return E_ID tx_clear bytemove(@txbuf, @Header, 3) txbuf[04] := id txbuf[05] := 7 txbuf[07] := INST_READ txbuf[08] := addr.byte[0] txbuf[09] := addr.byte[1] txbuf[10] := len cs := checksum(@txbuf) bytemove(@txbuf[12], @cs, 2) tx_execute(14, M_TXRX) if (state == M_IDLE) if (rxlen == (len + 11)) if ((rxbuf[04] == id) and (rxbuf[07] == $55)) cs := rxbuf[len + 9] | (rxbuf[len + 10] << 8) if (checksum(@rxbuf) == cs) rxerror := rxbuf[08] idx := 9 repeat len byte[p_dest++] := rxbuf[idx++] return id return E_ID pub write_data(id, addr, len, p_src) : idx | cs '' Write n bytes to device #id '' -- reg is beginning register address in device '' -- len is number of bytes to write '' -- p_src is pointer to source bytes '' -- use rx_error() to check device status if ((id < 0) or (id == $FD) or (id => $FF)) return E_ID tx_clear bytemove(@txbuf, @Header, 3) txbuf[04] := id txbuf[05] := len + 5 txbuf[07] := INST_WRITE txbuf[08] := addr.byte[0] txbuf[09] := addr.byte[1] idx := 10 repeat len txbuf[idx++] := byte[p_src++] cs := checksum(@txbuf) bytemove(@txbuf[idx], @cs, 2) if (id == GLOBAL) tx_execute(len + 12, M_TX) rxerror := 0 return id tx_execute(len + 12, M_TXRX) if (state == M_IDLE) if (rxlen == 11) if ((rxbuf[04] == id) and (rxbuf[07] == $55)) cs := rxbuf[09] | (rxbuf[10] << 8) if (checksum(@rxbuf) == cs) rxerror := rxbuf[08] ifnot (rxerror & %0000_0101) ' range or data length error? return id else return E_RANGE return E_ID pub reg_write(id, addr, len, p_src) : idx | cs '' Register len bytes to device #id '' -- reg is beginning address in device '' -- len is number of bytes to register '' -- p_src is pointer to source bytes '' -- use rx_error() to check device status '' -- follow with action() to execute if ((id < 0) or (id == $FD) or (id => $FF)) return E_ID tx_clear bytemove(@txbuf, @Header, 3) txbuf[04] := id txbuf[05] := len + 5 txbuf[07] := INST_REG_WRITE txbuf[08] := addr.byte[0] txbuf[09] := addr.byte[1] idx := 10 repeat len txbuf[idx++] := byte[p_src++] cs := checksum(@txbuf) bytemove(@txbuf[idx], @cs, 2) if (id == GLOBAL) tx_execute(len + 12, M_TX) rxerror := 0 return id tx_execute(len + 12, M_TXRX) if (state == M_IDLE) if (rxlen == 11) if ((rxbuf[04] == id) and (rxbuf[07] == $55)) cs := rxbuf[09] | (rxbuf[10] << 8) if (checksum(@rxbuf) == cs) rxerror := rxbuf[08] ifnot (rxerror & %0000_0101) ' range or data length error? return id else return E_RANGE return E_ID pub action(id) | cs '' Initiate action setup by reg_write if ((id < 0) or (id == $FD) or (id => $FF)) return E_ID tx_clear bytemove(@txbuf, @Header, 3) txbuf[04] := id txbuf[05] := 3 txbuf[07] := INST_ACTION cs := checksum(@txbuf) bytemove(@txbuf[08], @cs, 2) if (id == GLOBAL) tx_execute(10, M_TX) rxerror := 0 return id tx_execute(10, M_TXRX) if (state == M_IDLE) if (rxlen == 11) if ((rxbuf[04] == id) and (rxbuf[07] == $55)) cs := rxbuf[09] | (rxbuf[10] << 8) if (checksum(@rxbuf) == cs) rxerror := rxbuf[08] return id return E_ID pub reset(id, mode) : cs '' Reset factory defaults in device #id '' -- mode $FF = reset all values -- WARNING: sets ID to #01, baud to 57.6K '' -- mode $01 = reset all values except ID '' -- mode $02 = reset all values except ID and BAUD if ((id < 0) or (id == $FD) or (id => $FF)) return E_ID if ((mode <> $FF) and (mode <> $01) and (mode <> $02)) return E_ID tx_clear bytemove(@txbuf, @Header, 3) txbuf[04] := id txbuf[05] := 4 txbuf[07] := INST_RESET txbuf[08] := mode cs := checksum(@txbuf) bytemove(@txbuf[09], @cs, 2) if (id == GLOBAL) tx_execute(11, M_TX) rxerror := 0 return id tx_execute(11, M_TXRX) if (state == M_IDLE) if (rxlen == 11) if ((rxbuf[04] == id) and (rxbuf[07] == $55)) cs := rxbuf[09] | (rxbuf[10] << 8) if (checksum(@rxbuf) == cs) rxerror := rxbuf[08] return id return E_ID con { ---------------- } { Helper Methods } { ---------------- } pub checksum(p_buf) : crc | len, idx '' Calculate checksum in dynamixel packet '' -- p_buf is pointer to packet buffer len := (byte[p_buf][5] | (byte[p_buf][6] << 8)) + 5 repeat idx from 0 to len-1 crc ^= (byte[p_buf][idx] << 8) repeat 8 crc <<= 1 if (crc & $1_0000) crc ^= $8005 crc &= $FFFF pub tx_execute(len, mode) '' Transmit tx buffer (len bytes) '' -- mode is M_TX (transmit only) or M_TXRX (transmit and receive) repeat while (state > M_IDLE) ' ensure not busy rx_clear ' clear rsponse data txlen := len ' set new tx len state := mode ' go! if (mode == M_TXRX) ' expecting response? repeat until (state =< M_IDLE) ' wait for it pub status '' Returns state of UART return state pub tx_clear '' Clear tx state and buffer state := M_IDLE bytefill(@txbuf, 0, BUF_SIZE) ' clear buffer txlen := 0 ' reset length txidx := 0 ' reset put index pub tx_putbyte(idx, b) '' Writes byte b to tx buffer '' -- writes to position at txidx '' -- set idx to -1 for auto-update; 0+ to reset txidx tx_put(idx, @b, 1) ' write 1 byte to buffer pub tx_putword(idx, w) '' Writes word w to tx buffer '' -- writes w to position at txidx '' -- set idx to -1 for auto-update; 0+ to reset txidx tx_put(idx, @w, 2) ' write 2 bytes to buffer pub tx_putval(idx, value, n) '' Writes n bytes from value to tx buffer '' -- writes to position at txidx '' -- set idx to -1 for auto-update; 0+ to reset txidx '' -- use for 1, 2, or 4-byte values/packets tx_put(idx, @value, (n <# 4)) ' write up to 4 bytes to buffer pub tx_put(idx, p_src, n) '' Writes n bytes from source at p_src to tx buffer '' -- writes to position at txidx '' -- set idx to -1 for auto-update; 0+ to reset '' -- use for large (pre-defined) packets (more than 4 bytes) if (idx => 0) ' reset position index? txidx := idx ' yes repeat n ' copy n bytes txbuf[txidx++] := byte[p_src++] ' to buffer from source pub tx_bufidx '' Returns index position for tx buffer (used by tx_putval and tx_put) return txidx pub tx_buflen '' Returns length of transmit buffer '' -- valid after tx_execute return txlen pub tx_bufaddr '' Returns hub address of tx buffer '' -- allows parent object to maniputlate (caution!) return @txbuf pub rx_clear '' Clear rx state and buffer bytefill(@rxbuf, 0, BUF_SIZE) rxlen := 0 rxerror := 0 pub rx_buflen '' Returns length of rx buffer return rxlen pub rx_bufaddr '' Returns hub address of rx buffer return @rxbuf pub rx_error '' Returns rx error byte return rxerror pub extract_byte(p_src, ofs) '' Returns byte from p_src[ofs] result := byte[p_src][ofs] pub extract_word(p_src, ofs) '' Returns word from p_src[ofs] result.byte[0] := byte[p_src][ofs++] result.byte[1] := byte[p_src][ofs] pub extract_long(p_src, ofs) '' Returns long from p_src[ofs] result.byte[0] := byte[p_src][ofs++] result.byte[1] := byte[p_src][ofs++] result.byte[2] := byte[p_src][ofs++] result.byte[3] := byte[p_src][ofs] con { dynamixel 2.0 eeprom } P_MODEL_NUMBER_0 = 0 P_MODEL_NUMBER_1 = 1 P_MODEL_INFO_0 = 2 P_MODEL_INFO_1 = 3 P_MODEL_INFO_2 = 4 P_MODEL_INFO_3 = 5 P_VERSION = 6 P_ID = 7 P_BAUD_RATE = 8 P_RETURN_DELAY_TIME = 9 P_DRIVE_MODE = 10 P_OPERATING_MODE = 11 P_SECONDARY_ID = 12 P_PROTOCOL_VERSION = 13 P_HOME_OFFSET = 20 P_MOVING_THRESHOLD_0 = 24 P_MOVING_THRESHOLD_1 = 25 P_MOVING_THRESHOLD_2 = 26 P_MOVING_THRESHOLD_3 = 27 P_LIMIT_TEMPERATURE = 31 P_MAX_VOLTAGE_0 = 32 P_MAX_VOLTAGE_1 = 33 P_MIN_VOLTAGE_0 = 34 P_MIN_VOLTAGE_1 = 35 P_PWM_LIMIT_0 = 36 P_PWM_LIMIT_1 = 37 P_CURRENT_LIMIT_0 = 38 P_CURRENT_LIMIT_1 = 39 P_ACCEL_LIMIT_0 = 40 P_ACCEL_LIMIT_1 = 41 P_ACCEL_LIMIT_2 = 42 P_ACCEL_LIMIT_3 = 43 P_VELOCITY_LIMIT_0 = 44 P_VELOCITY_LIMIT_1 = 45 P_VELOCITY_LIMIT_2 = 46 P_VELOCITY_LIMIT_3 = 47 P_MAX_POSITION_0 = 48 P_MAX_POSITION_1 = 49 P_MAX_POSITION_2 = 50 P_MAX_POSITION_3 = 51 P_MIN_POSITION_0 = 52 P_MIN_POSITION_1 = 53 P_MIN_POSITION_2 = 54 P_MIN_POSITION_3 = 55 P_SHUTDOWN = 63 con { dynamixel ram } P_TORQUE_ENABLE = 64 P_LED = 65 P_STATUS_RETURN_LVL = 68 P_REGISTERED_INSTRUCTION = 69 P_HARDWARE_ERROR_STATUS = 70 P_VELOCITY_I_GAIN_0 = 76 P_VELOCITY_I_GAIN_1 = 77 P_VELOCITY_P_GAIN_0 = 78 P_VELOCITY_P_GAIN_1 = 79 P_POSITION_D_GAIN_0 = 80 P_POSITION_D_GAIN_1 = 81 P_POSITION_I_GAIN_0 = 82 P_POSITION_I_GAIN_1 = 83 P_POSITION_P_GAIN_0 = 84 P_POSITION_P_GAIN_1 = 85 P_FEEDFWD_2ND_GAIN_0 = 88 P_FEEDFWD_2ND_GAIN_1 = 89 P_FEEDFWD_1ST_GAIN_0 = 90 P_FEEDFWD_1ST_GAIN_1 = 91 P_BUS_WATCHDOG = 98 P_GOAL_PWM = 100 P_GOAL_PWM_0 = 100 P_GOAL_PWM_1 = 101 P_GOAL_CURRENT = 102 P_GOAL_CURRENT_0 = 102 P_GOAL_CURRENT_1 = 103 P_GOAL_VELOCITY = 104 P_GOAL_VELOCITY_0 = 104 P_GOAL_VELOCITY_1 = 105 P_GOAL_VELOCITY_2 = 106 P_GOAL_VELOCITY_3 = 107 P_PROFILE_ACCEL = 108 P_PROFILE_ACCEL_0 = 108 P_PROFILE_ACCEL_1 = 109 P_PROFILE_ACCEL_2 = 110 P_PROFILE_ACCEL_3 = 111 P_PROFILE_VELOCITY = 112 P_PROFILE_VELOCITY_0 = 112 P_PROFILE_VELOCITY_1 = 113 P_PROFILE_VELOCITY_2 = 114 P_PROFILE_VELOCITY_3 = 115 P_GOAL_POSITION = 116 P_GOAL_POSITION_0 = 116 P_GOAL_POSITION_1 = 117 P_GOAL_POSITION_2 = 118 P_GOAL_POSITION_3 = 119 P_REALTIME_TICK = 120 P_REALTIME_TICK_0 = 120 P_REALTIME_TICK_1 = 121 P_MOVING = 122 P_MOVING_STATUS = 123 P_PRESENT_PWM = 124 P_PRESENT_PWM_0 = 124 P_PRESENT_PWM_1 = 125 P_PRESENT_CURRENT = 126 P_PRESENT_CURRENT_0 = 126 P_PRESENT_CURRENT_1 = 127 P_PRESENT_VELOCITY = 128 P_PRESENT_VELOCITY_0 = 128 P_PRESENT_VELOCITY_1 = 129 P_PRESENT_POSITION = 132 P_PRESENT_POSITION_0 = 132 P_PRESENT_POSITION_1 = 133 P_VELOCITY_TRAJECTORY = 136 P_VELOCITY_TRAJECTORY_0 = 136 P_VELOCITY_TRAJECTORY_1 = 137 P_VELOCITY_TRAJECTORY_2 = 138 P_VELOCITY_TRAJECTORY_3 = 139 P_POSITION_TRAJECTORY = 140 P_POSITION_TRAJECTORY_0 = 140 P_POSITION_TRAJECTORY_1 = 141 P_POSITION_TRAJECTORY_2 = 142 P_POSITION_TRAJECTORY_3 = 143 P_INPUT_VOLTAGE = 144 P_INPUT_VOLTAGE_0 = 144 P_INPUT_VOLTAGE_1 = 145 P_TEMPERATURE = 146 con { error codes } E_ID = -1 E_VALUE = -2 E_RANGE = -3 con { ------------- } { UART Driver } { ------------- } dat { fast half-duplex uart } org 0 fasthds mov t1, par ' start of structure add t1, #4 ' skip over state mov txlenpntr, t1 ' save hub address of txlen add t1, #4 mov rxlenpntr, t1 ' save hub address of rxlen add t1, #4 rdlong t2, t1 ' get compressed pins mov t3, t2 ' make a copy and t2, #$FF ' isolate txe test t2, #$80 wc ' check bit7 if_c mov txemask, #0 ' no mask if_nc mov txemask, #1 if_nc shl txemask, t2 if_nc andn outa, txemask ' set to disabled if_nc or dira, txemask ' make output mov t2, t3 shr t2, #8 and t2, #$3F ' isolate tx pin mov txmask, #1 ' make mask shl txmask, t2 mov t2, t3 shr t2, #16 and t2, #$3F ' isolate rx pin mov rxmask, #1 ' make mask shl rxmask, t2 andn dira, rxmask ' force to input ' assume TTL andn outa, txmask ' 0 output bit andn dira, txmask ' float SIO/TX to pull-up ' check for and setup RS-485 cmp txmask, rxmask wz ' same pin? (1-wire TTL) if_ne or outa, txmask ' no, set tx high to idle if_ne or dira, txmask ' and make output add t1, #4 rdlong txetix, t1 ' get txe timing add t1, #4 rdlong bit1x0tix, t1 ' read bit timing mov bit1x5tix, bit1x0tix ' create 1.5 bit timing shr bit1x5tix, #1 add bit1x5tix, bit1x0tix add t1, #4 rdlong tocycles, t1 ' read timeout cycles add t1, #4 rdlong txbufpntr, t1 ' read address of txbuf[0] add t1, #4 rdlong rxbufpntr, t1 ' read address of rxbuf[0] wrlong M_IDLE, par nop nop check_state rdlong axstate, par ' get state from appication cmps axstate, M_TX wz, wc ' tx only? if_e jmp #transmit cmp axstate, M_TXRX wz, wc ' tx and rx response if_e jmp #transmit jmp #check_state ' no packet, try again ' ========== ' TRANSMIT ' ========== transmit rdlong txcount, txlenpntr wz ' get length of packet if_z wrlong M_IDLE, par ' if zero, clear state if_z jmp #check_state ' no go mov txhub, txbufpntr ' point to txbuf[0] tx_enable tjz txemask, #get_tx_byte ' skip if txe not used or outa, txemask ' enable transmit or dira, txemask mov bittimer, txetix ' set timer add bittimer, cnt ' start it waitcnt bittimer, #0 ' let timer expire get_tx_byte rdbyte txwork, txhub ' read byte from txbuf add txhub, #1 ' point to next mov bittimer, bit1x0tix ' load bit timing add bittimer, cnt ' sync with system counter cmp txmask, rxmask wz ' set z for driven mode ' tx unrolled for best speed tx_bits if_e or dira, txmask ' start (open drain) if_ne andn outa, txmask ' start (driven) waitcnt bittimer, bit1x0tix ' let timer expire, reload test txwork, #%0000_0001 wc ' bit0 if_e muxnc dira, txmask if_ne muxc outa, txmask waitcnt bittimer, bit1x0tix test txwork, #%0000_0010 wc if_e muxnc dira, txmask if_ne muxc outa, txmask waitcnt bittimer, bit1x0tix test txwork, #%0000_0100 wc if_e muxnc dira, txmask if_ne muxc outa, txmask waitcnt bittimer, bit1x0tix test txwork, #%0000_1000 wc if_e muxnc dira, txmask if_ne muxc outa, txmask waitcnt bittimer, bit1x0tix test txwork, #%0001_0000 wc if_e muxnc dira, txmask if_ne muxc outa, txmask waitcnt bittimer, bit1x0tix test txwork, #%0010_0000 wc if_e muxnc dira, txmask if_ne muxc outa, txmask waitcnt bittimer, bit1x0tix test txwork, #%0100_0000 wc if_e muxnc dira, txmask if_ne muxc outa, txmask waitcnt bittimer, bit1x0tix test txwork, #%1000_0000 wc if_e muxnc dira, txmask if_ne muxc outa, txmask waitcnt bittimer, bit1x0tix if_e andn dira, txmask ' stop bit (open drain) if_ne or outa, txmask ' stop bit (driven) waitcnt bittimer, bit1x0tix djnz txcount, #get_tx_byte ' done? tjz txemask, #check_rx ' if txe used tx_disable andn outa, txemask ' disable transmitter check_rx cmp axstate, M_TX wc, wz ' tx only? if_e wrlong M_IDLE, par ' yes, alert hub we're done if_e jmp #check_state ' wait for next command ' ============= ' TURN-AROUND ' ============= turn_around andn dira, rxmask ' ensure rx is input (TTL mode) mov t1, tocycles ' set time-out :loop mov bittimer, cnt ' start bit timer test rxmask, ina wc ' look for start bit if_nc jmp #receive ' if start found, go get it djnz t1, #:loop ' decrement check cycles wrlong M_TIMEOUT, par ' flag error jmp #check_state ' ========= ' RECEIVE ' ========= receive mov rxcount, #0 ' reset for rx mov rxhub, rxbufpntr rx_next mov rxwork, #0 ' clear work var add bittimer, bit1x5tix ' skip over start bit ' rx unrolled for best speed rx_byte waitcnt bittimer, bit1x0tix ' wait for middle of bit test rxmask, ina wc ' rx --> c muxc rxwork, #%0000_0001 ' c --> bit0 waitcnt bittimer, bit1x0tix test rxmask, ina wc ' rx --> c muxc rxwork, #%0000_0010 ' c --> bit1 waitcnt bittimer, bit1x0tix test rxmask, ina wc ' rx --> c muxc rxwork, #%0000_0100 ' c --> bit2 waitcnt bittimer, bit1x0tix test rxmask, ina wc ' rx --> c muxc rxwork, #%0000_1000 ' c --> bit3 waitcnt bittimer, bit1x0tix test rxmask, ina wc ' rx --> c muxc rxwork, #%0001_0000 ' c --> bit4 waitcnt bittimer, bit1x0tix test rxmask, ina wc ' rx --> c muxc rxwork, #%0010_0000 ' c --> bit5 waitcnt bittimer, bit1x0tix test rxmask, ina wc ' rx --> c muxc rxwork, #%0100_0000 ' c --> bit6 waitcnt bittimer, bit1x0tix test rxmask, ina wc ' rx --> c muxc rxwork, #%1000_0000 ' c --> bit7 waitpeq rxmask, rxmask ' wait for stop bit put_rx_buf wrbyte rxwork, rxhub ' write byte to hub add rxhub, #1 ' update buffer pointer add rxcount, #1 ' update rx count mov t1, tocycles ' setup for rx time-out check :loop mov bittimer, cnt ' restart timer test rxmask, ina wc ' look for start bit if_nc jmp #rx_next ' if start found, go get it djnz t1, #:loop ' decrement check cycles wrlong rxcount, rxlenpntr ' send count to hub wrlong M_IDLE, par ' indicate done jmp #check_state ' ------------------------------------------------------------------------------------------------- M_TIMEOUT long -1 M_IDLE long %00 ' done, waiting for command M_TX long %01 ' transmit only M_RX long %10 ' entered receive state M_TXRX long %11 ' transmit and recieve axstate res 1 txlenpntr res 1 rxlenpntr res 1 txemask res 1 ' pin masks txmask res 1 rxmask res 1 txetix res 1 ' timing for txe bit1x0tix res 1 ' bit timing bit1x5tix res 1 ' 1.5 bit timing (rx) bittimer res 1 ' timer for bit sampling tocycles res 1 ' timeout cycles txbufpntr res 1 ' hub address of txbuf[0] txhub res 1 ' working pointer txcount res 1 ' bytes to tx txwork res 1 ' tx byte out totimer res 1 rxbufpntr res 1 ' hub address of rxbuf[0] rxhub res 1 ' working pointer rxwork res 1 ' rx byte in rxcount res 1 ' bytes received t1 res 1 ' work vars t2 res 1 t3 res 1 t4 res 1 fit 496 con { hd ttl connections } {{ Looking into cable (female connector) ___ ___ ___ / \/ \/ \ │ (D) (V) (G) │ └───────────────┘ 3 D = Serial Data 2 V = 12V supply 1 G = Ground }} con { hd 485 connections } {{ +5v +5v +12v ___ ___ ___ ___    / \/ \/ \/ \ │ │ │ │ (-) (+) (V) (G) │ 10k  │ │ └────────────────────┘ 4k7 │ ┌─────────┐ │ │     rx ────┻─┤1° 8├─┘ │ │ │ │ │ txe ──────┳─┤2 7├───────┳────────┼───── Pin 4 D- ──────┘ │ │ │ ┣─┤3 6├───────┼─┳──────┼───── Pin 3 D+ ───────────┘ │ │ tx ──────┼─┤4 5├─┐ │ │ └───── Pin 2 12v ────────────────┘ │ │ └─────────┘ │ │ │ ┌───── Pin 1 Ground ─────────────────────┘ 10k  MAX485 │ │ └ ┐ │ │ │ 120  ┌ ┘ │ │ │ │ │ │   └─┘  RO Receive output /RE Receive enable (active low) DE Transmit enable (active high) DI Transmit input Vss ground A differential IO B differential IO Vdd +5v Note: 4.7k into RX pin limits current when output is driven. 10K pull-up on RX keeps RX at idle when RO disabled. }} dat { license } {{ Terms of Use: MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. }}