Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d17d6faab | |||
| 1b5bbf2755 | |||
| 8aab304e5f | |||
| 76fa76f5b8 | |||
| 0f14fd0155 | |||
| bc208e2a9f |
26
README.md
26
README.md
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Это проект с двумя частями программы:
|
Это проект с двумя частями программы:
|
||||||
* программа `ground` - запускается на наземной станции
|
* программа `ground` - запускается на наземной станции
|
||||||
|
* программа `air` - запускается на воздушной станции (SDRPi)
|
||||||
|
|
||||||
|
|
||||||
# Зависимости для ground
|
# Зависимости для ground
|
||||||
@@ -22,3 +23,28 @@ brew install sdl2
|
|||||||
|
|
||||||
Под шиндовс, как всегда, надо страдать. Рекомендуется установить `sdl2` через `vcpkg`.
|
Под шиндовс, как всегда, надо страдать. Рекомендуется установить `sdl2` через `vcpkg`.
|
||||||
|
|
||||||
|
# Запуск
|
||||||
|
|
||||||
|
## на "воздушной" части:
|
||||||
|
```shell
|
||||||
|
sdrpi-fpv-control-air /dev/ttyPS0
|
||||||
|
```
|
||||||
|
|
||||||
|
> На данный момент эта команда добавлена в автозапуск, никаких дополнительных действий от пользователя не требуется.
|
||||||
|
|
||||||
|
## На "земле"
|
||||||
|
|
||||||
|
Если у вас windows, скачать релиз можно:
|
||||||
|
* с шары (`\\SHARE\share\vlad405\sdripi-fpv-control`) [(ссылка для линуксоидов)](smb://share.local/share/vlad405/sdripi-fpv-control)
|
||||||
|
* со [страницы релиза](https://git.wawaa.ru/VladislavOstapov/sdrpi-fpv-control/releases).
|
||||||
|
|
||||||
|
Если вы ~~нормальный человек~~ разработчик, то проще собрать программу при помощи cmake.
|
||||||
|
|
||||||
|
Запускать программу надо на ноуте, предварительно подключив пульт.
|
||||||
|
```shell
|
||||||
|
sdrpi-fpv-control-ground 192.168.1.5 20
|
||||||
|
```
|
||||||
|
У программы 2 необязательных аргумента. Первый - IP адрес "воздушной" SDRPi. Второй - количество пакетов управления в секунду.
|
||||||
|
По умолчанию IP адрес - localhost, а количество пакетов в секунду - 5.
|
||||||
|
Для мягкого управления рекомендуется ставить от 15 до 50 пакетов в секунду. При показе рекомендуется устанавливать значение 20.
|
||||||
|
|
||||||
|
|||||||
183
air/main.cpp
183
air/main.cpp
@@ -1,4 +1,5 @@
|
|||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
#include <asm/termbits.h>
|
#include <asm/termbits.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
@@ -11,17 +12,26 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вспомогательная функция для получения текущего времени в миллисекундах
|
||||||
|
*/
|
||||||
|
inline int64_t milliseconds() {
|
||||||
|
timeval tv{};
|
||||||
|
gettimeofday(&tv,nullptr);
|
||||||
|
return ((tv.tv_sec * 1000000l) + tv.tv_usec) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int64_t SBUS_SEND_FRAME_INTERVAL = 15;
|
||||||
|
constexpr int64_t SBUS_RXLOSS_INTERVAL = 500;
|
||||||
|
|
||||||
class UDPServer {
|
class UDPServer {
|
||||||
private:
|
public:
|
||||||
int sockfd;
|
int sockfd;
|
||||||
sockaddr_in server_addr, client_addr;
|
sockaddr_in server_addr, client_addr;
|
||||||
socklen_t client_len;
|
socklen_t client_len;
|
||||||
|
|
||||||
public:
|
|
||||||
UDPServer(uint16_t port) : client_len(sizeof(client_addr)) {
|
UDPServer(uint16_t port) : client_len(sizeof(client_addr)) {
|
||||||
// Создание UDP сокета
|
// Создание UDP сокета
|
||||||
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
@@ -84,7 +94,9 @@ struct SbusData {
|
|||||||
bool lost_frame = false;
|
bool lost_frame = false;
|
||||||
bool failsafe = false;
|
bool failsafe = false;
|
||||||
bool ch17 = false, ch18 = false;
|
bool ch17 = false, ch18 = false;
|
||||||
static constexpr size_t NUM_CH = 16;
|
static constexpr size_t NUM_CH = 18;
|
||||||
|
static constexpr int16_t SBUS_CH_MIN = 173 + 10;
|
||||||
|
static constexpr int16_t SBUS_CH_MAX = 1812 - 10;
|
||||||
int16_t ch[NUM_CH];
|
int16_t ch[NUM_CH];
|
||||||
|
|
||||||
/* Message len */
|
/* Message len */
|
||||||
@@ -98,52 +110,68 @@ struct SbusData {
|
|||||||
static constexpr uint8_t CH18_MASK_ = 0x02;
|
static constexpr uint8_t CH18_MASK_ = 0x02;
|
||||||
static constexpr uint8_t LOST_FRAME_MASK_ = 0x04;
|
static constexpr uint8_t LOST_FRAME_MASK_ = 0x04;
|
||||||
static constexpr uint8_t FAILSAFE_MASK_ = 0x08;
|
static constexpr uint8_t FAILSAFE_MASK_ = 0x08;
|
||||||
/* Data */
|
uint8_t binaryBuffer[25];
|
||||||
uint8_t buf_[BUF_LEN_];
|
|
||||||
|
|
||||||
void fillDataBuf() {
|
void fillDataBuf() {
|
||||||
/* Assemble packet */
|
typedef struct sbusChannels_s {
|
||||||
buf_[0] = HEADER_;
|
// 176 bits of data (11 bits per channel * 16 channels) = 22 bytes.
|
||||||
buf_[1] = static_cast<uint8_t>((ch[0] & 0x07FF));
|
unsigned int chan0 : 11;
|
||||||
buf_[2] = static_cast<uint8_t>((ch[0] & 0x07FF) >> 8 |
|
unsigned int chan1 : 11;
|
||||||
(ch[1] & 0x07FF) << 3);
|
unsigned int chan2 : 11;
|
||||||
buf_[3] = static_cast<uint8_t>((ch[1] & 0x07FF) >> 5 |
|
unsigned int chan3 : 11;
|
||||||
(ch[2] & 0x07FF) << 6);
|
unsigned int chan4 : 11;
|
||||||
buf_[4] = static_cast<uint8_t>((ch[2] & 0x07FF) >> 2);
|
unsigned int chan5 : 11;
|
||||||
buf_[5] = static_cast<uint8_t>((ch[2] & 0x07FF) >> 10 |
|
unsigned int chan6 : 11;
|
||||||
(ch[3] & 0x07FF) << 1);
|
unsigned int chan7 : 11;
|
||||||
buf_[6] = static_cast<uint8_t>((ch[3] & 0x07FF) >> 7 |
|
unsigned int chan8 : 11;
|
||||||
(ch[4] & 0x07FF) << 4);
|
unsigned int chan9 : 11;
|
||||||
buf_[7] = static_cast<uint8_t>((ch[4] & 0x07FF) >> 4 |
|
unsigned int chan10 : 11;
|
||||||
(ch[5] & 0x07FF) << 7);
|
unsigned int chan11 : 11;
|
||||||
buf_[8] = static_cast<uint8_t>((ch[5] & 0x07FF) >> 1);
|
unsigned int chan12 : 11;
|
||||||
buf_[9] = static_cast<uint8_t>((ch[5] & 0x07FF) >> 9 |
|
unsigned int chan13 : 11;
|
||||||
(ch[6] & 0x07FF) << 2);
|
unsigned int chan14 : 11;
|
||||||
buf_[10] = static_cast<uint8_t>((ch[6] & 0x07FF) >> 6 |
|
unsigned int chan15 : 11;
|
||||||
(ch[7] & 0x07FF) << 5);
|
} __attribute__((__packed__)) sbusChannels_t;
|
||||||
buf_[11] = static_cast<uint8_t>((ch[7] & 0x07FF) >> 3);
|
static_assert(sizeof(sbusChannels_s) == 22);
|
||||||
buf_[12] = static_cast<uint8_t>((ch[8] & 0x07FF));
|
struct sbusFrame_s {
|
||||||
buf_[13] = static_cast<uint8_t>((ch[8] & 0x07FF) >> 8 |
|
uint8_t syncByte = HEADER_;
|
||||||
(ch[9] & 0x07FF) << 3);
|
sbusChannels_t channels{};
|
||||||
buf_[14] = static_cast<uint8_t>((ch[9] & 0x07FF) >> 5 |
|
uint8_t flags{};
|
||||||
(ch[10] & 0x07FF) << 6);
|
/**
|
||||||
buf_[15] = static_cast<uint8_t>((ch[10] & 0x07FF) >> 2);
|
* The endByte is 0x00 on FrSky and some futaba RX's, on Some SBUS2 RX's the value indicates the telemetry byte that is sent after every 4th sbus frame.
|
||||||
buf_[16] = static_cast<uint8_t>((ch[10] & 0x07FF) >> 10 |
|
*
|
||||||
(ch[11] & 0x07FF) << 1);
|
* See https://github.com/cleanflight/cleanflight/issues/590#issuecomment-101027349
|
||||||
buf_[17] = static_cast<uint8_t>((ch[11] & 0x07FF) >> 7 |
|
* and
|
||||||
(ch[12] & 0x07FF) << 4);
|
* https://github.com/cleanflight/cleanflight/issues/590#issuecomment-101706023
|
||||||
buf_[18] = static_cast<uint8_t>((ch[12] & 0x07FF) >> 4 |
|
*/
|
||||||
(ch[13] & 0x07FF) << 7);
|
uint8_t endByte = FOOTER_;
|
||||||
buf_[19] = static_cast<uint8_t>((ch[13] & 0x07FF) >> 1);
|
} __attribute__ ((__packed__));
|
||||||
buf_[20] = static_cast<uint8_t>((ch[13] & 0x07FF) >> 9 |
|
static_assert(sizeof(sbusFrame_s) == sizeof(binaryBuffer));
|
||||||
(ch[14] & 0x07FF) << 2);
|
|
||||||
buf_[21] = static_cast<uint8_t>((ch[14] & 0x07FF) >> 6 |
|
auto* dest = reinterpret_cast<sbusFrame_s*>(binaryBuffer);
|
||||||
(ch[15] & 0x07FF) << 5);
|
dest->syncByte = HEADER_;
|
||||||
buf_[22] = static_cast<uint8_t>((ch[15] & 0x07FF) >> 3);
|
dest->channels.chan0 = std::min(std::max(ch[0], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
buf_[23] = 0x00 | (ch17 * CH17_MASK_) | (ch18 * CH18_MASK_) |
|
dest->channels.chan1 = std::min(std::max(ch[1], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
(failsafe * FAILSAFE_MASK_) |
|
dest->channels.chan2 = std::min(std::max(ch[2], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
(lost_frame * LOST_FRAME_MASK_);
|
dest->channels.chan3 = std::min(std::max(ch[3], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
buf_[24] = FOOTER_;
|
dest->channels.chan4 = std::min(std::max(ch[4], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan5 = std::min(std::max(ch[5], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan6 = std::min(std::max(ch[6], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan7 = std::min(std::max(ch[7], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan8 = std::min(std::max(ch[8], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan9 = std::min(std::max(ch[9], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan10 = std::min(std::max(ch[10], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan11 = std::min(std::max(ch[11], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan12 = std::min(std::max(ch[12], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan13 = std::min(std::max(ch[13], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan14 = std::min(std::max(ch[14], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
dest->channels.chan15 = std::min(std::max(ch[15], SBUS_CH_MIN), SBUS_CH_MAX);
|
||||||
|
|
||||||
|
dest->flags = 0;
|
||||||
|
if (ch[16] >= (SBUS_CH_MAX + SBUS_CH_MAX) / 2) { dest->flags |= CH17_MASK_; }
|
||||||
|
if (ch[16] >= (SBUS_CH_MAX + SBUS_CH_MAX) / 2) { dest->flags |= CH18_MASK_; }
|
||||||
|
|
||||||
|
dest->endByte = FOOTER_;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -243,32 +271,57 @@ int main(int argc, char* argv[]) {
|
|||||||
std::cout << "Press Ctrl+C to exit" << std::endl;
|
std::cout << "Press Ctrl+C to exit" << std::endl;
|
||||||
|
|
||||||
SbusData sb{};
|
SbusData sb{};
|
||||||
|
int64_t lastSbusWrite = 0;
|
||||||
|
int64_t lastSbusRecv = 0;
|
||||||
|
|
||||||
int packet_count = 0;
|
int packetCount = 0;
|
||||||
|
int totalPacketCount = 0;
|
||||||
|
int64_t lastStatisticsShow = milliseconds();
|
||||||
while (true) {
|
while (true) {
|
||||||
// Прием UDP пакета
|
pollfd udpFd{.fd = udp_server.sockfd, .events = POLLIN, .revents = 0};
|
||||||
std::vector<uint16_t> data = udp_server.receive();
|
poll(&udpFd, 1, SBUS_SEND_FRAME_INTERVAL / 4);
|
||||||
|
|
||||||
if (!data.empty()) {
|
if (udpFd.revents & POLLIN) {
|
||||||
packet_count++;
|
// Прием UDP пакета
|
||||||
|
std::vector<uint16_t> data = udp_server.receive();
|
||||||
|
lastSbusRecv = milliseconds();
|
||||||
|
|
||||||
for (int i = 0; i < data.size() && i < SbusData::NUM_CH; ++i) {
|
if (!data.empty()) {
|
||||||
auto item = static_cast<int16_t>(data[i]);
|
packetCount++;
|
||||||
sb.ch[i] = static_cast<int16_t>((item - 1000.0) * 2);
|
|
||||||
if (sb.ch[i] < 50) {
|
for (int i = 0; i < data.size() && i < SbusData::NUM_CH; ++i) {
|
||||||
sb.ch[i] = 50; // минимальное число
|
auto item = static_cast<double>(data[i]);
|
||||||
} else if (sb.ch[i] > 1900) {
|
item -= 1000.0;
|
||||||
sb.ch[i] = 1900; // максимальное число
|
item = std::min(item, 1000.0);
|
||||||
|
item = std::max(item, 0.0);
|
||||||
|
item *= (SbusData::SBUS_CH_MAX - SbusData::SBUS_CH_MIN) / 1000.0;
|
||||||
|
item += SbusData::SBUS_CH_MIN;
|
||||||
|
|
||||||
|
sb.ch[i] = static_cast<int16_t>(item);
|
||||||
|
if (sb.ch[i] < SbusData::SBUS_CH_MIN) {
|
||||||
|
sb.ch[i] = SbusData::SBUS_CH_MIN; // минимальное число
|
||||||
|
} else if (sb.ch[i] > SbusData::SBUS_CH_MAX) {
|
||||||
|
sb.ch[i] = SbusData::SBUS_CH_MAX; // максимальное число
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
sb.fillDataBuf();
|
||||||
}
|
}
|
||||||
sb.fillDataBuf();
|
}
|
||||||
serial.write(sb.buf_);
|
|
||||||
|
|
||||||
// Выводим статистику каждые 100 пакетов
|
const auto now = milliseconds();
|
||||||
if (packet_count % 100 == 0) {
|
if (now - lastSbusWrite >= SBUS_SEND_FRAME_INTERVAL && now - lastSbusRecv <= SBUS_RXLOSS_INTERVAL) {
|
||||||
std::cout << "Received " << packet_count << " packets total" << std::endl;
|
lastSbusWrite = now;
|
||||||
|
if (!serial.write(sb.binaryBuffer)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (now - lastStatisticsShow >= 1000) {
|
||||||
|
lastStatisticsShow = now;
|
||||||
|
totalPacketCount += packetCount;
|
||||||
|
std::cout << "Received " << totalPacketCount << " packets total, " << packetCount << " in last second" << std::endl;
|
||||||
|
packetCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
|||||||
@@ -64,13 +64,12 @@ bool JoystickReader::readData(std::vector<uint16_t>& data) {
|
|||||||
// Читаем кнопки
|
// Читаем кнопки
|
||||||
int buttons = SDL_JoystickNumButtons(joystick);
|
int buttons = SDL_JoystickNumButtons(joystick);
|
||||||
for (int i = 0; i < buttons && i < data.size() - axes; ++i) {
|
for (int i = 0; i < buttons && i < data.size() - axes; ++i) {
|
||||||
auto buttonState = SDL_JoystickGetButton(joystick, i);
|
data[axes + i] = SDL_JoystickGetButton(joystick, i) ? 2000 : 1000;
|
||||||
data[axes + i] = static_cast<uint16_t>(1000.0 + (buttonState * (1000.0 / 255.0)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& i: data) {
|
for (auto& i: data) {
|
||||||
if (i < 950) i = 950;
|
if (i < 1000) i = 1000;
|
||||||
if (i > 2050) i = 2050;
|
if (i > 2000) i = 2000;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
JoystickReader reader;
|
JoystickReader reader;
|
||||||
const int64_t timeInterval = 1000 / frequency;
|
const int64_t timeInterval = 1000 / frequency;
|
||||||
while (!reader.initialize()) {
|
if (!reader.initialize()) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
UDPSocket udp(sendAddress, 1066);
|
UDPSocket udp(sendAddress, 1066);
|
||||||
|
|||||||
Reference in New Issue
Block a user