Закодировать точки (ступеньки) синусоиды можно двумя методами:
1) Задаем число точек (ступенек) на период. Период квантования синусоиды можно рассчитать:
Преимущество метода: простота.
Недостаток метода: если скорость нарастания сигнала различна (у синусоиды она уменьшается по мере приближения к ), появляется много одинаковых точек (расположенных на одном уровне), которые занимают много места в памяти.
Решить эту проблему позволяет второй метод.
2) Задаем период квантования синусоиды. Число точек (ступенек) на период заранее неизвестно. Генерируем 12-битный синусоидальный сигнал и через каждый промежуток времени, равный периоду квантования синусоиды, смотрим, изменился ли уровень сигнала. Если уровень сигнала не изменился, увеличиваем число повторов текущей точки на единицу. Если уровень изменился, это означает, что мы получили следующую точку синусоиды. Число ее повторов находится аналогично.
Преимущество метода: благодаря тому, что мы знаем число повторов каждой точки синусоиды, нет необходимости хранить в памяти одинаковые точки.
Для получения точек синусоиды был выбран период квантования синусоиды, равный 4000 мкс, при частоте синусоиды, равной 1 Гц. Синусоида будет состоять из 246 точек (ступенек) на период. При изменении частоты синусоиды от 5 до 100 Гц число точек меняться не будет, изменится лишь период квантования синусоиды. Рассчитывается он следующим образом.
Так как частота синусоиды может меняться от 5 до 100 Гц, период квантования синусоиды будет различным для разных частот и его можно найти (в пересчете на такты микроконтроллера, для 16-битного таймера):
- период квантования синусоиды для частоты 1 Гц, равный 4000 мкс;
- тактовая частота кварца, равная 30 МГц;
– число тактов кварца за 1 машинный цикл, равное 12-ти;
– заданная частота синусоиды (от 5 до 100 Гц):
Алгоритм функционирования программы приведен на рис. 6-9:
Рис. 8. Алгоритм подпрограммы обслуживания прерывания от таймера T0.
Рис. 9. Алгоритм подпрограммы обслуживания прерывания от таймера T1.
В соответствии с данным алгоритмом была разработана программа, обеспечивающая выполнение микроконтроллером заданной функции.
Составим граф состояний клавиатуры (рис. 10) , согласно которому в дальнейшем будет удобнее работать с клавиатурой. Здесь вершины графа:
1 – режим RABOTA: входим в режим по нажатию клавиши RABOTA, в этом режиме происходит вывод синусоиды.
2 – режим VVOD: входим в режим по нажатию клавиши VVOD, в этом режиме происходит опрос клавиатуры.
3 – режим VVOD_U: входим в режим по нажатию клавиши BUTTON_U, в этом режиме происходит опрос клавиатуры и изменение амплитудного значения напряжения синусоиды.
4 – режим VVOD_f: входим в режим по нажатию клавиши BUTTON_f, в этом режиме происходит опрос клавиатуры и изменение частоты синусоиды.
Рис. 10. Граф состояний клавиатуры.
Описание программы
В начале программы объявим все необходимые константы, а именно:
· константы для ЦАПов, определяющие выводы шины управления для ЦАПов;
· константы для клавиатуры, определяющие коды режимов работы и коды клавиш;
· константы для дисплея, определяющие выводы шины управления для дисплея и др.
Далее будут располагаться макроопределения (макросы) для работы с ЦАПами:
;сначала выводим данные на пины порта - см. timing diagram
mov P0,data_
clr pin_
clr WR_DAC
nop
nop
setb WR_DAC
setb pin_
ENDM
· Макрос MACRO_LDAC обновляет значение на выходе выбранного ЦАПа:
MACRO_LDAC macro pin_
clr pin_
nop
nop
setb pin_
ENDM
В данной программе мы будем использовать сегмент данных (DS) и сегмент кода (CS). В сегменте данных (т.е. в ОЗУ) начиная с адреса 08h будут располагаться следующие переменные:
Addr_tochki: DS 2 ;адрес выводимой точки в массиве
Counter_tochka: DS 1 ;число повторений выводимой точки
Cod_tochki: DS 2 ;код текущей точки – мл. и старш. байты
Regim_raboty: DS 1 ;код режима работы
U_value: DS 1 ;величина напряжения
f_value: DS 1 ;величина частоты
Predidyshiy_kod_s_klavy: DS 1 ;предыдущий код, получ. с клавиатуры
В сегменте кода (т.е. в ПЗУ) будет располагаться программа.
По адресу в ПЗУ 00h мы должны разместить команду абсолютного перехода на метку start (т.е. на начало программы).
Далее следует директива ORG 0003h, которая устанавливает в счетчик РС адрес вектора прерывания INT0 - в том случае, если прерывание было вызвано определенным событием (в данном случае – нажатием на кнопку VVOD, т.е. фронтом). В случае, если данное прерывание было вызвано, выполнится команда, находящаяся в ПЗУ по адресу 0003h – а это команда абсолютного перехода на метку interrupt_VVOD (т.е. на начало подпрограммы обработки прерывания от внешнего источника INT0).
Аналогично с директивами ORG 000Вh и ORG 001Вh, которые устанавливают в счетчик РС адрес вектора прерывания T0 и Т1 соответственно - в том случае, если прерывание было вызвано переполнением таймера Т0 или Т1. В случае, если было вызвано одно из этих прерываний, выполнится команда, находящаяся в ПЗУ по адресу 000Вh или 001Вh, т.е. команда абсолютного перехода на начало подпрограммы обработки прерывания от таймера Т0 или Т1 соответственно.
CSEG
ajmp start
org 0003h ;установили в счетчик РС адрес вектора прерывания INT0
ajmp interrupt_VVOD
org 000Bh ;установили в счетчик РС адрес вектора прерывания T0
ajmp interrupt_T0
org 001Bh ;установили в счетчик РС адрес вектора прерывания T1
ajmp podprog_DAC_OUT
Далее в ПЗУ у нас располагается основная программа. В начале основной программы необходимо выполнить инициализацию:
· стека:
mov SP,#Stack_base
· порта Р0 (начальным значением):
mov P0,#0FFh ;установим на пины порта Р0 все 1цы
· ЦАПов:
;пины должны быть не активны + учтем, что они инверсные:
setb CSL_REF
setb CSM_REF
setb LDAC_REF
setb WR_DAC
setb CSL_OUT
setb CSM_OUT
setb LDAC_OUT
· клавиатуры:
setb IT0 ;тип прерывания INT0 - по фронту
setb EA ;снятие блокировки прерываний
setb EX0 ;разрешение внешнего прерывания 0
mov Predidyshiy_kod_s_klavy,#00h
· дисплея:
clr E_LCD
acall podprog_nastroyka_displaya
· режима работы
mov Regim_raboty,#VVOD
mov U_value,#05h;#0Ah
mov f_value,#05h
acall Redraw_LCD_RABOTA
Далее мы настраиваем таймеры Т0 и Т1 и запускаем таймер Т0, который опрашивает клавиатуру раз в 1 мс:
mov TMOD,#00010001b ;16-битные таймеры Т0 и Т1
setb EA ;снимаем запрет всех прерываний
setb ET0 ;разрешаем прерывания от таймера Т0
setb ET1 ;разрешаем прерывания от таймера Т1
mov TL0,#low(Timer_1_ms)
mov TH0,#high(Timer_1_ms)
setb TR0 ;запускаем таймер Т0
Затем мы инициализируем синусоиду. Для получения точек синусоиды был выбран период квантования синусоиды, равный 4000 мкс, при частоте синусоиды 1 Гц. Синусоида будет состоять из 246 точек (ступенек) на период. В конце программы (после основной программы и всех подпрограмм) с помощью директивы DB размещена таблица точек синусоиды, где последовательно располагаются: время удержания точки и код точки синусоиды (младший и старший байты).
Инициализация синусоиды:
;адрес первой выводимой точки в таблице
mov Addr_tochki,#LOW(Start_sin)
mov Addr_tochki+1,#HIGH(Start_sin)
;число повторений точки - получим из таблицы в ПЗУ
mov DPTR,#Start_sin
mov A,#00h
movc A,@A+DPTR
mov Counter_tochka,A
;выводим код точки на выходной ЦАП
;старший байт - получим из таблицы в ПЗУ
mov A,#02h
movc A,@A+DPTR
MACRO_WR_DAC CSM_OUT,A ;сначала ст. байт кода точки в ст.байт ЦАП
;младший байт - получим из таблицы в ПЗУ
mov A,#01h
movc A,@A+DPTR
MACRO_WR_DAC CSL_OUT,A ;потом мл. байт кода точки в мл. байт ЦАП
MACRO_LDAC LDAC_OUT
В конце основной программы нам необходимо поместить команду ajmp $, которая выполняет абсолютный переход на саму себя, что не дает выполниться подпрограммам, расположенным далее. Подпрограммы обработки прерываний смогут выполниться лишь в том случае, если произойдут события, по которым вызываются прерывания (например, нажатие кнопки VVOD или переполнение таймера Т0/Т1).
По умолчанию при запуске программы установлен режим работы VVOD. В этом режиме происходит следующее.
В данном режиме останавливается вывод синусоиды и раз в миллисекунду начинает происходить опрос клавиатуры по таймеру Т0, а также производится проверка корректности введенных значений напряжения и частоты:
interrupt_VVOD:
clr TR1
clr TR0
mov TL0,#low(Timer_1_ms)
mov TH0,#high(Timer_1_ms)
mov Regim_raboty,#VVOD
setb TR0
;проверка нижнего допустимого значения напряжения - больше или равно 5
mov A,#5
subb A,U_value
jc loop_podprog_VVOD_U_Value_correktno_vvod
mov U_value,#5
loop_podprog_VVOD_U_Value_correktno_vvod:
;проверка нижнего допустимого значения частоты - больше или равно 5
mov A,#5
subb A,f_value
jc loop_podprog_VVOD_f_Value_correktno_vvod
mov f_value,#5
loop_podprog_VVOD_f_Value_correktno_vvod:
acall Redraw_LCD_Rabota
reti
Клавиатура будет опрашиваться до тех пор, пока не будет нажата клавиша BUTTON_RABOTA (т.е. пока не начнется вывод синусоиды).
При переполнении таймера Т0, который мы запустили при инициализации микроконтроллера, произойдет вызов подпрограммы обработки прерывания от таймера Т0.
В начале данной подпрограммы перезапускается таймер Т0 для дальнейшего опроса клавиатуры:
interrupt_T0:
;перезапустим таймер Т0
clr TR0
mov TL0,#low(Timer_1_ms)
mov TH0,#high(Timer_1_ms)
setb TR0
Затем код режима работы сравнивается с кодами всех режимов работы.
mov A,Regim_raboty
cjne A,#VVOD,interrupt_T0_VVOD_U
…
В случае если код изменился, в переменную Regim_raboty помещается новый код режима работы, после чего происходит выход из прерывания.
Рассмотрим работу подпрограммы более подробно.
Если мы находимся в режиме работы VVOD, мы получаем с клавиатуры код нажатой клавиши (с помощью подпрограммы, возвращающей код нажатой клавиши и осуществляющей защиту клавиатуры от дребезга), и в зависимости от полученного кода выполняем различные действия.
acall podprog_anti_drebezg
cjne A,#BUTTON_U,interrupt_T0_BUTTON_f
;если была нажата клавиша U, на дисплее высветится *U(B):000
mov Regim_raboty,#VVOD_U
mov U_value,#00h
acall Redraw_LCD_U
ajmp interrupt_T0_end
interrupt_T0_BUTTON_f:
cjne A,#BUTTON_f,interrupt_T0_BUTTON_RABOTA
;если была нажата клавиша f, на дисплее высветится *f(B):000:
mov Regim_raboty,#VVOD_f
mov f_value,#00h
acall Redraw_LCD_f
ajmp interrupt_T0_end
Если были нажаты клавиши BUTTON_U или BUTTON_f, режим работы меняется соответственно на VVOD_U или VVOD_f, а также с помощью подпрограмм Redraw_LCD_U/Redraw_LCD_f обновляется дисплей (рис. 11 и 12):
Рис.11. Символы, выводимые на дисплей подпрограммой Redraw_LCD_U.
Рис.12. Символы, выводимые на дисплей подпрограммой Redraw_LCD_f.
Если же была нажата клавиша BUTTON_RABOTA, режим работы меняется на RABOTA, останавливается таймер Т0, в первый (опорный) ЦАП выводится опорное напряжение (по умолчанию равное 5 В), устанавливается значение частоты синусоиды (по умолчанию равное 5 Гц), а также настраивается и запускается таймер Т1, который используется для вывода синусоиды.
interrupt_T0_BUTTON_RABOTA:
cjne A,#BUTTON_RABOTA,interrupt_T0_end
mov Regim_raboty,#RABOTA
;здесь настроим вызов прерывания для синусоиды
clr TR0 ;остановили таймер клавиатуры и дисплея
acall podprog_DAC_U
acall podprog_f_SIN
acall Redraw_LCD_RABOTA
;запускаем таймер для синусоиды
mov TL1,Zaderzhka_f_5_100
mov TH1,Zaderzhka_f_5_100+1
setb TR1
ajmp interrupt_T0_end
Таким образом, если в свой предыдущий вызов подпрограмма установила режим работы VVOD_U или VVOD_f, вызываются подпрограммы podprog_VVOD_U или podprog_VVOD_f соответственно, которые осуществляют ввод напряжения/частоты с клавиатуры.
interrupt_T0_VVOD_U:
cjne A,#VVOD_U,interrupt_T0_VVOD_f
;поместим на 1ю строчку дисплея 00:
acall podprog_VVOD_U
mov Regim_raboty,A
ajmp interrupt_T0_end
interrupt_T0_VVOD_f:
cjne A,#VVOD_f,interrupt_T0_end
acall podprog_VVOD_f
mov Regim_raboty,A
interrupt_T0_end:
reti
При переполнении таймера Т1 произойдет вызов подпрограммы обработки прерывания от таймера Т1.
В начале данной подпрограммы перезапускается таймер Т1, что позволит вывести следующую точку синусоиды через время, определяемое частотой синусоиды (т.е. через время, равное периоду квантования синусоиды для данной частоты). Период квантования синусоиды для частот от 5 до 100 Гц с шагом 1 Гц рассчитан и помещен в таблицу в конце программы с использованием директивы DW (т.к. значения периода квантования – двухбайтные числа).
После перезапуска таймера Т1 производится проверка, сколько осталось повторений текущей точки. Если число оставшихся повторений текущей точки равно нулю, в данном прерывании мы будем работать со следующей точкой синусоиды. Если текущая точка является последней точкой синусоиды, в данном прерывании мы будем работать с первой точкой синусоиды.
Выводим код точки на второй (выходной) ЦАП – сначала старший байт, потом младший, и выходим из прерывания.
Помимо подпрограмм обработки прерываний, программа включает следующие необходимые подпрограммы.
1) Подпрограмма задержки на N мкс: podprog_wait_N_mks
В подпрограмму передается число в DPTR, равное времени задержки, пересчитанному в тактах МК.
2) Подпрограмма установки напряжения на опорном ЦАП: podprog_DAC_U
Максимальный код, который мы можем передать на 12-битный ЦАП (4095), ставим в соответствие максимальному напряжению, которое мы можем ввести с клавиатуры, т.е. 15В:
.
Т.е. умножая вводимое с клавиатуры напряжение на 273 (т.е. 111h), мы будем выдавать на ЦАП соответствующий такому напряжению код:
5В – 555h
6B – 666h
7B – 777h
…
14B – EEEh
15B - FFFh
3) Подпрограмма загрузки периода квантования синусоиды, пересчитанного для частот синусоиды от 5 до 100 Гц, из ПЗУ: podprog_f_SIN
Подпрограмма загружает в 2-байтную переменную Zaderzhka_f_5_100 2-байтное число, равное периоду квантования синусоиды, пересчитанному в тактах МК.
4) Подпрограмма работы с клавиатурой, которая возвращает 8-битный код нажатой клавиши в регистр R0: podprog_klava
Используя операцию «логическое И с маской», выделяем сначала младшую тетраду кода клавиши (в используемой нами матричной клавиатуре это определяет «строку», в которой находится клавиша), а затем старшую тетраду кода клавиши (в используемой нами матричной клавиатуре это определяет «столбец», в котором находится клавиша). Затем объединяем тетрады с помощью логического ИЛИ и получаем код нажатой клавиши.
5) Подпрограмма защиты клавиатуры от дребезга, которая возвращает в аккумулятор код нажатой клавиши с учетом защиты от возможного дребезга: podprog_anti_drebezg
Эта подпрограмма получает код нажатой клавиши с помощью предыдущей подпрограммы, запоминает его, а затем еще пять раз получает код клавиши и выполняет сравнение текущего кода с самым первым.
6) Подпрограмма ввода напряжения с клавиатуры: podprog_VVOD_U
Получаем напряжение с клавиатуры, используя две предыдущие подпрограммы, затем выясняем, была ли нажата цифровая клавиша (0-9) либо другая клавиша (BUTTON_U, BUTTON_f, BUTTON_RABOTA).
Если была нажата какая-либо из цифровых клавиш, вычисляем значение напряжения, проверяем, не превышает ли оно 255 (чтобы не произошло переполнение), а затем – не превышает ли оно 15 (максимальное допустимое значение напряжения). Если все верно, выводим это значение напряжения на дисплей.
Если же была нажата какая-либо из клавиш BUTTON_U, BUTTON_f, BUTTON_RABOTA, соответственно обрабатываем эти состояния.
7) Подпрограмма ввода частоты с клавиатуры: podprog_VVOD_f
Данная подпрограмма работает аналогично предыдущей подпрограмме.
8) Подпрограмма, выводящая на дисплей строки U(B):0ХХи *f(Hz):ХХХ: Redraw_LCD_f
9) Подпрограмма, выводящая на дисплей строки *U(B):0ХХи f(Hz):ХХХ: Redraw_LCD_U
10) Подпрограмма, выводящая на дисплей строки U(B):0ХХи f(Hz):ХХХ: Redraw_LCD_ RABOTA
11) Подпрограмма, выводящая на дисплей строку символов U(B): или f(Hz): print_string_LCD
В подпрограмму передается в DPTR адрес начала строки в ПЗУ, после чего строка выводится на дисплей.
12) Подпрограмма, выводящая на дисплей численное значение напряжения или частоты: print_num_LCD
В подпрограмму передается в аккумуляторе содержимое переменной U_value или f_value. Выводим на дисплей поочередно сотни, десятки и единицы, которые выделяем с помощью операции DIV AB – деление A на B. Результат: в A -частное, в B - остаток.
13) Подпрограмма преобразования двоичного кода цифровой клавиши в число от нуля до 9: podprog_kod_v_chislo
14) Подпрограмма настройки дисплея, которая осуществляется согласно таблице "Начальная настройка индикатора" из даташита: podprog_nastroyka_displaya
15) Подпрограмма ожидания флага BUSY: podprog_wait_BUSY_flag_LCD
Ожидаем, пока флаг BUSY (старший бит байта команды) не сбросится (т.е. пока не станет равен нулю).
16) Подпрограмма чтения данных из дисплея, данные возвращаются в аккумулятор: podprod_read_data_LCD
17) Подпрограмма записи данных в дисплей, данные в подпрограмму передаются в аккумуляторе: podprog_write_data_LCD
18) Подпрограмма чтения команд из дисплея, команды возвращаются в аккумулятор: podprod_read_command_LCD
19) Подпрограмма записи команд в дисплей, команды в подпрограмму передаются в аккумуляторе: podprod_write_command_LCD