mirror of
https://github.com/ArduPilot/ardupilot
synced 2025-01-03 14:38:30 -04:00
490841a814
By opening with O_CLOEXEC we make sure we don't leak the file descriptor when we are exec'ing or calling out subprograms. Right now we currently don't do it so there's no harm, but it's good practice in Linux to have it.
179 lines
5.3 KiB
C++
179 lines
5.3 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/*
|
|
this is a driver for SBUS input in Linux board using a UART. Note
|
|
that it relies on kernel support for 100kbaud and on a uart inverted
|
|
in hardware
|
|
*/
|
|
|
|
#include <AP_HAL/AP_HAL.h>
|
|
|
|
#if CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_LINUX_DISCO || \
|
|
CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_LINUX_AERO
|
|
#include "RCInput_SBUS.h"
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/ioctl.h>
|
|
#include <asm/termbits.h>
|
|
|
|
extern const AP_HAL::HAL& hal;
|
|
|
|
using namespace Linux;
|
|
|
|
#define SBUS_FRAME_SIZE 25
|
|
|
|
void RCInput_SBUS::init()
|
|
{
|
|
fd = open(device_path, O_RDWR | O_NONBLOCK | O_CLOEXEC);
|
|
if (fd != -1) {
|
|
printf("Opened SBUS input %s fd=%d\n", device_path, (int)fd);
|
|
fflush(stdout);
|
|
struct termios2 tio {};
|
|
|
|
if (ioctl(fd, TCGETS2, &tio) != 0) {
|
|
close(fd);
|
|
fd = -1;
|
|
return;
|
|
}
|
|
tio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR
|
|
| IGNCR | ICRNL | IXON);
|
|
tio.c_iflag |= (INPCK | IGNPAR);
|
|
tio.c_oflag &= ~OPOST;
|
|
tio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
|
tio.c_cflag &= ~(CSIZE | CRTSCTS | PARODD | CBAUD);
|
|
// use BOTHER to specify speed directly in c_[io]speed member
|
|
tio.c_cflag |= (CS8 | CSTOPB | CLOCAL | PARENB | BOTHER | CREAD);
|
|
tio.c_ispeed = 100000;
|
|
tio.c_ospeed = 100000;
|
|
// see select() comment below
|
|
tio.c_cc[VMIN] = SBUS_FRAME_SIZE;
|
|
tio.c_cc[VTIME] = 0;
|
|
if (ioctl(fd, TCSETS2, &tio) != 0) {
|
|
close(fd);
|
|
fd = -1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RCInput_SBUS::set_device_path(const char *path)
|
|
{
|
|
device_path = path;
|
|
printf("Set SBUS device path %s\n", path);
|
|
}
|
|
|
|
#define SBUS_DEBUG_LOG 0
|
|
#define SBUS_CAUSE_CORRUPTION 0
|
|
|
|
void RCInput_SBUS::_timer_tick(void)
|
|
{
|
|
if (fd == -1) {
|
|
return;
|
|
}
|
|
|
|
// read up to 10 frames at a time
|
|
uint8_t bytes[SBUS_FRAME_SIZE*10];
|
|
int nread;
|
|
|
|
fd_set fds;
|
|
struct timeval tv;
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
|
|
// as VMIN is SBUS_FRAME_SIZE the select won't return unless there is
|
|
// at least SBUS_FRAME_SIZE bytes available
|
|
if (select(fd+1, &fds, nullptr, nullptr, &tv) != 1) {
|
|
return;
|
|
}
|
|
|
|
#if SBUS_DEBUG_LOG
|
|
static int logfd = -1;
|
|
if (logfd == -1) {
|
|
logfd = open("sbus.log", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0644);
|
|
}
|
|
#endif
|
|
|
|
#if SBUS_CAUSE_CORRUPTION
|
|
// deliberately lose bytes from the port
|
|
static unsigned corruption_counter;
|
|
if (corruption_counter++ % 1000 == 0) {
|
|
uint8_t nn = corruption_counter/1000;
|
|
int n2 = ::read(fd, bytes, nn);
|
|
dprintf(logfd, "throw %u\n", (unsigned)n2);
|
|
}
|
|
#endif
|
|
|
|
// cope with there being a large number of pending bytes at
|
|
// the start
|
|
do {
|
|
nread = ::read(fd, bytes, sizeof(bytes));
|
|
} while (nread == sizeof(bytes));
|
|
|
|
if (nread % SBUS_FRAME_SIZE != 0) {
|
|
/*
|
|
SBUS frames are 25 bytes long, and always start with
|
|
0x0f, but there is no other framing information to
|
|
prevent us getting out of sync. All we have are the
|
|
timing guarantees
|
|
|
|
In this case we have read a partial frame, or lost some
|
|
bytes. A 25 bytes frame at 100000 baud takes 2.5ms. By
|
|
delaying 3.5ms here we will get any remaining bytes for
|
|
this frame. We shouldn't get the start of the next frame
|
|
as frames happen at most every 7ms
|
|
|
|
This strategy allows us to resync even if we lose
|
|
bytes. It assumes an interframe gap of more than
|
|
3.5ms. If SBUS is run at very high rate (like 300Hz)
|
|
then this won't work
|
|
*/
|
|
hal.scheduler->delay_microseconds(3500);
|
|
int n2 = ::read(fd, bytes+nread, sizeof(bytes)-nread);
|
|
if (n2 > 0) {
|
|
nread += n2;
|
|
}
|
|
}
|
|
if (nread % SBUS_FRAME_SIZE != 0) {
|
|
// if we don't have a multuple of 25 then throw away the lot
|
|
#if SBUS_DEBUG_LOG
|
|
dprintf(logfd, "discard %u\n", (unsigned)nread);
|
|
#endif
|
|
return;
|
|
}
|
|
if (nread <= 0) {
|
|
return;
|
|
}
|
|
#if SBUS_DEBUG_LOG
|
|
if (logfd != -1) {
|
|
dprintf(logfd, "%06u %u: ", (unsigned)AP_HAL::millis(), (unsigned)nread);
|
|
for (uint8_t i=0; i<nread; i++) {
|
|
dprintf(logfd, "%02x ", (unsigned)bytes[i]);
|
|
}
|
|
dprintf(logfd, "\n");
|
|
}
|
|
#endif
|
|
// only process the last SBUS_FRAME_SIZE bytes. Only the latest
|
|
// frame matters
|
|
add_sbus_input(&bytes[nread-SBUS_FRAME_SIZE], SBUS_FRAME_SIZE);
|
|
}
|
|
#endif // CONFIG_HAL_BOARD_SUBTYPE
|
|
|