А теперь немного “покодим” на ядре M8C

(продолжение, начало смотри здесь)

Хватит микропроцессорному ядру прохлаждаться – теперь будем программно управлять генераторами!
С этой целью напишем небольшую программу, воспроизводящую две партии голоса одновременно. Все команды будут снабжены подробными комментариями, о небольшом изменении внутренней структуры 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" – признак того, что звучание ноты должно продолжаться.

Подготовка аппаратной части PSoC

Немного модифицируем предыдущий вариант:
– разрешим непрерывную работу 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".

FAQ: Как задать собственную мелодию?

По-хорошему, надо писать специальную программу – конвертер файлов *.mid в используемый формат. Если мелодия не длинная, то можно и вручную.

Вначале найдите приемлемо звучащий файл *.mid с вашей мелодией и разложите ее на два голоса, в простейшем случае просто удалив все лишние треки и оставив только два. Делается это в каком-нибудь музыкальном редакторе, наподобие CakeWalk. Потом с помощью функций экспорта переведите каждый трек в удобоваримый текстовый формат, а затем согласно вышеприведенной таблице замените буквенное обозначение нот на числа (для этого в текстовом редакторе используйте функцию поиск/замена). Длительности нот задайте количеством байтов "2", следующих непосредственно за нотой, длительности пауз – байтами "1", конец мелодии – кодом "0".

FAQ: Почему байт “1” вызывает паузу (отсутствие звучания)?

Байт "1", как и любой другой (кроме "0" и "2"), записывается согласно алгоритму в регистр периода счетчика. Но поскольку значение для регистра сравнения оказывается в этом случае равным "0", то при действующем факторе "Less Than " сигнальный выход счетчика (точнее – выход компаратора счетчика) всегда, т.е. на протяжении всего счета, будет равен 0.

FAQ: Где производится “сложение” (смешивание) голосов?

На пьезоизлучателе, который подключен не к выходу и общему проводу (как это обычно делается), а к выходам двух разных источников сигнала. Если говорить математически точно, то производится не сложение, а "вычитание" сигналов, или, что то же самое – сложение сигналов, один из которых подан в противофазе. В данном применении это не принципиально, зато позволило обойтись без отдельного смесителя. Замечу, что такое включение дает "линейную" операцию, не приводящую к искажению сигнала (появлению комбинационных частот).


antiradio.narod.ru/psoc
Дата создания документа: 06.01.2004. Последнее обновление: 15.04.2008.

Сайт создан в системе uCoz