Задача
№ 04 Цель
задачи:
Разработать
программу для измерения частоты сигнала
ёмкостного датчика нагрузки: МК должен сохранять результат в памяти и
отправлять на последовательный порт ПК.
Для выполнения задания
необходимо:
- Компилятор Си для AVR ImageCraft
- Программный эмулятор для AVR
Visual Micro Lab
- Data Sheet на МК AVR AT90s2313
-
свободное время и желание.
Вступление...
Ёмкостной датчик - это устройство
которое под действием нагрузки изменяет
свою емкость, которая является частью
времязадающей цепи генератора
электрических колебаний. Вот частоту
сигнала с этого генератора нам и нужно
померить.
Вот
один из вариантов конструкции
емкостного датчика нагрузки из двух
металлических пластин с воздушным
зазором.
Я предлагаю такую
конструкцию датчика:
Это металлический
плоский ящик-опора на который действует
измеряемая нагрузка.
Металлический верх ящика является
упругим элементом датчика и
одновременно обкладкой измерительного
конденсатора соединенной с общим
проводом электрической схемы
устройства - так достигается
экранировка устройства от внешних
электромагнитных полей.
К верху ящика, с низу приклеена по краям,
с зазором, металлическая пластина -
вторая обкладка конденсатора - она
остается плоской не зависимо от
нагрузки.
При нагружении ящика его верх
деформируется и зазор между пластинами уменьшается -
соответственно емкость конденсатора увеличивается.
Значит уменьшается частота
генератора частото-задающая RC цепь
которого состоит из этого конденсатора
и дополнительного резистора.
Частота делится делителем на 128 и
передается на устройство фиксации.
Электрическая
часть датчика собрана на двух
микросхемах по схеме:
Устройство питается от напряжения 5-15
вольт и выдает почти прямоугольный
сигнал на выходе.
Резисторы 1 и 2 вместе с измерительным
конденсатором задают частоту генерации.
Период колебаний генератора чуть больше
произведения R*C.
Частота на выходе устройства без
нагрузки примерно 2000 Гц - учитывая
делитель генератор должен выдавать 256
кГц.
Можно
применить генератор низкой частоты без
деления - но ВЧ генераторы более
стабильны.
Очень простая схема генератора может
быть сделана на микросхемах таймерах 555
серии.
Моя схема нравится мне тем,
что одна из обкладок конденсатора
связана с "общим проводом" и тем,
что благодаря делителю частоты можно в
широких пределах вирировать параметры
системы в целом.
Электрическая часть датчика
смонтирована внутри ящика.
Наверно
по такой схеме можно реализовать не плохой и недорогой датчик ускорения!
только деформироваться будет тонкая внутренняя диафрагма
а корпус будет жестким.
|
Работаем...
В задаче_03
мы написали программу которая каждые 20
мс по прерыванию от переполнения таймера_0 попадает в
функцию - обработчик прерывания.
можно
модифицировать эту программу.
но! повторение мать учения.
давайте повторим процесс создания
программы с "0".
Новая программа должна будет
каждые 20 мс прерываться и измерять период
цифрового сигнала
поступающего на ножку 6 МК PD2 (INT0) и выводить
результат измерения по rs232 со скоростью
9600 на ножку 3 МК PD1 (TxD).
Этих функций достаточно для отладки
датчика с МК "в металле" в
комбинации с софтом на ПК.
Сохранение
в памяти - пока будет в виде упоминания
прототипа соответствующей функции в Си
программе для МК.
Как будем измерять период сигнала?
Давайте задействуем для этого еще одно
прерывание - внешнее прерывание по спаду
сигнала на ножке 6 МК, это бит PD2 (INT0)
(см. DataSheet ).
Мы укажем МК что бы это прерывание
происходило по спаду сигнала, т.е. при изменении
уровня сигнала на ножке с "1" на
"0"
Возможны еще 3 варианта событий
вызывающих это прерывание:
- фронт сигнала
- уровень лог. "0"
- уровень лог. "1"
|
Прерываваясь
каждые 20 мс, мы будем разрешать
прерывание INT0 - по его первому
возникновению мы начнем отсчет, и
продолжим счет до его второго
возникновения. таким образом насчитанное
число будет пропорционально периоду
входного сигнала.
ЛЕПОТА!
Запретим прерывание по INT0 и отправим
результат по rs232 в ПК, предварительно
сохранив его (пока сохранения не будет).
...и опять ждать отсчета очередных 20 мс.
Пишем
на Си ...
Давайте используем АпБилдер
компилятора ICC.
1) в секции CPU:
-
"тагет" 2313
- частота 3.6864
- ставим крестик на INT0 и выбираем
событие "фоллин едж" по спаду
значит прерывание будет.
2) в секции
Ports: -
давайте сделаем три ножки PB0_PB2 выходами,
авось пригодится. 3)
в секции Taimer0: -
ставим 2 флажка - использовать таймер и
прерываться по переполнению
- ставим частоту прерываний 50 Гц
- деление 1024 4)
в секции UART: -
ставим 2 флажка: использовать UART и
разрешить передачу
- скорость 9600 5)
в "опциях" выбираем " инклуд мэйн
- т.е. вставить в текст программы функцию main() 5)
"Ок" - код заготовка есть. Давайте
уважать Си! Не смотря на то, что
компилятор делает по другому - будем
перечислять прототипы используемых в
программе функций.
это удобно - особенно если захочешь
использовать свой код через годик
другой - гораздо легче разобраться что
делает программа.
Значит так... модифицируем: #include
<io2313v.h>
#include <macros.h>
//////////////////////////
/// Прототипы функций
void port_init(void);
void timer0_init(void);
void timer0_ovf_isr(void);
void uart0_init(void);
void int0_isr(void);
void init_devices(void);
void get_period(void);
void send_result(void);
void save_result(void);
//////////////////////////
void main(void)
{
init_devices();
//insert your fcode here...
}
//////////////////////////
void
get_period(void)
{
}
void send_result(void)
{
}
void save_result(void)
{
} void
port_init(void)
{
PORTB = 0xFF;
DDRB = 0x07;
PORTD = 0x7F;
DDRD = 0x00;
}
далее без
изменений...
только main убрать в низу. Я
добавил три функции:
void get_period(void);
void send_result(void);
void save_result(void); что
они делают, по порядку:
- измерить период сигнала
- отправить результат по rs232
- сохранить результат во внешнюю память
Обязательно
давайте функциям названия с намеком
на то что они делают! Не бойтесь
длинных названий - это нормальная
практика... |
Теперь
сохраним этот файл под именем work04.c в
папке work04, и
создадим новый проект work04.prj
Идем дальше...
Вместо
//insert your code here...
напишем: while(1);
Откомпилируем программу - все в норме - 9%
ресурсов задействовано уже. Кстати,
надо внести поправочку в инициализацию
МК - дело в том что прерывание должно
быть отключено на старте и включать его
мы будем по мере необходимости - т.е.
непосредственно при измерении периода сигнала. Как
отключить это прерывание - нужно
почитать DataSheet МК про устройство
регистров: MCUCR (стр.26 DataSheet) и GIMSK - прошу,
почитайте, это нужно вам! я
почитал и вам расскажу, что нужно при запуске в
регистр GIMSK записать 0х00... это
запретит прерывание INT0, сделайте это
в функции void init_devices(void) GIMSK = 0х00;
// INT0 отключено
// 0x40 - включено Значит
когда нам понадобиться прерывание по INT0
- мы запишем в регистр GIMSK (стр.23 DataSheet)
значение 0х40
вот
так просто...
включаем - выключаем, прерываниями МК управляем!
Итак МК стартовал и залетел в прерывание
по переполнению таймера0 - давайте
укажем ему что нужно делать в этом
состоянии.
Заполним "тело" функции - обработчика этого прерывания
кодом программы на Си: void
timer0_ovf_isr(void)
{
// MK прервался и мы
попали сюда!
// Значит TIMER0 перескочил с 255
на 0
TCNT0 = 0xB8;
// в регистр
TCNT0 записали 184
//нам ведь нужно
считать со 184...
//..пошел отсчет
очередных 20 мс
get_period();
//..измерить период send_result();
//..отправить
результат по rs232 save_result();
//.. сохранить в
память
}
Перезапустили отсчет очередных 20 мС и
попали в функцию "измерить период" -
давайте сочиним и для этой функции "тело": ...у
меня вот такое тело получилось:
void get_period(void)
{
// очистим флаг прерывания INT0
// записью "1" в бит_6 рег. GIFR
GIFR |= 0x40; // turn on bit6
// см. Help раздел
"Bit Twiddling"
// операции
с битами - распечатайте!!!
// включить прерывание по спаду INT0
GIMSK = 0x40;
while(no_INT0); // ждем прерывания по
// очередному спаду сигнала
// мы здесь, значит прерывание случилось
no_INT0 = 1;
// восстановим признак ожидания INT0
GIFR |= 0x40; // очистим флаг INT0
// начнем счет периода сигнала
// до следующего спада сигнала
while((no_INT0) && (result < max_count))
{
result++; // увеличиваем на 1
}
// из цикла вылетим при прерывании
INT0
// по очередному спаду или если период
// слишком большой то по достижении
// числа max_count
// ВЫКЛ. прерывание INT0
GIMSK = 0x00; // можно = 0;
no_INT0 = 1;
// восстановим признак ожидания INT0
} эта
функция свое дело сделала - переменная
содержит число пропорциональное
периоду входного сигнала.
Кстати о переменных - я применил две глобальные
- т.е. видимые во всех функциях
программы - такие переменные объявим
сразу после #include <macros.h> //////////////////////////
/// глобальные переменные
int result; // период сигнала - два байта
char no_INT0=1; // INT0 обнуляет эту переменную Возможно,
со временем их станет больше...
я не опытный программист по этому
применяю в основном глобальные
переменные - это не правильно, но мне
так легче... Функция
- обработчик прерывания INT0 вот такая:
void int0_isr(void)
{
//external interupt on INT0
no_INT0 = 0;
// обнулили признак "no_INT0"
}
Здесь мы делаем признак no_INT0 нулем т.е.
"ложно" - чтобы выскочить из цикла while
в котором нас застало это прерывание.
Вы
заметили еще не определенное число
max_count - это страховка
срабатывающая при отсутствии сигнала
или слишком низкой частоте входного
сигнала
Будем считать таким сигнал с частотой
ниже 500 Гц. т.е. с периодом более 2 мС
Рассчитаем значение числа max_count
- цикл while со сложным условием
займет наверно 9 тактов (уточним при
эмуляции)
- МК тикает 3686.4 х 2 = 7373 раз за 2 мС
- делим 7373 на 9 получим 819 если наши
предположения по длительности цикла
верны то за 2 мс мы насчитаем 820 а при
частоте 2000 Гц мы насчитаем в 4 раза
меньше т.е. 205.
Возьмем для начала max_count
= 900 а по результатам эмуляции
подкорректируем это число:
# define max_count 900
напишем
сразу после #include <macros.h>
Теперь если результат
получится 900 - значит сигнал или
отсутствует или он слишком
низкочастотный.
На очереди функция void send_result(void)
вывод результата в последовательном
виде на ножку 3 МК PD1 (TxD). выводить
результат будем с новой строки, по два
байта старший, затем, через пробел,
младший байт: void send_result(void)
{
temp = result; // спасли результат
low_result=(temp & 0x00FF);
// уничтожили старшие 8 бит и
// получили младший байт результата
temp = result;
hi_result= temp >> 8; // сдвинули на 8 бит вправо
// получили стрший байт результата
putchar(0x0D); // на новую строку
// .. нет таблицы под рукой - могу ошибаться hi_result
= + 65; // вместо 0,1,2,3 мы
получим
// символы A B C D
putchar(hi_result);
// вывест старший байт как A B C
или D
putchar(' ');
// вывести пробел
putchar(low_result);
// вывести младший байт как
повезет
} Все вроде
хорошо, НО! если мы будем принимать
данные в специальной программе, а если
мы хотим получать данные в терминальной
программе то возникнут траблы - грабли...
младший байт результата может оказаться
служебным непечатаемым символом (ищи и
смотри таблицу символов) и тогда
мы увидим только символ старшего байта -
его я защитил добавлением числа 65, это
превратит 0, 1, 2 и 3 (для чисел до 900
возможны только эти значения) в символы A
B C и D.
Как решить эту
проблему?
ЗАДАНИЕ -
напишите функцию, или придумайте
алгоритм превращающей двух байтовое
число в набор символов - цифр: например
число 856 в символы '8' '5' и '6' - это поможет
нам при эмуляции легко ориентироваться
выводя числа на терминал в привычном
десятичном виде..
Обратите внимание на новые переменные:
char low_result; // младший байт результата
char hi_result; // стрший байт результата
int temp; //
сохранитель результата
Функцию
сохранения результата
в памяти оставляем пока пустой.
Эмуляцию программы попробуйте
сделать самостоятельно. можете
скачать рабочие файлы
в архиве work04_1.zip
Пошли
дальше... еще вариант!
Для измерения периода более эффективно
применение не задействованного Timer1 -
это 16 битный таймер, давайте сделаем
чтобы он считал со скоростью тиканья
кварца и по первому прерыванию INT0
будем запускать таймер1 с 0х0000 а по
второму останавливать и считывать два
байта результата. Нам
нужно узнать, как управлять этим
таймером - читайте DataSheet все что
касается Timer1.
Есть еще один способ научится управлять периферией
- использовать АпБилдер - генерируя код
для разных стартовых состояний Timer1 мы
увидим какие биты и в каких регистрах им
управляют. Запускаем
ICC и АпБилдер.
Установим все настройки какие были у нас
и нажмем "Preview" - скопируйте текст в
отдельный файл _no_timer1.txt для
последующего просмотра. закройте окно
кода.
Перейдем на закладку: Timer1
- включите "Use Timer1"
- выберите коэф. деления "1" - таймер
будет считать с частотой процессора.
"Preview" код скопируйте в файл _timer1_on.txt
Давайте посмотрим, что изменилось в коде:
ага, появилась функция инициализации
Timer1:
//TIMER1 initialisation - prescale:1
// desired value: 1Hz
// actual value: Out of range
void timer1_init(void)
{
TCCR1B = 0x00; //stop timer
//set count value
TCNT1H = 0x00 /*INVALID SETTING*/;
TCNT1L = 0x00 /*INVALID SETTING*/;
// с какого числа считать ...
// мы будем с нуля считать
//set compare value
OCR1H = 0x00 /*INVALID SETTING*/;
OCR1L = 0x00 /*INVALID SETTING*/;
// режим сравнения - мы
использовать не будем
TCCR1A = 0x00;
TCCR1B = 0x01; //start Timer
} Смущают
только: *INVALID SETTING* -
придется почитать DataSheet на предмет
допустимости таких значений. Изучите
рисунок 30 - схему таймер1.
Эти сообщения вызваны тем что указав
Билдеру коэф. пред. делителя "1" мы
оставили значение частоты переполнений
1 Гц - что не возможно выполнить - вот он и
ругается.
Почитали: 0x00 являются допустимыми
значениями регистров - такими они
являются при запуске МК.
Смотрим регистры TCNT1H TCNT1L стр. 33 DataSheet
- в них содержится собственно насчитанное
значение!
Мы будем запускать счет с нуля по
первому прерыванию INT0 , а по второму INT0
или по достижения числа max_count в цикле
while(...) (это означает низкую частоту
синала), остановим Timer1 и прочитаем в
регистрах TCNT1H и TCNT1L результат -
число пропорциональное периоду сигнала.
Регистр TCCR1B стр. 32 DataSheet - три
младших бита 0_2 этого регистра управляют
запуском и коэф. деления см. таблицу на
стр. 33 DataSheet - нас интересуют два
сочетания этих бит:
000 - остановить Timer1
001 - запустить Timer1 с коэф деления "1"
Регистр TCCR1A
мы не используем.
Пожалуйста прочитайте и поймите, что
связано с этим регистром в МК, это нужно
вам! Давайте
включим в функцию инициализации "железа"
в нашей программе, строчки посвященные
Timer1: void init_devices(void)
{
// нужно отключить
прерывания
// на время инициализации
CLI(); // запретить все
прерывания
port_init();
timer0_init();
uart0_init(); TCCR1B = 0x00;
// остановить таймер1
TCNT1H = 0x00;
TCNT1L = 0x00;
// с какого числа считать ...
// мы будем с нуля считать
TCCR1A = 0x00;
// Мы не запустили Таймер1
// пока
нам он не нужен
MCUCR = 0x02;
GIMSK = 0x00; // INT0 отключено
// 0x40 - включено
TIMSK = 0x02;
SEI(); //
разрешить прерывания
}
Теперь давайте напишем новую функцию
измерения периода сигнала, но для
"мгновенного" старта и остановки
таймера будем его включать - выключать
инвертированием бита в функции -
обработчике прерывания - соответственно
начнем изменения с неё:
обработчик
прерывания INT0 будет таким:
void int0_isr(void)
{
//
инвертируем бит_1 регистра TCCR1B
TCCR1B^=0x01;
// если
таймер1 стоял - он начал отсчет
// если
таймер1 считал - он остановится //
реакции на INT0 почти мгновенны!
Смотрим
Help компилятора:
Bit Twiddling - нас интересует "flip bit" |
no_INT0 = 0;
// обнулили признак " no_INT0"
}
Запуск и останов таймера1 произойдут с
одинаковой задержкой после
прерывания - значит результат будет
пропорционален периоду сигнала с
высокой точностью!
Задержка - это время реакции МК на
прерывание, плюс вызов функции, плюс "переворот"
бита. Выше
мы посчитали, что МК тикает 3686.4 х 2 = 7373 раз за 2 мС.
каждый "тик" занимает (1/3.6864) мкС
Период входного
сигнала будет равен:
(число в таймере1 / 3.6864 ) мкС
Перепишем функцию измерения периода
сигнала:
void get_period(void)
{
// очистим флаг прерывания INT0
// записью "1" в бит_6 рег. GIFR
GIFR |= 0x40; // turn on bit6
"Флаг"
необходимо "очистить" потому,
что в AVR (так и в большинстве других
МК) флаги выставляться по
возникновении события не зависимо
от того - разрешено ли прерывание!
Это
надо учитывать и,
этим можно пользоваться! |
// включить прерывание по спаду INT0
GIMSK = 0x40;
while(no_INT0); // ждем прерывания по
// очередному спаду сигнала
// мы здесь, значит прерывание случилось
// Taimer1 считает с нуля
no_INT0 = 1;
// восстановим признак ожидания INT0
GIFR |= 0x40; // очистим флаг INT0
counter = 0;
// ждем следующего спада
сигнала
// и следим за его
длительностью
while((no_INT0) && (counter < max_count))
{
counter ++; // увеличиваем на 1
}
// !!! счетчик
переменная "counter" !!!
// из цикла вылетим при прерывании
INT0
// по очередному спаду или если период
// слишком большой то по достижении
// числа max_count
// ВЫКЛ. прерывание INT0
GIMSK = 0x00; // можно =0;
no_INT0 = 1;
// восстановим признак ожидания INT0 //
возможно сигнал слишком низкочастотный
и
// мы вылетели раньше второго прерывания
//
значит нужно остановить таймер1
TCCR1B = 0x00;
// теперь
сохраним результат измерения:
low_result = TCNT1L; // младший байт результата
hi_result
= TCNT1H; // стрший байт результатая
Важно!
Первым читатьTCNT1L а затем TCNT1H
При записи в этот регистр -
последовательность обращения к
байтам - противоположная!
см. стр. 34 DataSheet - особенности в
обращении к 16-битным устройствам AVR!
|
// восстановим
ноль для следующего отсчета
TCNT1H = 0x00;
TCNT1L = 0x00;
} В этой
функции мы измерили период входного
сигнала - теперь старший и младший байты
результата хранятся в переменных low_result
и hi_result,
а переменная counter позволит нам
судить о достаточности частоты сигнала -
если она равна max_count значит сигнал
слишком медленный.
Timer1 - остановлен и сброшен в ноль.
Мы готовы, как пионеры, к следующему
измерению.
Давайте модифицируем функцию вывода
данных по rs232 - теперь результат будет
выводится с новой строки в десятичном
виде по 4 цифры, а при низкой частоте
сигнала будет выводится сообщение "NO-F"
Значит при эмуляции программы без
входного сигнала мы должны получать в
терминале сообщения "NO-F" каждые 20
мс.
Поживем, увидим...
Пишем функцию
вывода по новой:
void send_result(void)
{
// если
частота более 500 гц
if (counter < max_count)
{
// получим результат как 2-байтовую
величину
result = 0; // обнулили результат
result += hi_result;
// прибавили старший байт
result << 8; //
продвинули старший байт на
// 8 позиций в лево - на место старшего байта result
+= low_result; //
прибавили младший байт //
теперь result содержит результат
измерения //
будем выводить его по одной цифре: dig_out=0;
// цифра для вывода
по rs232
while (result >= 1000)
{
result -= 1000; //
уменьшаем на 1000
dig_out ++;
// посчитали
тысячу
}
putchar(0x0D);
// перешли на новую строку putchar(dig_out);
// вывели цифру "тысячи" dig_out
= 0; //
обнулили while
(result >= 100)
{
result -= 100; //
уменьшаем на 100
dig_out ++;
// посчитали
сотню
}
putchar(dig_out);
// вывели цифру "сотни" dig_out
= 0; //
обнулили while
(result >= 10)
{
result -= 10; //
уменьшаем на 10
dig_out ++;
// посчитали
десятки
}
putchar(dig_out);
// вывели цифру "десятки" dig_out
= 0; //
обнулили while
(result >= 1)
{
result -= 1; //
уменьшаем на 1
dig_out ++;
// посчитали
единицы
}
putchar(dig_out);
// вывели цифру "единицы"
} //
если частота мене 500 гц
if (!(counter < max_count))
{ //
выводим посимвольно
"NO-F" с новой строки
putchar(0x0D);
// перешли на новую строку putchar('N');
putchar('O');
putchar('-');
putchar('F');
// вывели
надпись
}
} //
конец функции
похоже
сделали все что хотели...
скопируйте новый текст функции в
программу
и добавьте переменную:
char dig_out; //
цифра для вывода по rs232
Откомпилировал
- ошибок нет,
использовано 44% ресурсов МК.
можете
скачать рабочие файлы
в архиве work04_2.zip
Эмуляция
Эмуляция не
порадовала - программа не заработала,
пришлось искать ошибки... Нудно и долго...
Однако все нашел:
1) Сколько раз читал и в коде АпБилдера
написано прежде чем разрешить какое-то
прерывание нужно:
- запретить все прерывания CLI();
- разрешить новое прерывание
- вновь рзрешить все прерывания SEI();
Я этого не сделал и прерывание INT0 по
сигналу не включалось - программа
зависала на месте.
2) не учел что код чисел 1,2,3 ... это 49,50,51 ...
посему в терминале шла всякая бяка...
прибавил 48 во всех выводах цифр на
терминал:
putchar(dig_out + 48);
2) не
правильно записал операцию сдвига
битов в лево на 8 позиций:
написал так: << 8; исправил
на: <<= 8;
3) при низкочастотном сигнале зависали в
первом while в функции get_period()
- добавил и там вылет при большом периоде
и еще добавил if () и goto для
обработки этой ситуации 4)
подкорректировал число max_count по
результату эмуляции на 315 - при этом
измеряются периоды сигнала не
превышающие 2000 мкС - в противном случае
выводятся сообщения в терминал "NO_F"
в общем программа ожила и заработала по предписанному
алгоритму. скачайте
исправленные файлы компилятора и
эмулятора в архиве work04_3.zip смотрите
код - эмулируйте, можете изменять
входную частоту, посчитайте и убедитесь
в пропорциональности результата и
периода входного сигнала.
Кстати максимальная частота
входного сигнала значительно больше 2
кГц - попробуйте ее определить!
Захотите
зашить программу в МК
смотрите раздел курса о прошивании.
|