Программа для индикатора типа "шкала светодиодов" |
Автор ARV | ||||||||||||||||
12.04.2008 г. | ||||||||||||||||
Микросхемы шкальных индикаторов типа К1003ПП1...ПП3 и их импортные аналоги известны всем, и, казалось бы, полностью должны удовлетворить любые потребности любителей мастерить индикаторы в виде линейки светодиодов. Ан нет, иной раз хочется чего-то более изысканного, например, побольше светодиодов в шкале или не одну, а две а то и три шкалы, или индикацию не просто в виде столбика или точки, а более экзотическую - в виде 2-х или 3-х точек... В общем, всегда что-то найдется такое. Для чего эти микросхемы не очень подходят. Какой же выход? Буду не оригинален - тут поможет микроконтроллер, как обычно, семейства AVR. Опубликовать схему и прошивку - дело плевое, однако это удовлетворит лишь небольшую группу любителей, а остальные будут недовольны - то одно в прошивке или схеме не так, то другое... Это мы проходили, и знаем лекарство: не нравится готовое - сделай сам! Но знаем мы и другое: знаешь сам - научи другого! И потому эта статья будет посвящена теории и практике написания программы для шкального индикатора. Программа будет писаться на Си (советую запасаться компиляторами) WinAVR, с минимальными переделками пойдет и на других диалектах (CodeVision и др.). Основная цель - обучение характерным приемам, освоив которые каждый желающий должен быть в состоянии разработать индикатор под свои конкретные нужды, во всяком случае, я в это искренне верю. Очень рекомендую сначала прочитать все до самого конца, и только потом начинать что-то делать в AVR Studio. Итак, первый этап - постановка задачи.Начинать будем с простого - сделаем шкалу из двух линеек по 8 светодиодов, которые «столбиком» будут показывать уровень входного напряжения на двух входах МК, т.е. сделаем «стерео» индикатор. Обязательно применим динамическую индикацию, т.к. иначе выводов МК может и не хватить (пока не задумываемся о конкретном типе МК). Сначала добьемся линейного соответствия количества светящихся светодиодов и входного напряжения, т.е. наша шкала будет иметь динамический диапазон примерно 18 dB. Напомню, что динамический диапазон определяется как 20log(dU), где dU - отношение максимального уровня индикации к минимальному. Так как у нас всего 8 светодиодов в шкале, то dU=8, это очевидно. Кстати, если шкала будет индицировать мощность, то динамический диапазон автоматически уменьшится вдвое... Этап второй - схема.
Пока что схему нарисуем безотносительно к конкретным выводам конкретного МК - будем пробовать сделать универсальную программу. Надеюсь, когда потребуется, ни у кого из читателей не возникнет проблемы «распределить» виртуальные названия выводов по «реальным» ножкам выбранного контроллера. И с учетом этого наша схема получается такой, как на рисунке 1. Конденсатор на сигнале AREF может и отсутствовать - это уже будет определяться конкретными требованиями к конкретному МК. Так же могут появиться некоторые дополнительные соединения (например, для atmega8 требуется всегда соединять вместе выводы GND и AGND, а так же VCC и AVCC). Эти нюансы в статье не рассматриваются ввиду их очевидности. На аналоговые входы AIN0 и AIN1 будут подаваться наши измеряемые сигналы: это должно быть постоянное напряжение не более 2,5В. Подать такие сигналы лучше всего с эмиттерного повторителя, т.к. он имеет высокое входное и низкое выходное сопротивление, что очень удачно вписывается в концепцию как со стороны «входа», так и «выхода». Однако, варианты возможны и в этом случае, потому снова отбросим нюансы источника сигнала и не будем о них задумываться, главное для нас - программа. Этап третий - алгоритм работы.Не будем рисовать диаграммы и структурные схемы, а обдумаем алгоритм программы «на словах». Как обычно, вначале нужно проинициализировать все необходимые периферийные устройства МК. Динамическая индикация проще всего и наиболее красиво реализуется в виде фонового процесса - по прерыванию от таймера. Процедура обработки этого прерывания должна постоянно обновлять уровни на портах (согласно принятым на схеме обозначениям) LEDS, СОМ0 и СОМ1, обеспечивая поочередное включение восьмерок светодиодов. В основном цикле наша программа должна постоянно и непрерывно осуществлять измерение при помощи АЦП уровни на входах AIN0 и AIN1, обрабатывать их по определенному алгоритму (например. проводить цифровую фильтрацию или масштабирование и т.п.), и подготавливать данные для вывода на шкалы - те самые, которые будут использованы в функции динамической индикации. Вот, собственно, и все... Этап четвертый - написание программы.Будем писать программу по блочно-модульному принципу, т.е. она будет состоять из нескольких файлов - так легче продвигаться от «крупного» к «мелкому», ведь обучаться легче постепенно вникая в мелочи, чем сразу погрузиться в их пучину, не так ли? После того, как все будет готово, никто не запретит вам объединить все файлы в один, если по каким-то причинам вам покажется это более удобным. Хотя блочно-модульный принцип позволяет лече контролировать правильность программописания - можно компилировать каждый модуль в отдельности (не делая сборку проекта) и сразу корректировать ошибки. Создаем проект WinAVR GCC в AVR Studio, указываем папку проекта и название главного файла нашего проекта (например, SCALE.C), затем выбираем из списка желаемый тип МК. Затем в опциях проекта указываем желаемую тактовую частоту. В файл SCALE.C вводим следующее:
Этот текст программы на 100% отражает ранее описанный алгоритм «в общих чертах». Если создать пока пустой файл SCALE.H - программу можно откомпилировать и убедиться, что ошибок в ней нет. Этап четвертый, шаг второй - работа с АЦП.Возможно, для столь простого проекта это и не очень рационально, но все же создадим отдельный файл ADC.C, подключим его к нашему проекту и введем в него следующее:
Мы сделали единственную функцию, которая на входе получает номер канала АЦП chanel (беззнаковый байт), а возвращает результат нескольких замеров сигнала по этому самому каналу. Количество замеров определяется константой ADC_AVERAGE, которая должна быть описана в файле SCALE.H, например так:
Тут есть небольшой нюанс: файл SCALE.H подключается у нас в различных файлах, потому, чтобы не было конфликтов и ошибок, этот файл должен иметь особую структуру:
Вначале директивой условной компиляции мы проверяем: описана ли константа _MY_ENTRY_SCALE ? Если она описана - весь условный блок компилятором игнорируется, что равносильно пустому содержимому файла. А вот если константа не описана (это может быть только в том случае, если файл SCALE.H встретился компилятору впервые), то первым делом эта константа описывается (значение 1, но может быть абсолютно любое, даже отсутствовать), а потом уже обрабатываются все прочие строки файла. Это обычная практика в Си - хотите научиться хорошо писать на Си - привыкайте! Я не буду больше упоминать это «обрамление», но помните, что все описания в нашем проекте должны находиться внутри него. Еще в функции get_adc используется другая константа REFERENCE. Она должна содержать байтовую константу, содержащую биты, которые необходимо установить в регистре ADMUX для выбора источника опорного напряжения АЦП. Рекомендую использовать встроенный источник 2,56В с подключением внешнего конденсатора. Обычно для этого следует задать такое значение константы:
Попутно хочу обратить внимание всех начинающих Си-программистов на 3 правила для директивы #define:
Несоблюдение этих правил часто приводит к труднопонимаемым сообщениям об ошибках компиляции или необъяснимому поведению программы. Даже если вам надо описать одно-единственное число (см. ранее ADC_AVERAGE) - заключайте его в скобки. Это нетрудно, заодно вырабатывает привычку, а в будущем эта привычка обережет вас от проблем. Почему замеры АЦП усредняются? В принципе, для нашего случая это делать вовсе необязательно, просто я хотел показать пример универсальной функции, которая выручит вас в любом проекте. В конце концов никто не запретит сделать ADC_AVERAGE равной 1, и умный компилятор «соптимизирует» функцию, выкинув цикл и деление на 1. А вот когда вы соберетесь делать вольтметр с цифровой индикацией - тут-то вам и пригодится усреднение. Этап четвертый, шаг третий - динамическая индикация.
Динамическая индикация потребует от нас немного больше размышлений, чем работа с АЦП. Это фоновый процесс, который можно рассматривать как отдельную «программу в программе». А раз так, сформулируем задачу, продумаем алгоритм и т.п. - т.е. повторим все этапы, как для основной задачи, только кратко. Динамическая индикация в нашей шкале будет заключаться в том, что при каждом вызове наша функция должна сначала погасить светящуюся в данный момент шкалу (записью в соответствующий СОМх единички), затем вывести в порт LEDS заранее вычисленный байт (собственно «уровень») для другой шкалы, после чего включить эту самую другую шкалу. Разобравшись с алгоритмом, обдумаем необходимые нам переменные. Во-первых, раз шкал две, то должно быть как минимум 2 глобальных (т.е. доступных из любой функции всей программы) байтовых переменных, хранящих текущий уровень на шкале. В основном цикле в эти переменные будут записываться значения, а в фоновом процессе индикации извлекаться и выводиться наружу. Наиболее удобно использовать массив, т.к. это позволит при желании достаточно просто увеличить число шкал. Еще нам надо где-то «помнить» номер текущей шкалы, заодно знать, какими битами в каких портах все эти наши шкалы управляются. Все константы опишем, как обычно, в файле SCALE.H, а остальное - в файле IND.C, который создадим и подключим к нашему проекту.
Итак, опишем следующие константы и макросы в файле SCALE.H:
Несколько комментариев к описаниям. Во-первых, обратите внимание, что мы определяем новые имена реальных портов МК, чтобы все функции нашей программы были неизменны (или максимально неизменны) при использовании любых МК, меняться будет только содержимое файла SCALE.H (т.е. этот файл у нас получается платформо-зависимым, а все остальные платформо-независимыми). Во-вторых, аналогично поступаем с номерами битов этих портов - вообще, использование символьных имен вместо конкретных числовых констант есть признак хорошего стиля программирования. Т.е чем меньше внутри функций у вас будет обращений к аппаратным (т.е. платформо-зависимым) средствам МК, тем лучше. Ну, а теперь обратимся к собственно файлу IND.C:
Что занимательного в этом коде? Во-первых, использована пока нигде не описанная константа USER_VECTOR для задания номера вектора прерывания - о ней речь позже. Во-вторых, стиль описания обработчика прерывания - специфичен для каждого компилятора. Как я и предупреждал, этот код для WinAVR (макрос ISR характерен именно для него). В-третьих, не смотря на ранее описанный алгоритм, массивов у нас два - один, как ранее сказано, для хранения уровней шкал, а второй хранит константы для управления включением шкал. Для 2-х шкал это не принципиально, но если захочется больше - будет очень удобно. В-четвертых, обратите внимание, что вывод в порт COM ведется не напрямую, а при помощи операторов |= и &= - это важно: так обеспечивается корректная работа с линиями портов, если часть из них задействована под другие нужды устройства. Этап четвертый, шаг четвертый - настройка периферии. Теперь мы готовы к тому, чтобы произвести конфигурацию задействованной аппаратуры. Для этого по традиции создадим отдельный файл GLOBAL.C следующего содержания:
В общем, код подробно прокомментирован и в дополнительных пояснениях не нуждается. А вот про новые описания в файле SCALE.H следует сказать:
ADC_SPEED - это значение битов предделителя тактовой частоты вашего АЦП. Зависит от того, что вы измеряете, с какой точностью желаете получать результат и от тактовой частоты вашего МК. Я предлагаю использовать самую «медленную» скорость работы АЦП, а уж вы, исходя из собственных предпочтений, установите свое значение. TMR0_SPEED - это значение битов предделителя таймера. Точно так же зависит от тактовой частоты вашего МК и от того, как часто надо «мигать» светодиодами шкал. Если МК больше ничего. Кроме индикации не делает - в большинстве случаев подойдет это значение, хотя его можно и уменьшать и увеличивать. USER_VECTOR - это алиас константы, определяющей номер вектора прерывания от выбранного таймера. Если вы будете использовать таймер другой, или в другом режиме (например, CTC) - измените эту константу на соответствующую. Обязательно приведите в соответствие значение этой константы и значения TIMSK! Иначе будете удивляться, что прерывания не возникают. Этап четвертый, шаг заключительный - обработка и вывод значений.Нам осталось совсем немного: написать функции обработки и вывода измеренных АЦП значений сигнала. Обработку пока не будем делать, облегчим себе жизнь. А вот с индикацией разберемся. АЦП возвращает нам число от 0 до 1023, пропорциональное уровню на входе от 0 до 2,56В. А шкала у нас дискретная из восьми светодиодов. Вспомним исходные требования: наша шкала должна быть линейной, т.е. каждый светодиод должен соответствовать 1/8 всего измеряемого диапазона. Разобьем весь диапазон от 0 до 1023 на 8 равных частей, и пусть наши светодиоды начинают светиться, если уровень сигнала перешагнет «середину» соответствующего участка.
Разместим эти функции в файле PREP.C, который так же подключим к нашему проекту.
Функция output в цикле по переменной i осуществляет сравнение входного значения val с пороговыми. Если входное значение больше порогового - в вспомогательной переменнй tmp устанавливается в единицу очередной бит, начиная с младшего. Как только окажется, что входное напряжение меньше порогового, цикл досрочно прекращается, и полученное к этому моменту значение tmp заносится в соответствующую ячейку массива «уровней» шкал. Обратите внимание, что функция обращается к «внешнему» массиву, который определен совсем в другом модуле проекта. Этап пятый - компиляция и сборка проекта.Если вы выполняли промежуточные компиляции отдельных файлов проекта, или уверены, что в них нет ошибок, можно выполнить сборку проекта. Надеюсь, вы знаете, как это делается. Для экспериментов и отладки лучше отключить оптимизацию компилятора (указать в опциях проекта параметр -O0). Если сборка пройдет успешно, в окне сообщений вы увидите сведения о проценте использования памяти МК кодом. Если будут ошибки - придется их исправлять. Надеюсь, у вас все получится с первого раза, тем более что готовые исходники уже имеются в архиве - вам даже вводить вручную ничего не надо. Ну, а для отладки хорошо подойдет протеус (и для него проектик имеется в архиве). Вы сможете двигать переменные резисторы и наблюдать, как показывают шкалы. В обоих проектах я использовал микроконтроллер atmega8, но уверяю вас, что программный код с минимальными усилиями переносим на любой другой МК, имеющий АЦП и достаточное количество портов! Перед тем, как прошивать полученный hex-файл в реальный микроконтроллер, рекомендую пересобрать проект с установленной опцией оптимизации -Os - вы сразу заметите, как уменьшится размер кода! Этап шестой - мечты и планы.Итак, основа шкального индикатора для «стереоварианта» готова. Надеюсь, есть и необходимые знания для продвижения вперед. И остается только выбрать направление для следующего шага. Могу для начала посоветовать кое-что. Во-первых, в коде заложена возможность предварительной обработки сигнала. Она может быть довольно сложной. Например, разве не интересно вам получить индикатор, который на пиковые уровни будет реагировать мгновенно, а потом медленно и плавно спадать, если больше пиков не повторяется? Во-вторых, все готово для многошкального индикатора. Надо лишь изменить константу SCALE_NUM, соответственно определить необходимое количество констант СОМх, заполнить массив commons этими значениями. В основном цикле вместо константы 2 надо будет поставить SCALE_NUM (это, кстати, полезно сделать сразу, даже для 2-х шкал). Как и для чего применять многоканальный индикатор - это уж дело ваше. В-третьих, достаточно просто переделать индикатор под 16-уровневую шкалу: всего лишь надо переделать главный цикл (не надо измерять 2 канала) и функцию output (надо рассчитать соответствующие уровни и «распределить» из по обоим элементам массива scale[]), а имеющиеся 2 шкалы разместить «паровозиком». В-четвертых, можно сделать шкалу нелинейной, логарифмической. Теоретически АЦП AVR позволяет получить динамический диапазон измерений не менее 54 dB, а в идеале 60. Этого более чем достаточно для любого даже высококачественного звукоусилительного устройства. А делов-то: переделать функцию output, т.е. пересчитать пороги срабатывания... В-пятых...но не хватит ли? У вас, уверен, и своя голова на плечах! Удачи! P.S. Для кто читал невнимательно: все упомянутые в тексте статьи исходные тексты модулей, а так же проект Протеуса доступны для скачивания из файлового архива. Добавить в любимые (0) | Просмотров: 35408
Только зарегистрированные пользователи могут оставлять коментарии. |
« Пред. | След. » |
---|