search icon search icon ВЕРСИЯ ДЛЯ СЛАБОВИДЯЩИХ

Инженерный тур. 3 этап

Общая информация

Современная космическая отрасль активно развивает технологии малых спутников, которые стали ключевым инструментом для оперативного получения данных о состоянии Земли. Эти аппараты используются в широком спектре задач: от мониторинга климатических изменений и состояния экосистем до быстрого реагирования на чрезвычайные ситуации, таких как природные катастрофы.

В этом году участникам Олимпиады предлагается стать частью команды разработчиков нового поколения малых спутников для исследования Земли и создать свой аппарат — перспективную платформу для получения оперативной информации о климате, экосистемах и урбанистических процессах. Его основная миссия включает три ключевых этапа:

  1. ориентация в надир,
  2. съемка данных высокого качества,
  3. передача их на наземную станцию.
Легенда задачи

Участники Олимпиады — это команда инженеров, работающих над созданием малого спутника для дистанционного зондирования Земли. Аппарат должен быть компактным, энергоэффективным и способным выполнять задачи в условиях ограниченных ресурсов. Он разрабатывается на базе конструктора Introsat, поэтому для реализации проекта задействуются современные технологии и компоненты.

Для обеспечения точной ориентации спутника в надир используются инфракрасные датчики, которые позволяют определять положение Земли относительно аппарата. Это гарантирует стабильное наведение и минимизирует искажения при съемке. Главный инструмент для сбора данных — камера высокого разрешения, способная фиксировать детализированные изображения поверхности Земли.

Основные задачи, которые решают разработчики:

  • создание системы ориентации,
  • настройка камеры для съемки данных высокого качества,
  • проектирование системы передачи данных на наземную станцию,
  • расчет оптимальной орбиты для выполнения миссии.
Требования к команде и компетенциям участников

Команда заключительного этапа должна состоять из трех или четырех человек. Если в заключительный этап по итогам второго отборочного этапа проходит команда менее, чем из трех участников, и если нет со стороны команды возражений, то она будет расформирована, а ее члены должны вступить в другие команды и/или образовать новые, численностью не менее, чем из трех человек.

Роли в команде могут быть любыми, но для выполнения заданий заключительного этапа выделяют четыре сбалансированных направления (рекомендуется, чтобы каждым из них занимался выделенный участник):

  • Радиотехника: сформированное представление об основах радиосвязи, умение работать с радиомодулями и организовывать каналы связи с использованием различных технических решений. Рекомендовано знакомство с ПО SDR#, GNU Radio.
  • Программирование (полезной нагрузки): разработка и создание ПО для обработки данных матричных датчиков и взаимодействие с другими системами КА.
  • Программирование микроконтроллеров/разработка спутниковых систем: написание управляющих программ ко всем прототипам; решение задач потребует знакомства с STM32CubeIDE, умения работать с кодом на языке С и базовых понятий об алгоритмах управления.
  • Физика (баллистика): решение задач по проектированию миссий на орбите, для чего в первую очередь необходимо знать основы небесной механики. Рекомендовано знакомство с синтаксисом языка JavaScript для написания алгоритмов в симуляторе.
Оборудование и программное обеспечение
Стенд

Рис. 4.1.

В качестве небольшого настольного стенда командам предлагается использовать следующие компоненты (см. рис. 4.1):

  • стенд с установленным на подвесе спутником;
  • макет, имитирующий земной шар.

На испытаниях заключительного этапа при установке на подвес спутнику будет придаваться начальная закрутка. Он должен:

  1. стабилизироваться;
  2. сориентироваться на Землю;
  3. сделать снимок;
  4. передать снимок на наземную станцию.
Первый день
Программист (платформа спутника)

Первый день посвящен сборке оборудования и отработке материала удаленного практикума, а также написанию алгоритма стабилизации.

В работе можно использовать проект STM32CubeIDE, который получился у команды в результате отработки удаленного практикума, или запросить у организаторов сборку стартового проекта.

День делится на два этапа (см. таблицу 1.1).

Баллы, которые можно получить за сдачу задачи Первый день: 9:00–13:00 Первый день: 14:00–18:00 Второй день: 9:00–13:00
Первая половина дня: 9:00–13:00 Настроить беспроводную передачу информации по UART и беспроводной способ прошивки микроконтроллера 2 1 0
Задать управление двигателем, продемонстрировать изменение направления и скорости вращения 2 1 0
Считать данные с датчика позиционирования (гироскоп) так, как это делалось на удаленной работе 2 1 0
Вторая половина дня: 14:00–18:00 Реализовать алгоритм стабилизации спутника 4 4 2
Итого баллов 10

Для выполнения задач первой половины дня советуем использовать методические указания:

Алгоритм стабилизации работает с рассогласованием, т. е. с ошибкой между желаемым значением параметра и текущим. Обычно рассогласование обозначают буквой \(e\):

\[e = x_\text{текущее} - x_\text{идеальное},\] где \(x\) — угловая скорость; в данном случае: \(x_\text{текущее}\) — угловая скорость спутника; \(x_\text{идеальное}\) — необходимая угловая скорость спутника (0).

Для того чтобы управлять изменением скорости вращения спутника, необходимо изменить скорость маховика.

Если воспользоваться законом сохранения момента импульса, то получим, что изменение скорости маховика пропорционально изменению скорости спутника, в данном случае для реализации алгоритма нужно найти коэффициент пропорциональности: \[w_{wheel} += Kp\cdot e.\]

Cборка

Прежде чем приступить к работе с микроконтроллером, необходимо осуществить сборку основных плат конструктора IntroSat.

Сборка плат
1

Установите плату Blue Pill на материнскую плату.

Важно!

Разъем microusb на плате BluePill должен находиться над надписью USB SIDE.

2 Установите модуль Bluetooth в соответствующий разъем.
3 Закрепите 4 стойки PCHSN5 длиной по 5 мм на материнской плате при помощи винтов М2.5х4 с полукруглой головкой.
4

Установите аккумулятор в модуль питания, при необходимости закрепите его пластиковой стяжкой через ушки в плате.

Модуль питания установите на материнскую плату так, чтобы шестиконтактные разъемы сопрягались друг с другом. Закрепите гайками модуль питания.

После этого можно проверить, что все подключено верно, сдвинув выключатель (белый ползунок) на модуле питания. Должны загореться светодиоды на плате Blue Pill и bluetooth-модуле.

Обмен данными по UART и беспроводная прошивка

К материнской плате подключается ведомый bluetooth-модуль, а ведущий соединяется с USB-UART конвертером по схеме, приведенной в таблице 1.2.

Bluetooth-модуль Usb-uart-конвертер

RX (RXD) >---

TX (TXD) >---

GND >---

5V >---

---> TX (TXD)

---> RX (RXD)

---> GND

---> VCC

Необходимо запрограммировать микроконтроллер на передачу текстового сообщения через UART и считать его из COM-порта компьютера, к которому подключен USB-UART-конвертер с ведущим bluetooth-модулем.

После этого нужно добавить код для беспроводной прошивки микроконтроллера через UART. Код загружается через ST-LINK, после чего появится возможность загружать последующие программы по беспроводному соединению. Убедитесь в работоспособности такого метода загрузки.

В таблице 1.3 условно показан способ коммутации программатора ST-Link V2 и платы с микроконтроллером Blue Pill.

ST-Link V2* Blue Pill

(2) SWCLK >---

(4) SWDIO >---

(5) или (6) GND >---

(7) или (8) 3.3V >---

---> SCK

---> DIO

---> GND

---> 3.3V

*Примечание: распиновка может отличаться от приведенной, однако важно соединить правильно соответствующие надписи.

После подключения программатора к микроконтроллеру и затем к usb-порту ПК станет возможна загрузка программы в плату. Для загрузки программы в плату нажмите на значок в верхней панели меню.

После этого, возможно, ST-Link попросит обновления. Для этого во вкладке меню Help есть соответствующая опция. Если ST-Link не просит обновления, просто пропустите этот шаг.

Рис. 4.2.

Выберите ST-LINK Upgrade. Откроется новое окно. В нем выберите среди устройств ST-LINK/V2, а затем — Open in update mode. После этого нажмите Upgrade.

Рис. 4.3.

Иногда требуется переподключить программатор, чтобы утилита по его обновлению идентифицировала его. Для загрузки программы в плату нажмите на значок в верхней панели меню.

Примечание. Помимо режима загрузки существует также режим отладки. Он бывает крайне полезен, если где-то в коде закралась ошибка, но ее не удается быстро найти. Для перехода в режим отладки (debug) можно нажать на значок жучка в верхней панели меню. О том, как пользоваться режимом отладки можно посмотреть, например, здесь: https://youtu.be/BVC7KaUNCS8. На очном финале весьма полезно будет уметь пользоваться данным режимом.

Работа с датчиком позиционирования

Закрепите датчик позиционирования на материнской плате конструктора IntroSat.

1 Закрепите две стойки PCHSN11 (длиной 11 мм) на материнской плате при помощи винтов М2.5х4 с полукруглой головкой
2 Установите датчик позиционирования в соответствующий разъем и зафиксируйте его гайками

Работа с датчиком позиционирования происходит по интерфейсу I2C. Необходимо считать значения кватернионов с гироскопа и магнитометра, как рассказывалось в видео по удаленной работе, и вывести их в COM-порт компьютера по UART.

Работа с двигателем

Подготовьте к работе плату маховика (см. таблицу 1.4).

1 Установите маховик на адаптер на оси двигателя с помощью пары винтов М2.5х6 с выпуклой головкой
2 Прикрутите плату маховика к нижней грани куба винтами с потайной головкой М2.5х12 и зафиксируйте гайками

Подключите модуль маховика к материнской плате с помощью шлейфа I2C и провода питания. Для этого можно использовать любые из доступных разъемов MicroMatch на материнской плате и модуле маховика. Питание на двигатель маховика подается от 5-вольтового вывода материнской платы.

Финальная сборка

Перед финальной сборкой рекомендуется проверить правильность всего подключения, чтобы избежать необходимости пересборки. При этом удобным будет оставить ST-Link подключенным к Blue Pill, оставив его снаружи: вероятно, понадобится перепрошивать или отлаживать микроконтроллер через него.

Сборка корпуса
1 Возьмите два рельса и установите в выемки перекладины с пазом.
2

В получившиеся углубления на стыке рельса и перекладины установите перекладины с торцевым отверстием так, чтобы поперечные отверстия в этих перекладинах были ориентированы перпендикулярно рельсу (они используются для установки боковых панелей).

Закрепите угловое соединение винтами М2.5х12 с потайной головкой.

3 Установите две оставшиеся перекладины и два оставшихся рельса аналогичным образом и окончательно соберите конструкцию при помощи винтов М2.5х12.
4 Установите собранную материнскую плату на верхнюю панель с помощью межплатных стоек PCHSN-11 (для этого нужно будет соединить стойки между собой по 3 шт.) и гаек.

Сборка и подключение
5 Закрепите верхнюю панель на раме с помощью четырех винтов М2.5х6 с потайной головкой.
6 Закрепите плату на нижней панели с помощью винтов М2.5х12 с потайной головкой, используя дополнительные гайки в качестве прокладок между панелью и платой.
7 Прикрепите нижнюю панель к раме с помощью винтов М2.5х6 с потайной головкой.
8

Подключите модуль маховика к материнской плате с помощью шлейфа I2C и провода питания. Для этого можно использовать любые из доступных разъемов MicroMatch на материнской плате и модуле маховика. Питание на двигатель маховика подается от 5-вольтового вывода материнской платы.

Боковые грани можно установить по желанию.

Конечная сборка
9 Подвесьте спутник на подвес.

 l 

Решение

Ниже представлено решение первой половины дня.

C++
/* USER CODE BEGIN Includes */
// Подключение необходимых библиотек
#include "string.h"
#include "stdio.h"
#include "IS_Bluetooth.h"
#include <GyroscopeV2.h>
#include <MagnetometerV2.h>
#include <MotorFlyWheel.h>
#include <Quaternion/Quaternion.h>
/* USER CODE BEGIN PM */
// Использование пространства имён интросата
using namespace IntroSatLib;
/* USER CODE BEGIN 0 */
// Необходимо создать устройства
GyroscopeV2 mpu(&hi2c1, 0x6B);
MotorFlyWheel flyWheel(&hi2c1, 0x38);
  /* USER CODE BEGIN 2 */
  // Строка для вывода информации при отладке
  char result[100] = {0};
  char in = 0;
  // Инициализация гироскопа и установка нулевой скорости маховика
  mpu.Init();
  flyWheel.NeedSpeed(0);
    /* USER CODE BEGIN 3 */
    HAL_UART_Receive(&huart1, &in, 1, 1000);
    if(in == 'b'){ in= 0; enter_bootloader(); }
    float x = mpu.X();
    float y = mpu.Y();
    float z = mpu.Z();
    sprintf(result, "Angle speed\t\tx:%f\ty:%f\tz:%f\r\n", x, y, z);
    HAL_UART_Transmit(&huart1, (uint8_t*)result, strlen(result), 1000);
    HAL_Delay(250);
    motor.NeedSpeed(2000);
    HAL_Delay(1000);
    motor.NeedSpeed(-2000);
    HAL_Delay(1000);
   }
  /* USER CODE END 3 */
Ниже представлено решение второй половины дня.
C++
float last_err = 0;
float Integrator = 0;
float Kp = 400;
float Ki = 0.3;
float Kd = 0;
/* USER CODE END 0 */
/* USER CODE BEGIN 3 */
/* предыдущий код, тут только изменения */
/* sprintf(result, "Angle speed\t\tx:%f\ty:%f\tz:%f\r\n", x, y, z);
   HAL_UART_Transmit(&huart1, (uint8_t*)result, strlen(result), 1000);
   HAL_Delay(250);
   motor.NeedSpeed(2000);
   HAL_Delay(1000);
   motor.NeedSpeed(-2000);
   HAL_Delay(1000); */

   float needSpeed = 0;
   float err = z - needSpeed;
   last_err = err;  
   Integrator = Integrator + err * 0.1;
   if (Integrator > 4.0f) { Integrator = 4.0f; }
   if (Integrator < -4.0f) { Integrator = -4.0f; }
   float P = Kp * err;
   float D = Kd * (err - last_err) / T;
   float I = Integrator * Ki;
   float PID_rez = P + I + D;
   float Raw_Motor_Speed = motor.CurrentSpeed();
   float Motor_Speed = (Raw_Motor_Speed + PID_rez);
   
   if (Motor_Speed > 3000) { Motor_Speed = 3000; }
   if (Motor_Speed < -3000) { Motor_Speed = -3000; }
   motor.NeedSpeed(Motor_Speed);
   HAL_Delay(1000);
Программист (полезная нагрузка)

Этот день направлен на подготовку сенсорики, необходимой для ориентации аппарата. Участникам нужно научиться получать информацию с матричного ИК-датчика AMG8833 и реализовать протокол для формирования управляющих сигналов на маховик. Задачи программиста ПН и программиста платформы спутника плотно связаны.

Начало работы

Перед началом работы необходимо спаять схему для отказоустойчивой работы датчика (см. рис. 4.4).

Рис. 4.4. Схема для отказоустойчивой работы датчика: SCLx и SDAx — контакты SCL и SDA I2C под номером x, GPIOx_Y — любой свободный контакт, настроенный на выход

Будьте внимательны: AMG8833 подключается к МК, отвечающему за ориентацию (тот, что установлен на материнской плате). Кроме того, на плате расширения устанавливается еще один МК, который отвечает за работу с камерой, поэтому для него нужно подготовить посадочное место.

Чтение данных

Для чтения данных можно воспользоваться документацией на датчик из открытых источников или использовать способ, реализованный в библиотеке IntrosatLib.

Поддерживаемые инструкции см. ниже.

IRCamera amg(&hi2c1); // создание объекта для работы с
amg.useForceReset(GPIOx, GPIO_PIN_Y); // настройка контакта для сброса
amg.useMirrored(); // использовать если модуль расположен так:

Рис. 4.5.

amg.Init() // инициализация модуля
uint8_t res = amg.Read(); // возвращает 0, если данные правильно прочитались.
float value = amg.getPixel(x, y); // получение градусов по цельсию, x - 0 самая левая часть снимка, x - 7 самая правая часть снимка, y - 0 самая верхняя часть снимка, y - 7 самая нижняя часть снимка.
int16_t rawValue = amg.getPixelRaw(x, y); //получение сырых данных.

Пример кода

C++
amg.useForceReset(GPIOX, GPIO_PIN_Y); 
amg.useMirrored(); 
amg.Init(); 
/*USER CODE END 2*/

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    if (amg.Read()) { continue; } 
    for (uint8_t y = 0; y < 8; y++) 
    {
        for (uint8_t x = 0; x < 8; x++) 
        {
            char data[10]; 
            uint32_t len = sprintf(data, "%0.2f ", amg.getPixel(x, y)); 
            HAL_UART_Transmit(&huart2, (uint8_t*)data, len, 100);
        }
        char next = '\n';
         HAL_UART_Transmit(&huart2, (uint8_t*)&next, 1, 100); 
        }
    }
}
/* USER CODE END WHILE */

На выходе необходимо получать массив температур \(8 \times 8\). Для проверки полученных данных можно реализовать скрипт на языке Рython, визуализирующий снимок.

Рис. 4.6.

Обработка данных

Для наведения с использованием показаний ИК-датчика необходимо создать протокол, сообщающий о том, в каком месте кадра находится Земля (теплый шар, имитирующий ее), чтобы затем отдавать корректировки в программу управления маховиками. С этой целью рекомендуется реализовать следующую логику, а именно: функция оценки положения должна обрабатывать полученный снимок и в зависимости от его содержания возвращать:

  • 0 — если корректировки не требуются;
  • положительное число (цифру) — если для корректировки необходимо вращать спутник по часовой стрелке;
  • отрицательное число (цифру) — если для корректировки необходимо вращать спутник против часовой стрелки;
  • большую по модулю константу (например, 127) — если Земли нет в кадре.

Более конкретно протокол общения лучше обсудить с программистом платформы спутника, так как в будущем он будет работать с этой функцией.

Решение

C++
//часть кода цикла while(1)
    using namespace IntroSatLib;
    IRCamera amg(&hi2c2);
    amg.Init();
    amg.useForceReset(GPIOA, GPIO_PIN_11);
    float IRData[8][8];
    int command = 0;
    while (1) {
        if (amg.Read() == 0) { continue }
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                IRData[i][j] = amg.getPixel(j, i);
            }
        }
        command = findCenter();
    }
    // конец части кода цикла while(1)
int findCenter() {
    int edgeCount = 0;
    int leftEdgePosition = 0;
    int rightEdgePosition = 0;
    for (int j = 0; j < 6; j++) {
        if (IRData[5][j + 1] - IRData[5][j] >= 5){
            edgeCount++;
            rightEdgePosition = j;
        } else if (IRData[5][j + 1] - IRData[5][j] >= -5){
            edgeCount++;
            leftEdgePosition = j + 1;
        }
    }
    if (edgeCount == 0) {
        return -127;
    } else if (edgeCount == 1 && leftEdgePosition != 0){
        return 1;
    } else if (edgeCount == 1 && rightEdgePosition != 0){
        return -1;
    } else if (edgeCount == 2){
        if ((leftEdgePosition) == (7 - rightEdgePosition)){
            return 0;
        } else if ((leftEdgePosition) > (7 - rightEdgePosition)){
            return 1;
        } else if ((leftEdgePosition) < (7 - rightEdgePosition)){
            return -1;
        }
    }
}
Инженер связи

В течение первого дня необходимо научиться передавать данные по радиоканалу. Для этого следует:

  1. подключить передатчик CC1101 к STM32;
  2. научиться передавать через него данные;
  3. собрать минимальный граф по обработке радиосигнала для получения данных на Земле.
  1. Подключение программатора

    Код прошивается при помощи программатора ST-Link V2.

    В таблице 1.5 условно показан способ коммутации программатора ST-Link V2 и платы с микроконтроллером Blue Pill.

    ST-Link V2* Blue Pill
    (2) SWCLK >--- ---> SCK
    (4) SWDIO >--- ---> DIO
    (5) или (6) GND >--- ---> GND
    (7) или (8) 3.3V >--- ---> 3.3V

    *Примечание: распиновка может отличаться от приведенной, однако важно соединить правильно соответствующие надписи.

  2. Подключение передатчика CC1101

    Рис. 4.7.

    Подключение передатчика осуществляется по следующей схеме (см. рис. 4.7, таблицу 1.6).

    CC1101 BluePill
    VCC 3.3
    GND G
    SI PB15
    SO PB14
    CSN PB12
    SCK PB13
    GD0 PA8
  3. Подготовка прошивки

    Рекомендуем использовать единый начальный проект, познакомиться с которым можно по ссылке: https://disk.360.yandex.ru/d/qDrmWDJBvF7ZaA/SS2025Final-master.zip.

    1. Создание объекта радиопередатчика

      В стартовом объекте уже есть строки, связанные с созданием объекта передатчика, теперь их необходимо раскомментировать.

      Рис. 4.8.

    2. Инициализация передатчика

      Далее следует инициализировать созданный объект передатчика, задав ему настройки. Это можно сделать в секции User Code 2, которая находится перед бесконечным циклом в функции main. Ниже приведен пример конфигурации передатчика.

      C++
      trans.Init();                // must be set to initialize the cc1101!
      trans.setCCMode(1);          // set config for internal transmission mode.
      trans.setModulation(0);      // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
      trans.setMHZ(<частота в МГц>);        // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
      trans.setDeviation(47.60);   // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
      trans.setChannel(0);         // Set the Channelnumber from 0 to 255. Default is cahnnel 0.
      trans.setChsp(199.95);       // The channel spacing is multiplied by the channel number CHAN and added to the base frequency in kHz. Value from 25.39 to 405.45. Default is 199.95 kHz.
      trans.setRxBW(812.50);       // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
      trans.setDRate(0.1);         // Set the Data Rate in kBaud. Value from 0.02 to 1621.83. Default is 99.97 kBaud!
      trans.setPA(10);             // Set TxPower. The following settings are possible depending on the frequency band.  (-30  -20  -15  -10  -6    0    5    7    10   11   12) Default is max!
      trans.setSyncMode(2);        // Combined sync-word qualifier mode. 0 = No preamble/sync. 1 = 16 sync word bits detected. 2 = 16/16 sync word bits detected. 3 = 30/32 sync word bits detected. 4 = No preamble/sync, carrier-sense above threshold. 5 = 15/16 + carrier-sense above threshold. 6 = 16/16 + carrier-sense above threshold. 7 = 30/32 + carrier-sense above threshold.
      trans.setSyncWord(211, 145); // Set sync word. Must be the same for the transmitter and receiver. (Syncword high, Syncword low)
      trans.setAdrChk(0);          // Controls address check configuration of received packages. 0 = No address check. 1 = Address check, no broadcast. 2 = Address check and 0 (0x00) broadcast. 3 = Address check and 0 (0x00) and 255 (0xFF) broadcast.
      trans.setAddr(0);            // Address used for packet filtration. Optional broadcast addresses are 0 (0x00) and 255 (0xFF).
      trans.setWhiteData(0);       // Turn data whitening on / off. 0 = Whitening off. 1 = Whitening on.
      trans.setPktFormat(0);       // Format of RX and TX data. 0 = Normal mode, use FIFOs for RX and TX. 1 = Synchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins. 2 = Random TX mode; sends random data using PN9 generator. Used for test. Works as normal mode, setting 0 (00), in RX. 3 = Asynchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins.
      trans.setLengthConfig(1);    // 0 = Fixed packet length mode. 1 = Variable packet length mode. 2 = Infinite packet length mode. 3 = Reserved
      trans.setPacketLength(0);    // Indicates the packet length when fixed packet length mode is enabled. If variable packet length mode is used, this value indicates the maximum packet length allowed.
      trans.setCrc(0);             // 1 = CRC calculation in TX and CRC check in RX enabled. 0 = CRC disabled for TX and RX.
      trans.setCRC_AF(0);          // Enable automatic flush of RX FIFO when CRC is not OK. This requires that only one packet is in the RXIFIFO and that packet length is limited to the RX FIFO size.
      trans.setDcFilterOff(0);     // Disable digital DC blocking filter before demodulator. Only for data rates <= 250 kBaud The recommended IF frequency changes when the DC blocking is disabled. 1 = Disable (current optimized). 0 = Enable (better sensitivity).
      trans.setManchester(0);      // Enables Manchester encoding/decoding. 0 = Disable. 1 = Enable.
      trans.setFEC(0);             // Enable Forward Error Correction (FEC) with interleaving for packet payload (Only supported for fixed packet length mode. 0 = Disable. 1 = Enable.
      trans.setPRE(0);             // Sets the minimum number of preamble bytes to be transmitted. Values: 0 : 2, 1 : 3, 2 : 4, 3 : 6, 4 : 8, 5 : 12, 6 : 16, 7 : 24
      trans.setPQT(0);             // Preamble quality estimator threshold. The preamble quality estimator increases an internal counter by one each time a bit is received that is different from the previous bit, and decreases the counter by 8 each time a bit is received that is the same as the last bit. A threshold of 4xPQT for this counter is used to gate sync word detection. When PQT=0 a sync word is always accepted.
      trans.setAppendStatus(0);    // When enabled, two status bytes will be appended to the payload of the packet. The status bytes contain RSSI and LQI values, as well as CRC OK.

      Обратите внимание на ключевые настройки, приведенные в таблице 1.7.

      Настройка Описание
      setMHz Установка центральной частоты (в МГЦ)
      setDeviation Установка девиации сигнала (в кГц)
      setDRate Установка скорости передачи (в Кбод/с)
      setModulation Выбор модуляции

      Для первого тестирования рекомендуется установить минимальную скорость передачи — 100 бод/с, чтобы было проще наблюдать сообщение на радиоприемнике.

    3. Передача данных

      После инициализации передатчика доступна возможность отправлять через него данные с помощью метода .SendData. Для того чтобы код отправлял сообщение постоянно, нужно поместить вызов этого метода в раздел USER CODE WHILE, который находится внутри бесконечного цикла.

      Например, сообщения можно отправлять так (см. ниже).

      C
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
          trans.SendData("Hello world!");
          /* USER CODE END WHILE */
          /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */

      У метода .SendData есть два вида реализации, которые отличаются принимаемыми аргументами (см. таблицу 1.8).

      Сигнатура Описание
      .SendData(const char* string) Реализация, которая подходит только для передачи текста.
      .SendData(uint8_t* txBuffer, uint8_t size) Универсальная реализация, подходящая под любой вид данных, включая текст. На нее подается указатель на начало данных и их длина.
  4. Процесс прошивки

    После подключения программатора к микроконтроллеру, а затем — к USB-порту ПК станет возможна загрузка программы в плату.

    Для загрузки программы в плату нажмите на значок в верхней панели меню.

    Примечание. Помимо режима загрузки существует также режим отладки, который бывает крайне полезен, если где-то в коде закралась ошибка, но ее не удается найти быстро. Для перехода в режим отладки (debug) можно нажать на значок жучка в верхней панели меню. О том, как пользоваться режимом отладки можно посмотреть, например, здесь: https://youtu.be/BVC7KaUNCS8.

  5. Проверка приемника

    Для того чтобы убедиться, что приемник на базе RTL-SDR принимает сигнал, можно воспользоваться gqrx. В ПО должен отчетливо быть виден пик полезного сигнала.

  6. Проверка работы GNU Radio

    Убедившись, что в gqrx есть сигнал, постройте простейший граф в GNU Radio, в котором используйте лишь источник сигнала (блок osmocom source) и его вывод (блок QT GUI Sink). Не забудьте установить корректную частоту дискретизации (sample rate), например, 1000000 сэмплов/с.

    Ссылка на вебинар: https://orbita.education/ru/materials/298/302/4867.

Предупреждение

Во избежание проблем пересечения каналов разных команд, за каждой командой закрепляется ее центральная частота. Для получения своей частоты обратитесь к организаторам.

Внимание: за преднамеренную работу на других частотах, с целью помешать другим командам, будут начисляться штрафы по задачам инженера связи.

Решение

В соответствии с заданием, граф состоит из двух блоков. Все параметры остаются по умолчанию за исключением частоты, которая будет отличаться у разных команд.

Рис. 4.9.

Код для передатчика см. ниже.

C++
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
IntroSatLib::intefaces::SPI spi(&hspi2);
IntroSatLib::intefaces::GPIO sck(CC1101_SCK_GPIO_Port, CC1101_SCK_Pin);
IntroSatLib::intefaces::GPIO mosi(CC1101_MOSI_GPIO_Port, CC1101_MOSI_Pin);
IntroSatLib::intefaces::GPIO miso(CC1101_MISO_GPIO_Port, CC1101_MISO_Pin);
IntroSatLib::intefaces::GPIO reset(CC1101_CS_GPIO_Port, CC1101_CS_Pin);
IntroSatLib::intefaces::GPIO gd0(CC1101_GD0_GPIO_Port, CC1101_GD0_Pin);
IntroSatLib::CC1101WithGD0 trans(spi, sck, mosi, miso, reset, gd0);
/* USER CODE END 0 */
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
//…..
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_I2C1_Init();
  MX_I2C2_Init();
  MX_SPI1_Init();
  MX_SPI2_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  MX_FATFS_Init();
  /* USER CODE BEGIN 2 */
  trans.Init();                // must be set to initialize the cc1101!
  trans.setCCMode(1);          // set config for internal transmission mode.
  trans.setModulation(0);
  trans.setMHZ(444);
  trans.setDeviation(47.60); 
  trans.setChannel(0); 
  trans.setChsp(199.95);
  trans.setRxBW(812.50); 
  trans.setDRate(0.1);
  trans.setPA(10); 
  trans.setSyncMode(2);
  trans.setSyncWord(211, 145);
  trans.setAdrChk(0);
  trans.setAddr(0);
  trans.setWhiteData(0);
  trans.setPktFormat(0);
  trans.setLengthConfig(1); 
  trans.setPacketLength(0);
  trans.setCrc(0);
  trans.setCRC_AF(0);
  trans.setDcFilterOff(0);
  trans.setManchester(0);
  trans.setFEC(0);
  trans.setPRE(0); 
  trans.setPQT(0);
  trans.setAppendStatus(0);    
    /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
      trans.SendData("Hello world");
  }
  /* USER CODE END 3 */
}
Баллистик

Аппарат ДЗЗ был развернут на орбите, однако по расчетному времени пролетов над ЦУП не выходит на связь. Через некоторое время удалось поймать сигнал от аппарата, однако не в заданное время и не в заданном месте. Это может свидетельствовать о том, что аппарат находится не на той орбите. Необходимо уточнить параметры текущей орбиты аппарата.

Известно, что:

  • проход над Канберра (–35,2931, 149,1269) был в момент времени с 03:41 по 03:47;
  • проход над Синьюй (27,8043, 114,9333) был в момент времени с 16:24 по 16:29;
  • проходы над Эль-Минья (28,1194, 30,7444) были в моменты времени с 08:26 по 08:32 и с 23:16 по 23:21.

Все проходы даны за 01 апреля 2025 года.

Параметры планируемой орбиты миссии:

  • Большая полуось: 7195 км.
  • Наклонение: 60°.
  • Эксцентриситет: 0,003.
  • Аргумент перицентра: 0.
  • Долгота восходящего узла: 185°.
  • Истинная аномалия: 0°.

Время вывода аппарата на орбиту: 01 апреля 2025 года 00:00 по UTC. Время моделирования — 24 ч.

Известно, что наклонение и долгота восходящего узла остались неизменными.

Радиус Земли: 6371008,8 м.

Угол над горизонтом, при котором возможен прием данных: 30°.

За каждый проход над станцией в установленный промежуток времени начисляется 2,25 балла. Максимальный балл за задачу — 9. Начиная с пятой попытки начинается дисконт за каждую попытку, равный 10% максимального балла. Оценка снижается вплоть до 0,9 балла, далее не уменьшается с числом попыток.

Решение

Параметры установившейся орбиты миссии:

  • Большая полуось: 7250 км.
  • Наклонение: 60°.
  • Эксцентриситет: 0.
  • Аргумент перицентра: 0.
  • Долгота восходящего узла: 185°.
  • Истинная аномалия: 155°.

Критерии оценки первого дня

Задача Максимальный балл
Программист (платформа спутника)
Настроена беспроводная передача информации по UART и беспроводной способ прошивки микроконтроллера 2
Задано управление двигателем, продемонстрировано изменение направления и скорости вращения 2
Считаны данные с датчика позиционирования (как это делалось на удаленной работе) 2
Реализован алгоритм стабилизации спутника 4
Программист (полезная нагрузка)
Реализовано чтение данных с датчика AMG8833 3
Спаяна схема для восстановления работы датчика, реализована соответствующая управляющая функция 2
Реализована функция оценки положения по данным датчика AMG8833 4
Инженер связи
Подключен передатчик, написана и загружена прошивка для передачи данных 3
Подключен и проверен приемник 1
С помощью приемника подтверждена работа передатчика; проверена правильность его центральной частоты и девиации 4
Проверена работа GNU Radio 1
Баллистик
Решена первая задача в симуляторе 9
Итого 37
Второй день
Программист (платформа спутника)

Второй день посвящен алгоритму ориентации.

Основная задача второго дня: написание алгоритма ориентации спутника на землю, используя показания с матричного ИК-датчика, которые предобработал программист полезной нагрузки, написав алгоритм ориентации на Землю.

Для решения задачи можно принять, что начальная скорость спутника равна 0. Код опроса датчика программиста полезной нагрузки необходимо встроить в прошивку. Ориентация функционирует, и опрос датчика работает на одном контроллере.

Цитата из задачи программиста полезной нагрузки прошлого дня:

«Функция оценки положения должна обрабатывать полученный снимок и в зависимости от его содержания возвращать:

  • 0 — если корректировки не требуются;
  • положительное число (цифру) — если для корректировки необходимо вращать спутник по часовой стрелке;
  • отрицательное число (цифру) — если для корректировки необходимо вращать спутник против часовой стрелки;
  • большую по модулю константу (например, 127) — если Земли нет в кадре».

Решение

C++
/* USER CODE BEGIN 3 */
    /* предыдущий код, тут только изменения */
/* sprintf(result, "Angle speed\t\tx:%f\ty:%f\tz:%f\r\n", x, y, z);
HAL_UART_Transmit(&huart1, (uint8_t*)result, strlen(result), 1000);
HAL_Delay(250);
motor.NeedSpeed(2000);
HAL_Delay(1000);
motor.NeedSpeed(-2000);
HAL_Delay(1000); */
float center = findCenter();
float needSpeed = (center < -1 ? -1 : center) * 0.2;
Программист (полезная нагрузка)

Этот день посвящен:

  1. работе с камерой и формированию пакета данных для передачи его на наземную станцию;
  2. созданию скрипта для визуализации снимка.

Работа с камерой и дальнейшая передача снимка на наземную станцию для удобства реализации вынесена на отдельный МК. Поэтому задачи этого и следующего дня будут тесно связаны с задачами инженера связи.

Взаимодействие с камерой

Камера способна делать черно-белые снимки с максимальным разрешением \(640 \times 480\) пикселей. Общение с камерой происходит по протоколу UART на скорости 230400 бод без бита четности.

Поддерживаемые команды:

  • Команда: 0x74 (или символ t) — сделать снимок (делает новый снимок и сохраняет его в память платы камеры).
  • Команда: 0x70 (или символ p) — запрос данных снимка (запрашивает данные о сохраненном снимке; возвращает структуру ImageProperties побайтово, Little-endian).

    Рис. 4.10.

    Поля:

    • height — 2 байта. Хранит высоту снимка в пикселях.
    • width — 2 байта. Хранит ширину снимка в пикселях.
    • vStart — 2 байта. Хранит индекс первой строки матрицы, включенной в передаваемый снимок.
    • hStart — 2 байта. Хранит индекс первого столбца матрицы, включенного в передаваемый снимок.
    • colorspace — 2 байта. Не используется.
    • exposure — 2 байта. Хранит значение экспозиции снимка. Чем выше это значение, тем сильнее камера усиливала приходящий на сенсор сигнал. Имеет диапазон [0–509]. Верхняя граница в 509 — искусственная (?). Вручную можно установить больше, это никак не изменяет фактическую картинку по сравнению со значением 509.
    • length — 4 байта. Хранит длину снимка в байтах.
    • numberOfChunks — 2 байта. Хранит количество пакетов данных, на которые разбито изображение.
  • Команда: 0x6e (или символ n) — запрос следующего пакеты данных. В ответ на эту команду камера присылает побайтово структуру с данными снимка, Littleendian.

    Рис. 4.11.

    Поля:

    • chunkID — 2 байта. Порядковый номер пакета данных в кадре.
    • payloadLength — 2 байта. Количество полезных данных в поле payload. Общая длина пакета всегда остается одинаковой — в поле payload всегда 240 байт, но лишь часть из них может быть полезной (в последнем пакете снимка, количество байт в котором некратно 240).
    • isLastChunk — 2 байта. True, если пакет является последним. False, если пакет не является последним.
    • payload — 240 байт. Байты с 0 до payloadLength-1 — пиксели картинки. 1 байт = 1 пиксель.
    • checksum — 2 байта. Не используется.
  • Команда: 0x72 (или символ r) — сброс chunkID. Сбрасывает счетчик чанков в памяти камеры. После сброса по команде 0x6e будет отправлен первый пакет данных кадра.
  • Команда: 0x73 (или символ s) + uint16_t (ширина) + uint16_t (высота) — изменение размера снимка. Байты ширины и высоты отправляются little-endian.
  • Команда: 0x65 (или символ e) + uint16_t (экспозиция) — изменение экспозиции.

    Байты значения экспозиции отправляются little-endian. Значение экспозиции может быть в диапазон [0–509]. Верхняя граница в 509. При отправке значения, отличного от нуля, камера начинает снимать с установленной экспозицией. При отправке нуля камера переходит в режим автоэкспозиции. После этого может потребоваться некоторое время, чтобы алгоритм автоэкспозиции подобрал идеальное значение.

Формирование пакета данных

После того как участники научились получать снимки с камеры, нужно сформировать пакет для отправки его на наземную станцию. Формат пакета и его структура могут быть выбраны на усмотрение команды, но каждый отправляемый на вход передатчика пакет обязательно должен содержать:

  • преамбулу;
  • время формирования пакета в бортовой системе (от начала миссии);
  • идентификатор пакета внутри снимка (так как один снимок передается несколькими пакетами по частям);
  • длину полезной нагрузки (будет полезно для задачи следующего дня);
  • 480 байт данных снимка (два чанка);
  • постамбулу.

Создания скрипта для визуализации изображения

Чтобы проверить правильность передачи данных, следует создать python-скрипт для обработки пакетов и визуализации полученного снимка. Для взаимодействия с GNURadio можно использовать клиент-серверную архитектуру. Скрипт обработки сигнала, реализуемый в GNURadio, в данном случае будет являться сервером, а python-скрипт для визуализации сообщения — клиентом.

Шаблон серверной части см. ниже.

Python
import socket  # Импорт модуля для работы с сокетами (сетевое взаимодействие)
import cv2  # Импорт OpenCV для работы с изображениями
import numpy as np  # Импорт NumPy для работы с массивами данных
# Создание сокета для подключения к серверу
sock = socket.socket()
# Подключение к серверу по адресу 127.0.0.1 (локальный хост) и порту 52001
sock.connect(("127.0.0.1", 52001))
# Флаг для управления циклом приема данных
rec_flag = True
# Переменная для накопления данных изображения
image_data = b''
# Функция для отображения изображения
def show_image(image_data, width, height):
    # Преобразование байтовых данных в массив NumPy
    img_array = np.frombuffer(image_data, dtype=np.uint8)
    # Изменение формы массива в соответствии с размерами изображения
    img_array = img_array.reshape((height, width))
    # Отображение изображения в окне с названием "Captured Image"
    cv2.imshow("Captured Image", img_array)
    # Ожидание нажатия любой клавиши для закрытия окна
    cv2.waitKey(0)
    # Закрытие всех окон OpenCV
    cv2.destroyAllWindows()
# Основной блок программы
if __name__ == "__main__":
    try:
        # Цикл приема данных от сервера
        while(rec_flag):
            # Получение данных от сервера (bufsize байт за раз)
            data_raw = sock.recv(bufsize)
            # Здесь нужно обработать пакет и выделить полезные данные (payload)
            # Например, извлечь байты изображения из data_raw
            # Добавление полученных данных к общему массиву image_data
            image_data += payload
            # Условие окончания приема изображения (нужно доработать)
            # Например, если получен специальный флаг окончания передачи
            if _____ :  # Здесь нужно добавить условие завершения
                rec_flag = False  # Остановка цикла приема данных
        # Отображение полученного изображения
        show_image(image_data, 640, 480)
   
    # Обработка прерывания с клавиатуры (Ctrl+C)
    except KeyboardInterrupt:
        print("Завершение работы")
    # Блок finally выполняется всегда, даже если произошла ошибка
    finally:
        # Закрытие соединения с сервером
        sock.close()
        print("Соединение закрыто.")

Решение

C
typedef struct {
    uint16_t height, width;
    uint16_t vStart, hStart;
    uint16_t colorspace;
    uint16_t exposure;
    uint32_t length;
    uint16_t numberOfChunks;
} ImageProperties;
typedef struct {
    uint16_t chunkID, payloadLength;
    bool isLastChunk;
    uint8_t payload[240];
    uint8_t checksum;
} Chunk;
//часть кода цикла while(1)
uint8_t buffer[3 + 0 + 3] = {0};
uint8_t bufferProperties[3 + sizeof(ImageProperties) + 3] = {0};
uint8_t bufferData[3 + sizeof(Chunk) + 3] = {0};
HAL_UART_Transmit(&huart2, (uint8_t *)"t", 1, 100);
HAL_UART_Receive(&huart2, buffer, 3 + 0 + 3, 2000);
HAL_UART_Transmit(&huart2, (uint8_t *)"p", 1, 100);
HAL_UART_Receive(&huart2, bufferProperties, 3 + sizeof(ImageProperties) + 3, 2000);
ImageProperties* properties = (ImageProperties*)(bufferProperties + 3);
for (int i = 0; i < properties->numberOfChunks; i++){
    HAL_UART_Transmit(&huart2, (uint8_t *)"n", 1, 100);
    HAL_UART_Receive(&huart2, bufferData, 3 + sizeof(Chunk) + 3, 2000);
    Chunk* data = (Chunk*)(bufferData + 3);
    for(int j = 0; j < data->payloadLength; j + 24){
        uint8_t msg[32] = {0};
        uint32_t time = HAL_GetTick();
        uint16_t id = data->chunkID * 10 + j;
        msg[0] = 0xFF;
        msg[1] = 0xFF;
        msg[2] = (uint8_t)(time >> 0);
        msg[3] = (uint8_t)(time >> 8);
        msg[4] = (uint8_t)(time >> 16);
        msg[5] = (uint8_t)(time >> 24);
        msg[6] = (uint8_t)(id >> 0);
        msg[7] = (uint8_t)(id >> 16);
        for (int k = 0; k < 24; ++k) {
            msg[k + 8] = (data->payload)[j * 24 + k];
        }
       
        HAL_UART_Transmit(&huart1, msg, 32, 1000);
    }
}
//конец части кода цикла while(1)
Инженер связи

В течение дня необходимо наладить канал для получения данных полезной нагрузки МК, протестировать их передачу и прием, а также начать реализовывать помехозащищенное кодирование для более надежной передачи данных (см. рис. 4.124.13).

Рис. 4.12.

Рис. 4.13.

Решение

Реализация канала связи между МК и помехозащищенного кодирования остается на выбор участников.

Пример реализации графа для обработки сигнала, где пакет состоит из преамбулы, двух 16-битных длин полезной нагрузки и самой полезной нагрузки.

Баллистик

Ученые вывели на орбиту спутник для наблюдения за поверхностью Земли. Целевая зона съемки: область, ограниченная 59° и 61° северной широты, 93° и 95° восточной долготы. Аппарат должен снять часть указанной зоны, а после передать данные на наземную станцию в Бразилиа (–15,7797, 312,0703).

Однако спутник имеет конструкционную особенность: камера, которая производит съемку местности, расположена на оси его ориентации в положительном направлении. Антенна, позволяющая передавать снимки на Землю, расположена на той же оси, но в противоположном направлении.

Поэтому для того, чтобы снять зону и передать снимок на Землю, аппарату необходимо при пролете над зоной съемки ориентироваться в надир (по отношению к Земле) камерой, а затем ориентироваться в надир антенной (совершить поворот на 180° и поддерживать направление в надир далее), чтобы передать данные на Землю при пролете над городом Бразилиа.

В качестве решения нужно рассчитать орбиту спутника и написать программу управления спутником.

Время вывода аппарата на орбиту: 01 апреля 2025 года 00:00 по UTC. Время моделирования — 24 ч.

На момент начала симуляции считать спутник стабилизированным.

По умолчанию спутник ориентирован в положительном направлении оси \(oZ\).

Справка по начальным параметрам и синтаксису: https://disk.360.yandex.ru/d/HyvQpPND51Y1Ew.

Радиус Земли: 6371008,8 м.

Угол над горизонтом, при котором возможен прием данных, при условии, что антенна спутника смотрит в надир: 60°.

Начисляется 8 баллов, если спутник пролетел над зоной интереса и в этот момент был ориентирован камерой в надир. Еще 8 баллов начисляется, если после описанного пролета над зоной интереса спутник пролетел над наземной станцией и был ориентирован антенной в надир.

Решение

Параметры орбиты:

  • Большая полуось: 7000 км.
  • Наклонение: 90°.
  • Эксцентриситет: 0.
  • Аргумент перицентра: 0.
  • Долгота восходящего узла: 0°.
  • Истинная аномалия: 0°.

Код управления спутником см. ниже.

JavaScript
'use strict';
var wheels;
var gyros;
function setup() {
    wheels = spacecraft.devices[0];
    gyros = spacecraft.devices[1];
 }
var old_a_cam = 0;
var old_a_ant = 0;
 function loop() {
     if (spacecraft.flight_time > 0 && spacecraft.flight_time < 800) {
        wheels.functions[1].motor_torque = 0;
        let a = orientation_angle_cam();
        let m = 90 + spacecraft.devices[2].functions[0].location[0];
        let s = 0.000005*(a-m) + 0.00007*(a-old_a_cam);
        wheels.functions[1].motor_torque = -s;
        old_a_cam = a;
     }
    
    else if (spacecraft.flight_time > 50000 && spacecraft.flight_time < 50800) {
        wheels.functions[1].motor_torque = 0;
        let a = orientation_angle_ant();
        let m = 90 + spacecraft.devices[2].functions[0].location[0];
        let s = 0.000005*(a-m) + 0.00007*(a-old_a_ant);
        wheels.functions[1].motor_torque = s;
        old_a_ant = a;
    }
    else {
        wheels.functions[1].motor_torque = 0;
    }
 }
 
function rotateVectorByQuaternion(vector, quaternion) {
    const [qx, qy, qz, qw] = quaternion;
    const [vx, vy, vz] = vector;
    const rotatedVector = [
        (1 - 2 * qy * qy - 2 * qz * qz) * vx + (2 * qx * qy - 2 * qz * qw) * vy + (2 * qx * qz + 2 * qy * qw) * vz,
        (2 * qx * qy + 2 * qz * qw) * vx + (1 - 2 * qx * qx - 2 * qz * qz) * vy + (2 * qy * qz - 2 * qx * qw) * vz,
        (2 * qx * qz - 2 * qy * qw) * vx + (2 * qy * qz + 2 * qx * qw) * vy + (1 - 2 * qx * qx - 2 * qy * qy) * vz
    ];
    return rotatedVector;
}
function angleBetweenVectors(vector1, vector2) {
    const dotProduct = vector1[0] * vector2[0] + vector1[1] * vector2[1] + vector1[2] * vector2[2];
    const magnitude1 = Math.sqrt(vector1[0] * vector1[0] + vector1[1] * vector1[1] + vector1[2] * vector1[2]);
    const magnitude2 = Math.sqrt(vector2[0] * vector2[0] + vector2[1] * vector2[1] + vector2[2] * vector2[2]);
    const angleRadians = Math.acos(dotProduct / (magnitude1 * magnitude2));
    const angleDegrees = angleRadians * (180 / Math.PI);
    return angleDegrees;
}
var cam_vector = [0,0,1];
var ant_vector = [0,0,-1];
function my_angle_cam(orientation) {
    return angleBetweenVectors(cam_vector, rotateVectorByQuaternion(cam_vector, orientation));
}
function my_angle_ant(orientation) {
    return angleBetweenVectors(cam_vector, rotateVectorByQuaternion(ant_vector, orientation));
}
function orientation_angle_cam() {
     let star_tracker = spacecraft.devices[3].functions[0];
     let orientation = star_tracker.orientation;
     return my_angle_cam(orientation);
}
function orientation_angle_ant() {
     let star_tracker = spacecraft.devices[3].functions[0];
     let orientation = star_tracker.orientation;
     return my_angle_ant(orientation);
}

Критерии оценки второго дня

Задача Максимальный балл
Программист (платформа спутника)
Аппарат сориентирован на Землю 15
Программист (полезная нагрузка)
Создана функция получения снимка с камеры 4
Создана функция формирования пакета данных 5
Доработан скрипт визуализации, получена картинка 7
Инженер связи
Реализована отправка, прием и корректное декодирование тестовых данных 7
Реализована на МК отправка пакета данных от программиста полезной нагрузки 1
Реализован прием пакета данных от программиста полезной нагрузки 7
Реализована передача принятого пакета данных программисту полезной нагрузки посредством клиент-серверного взаимодействия 1
Баллистик
Решена вторая задача в симуляторе 16
Итого 63
Третий день
Программист (платформа спутника)

Необходимо выполнить несколько задач:

  • собрать единую программу управления, в которой реализована стабилизация аппарата после начальной закрутки, ориентации на Землю и отправке сигнала модулю с камерой о необходимости сделать снимок;
  • доработать точность алгоритма ориентации, чтобы Солнце было строго по центру.
Таблица: Циклограмма работы спутника
Шаг Описание шага
1 Стабилизация спутника после начальной закрутки
2 Ориентация на землю
3 Передача сигнала о необходимости сделать кадр

Примечание:

  • сигналом о необходимости сделать кадр является подача 1 на ножку микроконтроллера, подключенную к контроллеру передатчику;
  • точность наведения оценивается по среднему сдвигу Земли относительно центра кадра за три запуска.

Решение

C++
/* предыдущий код, тут только изменения */
int counter = 0;
/* USER CODE END 0 */
   /* USER CODE BEGIN 3 */
   /* предыдущий код, тут только изменения */
   
   float center = findCenter();
   if (center == 0) { counter++; }
   else { counter = 0; }
   HAL_GPIO_WritePin(TAKE_PHOTO_GPIO_Port, TAKE_PHOTO_Pin, counter > 30 ? GPIO_PIN_SET : GPIO_PIN_RESET);
   float needSpeed = (center < -1 ? -1 : center) * 0.2;
Программист (полезная нагрузка)

В течение дня необходимо подготовить все для финальной защиты, а именно:

  • Завершить задачи прошлых дней, если они остались.
  • Наладить взаимодействие созданных ранее программ работы с камерой и формирования пакетов с программой передачи данных инженера связи.
  • Наладить взаимодействие с МК, отвечающим за ориентацию (описано ниже).
  • Интегрировать приемопередающий комплекс в состав спутника.
  • Проверить полный цикл работы спутника.

Основная задача — подготовка к защите, именно ее рекомендуется сделать приоритетной. Если члены команды уверены, что они располагают временем, то им предлагается решить задачу сжатия данных.

Взаимодействие с МК, отвечающим за ориентацию

В качестве взаимодействия со вторым микроконтроллером требуется реализовать функцию, которая запускает процесс получения снимка с камеры и его отправки на наземную станцию, если на определенный (на усмотрение участников) GPIO-пин приходит высокий логический сигнал.

Реализация алгоритма сжатия пакетов

В рамках задачи следует реализовать описанный ниже алгоритм сжатия данных. Учтите, что обычно эффективность сжатия зависит от размера сжимаемого блока данных — чем больше блок данных, тем выше коэффициент сжатия. Не забудьте реализовать алгоритм распаковки в скрипте для визуализации снимка.

Алгоритм RLE-сжатия

Данный алгоритм использует метод Run-Length Encoding (RLE) для эффективного сжатия последовательности байтов. RLE — алгоритм сжатия данных, заменяющий повторяющиеся символы (серии) на один символ и число его повторов.

Серией называется последовательность, состоящая из нескольких одинаковых символов. При кодировании (упаковке, сжатии) строка одинаковых символов, составляющих серию, заменяется строкой, содержащей сам повторяющийся символ и количество его повторов.

Правила кодирования

  1. Флаговый бит

    Старший бит первого байта группы используется как флаг:

    • 0 — последовательность уникальных байтов.
    • 1 — последовательность повторяющихся байтов.
  2. Повторяющиеся байты (RLE-группы)

    Если байт повторяется два и более раз подряд, он кодируется как:

    (0x80 | количество), значение
    • где (0x80 | количество) указывает, что это RLE-группа;
    • максимальное количество повторений в одной группе — 127 (из-за 7-битного счетчика);
    • если количество повторений превышает 127, последовательность разбивается на несколько групп.

    Пример: AA AA AA AA BB BB BB BB BB.

    Кодируется как: 0x83 AA 0x85 BB.

    • 0x84 = 0x80 | 4 — 4 повторения AA.
    • 0x85 = 0x80 | 5 — 5 повторений BB.
  3. Уникальные байты (обычные последовательности)

    Если несколько байтов не повторяются подряд, они записываются в обычном виде:

    количество, данные
    • Максимальная длина такой последовательности — 127.

    Пример: 12 34 56 78 AA AA AA 99 88.

    Кодируется как: 0x04 12 34 56 78 0x83 AA 0x02 99 88.

    • 0x04 — 4 байта без изменений.
    • 0x83 AA — 3 повторения AA.
    • 0x02 99 88 — 2 байта без изменений.

Решение

C++
// Взаимодействие с МК отвечающим за ориентацию
HAL_GPIO_WritePin(TAKE_PHOTO_GPIO_Port, TAKE_PHOTO_Pin, GPIO_PIN_SET);
C
// Функция RLE сжатия для МК
int rle_encode(const uint8_t *input, int input_size, uint8_t *output) {
    int i = 0, out_index = 0;
    while (i < input_size) {
        int run_length = 1;
        while (i + run_length < input_size && input[i] == input[i + run_length] && run_length < 127) {
            run_length++;
        }
        if (run_length > 1) {
            output[out_index++] = 0x80 | run_length;
            output[out_index++] = input[i];
            i += run_length;
        } else {
            int unique_length = 1;
            while (i + unique_length < input_size &&
                   (unique_length == 1 || input[i + unique_length] != input[i + unique_length - 1]) &&
                   unique_length < 127) {
                unique_length++;
            }
            output[out_index++] = unique_length;
            for (int j = 0; j < unique_length; j++) {
                output[out_index++] = input[i + j];
            }
            i += unique_length;
        }
    }
    return out_index;
}
Python
# Функция для декодирования на python
def rle_decode(encoded):
    decoded = []
    i = 0
    while i < len(encoded):
        header = encoded[i]
        i += 1
        if header & 0x80:
            count = header & 0x7F
            value = encoded[i]
            decoded.extend([value] * count)
            i += 1
        else:
            count = header
            decoded.extend(encoded[i:i + count])
            i += count
    return decoded
Инженер связи

В течение дня радист продолжает работу над помехозащищенным кодированием.

Решение

Выбор помехозащищенного кодирования и место его декодирования остаются за участниками. В качестве примера рассмотрим решение, в котором в качестве помехозащищенного кодирования используется код Хэмминга в рамках графа GNU Radio. Для этого можно использовать блок Python.

Блок, в котором задать следующий код, см. ниже.

Python
import numpy as np
from gnuradio import gr
import pmt
class hamming_decoder_pdu(gr.basic_block):
    def __init__(self, n, k):
        """
        Конструктор блока.
        Параметры:
        n (int): Длина кодового слова (n >= k).
        k (int): Длина информационного слова (k < n).
        """
        gr.basic_block.__init__(self,
            name="hamming_decoder_pdu",
            in_sig=None,
            out_sig=None)
        # Сохраняем параметры кода Хэмминга
        self.n = n
        self.k = k
        # Вычисляем количество проверочных битов
        self.r = n - k
        # Регистрируем порты для PDU
        self.message_port_register_in(pmt.intern("in"))
        self.message_port_register_out(pmt.intern("out"))
        # Устанавливаем обработчик входящих сообщений
        self.set_msg_handler(pmt.intern("in"), self.handle_msg)
    def handle_msg(self, msg):
        """
        Обработчик входящих PDU сообщений.
        """
        # Получаем метаданные и данные из PDU
        meta = pmt.car(msg)
        data = pmt.cdr(msg)
        # Преобразуем данные в массив numpy
        data = pmt.u8vector_elements(data)
        # Декодируем данные с использованием кода Хэмминга
        decoded_data = self.hamming_decode(data)
        # Преобразуем декодированные данные обратно в PDU
        decoded_pdu = pmt.init_u8vector(len(decoded_data), decoded_data)
        self.message_port_pub(pmt.intern("out"), pmt.cons(meta, decoded_pdu))
    def hamming_decode(self, data):
        """
        Декодирует данные с использованием кода Хэмминга (n, k).
        Параметры:
        data (list): Входные данные в виде списка битов.
        Возвращает:
        list: Декодированные данные.
        """
        decoded_data = []
        # Проходим по данным с шагом n
        for i in range(0, len(data), self.n):
            chunk = data[i:i + self.n]
            # Если длина чанка меньше n, пропускаем его
            if len(chunk) < self.n:
                continue
            # Вычисляем синдром
            syndrome = self.calculate_syndrome(chunk)
            # Если синдром не равен нулю, исправляем ошибку
            if syndrome != 0:
                if syndrome - 1 < len(chunk):
                    chunk[syndrome - 1] ^= 1  # Инвертируем ошибочный бит
            # Извлекаем информационные биты
            info_bits = self.extract_info_bits(chunk)
            decoded_data.extend(info_bits)
        return decoded_data
    def calculate_syndrome(self, chunk):
        """
        Вычисляет синдром для чанка данных.
        Параметры:
        chunk (list): Чанк данных длиной n.
        Возвращает:
        int: Значение синдрома.
        """
        syndrome = 0
        for i in range(self.r):
            mask = 1 << i
            parity = 0
            for j in range(self.n):
                if (j + 1) & mask:
                    parity ^= chunk[j]
            syndrome |= parity << i
        return syndrome
    def extract_info_bits(self, chunk):
        """
        Извлекает информационные биты из чанка данных.
        Параметры:
        chunk (list): Чанк данных длиной n.
        Возвращает:
        list: Информационные биты длиной k.
        """
        info_bits = []
        info_positions = self.get_info_positions()
        for pos in info_positions:
            info_bits.append(chunk[pos])
        return info_bits
    def get_info_positions(self):
        """
        Возвращает позиции информационных битов в кодовом слове.
        Возвращает:
        list: Список позиций информационных битов.
        """
        positions = []
        for i in range(1, self.n + 1):
            if not (i & (i - 1)) == 0:  # Проверяем, не является ли i степенью двойки
                positions.append(i - 1)
        return positions[:self.k]  # Возвращаем только первые k позиций
Баллистик

После проведения успешных испытаний с опытными образцами космических аппаратов для дистанционного зондирования Земли было принято решение развернуть группировку таких аппаратов на орбите.

Цель группировки: обеспечить полное покрытие указанной зоны съемкой аппаратами с мультиспектральными камерами в ИК-диапазон.

Орбита аппаратов должна удовлетворять следующим условиям:

  • высота аппарата над зоной съемки не должна превышать 1000 км и не должна находиться ниже 300 км;
  • ширина полосы съемки — 1000 км;
  • зона съемки — территория РФ (считать ее прямоугольником, который ограничен 41,16° и 77,72° северной широты, 27,33° и 190,35° восточной долготы).

Порядок определения узлов зоны:

  1. зона разбивается по широте с севера на юг на полосы шириной 100 км, остаток отбрасывается;
  2. каждая полоса разбивается по долготе с запада на восток на ячейки \(100 \times 100\) км, остаток отбрасывается.

Назовем узлом центр каждой такой ячейки. Узел считается снятым, если след аппарата в надире прошел от него не далее, чем на расстоянии половины ширины полосы съемки.

Задача — используя минимальное количество аппаратов, обеспечить максимальное покрытие за сутки симуляции (то есть за 24 ч должна быть снята максимальная площадь зоны съемки).

Количество спутников, доступных для миссии — не более 100.

Внимание: учитывать теплобаланс, электробаланс и создавать программу управления спутником не нужно!

Дата и время начала симуляции: 01.04.2025 00:00 UTC.

Время симуляции: 24 ч.

Решение

Параметры орбиты «Аппарат 1»:

  • Большая полуось: 7300 км.
  • Наклонение: 90°.
  • Эксцентриситет: 0.
  • Аргумент перицентра: 40.
  • Долгота восходящего узла: 27,33°.
  • Истинная аномалия: 0°.

Параметры орбиты «Аппарат 2»:

  • Большая полуось: 7300 км.
  • Наклонение: 90°.
  • Эксцентриситет: 0.
  • Аргумент перицентра: 40.
  • Долгота восходящего узла: 60,73°.
  • Истинная аномалия: 0°.

Параметры орбиты «Аппарат 3»:

  • Большая полуось: 7300 км
  • Наклонение: 90°.
  • Эксцентриситет: 0.
  • Аргумент перицентра: 40.
  • Долгота восходящего узла: 93,34°.
  • Истинная аномалия: 0°.

Параметры орбиты «Аппарат 4»:

  • Большая полуось: 7300 км.
  • Наклонение: 90°.
  • Эксцентриситет: 0.
  • Аргумент перицентра: 40.
  • Долгота восходящего узла: 125,94°.
  • Истинная аномалия: 0°.

Параметры орбиты «Аппарат 5»:

  • Большая полуось: 7300 км.
  • Наклонение: 90°
  • Эксцентриситет: 0.
  • Аргумент перицентра: 40.
  • Долгота восходящего узла: 189,35°.
  • Истинная аномалия: 0°.

Критерии оценки третьего дня

Задача Максимальный балл
Программист (платформа спутника)

Точность алгоритма ориентации.

Оценки за попытку:

  • отсутствие Земли в кадре — 0% отклонения;
  • Земля в кадре — разница координат центра Земли и центра кадра, деленное на половину ширины кадра, умноженное на 100%;
  • общая оценка — среднее арифметическое трех попыток.
10
Исполнение циклограммы (каждый шаг — 5 баллов). 15
Программист (полезная нагрузка)
Налажено взаимодействие полезной нагрузки с МК, отвечающим за ориентацию аппарата. 3
Налажено взаимодействие полезной нагрузки с МК, отвечающим за передачу данных. 4
Полезная нагрузка интегрирована в корпус аппарата. 4
Реализованы функции кодирования и декодирования согласно алгоритму RLE. 14
Инженер связи
Реализовано помехозащищенное кодирование. 5
Повреждено не более 10% изображения. 8
Повреждено не более 3% изображения. 13
Баллистик
Решена третья задача в симуляторе. 25
Итого 100
Критерии оценки защиты
Задача Максимальный балл
Спутник собран, все элементы надежно закреплены, при этом не используются кустарные методы крепления (клей, изолента и пр.). 5
Циклограмма работы спутника (баллы начисляются за корректную работу в рамках каждого этапа отдельно). Стабилизация 4
Ориентация 8
Съемка 3
Передача данных 6
Прием данных 9
Соблюдена последовательность выполнения этапов (по 0,6 балла за каждый выполненный корректно в свою очередь этап). 3

Положение Земли относительно спутника при съемке (оценивается отклонение от оптической оси камеры)

Расчет по формуле: \((30° - \Delta) / 30° \cdot 6\).

6
Качество переданного снимка — оценивается с помощью сравнения снимка, принятого с помощью эталонного решения и снимка, принятого участниками. 6
Итого 50
text slider background image text slider background image
text slider background image text slider background image text slider background image text slider background image