/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
* ArduPilot device driver for SLAMTEC RPLIDAR A2 (16m range version)
*
* ALL INFORMATION REGARDING PROTOCOL WAS DERIVED FROM RPLIDAR DATASHEET:
*
* https://www.slamtec.com/en/Lidar
* http://bucket.download.slamtec.com/63ac3f0d8c859d3a10e51c6b3285fcce25a47357/LR001_SLAMTEC_rplidar_protocol_v1.0_en.pdf
*
* Author: Steven Josefs, IAV GmbH
* Based on the LightWare SF40C ArduPilot device driver from Randy Mackay
*
*/
#include
#include "AP_Proximity_RPLidarA2.h"
#include
#include
#include
#define RP_DEBUG_LEVEL 0
#if RP_DEBUG_LEVEL
#include
#define Debug(level, fmt, args ...) do { if (level <= RP_DEBUG_LEVEL) { gcs().send_text(MAV_SEVERITY_INFO, fmt, ## args); } } while (0)
#else
#define Debug(level, fmt, args ...)
#endif
#define COMM_ACTIVITY_TIMEOUT_MS 200
#define RESET_RPA2_WAIT_MS 8
#define RESYNC_TIMEOUT 5000
// Commands
//-----------------------------------------
// Commands without payload and response
#define RPLIDAR_PREAMBLE 0xA5
#define RPLIDAR_CMD_STOP 0x25
#define RPLIDAR_CMD_SCAN 0x20
#define RPLIDAR_CMD_FORCE_SCAN 0x21
#define RPLIDAR_CMD_RESET 0x40
// Commands without payload but have response
#define RPLIDAR_CMD_GET_DEVICE_INFO 0x50
#define RPLIDAR_CMD_GET_DEVICE_HEALTH 0x52
// Commands with payload and have response
#define RPLIDAR_CMD_EXPRESS_SCAN 0x82
extern const AP_HAL::HAL& hal;
/*
The constructor also initialises the proximity sensor. Note that this
constructor is not called until detect() returns true, so we
already know that we should setup the proximity sensor
*/
AP_Proximity_RPLidarA2::AP_Proximity_RPLidarA2(
AP_Proximity &_frontend,
AP_Proximity::Proximity_State &_state) :
AP_Proximity_Backend(_frontend, _state)
{
const AP_SerialManager &serial_manager = AP::serialmanager();
_uart = serial_manager.find_serial(AP_SerialManager::SerialProtocol_Lidar360, 0);
if (_uart != nullptr) {
_uart->begin(serial_manager.find_baudrate(AP_SerialManager::SerialProtocol_Lidar360, 0));
}
_cnt = 0 ;
_sync_error = 0 ;
_byte_count = 0;
}
// detect if a RPLidarA2 proximity sensor is connected by looking for a configured serial port
bool AP_Proximity_RPLidarA2::detect()
{
return AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_Lidar360, 0) != nullptr;
}
// update the _rp_state of the sensor
void AP_Proximity_RPLidarA2::update(void)
{
if (_uart == nullptr) {
return;
}
// initialise sensor if necessary
if (!_initialised) {
_initialised = initialise(); //returns true if everything initialized properly
}
// if LIDAR in known state
if (_initialised) {
get_readings();
}
// check for timeout and set health status
if ((_last_distance_received_ms == 0) || (AP_HAL::millis() - _last_distance_received_ms > COMM_ACTIVITY_TIMEOUT_MS)) {
set_status(AP_Proximity::Status::NoData);
Debug(1, "LIDAR NO DATA");
} else {
set_status(AP_Proximity::Status::Good);
}
}
// get maximum distance (in meters) of sensor
float AP_Proximity_RPLidarA2::distance_max() const
{
return 16.0f; //16m max range RPLIDAR2, if you want to support the 8m version this is the only line to change
}
// get minimum distance (in meters) of sensor
float AP_Proximity_RPLidarA2::distance_min() const
{
return 0.20f; //20cm min range RPLIDAR2
}
bool AP_Proximity_RPLidarA2::initialise()
{
// initialise sectors
if (!_sector_initialised) {
init_sectors();
return false;
}
if (!_initialised) {
reset_rplidar(); // set to a known state
Debug(1, "LIDAR initialised");
return true;
}
return true;
}
void AP_Proximity_RPLidarA2::reset_rplidar()
{
if (_uart == nullptr) {
return;
}
uint8_t tx_buffer[2] = {RPLIDAR_PREAMBLE, RPLIDAR_CMD_RESET};
_uart->write(tx_buffer, 2);
_resetted = true; ///< be aware of extra 63 bytes coming after reset containing FW information
Debug(1, "LIDAR reset");
// To-Do: ensure delay of 8m after sending reset request
_last_reset_ms = AP_HAL::millis();
_rp_state = rp_resetted;
}
// initialise sector angles using user defined ignore areas, left same as SF40C
void AP_Proximity_RPLidarA2::init_sectors()
{
// use defaults if no ignore areas defined
const uint8_t ignore_area_count = get_ignore_area_count();
if (ignore_area_count == 0) {
_sector_initialised = true;
return;
}
uint8_t sector = 0;
for (uint8_t i=0; i 0) && (sector < PROXIMITY_SECTORS_MAX)) {
uint16_t sector_size;
if (degrees_to_fill >= 90) {
// set sector to maximum of 45 degrees
sector_size = 45;
} else if (degrees_to_fill > 45) {
// use half the remaining area to optimise size of this sector and the next
sector_size = degrees_to_fill / 2.0f;
} else {
// 45 degrees or less are left so put it all into the next sector
sector_size = degrees_to_fill;
}
// record the sector middle and width
_sector_middle_deg[sector] = wrap_360(start_angle + sector_size / 2.0f);
_sector_width_deg[sector] = sector_size;
// move onto next sector
start_angle += sector_size;
sector++;
degrees_to_fill -= sector_size;
}
}
}
// set num sectors
_num_sectors = sector;
// re-initialise boundary because sector locations have changed
init_boundary();
// record success
_sector_initialised = true;
}
// set Lidar into SCAN mode
void AP_Proximity_RPLidarA2::set_scan_mode()
{
if (_uart == nullptr) {
return;
}
uint8_t tx_buffer[2] = {RPLIDAR_PREAMBLE, RPLIDAR_CMD_SCAN};
_uart->write(tx_buffer, 2);
_last_request_ms = AP_HAL::millis();
Debug(1, "LIDAR SCAN MODE ACTIVATED");
_rp_state = rp_responding;
}
// send request for sensor health
void AP_Proximity_RPLidarA2::send_request_for_health() //not called yet
{
if (_uart == nullptr) {
return;
}
uint8_t tx_buffer[2] = {RPLIDAR_PREAMBLE, RPLIDAR_CMD_GET_DEVICE_HEALTH};
_uart->write(tx_buffer, 2);
_last_request_ms = AP_HAL::millis();
_rp_state = rp_health;
}
void AP_Proximity_RPLidarA2::get_readings()
{
if (_uart == nullptr) {
return;
}
Debug(2, " CURRENT STATE: %d ", _rp_state);
uint32_t nbytes = _uart->available();
while (nbytes-- > 0) {
uint8_t c = _uart->read();
Debug(2, "UART READ %x <%c>", c, c); //show HEX values
STATE:
switch(_rp_state){
case rp_resetted:
Debug(3, " BYTE_COUNT %d", _byte_count);
if ((c == 0x52 || _information_data) && _byte_count < 62) {
if (c == 0x52) {
_information_data = true;
}
_rp_systeminfo[_byte_count] = c;
Debug(3, "_rp_systeminfo[%d]=%x",_byte_count,_rp_systeminfo[_byte_count]);
_byte_count++;
break;
} else {
if (_information_data) {
Debug(1, "GOT RPLIDAR INFORMATION");
_information_data = false;
_byte_count = 0;
set_scan_mode();
break;
}
if (_cnt>5) {
_rp_state = rp_unknown;
_cnt=0;
break;
}
_cnt++;
break;
}
break;
case rp_responding:
Debug(2, "RESPONDING");
if (c == RPLIDAR_PREAMBLE || _descriptor_data) {
_descriptor_data = true;
_descriptor[_byte_count] = c;
_byte_count++;
// descriptor packet has 7 byte in total
if (_byte_count == sizeof(_descriptor)) {
Debug(2,"LIDAR DESCRIPTOR CATCHED");
_response_type = ResponseType_Descriptor;
// identify the payload data after the descriptor
parse_response_descriptor();
_byte_count = 0;
}
} else {
_rp_state = rp_unknown;
}
break;
case rp_measurements:
if (_sync_error) {
// out of 5-byte sync mask -> catch new revolution
Debug(1, " OUT OF SYNC");
// on first revolution bit 1 = 1, bit 2 = 0 of the first byte
if ((c & 0x03) == 0x01) {
_sync_error = 0;
Debug(1, " RESYNC");
} else {
if (AP_HAL::millis() - _last_distance_received_ms > RESYNC_TIMEOUT) {
reset_rplidar();
}
break;
}
}
Debug(3, "READ PAYLOAD");
payload[_byte_count] = c;
_byte_count++;
if (_byte_count == _payload_length) {
Debug(2, "LIDAR MEASUREMENT CATCHED");
parse_response_data();
_byte_count = 0;
}
break;
case rp_health:
Debug(1, "state: HEALTH");
break;
case rp_unknown:
Debug(1, "state: UNKNOWN");
if (c == RPLIDAR_PREAMBLE) {
_rp_state = rp_responding;
goto STATE;
break;
}
_cnt++;
if (_cnt>10) {
reset_rplidar();
_rp_state = rp_resetted;
_cnt=0;
}
break;
default:
Debug(1, "UNKNOWN LIDAR STATE");
break;
}
}
}
void AP_Proximity_RPLidarA2::parse_response_descriptor()
{
// check if descriptor packet is valid
if (_descriptor[0] == RPLIDAR_PREAMBLE && _descriptor[1] == 0x5A) {
if (_descriptor[2] == 0x05 && _descriptor[3] == 0x00 && _descriptor[4] == 0x00 && _descriptor[5] == 0x40 && _descriptor[6] == 0x81) {
// payload is SCAN measurement data
_payload_length = sizeof(payload.sensor_scan);
static_assert(sizeof(payload.sensor_scan) == 5, "Unexpected payload.sensor_scan data structure size");
_response_type = ResponseType_SCAN;
Debug(2, "Measurement response detected");
_last_distance_received_ms = AP_HAL::millis();
_rp_state = rp_measurements;
}
if (_descriptor[2] == 0x03 && _descriptor[3] == 0x00 && _descriptor[4] == 0x00 && _descriptor[5] == 0x00 && _descriptor[6] == 0x06) {
// payload is health data
_payload_length = sizeof(payload.sensor_health);
static_assert(sizeof(payload.sensor_health) == 3, "Unexpected payload.sensor_health data structure size");
_response_type = ResponseType_Health;
_last_distance_received_ms = AP_HAL::millis();
_rp_state= rp_health;
}
return;
}
Debug(1, "Invalid response descriptor");
_rp_state = rp_unknown;
}
void AP_Proximity_RPLidarA2::parse_response_data()
{
switch (_response_type){
case ResponseType_SCAN:
Debug(2, "UART %02x %02x%02x %02x%02x", payload[0], payload[2], payload[1], payload[4], payload[3]); //show HEX values
// check if valid SCAN packet: a valid packet starts with startbits which are complementary plus a checkbit in byte+1
if ((payload.sensor_scan.startbit == !payload.sensor_scan.not_startbit) && payload.sensor_scan.checkbit) {
const float angle_sign = (frontend.get_orientation(state.instance) == 1) ? -1.0f : 1.0f;
const float angle_deg = wrap_360(payload.sensor_scan.angle_q6/64.0f * angle_sign + frontend.get_yaw_correction(state.instance));
const float distance_m = (payload.sensor_scan.distance_q2/4000.0f);
#if RP_DEBUG_LEVEL >= 2
const float quality = payload.sensor_scan.quality;
Debug(2, " D%02.2f A%03.1f Q%02d", distance_m, angle_deg, quality);
#endif
_last_distance_received_ms = AP_HAL::millis();
uint8_t sector;
if (convert_angle_to_sector(angle_deg, sector)) {
if (distance_m > distance_min()) {
if (_last_sector == sector) {
if (_distance_m_last > distance_m) {
_distance_m_last = distance_m;
_angle_deg_last = angle_deg;
}
} else {
// a new sector started, the previous one can be updated now
_angle[_last_sector] = _angle_deg_last;
_distance[_last_sector] = _distance_m_last;
_distance_valid[_last_sector] = true;
// update boundary used for avoidance
update_boundary_for_sector(_last_sector, true);
// initialize the new sector
_last_sector = sector;
_distance_m_last = distance_m;
_angle_deg_last = angle_deg;
}
} else {
_distance_valid[sector] = false;
}
}
} else {
// not valid payload packet
Debug(1, "Invalid Payload");
_sync_error++;
}
break;
case ResponseType_Health:
// health issue if status is "3" ->HW error
if (payload.sensor_health.status == 3) {
Debug(1, "LIDAR Error");
}
break;
default:
// no valid payload packets recognized: return payload data=0
Debug(1, "Unknown LIDAR packet");
break;
}
}