(продолжение, начало смотри здесь)
Хватит микропроцессорному ядру прохлаждаться – теперь будем программно управлять генераторами!
С этой целью напишем небольшую программу, воспроизводящую две партии голоса одновременно. Все команды будут снабжены
подробными комментариями, о небольшом изменении внутренней структуры PSoC см. ниже.
Расположим во FLASH памяти последовательность байтов. Это будет цепочка значений, которые с частотой около 5 раз/сек будут засылаться в регистры периодов наших 8-битных счетчиков.
16-битный счетчик выступит в роли таймера. Хороший подход при программировании – событийно-ориентированный. Поэтому сразу приучим себя не опрашивать состояние счетчика в цикле, а использовать аппаратное прерывание счетчика (по достижении конца цикла). В этом случае процессор также будет много простаивать, что даст возможность в будущем загрузить его дополнительной работой.
Вот таблица соответствия периодов, загружаемых в счетчик, музыкальным нотам (без привязки к стандарту Ля-440Гц):
До | # | Ре | # | Ми | Фа | # | Соль | # | Ля | # | Си | До | # | Ре | # | Ми | Фа | # | и т.д. |
254 | 240 | 226 | 214 | 202 | 190 | 180 | 170 | 160 | 151 | 143 | 135 | 127 | 120 | 113 | 107 | 101 | 95 | 90 | и т.д. |
Примечание. По мере продвижения по таблице вправо, точность представления нот ухудшается из-за использованного 8-битного представления чисел.
Значению "0" поставим в соответствие признак конца мелодии, значению "1" – признак "паузы", значению "2" – признак того, что звучание ноты должно продолжаться.
Немного модифицируем предыдущий вариант:
– разрешим непрерывную работу 8-битных счетчиков, подключив их входы "Enable" к уровням High (первый счетчик) и Low
(второй счетчик) – в зависимости от ранее установленной нами инверсности этих входов;
– тактовые входы 8-битных счетчиков подключим к источнику тактовых сигналов VC3, тактовый вход 16-битного счетчика
оставим подключенным к 32 кГц;
– в "Global Resources" в качестве VC3_Source выберем источник "SysClk/1" с делителем "VC3 Divider" = 240;
– еще раз убедимся, что в качестве тактовых сигналов микросхемы выбраны внутренний осциллятор 32 кГц, и "CPU_Clock" = 3_MHz;
– в окне "User Module Parameters" у 16-битного счетчика зададим "Period" = 6000.
Основное тело, воспроизводящее мелодию, пишем в обработчике прерывания 16-битного счетчика counter16_1int.asm.
Этот участок вызывается каждые 0,18 с (1/32кГц * 6000). Заготовка обработчика создалась автоматически, когда мы
поместили счетчик в проект и (!) выполнили первую генерацию кода (командой "Generate Application"" или "Build").
Текст обработчика удобно вызывать через дерево файлов проекта. Выкидываем все лишнее из этого файла, чтобы получился
следующий текст:
Внимание: грабли!
Если PSoC Designer 4.0 относится лояльно к вмешательству в текст модулей, то версия 4.2
и выше при повторной генерации кода восстанавливает иходные тексты так, что все изменения, если они не заключены между
строк ;@PSoC_UserCode_… , злостно уничтожаются.
Текст модуля counter16_1int.asm
;Строчки ниже создались автоматически при занесении 16-битного счетчика в проект ;и выполнении первой генерации кода ;здесь перечисляются файлы-описания, которые будут включены в этот программный модуль. ;Ради любопытства можете заглянуть в эти файлы – они входят в состав нашего проектаinclude "m8c.inc" include "Counter16_1.inc" export _Counter16_1_ISR
AREA bss (RAM,REL) ;@PSoC_UserCode_INIT@ (Do not change this line.) ;это секция, где задаются переменные и их длина в байтах
;Говорим, что все перечисленные имена будут внешними, чтобы на них можно было сослаться ;из других модулей, иначе имена будут действительны только в пределах данного модуля export adr1 export adr2 export track1 export track2 ;Мы будем использовать две 2-х байтные переменные (указатели на текущую ноту) adr1: BLK 2 adr2: BLK 2 ;@PSoC_UserCode_END@ (Do not change this line.)
;Заголовок секции, где пишется исполняемый код (тоже создался автоматически) AREA UserModules (ROM, REL) _Counter16_1_ISR: ; – это точка входа (метка) ;@PSoC_UserCode_BODY@ (Do not change this line.) push A ; сохраним содержимое A и X в стеке, так как будем их использовать ("портить"). В дан- push X ; ной задаче это можно и не делать, так как никакая другая процедура их не использует
;-------------- Запишем ноту, адресуемую 1-ым указателем, в 1-й счетчик. -----------
;Сначала загрузим текущую ноту из ячейки, на которую указывает переменная adr1 в аккумулятор. ;Это действие пришлось разбить на два этапа, так как для обращения к памяти программ ;(где мы собираемся держать свои данные – см. ниже) есть только одна команда – romx, ;которая грузит в аккумулятор A значение, на которое указывает регистровая пара A:X. mov X,[adr1+0] ;загружаем младший байт указателя mov A,[adr1+1] ;загружаем старший байт указателя romx
;Перед тем, как записать ноту в счетчик, проверим несколько условий: ;если загрузилось значение 0, то переходим на метку end:, где организуется проигрывание с начала, ;если при сравнении с кодом 2 мы фиксируем равенство, то преходим на метку n1: ; – в обход записи нового значения в счетчикjz end cmp A,2 jz n1
;Записываем значение периода, которое должно быть в аккумуляторе, в регистр периода счетчика № 1. ;Наиболее легкий путь – воспользоваться API (программным интерфейсом) этого счетчика. ;Процедура Counter8_1_WritePeriod находится в программном модуле Counter8_1.asm проекта (уже ;создан автоматически!) и не меняет содержимого аккумулятора (в отличие, например, от API ;16...32 битных счетчиков)call Counter8_1_WritePeriod
;Теперь занесем в другой регистр счетчика значение, при котором выход счетчика будет ;устанавливаться в "1". Чтообы получить скважность, равную 2-м, поделим период счетчика пополам. ;Для этого, например, вначале сдвинем содержимое A вправо, а затем очистим старший разрядasr A and A,0b01111111
;Немножко разнообразим "музыкальную композицию" – добавим динамику слабых/сильных долей в такте. ;Пусть теперь каждая четная доля звучит слабее, чем нечетная, с этой целью мы дополнительно ;уменьшим скважность вых. импульсов, что субъективно выразится в уменьшении громкости.tst [adr1+0],1 ;проверка младшего бита jnz b1 ;если не равен "0", то обойти asr A ;иначе дополнительно поделить значение на 2 asr A b1:
;С помощью API заносим полученное значение в соответствующий регистр счетчикаcall Counter8_1_WriteCompareValue
;Переставим указатель на следующую нотуn1: inc [adr1+0] ;увеличим на 1 младший байт adc [adr1+1],0 ;в зависимости от флага переноса увеличим старший байт
;-------------- Запишем ноту, адресуемую 2-ым указателем, во 2-й счетчик. ----------- ;Все аналогичноmov X,[adr2+0] mov A,[adr2+1] romx jz end cmp A,2 jz n2 call Counter8_2_WritePeriod asr A and A,0b01111111 tst [adr2+0],1 jnz b2 asr A asr A b2: call Counter8_2_WriteCompareValue n2: inc [adr2+0] adc [adr2+1],0
;возвратимся из прерывания для выполнения других дел (до поступления нового прерывания) pop X ;перед возвратом восстановим содержимое X и A (должны быть парны командам push) pop A ; reti
;А сюда передается управление, когда достигается конец мелодии. Чтобы стартовать ;проигрывание с начала, установим указатели на начала нотных последовательностей end: mov [adr1+0],(<track1) ;загрузим младший байт адреса, определяемого меткой track1 mov [adr1+1],(>track1) ;загрузим старший байт адреса, определяемого меткой track1 mov [adr2+0],(<track2) ;загрузим младший байт адреса, определяемого меткой track2 mov [adr2+1],(>track2) ;загрузим старший байт адреса, определяемого меткой track2 pop X ;перед возвратом восстановим содержимое X и A (должны быть парны командам push) pop A ; reti ;возвращаемся из прерывания для выполнения других дел ;Дальше начинается область данных – последовательности нот двух партий голоса. ;Значения представляют собой периоды, последовательно загружаемые в счетчики, ;за исключением следующих кодов: ;0 - конец мелодии ;1 - проигрывание "паузы" ;2 - команда на продолжение звучания предыдущей ноты track1: DB 127,202,127,202,127,202,127,202,101,127,101,127,101,127,101,127 DB 151,190,151,190,151,190,151,190,135,226,113,226,127,226,135,226 DB 127,202,127,202,127,202,127,202,101,170,101,170,101,170,101,170 DB 151,190,151,190,151,190,151,190,135,226,113,226,127,226,135,226 DB 127,202,127,202,127,202,127,202,101,127,101,127,101,127,101,127 DB 151,190,151,190,151,190,151,190,135,226,113,226,127,226,135,226 DB 127,202,127,202,127,202,127,202,101,127,101,127,101,127,101,127 DB 113,151,113,151,113,190,113,190,135,226,113,226,127,226,135,226 DB 1, 1, 1, 1, 1, 1, 1, 1,202,127,202,202,135,202,202,151 DB 202,202,135,202,151,202,202,202,151,190,151,151, 2,202,202,127 DB 151,151, 2,202, 2, 2, 2, 1,202,127,202,202,135,202,202,151 DB 202,202,135,202,151,202,202,202,151,190,151,151,127,202,202,127 DB 151,151, 2,202, 2, 2, 2, 1,127, 2,135,135, 2,151,151, 2 DB 127,135, 2,151, 2,151,135,127, 95, 2,101,101, 2,127,127, 2 DB 95,101, 2,135, 2, 2, 2, 1,127, 2,135,135, 2,151,151, 2 DB 127,135, 2,151, 2,151,135,127, 95, 2,101,101, 2,127,127, 2 DB 95,101, 2,135, 2, 2, 2, 1, 0 track2: DB 76,1,76,1,76,1,76,1,76,1,85,85,2,2,2,1,113,1,113,1,113,2,95,1,95,2,101,101,2,2,2,1 DB 76,1,76,1,76,1,76,1,76,1,85,85,2,2,2,1,113,113,2,2,76,2,95,2,101,2,2,2,2,2,2,2 DB 76,1,76,1,76,1,76,1,76,1,85,85,2,2,2,1,113,1,113,1,113,2,95,1,95,2,101,101,2,2,2,1 DB 76,1,76,1,76,1,76,2,76,1,85,85,2,2,2,101,113,95,2,1,95,76,2,2,67,2,2,2,2,2,2,2 DB 1,101,101,101,101,2,85,2,64,2,67,67,2,76,76,2,64,67,2,76,1,76,67,64,48,2,50,50,2,64,64,2 DB 48,50,2,67,2,2,2,1,64,2,67,67,2,76,76,2,64,67,2,76,1,76,67,64,48,2,50,50,2,64,64,2 DB 48,50,2,67,2,2,2,1,50,1,50,50,1,50,50,1,50,50,1,50,1,50,50,50,38,1,38,38,2,50,50,2 DB 38,38,2,50,2,2,2,1,50,1,50,50,1,50,50,1,50,50,1,50,1,50,50,50,38,1,38,38,2,50,50,2 DB 38,38,2,50,2,2,1,0 ;@PSoC_UserCode_END@ (Do not change this line.) ;---------------------- конец файла Counter16_1INT.asm -------------------------
В программном модуле main.asm осталось вписать строчки, разрешающие прерывания, да загружающие начальные значения переменных-указателей.
Текст модуля main.asm
include "m8c.inc" ;включаем в модуль файл с определениями и макросами include "PSoCAPI.inc" ;включаем файл с определениями от всех UserModules export _main _main: ;как и раньше, стартуем (подаем питание) на счетчики call Counter16_1_Start call Counter8_1_Start call Counter8_2_Start ;Устанавливаем указатели на начало области данных mov [adr1+0],(<track1) mov [adr1+1],(>track1) mov [adr2+0],(<track2) mov [adr2+1],(>track2) ;Индивидуально, разрешаем прерывание нашего 16-разрядного счетчика call Counter16_1_EnableInt ;Разрешаем все аппаратные прерывания с помощью макроса, заданного в модуле m8c.inc M8C_EnableGInt ;Далее работы больше нет никакой, возвращаем управление туда, откуда оно пришло, ;а именно – в модуль boot.asm, где организуется зацикливание ret ;------------------------------ конец файла main.asm -------------------------------
Это – все. Пробуйте! Для особо ленивых – архив проекта (40 кБ) (при загрузке в PSoC Designer версией старше 4.2 соглашайтесь с обновлением модулей). Папка "output" содержит готовую прошивку. Шрифты и раскраска синтаксиса в PSoC Designer вызываются кнопкой "Options".
По-хорошему, надо писать специальную программу – конвертер файлов *.mid в используемый формат. Если мелодия не длинная, то можно и вручную.
Вначале найдите приемлемо звучащий файл *.mid с вашей мелодией и разложите ее на два голоса, в простейшем случае просто удалив все лишние треки и оставив только два. Делается это в каком-нибудь музыкальном редакторе, наподобие CakeWalk. Потом с помощью функций экспорта переведите каждый трек в удобоваримый текстовый формат, а затем согласно вышеприведенной таблице замените буквенное обозначение нот на числа (для этого в текстовом редакторе используйте функцию поиск/замена). Длительности нот задайте количеством байтов "2", следующих непосредственно за нотой, длительности пауз – байтами "1", конец мелодии – кодом "0".
Байт "1", как и любой другой (кроме "0" и "2"), записывается согласно алгоритму в регистр периода счетчика. Но поскольку значение для регистра сравнения оказывается в этом случае равным "0", то при действующем факторе "Less Than " сигнальный выход счетчика (точнее – выход компаратора счетчика) всегда, т.е. на протяжении всего счета, будет равен 0.
На пьезоизлучателе, который подключен не к выходу и общему проводу (как это обычно делается), а к выходам
двух разных источников сигнала. Если говорить математически точно, то производится не сложение, а "вычитание"
сигналов, или, что то же самое – сложение сигналов, один из которых подан в противофазе. В данном применении
это не принципиально, зато позволило обойтись без отдельного смесителя. Замечу, что такое включение дает
"линейную" операцию, не приводящую к искажению сигнала (появлению комбинационных частот).
antiradio.narod.ru/psoc
Дата создания документа: 06.01.2004. Последнее обновление: 15.04.2008.