История одного осциллографа на stm32 / Habr

Чуть больше года назад, мне в голову пришла мысль о том, что хорошо бы было сделать осциллограф. Тогда мне хотелось, чтобы это было независимое устройство с собственным TFT дисплеем, да и вообще, идея разобраться с TFT дисплеями, мне казалась очень перспективной. Спустя некоторое время на али был заказан TFT размером 3.2 дюйма с драйвером SSD1289.

На тот момент у меня уже был опыт программирования микроконтроллеров AVR, поэтому решил запустить дисплей на моём любимом Atmega16. Дойдёт ли дело до создания осциллографа тогда ещё не знал, но то что буду в своих проектах использовать TFT знал точно, поэтому не стал искать сторонние библиотеки, а решил написать свою, которой пользуюсь и по сей день.
 
После того, как получилось инициализировать дисплей, стало понятно, что на Atmega16 сделать осциллограф не получится. Уж очень медленный он для дисплея такого размера. И что-то внутри подсказывало мне, что пора переходить на STM32, но оно же и останавливало меня. В общем, процесс обдумывания был недолгим и на али была заказана плата с STM32F103VET6 на борту. Но кроме причины описанной выше, была ещё одна причина перейти на

STM32 – встроенный 12 битный АЦП на 1Msps, который можно использовать для оцифровки сигнала.
 
К моему удивлению, после нескольких недель работы с STM32 стало понятно, что в них нет ничего сложного и я перестал понимать, почему не перешёл на них раньше. Перенести код, написанный для AVR, на STM32, не составило труда, но не давала покоя мысль о том, что мне необходимо, чтобы дисплей работал на максимальной скорости, а для этого надо было разобраться с FSMC. На самом деле и тут оказалось всё просто — это заняло у меня одни выходные. На этом подготовительные мероприятия с дисплеем были закончены и можно было переходить непосредственно к реализации осциллографа.
 
Первоочередной задачей было научиться выводить сигнал на дисплей, для этого накапливал необходимое количество выборок АЦП, затем выводил их на экран, заливал экран чёрным цветом и так по кругу. Кстати, уже тогда для сохранения данных в буфер использовал DMA.
 
Первый шаг был сделан и меня переполняла радость и гордость за проделанную работу. Далее, хотелось, чтобы синусоида не бежала, а стояла, для этого надо было научиться запускать преобразование АЦП по триггеру.
Сделать это можно с помощью обычного компаратора, как показано на схеме ниже.


Компаратор на ОУ.

На инвертирующий вход подаётся опорное напряжение, которое формируется с помощью ШИМ и RC цепочки. А на прямой вход подаётся сигнал, тот же что подаётся на вход АЦП.

Когда напряжение на прямом входе становится выше или ниже напряжения на инверсном входе изменяется полярность на выходе ОУ. Это изменение фиксирует вывод МК настроенный на внешнее прерывание. Изменяя активный фронт, для внешнего прерывания можно производить захват как по возрастающему, так и по спадающему фронту.

Далее, в прерывании включается DMA и работает до тех, пор пока буфер не заполнится. Да именно DMA, АЦП в моей реализации работает всегда. Резистор в обратной связи необходим для увеличения скорости нарастания, да и сам ОУ для этого дела, желательно выбрать побыстрее.
 
Спустя некоторое время мне на глаза попался обзор DSO 138 и из того же обзора я узнал, что его схема доступна в интернете и решил позаимствовать оттуда кусочек.


Фрагмент схемы DSO 138.

Что делает этот кусочек схемы?
Диапазон напряжений, с которыми может работать АЦП определяют уровни опорных напряжений(+VREF и -VREF), они не должны выходить за диапазон питания микроконтроллера. Нижнюю границу диапазона ограничивает 0 вольт, верхнюю — 3.3 вольта. Отсюда становится понятно что измерять отрицательные напряжения АЦП не может, а это необходимо.

Для того чтобы АЦП чувствовало отрицательные напряжения необходимо, чтобы в отсутствие сигнала на его вход подавалась половина опорного напряжения в нашем случае 1.6 вольта. В таком случаем при измерении отрицательного напряжения, например, минус 0,3 вольта, напряжение на входе АЦП уменьшится на 0,3 вольта и станет равно 1,6 — 0,3 = 1,3 вольта. Такое напряжение АЦП с лёгкостью оцифрует. Этот пример приблизителен потому, что не учитывает коэффициент усиления схемы(в данном случае мы приняли его за 1), зато нагляден.
 
Также хотелось обратить внимание, что питание ОУ двухполярное, это надо для того чтобы ОУ мог работать с отрицательным напряжением. В случае если питание ОУ однополярное(на один вывод питания подаётся 0, на второй 5 вольт)и на вход ему подать отрицательное напряжение, ОУ просто его не почувствует и сделать с ним ничего не сможет, вот так вот.

Для реализации двухполярного питания использовал две зарядки от телефона, соединив плюс одной с минусом другой, и принял потенциал этого соединения за точку отсчёта, то есть землю.
 
Схема была собрана на макетке и теперь осциллограф мог осуществлять захват по триггеру и измерять отрицательные напряжения, что делать дальше в плане железа у меня идей не было, поэтому перешёл к софту.
 
На тот момент времени у меня уже было чёткое понимание, что буфер должен быть кольцевой, а размер позаимствовал у DSO 138, то есть 4096 точек.

Для чего столько точек?
Такое количество точек, с помощью прореживания позволяет реализовать некоторые развёртки, которые нельзя реализовать аппаратно. У STM32 длительность преобразования составляет 12.5 цикла, для того чтобы найти период выборок надо к длительности преобразования прибавить значение, которое определяется битовым полем SMPx[2:0]. На данный момент у DSO 138 уменьшили размер буфера до 1024 точек, у меня же осталось 4096.

 
Так а для чего всё-таки кольцевой буфер?
Кольцевой буфер необходим для изменения соотношения количества пред и пост выборок, на быстрых развёртках. Предвыборки — выборки до срабатывания триггера, поствыборки — выборки после срабатывания триггера. Алгоритм работы следующий, после отрисовки осциллограммы:

  1. накапливаем нужное количество предвыборок;
  2. разрешаем внешние прерывания;
  3. МК начинает выполнение каких-то сторонних задач, до тех пор, пока не придёт сигнал внешнего прерывания.
 
По этому сигналу буфер дополняется поствыборками и вся осциллограмма выводится на дисплей.
Количество пред поствыборок пользователь может задавать только на быстрых развёртках, на медленных сразу после захвата точки отрисовываются на экране, минуя буфер. На самом деле конечно, буфер есть, потому что со времён стало понятно, что закрашивать после каждой отрисовки рабочую область неразумно, можно просто отрисовывать ту же осциллограмму только чёрным цветом и восстанавливать разметку, таким образом уменьшается время отрисовки одной осциллограммы и пропадает, такой неприятный эффект как мерцание.

Результат можно посмотреть на видео.

Что мог прототип осциллографа на тот момент?(а что он что-то мог?)
Можно было изменять время развёртки, выбрать тип триггера, выбрать активный фронт, изменять количество пред и пост выборок, изменять уровень триггера, также изменять базовый уровень.
Что касается типа триггера, в режиме авто, всё происходит также как в режиме норм за исключением того, что если в течение 100 миллисекунд не приходит сигнал, вызывающий внешнее прерывание, то происходит прерывание по таймеру и в нём заполняется буфер.
 
Для того чтобы реализовать остальные функции пришлось снова вернуться к железу. И думаю у многих возник вопрос, почему не заюзать всю схему от DSO138?
Всё просто, хотелось чтобы положение аттенюатора переключалось электроникой, а не переключателями и, главное, чтобы при смене открытого входа на закрытый и наоборот щёлкало реле))
В общем, передо мной стояла задача, найти аналоговую часть, которая бы соответствовала моим требованиям. На самом деле это задача непростая, если бы не одно но…

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

В итоге получилось сделать осциллограф обладающий следующими характеристиками:
Напряжение питания: 9 В
Потребляемый ток: 110 мА
Частота сэмплирования: 1 Мвыб/с
Аналоговая полоса пропускания: 0 — 200 КГц
Разрешение по вертикали: 12 бит
Максимальное входное напряжение: 50 В
Чувствительность по вертикали: 10 мВ/дел — 10 В/дел
Время горизонтальной развертки: 10 мкс/дел до 200 мс/дел
Входной импеданс: 1 МОм/20пФ
Режимы входа: DC, AC, земля
Режимы запуска — развертки: авто, нормальный, однократный

Результат можно посмотреть на видео.

habr.com

STM32F103C8T6 — делаем осциллограф. Часть 3 / Habr

Третья часть (первая и вторая) про то как я делаю осциллограф из отладочной платы ценой менее $3. Демонстрационное видео работы:


А описание некоторых ключевых особенностей под катом.

Аналоговая часть


Почти всё как было описано во второй части, кроме источника двухполярного питания. ОУ потребляют значительный ток (порядка 10 мА) и как не пытался схемами умножителей напряжения на диодах и конденсаторах получить приемлемых результатов — не удалось. Поэтому для положительного напряжения поставил вот такой модуль на основе МТ3608:

настроенный на 10 В выходного напряжения. А отрицательное напряжение получаю путём инвертирования положительного с помощью LT1054.

Про размер кода


В первой части я писал, что памяти потребляется очень много. Теперь я дошёл до того, что программа не влазит в память и изучил этот вопрос подробней.

CooCox CoIDE выводит информацию о размер программы в таком виде:

      text	   data	    bss	    dec	    hex	filename
     60224	   2500	  10540	  73264	  11e30	projectName.elf

где
  • text — размер сегмента с кодом, векторами прерываний и константами только на чтение;
  • data — размер сегмента с инициализированными не нулём переменными;
  • bss — размер сегмента с неинициализированными и инициализированными нулём переменными.

Вся программа занимает:

  • флеш — text + data + 10..50 байт
  • ОЗУ — data + bss + 10..50 байт

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

      text	   data	    bss	    dec	    hex	filename
       364	   1080	     32	   1476	    5c4	test-size.elf

Чтобы использовать макросы типа GPIO_BSRR_BS9 надо подключить файл stm32f10x.h.
Чтобы подключить файл stm32f10x.h надо в репозитоях добавить компонент STM32F10x_MD_STDLIB, который подтягивает за собой cmsis_core. В итоге для программы, записывающей одно значение в регистр получаем:
      text	   data	    bss	    dec	    hex	filename
      1316	   1104	     32	   2452	    994	test-size.elf

Далее меня интересуют функции типа sprintf и sscanf. Чтобы их использовать надо определить некоторые функции типа _sbrk и возможно некоторых других. Я взял готовый файл (есть в архиве с проектом). Добавляем 1 вызов sscanf и получаем:
Попробуйте угадать сколько, прежде чем смотреть!
      text	   data	    bss	    dec	    hex	filename
     39312	   2264	     96	  41672	   a2c8	test-size.elf

41 кБ флеша! Больше половины, того, что есть в контроллере!
В рабочей же прошивке при использовании printf добавление sscanf увеличивает потребление флеша на 13.2 кБ. В итоге от sscanf отказался, а команды от ПК стал парсить менее ресурсоёмким методом.
Отказ же от printf позволяет сэкономить ещё 8.3 кБ.

Режимы работы


Реализовал 3 режима по принципу действия: непрерывный, пакетный и логический и 3 по количеству каналов: 1, 2 и 4-х канальный.
МК имеет 9 аналоговых входов, но я не представляю когда мне может понадобиться больше 4-х каналов.

Непрерывный


Тут всё просто: в главном цикле МК считываем данные АЦП и передаём их на ПК, где можем строить непрерывный график. Недостаток — ограничение скорости со стороны канала МК -> ПК. Чтобы его обойти реализовал ещё 2 режима.

Пакетный


В этом режиме МК вначале набирает данные, потом пачкой передаёт на ПК. Опционально его можно разгонять. Про разгон подробно писал в предыдущих частях.

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

В отличие от проекта baghear у меня триггер программный. Преимущества такого решения:

  • Меньше деталей, а значит меньше цена и проще монтаж;
  • Возможность в будущем реализовать более сложные триггеры, а не просто «сигнал в A канале стал больше Х».

В одноканальном режиме оба АЦП по очереди преобразуют значение одного канала.
В двухканальном — каждый АЦП преобразует свой канал запускаясь одновременно с другим.
В 4-х канальном — у каждого АЦП есть 2 канала, которые он преобразует. Старт обоих АЦП одновременный.
Очевидно, что скорость частота преобразования канала обратнопропорциональна количеству каналов.

Логический анализатор


Самый быстрый режим. Примерно 20 MSPS на каждом канале. Самый быстрый код для этого режима выглядит так:
u32 i = 0;
dataBuffer.u8[i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;

и так далее на весь буфер.
Значение переменной i в этом случае вычисляются на этапе компиляции и в итоге из dataBuffer.u8[++i] = GPIOA->IDR; получается всего 2 операции — загрузить данные в регистр из порта и сохранить данные в память по заранее посчитанному адресу. Никакими циклами такой производительности достичь не получилось.

Программа для ПК


Главные, на мой взгляд, измение — переход на OpenGL. С ним графики рисовать стало проще (для меня это оказалось неожиданно, но там всё действительно просто и кратко!), рисуются они быстрее и получаются гораздо красивей, чем были раньше.

Итог


Проект не завершён, есть глюки, допиливать ещё много чего, но каких-то прорывов уже не предвидится. Для более быстрых систем нужно другое железо, например, отдельный АЦП + ПЛИС + память — а это уже будет гораздо дороже и сложнее монтировать.

Почитав комментарии к статье «История одного осциллографа на stm32» сразу отвечу на некоторые вопросы:

  • Дисплей прикручивать не собираюсь т.к.:
    • Он стоит денег, а комп есть.
    • По качеству будет хуже, чем на большом экране ПК.
    • Создавать и изменять пользовательский интерфейс на C# проще, чем паять и перепаивать.

  • Я не планирую его доводить коммерческого продукта и продавать.
  • Делал для 2-х целей: освоить МК и сделать себе цифровой осциллограф.

Архив с проектом
Если у кого появятся вопросы, а тут не зарегистрированы, пишите в почту: adefikux на gmail точка com.

habr.com

USB осциллограф своими руками

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

  • Частота дискретизации (семплирование) — 461 kSps
  • Входное напряжение — 6,6 В.
  • Входное сопротивление — 20 кОм.

Как видим, осциллограф имеет нестандартное входное сопротивление, поэтому стандартные осциллографические щупы к нему не подойдут и для измерения напряжений свыше 6,6 В придется делать делитель с согласованием именно на 20 кОм. Еще небольшое пояснение по поводу частоты дискретизации. Многие ошибочно полагают что это и есть полоса пропускания. В действительности это вовсе не так. 461 kSps означает что осциллограф за одну секунду делает 461 тысячу замеров. Если подать на его вход сигнал, к примеру 1 кГц (период T=1/F; T=1 миллисекунда). За период в 1 миллисекунду осциллограф сделает 461000*0,001=461 измерение. Будем говорить что на период приходится 461 точка. Этого количества точек более чем достаточно чтобы четко отрисовать сигнал. Но если мы подадим на вход сигнал 200 кГц, период которого составляет 5 микросекунд, то уже на этот период мы получим 2,3 точку.  Из 2 точек невозможно построить сигнал и оценить его параметры. Минимально необходимое число точек на период должно быть не менее 20. Поэтому максимальная частота при которой этим осциллографом можно будет рассмотреть сигнал будет 461/20= 23,5 кГц. Для звукового диапазона вполне подойдет. И не стоит забывать что это устройство не имеет гальванической развязки!!! Будьте внимательны если будете ремонтировать импульсные блоки питания!

Схема осциллографа представлена ниже. Оригинал схемы, печатной платы и прошивку вы можете скачать в конце статьи.

Как видно, схема состоит из одного процессора и его обвязки. Здесь особо нечего пояснять. Скажу только что на плате разведен только UART интерфейс для прошивки процессора. Я все же рекомендую развести SWD интерфейс и прошивать через него с помощью программатора STLINK. Это проще и быстрее. Но можно и так как на плате с помощью UART. Я вкратце опишу и тот и другой вариант. Для прошивки через UART нам потребуется любой переходник с USB в UART, из полно в продаже и стоят они не дорого. Подключаем переходник к плате по 3-х проводной шине RX, TX, GND. Затем скачиваем и устанавливаем программу STM Flash Loader Demo. Переводим плату в режим Boot. Для этого нажимаем и удерживаем кнопку Boot при нажатии кнопки Reset. Затем заходим в программу и выполняем пошаговые действия: выбираем номер COM порта, ожидаем соединения с платой, выбираем файл прошивки, ждем окончания процесса прошивки, закрываем прогу отключаем UART, и снимаем питание с платы. Теперь вариант с SWD. Подключаем программатор по 4 проводам: POWER, SWCLK, SWDIO, GND. (При этом питание на плату поступает с программатора). Качаем и ставим программу STM32 ST-Link Utility. При запуске программы она сама определит контроллер, вам останется лишь выбрать файл прошивки и запустить процесс прошивки.

И еще одно немаловажное замечание. Перед сборкой устройства, установите программную оболочку осциллографа на STM32 на свой ПК. Убедитесь что программа в принципе запускается. Были случаи когда программа просто не хотела запускаться на некоторых ПК и ноутбуках. С чем это связано — непонятно.

СКАЧАТЬ ПРОГРАММУ MINISCOPE V4

СКАЧАТЬ ДРАЙВЕР ДЛЯ MINISCOPE

СКАЧАТЬ ПРОШИВКУ ДЛЯ MINISCOPE

СКАЧАТЬ СХЕМУ И ПЛАТУ

elschemo.ru

Двухканальный USB осциллограф на STM32 – Miniscope v2c « схемопедия

Ниже представлен проект недорого USB осциллографа с применением STM32 микроконтроллера. Особенности устройства:

– использование очень дешевых STM32F103 микроконтроллеров в LQFP48 корпусе.

– односторонняя печатная плата, удобная для изготовления в домашних условиях.  

– выборка 2x461kSps (2x300kSps в старых версиях), 8 бит, передача данных по USB в реальном времени.

– прошивка по UART.

– диапазон рабочих напряжений 0 – 6.6 Вольт.  Нестандартное входное сопротивление 20 кОм (к несчастью, большее значение вызывает помехи на АЦП. Возможно, это можно исправить использованием ОУ. Обратите внимание: сопротивление может быть увеличено при использовании новой прошивки, которая использует отдельный АЦП для каждого канала).

Сигнал 300 мВ снятый при помощи miniscope v4:

Общая стоимость компонентов не превысила 10$.

Принципиальная схема USB-осциллографа:

Печатная плата – односторонняя, размер 66мм x 36мм.

Среда разработки

Для разработки miniscope v2 необходимо было выбрать среду разработки для STM микроконтроллеров. В этом файле лежат примеры проекта для IAR, Keil, RIDE, HiTop и TrueSTUDIO. К сожалению, не один из них мне не подошел. RIDE и HiTop требуют покупки лицензии через 7 дней. Пробные версии IAR и Keil имеют ограничение на размер кода и забирают очень много дискового пространства. То же самое с TrueSTUDIO.

В результате я выбрал CooCox, дистрибутив которого весит 115 МБ и около ~ 800 МБ после установки и распространяется бесплатно.

Прошивка микроконтроллера

На плате нет JTAG/SWD разъема, так как прошивка должна быть загружена по UART. Чтобы войти в режим загрузки, нажмите и удерживайте кнопку BOOT при нажатии кнопки RESET. Программа STM "Flash Loader Demo" без проблем работает с USB-UART переходником. Нормальное напряжение на выводах микроконтроллера 5В, поэтому можно использовать 5 или 3.3В RS232-UART/USB-UART переходник.

Кнопка RESET может быть удалена – микроконтроллер переходит в режим загрузки при нажатой кнопке BOOT если USB подключен.

Так как USB подключено без 1.5 кОм подтягивающих резисторов, его необходимо заново подключить после прошивки.  

Проект для тестирования микроконтроллера и зуммер: stm32scopeTest.7z

Советы по передаче данных по USB

Используйте CDC в качестве шаблона. Есть две конечных точки BULK. Для повышения скорости CDC потребуются небольшие изменения.

1. Уменьшите значение VCOMPORT_IN_FRAME_INTERVAL. Я не уверен, что значение = 1 подходит при двунаправленной передаче, поэтому я поставил значение = 2.

2. Увеличение значения USART_RX_DATA_SIZE. Я использовал 8192 байт (2 х 4 Кб), но я думаю, что существенной разницы при использовании  4096 байт.

3. Изменение Handle_USBAsynchXfer, т.к. он не будет передавать данные, если USART_Rx_Buffer будет полный. Таким образом, после каждого номера SOF будет отправлен максимальный по номеру байт.

Убедитесь, что на ПК приложение постоянно готово к приему данных. Убедитесь, что приоритет чтения для него выше,  чем у других приложений. Я использовал libusb, поэтому я использовал сочетания usb_submit_async / usb_reap_async для задания очереди запросов чтения.

Я не интересовался высокой скорость передачи данных с ПК, поэтому у меня нет советов по этому поводу. Miniscope v2c оправляет данные на ПК с максимально возможной скоростью. Данные отправляемые с ПК незначительны (ID запроса, изменение аналогового усиления).

Скачать файлы проекта

Оригинал статьи на английском языке (перевод: Александр Касьянов для сайта cxem.net)

shemopedia.ru

STM32F103C8T6 — делаем осциллограф. Часть 3

Третья часть (первая и вторая) про то как я делаю осциллограф из отладочной платы ценой менее $3. Демонстрационное видео работы:

А описание некоторых ключевых особенностей под катом.

Аналоговая чать

Почти всё как было описано во второй части, кроме источника двухполярного питания. ОУ потребляют значительный ток (порядка 10 мА) и как не пытался схемами умножителей напряжения на диодах и конденсаторах получить приемлемых результатов — не удалось. Поэтому для положительного напряжения поставил вот такой модуль на основе МТ3608:

настроенный на 10 В выходного напряжения. А отрицательное напряжение получаю путём инвертирования положительного с помощью LT1054.

Про размер кода

В первой части я писал, что памяти потребляется очень много. Теперь я дошёл до того, что программа не влазит в память и изучил этот вопрос подробней.

CooCox CoIDE выводит информацию о размер программы в таком виде:

      text	   data	    bss	    dec	    hex	filename
     60224	   2500	  10540	  73264	  11e30	projectName.elf

где

  • text — размер сегмента с кодом, векторами прерываний и константами только на чтение;
  • data — размер сегмента с инициализированными не нулём переменными;
  • bss — размер сегмента с неинициализированными и инициализированными нулём переменными.

Вся программа занимает:

  • флеш — text + data + 10..50 байт
  • ОЗУ — data + bss + 10..50 байт

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

      text	   data	    bss	    dec	    hex	filename
       364	   1080	     32	   1476	    5c4	test-size.elf

Чтобы использовать макросы типа GPIO_BSRR_BS9 надо подключить файл stm32f10x.h.
Чтобы подключить файл stm32f10x.h надо в репозитоях добавить компонент STM32F10x_MD_STDLIB, который подтягивает за собой cmsis_core. В итоге для программы, записывающей одно значение в регистр получаем:

      text	   data	    bss	    dec	    hex	filename
      1316	   1104	     32	   2452	    994	test-size.elf

Далее меня интересуют функции типа sprintf и sscanf. Чтобы их использовать надо определить некоторые функции типа _sbrk и возможно некоторых других. Я взял готовый файл (есть в архиве с проектом). Добавляем 1 вызов sscanf и получаем:

Попробуйте угадать сколько, прежде чем смотреть!
      text	   data	    bss	    dec	    hex	filename
     39312	   2264	     96	  41672	   a2c8	test-size.elf

41 кБ флеша! Больше половины, того, что есть в контроллере!
В рабочей же прошивке при использовании printf добавление sscanf увеличивает потребление флеша на 13.2 кБ. В итоге от sscanf отказался, а команды от ПК стал парсить менее ресурсоёмким методом.
Отказ же от printf позволяет сэкономить ещё 8.3 кБ.

Режимы работы

Реализовал 3 режима по принципу действия: непрерывный, пакетный и логический и 3 по количеству каналов: 1, 2 и 4-х канальный.
МК имеет 9 аналоговых входов, но я не представляю когда мне может понадобиться больше 4-х каналов.

Непрерывный

Тут всё просто: в главном цикле МК считываем данные АЦП и передаём их на ПК, где можем строить непрерывный график. Недостаток — ограничение скорости со стороны канала МК -> ПК. Чтобы его обойти реализовал ещё 2 режима.

Пакетный

В этом режиме МК вначале набирает данные, потом пачкой передаёт на ПК. Опционально его можно разгонять. Про разгон подробно писал в предыдущих частях.

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

В отличие от проекта baghear у меня триггер программный. Преимущества такого решения:

  • Меньше деталей, а значит меньше цена и проще монтаж;
  • Возможность в будущем реализовать более сложные триггеры, а не просто «сигнал в A канале стал больше Х».

В одноканальном режиме оба АЦП по очереди преобразуют значение одного канала.
В двухканальном — каждый АЦП преобразует свой канал запускаясь одновременно с другим.
В 4-х канальном — у каждого АЦП есть 2 канала, которые он преобразует. Старт обоих АЦП одновременный.
Очевидно, что скорость частота преобразования канала обратнопропорциональна количеству каналов.

Логический анализатор

Самый быстрый режим. Примерно 20 MSPS на каждом канале. Самый быстрый код для этого режима выглядит так:

u32 i = 0;
dataBuffer.u8[i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;

и так далее на весь буфер.
Значение переменной i в этом случае вычисляются на этапе компиляции и в итоге из dataBuffer.u8[++i] = GPIOA->IDR; получается всего 2 операции — загрузить данные в регистр из порта и сохранить данные в память по заранее посчитанному адресу. Никакими циклами такой производительности достичь не получилось.

Программа для ПК

Главные, на мой взгляд, измение — переход на OpenGL. С ним графики рисовать стало проще (для меня это оказалось неожиданно, но там всё действительно просто и кратко!), рисуются они быстрее и получаются гораздо красивей, чем были раньше.

Итог

Проект не завершён, есть глюки, допиливать ещё много чего, но каких-то прорывов уже не предвидится. Для более быстрых систем нужно другое железо, например, отдельный АЦП + ПЛИС + память — а это уже будет гораздо дороже и сложнее монтировать.

Почитав комментарии к статье «История одного осциллографа на stm32» сразу отвечу на некоторые вопросы:

  • Дисплей прикручивать не собираюсь т.к.:
    • Он стоит денег, а комп есть.
    • По качеству будет хуже, чем на большом экране ПК.
    • Создавать и изменять пользовательский интерфейс на C# проще, чем паять и перепаивать.
  • Я не планирую его доводить коммерческого продукта и продавать.
  • Делал для 2-х целей: освоить МК и сделать себе цифровой осциллограф.

Архив с проектом
Если у кого появятся вопросы, а тут не зарегистрированы, пишите в почту: adefikux на gmail точка com.

Автор: xedas

Источник

www.pvsm.ru

История одного осциллографа на stm32

Чуть больше года назад, мне в голову пришла мысль о том, что хорошо бы было сделать осциллограф. Тогда мне хотелось, чтобы это было независимое устройство с собственным TFT дисплеем, да и вообще, идея разобраться с TFT дисплеями, мне казалась очень перспективной. Спустя некоторое время на али был заказан TFT размером 3.2 дюйма с драйвером SSD1289.

На тот момент у меня уже был опыт программирования микроконтроллеров AVR, поэтому решил запустить дисплей на моём любимом Atmega16. Дойдёт ли дело до создания осциллографа тогда ещё не знал, но то что буду в своих проектах использовать TFT знал точно, поэтому не стал искать сторонние библиотеки, а решил написать свою, которой пользуюсь и по сей день.
 
После того, как получилось инициализировать дисплей, стало понятно, что на Atmega16 сделать осциллограф не получится. Уж очень медленный он для дисплея такого размера. И что-то внутри подсказывало мне, что пора переходить на STM32, но оно же и останавливало меня. В общем, процесс обдумывания был недолгим и на али была заказана плата с STM32F103VET6 на борту. Но кроме причины описанной выше, была ещё одна причина перейти на STM32 – встроенный 12 битный АЦП на 1Msps, который можно использовать для оцифровки сигнала.
 
К моему удивлению, после нескольких недель работы с STM32 стало понятно, что в них нет ничего сложного и я перестал понимать, почему не перешёл на них раньше. Перенести код, написанный для AVR, на STM32, не составило труда, но не давала покоя мысль о том, что мне необходимо, чтобы дисплей работал на максимальной скорости, а для этого надо было разобраться с FSMC. На самом деле и тут оказалось всё просто — это заняло у меня одни выходные. На этом подготовительные мероприятия с дисплеем были закончены и можно было переходить непосредственно к реализации осциллографа.
 
Первоочередной задачей было научиться выводить сигнал на дисплей, для этого накапливал необходимое количество выборок АЦП, затем выводил их на экран, заливал экран чёрным цветом и так по кругу. Кстати, уже тогда для сохранения данных в буфер использовал DMA.
 
Первый шаг был сделан и меня переполняла радость и гордость за проделанную работу. Далее, хотелось, чтобы синусоида не бежала, а стояла, для этого надо было научиться запускать преобразование АЦП по триггеру.
Сделать это можно с помощью обычного компаратора, как показано на схеме ниже.

Компаратор на ОУ.

На инвертирующий вход подаётся опорное напряжение, которое формируется с помощью ШИМ и RC цепочки. А на прямой вход подаётся сигнал, тот же что подаётся на вход АЦП.

Когда напряжение на прямом входе становится выше или ниже напряжения на инверсном входе изменяется полярность на выходе ОУ. Это изменение фиксирует вывод МК настроенный на внешнее прерывание. Изменяя активный фронт, для внешнего прерывания можно производить захват как по возрастающему, так и по спадающему фронту.

Далее, в прерывании включается DMA и работает до тех, пор пока буфер не заполнится. Да именно DMA, АЦП в моей реализации работает всегда. Резистор в обратной связи необходим для увеличения скорости нарастания, да и сам ОУ для этого дела, желательно выбрать побыстрее.
 
Спустя некоторое время мне на глаза попался обзор DSO 138 и из того же обзора я узнал, что его схема доступна в интернете и решил позаимствовать оттуда кусочек.

Фрагмент схемы DSO 138.

Что делает этот кусочек схемы?
Диапазон напряжений, с которыми может работать АЦП определяют уровни опорных напряжений(+VREF и -VREF), они не должны выходить за диапазон питания микроконтроллера. Нижнюю границу диапазона ограничивает 0 вольт, верхнюю — 3.3 вольта. Отсюда становится понятно что измерять отрицательные напряжения АЦП не может, а это необходимо.

Для того чтобы АЦП чувствовало отрицательные напряжения необходимо, чтобы в отсутствие сигнала на его вход подавалась половина опорного напряжения в нашем случае 1.6 вольта. В таком случаем при измерении отрицательного напряжения, например, минус 0,3 вольта, напряжение на входе АЦП уменьшится на 0,3 вольта и станет равно 1,6 — 0,3 = 1,3 вольта. Такое напряжение АЦП с лёгкостью оцифрует. Этот пример приблизителен потому, что не учитывает коэффициент усиления схемы(в данном случае мы приняли его за 1), зато нагляден.
 
Также хотелось обратить внимание, что питание ОУ двухполярное, это надо для того чтобы ОУ мог работать с отрицательным напряжением. В случае если питание ОУ однополярное(на один вывод питания подаётся 0, на второй 5 вольт)и на вход ему подать отрицательное напряжение, ОУ просто его не почувствует и сделать с ним ничего не сможет, вот так вот.

Для реализации двухполярного питания использовал две зарядки от телефона, соединив плюс одной с минусом другой, и принял потенциал этого соединения за точку отсчёта, то есть землю.
 
Схема была собрана на макетке и теперь осциллограф мог осуществлять захват по триггеру и измерять отрицательные напряжения, что делать дальше в плане железа у меня идей не было, поэтому перешёл к софту.
 
На тот момент времени у меня уже было чёткое понимание, что буфер должен быть кольцевой, а размер позаимствовал у DSO 138, то есть 4096 точек.

Для чего столько точек?
Такое количество точек, с помощью прореживания позволяет реализовать некоторые развёртки, которые нельзя реализовать аппаратно. У STM32 длительность преобразования составляет 12.5 цикла, для того чтобы найти период выборок надо к длительности преобразования прибавить значение, которое определяется битовым полем SMPx[2:0]. На данный момент у DSO 138 уменьшили размер буфера до 1024 точек, у меня же осталось 4096.
 
Так а для чего всё-таки кольцевой буфер?
Кольцевой буфер необходим для изменения соотношения количества пред и пост выборок, на быстрых развёртках. Предвыборки — выборки до срабатывания триггера, поствыборки — выборки после срабатывания триггера. Алгоритм работы следующий, после отрисовки осциллограммы:

  1. накапливаем нужное количество предвыборок;
  2. разрешаем внешние прерывания;
  3. МК начинает выполнение каких-то сторонних задач, до тех пор, пока не придёт сигнал внешнего прерывания.

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

Результат можно посмотреть на видео.

Что мог прототип осциллографа на тот момент?(а что он что-то мог?)
Можно было изменять время развёртки, выбрать тип триггера, выбрать активный фронт, изменять количество пред и пост выборок, изменять уровень триггера, также изменять базовый уровень.
Что касается типа триггера, в режиме авто, всё происходит также как в режиме норм за исключением того, что если в течение 100 миллисекунд не приходит сигнал, вызывающий внешнее прерывание, то происходит прерывание по таймеру и в нём заполняется буфер.
 
Для того чтобы реализовать остальные функции пришлось снова вернуться к железу. И думаю у многих возник вопрос, почему не заюзать всю схему от DSO138?
Всё просто, хотелось чтобы положение аттенюатора переключалось электроникой, а не переключателями и, главное, чтобы при смене открытого входа на закрытый и наоборот щёлкало реле))
В общем, передо мной стояла задача, найти аналоговую часть, которая бы соответствовала моим требованиям. На самом деле это задача непростая, если бы не одно но…
В один прекрасный момент я вспомнил, что у меня есть осциллограф, а его разработчик около года назад отвечал мне на вопросы по его использованию в skype, в общем, решил ему написать.
После того как рассказал ему о своих планах сделать осциллограф он предложил мне несколько вариантов аналоговой части, я выбрал наиболее мне понятную схему, задал ему имеющиеся у меня вопросы и принялся за реализацию. Через несколько месяцев осциллограф был готов.

В итоге получилось сделать осциллограф обладающий следующими характеристиками:
Напряжение питания: 9 В
Потребляемый ток: 110 мА
Частота сэмплирования: 1 Мвыб/с
Аналоговая полоса пропускания: 0 — 200 КГц
Разрешение по вертикали: 12 бит
Максимальное входное напряжение: 50 В
Чувствительность по вертикали: 10 мВ/дел — 10 В/дел
Время горизонтальной развертки: 10 мкс/дел до 200 мс/дел
Входной импеданс: 1 МОм/20пФ
Режимы входа: DC, AC, земля
Режимы запуска — развертки: авто, нормальный, однократный

Результат можно посмотреть на видео.

Автор: baghear

Источник

www.pvsm.ru

STM32F103C8T6 — делаем осциллограф. Часть 3 / Geektimes

Третья часть (первая и вторая) про то как я делаю осциллограф из отладочной платы ценой менее $3. Демонстрационное видео работы:

А описание некоторых ключевых особенностей под катом.

Аналоговая чать


Почти всё как было описано во второй части, кроме источника двухполярного питания. ОУ потребляют значительный ток (порядка 10 мА) и как не пытался схемами умножителей напряжения на диодах и конденсаторах получить приемлемых результатов — не удалось. Поэтому для положительного напряжения поставил вот такой модуль на основе МТ3608:

настроенный на 10 В выходного напряжения. А отрицательное напряжение получаю путём инвертирования положительного с помощью LT1054.

Про размер кода


В первой части я писал, что памяти потребляется очень много. Теперь я дошёл до того, что программа не влазит в память и изучил этот вопрос подробней.

CooCox CoIDE выводит информацию о размер программы в таком виде:

      text	   data	    bss	    dec	    hex	filename
     60224	   2500	  10540	  73264	  11e30	projectName.elf

где
  • text — размер сегмента с кодом, векторами прерываний и константами только на чтение;
  • data — размер сегмента с инициализированными не нулём переменными;
  • bss — размер сегмента с неинициализированными и инициализированными нулём переменными.

Вся программа занимает:

  • флеш — text + data + 10..50 байт
  • ОЗУ — data + bss + 10..50 байт

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

      text	   data	    bss	    dec	    hex	filename
       364	   1080	     32	   1476	    5c4	test-size.elf

Чтобы использовать макросы типа GPIO_BSRR_BS9 надо подключить файл stm32f10x.h.
Чтобы подключить файл stm32f10x.h надо в репозитоях добавить компонент STM32F10x_MD_STDLIB, который подтягивает за собой cmsis_core. В итоге для программы, записывающей одно значение в регистр получаем:
      text	   data	    bss	    dec	    hex	filename
      1316	   1104	     32	   2452	    994	test-size.elf

Далее меня интересуют функции типа sprintf и sscanf. Чтобы их использовать надо определить некоторые функции типа _sbrk и возможно некоторых других. Я взял готовый файл (есть в архиве с проектом). Добавляем 1 вызов sscanf и получаем:
Попробуйте угадать сколько, прежде чем смотреть!
      text	   data	    bss	    dec	    hex	filename
     39312	   2264	     96	  41672	   a2c8	test-size.elf

41 кБ флеша! Больше половины, того, что есть в контроллере!
В рабочей же прошивке при использовании printf добавление sscanf увеличивает потребление флеша на 13.2 кБ. В итоге от sscanf отказался, а команды от ПК стал парсить менее ресурсоёмким методом.
Отказ же от printf позволяет сэкономить ещё 8.3 кБ.

Режимы работы


Реализовал 3 режима по принципу действия: непрерывный, пакетный и логический и 3 по количеству каналов: 1, 2 и 4-х канальный.
МК имеет 9 аналоговых входов, но я не представляю когда мне может понадобиться больше 4-х каналов.

Непрерывный


Тут всё просто: в главном цикле МК считываем данные АЦП и передаём их на ПК, где можем строить непрерывный график. Недостаток — ограничение скорости со стороны канала МК -> ПК. Чтобы его обойти реализовал ещё 2 режима.

Пакетный


В этом режиме МК вначале набирает данные, потом пачкой передаёт на ПК. Опционально его можно разгонять. Про разгон подробно писал в предыдущих частях.

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

В отличие от проекта @baghear у меня триггер программный. Преимущества такого решения:

  • Меньше деталей, а значит меньше цена и проще монтаж;
  • Возможность в будущем реализовать более сложные триггеры, а не просто «сигнал в A канале стал больше Х».

В одноканальном режиме оба АЦП по очереди преобразуют значение одного канала.
В двухканальном — каждый АЦП преобразует свой канал запускаясь одновременно с другим.
В 4-х канальном — у каждого АЦП есть 2 канала, которые он преобразует. Старт обоих АЦП одновременный.
Очевидно, что скорость частота преобразования канала обратнопропорциональна количеству каналов.

Логический анализатор


Самый быстрый режим. Примерно 20 MSPS на каждом канале. Самый быстрый код для этого режима выглядит так:
u32 i = 0;
dataBuffer.u8[i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;
dataBuffer.u8[++i] = GPIOA->IDR;

и так далее на весь буфер.
Значение переменной i в этом случае вычисляются на этапе компиляции и в итоге из dataBuffer.u8[++i] = GPIOA->IDR; получается всего 2 операции — загрузить данные в регистр из порта и сохранить данные в память по заранее посчитанному адресу. Никакими циклами такой производительности достичь не получилось.

Программа для ПК


Главные, на мой взгляд, измение — переход на OpenGL. С ним графики рисовать стало проще (для меня это оказалось неожиданно, но там всё действительно просто и кратко!), рисуются они быстрее и получаются гораздо красивей, чем были раньше.

Итог


Проект не завершён, есть глюки, допиливать ещё много чего, но каких-то прорывов уже не предвидится. Для более быстрых систем нужно другое железо, например, отдельный АЦП + ПЛИС + память — а это уже будет гораздо дороже и сложнее монтировать.

Почитав комментарии к статье «История одного осциллографа на stm32» сразу отвечу на некоторые вопросы:

  • Дисплей прикручивать не собираюсь т.к.:
    • Он стоит денег, а комп есть.
    • По качеству будет хуже, чем на большом экране ПК.
    • Создавать и изменять пользовательский интерфейс на C# проще, чем паять и перепаивать.

  • Я не планирую его доводить коммерческого продукта и продавать.
  • Делал для 2-х целей: освоить МК и сделать себе цифровой осциллограф.

Архив с проектом
Если у кого появятся вопросы, а тут не зарегистрированы, пишите в почту: adefikux на gmail точка com.

archive.fo

alexxlab

leave a Comment