Driving possible

This commit is contained in:
Matthias Grob 2021-12-14 16:48:22 +01:00
parent ab486de430
commit a857df88e4
4 changed files with 121 additions and 76 deletions

View File

@ -55,6 +55,7 @@
#include <uORB/Publication.hpp>
#include <drivers/drv_hrt.h>
#include <math.h>
#include <mathlib/mathlib.h>
// The RoboClaw has a serial communication timeout of 10ms.
// Add a little extra to account for timing inaccuracy
@ -75,6 +76,8 @@
bool RoboClaw::taskShouldExit = false;
using namespace time_literals;
RoboClaw::RoboClaw(const char *deviceName, const char *baudRateParam):
_uart(0),
_uart_set(),
@ -88,6 +91,7 @@ RoboClaw::RoboClaw(const char *deviceName, const char *baudRateParam):
_param_handles.actuator_write_period_ms = param_find("RBCLW_WRITE_PER");
_param_handles.encoder_read_period_ms = param_find("RBCLW_READ_PER");
_param_handles.counts_per_rev = param_find("RBCLW_COUNTS_REV");
printf("Baudrate parameter: %s\n", baudRateParam);
_param_handles.serial_baud_rate = param_find(baudRateParam);
_param_handles.address = param_find("RBCLW_ADDRESS");
@ -105,6 +109,7 @@ RoboClaw::RoboClaw(const char *deviceName, const char *baudRateParam):
if (ret < 0) { err(1, "failed to get attr"); }
uart_config.c_oflag &= ~ONLCR; // no CR for every LF
uart_config.c_cflag &= ~CRTSCTS;
ret = cfsetispeed(&uart_config, _parameters.serial_baud_rate);
if (ret < 0) { err(1, "failed to set input speed"); }
@ -118,15 +123,16 @@ RoboClaw::RoboClaw(const char *deviceName, const char *baudRateParam):
if (ret < 0) { err(1, "failed to set attr"); }
FD_ZERO(&_uart_set);
FD_SET(_uart, &_uart_set);
// setup default settings, reset encoders
resetEncoders();
//resetEncoders();
}
RoboClaw::~RoboClaw()
{
setMotorDutyCycle(MOTOR_1, 0.0);
setMotorDutyCycle(MOTOR_2, 0.0);
// setMotorDutyCycle(MOTOR_1, 0.0);
// setMotorDutyCycle(MOTOR_2, 0.0);
close(_uart);
}
@ -149,10 +155,7 @@ void RoboClaw::taskMain()
// It is updated at the end of every loop. Sometimes, if the actuator_controls_0 message came in right before
// I should have read the encoders, waitTime will be 0. This is fine. When waitTime is 0, poll() will return
// immediately with a timeout. (Or possibly with a message, if one happened to be available at that exact moment)
uint64_t encoderTaskLastRun = 0;
int waitTime = 0;
uint64_t actuatorsLastWritten = 0;
int waitTime = 100_ms;
_actuatorsSub = orb_subscribe(ORB_ID(actuator_controls_0));
orb_set_interval(_actuatorsSub, _parameters.actuator_write_period_ms);
@ -168,74 +171,35 @@ void RoboClaw::taskMain()
fds[2].fd = _armedSub;
fds[2].events = POLLIN;
memset((void *) &_wheelEncoderMsg[0], 0, sizeof(_wheelEncoderMsg));
_wheelEncoderMsg[0].pulses_per_rev = _parameters.counts_per_rev;
_wheelEncoderMsg[1].pulses_per_rev = _parameters.counts_per_rev;
while (!taskShouldExit) {
int pret = poll(fds, sizeof(fds) / sizeof(pollfd), waitTime / 1000);
bool actuators_timeout = int(hrt_absolute_time() - actuatorsLastWritten) > 2000 * _parameters.actuator_write_period_ms;
if (fds[0].revents & POLLIN) {
orb_copy(ORB_ID(parameter_update), _paramSub, &_paramUpdate);
_parameters_update();
}
// No timeout, update on either the actuator controls or the armed state
if (pret > 0 && (fds[1].revents & POLLIN || fds[2].revents & POLLIN || actuators_timeout)) {
if (pret > 0 && (fds[1].revents & POLLIN || fds[2].revents & POLLIN)) {
orb_copy(ORB_ID(actuator_controls_0), _actuatorsSub, &_actuatorControls);
orb_copy(ORB_ID(actuator_armed), _armedSub, &_actuatorArmed);
int drive_ret = 0, turn_ret = 0;
const bool disarmed = !_actuatorArmed.armed || _actuatorArmed.lockdown || _actuatorArmed.manual_lockdown
|| _actuatorArmed.force_failsafe || actuators_timeout;
|| _actuatorArmed.force_failsafe;
if (disarmed) {
// If disarmed, I want to be certain that the stop command gets through.
int tries = 0;
while (tries < STOP_RETRIES && ((drive_ret = drive(0.0)) <= 0 || (turn_ret = turn(0.0)) <= 0)) {
PX4_ERR("Error trying to stop: Drive: %d, Turn: %d", drive_ret, turn_ret);
tries++;
px4_usleep(TIMEOUT_US);
}
setMotorDutyCycle(MOTOR_1, 0.f);
setMotorDutyCycle(MOTOR_2, 0.f);
} else {
drive_ret = drive(_actuatorControls.control[actuator_controls_s::INDEX_THROTTLE]);
turn_ret = turn(_actuatorControls.control[actuator_controls_s::INDEX_YAW]);
if (drive_ret <= 0 || turn_ret <= 0) {
PX4_ERR("Error controlling RoboClaw. Drive err: %d. Turn err: %d", drive_ret, turn_ret);
}
}
actuatorsLastWritten = hrt_absolute_time();
} else {
// A timeout occurred, which means that it's time to update the encoders
encoderTaskLastRun = hrt_absolute_time();
if (readEncoder() > 0) {
for (int i = 0; i < 2; i++) {
_wheelEncoderMsg[i].timestamp = encoderTaskLastRun;
_wheelEncoderMsg[i].encoder_position = _encoderCounts[i];
_wheelEncoderMsg[i].speed = _motorSpeeds[i];
_wheelEncodersAdv[i].publish(_wheelEncoderMsg[i]);
}
} else {
PX4_ERR("Error reading encoders");
const float throttle = _actuatorControls.control[actuator_controls_s::INDEX_THROTTLE];
const float yaw = _actuatorControls.control[actuator_controls_s::INDEX_YAW];
const float scale = 0.3f;
setMotorDutyCycle(MOTOR_1, (throttle - yaw) * scale);
setMotorDutyCycle(MOTOR_2, (throttle + yaw) * scale);
}
}
waitTime = _parameters.encoder_read_period_ms * 1000 - (hrt_absolute_time() - encoderTaskLastRun);
waitTime = waitTime < 0 ? 0 : waitTime;
}
orb_unsubscribe(_actuatorsSub);
@ -426,16 +390,12 @@ int RoboClaw::_sendUnsigned7Bit(e_command command, float data)
int RoboClaw::_sendSigned16Bit(e_command command, float data)
{
if (data > 1.0f) {
data = 1.0f;
} else if (data < -1.0f) {
data = -1.0f;
}
auto buff = (uint16_t)(data * INT16_MAX);
int16_t duty = math::constrain(data, -1.f, 1.f) * INT16_MAX;
uint8_t buff[2];
buff[0] = (duty >> 8) & 0xFF; // High byte
buff[1] = duty & 0xFF; // Low byte
uint8_t recv_buff;
return _transaction(command, (uint8_t *) &buff, 2, &recv_buff, 1);
return _transaction(command, (uint8_t *) &buff, 2, &recv_buff, 2);
}
int RoboClaw::_sendNothing(e_command command)
@ -488,6 +448,14 @@ int RoboClaw::_transaction(e_command cmd, uint8_t *wbuff, size_t wbytes,
buf[wbytes - 1] = sum & 0xFFu;
}
// printf("Write: ");
// for (size_t i = 0; i < wbytes; i++) {
// printf("%X ", buf[i]);
// }
// printf("\n");
int count = write(_uart, buf, wbytes);
if (count < (int) wbytes) { // Did not successfully send all bytes.
@ -495,30 +463,37 @@ int RoboClaw::_transaction(e_command cmd, uint8_t *wbuff, size_t wbytes,
return -1;
}
// else {
// printf("Successfully wrote %d of %zu bytes\n", count, wbytes);
// }
// READ
FD_ZERO(&_uart_set);
FD_SET(_uart, &_uart_set);
uint8_t *rbuff_curr = rbuff;
size_t bytes_read = 0;
// select(...) returns as soon as even 1 byte is available. read(...) returns immediately, no matter how many
// bytes are available. I need to keep reading until I get the number of bytes I expect.
//printf("Read: \n");
while (bytes_read < rbytes) {
// select(...) may change this timeout struct (because it is not const). So I reset it every time.
_uart_timeout.tv_sec = 0;
_uart_timeout.tv_usec = TIMEOUT_US;
err_code = select(_uart + 1, &_uart_set, nullptr, nullptr, &_uart_timeout);
//printf("Select: %d\n", err_code);
// An error code of 0 means that select timed out, which is how the Roboclaw indicates an error.
if (err_code <= 0) {
printf("select error: %d\n", err_code);
return err_code;
}
err_code = read(_uart, rbuff_curr, rbytes - bytes_read);
//printf("Read: %d\n", err_code);
if (err_code <= 0) {
printf("read error: %d\n", err_code);
return err_code;
} else {
@ -527,6 +502,14 @@ int RoboClaw::_transaction(e_command cmd, uint8_t *wbuff, size_t wbytes,
}
}
// printf("Read: ");
// for (size_t i = 0; i < bytes_read; i++) {
// printf("%X ", rbuff[i]);
// }
// printf("\n");
//TODO: Clean up this mess of IFs and returns
if (recv_checksum) {
@ -540,6 +523,7 @@ int RoboClaw::_transaction(e_command cmd, uint8_t *wbuff, size_t wbytes,
checksum_calc = _calcCRC(rbuff, bytes_read - 2, checksum_calc);
uint16_t checksum_recv = (rbuff[bytes_read - 2] << 8) + rbuff[bytes_read - 1];
//printf("crc calc: %X crc rec: %X\n", checksum_calc, checksum_recv);
if (checksum_calc == checksum_recv) {
return bytes_read;

View File

@ -0,0 +1,66 @@
/****************************************************************************
*
* Copyright (c) 2021 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file RoboclawSerialDevice.hpp
* @brief
* @author Matthias Grob <maetugr@gmail.com>
*/
#include "RoboclawDriver/RoboclawDriver.hpp"
#include <px4_platform_common/px4_work_queue/ScheduledWorkItem.hpp>
class RoboclawDevice : public RoboclawWritableInterface
{
public:
RoboclawDevice(const char *port);
~RoboclawDevice();
int init();
void printStatus();
private:
void Run();
size_t writeCallback(const uint8_t *buffer, const uint16_t length) override;
int setBaudrate(const unsigned baudrate);
static constexpr size_t READ_BUFFER_SIZE{256};
static constexpr int DISARM_VALUE = 0;
static constexpr int MIN_THROTTLE = 100;
static constexpr int MAX_THROTTLE = 1000;
char _port[20] {};
int _serial_fd{-1};
VescDriver _vesc_driver;
MixingOutput _mixing_output{4, *this, MixingOutput::SchedulingPolicy::Auto, false, false};
perf_counter_t _cycle_perf{perf_alloc(PC_ELAPSED, MODULE_NAME": cycle")};
};

View File

@ -3,4 +3,4 @@ serial_config:
- command: roboclaw start ${SERIAL_DEV} ${BAUD_PARAM}
port_config_param:
name: RBCLW_SER_CFG
group: Roboclaw
group: Roboclaw

View File

@ -82,11 +82,11 @@ static void usage()
PRINT_MODULE_DESCRIPTION(R"DESCR_STR(
### Description
This driver communicates over UART with the [Roboclaw motor driver](http://downloads.basicmicro.com/docs/roboclaw_user_manual.pdf).
This driver communicates over UART with the [Roboclaw motor driver](https://www.basicmicro.com/motor-controller).
It performs two tasks:
- Control the motors based on the `actuator_controls_0` UOrb topic.
- Read the wheel encoders and publish the raw data in the `wheel_encoders` UOrb topic
- Control the motors based on the `actuator_controls_0` uORB topic.
- Read the wheel encoders and publish the raw data in the `wheel_encoders` uORB topic
In order to use this driver, the Roboclaw should be put into Packet Serial mode (see the linked documentation), and
your flight controller's UART port should be connected to the Roboclaw as shown in the documentation. For Pixhawk 4,
@ -132,12 +132,7 @@ All available commands are:
*/
int roboclaw_main(int argc, char *argv[])
{
if (argc < 4) {
usage();
}
if (!strcmp(argv[1], "start")) {
if (!strcmp(argv[1], "start") && (argc >= 4)) {
if (thread_running) {
printf("roboclaw already running\n");