blob: 8928e5a78164fa3090fc0949c7d12c9fb60df98a [file] [log] [blame]
#include "bbb/uart_reader.h"
#include <fcntl.h>
#include <linux/serial.h>
#include <termio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <inttypes.h>
#include <algorithm>
#include "aos/common/logging/logging.h"
#include "bbb_cape/src/cape/cows.h"
#include "bbb/crc.h"
#define PACKET_SIZE (DATA_STRUCT_SEND_SIZE - 4)
// This is the code for receiving data from the cape via UART.
// NOTE: In order for this to work, you MUST HAVE
// "capemgr.enable_partno=BB_UART1"
// in your BBB's /media/BEAGLEBONE/uEnv.txt file!
// Implementation for setting custom serial baud rates based on this code:
// <http://jim.sh/ftx/files/linux-custom-baudrate.c>
namespace bbb {
namespace {
// TODO(brians): Determine this in some way that allows easy switching for
// testing with /dev/ttyUSB0 for example.
// TODO(brians): Change back to /dev/ttyO1.
const char *device = "/dev/ttyUSB0";
} // namespace
UartReader::UartReader(int32_t baud_rate)
: baud_rate_(baud_rate),
buf_(new AlignedChar[PACKET_SIZE]),
unstuffed_data_(new AlignedChar[PACKET_SIZE - 4]),
fd_(open(device, O_RDWR | O_NOCTTY)) {
static_assert((PACKET_SIZE % 4) == 0,
"We can't do checksums of lengths that aren't multiples of 4.");
if (fd_ < 0) {
LOG(FATAL, "open(%s, O_RDWR | O_NOCTTY) failed with %d: %s."
" Did you read my note in bbb/uart_reader.cc?\n",
device, errno, strerror(errno));
}
{
termios options;
if (tcgetattr(fd_, &options) != 0) {
LOG(FATAL, "tcgetattr(%d, %p) failed with %d: %s\n",
fd_, &options, errno, strerror(errno));
}
if (cfsetispeed(&options, B38400) != 0) {
LOG(FATAL, "cfsetispeed(%p, B38400) failed with %d: %s\n",
&options, errno, strerror(errno));
}
if (cfsetospeed(&options, B38400) != 0) {
LOG(FATAL, "cfsetospeed(%p, B38400) failed with %d: %s\n",
&options, errno, strerror(errno));
}
cfmakeraw(&options);
options.c_cflag |= CLOCAL; // ignore modem flow control
options.c_cflag |= CREAD; // enable receiver
options.c_cflag &= ~CRTSCTS; // disable flow control
//options.c_cflag |= PARENB; // enable parity; defaults to even
options.c_cflag = (options.c_cflag & ~CSIZE) | CS8; // 8 bits
options.c_cflag &= ~CSTOPB; // 1 stop bit
options.c_iflag = 0;
// Replace any received characters with parity or framing errors with 0s.
options.c_iflag = (options.c_iflag & ~(IGNPAR | PARMRK)) | INPCK;
//options.c_iflag |= IGNCR | PARMRK;
options.c_oflag = 0;
options.c_lflag = 0;
options.c_cc[VMIN] = 20;
options.c_cc[VTIME] = 0;
if (tcsetattr(fd_, TCSANOW, &options) != 0) {
LOG(FATAL, "tcsetattr(%d, TCSANOW, %p) failed with %d: %s\n",
fd_, &options, errno, strerror(errno));
}
}
{
serial_struct serinfo;
if (ioctl(fd_, TIOCGSERIAL, &serinfo) != 0) {
LOG(FATAL, "ioctl(%d, TIOCGSERIAL, %p) failed with %d: %s\n",
fd_, &serinfo, errno, strerror(errno));
}
serinfo.reserved_char[0] = 0;
serinfo.flags &= ~ASYNC_SPD_MASK;
serinfo.flags |= ASYNC_SPD_CUST;
//serinfo.flags |= ASYNC_LOW_LATENCY;
serinfo.custom_divisor = static_cast<int>(
static_cast<double>(serinfo.baud_base) / baud_rate_ + 0.5);
if (serinfo.custom_divisor < 1) serinfo.custom_divisor = 1;
if (ioctl(fd_, TIOCSSERIAL, &serinfo) < 0) {
LOG(FATAL, "ioctl(%d, TIOCSSERIAL, %p) failed with %d: %s\n",
fd_, &serinfo, errno, strerror(errno));
}
if (ioctl(fd_, TIOCGSERIAL, &serinfo) < 0) {
LOG(FATAL, "ioctl(%d, TIOCGSERIAL, %p) failed with %d: %s\n",
fd_, &serinfo, errno, strerror(errno));
}
if ((serinfo.flags & ASYNC_SPD_CUST) == 0) {
LOG(FATAL, "can not set custom baud rate\n");
}
if (serinfo.custom_divisor * baud_rate_ != serinfo.baud_base) {
LOG(WARNING, "actual baudrate is %d / %d = %f\n",
serinfo.baud_base, serinfo.custom_divisor,
static_cast<double>(serinfo.baud_base) / serinfo.custom_divisor);
}
}
if (fcntl(fd_, F_SETFL, 0) != 0) {
LOG(FATAL, "fcntl(%d, F_SETFL, 0) failed with %d: %s\n",
fd_, errno, strerror(errno));
}
}
UartReader::~UartReader() {
delete buf_;
delete unstuffed_data_;
if (fd_ > 0) close(fd_);
}
// TODO(brians): Figure out why this (sometimes?) gets confused right after
// flashing the cape.
bool UartReader::FindPacket() {
// How many 0 bytes we've found at the front so far.
int zeros_found = 0;
while (true) {
size_t already_read = ::std::max(0, packet_bytes_);
ssize_t new_bytes =
read(fd_, buf_ + already_read, PACKET_SIZE - already_read);
if (new_bytes < 0) {
if (errno == EINTR) continue;
LOG(ERROR, "read(%d, %p, %zd) failed with %d: %s\n",
fd_, buf_ + already_read, PACKET_SIZE - already_read,
errno, strerror(errno));
return false;
}
if (packet_bytes_ == -1) {
for (size_t to_check = already_read; to_check < already_read + new_bytes;
++to_check) {
if (buf_[to_check] == 0) {
++zeros_found;
if (zeros_found == 4) {
packet_bytes_ = 0;
zeros_found = 0;
new_bytes -= to_check + 1;
memmove(buf_, buf_ + to_check + 1, new_bytes);
to_check = 0;
}
} else {
zeros_found = 0;
}
}
}
if (packet_bytes_ != -1) { // if we decided that these are good bytes
packet_bytes_ += new_bytes;
if (packet_bytes_ == PACKET_SIZE) return true;
}
}
}
bool UartReader::ProcessPacket() {
uint32_t unstuffed =
cows_unstuff(reinterpret_cast<uint32_t *>(buf_), PACKET_SIZE,
reinterpret_cast<uint32_t *>(unstuffed_data_));
if (unstuffed == 0) {
LOG(WARNING, "invalid packet\n");
return false;
} else if (unstuffed != (PACKET_SIZE - 4) / 4) {
LOG(WARNING, "packet is %" PRIu32 " words instead of %" PRIu32 "\n",
unstuffed, (PACKET_SIZE - 4) / 4);
return false;
}
// Make sure the checksum checks out.
uint32_t sent_checksum;
memcpy(&sent_checksum, unstuffed_data_ + PACKET_SIZE - 8, 4);
uint32_t calculated_checksum = cape::CalculateChecksum(
reinterpret_cast<uint8_t *>(unstuffed_data_), PACKET_SIZE - 8);
if (sent_checksum != calculated_checksum) {
LOG(WARNING, "sent checksum: %" PRIx32 " vs calculated: %" PRIx32"\n",
sent_checksum, calculated_checksum);
return false;
}
return true;
}
bool UartReader::GetPacket(DataStruct *packet) {
static_assert(sizeof(*packet) <= PACKET_SIZE - 8,
"output data type is too big");
if (!FindPacket()) return false;
if (!ProcessPacket()) {
packet_bytes_ = -1;
int zeros = 0;
for (int i = 0; i < PACKET_SIZE; ++i) {
if (buf_[i] == 0) {
++zeros;
if (zeros == 4) {
LOG(INFO, "found another packet start at %d\n", i);
packet_bytes_ = PACKET_SIZE - (i + 1);
memmove(buf_, buf_ + i + 1, packet_bytes_);
return false;
}
} else {
zeros = 0;
}
}
return false;
} else {
packet_bytes_ = -1;
}
memcpy(packet, unstuffed_data_, sizeof(*packet));
return true;
}
} // namespace bbb