Код: Выделить всё
;программа сложения 4х прямоугольных сигналов с произвольными частотами
.include "m88def.inc" ;определения для Mega88
.include "MacroAVR.asm" ;макросы для упрощения кода
.equ F_CPU=8000000 ;частота контроллера
.equ SND_SAMPLE_FREQ =22050 ;частота выборок
.equ SND_OCR_VALUE =F_CPU/SND_SAMPLE_FREQ ;расчет значения для режима CTC
.equ SND_MID_VALUE =SND_OCR_VALUE/2 ;половина выходного сигнала
.equ NUMBER_OF_SIGNALS =4 ;количество смешиваемых сигналов
.equ FREQ0 =261 ;C4 ;частоты сигналов
.equ FREQ1 =311 ;D#4
.equ FREQ2 =110 ;A2
.equ FREQ3 =61 ;B1
.equ DIV0 =SND_SAMPLE_FREQ/FREQ0/2 ;расчет делителей
.equ DIV1 =SND_SAMPLE_FREQ/FREQ1/2 ;для частоты каждого сигнала
.equ DIV2 =SND_SAMPLE_FREQ/FREQ2/2
.equ DIV3 =SND_SAMPLE_FREQ/FREQ3/2
.def ZERO =R2 ;для ускорения арифметики
.def STR =R3 ;для ускорения вх/вых в прерывание
;структура данных для одного сигнала
.equ PH_OFFSET =0 ;фаза
.equ DIV_OFFSET =2 ;делитель
.equ OUT_OFFSET =4 ;значение на выходе
.equ SIGNAL_SIZE =2+2+1 ;ее размер
.dseg ;сегмент данных
SIG: .byte SIGNAL_SIZE*NUMBER_OF_SIGNALS ;данных сигналов
.cseg ;сегмент кода
.org 0
RJMP RESET
.org OVF1addr
RJMP SAMPLE_OUT ;прерывание для расчета и вывода семпла
DIVS:
.dw DIV0,DIV3,0,0 ;рассчитанная таблица частот сигналов (0-отключено для наглядности)
; .dw DIV0,DIV1,DIV2,DIV3
RESET: SETSTACK ;установка стека
OUTI CLKPR,(1<<CLKPCE) ;сброс делителя тактовой
STS CLKPR,ZERO
OUTI DDRC,(1<<PC4)|(1<<PC3)|(1<<PC2)|(1<<PC1)|(1<<PC0);тестовые выходы
OUTI TCCR1A,(1<<COM1A1)|(1<<WGM11)|(0<<WGM10) ;настройка таймера CTC, TOP=ICR
OUTI TCCR1B,(1<<WGM12)|(1<<WGM13)|(1<<CS10)
OUTI ICR1H,High(SND_OCR_VALUE) ;период квантования
OUTI ICR1L,Low(SND_OCR_VALUE)
OUTI OCR1AH,High(SND_MID_VALUE) ;в ШИМ - половина уровня
OUTI OCR1AL,Low(SND_MID_VALUE)
LDS R16,TIMSK1 ;вкл. прерывания OVF1
ORI R16,(1<<TOIE1)
STS TIMSK1,R16
SBI DDRB,PB1 ;включить вывод ШИМ
SETZ DIVS*2 ;копирование таблицы из флеша
SETY SIG ;в структуру сигнала
LDI R17,NUMBER_OF_SIGNALS
DLOOP: LPM XL,Z+ ;чтение делителя
LPM XH,Z+
STD Y+PH_OFFSET+0,XH ;запись начального значения фазы
STD Y+PH_OFFSET+1,XL
STD Y+DIV_OFFSET+0,XH ;и это же значение в делитель
STD Y+DIV_OFFSET+1,XL
STD Y+OUT_OFFSET,ZERO ;значение функции изначально=0
ADIW YL,SIGNAL_SIZE ;переход к следюущему
DJNZ R17,DLOOP
SEI ;вкл. глобальных прерываний
LOOP: SETX SIG+OUT_OFFSET ;основной цикл
LDI R17,NUMBER_OF_SIGNALS ;просто вывод
CLR R18 ;значения функций на выходы
OLOOP: LD R16,X ;для наглядности
BST R16,7
BLD R18,4
LSR R18
ADIW XL,SIGNAL_SIZE
DJNZ R17,OLOOP
IN R16,PORTC
ANDI R16,0xF0
OR R16,R18
OUT PORTC,R16
RJMP LOOP
;процедура вывода семпла
SAMPLE_OUT:
SBI PORTC,PC4 ;вывод загрузки контроллера процедурой
IN STR,SREG ;сохранение SREG и регистров
PUSH R17
PUSH R16
PUSHX
PUSHY
PUSHZ
LDI ZL,0x08 ;начальное значение суммы сигналов
LDI R17,NUMBER_OF_SIGNALS
SETY SIG ;установка на структуру первого сигнала
SLOOP: LDD XH,Y+PH_OFFSET+0 ;чтение фазы
LDD XL,Y+PH_OFFSET+1
CP XL,ZERO
CPC XH,ZERO
BREQ SKIP ;если 0, про сигнал не обрабатывать
LDD R16,Y+OUT_OFFSET ;чтение значения на выходе
SBIW XL,1 ;уменьшаем фазу
BRNE NO_INVERT ;если не 0, переход к сохранению
COM R16 ;если 0, инвертируем выход
STD Y+OUT_OFFSET,R16 ;сохраняем значение на выходе
LDD XH,Y+DIV_OFFSET+0 ;перезагружаем значение фазы
LDD XL,Y+DIV_OFFSET+1
NO_INVERT:
STD Y+PH_OFFSET+0,XH ;сохраняем фазу
STD Y+PH_OFFSET+1,XL
SBRC R16,7 ;суммируем значения на выходах
INC ZL
SBRS R16,7
DEC ZL
SKIP: ADIW YL,SIGNAL_SIZE ;переход к следующему
DJNZ R17,SLOOP
CJNE ZL,0x08,NO_HALF ;проверка суммы значений и преобразование в ШИМ
SETZ SND_MID_VALUE ;если сумма=0, выводим половину в ШИМ
RJMP OCR_SET
NO_HALF:CJLO ZL,0x08,OUT_MIN ;если сумма<0, выводим в ШИМ 0
SETZ SND_OCR_VALUE-1 ;если сумма>0, выводим в ШИМ максимальное значение
RJMP OCR_SET
OUT_MIN:SETZ 0
OCR_SET:
STS OCR1AH,ZH ;вывод в регистры ШИМ
STS OCR1AL,ZL
POPZ ;восстановление регистров
POPY
POPX
POP R16
POP R17
OUT SREG,STR
CBI PORTC,PC4
RETI