Программа на Си начинает работу с функции main()
В функции число из peremennaya будет помещено в val_d и обработано. Int funkziya(int val_d) { return ((val_d>511)?(-1024+val_d):(val_d)); } ((выражение)? (если выражение истина): (если выражение ложно)) 8. Структура программы на Си Программа на Си имеет определенную структуру:
1) заголовок
2) включение необходимых внешних файлов - #include
3) ваши определения для удобства работы - #define
4) объявление глобальных переменных и констант
Глобальные переменные и константы
- объявляются вне какой либо функции. т.е. не после фигурной скобки {
- доступны в любом месте программы - значит можно читать их значения и присваивать переменным значения там где вам требуется - в любом месте программы после их объявления.
5) описание функций - обработчиков прерываний
6) описание других функций используемых в программе
7) функция main <- это единственный обязательный пункт! Программа на Си начинает работу с функции main() по необходимости из main()вызываются другие функции программы, из которых может быть вызов следующих функций, по завершении работы функции программа возвращается по той же цепочке как вызывались функции.
main(){
... какой то код программы...
вызов функции_1; //программа перейдет в функцию_1
строка программы; // будет выполнятся после // возврата из функции_1
... какой то код программы...
} Пример программы: /* пункт 1 заголовок программы
Он оформляется как комментарий, и обычно содержит информацию
- о названии, назначении, версии и авторе программы - краткое описание алгоритма программы - пояснения о назначении выводов МК и режиме его работы, фьюзы - компилятор, инструменты и их версии
- другие сведения которые вы считаете полезным указать
*/
// комментарий после двух косых черт пишут в одну строку!
// пункт 2 включение внешних файлов
#include <mega16.h> /* перед компиляцией, препроцессор компилятора вставит вместо этой строчки содержимое (текст) заголовочного файла "хидера" mega16.h - этот файл содержит перечень регистров имеющихся в МК ATmega16 и соответствие их названий их физическим адресам в МК.
Посмотрите его содержание!!!
CVAVR\inc\mega16.h
Не забывайте указать какой МК вы используете в свойствах проекта в компиляторе */
//delay functions #include <delay.h> /* перед компиляцией, препроцессор компилятора вставит вместо этой строчки текст "хидера" delay.h - этот файл содержит функции для создания пауз в программе.
Теперь чтобы сделать паузу вам нужно лишь написать: */
delay_us(N); /* сделать паузу N (число) микроСек это должна быть константа - НЕ переменная!!! например delay_us(12 + 7*3); например delay_us(117); */
delay_ms(x); /* сделать паузу x милиСек x - может быть переменная, выражение или число от 0 до 65535 (тип unsigned int) например delay_ms(3280); например delay_ms(переменная); например delay_ms(переменная*4 + 760); */
// пункт 3 определения пользователя
// AD7896 control signals PORTB bit allocation #define ADC_BUSY PINB.0 #define NCONVST PORTB.1 /* после этих двух строк, перед компиляцией, препроцессор компилятора заменит в тексте программы ADC_BUSY на PINB.0 и NCONVST на PORTB.1
Таким образом вместо того что бы помнить что вывод занятости AD7896 подключен у вас к ножке PB0 вы можете проверять значение осмысленного понятия ADC_BUSY - "АЦП занят"
а вместо управления абстрактной ножкой PB1 (через PORTB.1) вы можете управлять "НьюКонвешнСтат" - NCONVST - "стартовать новое АЦ преобразование"
#define - Это удобно! Но ВОВСЕ не обязательно. */
#define INIT_TIMER0 TCNT0=0x100L-F_XTAL/64L/500L /* этот пример показывает что определения могут быть и сложней! */
#define - может содержать и некоторые переменные, вместо которых в тексте программы могут быть подставлены и числа и слова. Может определять даже сложные, полноценные функции.
Например: #define invbit(p,n) (p=p^bit(n))
Здесь переменные величины это 'p' и 'n'. Теперь для инвертирования бита 5 в регистре PORTB вам достаточно написать в программе:
invbit(PORTB,5);
Кроме того в самой правой части эти переменные величины могут быть связаны и арифметическими операциями и таких переменных может быть много.
Примеры #define есть в FAQ к курсу.
Определения БИТ-ов AVR (соответствие номера бита в регистре его названию по ДШ) есть в "хидерах" .h в компиляторах ICC, IAR, WinAVR и других компиляторах,
Но их нет в хидерах CodeVisionAVR - это не позволяет напрямую вставлять в текст программы примеры кода из даташит МК
Поэтому я сделал для вас файл - заголовок m8_128.h содержащий определения битов некоторых AVR.
Скачайте его и добавьте в программу вот так:
(Если вы читаете курс с начала и делаете то, что предлагается то этот файл у вас уже есть).
#include <mega16.h> /* сперва обычный "хидер"-заголовок для МК используемого в вашей программе */
#include <m8_128.h> /* затем мой "хидер"-заголовок с определениями названий и номеров битов для используемого МК */
Теперь вы можете использовать примеры на Си из ДШ на ваш МК!
9. Объявление переменных
[<storage modifier>]- необязательный элемент, он нужен толон нужен только в некоторых случаях и может быть:
extern - если переменная может использоваться в других файлах исходного кода программы, например объявляется во внешнем файле - хидере delay.h приведенном выше, а используется в основном файле программы.
volatile - ставьте если нужно предотвратить возможность повреждения содержимого переменной в прерывании, и не позволить компилятору попытаться выкинуть её при оптимизации кода.
Ставьте всегда если не знаете точно - нужно или нет!
пример: volatile unsigned char x;
static - если переменная локальная т.е. объявлена в какой либо функции после скобки { и должна сохранять свое значение до следующего вызова этой функции.
register - разместить переменную в регистрах AVR - это может ускорить доступ к ней. CVAVR по-умолчанию размещает переменные в регистрах до их заполнения. Но размещение переменных в регистрах делает их не видимыми при отладке в PROTEUS.
eeprom - разместить переменную в EEPROM. Это энергонезависимая память - значение таких переменных сохраняется при выключении питания и при перезагрузке МК.
пример: eeprom unsigned int x;
Если это первая переменная в EEPROM то её младший байт будет помещен в ячейку 1 EEPROM а старший в ячейку 2. Ячейка 0 не используется так как рекомендует производитель. CVAVR похоже не использует и 0 и 1 ячейки EEPROM. Необходимо помнить что запись в EEPROM длительный процесс - по таблице "Table 1. EEPROM Programming Time" это 8500 тактов процессора.
Количество записей в ячейки EEPROM ограничено!
Подробней в "Accessing the AVR internal EEPROM".
<identifier> - имя переменной - некоторый набор символов по вашему желанию, но не образующий зарезервированные слова языка Си.
Выше был уже пример идентификатора - имени переменной: Imya_peremennoi
Строки, массивы Вот несколько примеров объявления переменных:
unsigned char my_peremen = 34; unsigned int big_peremen = 34034;
Это объявлены две переменные и им присвоены значения.
Первая my_peremen - символьного типа - это 1 байт, она может хранить число от 0 до 255. В данном случае в ней хранится число 34.
Вторая big_peremen - целого типа, два байта, в ней может хранится число от 0 до 65535, а в примере в неё поместили десятичное число 34034.
Пример массива содержащего 3 числа или элемента массива.
char mas[3]={11,22,33};
Нумерация элементов начинается с 0. Т.е. элементы данного массива называются
mas[0], mas[1], mas[2]
и в них хранятся десятичные числа 11, 22 и 33.
Где то в программе вы можете написать:
mas[1] = 210;
Теперь в mas[1] будет хранится число 210
- массивы могут быть многомерными, - можно не присваивать значений элементам массива при объявлении.
НО только при объявлении вы можете присвоить значения всем элементам массива сразу! Потом это можно будет сделать только индивидуально для каждого элемента.
Строковая переменная или массив содержащий строку символов.
char stroka[6]="Hello";
Символов (букв) между кавычками 5, а я указал размер строки 6!
Дело в том, что строки символов должны заканчиваться десятичным числом 0.
Не путайте его с символом '0' которому соответствует десятичное число 48 по таблице ASCII - которая устанавливает каждому числу определенный символ.
Например:
Элемент строки stroka[1] содержит число 101 которому по таблице ASCII соответствует символ 'e'
Элемент stroka[4] содержит число 111 которому соответствует символ 'o'
Элемент stroka[5] содержит число 0 которому соответствует символ 'NUL' его еще обозначают вот так '\0'
Строковая переменная может быть "распечатана" или выведена в USART MK вот так: printf("%s\n", stroka);
КОНСТАНТЫ.
flash и const ставятся перед объявлением констант - неизменяемых данных хранящихся во флэш памяти программ. Они позволяют вам использовать не занятую программой память МК. Обычно для хранения строковых данных - различные информационные сообщения, либо чисел и массивов чисел.
КОНСТАНТЫ ПРИМЕРЫ из CVAVR help
flash int integer_constant=1234+5; flash char char_constant=’a’; flash long long_int_constant1=99L; flash long long_int_constant2=0x10000000; flash int integer_array1[ ]={1,2,3}; flash int integer_array2[10]={1,2}; flash int multidim_array[2][3]={{1,2,3},{4,5,6}}; flash char string_constant1[ ]=”This is a string constant”;
const char string_constant2[ ]=”This is also a string constant”;
В других компиляторах могут быть отличия!
10. Обработка прерываний! /* Конкретно в ЭТОЙ программе - есть только одно прерывание и значит одна функция обработчик прерывания.
Программа будет переходить на неё при возникновении прерывания:
ADC_INT - по событию "окончание АЦ преобразования"
*/
interrupt [ADC_INT] void adc_isr(void) { PORTB=(unsigned char) (~(ADCW>>2)); /* отобразить горящими светодиодами подключенными от + питания МК через резисторы 560 Ом к ножкам порта_B старшие 8 бит результата аналого-цифрового преобразования
Сделаем паузу 127 мСек - просто как пример пауз */ delay_ms(127);
/* В реальных программах старайтесь не делать пауз в прерываниях!
Обработчик прерывания должен быть как можно короче и быстрее.
Например - в обработчике прерывания вы только устанавливаете флаги (биты или переменная) означающие состояние кнопок, значения переменных или регистров, а обрабатываете это уже в основном цикле программы, через конструкции if - else или switch (описаны выше!)
*/
// начать новое АЦПреобразование ADCSRA|=0x40; } // закрывающая скобка обработчика прерывания
Функция обработчик прерывания может быть названа
вами произвольно - как и любая функция кроме main.
Здесь она названа: adc_isr
При каком прерывании ее вызывать - компилятор узнает из строчки:
interrupt[ADC_INT]
по первому зарезервированному слову - interrupt - он узнаёт, что речь идет об обработчике прерывания,
а номер вектора прерывания (адрес куда физически, внутри МК перескочит программа при возникновении прерывания) будет подставлен вместо ADC_INT препроцессором компилятора перед компиляцией - этот номер указан в подключенном нами ранее заголовочном файле ("хидере") описания "железа" МК - mega16.h - это число сопоставленное слову ADC_INT. Не ленитесь, посмотрите в файле!
Очень информативна и тем ценна для обучающегося следующая строка программы:
PORTB = (unsigned char) (~(ADCW >> 2));
Давайте проанализируем как она работает.
= оператор присваивания. Он означает присвоить значение вычисления выражения справа от оператора присваивания той переменной что указана слева от него.
Значит нужно вычислить выражение справа и поместить его в переменную PORTB.
Вычислим что справа от оператора присваивания.
ADCW - это переменная слово (двухбайтовая величина - так она объявлена в файле mega16.h) в котором CodeVisionAVR сохраняет 10-битный результат АЦП - а именно в битах9_0 (биты с 9-го по 0-й) т.е. результат выровнен обычно - вправо.
VMLAB имеет только 8 светодиодов - значит нужно отобразить 8 старших бит результата - т.е. биты_9_2 - для этого мы сдвигаем все биты слова ADCW вправо на 2 позиции
ADCW >> 2 /* биты 1 и 0 вылетают вправо из числа в небытие, бит_9 перемещается в позицию бит_7, бит_8 в позицию бит_6 и так далее до бит_2 становится бит_0 */
Теперь старшие 8 бит результата АЦП встали в биты7_0 младшего байта (LowByte - LB) слова ADCW
Светодиоды подключены так как написано выше - т.е. подключены правильно!
Загораются (показывая "1") при "0" на соответствующем выводе МК - значит нам нужно выводить в PORTB число в котором "1" заменены "0" и наоборот - это делает как я рассказал выше:
~ операция побитного инвертирования - меняет значения битов.
Значит результатом этого выражения
~(ADCW >> 2)
будут инвертированные 8 старших бит результата АЦП находящиеся в младшем (правом - LB) байте двух байтового слова ADCW
Выше я уже говорил что:
в Си в переменную можно помещать только тот тип данных который она может хранить!
Так как PORTB это байт, а ADCW - это два байта, то прежде чем выполнить оператор присваивания (это знак =) нужно преобразовать слово (слово - word - значит два байта) ADCW в без знаковый байт. Преобразование типов данных - делают так:
перед тем что надо преобразовать записывают в скобках () тип данных к которому нужно преобразовать.
Пишем...
(unsigned char) (~(ADCW>>2))
Результат этой строки - один байт и мы можем поместить его в PORTB
Если в регистре DDRB все биты равны "1" - т.е. все ножки порта_B выходы, мы безусловно увидим старшие 8 бит результата АЦП горящими светодиодами.
11. Обращение к регистрам Делать что-то пока на ножке PBn есть "1" while (PINB.n){ }; Выполнить что-то если на ножке PINC.n “0” if((~PINC)&(1 << n)){ };
В CVAVR можно написать проще:
if(!(PINC.n)){ };
Помните! Выполнение чего-то может быть прервано прерыванием. После завершения обработки прерывания выполнение чего-то продолжится. Примечание - Условие:
if((~PINC)&(1 << n)) { };
можно записать и вот так:
if(!(PINC & (1 << n))) { };
Пример: выполнить что-то если на ножке PBn есть "1"
if((PINB)&(1 << n)){ };
примечание - в CVAVR можно написать проще if(PINB.n){ };
Общая структура программы
Заголовок программы
Он оформляется как комментарий, и обычно содержит информацию
- о названии, назначении, версии и авторе программы - краткое описание алгоритма программы - пояснения о назначении выводов МК и режиме его работы, фьюзы - компилятор, инструменты и их версии - другие сведения которые вы считаете полезным указать
Включение внешних файлов
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|