Pull to refresh

Разработка программы отображения для гидроакустической станции под Linux

Reading time13 min
Views5.7K

Гидроакустическая станция серийная, называется Echologger MRS-900. Однолучевая, луч сканирует по кругу или в секторе. Связь с ПК по интерфейсу RS-232/RS-485. Штатное программное обеспечение есть, работает под Windows. От меня потребовалась версия под Linux.

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

Приложение должно было оформлено как библиотека и вызываться совместно с другой программой, которая обеспечивает интерфейс пользователя. Было требование совместимости с любой Linux и с любой ЭВМ, включающей видеокарту с 3D ускорителем. После обсуждения Заказчик согласился ограничиться Debian 9/10 версии, одной из текущих версий Ubuntu и одной архитектурой ЭВМ, на которой он сам проверять будет. Не самой быстрой и новой, что-то чуть ниже среднего на тот момент.

До этого был опыт разработки программы для научного гидролокатора, многолучевого, секторного обзора. Программа под Windows, на Visual C++. Картинка из этой программы:

Приходилось вникать в тему гидроакустики, читать про похожие серийные гражданские гидроакустические станции. У меня сложилось впечатление, что в большинстве гражданских гидроакустических станций применяется ОС Windows. Свои программисты у Заказчика были. Видимо, поэтому нас и наняли как специалистов по разработке ПО под Linux, одновременно имеющих какой-то опыт в гидроакустике.

Предлагаемая к разработке программа была с одной стороны проще - входной поток данных не нуждался в серьезной дополнительной обработке вроде наложения различных ВАРУ, выделения линии дна, обнаружения косяков рыбы и определения их параметров. Оставалась только простая обработка - наложение "мертвой зоны", усиление, регулировка яркости и контрастности. Это всё на уровне очень простых операций с отсчетами, сложение и умножение.

C другой стороны, требовалось постоянно менять масштаб, фактически показывать увеличенное "окно" общей круговой панорамы при необходимости, также нужно было перемещать окно. Чтобы было понятнее, почему это не очень приятно, хочу упрощенно, в двух словах, рассказать, как работает подобный гидролокатор.

Представьте луч шириной 2 градуса. Луч движется по кругу или в заданном секторе. Угловая скорость зависит от режима работы, например, от выставленной максимальной дальности. Периодически передатчик излучает короткий импульс, затем приемник начинает принимать эхосигналы. Период излучения также зависит от настроек ( выбранная дальность, и т.д.). Принятые сигналы обрабатываются, реализуется временная автоматическая регулировка усиления (ВАРУ), результаты в виде набора отсчётов поступают через последовательный интерфейс для отображения.

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

При тех параметрах гидролокатора, что были указаны на официальном сайте, размер набора отсчетов будет не менее 700 отсчетов, при периоде излучения не менее 10мс. Для отладки программного обеспечения Заказчик передал записи, сделанные на его реальном гидролокаторе. Конечно, при таком потоке входных данных лучше Python и не пробовать, только C/C++.

Для упрощения решения задачи масштабирования решил рисовать в памяти максимально подробную картинку, опираясь на параметры гидролокатора, а именно на разрешение по дальности и углу. Окончательно величина дискреты по углу и по дальности согласовывалась с Заказчиком - он как разработчик лучше всех знает свою аппаратуру и её реальные возможности.

Для каждой дискреты предварительно рассчитывались таблицы координат точек x, y. Вот пример функций расчёта:

// maximum amplitude samples in one beam
#define MAX_SAMPLES_TO_SHOW 1024

// beams per 360 degrees, maximum angles with step 0.1125 corresponf to 3600 beams, 720 beams correpond to 0.5 degrees
#define BEAMS 1440

// limit of reserved real screen pixels for each sample
#define MAX_PIXELS_PER_SAMPLE 32

// arrays of pixels coordinates for each sonar ADC sample 
int16_t pixel_table_x[BEAMS][MAX_SAMPLES_TO_SHOW][MAX_PIXELS_PER_SAMPLE],\
    pixel_table_y[BEAMS][MAX_SAMPLES_TO_SHOW][MAX_PIXELS_PER_SAMPLE],\
    pixel_table_n[BEAMS][MAX_SAMPLES_TO_SHOW];

// samples number equal to diameter
int samples_per_beam; // have to be lower than MAX_SAMPLES_TO_SHOW


// calculate angle between vector (x,y) and X axis, the result in the sonar coordinates
float angle(float x, float y) {
	float alpha, pi=3.1415926, tpi = 2*pi;
	if (abs(x)<0.0001) { 
		if (y>0) alpha=pi/2.0;
		else alpha=3.0*pi/2.0;
	}
    else if(abs(y)<0.0001) {
        if (x>0) alpha=0;
        else alpha=pi;
    }
	else {
		alpha = atan(y/x);
		if( (x<0)&&(y>0) ) alpha += pi;
		if( (x<0)&&(y<0) ) alpha += pi;
		if( (x>0)&&(y<0) ) alpha += tpi;
	}
    alpha = 3*pi/2.0-alpha;
    
    if ( alpha<0 ) alpha+=tpi;
    if ( alpha>tpi) alpha-=tpi;
    
	return alpha;
}


// calculate table of the pixels
void pixel_table_ini( int diagram_diameter_in_pixels ) {
	float r, alpha;
	int beam, i; // indexes in the table
	int j;
	float xc,yc,pi=3.1415926;
	float dyy=0;

    // reset the tables
	memset( pixel_table_n, 0, sizeof( pixel_table_n ) ); 
	memset( pixel_table_x, 0, sizeof( pixel_table_x ) ); 
	memset( pixel_table_y, 0, sizeof( pixel_table_y ) ); 
    
    // check for MAX_SAMPLES_TO_SHOW
    if (diagram_diameter_in_pixels > (MAX_SAMPLES_TO_SHOW*2))
        diagram_diameter_in_pixels = MAX_SAMPLES_TO_SHOW*2;
    
    samples_per_beam = diagram_diameter_in_pixels/2;
    float Radius = diagram_diameter_in_pixels / 2.0;

    // check all pixels inside diameter*diameter bar to fill pixels arrays
	for(float x=0; x < diagram_diameter_in_pixels; x++ ) {
        
		for(float y=0; y < diagram_diameter_in_pixels; y++ ) {
			xc = x - Radius;
			yc = y - Radius;
			r = sqrt( xc*xc + yc*yc ); // distance to center
			alpha = angle(xc,yc);

			beam = alpha/(3.1415926*2.0) * (float)BEAMS;
			i = (r + 0.5)*2.0; // index of the sample inside the beam
			i /= 2;
			if ( (i<Radius)&&(beam>=0)&&(beam<=BEAMS) ) {
				j = pixel_table_n[beam][i]; // index of the pixel in the table
				if( (j<MAX_PIXELS_PER_SAMPLE) ) {
					pixel_table_x[beam][i][j] = x;
					pixel_table_y[beam][i][j] = y;
					++pixel_table_n[beam][i];
				}
			}
		}
    }
}

Графическую библиотеку выбрал SDL2. Был позитивный опыт её использования под Windows, как раз для ПО научного гидролокатора, совместно с Visual C++. Ещё одна картинка из этой программы:

Позднее использовал совместно c CodeBlocks и MinGW. Звук был не нужен. Для установки SDL2 в Debian 10 мне было достаточно команды:

sudo apt-get install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev libsdl2-gfx-dev

Для компиляции программы как библиотеку c именем libsonarwindow.so можно использовать одну команду, пример:

g++ -std=c++0x -shared -fPIC -pedantic main.cpp -o libsonarwindow.so `sdl2-config --cflags --libs` -lSDL2_image -lSDL2_ttf -lpthread

Для компиляции в исполняемый файл:

g++ -std=c++0x -pedantic main.cpp -o main `sdl2-config --cflags --libs` -lSDL2_image -lSDL2_ttf -lpthread

"Перемещение" окна отображения по большой картинке со сменой масштаба увеличения в итоге было реализовано с помощью копирования "уменьшенного" окна из большой текстуры SDL2. Я считаю, что за счет такого подхода и возможностей SDL2 реализация задачи сильно упростилась. Ниже часть исходника, который выполняет эту задачу:

//----------------------- показать один луч ( набор отсчетов ) -----------------------------            
if (isDataBufferReady || usefile) {

    if (usefile) {
        // прочитать один набор отсчетов из файла
        GetUnpackedDesc( saved_beam_i, dataHeader); 
    }
    else {															
        dataHeader = dataHeaderStream;                    
        pUnpackedSamples = (uint32_t*)(comBuffer);
    }
    
    // предварительная обработка
    AddDeadZone( deadzone_value ); // default 5
    AddGain( gain_value, dataHeader.samples); // default 8
    AddBrightness( brightness_value, dataHeader.samples); // default 400
    AddContrast( contrast_value, dataHeader.samples); // default 2
    
    isDataBufferReady = false;
    
    int samples_n = dataHeader.samples; // number of samples in the burst

    // определить фактическую дальность
    current_range = 10.0; // значение по умолчанию, при этом реально в файле может быть 1392, по таблице 1376
    for (int i=0;i<15;i++) {
        if ( samples_set[i] == samples_n ) {
            current_range = range_set[i];
            break;
        }
    }

    // вычислить углы и номер луча для предварительно вычисленных координат точек на экране
    tetha = dataHeader.angle*0.0125/360.0;
    ray = tetha*BEAMS;
    beam = ray;
    
    // сделать аппроксимацию, если samplesnum >= samples в таблице
    if ( samples_per_beam <= samples_n ) { // too much samples
        
        float kpN = (float)samples_n / (float)samples_per_beam; // scale ratio
            for(int i=0; i<samples_per_beam; i++) {

                int start = kpN*(float)i - 0.5;
                if (start<0) start=0;
                end   = kpN*(float)(i+1) + 0.5;
                if( end >= samples_n ) end = samples_n - 1;

                for( int j=start; j<=end; j++) {
                    a = pUnpackedSamples[j];
                    if(j==start) Amax=a;
                    else if (a>Amax) Amax=a;
                }
                
                samples_to_show[i] = Amax;
            }
    }
    else { // если samplesnum < samples в таблице
        float kpN = (float)samples_n / (float)samples_per_beam;
        int j; // sample inside beam index
        for(int i=0; i<samples_per_beam; i++) {
            j = (kpN*(float)i);
            samples_to_show[i] = pUnpackedSamples[j];
        }
    }

    
    // после аппроксимации заполнить samples_drawn[] правильными точками,
    // это преобразование из samples_to_show[] в реальные точки в текстуре
    for(int i=0; i < samples_per_beam; i++ ) {
        sample = samples_to_show[i];

        if (sample>4096) sample=4095;
    
        for ( int j = 0; j < pixel_table_n[beam][i]; j++ ) {
            x = pixel_table_x[beam][i][j];
            y = pixel_table_y[beam][i][j];
            samples_drawn[x+y*MAX_SAMPLES_TO_SHOW] = sample;
        }
    }

}

// нарисовать красный курсор по текущему положению луча, если это задано в настройках
if (pointer_show)
    FillRedCursor(cursor, diameter, tetha);
           
is_beam_ready = false;

// преобразования точек по палитре, добавление курсора и шкал дальности, результат в pixels[]
int samples_index = 0;
int pixels_index = 0;
for(y=0;y<diameter;y++) {
    for(x=0;x<diameter;x++) {
        sample = samples_drawn[x+samples_index];
        if (sample<4096) {
            
            if (show_in_rainbow_palette) pixels[pixels_index] = palette_rainbow[ sample ];
            else pixels[pixels_index] = palette_brown[ sample ];
        }
        else pixels[pixels_index]=background_color;
        
        if (cursor[pixels_index])
            pixels[pixels_index] = cursor[pixels_index];
        else 
            if (grid_show) 
                if (net[pixels_index]) pixels[pixels_index] = net[pixels_index];
                            
        ++pixels_index;
    }
    samples_index+=MAX_SAMPLES_TO_SHOW;
}


// преобразовать pixels[] в SDL2 текстуру
SDL_UpdateTexture(texture, NULL, pixels, diameter * sizeof(uint32_t));

// ----        вычисления, какую часть текстуры отображать на экране,         ----
// ---- чтобы обеспечить масштабирование и передвижение по картинке от сонара ----

//--- значения по умолчанию, куда копировать
dest.w = diameter;
dest.h = diameter;

// центрирование
dest.x = (window_width-diameter)/2;  
dest.y = (window_height-diameter)/2;
if (dest.y>16) dest.y-=16;

// прямоугольник, который копировать из большой текстуры
src.x = source_rect.x;
src.y = source_rect.y;
src.w = source_rect.w;
src.h = source_rect.h;


if ( window_height < window_width ) { // альбомная ориентация окна отображения
    float window_ratio = (float)window_width/((float)window_height-10);
    // .h не менять, увеличивать .w:
    dest.x = dest.x-(window_ratio-1.0)*(float)dest.w/2;
    src.x  = src.x-(window_ratio-1.0)*(float)src.w/2;

    dest.w = dest.w * window_ratio;
    src.w  = src.w  * window_ratio;
}
else { // портретная ориентация окна отображения
    float window_ratio = ((float)window_height-10)/((float)window_width);

    dest.y = dest.y-(window_ratio-1.0)*(float)dest.h/2;
    src.y  = src.y-(window_ratio-1.0)*(float)src.h/2;

    dest.h = dest.h * window_ratio;
    src.h  = src.h  * window_ratio;
}

if ((src.w+src.x)>diameter) {
    float k = (float)(diameter - src.x)/(float)src.w;
    src.w = diameter - src.x;
    dest.w *= k;
    dest.x = 0;
}

if (src.x<0) {
    float k_short = (float)(-src.x)/(float)src.w;
    float k = (float)(src.w+src.x)/(float)src.w;
    src.w = src.w+src.x; // cut left side
    src.x = 0;
    dest.x = (float)dest.w * k_short;
    dest.w *= k;
}

if ((src.h+src.y)>diameter) {
    float k = (float)(diameter - src.y)/(float)src.h;
    src.h = diameter - src.y;
    dest.h *= k;
    dest.y = 0;
}

if (src.y<0) {
    float k_short = (float)(-src.y)/(float)src.h;
    float k = (float)(src.h+src.y)/(float)src.h;
    src.h = src.h+src.y; // cut left side
    src.y = 0;
    dest.y = (float)dest.h * k_short;
    dest.h *= k;
}

// ---- конец вычислений, какую часть текстуры отображать на экране ----

// копирование в окно отображения прямоугольную часть большой картины
SDL_RenderCopy(renderer, texture, &src, &dest);

Результат отображения эхосигналов без масштабирования:

Та же панорама, но с максимальным увеличением участка:

Отображение в другой палитре:

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

Пример использования совместно с Qt, файл main.cpp :

#include "mainwindow.h"
#include "sonarwindow.h"
#include <QApplication>
#include <QWidget>

// to compile:
// Build->Rebuild all

// to launch from command line copy gost_b.ttf, hrs900_20150526_063659.bin
// to directory ~/build-qt_example-Desktop-Debug  , then:

// cd ~/build-qt_example-Desktop-Debug
// export LD_LIBRARY_PATH=/home/<user>/qt_example:$LD_LIBRARY_PATH
// ./qt_example

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;

    // 
    File_Read("hrs900_20150526_063659.bin");
    File_Play(1); // 1 максимальная скорость, 2,3,4 медленнее

    View_setting(8, 5, 1, 400, 0, true, true, true, true, true);
    Create_Window(100,120,820,820);

    //Change_Window(200,200,620,720); 

    w.move(95,40);
    w.show();
    w.raise();
    w.activateWindow();

    return a.exec();
}

Так пример с Qt выглядит на экране:

Заголовочный файл библиотеки sonarwindow.h :

void View_setting(int gain, int deadzone, int contrast, int brightness, bool pallete, bool grid, bool autocontrast, bool pointer, bool fitwindow, bool repeatplayback);

// рекомендованные значения:
// bool grid_show = true, show_in_rainbow_palette = false, pointer = true, repeat_playback = true;
// int contrast_value = 1, deadzone_value = 5, gain_value = 8, brightness_value = 400;

int Create_Window( int x_pos, int y_pos, int width, int height );
int Change_Window( int x_pos, int y_pos, int width, int height );
int FillRawComBufferArray(unsigned char* inArray, int inArraySize);
int File_Read(const char* afilename);
int File_Play(int speed);
void File_Hold();
void File_Stop();

Пример использования библиотеки с GUI библиотекой FLTK:

#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>

#include "sonarwindow.h"

#include <pthread.h>

#include <string.h>
#include <fcntl.h> // Contains file controls like O_RDWR
#include <errno.h> // Error integer and strerror() function
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h> // write(), read(), close()

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Menu_Bar.H>

// To install FLTK:
// sudo apt install fltk1.3-dev

// To compile and run:
// g++ -L/home/tolik/sonar_FLTK -o test main.cpp -lsonarwindow -L/usr/local/lib -lfltk -lXext -lXinerama -lXft -lX11 -lXi -lm -lpthread
// export LD_LIBRARY_PATH=/home/user/sonar_FLTK:$LD_LIBRARY_PATH
// ./test

// FLTK buttons callbacks
void start_button_callback(Fl_Widget *w, void *data) {
    File_Play(1);
    printf(" start \n");
}

void stop_button_callback(Fl_Widget *w, void *data) {
    File_Stop();
    printf(" stop \n");
}

bool stop_serial_reading=false;
bool usefile = false;
const char* USBname=NULL;
int USB; //USB port id
char buf = '\0';
struct termios tty;
struct termios tty_old;

#define DEMOCOMBUFSIZE 128
unsigned char DemoComBuffer[DEMOCOMBUFSIZE];
int DemoComBufferLast=0;

int UpdateRawComBuffer()
{		
	while (DemoComBufferLast<sizeof(DemoComBuffer))
	{
		int testReading=read( USB, &DemoComBuffer[DemoComBufferLast], 1);
		if (testReading==-1)
		{
			break; //COM is empty				
		}		
		DemoComBufferLast++;		
		while((DemoComBufferLast<sizeof(DemoComBuffer)) && (testReading!=-1))
		{				
			testReading=read( USB, &DemoComBuffer[DemoComBufferLast++], 1);				
			//if (testReading!=-1) TotalComRead++;
			//printf("+%c", DemoComBuffer[DemoComBufferLast-1]);
		}	
		if (testReading==-1) DemoComBufferLast--;		
	}		
	int UsedElements=FillRawComBufferArray(&DemoComBuffer[0], DemoComBufferLast);		
	memmove((unsigned char*)&DemoComBuffer[0], ((unsigned char*)&DemoComBuffer[0])+UsedElements, sizeof(DemoComBuffer)-UsedElements);
	DemoComBufferLast-=UsedElements;
    return 0;
}    


void* thread_serial_port_parsing_auto2( void* args ) {      
    printf("thread_serial_port_parsing_auto2 - started\n");
    while(!stop_serial_reading) {                   
        UpdateRawComBuffer();
    }
    return &stop_serial_reading;
}




int main( int argc, char **argv ) {
	
    usefile = true;
    
    if( !usefile) { // just to communicate via serial port
        
        // select COM or file
        USBname="/dev/ttyS1";
        printf("%s\n", USBname);        
        USB = open( USBname, O_RDWR| O_NOCTTY | O_NONBLOCK);	
        if (USB < 0) printf("Error %i from open: %s\n", errno, strerror(errno));		
        memset (&tty, 0, sizeof tty);
        
        if ( tcgetattr ( USB, &tty ) != 0 )
        {
            //std::cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl;
            printf("Error");
        }
        
        tty_old = tty;
        /* Set Baud Rate */
        //cfsetospeed (&tty, (speed_t)B38400);
        //cfsetispeed (&tty, (speed_t)B38400);
        
        cfsetospeed (&tty, (speed_t)B115200);
        cfsetispeed (&tty, (speed_t)B115200);		
        /* Setting other Port Stuff */
        tty.c_cflag     &=  ~PARENB;            // Make 8n1
        tty.c_cflag     &=  ~CSTOPB;
        tty.c_cflag     &=  ~CSIZE;
        tty.c_cflag     |=  CS8;
        tty.c_cflag     &=  ~CRTSCTS;           // no flow control
        tty.c_cc[VMIN]   =  0;                  // read doesn't block
        tty.c_cc[VTIME]  =  0;                  // 
        tty.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines
        /* Make raw */
        cfmakeraw(&tty);
        /* Flush Port, then applies attributes */
        tcflush( USB, TCIFLUSH );
        if ( tcsetattr ( USB, TCSANOW, &tty ) != 0)
        {
            //std::cout << "Error " << errno << " from tcsetattr" << std::endl;	
            printf("Error 1");
        }
        int n = 0,
        spot = 0;	
        n = read( USB, &buf, 1 );
        if (n < 0) {
            printf("Error 2\n");//std::cout << "Error reading: " << strerror(errno) << std::endl;	
        }			
        
        // launch COM serial reading thread (should be external)
        pthread_t id;    
        if (!usefile) {                
            int ret;        
            ret = pthread_create(&id, NULL, &thread_serial_port_parsing_auto2, NULL);
            if(ret==0) printf("main(): thread_serial_port_parsing_auto2() thread created successfully.\n");
            else{
                printf("main(): thread_serial_port_parsing_auto2() thread not created.\n");
                return 0; /*return from main*/
            }        
        }	

    }
    
    Fl_Menu_Item menuitems[] = {
      { "&File",              0, 0, 0, FL_SUBMENU },
        { "E&xit", 0, (Fl_Callback *)stop_button_callback, 0 },
        { 0 },
      { "&Settings", 0, 0, 0, FL_SUBMENU },
        { "&Language",       0, (Fl_Callback *)stop_button_callback, 0, FL_MENU_DIVIDER },
        { "&Sonar settings",        FL_COMMAND + 's', (Fl_Callback *)stop_button_callback },
        { "&View settings",       0, (Fl_Callback *)stop_button_callback },
        { "&Working folder",      0, (Fl_Callback *)stop_button_callback },
        { "&Autoscreenshot",     0, (Fl_Callback *)stop_button_callback },
        { 0 },
      { "&Help", 0, 0, 0, FL_SUBMENU },
        { "&About...",       0, (Fl_Callback *)stop_button_callback },
        { 0 },
      { 0 }
    };
    
    
    Fl_Window *window = new Fl_Window(80,10,850,800,"Control");
    Fl_Button *button_start = new Fl_Button(710, 130, 100, 25, "Start");
    Fl_Button *button_stop = new Fl_Button(710, 170, 100, 25, "Stop");

    Fl_Menu_Bar *m = new Fl_Menu_Bar(0, 0, 680, 30);
    m->copy(menuitems);

    int xyz_data;
    button_start->callback(start_button_callback, &xyz_data);
    button_stop->callback(stop_button_callback, &xyz_data);
  
    File_Read("hrs900_20150526_063659.bin");
    File_Play(1);
    View_setting(8, 5, 1, 400, true, true, true, true, true, true);
    
    Create_Window(100,100,650,800);   
    sleep(1);
    
    // Change_Window(200,200,620,720);
    window->end();
    window->show(argc, argv);
  
    return Fl::run();
}

Установка FLTK для Debian/Ubuntu:

sudo apt install fltk1.3-dev

Компиляция примера совместно с нашей библиотекой:

g++ -L/home/tolik/sonar_FLTK -o test main.cpp -lsonarwindow -L/usr/local/lib -lfltk -lXext -lXinerama -lXft -lX11 -lXi -lm -lpthread
 export LD_LIBRARY_PATH=/home/user/sonar_FLTK:$LD_LIBRARY_PATH

Работа программа по записанным данным на видео:

Заказчик перед началом разработки предоставил исходники своей программы под Windows. В части отображения круговой диаграммы наш исходник получился раза в 5 короче, всё благодаря использованию SDL2.

Нам было интересно попробовать откомпилировать и запустить эту программу отображения под Raspberry Pi. Rasbian хоть и на базе Debian, но другая архитектура, хорошая проверка с точки зрения кроссплатформенности. Установить SDL2 получилось, откомпилировать получилось, запустить получилось. Работала программа существенно медленнее, чем на моем старом i3-2100, со старенькой видеокартой Radeon 512Мб. Но работала, без доработок, что не может не радовать. Заказчик использовал эту библиотеку в основном с Ubuntu.

Tags:
Hubs:
Total votes 8: ↑8 and ↓0+8
Comments13

Articles