diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 71783893fa9063bc0657f99505b79169c85c3930..9ac61dfb6646628e4e93c944a87077ee38eda4af 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -203,3 +203,13 @@ ifneq (,$(filter xbee,$(USEMODULE))) USEMODULE += xtimer USEMODULE += netif endif + +ifneq (,$(filter uart_half_duplex,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio + FEATURES_REQUIRED += periph_uart + USEMODULE += xtimer +endif + +ifneq (,$(filter feetech,$(USEMODULE))) + USEMODULE += uart_half_duplex +endif diff --git a/drivers/Makefile.include b/drivers/Makefile.include index 971e91089bf734a37af926f7387e4b35dc8f03c3..434371db6d69df8b81334f2b21532bd2f4ed5ae6 100644 --- a/drivers/Makefile.include +++ b/drivers/Makefile.include @@ -106,3 +106,9 @@ endif ifneq (,$(filter adxl345,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/adxl345/include endif +ifneq (,$(filter uart_half_duplex,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/drivers/uart_half_duplex/include +endif +ifneq (,$(filter feetech,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/drivers/feetech/include +endif diff --git a/drivers/feetech/Makefile b/drivers/feetech/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2 --- /dev/null +++ b/drivers/feetech/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/feetech/feetech.c b/drivers/feetech/feetech.c new file mode 100644 index 0000000000000000000000000000000000000000..ad3802bf699fb9864d6adc93f2288c4f197782e6 --- /dev/null +++ b/drivers/feetech/feetech.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup drivers_feetech + * @{ + * + * @file + * @brief Driver implementation for Feetech devices + * + * @author Loïc Dauphin <loic.dauphin@inria.fr> + * + * @} + */ + +#include "feetech.h" + +#include "feetech_protocol.h" +#include "feetech_reader.h" +#include "feetech_writer.h" + +#include "periph/uart.h" +#include "xtimer.h" +#include "byteorder.h" + +#include <string.h> + +void feetech_init(feetech_t *device, uart_half_duplex_t *stream, feetech_id_t id) +{ + device->stream = stream; + device->id = id; +} + +int feetech_ping(uart_half_duplex_t *stream, feetech_id_t id) +{ + feetech_writer_t pw; + + uart_half_duplex_set_tx(stream); + feetech_writer_init(&pw, stream->buffer, stream->size); + feetech_writer_ping_make(&pw, id); + uart_half_duplex_send(stream, pw.size); + + uart_half_duplex_set_rx(stream); + if (uart_half_duplex_recv(stream, FEETECH_ACK_SIZE) != FEETECH_ACK_SIZE) { + return FEETECH_TIMEOUT; + } + + return FEETECH_OK; +} + +int feetech_write(feetech_t *device, feetech_addr_t reg, const uint8_t *data, size_t length) +{ + uart_half_duplex_set_tx(device->stream); + if (device->stream->size < length) { + return FEETECH_BUFFER_TOO_SMALL; + } + + feetech_writer_t pw; + + feetech_writer_init(&pw, device->stream->buffer, device->stream->size); + feetech_writer_write_make(&pw, device->id, reg, data, length); + uart_half_duplex_send(device->stream, pw.size); + + uart_half_duplex_set_rx(device->stream); + if (uart_half_duplex_recv(device->stream, FEETECH_ACK_SIZE) != FEETECH_ACK_SIZE) { + return FEETECH_TIMEOUT; + } + + return FEETECH_OK; +} + +int feetech_write8(feetech_t *device, feetech_addr_t reg, uint8_t value) +{ + return feetech_write(device, reg, &value, 1); +} + +int feetech_write16(feetech_t *device, feetech_addr_t reg, uint16_t value) +{ + value = HTONS(value); + return feetech_write(device, reg, (uint8_t*)&value, 2); +} + +int feetech_read(feetech_t *device, feetech_addr_t reg, uint8_t *data, size_t length) +{ + uart_half_duplex_set_tx(device->stream); + if (device->stream->size < length) { + return FEETECH_BUFFER_TOO_SMALL; + } + + feetech_writer_t pw; + + feetech_writer_init(&pw, device->stream->buffer, device->stream->size); + feetech_writer_read_make(&pw, device->id, reg, length); + uart_half_duplex_send(device->stream, pw.size); + + uart_half_duplex_set_rx(device->stream); + const size_t esize = FEETECH_RESPONSE_SIZE(length); + if (uart_half_duplex_recv(device->stream, esize) != esize) { + return FEETECH_TIMEOUT; + } + + feetech_reader_t pr; + feetech_reader_init(&pr, device->stream->buffer, esize); + if (!feetech_reader_is_valid(&pr)) { + return FEETECH_INVALID_MESSAGE; + } + + if (feetech_reader_response_get_payload_size(&pr) != length) { + return FEETECH_INVALID_MESSAGE; + } + + memcpy(data, feetech_reader_response_get_payload(&pr), length); + return FEETECH_OK; +} + +int feetech_read8(feetech_t *device, feetech_addr_t reg, uint8_t *value) +{ + return feetech_read(device, reg, value, 1); +} + +int feetech_read16(feetech_t *device, feetech_addr_t reg, uint16_t *value) +{ + const int ret = feetech_read(device, reg, (uint8_t*)value, 2); + if (ret == FEETECH_OK) { + *value = NTOHS(*value); + } + return ret; +} diff --git a/drivers/feetech/include/feetech_protocol.h b/drivers/feetech/include/feetech_protocol.h new file mode 100644 index 0000000000000000000000000000000000000000..98ddfd4c5d5e18ff73b689e6ace284a108647d6a --- /dev/null +++ b/drivers/feetech/include/feetech_protocol.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup drivers_feetech + * + * @{ + * + * @file + * @brief Feetech protocol definitions + * + * @author Loïc Dauphin <loic.dauphin@inria.fr> + */ + +#ifndef FEETECH_PROTOCOL_H +#define FEETECH_PROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define FEETECH_START (0xFF) + +typedef enum { + SCS15_B_1M = 0, + SCS15_B_0_5M = 1, + SCS15_B_250K = 2, + SCS15_B_128K = 3, + SCS15_B_115200 = 4, + SCS15_B_76800 = 5, + SCS15_B_57600 = 6, + SCS15_B_38400 = 7 +} scs15_baudrate_t; + +typedef enum { + SCS15_ID = 5, + SCS15_BAUD_RATE = 6, + SCS15_RETURN_DELAY_TIME = 7, + SCS15_RETURN_LEVEL = 8, + SCS15_LIMIT_TEMPERATURE = 13, + SCS15_MAX_LIMIT_VOLTAGE = 14, + SCS15_MIN_LIMIT_VOLTAGE = 15, + SCS15_ALARM_LED = 18, + SCS15_ALARM_SHUTDOWN = 19, + SCS15_COMPLIANCE_P = 21, + SCS15_COMPLIANCE_D = 22, + SCS15_COMPLIANCE_I = 23, + SCS15_CW_DEAD = 26, + SCS15_CCW_DEAD = 27, + SCS15_TORQUE_ENABLE = 40, + SCS15_LED = 41, + SCS15_LOCK = 48, + SCS15_PRESENT_VOLTAGE = 62, + SCS15_PRESENT_TEMPERATURE = 63, + SCS15_REGISTERED_INSTRUCTION = 64, + SCS15_ERROR = 65, + SCS15_MOVING = 66, +} scs15_register8_t; + +typedef enum { + SCS15_MODEL_NUMBER = 0, + SCS15_VERSION = 3, + SCS15_MIN_ANGLE_LIMIT = 9, + SCS15_MAX_ANGLE_LIMIT = 11, + SCS15_MAX_TORQUE = 16, + SCS15_PUNCH = 24, + SCS15_IMAX = 28, + SCS15_OFFSET = 30, + SCS15_GOAL_POSITION = 42, + SCS15_GOAL_TIME = 44, + SCS15_GOAL_SPEED = 46, + SCS15_PRESENT_POSITION = 56, + SCS15_PRESENT_SPEED = 58, + SCS15_PRESENT_LOAD = 60, + SCS15_VIR_POSITION = 67, + SCS15_CURRENT = 69, +} scs15_register16_t; + +typedef enum { + INST_PING = 0x01, + INST_READ = 0x02, + INST_WRITE = 0x03, + INST_REG_WRITE = 0x04, + INST_ACTION = 0x05, + INST_RESET = 0x06, + INST_SYNC_WRITE = 0x83, +} feetech_intruction_t; + +#ifdef __cplusplus +} +#endif + +#endif +/** @} */ diff --git a/drivers/feetech/include/feetech_reader.h b/drivers/feetech/include/feetech_reader.h new file mode 100644 index 0000000000000000000000000000000000000000..a119819454dd5adb4c0956d013190aa349984ffa --- /dev/null +++ b/drivers/feetech/include/feetech_reader.h @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup drivers_feetech + * + * @{ + * + * @file + * @brief Interface definition for Feetech packet reader + * + * @author Loïc Dauphin <loic.dauphin@inria.fr> + */ + +#ifndef FEETECH_READER_H +#define FEETECH_READER_H + +#include "feetech_protocol.h" + +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define FEETECH_ACK_SIZE (6) +#define FEETECH_RESPONSE_SIZE(len) (6 + len) + +/** + * @brief Feetech packet reader struct + */ +typedef struct { + const uint8_t *buffer; /**< data buffer */ + size_t size; /**< data buffer's size */ +} feetech_reader_t; + +/** + * @brief Initialize the Feetech packet reader + * + * @param[out] reader the packet reader + * @param[in] buffer the buffer used to store data + * @param[in] size the size of the buffer + */ +static inline void feetech_reader_init(feetech_reader_t *reader, const uint8_t *buffer, size_t size) +{ + reader->buffer = buffer; + reader->size = size; +} + +/** + * @brief Compute the packet's sum + * + * @param[in] reader the packet reader + * + * @return the sum of the packet + */ +uint8_t feetech_reader_compute_sum(const feetech_reader_t *reader); + +/** + * @brief Check if the packet has the minimum required size + * + * @param[in] reader the packet reader + * + * @return true if the packet has the minimum required size + * @return false otherwise + */ +static inline bool feetech_reader_check_minsize(const feetech_reader_t *reader) +{ + return 5 < reader->size; +} + + +/** + * @brief Check if the packet begins with 2 FEETECH_START bits + * + * @param[in] reader the packet reader + * + * @return true if the packet begins with 2 FEETECH_START bits + * @return false otherwise + */ +static inline bool feetech_reader_check_start(const feetech_reader_t *reader) +{ + return + reader->buffer[0] == FEETECH_START && + reader->buffer[1] == FEETECH_START; +} + +/** + * @brief Check if the packet's size is the same as the buffer's size + * + * @param[in] reader the packet reader + * + * @return true if the packet's size is the same as the buffer's size + * @return false otherwise + */ +static inline bool feetech_reader_check_size(const feetech_reader_t *reader) +{ + return reader->size == (size_t)(reader->buffer[3] + 4); +} + +/** + * @brief Check if the computed sum and the sum of the packet are equal + * + * @param[in] reader the packet reader + * + * @return true if the computed sum and the sum of the packet are equal + * @return false otherwise + */ +static inline bool feetech_reader_check_sum(const feetech_reader_t *reader) +{ + return feetech_reader_compute_sum(reader) == reader->buffer[reader->size - 1]; +} + +/** + * @brief Check if the packet is valid + * + * @param[in] reader the packet reader + * + * @return true if the packet is valid + * @return false otherwise + */ +bool feetech_reader_is_valid(const feetech_reader_t *reader); + +/** + * @brief Get the packet's device id + * + * @param[in] reader the packet reader + * + * @return the packet's device id + */ +static inline uint8_t feetech_reader_get_id(const feetech_reader_t *reader) +{ + return reader->buffer[2]; +} + +/** + * @brief Get the packet's instruction code + * + * @param[in] reader the packet reader + * + * @return the packet's instruction code + */ +static inline uint8_t feetech_reader_get_instr(const feetech_reader_t *reader) +{ + return reader->buffer[4]; +} + +/** + * @brief Get the packet's payload (response) + * + * @param[in] reader the packet reader + * + * @return the addess of the begining of the payload + */ +static inline const uint8_t *feetech_reader_response_get_payload(const feetech_reader_t *reader) +{ + return &reader->buffer[5]; +} + +/** + * @brief Get the packet's payload size (response) + * + * @param[in] reader the packet reader + * + * @return the size of the payload + */ +static inline size_t feetech_reader_response_get_payload_size(const feetech_reader_t *reader) +{ + return reader->buffer[3] - 2; +} + +/** + * @brief Get the packet's payload (WRITE) + * + * @param[in] reader the packet reader + * + * @return the begining addess of the payload + */ +static inline const uint8_t *feetech_reader_write_get_payload(const feetech_reader_t *reader) +{ + return &reader->buffer[6]; +} + +/** + * @brief Get the packet's payload size (WRITE) + * + * @param[in] reader the packet reader + * + * @return the size of the payload + */ +static inline size_t feetech_reader_write_get_payload_size(const feetech_reader_t *reader) +{ + return reader->buffer[3] - 3; +} + +/** + * @brief Get the packet's target register address (WRITE) + * + * @param[in] reader the packet reader + * + * @return the register address + */ +static inline uint8_t feetech_reader_write_get_reg(const feetech_reader_t *reader) +{ + return reader->buffer[5]; +} + +/** + * @brief Get the packet's READ size + * + * @param[in] reader the packet reader + * + * @return the READ size + */ +static inline size_t feetech_reader_read_get_size(const feetech_reader_t *reader) +{ + return reader->buffer[6]; +} + +/** + * @brief Get the packet's target register address (READ) + * + * @param[in] reader the packet reader + * + * @return the register address + */ +static inline uint8_t feetech_reader_read_get_reg(const feetech_reader_t *reader) +{ + return reader->buffer[5]; +} + +/** + * @brief Get the packet items' payload size (SYNC_WRITE) + * + * @param[in] reader the packet reader + * + * @return the size of the items' payload + */ +static inline size_t feetech_reader_sync_write_get_payload_size(const feetech_reader_t *reader) +{ + return reader->buffer[6]; +} + +/** + * @brief Get the packet's target register address (SYNC_WRITE) + * + * @param[in] reader the packet reader + * + * @return the register address + */ +static inline uint8_t feetech_reader_sync_write_get_reg(const feetech_reader_t *reader) +{ + return reader->buffer[5]; +} + +/** + * @brief Get the packet items' count (SYNC_WRITE) + * + * @param[in] reader the packet reader + * + * @return the number of items in the packet + */ +size_t feetech_reader_sync_write_get_items_count(const feetech_reader_t *reader); + +/** + * @brief Get the packet item's device id (SYNC_WRITE) + * + * @param[in] reader the packet reader + * @param[in] index the item index + * + * @return the item's device id + */ +uint8_t feetech_reader_sync_write_item_get_id(const feetech_reader_t *reader, uint8_t index); + +/** + * @brief Get the packet item's payload (SYNC_WRITE) + * + * @param[in] reader the packet reader + * @param[in] index the item index + * + * @return the begining addess of the payload + */ +const uint8_t *feetech_reader_sync_write_item_get_payload(const feetech_reader_t *reader, uint8_t index); + +#ifdef __cplusplus +} +#endif + +#endif +/** @} */ diff --git a/drivers/feetech/include/feetech_writer.h b/drivers/feetech/include/feetech_writer.h new file mode 100644 index 0000000000000000000000000000000000000000..a09210430d97ffad4be97207b2a643b663d2fb75 --- /dev/null +++ b/drivers/feetech/include/feetech_writer.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup drivers_feetech + * + * @{ + * + * @file + * @brief Interface definition for Feetech packet writer + * + * @author Loïc Dauphin <loic.dauphin@inria.fr> + */ + +#ifndef FEETECH_WRITER_H +#define FEETECH_WRITER_H + +#include "feetech_protocol.h" + +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Feetech packet writer struct + */ +typedef struct { + uint8_t *buffer; /**< data buffer */ + size_t size; /**< packet's size */ + size_t buffer_max_size; /**< data buffer's size */ +} feetech_writer_t; + +/** + * @brief Initialize the Feetech packet writer + * + * @param[out] writer the packet writer + * @param[in] buffer the buffer used to store data + * @param[in] buffer_max_size the size of the buffer (= maximum packet size) + */ +void feetech_writer_init(feetech_writer_t *writer, uint8_t *buffer, size_t buffer_max_size); + +/** + * @brief Get the data buffer to send + * + * @param[out] writer the packet writer + * + * @return the begining address of the buffer + */ +const uint8_t *feetech_writer_get_data(const feetech_writer_t *writer); + +/** + * @brief Get the data buffer's size to send + * + * @param[out] writer the packet writer + * + * @return the buffer's size + */ +size_t feetech_writer_get_size(const feetech_writer_t *writer); + +/** + * @brief Build a response packet + * + * @param[out] writer the packet writer + * @param[in] id the responder's id + * @param[in] buffer the response data + * @param[in] size the response size + */ +void feetech_writer_response_make(feetech_writer_t *writer, uint8_t id, const uint8_t *buffer, size_t size); + +/** + * @brief Build an ack packet + * + * @param[out] writer the packet writer + * @param[in] id the responder's id + */ +void feetech_writer_ack_make(feetech_writer_t *writer, uint8_t id); + +/** + * @brief Build a PING packet + * + * @param[out] writer the packet writer + * @param[in] id the destination's id + */ +void feetech_writer_ping_make(feetech_writer_t *writer, uint8_t id); + +/** + * @brief Build a WRITE packet + * + * @param[out] writer the packet writer + * @param[in] id the destination's id + * @param[in] reg the register to write in + * @param[in] buffer the data buffer to write + * @param[in] size the data buffer's size + */ +void feetech_writer_write_make(feetech_writer_t *writer, uint8_t id, uint8_t reg, const uint8_t *buffer, size_t size); + +/** + * @brief Build a WRITE packet (8 bits) + * + * @param[out] writer the packet writer + * @param[in] id the destination's id + * @param[in] reg the register to write in + * @param[in] value the value to write in the register + */ +void feetech_writer_write8_make(feetech_writer_t *writer, uint8_t id, uint8_t reg, uint8_t value); + +/** + * @brief Build a WRITE packet (16 bits) + * + * @param[out] writer the packet writer + * @param[in] id the destination's id + * @param[in] reg the register to write in + * @param[in] value the value to write in the register + */ +void feetech_writer_write16_make(feetech_writer_t *writer, uint8_t id, uint8_t reg, uint16_t value); + +/** + * @brief Build a READ packet + * + * @param[out] writer the packet writer + * @param[in] id the destination's id + * @param[in] reg the register to read + * @param[in] size the size to read + */ +void feetech_writer_read_make(feetech_writer_t *writer, uint8_t id, uint8_t reg, size_t size); + +/** + * @brief Begin to build a SYNC_WRITE packet + * + * @param[out] writer the packet writer + * @param[in] reg the register to write in + * @param[in] size the data buffer's size + */ +void feetech_writer_sync_write_begin(feetech_writer_t *writer, uint8_t reg, size_t size); + +/** + * @brief End the building of a SYNC_WRITE packet + * + * @param[out] writer the packet writer + */ +void feetech_writer_sync_write_end(feetech_writer_t *writer); + +/** + * @brief Add an item to a SYNC_WRITE packet + * + * @param[out] writer the packet writer + * @param[in] id the destination's id + * @param[in] buffer the data buffer to write + * @param[in] size the data buffer's size + */ +void feetech_writer_sync_write_add(feetech_writer_t *writer, uint8_t id, const uint8_t *buffer, size_t size); + +/** + * @brief Add an item to a SYNC_WRITE packet (8 bits) + * + * @param[out] writer the packet writer + * @param[in] id the destination's id + * @param[in] value the value to write + */ +void feetech_writer_sync_write_add_8bits(feetech_writer_t *writer, uint8_t id, uint8_t value); + +/** + * @brief Add an item to a SYNC_WRITE packet (16 bits) + * + * @param[out] writer the packet writer + * @param[in] id the destination's id + * @param[in] value the value to write + */ +void feetech_writer_sync_write_add_16bits(feetech_writer_t *writer, uint8_t id, uint16_t value); + +#ifdef __cplusplus +} +#endif + +#endif +/** @} */ diff --git a/drivers/feetech/reader.c b/drivers/feetech/reader.c new file mode 100644 index 0000000000000000000000000000000000000000..62950aa0c597f771e413898a3e01229aeb2a62ee --- /dev/null +++ b/drivers/feetech/reader.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup drivers_feetech + * @{ + * + * @file + * @brief Feetech messages reader + * + * @author Loïc Dauphin <loic.dauphin@inria.fr> + * + * @} + */ + +#include "feetech_reader.h" + +static uint8_t _compute_sum(const feetech_reader_t *reader) +{ + uint8_t sum = 0; + for (size_t i = 2 ; i < reader->size-1 ; i++) { + sum += reader->buffer[i]; + } + return sum; +} + +uint8_t feetech_reader_compute_sum(const feetech_reader_t *reader) +{ + return ~_compute_sum(reader); +} + +bool feetech_reader_is_valid(const feetech_reader_t *reader) +{ + return + feetech_reader_check_minsize(reader) && + feetech_reader_check_start(reader) && + feetech_reader_check_size(reader) && + feetech_reader_check_sum(reader); +} + +size_t feetech_reader_sync_write_get_items_count(const feetech_reader_t *reader) +{ + return (reader->buffer[3] - 4) / (reader->buffer[6] + 1); +} + +uint8_t feetech_reader_sync_write_item_get_id(const feetech_reader_t *reader, uint8_t index) +{ + return reader->buffer[7 + index * (feetech_reader_sync_write_get_payload_size(reader) + 1)]; +} + +const uint8_t *feetech_reader_sync_write_item_get_payload(const feetech_reader_t *reader, uint8_t index) +{ + return &reader->buffer[7 + index * (feetech_reader_sync_write_get_payload_size(reader) + 1) + 1]; +} diff --git a/drivers/feetech/writer.c b/drivers/feetech/writer.c new file mode 100644 index 0000000000000000000000000000000000000000..c8b14db8ed33be7a93a6b856758827c40d170d8c --- /dev/null +++ b/drivers/feetech/writer.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup drivers_feetech + * @{ + * + * @file + * @brief Feetech messages writer + * + * @author Loïc Dauphin <loic.dauphin@inria.fr> + * + * @} + */ + +#include "feetech_writer.h" + +void feetech_writer_init(feetech_writer_t *writer, uint8_t *buffer, size_t buffer_max_size) +{ + writer->buffer = buffer; + writer->size = 0; + writer->buffer_max_size = buffer_max_size; +} + +const uint8_t *feetech_writer_get_data(const feetech_writer_t *writer) +{ + return (const uint8_t*)writer->buffer; +} + +size_t feetech_writer_get_size(const feetech_writer_t *writer) +{ + return writer->size; +} + +void feetech_writer_response_make(feetech_writer_t *writer, uint8_t id, const uint8_t *buffer, size_t size) +{ + const size_t len = 2 + size; + if (len + 4 <= writer->buffer_max_size) { + writer->size = len + 4; + + uint8_t sum = 0; + + writer->buffer[0] = FEETECH_START; + writer->buffer[1] = FEETECH_START; + sum += writer->buffer[2] = id; + sum += writer->buffer[3] = len; + sum += writer->buffer[4] = 0; + + for (size_t i = 0 ; i < size ; i++) { + sum += writer->buffer[5 + i] = buffer[i]; + } + + writer->buffer[size - 1] = ~sum; + } + else { + writer->size = 0; + } +} + + +void feetech_writer_ack_make(feetech_writer_t *writer, uint8_t id) +{ + const size_t len = 2; + if (len + 4 <= writer->buffer_max_size) { + writer->size = len + 4; + + uint8_t sum = 0; + + writer->buffer[0] = FEETECH_START; + writer->buffer[1] = FEETECH_START; + sum += writer->buffer[2] = id; + sum += writer->buffer[3] = len; + sum += writer->buffer[4] = 0; + writer->buffer[5] = ~sum; + } + else { + writer->size = 0; + } +} + +void feetech_writer_ping_make(feetech_writer_t *writer, uint8_t id) +{ + const size_t len = 2; + if (len + 4 <= writer->buffer_max_size) { + writer->size = len + 4; + + uint8_t sum = 0; + + writer->buffer[0] = FEETECH_START; + writer->buffer[1] = FEETECH_START; + sum += writer->buffer[2] = id; + sum += writer->buffer[3] = len; + sum += writer->buffer[4] = INST_PING; + writer->buffer[5] = ~sum; + } + else { + writer->size = 0; + } +} + +void feetech_writer_write8_make(feetech_writer_t *writer, uint8_t id, uint8_t reg, uint8_t value) +{ + const size_t len = 4; + if (len + 4 <= writer->buffer_max_size) { + writer->size = len + 4; + + uint8_t sum = 0; + + writer->buffer[0] = FEETECH_START; + writer->buffer[1] = FEETECH_START; + sum += writer->buffer[2] = id; + sum += writer->buffer[3] = len; + sum += writer->buffer[4] = INST_WRITE; + + sum += writer->buffer[5] = reg; + sum += writer->buffer[6] = value; + + writer->buffer[7] = ~sum; + } + else { + writer->size = 0; + } +} + +void feetech_writer_write16_make(feetech_writer_t *writer, uint8_t id, uint8_t reg, uint16_t value) +{ + const size_t len = 5; + if (len + 4 <= writer->buffer_max_size) { + writer->size = len + 4; + + uint8_t sum = 0; + + writer->buffer[0] = FEETECH_START; + writer->buffer[1] = FEETECH_START; + sum += writer->buffer[2] = id; + sum += writer->buffer[3] = len; + sum += writer->buffer[4] = INST_WRITE; + + sum += writer->buffer[5] = reg; + sum += writer->buffer[6] = (value >> 8) & 0xFF; + sum += writer->buffer[7] = value & 0xFF; + + writer->buffer[8] = ~sum; + } + else { + writer->size = 0; + } +} + +void feetech_writer_write_make(feetech_writer_t *writer, uint8_t id, uint8_t reg, const uint8_t *buffer, size_t size) +{ + const size_t len = 3 + size; + if (len + 4 <= writer->buffer_max_size) { + writer->size = len + 4; + + uint8_t sum = 0; + + writer->buffer[0] = FEETECH_START; + writer->buffer[1] = FEETECH_START; + sum += writer->buffer[2] = id; + sum += writer->buffer[3] = len; + sum += writer->buffer[4] = INST_WRITE; + + sum += writer->buffer[5] = reg; + for (size_t i = 0 ; i < size ; i++) { + sum += writer->buffer[6 + i] = buffer[i]; + } + + writer->buffer[writer->size - 1] = ~sum; + } + else { + writer->size = 0; + } +} + +void feetech_writer_read_make(feetech_writer_t *writer, uint8_t id, uint8_t reg, size_t size) +{ + const size_t len = 4; + if (len + 4 <= writer->buffer_max_size) { + writer->size = len + 4; + + uint8_t sum = 0; + + writer->buffer[0] = FEETECH_START; + writer->buffer[1] = FEETECH_START; + sum += writer->buffer[2] = id; + sum += writer->buffer[3] = len; + sum += writer->buffer[4] = INST_READ; + + sum += writer->buffer[5] = reg; + sum += writer->buffer[6] = (uint8_t)size; + + writer->buffer[7] = ~sum; + } + else { + writer->size = 0; + } +} + +size_t feetech_writer_sync_write_required(feetech_writer_t *writer) +{ + if (8 <= writer->size && writer->buffer[4] == INST_SYNC_WRITE) { + return writer->buffer[6]; + } + return 0; +} + +void feetech_writer_sync_write_end(feetech_writer_t *writer) +{ + if (writer->size <= 8) { + writer->size = 0; + } +} + +void feetech_writer_sync_write_add(feetech_writer_t *writer, uint8_t id, const uint8_t *buffer, size_t size) +{ + if (feetech_writer_sync_write_required(writer) == size && + size != 0 && writer->size + size + 1 <= writer->buffer_max_size) { + + uint8_t sum = ~writer->buffer[writer->size - 1]; + + sum += writer->buffer[writer->size - 1] = id; + for (size_t i = 0 ; i < size ; i++) { + sum += writer->buffer[writer->size + i] = buffer[i]; + } + + writer->buffer[3] += size + 1; + writer->buffer[writer->size + size] = ~(sum + size + 1); + writer->size += size + 1; + } + else { + writer->size = 0; + } +} + +void feetech_writer_sync_write_add_8bits(feetech_writer_t *writer, uint8_t id, uint8_t value) +{ + if (feetech_writer_sync_write_required(writer) == 1 && + writer->size + 2 <= writer->buffer_max_size) { + + uint8_t sum = ~writer->buffer[writer->size - 1]; + + sum += writer->buffer[writer->size - 1] = id; + sum += writer->buffer[writer->size] = value; + + writer->buffer[3] += 2; + writer->buffer[writer->size + 1] = ~(sum + 2); + writer->size += 2; + } + else { + writer->size = 0; + } +} + +void feetech_writer_sync_write_add_16bits(feetech_writer_t *writer, uint8_t id, uint16_t value) +{ + if (feetech_writer_sync_write_required(writer) == 2 && + writer->size + 3 <= writer->buffer_max_size) { + + uint8_t sum = ~writer->buffer[writer->size - 1]; + + sum += writer->buffer[writer->size - 1] = id; + sum += writer->buffer[writer->size + 0] = (value >> 8) & 0xFF; + sum += writer->buffer[writer->size + 1] = value & 0xFF; + + writer->buffer[3] += 3; + writer->buffer[writer->size + 2] = ~(sum + 3); + writer->size += 3; + } + else { + writer->size = 0; + } +} + +void feetech_writer_sync_write_begin(feetech_writer_t *writer, uint8_t reg, size_t size) +{ + const size_t len = 4; + if (len + 4 <= writer->buffer_max_size) { + writer->size = len + 4; + + uint8_t sum = 0; + + writer->buffer[0] = FEETECH_START; + writer->buffer[1] = FEETECH_START; + sum += writer->buffer[2] = 0xFF; + sum += writer->buffer[3] = len; + sum += writer->buffer[4] = INST_SYNC_WRITE; + + sum += writer->buffer[5] = reg; + sum += writer->buffer[6] = (uint8_t)size; + + writer->buffer[7] = ~sum; + } +} diff --git a/drivers/include/feetech.h b/drivers/include/feetech.h new file mode 100644 index 0000000000000000000000000000000000000000..1a155f5d91c8f7d48c02464a6152454320bf52db --- /dev/null +++ b/drivers/include/feetech.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @defgroup drivers_feetech Feetech driver + * @ingroup drivers_actuators + * + * This module contains drivers for any device using feetech's servomotors communication bus. + * The bus is mainly used for servomotors, but a device can be anything : sensors, other actuators. + * + * @{ + * + * @file + * @brief Interface definition for Feetech devices driver + * + * @author Loïc Dauphin <loic.dauphin@inria.fr> + */ + +#ifndef FEETECH_H +#define FEETECH_H + +#include <stdlib.h> + +#include "feetech_protocol.h" +#include "uart_half_duplex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint8_t feetech_id_t; /**< device id type */ +typedef uint8_t feetech_addr_t; /**< address type */ + +/** + * @brief Descriptor struct for a feetech device + */ +typedef struct { + uart_half_duplex_t *stream; /**< the stream used */ + feetech_id_t id; /**< the device address */ +} feetech_t; + +/** + * @brief Possible feetech return values + */ +enum { + FEETECH_OK, /**< Success */ + FEETECH_TIMEOUT, /**< No response from the device */ + FEETECH_BUFFER_TOO_SMALL, /**< Buffer is too small for the message */ + FEETECH_INVALID_MESSAGE, /**< Invalid message received */ +}; + +/** + * @brief Send a PING message to a device + * + * @param[in] stream the stream + * @param[in] id the device address + * + * @return FEETECH_OK if a device answered + * @return FEETECH_TIMEOUT if the device did not answer + * @return FEETECH_BUFFER_TOO_SMALL if buffer is too small for the message + * @return FEETECH_INVALID_MESSAGE if an invalid message was received + */ +int feetech_ping(uart_half_duplex_t *stream, feetech_id_t id); + +/** + * @brief Initialize a Feetech device + * + * @param[out] device the Feetech device + * @param[in] stream the stream + * @param[in] id the device address + */ +void feetech_init(feetech_t *device, uart_half_duplex_t *stream, feetech_id_t id); + +/** + * @brief Write to a device 8bits address + * + * @param[in] device the Feetech device + * @param[in] addr the address to write + * @param[in] value the value to write + * + * @return FEETECH_OK on success + * @return FEETECH_TIMEOUT if the device did not answer + * @return FEETECH_BUFFER_TOO_SMALL if buffer is too small for the message + * @return FEETECH_INVALID_MESSAGE if an invalid message was received + */ +int feetech_write8(feetech_t *device, feetech_addr_t addr, uint8_t value); + +/** + * @brief Write to a device 16bits address + * + * @param[in] device the Feetech device + * @param[in] addr the address to write + * @param[in] value the value to write + * + * @return FEETECH_OK on success + * @return FEETECH_TIMEOUT if the device did not answer + * @return FEETECH_BUFFER_TOO_SMALL if buffer is too small for the message + * @return FEETECH_INVALID_MESSAGE if an invalid message was received + */ +int feetech_write16(feetech_t *device, feetech_addr_t addr, uint16_t value); + +/** + * @brief Write to a device address + * + * @param[in] device the Feetech device + * @param[in] addr the address to start write + * @param[in] data the data to write + * @param[in] length the data length + * + * @return FEETECH_OK on success + * @return FEETECH_TIMEOUT if the device did not answer + * @return FEETECH_BUFFER_TOO_SMALL if buffer is too small for the message + * @return FEETECH_INVALID_MESSAGE if an invalid message was received + */ +int feetech_write(feetech_t *device, feetech_addr_t addr, const uint8_t *data, size_t length); + +/** + * @brief Read from a device 8bits address + * + * @param[in] device the Feetech device + * @param[in] addr the address to read + * @param[out] value the value to read + * + * @return FEETECH_OK on success + * @return FEETECH_TIMEOUT if the device did not answer + * @return FEETECH_BUFFER_TOO_SMALL if buffer is too small for the message + * @return FEETECH_INVALID_MESSAGE if an invalid message was received + */ +int feetech_read8(feetech_t *device, feetech_addr_t addr, uint8_t *value); + +/** + * @brief Read from a device 16bits address + * + * @param[in] device the Feetech device + * @param[in] addr the address to read + * @param[out] value the value to read + * + * @return FEETECH_OK on success + * @return FEETECH_TIMEOUT if the device did not answer + * @return FEETECH_BUFFER_TOO_SMALL if buffer is too small for the message + * @return FEETECH_INVALID_MESSAGE if an invalid message was received + */ +int feetech_read16(feetech_t *device, feetech_addr_t addr, uint16_t *value); + +/** + * @brief Read from a device address + * + * @param[in] device the Feetech device + * @param[in] addr the address to start read + * @param[out] data the data buffer to fill + * @param[in] length the data length + * + * @return FEETECH_OK on success + * @return FEETECH_TIMEOUT if the device did not answer + * @return FEETECH_BUFFER_TOO_SMALL if buffer is too small for the message + * @return FEETECH_INVALID_MESSAGE if an invalid message was received + */ +int feetech_read(feetech_t *device, feetech_addr_t addr, uint8_t *data, size_t length); + +#ifdef __cplusplus +} +#endif + +#endif +/** @} */ diff --git a/drivers/uart_half_duplex/Makefile b/drivers/uart_half_duplex/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2 --- /dev/null +++ b/drivers/uart_half_duplex/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/uart_half_duplex/include/uart_half_duplex.h b/drivers/uart_half_duplex/include/uart_half_duplex.h new file mode 100644 index 0000000000000000000000000000000000000000..7e694d75f527e6352b4eec70ad612e1ee66ab654 --- /dev/null +++ b/drivers/uart_half_duplex/include/uart_half_duplex.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @defgroup drivers_uart_half_duplex half-duplex UART Driver + * @ingroup drivers_actuators + * + * This module contains drivers for UART half-duplex communication bus. + * It needs to manage the communication direction by enabling or disabling TX. + * + * @{ + * + * @file + * @brief Interface definition for half-duplex UART driver + * + * @author Loïc Dauphin <loic.dauphin@inria.fr> + */ + +#ifndef UART_HALF_DUPLEX_H +#define UART_HALF_DUPLEX_H + +#include <stdlib.h> + +#include "periph/uart.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef UART_HALF_DUPLEX_DEFAULT_TIMEOUT_US +#define UART_HALF_DUPLEX_DEFAULT_TIMEOUT_US (20000LU) /**< Default recv timeout (in microseconds) */ +#endif + +/** + * @brief half-duplex UART direction management method type + */ +typedef int uart_half_duplex_dir_t; + +#define UART_HALF_DUPLEX_DIR_NONE (0) /**< Don't manage direction */ + +#define UART_HALF_DUPLEX_DIR_PIN_SET(pin) (((pin + 1) << 1)) /**< pin set enables TX */ +#define UART_HALF_DUPLEX_DIR_PIN_CLEAR(pin) (((pin + 1) << 1) | 1) /**< pin clear enables TX */ + +/** + * @brief Configuration for half-duplex UART + */ +typedef struct { + uart_t uart; /**< the half-duplex UART bus to use */ + uint32_t baudrate; /**< the baudrate to use */ + uart_half_duplex_dir_t dir; /**< the direction management method */ +} uart_half_duplex_params_t; + +/** + * @brief Descriptor struct for half-duplex UART + */ +typedef struct { + uint8_t *buffer; /**< the buffer used for TX and RX */ + size_t size; /**< the number of available elements for TX/RX */ + size_t buffer_max_size; /**< the buffer size */ + uint32_t timeout_us; /**< the maximum duration (in microseconds) for waiting data */ + uart_half_duplex_params_t params; /**< the half-duplex UART configuration */ +} uart_half_duplex_t; + +/** + * @brief Possible UART_HALF_DUPLEX return values + */ +enum { + UART_HALF_DUPLEX_OK = UART_OK, /**< everything in order */ + UART_HALF_DUPLEX_NODEV = UART_NODEV, /**< invalid UART device given */ + UART_HALF_DUPLEX_NOBAUD = UART_NOBAUD, /**< given baudrate is not applicable */ + UART_HALF_DUPLEX_INTERR = UART_INTERR, /**< all other internal errors */ + UART_HALF_DUPLEX_NOMODE = UART_NOMODE, /**< given mode is not applicable */ + UART_HALF_DUPLEX_NOBUFF = -5 /**< invalid buffer given */ +}; + +/** + * @brief Initialize the half-duplex UART bus to communicate with devices + * + * @param[out] dev the device + * @param[in] buffer the buffer used for TX and RX + * @param[in] buffer_max_size the buffer size + * @param[in] params the initialization parameters + * + * @return UART_HALF_DUPLEX_OK if everything is in order + * @return UART_HALF_DUPLEX_NODEV if invalid UART device was given + * @return UART_HALF_DUPLEX_NOBAUD if given baudrate is not applicable + * @return UART_HALF_DUPLEX_INTERR if an other internal error occured + * @return UART_HALF_DUPLEX_NOMODE if the given mode is not applicable + * @return UART_HALF_DUPLEX_NOBUFF if an invalid buffer was given + */ +int uart_half_duplex_init(uart_half_duplex_t *dev, uint8_t *buffer, size_t buffer_max_size, const uart_half_duplex_params_t *params); + +/** + * @brief Set the half-duplex UART bus in TX mode + * + * @param[in] dev the device + */ +static inline void uart_half_duplex_set_tx(uart_half_duplex_t *dev) +{ + dev->size = dev->buffer_max_size; +} + +/** + * @brief Set the half-duplex UART bus in RX mode + * + * @param[in] dev the device + */ +static inline void uart_half_duplex_set_rx(uart_half_duplex_t *dev) +{ + dev->size = 0; +} + +/** + * @brief Send the data contained in the driver's buffer + * + * @param[in] dev the device + * @param[in] size the number of characters to send + * + * @return the number of characters actually sent + */ +size_t uart_half_duplex_send(uart_half_duplex_t *dev, size_t size); + +/** + * @brief Recv data an fill the driver's buffer + * + * @param[in] dev the device + * @param[in] size the number of characters to receive + * + * @return the number of characters actually received + */ +size_t uart_half_duplex_recv(uart_half_duplex_t *dev, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif +/** @} */ diff --git a/drivers/uart_half_duplex/uart_half_duplex.c b/drivers/uart_half_duplex/uart_half_duplex.c new file mode 100644 index 0000000000000000000000000000000000000000..7147899b77ac557df00b2417b5c0bf0db8ad2de9 --- /dev/null +++ b/drivers/uart_half_duplex/uart_half_duplex.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup drivers_uart_half_duplex + * @{ + * + * @file + * @brief Driver implementation for half-duplex UART devices + * + * @author Loïc Dauphin <loic.dauphin@inria.fr> + * + * @} + */ + +#include "uart_half_duplex.h" + +#include "periph/uart.h" +#include "periph/gpio.h" +#include "xtimer.h" + +#define IS_SET(dir) ((dir & 1) == 0) +#define IS_CLEAR(dir) ((dir & 1) == 1) + +#define IS_PIN(dir) (dir >= UART_HALF_DUPLEX_DIR_PIN_SET(0)) +#define GET_PIN(dir) ((dir >> 1) - 1) + +static inline void _enable_tx(uart_half_duplex_dir_t dir) +{ + if (IS_PIN(dir)) { + if (IS_SET(dir)) { + gpio_set(GET_PIN(dir)); + return; + } + if (IS_CLEAR(dir)) { + gpio_clear(GET_PIN(dir)); + return; + } + } +} + +static inline void _disable_tx(uart_half_duplex_dir_t dir) +{ + if (IS_PIN(dir)) { + if (IS_SET(dir)) { + gpio_clear(GET_PIN(dir)); + return; + } + if (IS_CLEAR(dir)) { + gpio_set(GET_PIN(dir)); + return; + } + } +} + +static void _rx_cb(void* data, uint8_t c) +{ + uart_half_duplex_t *dev = data; + if (dev->size < dev->buffer_max_size) { + dev->buffer[dev->size++] = c; + } +} + +int uart_half_duplex_init(uart_half_duplex_t *dev, uint8_t *buffer, size_t buffer_max_size, const uart_half_duplex_params_t *params) +{ + if (buffer == NULL || buffer_max_size <= 7) { + return UART_HALF_DUPLEX_NOBUFF; + } + + dev->buffer = buffer; + dev->buffer_max_size = buffer_max_size; + dev->params = *params; + dev->timeout_us = UART_HALF_DUPLEX_DEFAULT_TIMEOUT_US; + + if (IS_PIN(dev->params.dir)) { + gpio_init(GET_PIN(dev->params.dir), GPIO_OUT); + } + + int ret = uart_init(dev->params.uart, dev->params.baudrate, _rx_cb, dev); + + _disable_tx(dev->params.dir); + uart_half_duplex_set_rx(dev); + + return ret; +} + +size_t uart_half_duplex_send(uart_half_duplex_t *dev, size_t size) +{ + _enable_tx(dev->params.dir); + uart_write(dev->params.uart, dev->buffer, size); + _disable_tx(dev->params.dir); + return size; +} + +size_t uart_half_duplex_recv(uart_half_duplex_t *dev, size_t size) +{ + const uint32_t begin = xtimer_now_usec(); + while (xtimer_now_usec() - begin < dev->timeout_us) { + if (dev->size >= size) { + break; + } + } + return dev->size; +} diff --git a/tests/driver_feetech/Makefile b/tests/driver_feetech/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..7bcc6f2da02efe9f3a7e7409df98d70051d4cc7a --- /dev/null +++ b/tests/driver_feetech/Makefile @@ -0,0 +1,13 @@ +APPLICATION = driver_feetech +include ../Makefile.tests_common + +# chronos : USART_1 undeclared +BOARD_BLACKLIST += chronos + +# mips-malta : undefined reference to uart_write +BOARD_BLACKLIST += mips-malta + +USEMODULE += feetech +USEMODULE += shell + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_feetech/README.md b/tests/driver_feetech/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2a89c5198f010f573b11befb804aa838fbc0efdb --- /dev/null +++ b/tests/driver_feetech/README.md @@ -0,0 +1,40 @@ +# About + +This application is intended for testing Feetech TTL bus. +It is especially designed to test Feetech SCS15 servomotors. + +# Usage + +To have the list of available commands : +``` +help +``` + +You will need to initialize one UART at 1000000 baudrate (if the servomotor is in factory configuration) : +``` +init 1 1000000 +``` + +To ping the servomotor : +``` +ping 1 +``` + +Be careful ! If 2 servomotors with the same ID are connected on the same bus, you will have no response. +Factory configuration ID is 1, you need to change this to connect an other servo. + +To scan every connected servomotors (IDs from 0 to 253) : +``` +scan +``` + +To read a servo register : +``` +read 1 PRESENT_POSITION +``` + +To move a servo, you need to enable the torque and set the goal position [0-1024] : +``` +write 1 ENABLE_TORQUE 1 +write 1 GOAL_POSITION 512 +``` diff --git a/tests/driver_feetech/main.c b/tests/driver_feetech/main.c new file mode 100644 index 0000000000000000000000000000000000000000..8eb6379096a4f705e0bb8eee14bfa0a379d2bbb4 --- /dev/null +++ b/tests/driver_feetech/main.c @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2017 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +#include "feetech.h" +#include "shell.h" +#include "shell_commands.h" +#include "uart_stdio.h" + +#include <stdio.h> +#include <string.h> + +#define ARRAY_LEN(array) (sizeof(array)/sizeof(array[0])) + +typedef struct { + const char *name; + int addr; +} reg_name_addr_t; + +static const reg_name_addr_t regs8[] = { + { "ID", SCS15_ID }, + { "BAUD_RATE", SCS15_BAUD_RATE }, + { "RETURN_DELAY_TIME", SCS15_RETURN_DELAY_TIME }, + { "RETURN_LEVEL", SCS15_RETURN_LEVEL }, + { "LIMIT_TEMPERATURE", SCS15_LIMIT_TEMPERATURE }, + { "MAX_LIMIT_VOLTAGE", SCS15_MAX_LIMIT_VOLTAGE }, + { "MIN_LIMIT_VOLTAGE", SCS15_MIN_LIMIT_VOLTAGE }, + { "ALARM_LED", SCS15_ALARM_LED }, + { "ALARM_SHUTDOWN", SCS15_ALARM_SHUTDOWN }, + { "COMPLIANCE_P", SCS15_COMPLIANCE_P }, + { "COMPLIANCE_D", SCS15_COMPLIANCE_D }, + { "COMPLIANCE_I", SCS15_COMPLIANCE_I }, + { "CW_DEAD", SCS15_CW_DEAD }, + { "CCW_DEAD", SCS15_CCW_DEAD }, + { "TORQUE_ENABLE", SCS15_TORQUE_ENABLE }, + { "LED", SCS15_LED }, + { "LOCK", SCS15_LOCK }, + { "PRESENT_VOLTAGE", SCS15_PRESENT_VOLTAGE }, + { "PRESENT_TEMPERATURE", SCS15_PRESENT_TEMPERATURE }, + { "REGISTERED_INSTRUCTION", SCS15_REGISTERED_INSTRUCTION }, + { "ERROR", SCS15_ERROR }, + { "MOVING", SCS15_MOVING }, +}; + +static const reg_name_addr_t regs16[] = { + { "MODEL_NUMBER", SCS15_MODEL_NUMBER }, + { "VERSION", SCS15_VERSION }, + { "MIN_ANGLE_LIMIT", SCS15_MIN_ANGLE_LIMIT }, + { "MAX_ANGLE_LIMIT", SCS15_MAX_ANGLE_LIMIT }, + { "MAX_TORQUE", SCS15_MAX_TORQUE }, + { "PUNCH", SCS15_PUNCH }, + { "IMAX", SCS15_IMAX }, + { "OFFSET", SCS15_OFFSET }, + { "GOAL_POSITION", SCS15_GOAL_POSITION }, + { "GOAL_TIME", SCS15_GOAL_TIME }, + { "GOAL_SPEED", SCS15_GOAL_SPEED }, + { "PRESENT_POSITION", SCS15_PRESENT_POSITION }, + { "PRESENT_SPEED", SCS15_PRESENT_SPEED }, + { "PRESENT_LOAD", SCS15_PRESENT_LOAD }, + { "VIR_POSITION", SCS15_VIR_POSITION }, + { "CURRENT", SCS15_CURRENT }, +}; + +static const int32_t baudrates[] = { + 1000000L, + 500000L, + 250000L, + 128000L, + 115200L, + 76800L, + 57600L, + 38400L, +}; + +static uint8_t feetech_buffer[128]; +static uart_half_duplex_t stream; + +static int parse_uart(char *arg) +{ + unsigned uart = (unsigned)atoi(arg); + if (uart >= UART_NUMOF) { + printf("Error: Invalid UART_DEV device specified (%u).\n", uart); + return -1; + } + else if (UART_DEV(uart) == UART_STDIO_DEV) { + printf("Error: The selected UART_DEV(%u) is used for the shell!\n", uart); + return -2; + } + return uart; +} + +static int32_t parse_baud(char *arg) +{ + int32_t baud = (int32_t)atoi(arg); + + for (size_t i = 0 ; i < ARRAY_LEN(baudrates) ; i++) { + if(baud == baudrates[i]) { + return baud; + } + } + + printf("Error: Invalid baudrate (%s)\n", arg); + return -1; +} + +static int parse_dev(char *arg) +{ + int dev = (int)atoi(arg); + if (dev < 0 || 254 < dev) { + printf("Error: Invalid device id (%s)\n", arg); + return -1; + } + return dev; +} + +static void parse_reg(char *arg, int *reg8, int *reg16) +{ + *reg8 = -1; + *reg16 = -1; + + for (size_t i = 0 ; i < ARRAY_LEN(regs8) ; i++) { + if(strcmp(arg, regs8[i].name) == 0) { + *reg8 = regs8[i].addr; + return; + } + } + + for (size_t i = 0 ; i < ARRAY_LEN(regs16) ; i++) { + if(strcmp(arg, regs16[i].name) == 0) { + *reg16 = regs16[i].addr; + return; + } + } + + printf("Error: Invalid register (%s)\n", arg); +} + +void print_registers(void) { + puts("available 8bits registers :"); + for (size_t i = 0 ; i < ARRAY_LEN(regs8) ; i++) { + printf("\t%s\n", regs8[i].name); + } + + puts("available 16bits registers :"); + for (size_t i = 0 ; i < ARRAY_LEN(regs16) ; i++) { + printf("\t%s\n", regs16[i].name); + } +} + +static int cmd_init(int argc, char **argv) { + int uart = -1; + int baud = -1; + uint32_t timeout = -1; + + if (argc != 3 && argc != 4) { + printf("usage; %s <uart> <baudrate> [<timeout_us>]\n", argv[0]); + puts("available baudrates :"); + for (size_t i = 0 ; i < ARRAY_LEN(baudrates) ; i++) { + printf("\t%ld\n", (long int)baudrates[i]); + } + return 1; + } + /* parse parameters */ + uart = parse_uart(argv[1]); + if (uart < 0) { + return -1; + } + + baud = parse_baud(argv[2]); + if (baud < 0) { + return -1; + } + + if (argc == 4) { + timeout = (uint32_t)atol(argv[3]); + if (timeout == 0) { + printf("Error : Invalid timeout (%s)", argv[3]); + return -1; + } + } + + /* init */ + uart_half_duplex_params_t params = { + .uart = uart, + .baudrate = baud, + .dir = UART_HALF_DUPLEX_DIR_NONE, + }; + + int ret = uart_half_duplex_init(&stream, feetech_buffer, ARRAY_LEN(feetech_buffer), ¶ms); + + if (argc == 4) { + stream.timeout_us = timeout; + } + + if (ret == UART_HALF_DUPLEX_NODEV) { + puts("Error: invalid UART device given"); + return -1; + } + if (ret == UART_HALF_DUPLEX_NOBAUD) { + puts("Error: given baudrate is not applicable"); + return -1; + } + if (ret == UART_HALF_DUPLEX_INTERR) { + puts("Error: internal error"); + return -1; + } + if (ret == UART_HALF_DUPLEX_NOMODE) { + puts("Error: given mode is not applicable"); + return -1; + } + if (ret == UART_HALF_DUPLEX_NOBUFF) { + puts("Error: invalid buffer given"); + return -1; + } + + printf("Successfully initialized Feetech TTL bus UART_DEV(%i)\n", uart); + return 0; +} + +static int cmd_ping(int argc, char **argv) { + int id = -1; + + if (argc != 2) { + printf("usage; %s <dev_id>\n", argv[0]); + return 1; + } + /* parse parameters */ + id = parse_dev(argv[1]); + if (id < 0) { + return -1; + } + + /* ping */ + if (feetech_ping(&stream, id) == FEETECH_OK) { + printf("Device %i responded\n", id); + } + else { + printf("No response from %i\n", id); + } + return 0; +} + +static int cmd_scan(int argc, char **argv) { + int min = -1; + int max = -1; + + if (argc == 3) { + min = atoi(argv[1]); + max = atoi(argv[2]); + if (min < 0) { + return -1; + } + if (max > 254) { + return -1; + } + if (max < min) { + return -1; + } + } + else if (argc == 1) { + min = 0; + max = 254; + } + else { + printf("usage; %s [<min_id> <max_id>]\n", argv[0]); + return 1; + } + + /* ping */ + puts("Scanning..."); + for (int id = min ; id < max ; id++) { + if (feetech_ping(&stream, id) == FEETECH_OK) { + printf("Device %i available\n", id); + } + } + puts("End"); + return 0; +} + +static int cmd_read(int argc, char **argv) { + int id = -1; + int reg8 = -1; + int reg16 = -1; + + if (argc != 3) { + printf("usage; %s <dev_id> <reg>\n", argv[0]); + print_registers(); + return 1; + } + /* parse parameters */ + id = parse_dev(argv[1]); + if (id < 0) { + return -1; + } + + parse_reg(argv[2], ®8, ®16); + if (reg8 < 0 && reg16 < 0) { + return -1; + } + + /* read */ + feetech_t dev; + feetech_init(&dev, &stream, id); + if (reg8 >= 0) { + uint8_t val = 0; + int ret = feetech_read8(&dev, reg8, &val); + if (ret != FEETECH_OK) { + printf("Error[%i] : No response from %i\n", ret, id); + return -1; + } + printf("%i\n", (int)val); + } + else { + uint16_t val = 0; + int ret = feetech_read16(&dev, reg16, &val); + if (ret != FEETECH_OK) { + printf("Error[%i] : No response from %i\n", ret, id); + return -1; + } + printf("%i\n", (int)val); + } + return 0; +} + +static int cmd_write(int argc, char **argv) { + int id = -1; + int reg8 = -1; + int reg16 = -1; + + if (argc != 4) { + printf("usage; %s <dev_id> <reg> <value>\n", argv[0]); + print_registers(); + return 1; + } + /* parse parameters */ + id = parse_dev(argv[1]); + if (id < 0) { + return -1; + } + + parse_reg(argv[2], ®8, ®16); + if (reg8 < 0 && reg16 < 0) { + return -1; + } + + int val = atoi(argv[3]); + if (val < 0) { + return -1; + } + + /* read */ + feetech_t dev; + feetech_init(&dev, &stream, id); + if (reg8 >= 0) { + int ret = feetech_write8(&dev, reg8, val); + if (ret != FEETECH_OK) { + printf("Error[%i] : No response from %i\n", ret, id); + return -1; + } + printf("Written %i at address %i\n", (int)val, reg8); + } + else { + int ret = feetech_write16(&dev, reg16, val); + if (ret != FEETECH_OK) { + printf("Error[%i] : No response from %i\n", ret, id); + return -1; + } + printf("Written %i at address %i\n", (int)val, reg16); + } + return 0; +} + +static const shell_command_t shell_commands[] = { + { "init", "Initialize a Feetech TTL bus with a given baudrate", cmd_init }, + { "ping", "Ping a Feetech device", cmd_ping }, + { "scan", "Find all Feetech devices between min_id and max_id", cmd_scan }, + { "read", "Read a Feetech device register", cmd_read }, + { "write", "Write in a Feetech device register", cmd_write }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + puts("\nManual Feetech device driver test"); + puts("==================================="); + puts("This application is intended for testing Feetech TTL bus\n"); + + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + return 0; +}