Помимо обычного режима, отображающего зависимость напряжения от времени, многие осциллографы имеют режим X-Y. В этом режиме рисуется кривая на плоскости. Координаты X и Y точек, принадлежащих кривой, определяются входом с двух каналов осциллографа. Режим X-Y многим знаком по фигурам Лиссажу . Но при желании можно нарисовать и что-то поинтересней. Этим мы сегодня и займемся.

Идея не нова и описана во многих источниках. Мне кажется, что впервые я ее подсмотрел у Alan Wolke, W2AEW (видео один , два и три ), но это не точно.

Итак, нам предстоит генерировать два сигнала сложной формы. Звучит, как задача для микроконтроллера и пары ЦАП. Было решено воспользоваться самодельной отладочной платой на базе микроконтроллера STM32F405. Если вы захотите повторить описанные далее шаги, то можете воспользоваться платой HydraBus , BlackIce II , или какой-то другой. Главное, чтобы микроконтроллер имел два ЦАП. Спортировать проект с STM32F405 на другой микроконтроллер STM32 проще простого.

Примечание: Вас могут заинтересовать статьи Передача изображений в SSB-сигнале с помощью Python и Микроконтроллеры STM32: использование АЦП и ЦАП , если вдруг вы их пропустили.

Рисуемую фигуру определим, как множество вершин с заданными координатами X и Y:

uint8_t xs [ ] = {
2 , 2 , 22 , 22 , 2 , 22 ,
27 , 48 , 48 , 27 , 27 , 27 , 48 , 48 , 27 , 48 ,
52 , 52 , 73 , 73 , 52 , 73 , 73 ,
85 , 77 , 77 , 77 , 85 , 91 , 98 , 98 , 98 , 91 ,
102 , 102 , 102 , 123 , 102 , 123 ,
} ;

uint8_t ys [ ] = {
42 , 84 , 84 , 63 , 63 , 42 ,
42 , 63 , 84 , 84 , 63 , 84 , 84 , 63 , 42 , 42 ,
42 , 84 , 84 , 63 , 63 , 63 , 42 ,
42 , 50 , 84 , 50 , 42 , 42 , 50 , 84 , 50 , 42 ,
42 , 84 , 63 , 84 , 63 , 42 ,
} ;

Координаты принадлежат воображаемой плоскости размером 128 на 128 точек. Числа от 0 до 127 могут быть записаны в один байт, и они относительно удобны при переносе рисунка из тетрадки в код. Степень двойки позволяет использовать битовые сдвиги вместо дорогой операции деления. Это пригодится нам далее.

В коде использовано несколько глобальных переменных:

uint8_t curr_x = 0 ;
uint8_t curr_y = 0 ;

Здесь хранятся текущие координаты X и Y. Это то, что сейчас выдают ЦАП.

int16_t idx = 0 ;
int16_t prev_idx = 0 ;

Индексы точек к которой и от которой мы сейчас рисуем отрезок.

int16_t dx = 0 ;
int16_t dy = 0 ;

Инкремент curr_x и curr_y за одну итерацию рисования отрезка.

int8_t direction = 1 ;

Код устроен так, что сначала фигура рисуется от первой точки к последней, а затем от последней точки к первой. Переменная direction хранит 1 , если в данный момент мы рисуем в прямом направлении, и -1 , если в обратном. Рисование в двух направлениях использовано для ликвидации отрезка между первой и последней точкой на конечном рисунке. Такой артефакт возникает, если зациклить отрисовку «в лоб».

Когда мы понимаем, что закончили рисовать очередной отрезок, и пора рисовать следующий, вызывается процедура next_line() :

void next_line ( ) {
const uint8_t speed = 32 ;
int8_t next_direction = direction ;
idx += direction ;
if ( idx < 0 ) {
idx = 0 ;
next_direction = direction ;
} else if ( idx >= sizeof ( xs ) / sizeof ( xs [ 0 ] ) ) {
idx = sizeof ( xs ) / sizeof ( xs [ 0 ] ) 1 ;
next_direction = direction ;
}

prev_idx = idx direction ;
if ( prev_idx < 0 ) {
prev_idx = sizeof ( xs ) / sizeof ( xs [ 0 ] ) 1 ;
} else if ( prev_idx >= sizeof ( xs ) / sizeof ( xs [ 0 ] ) ) {
prev_idx = 0 ;
}

direction = next_direction ;
curr_x = xs [ prev_idx ] ;
curr_y = ys [ prev_idx ] ;

dx = ( ( ( int16_t ) xs [ idx ] ) ( ( int16_t ) xs [ prev_idx ] ) ) / speed ;
dy = ( ( ( int16_t ) ys [ idx ] ) ( ( int16_t ) ys [ prev_idx ] ) ) / speed ;
if ( dx == 0 ) {
if ( xs [ idx ] > xs [ prev_idx ] ) {
dx = 1 ;
} else if ( xs [ idx ] < xs [ prev_idx ] ) {
dx = 1 ;
}
}

if ( dy == 0 ) {
if ( ys [ idx ] > ys [ prev_idx ] ) {
dy = 1 ;
} else if ( ys [ idx ] < ys [ prev_idx ] ) {
dy = 1 ;
}
}
}

Здесь происходит инкремент / декремент idx и next_idx . Если мы достигли последней точки, обновляется direction . Также пересчитываются значения curr_x , curr_y , dx и dy .

Fun fact! В качестве упражнения предлагаю вам поэкспериментировать с разными значениями speed . Как меняется изображение при больших и меньших значениях константы? Объясните результат.

В начале исполнения мы инициализируем ЦАП, а также проставляем значения глобальным переменным вызовом next_line() :

void init ( ) {
HAL_DAC_Start ( & hdac , DAC_CHANNEL_1 ) ;
HAL_DAC_Start ( & hdac , DAC_CHANNEL_2 ) ;
next_line ( ) ;
UART_Printf ( «Ready! r n » ) ;
HAL_Delay ( 1 ) ;
}

А так выглядит основной цикл программы:

void loop ( ) {
curr_x += dx ;
curr_y += dy ;
if ( ( ( dx > 0 ) && ( curr_x > xs [ idx ] ) ) ||
( ( dx < 0 ) && ( curr_x < xs [ idx ] ) ) ) {
curr_x = xs [ idx ] ;
}
if ( ( ( dy > 0 ) && ( curr_y > ys [ idx ] ) ) ||
( ( dy < 0 ) && ( curr_y < ys [ idx ] ) ) ) {
curr_y = ys [ idx ] ;
}

uint32_t x = ( ( uint32_t ) curr_x ) * 0xFFF / 128 ;
uint32_t y = ( ( uint32_t ) curr_y ) * 0xFFF / 128 ;
HAL_DAC_SetValue ( & hdac , DAC_CHANNEL_1 , DAC_ALIGN_12B_R , x ) ;
HAL_DAC_SetValue ( & hdac , DAC_CHANNEL_2 , DAC_ALIGN_12B_R , y ) ;

if ( ( curr_x == xs [ idx ] ) && ( curr_y == ys [ idx ] ) ) {
next_line ( ) ;
}
}

Здесь curr_x и curr_y увеличиваются на dx и dy соответственно, с поправкой на то, что мы можем промахнутся мимо целевой точки из-за ошибки округления. Затем полученные координаты масштабируются из 7 бит в 12 бит, и это отправляется на пару ЦАП. Если мы видим, что дошли до последней точки в отрезке, то вызываем next_line() .

Приведенный код не претендует на неземную красоту, но он работает:

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

Проект можно повторить на микроконтроллере, отличном от STM32, или даже на FPGA. Если у выбранного вами железа нет ЦАП, это не страшно. Можно воспользоваться внешним ЦАП или сделать ЦАП с нуля на R-2R лестнице .

Надеюсь, что вы нашли эту информацию полезной. Полную версию исходников вы найдете в этом репозитории на GitHub .

admin

Share
Published by
admin

Recent Posts

Что такое Zulip

Zulip — программное обеспечение для реализации корпоративного чата. Разработан в 2012 году, в 2014 был…

2 месяца ago

Что такое Zookeeper

Zookeeper — cервис-координатор, который позволяет обеспечить контроль синхронизации данных. Разработан на Java компанией Apache Software…

2 месяца ago

Что такое Zimbra

Zimbra — программное обеспечение для реализации почтового сервиса или, если сказать точнее, автоматизации совместной деятельности…

2 месяца ago

Что такое Zabbix

Zabbix — бесплатная система мониторинга. Позволяет отслеживать состояние сетевых узлов, компьютеров и серверов. Возможности: Поддержка…

2 месяца ago

Что такое YouTube

YouTube — компания-владелец одноименного портала для просмотра и хранения видео. Чтобы пользоваться данным порталом достаточно…

2 месяца ago

Что такое yota

Yota — провайдер, предоставляющий доступ к сети Интернет по беспроводной связи. Впервые, сервис начал работать…

2 месяца ago