Всем доброго времени суток! Как уже понятно из названия, сегодня мы разберемся как вывести произвольное изображение на экран осциллографа 👍 И поможет нам в этом наш верный друг - микроконтроллер STM32.
Итак, мой комплект для экспериментов на сегодня:
У осциллографа есть режим XY, который мы и будем использовать. Идея этого режима максимально проста - сигнал одного из каналов осциллографа отклоняет луч по горизонтальной оси, а сигнал второго - по вертикальной. Таким образом, подавая разные сигналы на 2 канала осциллографа мы можем перемещать луч в 2D-плоскости экрана, что нам, собственно, и требуется.
Вообще этот режим нужен для того, чтобы построить наглядную зависимость одного сигнала от другого, мы же его используем для вывода на экран графического изображения. И для подачи сигналов мы задействуем два канала ЦАП микроконтроллера, по одному на каждый входной канал осциллографа.
Рисовать будем одноцветные изображения, в силу ограничений экрана прибора. Также нужно учитывать, что рисуемые контуры должны быть замкнутыми, то есть заканчиваться в точке начала. Это связано с тем, что выводимое изображение будет складываться из линий, которые пройдет луч между точками, в которые мы его отправляем.
То есть, например, мы действуем так:
- выдаем на каналы ЦАП значения напряжений, соответствующие координатам одной точки на экране
- выдаем значения для координат второй точки
- повторяем эти операции в цикле
Это позволит нам отрисовать на экране линию между этими двумя точками. Поэтому в случае, если конец контура не совпадает с началом, мы увидим дополнительную линию, которая соединяет эти точки начала и конца.
Кроме того, точки мы должны выводить последовательно друг за другом, как будто рисуем эту линию на бумаге. Вот так:
В общем-то описанные шаги практически в полной мере отражают алгоритм, по которому будет работать наша программа. Мы будем последовательно выводить точки изображения от начальной до конечной, и все это циклически повторять. Дело за малым - пишем реализацию 👍
Для первого примера выведем какую-нибудь простую фигуру из линий, пусть будет квадрат. Итак, создаем проект в STM32CubeMx и включаем два канала DAC. Я использую STM32F103VET6, так что это выводы PA4 и PA5.
В качестве изображений для вывода на экран я буду использовать картинки 512*512 пикселей, чтобы они примерно занимали весь экран осциллографа. Вообще это все можно настраивать/перемещать настройками осциллографа, меняя положение и цену деления шкалы прибора. У меня на ось X приходится 12 "клеток", на ось Y - 8:
STM32 может выдать напряжение от 0 до 3.3 В. Ставим на осциллографе 200 мВ на деление, получаем следующее...
Весь диапазон 1-го канала (оси X) равен 12 клеток * 200 мВ = 2.4 В. То есть, выдавая на первый канал ЦАП напряжения от 0 до 2.4 В, мы охватим весь простор экрана осциллографа. DAC в STM32 12-битный, то есть напряжению 3.3 В на выходе соответствует цифровое значение 4095. Тогда 2.4 В это:
\frac{4095}{3.3} * 2.4 = 2978
Получаем, что выдавая напряжение на выход ЦАП мы должны оперировать цифровыми значениями от 0 до 2978.
Аналогично, для второго канала, только с учетом, что там 8 клеток * 200 мВ - диапазон значений ЦАП составляет (0 ... 1985). На практике, будем выдавать чуть поменьше, чтобы было расстояние до краев экрана, плюс добавим сдвиг от 0, опять же, чтобы изображение не находилось в самом углу. На самом деле, сверх меры нет смысла заморачиваться с этим, поскольку на практике все будет видно, куда, что подвинуть и где увеличить/уменьшить амплитуду.
Входы осциллографа подключаем напрямую к выводам микроконтроллера, ничего дополнительного не требуется. Итак, рисуем квадрат и задаем его координаты:
Поскольку изображение квадратное, то на обе оси (на оба канала) будем выдавать значения в диапазоне от 0 до 1985. И добавим запас от краев экрана - в итоге выдаем на ЦАП от 300 до 1685:
#define MIN_DAC_VALUE 300 #define MAX_DAC_VALUE 1685 #define IMAGE_SIZE 512
Создаем массив с нашими точками:
uint16_t square[8] = {100, 100, 100, 400, 400, 400, 400, 100};
А в цикле while(1)
работаем с DAC:
for (uint16_t i = 0; i < 8; i += 2) { HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, MIN_DAC_VALUE + (square[i] * (MAX_DAC_VALUE - MIN_DAC_VALUE) / IMAGE_SIZE)); HAL_DAC_SetValue(&hdac, DAC_CHANNEL_2, DAC_ALIGN_12B_R, MIN_DAC_VALUE + (square[i + 1] * (MAX_DAC_VALUE - MIN_DAC_VALUE) / IMAGE_SIZE)); usDelay(2); }
Для того, чтобы получить значения для DAC, мы преобразуем и сдвигаем координаты точек из массива:
Out = Min + координата * \frac{размер \medspace изображения}{Max - Min}
Кроме того, мы добавили функцию задержки, для этого включаем таймер (TIM6) и настраиваем для него прерывания по переполнению. Предделитель частоты и период таймера такие, чтобы прерывание генерировалось каждые 10 мкс. Настройки в CubeMx приводить не буду, все делаем так же как тут - ссылка.
Добавляем функцию простейшей задержки, и в прерывании таймера декрементируем значение счетчика:
void usDelay(uint32_t time) { usCnt = time; while(usCnt > 0); }
void TIM6_IRQHandler(void) { /* USER CODE BEGIN TIM6_IRQn 0 */ /* USER CODE END TIM6_IRQn 0 */ HAL_TIM_IRQHandler(&htim6); /* USER CODE BEGIN TIM6_IRQn 1 */ if (usCnt > 0) { usCnt--; } /* USER CODE END TIM6_IRQn 1 */ }
Вот и все, запускаем и смотрим на экран:
Все отработало четко по плану. Собственно, вот таким образом можно вывести произвольный контур, задав массив с координатами точек по аналогичной схеме.
Но, поскольку вручную считать и копировать координаты довольно утомительное и времязатратное занятие, я написал небольшую утилиту на Qt для генерации массива нужного вида. Работает это так: открываем нужное изображение-контур в программе, далее обводим его линией, либо расставляем точки по периметру и генерируем массив. Все максимально просто! Можно и просто рисовать произвольную форму и создавать массив, все работает точно по такому же принципу.
Код утилиты разберем в отдельной статье (может быть), а сегодня я просто выкладываю ссылку на проект. В папку "build-..." вместе с ехешником я положил нужные для запуска dll, так что программу можно просто запускать по .exe файлу, без установки Qt и сборки проекта из исходников.
После генерации получаем .c файл с массивом и добавляем его в наш проект для IAR'а. Примеры массивов я разместил в папке Images (ссылка на проект в конце статьи). Указатель на массив и его размер упакованы в структуру PAINT_Image
. Кроме того для отрисовки добавлена специальная функция, которая как раз и принимает в качестве аргумента указатель на эту структуру.
void PAINT_Draw(PAINT_Image* image);
Так что просто вызываем эту функцию, например:
PAINT_Draw(&catImage);
И получаем:
Занятие довольно забавное, так что я поигрался с разными контурами, вот такие получились результаты:
В общем-то вот все и готово! Следуя этим шагам можно нарисовать на экране осциллографа любое изображение (само собой учитывая физические ограничения, которые мы обсудили).
Выкладываю все необходимые проекты:
На выходных попробуем.
Отлично! 🙂
Попробую в Keil сделать. Есть подводные камни при переносе из IAR?
Не должно быть, в Cube можно изначально сгенерить под Кейл, а остальные файлы не зависят.