AVR123:Глава 5 — различия между версиями
Nekolex (обсуждение | вклад) |
Nekolex (обсуждение | вклад) |
||
(не показаны 4 промежуточные версии этого же участника) | |||
Строка 454: | Строка 454: | ||
<font color="#3366ff">Ходовые конструкции на Си</font> | <font color="#3366ff">Ходовые конструкции на Си</font> | ||
− | В компиляторе '''[http://avr123.nm.ru/CVAVR CVAVR]''' заготовки этих конструкций находятся под ярлыком "'''Code Templates'''" слева вверху. Вы можете выбирать нужные заготовки и вставлять их в свою программу. | + | В компиляторе '''[[http://avr123.nm.ru/CVAVR CVAVR]''' заготовки этих конструкций находятся под ярлыком "'''Code Templates'''" слева вверху. Вы можете выбирать нужные заготовки и вставлять их в свою программу. |
[[Image:05 temp.png]] | [[Image:05 temp.png]] | ||
Строка 585: | Строка 585: | ||
; break; :- если его не использовать то найдя нужный вариант программа будет выполнять и следующие ниже условия case | ; break; :- если его не использовать то найдя нужный вариант программа будет выполнять и следующие ниже условия case | ||
− | [http://www.citforum.ru/programming/c/h14.shtml#145 Прочитайте]подробней о ''switch'' с примерами. | + | [[http://www.citforum.ru/programming/c/h14.shtml#145 Прочитайте]подробней о ''switch'' с примерами. |
− | [http://avr123.nm.ru/ascii.pdf Скачайте и распечатайте таблицу символов ASCII на ОДНОЙ странице!] | + | [[http://avr123.nm.ru/ascii.pdf Скачайте и распечатайте таблицу символов ASCII на ОДНОЙ странице!] |
+ | |||
+ | 9) goto - оператор безусловного (немедленного) перехода. | ||
+ | <source lang="C"> | ||
+ | ... какой-то код нашей программы на Си ... | ||
+ | |||
+ | mesto_5: /* сюда мы попадем после выполнения строки программы goto mesto_5 */ | ||
+ | код будет выполнятся после goto mesto_5; | ||
+ | |||
+ | ... какой-то код нашей программы на Си ... | ||
+ | |||
+ | mesto_1: /* сюда мы попадем после выполнения строки программы goto mesto_1 */ | ||
+ | код будет выполнятся после goto mesto_1; | ||
+ | |||
+ | ... какой-то код нашей программы на Си ... | ||
+ | |||
+ | goto mesto_1; /* перейти в то место программы где в начале строки написано mesto_1: */ | ||
+ | |||
+ | ... какой-то код нашей программы на Си ... | ||
+ | |||
+ | goto mesto_5; /* перейти в то место программы где в начале строки написано mesto_5: */ | ||
+ | |||
+ | ... какой-то код нашей программы на Си ... | ||
+ | </source> | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | Используйте goto с осторожностью! Думайте к чему может привести выполнение функций или конструкций вашей программы не до конца. | ||
+ | |||
+ | CVAVR помогает вам в контроле не позволяя где угодно втыкать goto | ||
+ | |||
+ | |||
+ | Оператор Си ? работает почти как if - вот так: | ||
+ | |||
+ | Пример функция в которую передается значение переменной val_d | ||
+ | |||
+ | Вызов функции и передач в нее числа хранящегося в переменной с именем peremennaya | ||
+ | |||
+ | resultat = funkziya(peremennaya); | ||
+ | |||
+ | |||
+ | |||
+ | В функции число из peremennaya будет помещено в val_d и обработано. | ||
+ | |||
+ | int funkziya(int val_d) | ||
+ | { | ||
+ | return ((val_d>511)?(-1024+val_d):(val_d)); | ||
+ | } | ||
+ | |||
+ | ( (выражение) ? ( если выражение истина ) : ( если выражение ложно ) ) | ||
+ | |||
+ | если val_d>511 то функция возвратит val_d уменьшенное на 1024 | ||
+ | |||
+ | если val_d=<511 то функция возвратит val_d не меняя его. | ||
+ | |||
+ | Возвращаемое функцией значение val_d будет помещено в переменную в строке вызова функции - resultat | ||
+ | |||
+ | Подробнее о функциях будет написано ниже. | ||
+ | |||
+ | |||
+ | Ну вот - ПОЧТИ | ||
+ | всё что нужно нам из Си ! | ||
+ | |||
+ | |||
+ | Как использовать описанное выше вы можете | ||
+ | посмотреть в примерах к компилятору ! | ||
+ | |||
+ | Примеры в папке : | ||
+ | |||
+ | C:\CVAVR\EXAMPLES | ||
+ | |||
+ | Открывайте файлы исходников .с и разбирайте текст программ - | ||
+ | что делает каждая строчка кода ! | ||
+ | |||
+ | Это великолепный способ | ||
+ | само-обучения программированию ! | ||
+ | |||
+ | Новичку понадобятся для понимания программ написанных профи : | ||
+ | |||
+ | Указатели, Структуры и Союзы. | ||
+ | |||
+ | Прочитайте о них в он-лайн книгах по Си которые расположены ниже на страничке. Примеры применения указателей, структур и союзов в разных компиляторах вы найдете в FAQ к курсу по AVR - скачайте и читайте. | ||
+ | |||
+ | ==Структура программы на языке Си.== | ||
+ | |||
+ | Программа на языке Си это текстовый файл, обычно с расширением .c | ||
+ | |||
+ | Текст программы называют исходным или "исходником" или "сурцом" | ||
+ | от анг. source code - это вам ключевые слова для поиска | ||
+ | |||
+ | Весь исполняемый код программы на Си находится в функциях - | ||
+ | т.е. в фигурных скобках { исполняемый код программы } | ||
+ | |||
+ | |||
+ | |||
+ | ===Программа на Си имеет определенную структуру :=== | ||
+ | |||
+ | 1) заголовок | ||
+ | |||
+ | 2) включение необходимых внешних файлов - #include | ||
+ | |||
+ | 3) ваши определения для удобства работы - #define | ||
+ | |||
+ | 4) объявление глобальных переменных и констант | ||
+ | |||
+ | Глобальные переменные и константы | ||
+ | |||
+ | - объявляются вне какой либо функции. | ||
+ | т.е. не после фигурной скобки { | ||
+ | |||
+ | - доступны в любом месте программы - значит можно читать их значения и присваивать переменным значения там где вам требуется - в любом месте программы после их объявления. | ||
+ | |||
+ | 5) описание функций - обработчиков прерываний | ||
+ | |||
+ | 6) описание других функций используемых в программе | ||
+ | |||
+ | 7) функция main <- это единственный обязательный пункт ! | ||
+ | |||
+ | |||
+ | |||
+ | Это не жесткий порядок а ориентировочный ! | ||
+ | |||
+ | Иногда п.6 это прототипы функций, а сами функции | ||
+ | описываются полностью после п.7 | ||
+ | |||
+ | Прототип функции - показывает образец того как применять функцию в программе, какие значения в нее передаются и если она возвращает какое-то значение то прототип указывает тип возвращаемых данных. Прототип не имеет скобок { } а после скобок ( ) ставится ; | ||
+ | |||
+ | Функция - имеет { "тело" } в фигурных скобках. Тело это код на Си определяющий то что делает функция. | ||
+ | |||
+ | ; после вызова функции не ставится ! | ||
+ | |||
+ | Программа на Си начинает работу с функции main() | ||
+ | |||
+ | по необходимости из main()вызываются другие функции программы, из которых может быть вызов следующих функций, по завершении работы функции программа возвращается по той же цепочке как вызывались функции. | ||
+ | <source lang="C"> | ||
+ | main(){ | ||
+ | |||
+ | ... какой то код программы ... | ||
+ | |||
+ | вызов функции_1; //программа перейдет в функцию_1 | ||
+ | |||
+ | строка программы; // будет выполнятся после | ||
+ | // возврата из функции_1 | ||
+ | |||
+ | ... какой то код программы ... | ||
+ | |||
+ | } | ||
+ | </source> | ||
+ | На странице 3. "Прерывания в AVR" - вы уже читали, что описанный выше ход программы может нарушаться прерываниями. | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ==Пример программы на Си== | ||
+ | |||
+ | с описанной выше структурой я буду писать на голубом фоне. | ||
+ | |||
+ | По мере надобности я буду разрывать голубой фон обычным текстом, | ||
+ | затем голубой фон и программа будет продолжаться. | ||
+ | |||
+ | Ниже будет написан пример ОДНОЙ программы на Си. | ||
+ | |||
+ | <source lang="C"> | ||
+ | /* пункт 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 | ||
+ | <source lang="C"> | ||
+ | Таким образом вместо того что бы помнить что вывод занятости AD7896 подключен у вас к ножке PB0 вы можете проверять значение осмысленного понятия ADC_BUSY - "АЦП занят" | ||
+ | |||
+ | а вместо управления абстрактной ножкой PB1 (через PORTB.1) вы можете управлять "НьюКонвешнСтат" - NCONVST - "стартовать новое АЦ преобразование" | ||
+ | <source lang="C"> | ||
+ | #define - Это удобно ! Но ВОВСЕ не обязательно. | ||
+ | */ | ||
+ | #define INIT_TIMER0 TCNT0=0x100L-F_XTAL/64L/500L | ||
+ | /* этот пример показывает что определения могут быть и сложней ! */ | ||
+ | </source> | ||
+ | |||
+ | #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. | ||
+ | |||
+ | Скачайте его и добавьте в программу вот так: | ||
+ | |||
+ | (Если вы читаете курс с начала и делаете то, что предлагается то этот файл у вас уже есть). | ||
+ | <source lang="C"> | ||
+ | #include <mega16.h> | ||
+ | /* сперва обычный "хидер"-заголовок для МК | ||
+ | используемого в вашей программе */ | ||
+ | |||
+ | #include <m8_128.h> | ||
+ | /* затем мой "хидер"-заголовок с определениями | ||
+ | названий и номеров битов для используемого МК */ | ||
+ | </source> | ||
+ | |||
+ | Теперь вы можете использовать примеры | ||
+ | на Си из ДШ на ваш МК ! | ||
+ | |||
+ | Мой файл m8 128.h содержит определения битов для микроконтроллеров | ||
+ | ATmega8 ATmega16 ATmega32 ATmega64 ATmega128 | ||
+ | |||
+ | |||
+ | |||
+ | Для всех AVR определения битов есть в .h заголовках WinAVR | ||
+ | вот архив (127 Кб) скачайте и используйте если понадобится. | ||
+ | |||
+ | |||
+ | Мастер начального кода программы в компиляторе | ||
+ | ICC умеет по вашему желанию автоматически делать | ||
+ | #define для ножек для ножек МК ! | ||
+ | |||
+ | Подробней про это и с картинкой смотри в соответствующей | ||
+ | задаче курса. | ||
+ | |||
+ | Пункт 4. | ||
+ | |||
+ | ==Объявление переменных== | ||
+ | |||
+ | Перед использованием переменной в программе | ||
+ | на Си её необходимо объявить - т.е. указать компилятору | ||
+ | какой тип данных она может хранить и как она называется. | ||
+ | |||
+ | Наиболее подробно об этом по ссылке: | ||
+ | |||
+ | 1.2. ТИПЫ ДАННЫХ И ИХ ОБ ЯВЛЕНИЕ | ||
+ | |||
+ | Ниже сжато - самое главное: | ||
+ | |||
+ | Формат объявления переменной таков: | ||
+ | |||
+ | [<storage modifier>] <type definition> <identifier>; | ||
+ | |||
+ | |||
+ | ;[<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". | ||
+ | |||
+ | Книги и учебники по радиоэлектронике и микроконтроллерам там | ||
+ | |||
+ | Скачать весь курс по AVR одним архивом | ||
+ | |||
+ | |||
+ | |||
+ | Глобальные переменные объявляются до появления их в тексте какой либо функции. После объявления, глобальные переменные доступны в любой функции программы. | ||
+ | |||
+ | Локальные переменные объявляются в самом | ||
+ | начале функций - т.е. сразу после фигурной скобки { | ||
+ | |||
+ | Локальные переменные доступны только в той функции где они объявлены! В разных функциях могут быть объявлены локальные переменные с одинаковыми именами - я не советую вам так делать. | ||
+ | |||
+ | Советую не использовать ЛОКАЛЬНЫЕ переменные | ||
+ | в главной функции main. | ||
+ | |||
+ | |||
+ | |||
+ | <source lang="C"> | ||
+ | <type definition> - тип данных которые может хранить переменная. | ||
+ | |||
+ | наиболе часто используемые типы данных : | ||
+ | |||
+ | unsigned char - хранит числа от 0 до 255 (байт) | ||
+ | |||
+ | unsigned int - хранит числа от 0 до 65535 (слово == 2 байта) | ||
+ | |||
+ | unsigned long int - хранит от 0 до 4294967295 (двойное слово == 4 байта) | ||
+ | </source> | ||
+ | |||
+ | Подробнее все типы данных посмотрите в Help | ||
+ | |||
+ | CVAVR\bin\CVAVR.HLP | ||
+ | |||
+ | Раздел "Data Types" | ||
+ | |||
+ | Уточняйте ТИПЫ данных в руководстве к вашему компилятору ! | ||
+ | |||
+ | Вместо unsigned char - можно писать писать просто char, так как компилятор по-умолчанию считает char без знаковым байтом. | ||
+ | |||
+ | А если вам нужен знаковый байт то объявляйте его так : | ||
+ | |||
+ | signed char imya_peremennoi; | ||
+ | |||
+ | <identifier> - имя переменной - некоторый набор символов по вашему желанию, но не образующий зарезервированные слова языка Си. | ||
+ | |||
+ | Выше был уже пример идентификатора - имени переменной: | ||
+ | imya_peremennoi | ||
+ | |||
+ | |||
+ | |||
+ | Давайте осмысленные имена переменным и функциям - напоминающие, "говорящие" вам об их назначении. | ||
+ | |||
+ | Принято использовать маленькие буквы, а для отличия имен переменных от названия функций - имена переменных можно например начинать с буквы, а названия функций (кроме main конечно) с символа подчеркивания. | ||
+ | |||
+ | Например так : | ||
+ | |||
+ | moya_peremennaya _vasha_funkziya | ||
+ | |||
+ | Внимание! | ||
+ | |||
+ | Глобальные переменные, а также локальные с модификатором static - при старте и рестарте программы равны 0 если вы не присвоили им (например оператором =) иное значение при их объявлении или по ходу программы. | ||
+ | |||
+ | |||
+ | |||
+ | Подробные примеры объявления переменных посмотрите | ||
+ | пожалуйста в разделе Variables в "Хелп" компилятора CVAVR. | ||
+ | |||
+ | |||
+ | |||
+ | Вот несколько примеров объявления переменных : | ||
+ | |||
+ | 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); | ||
+ | |||
+ | Вы можете преобразовать строковую переменную в число! Если исходная строка символов такая : | ||
+ | |||
+ | char stroka[]="3654694"; | ||
+ | то вот так: | ||
+ | |||
+ | perem_1 = atoi(stroka); | ||
+ | |||
+ | мы поместим в переменную perem_1 (которую должны были ранее в программе объявить как "беззнаковую целую") число 36546. | ||
+ | Это число влезет в переменную perem_1 которая может хранить числа от 0 до 65535. | ||
+ | А вот 9 и 4 уже не поместятся. Для бОльших чисел есть функция - atol() | ||
+ | Чтобы использовать эти функции необходимо включить в начале программы заголовочный файл : | ||
+ | |||
+ | #include <stdlib.h> | ||
+ | |||
+ | |||
+ | Для преобразования числа в строку | ||
+ | |||
+ | есть itoa() и ltoa() | ||
+ | |||
+ | и аналогичные функции для чисел с плавающей точкой. | ||
+ | |||
+ | Подробнее об этих и других полезных функциях смотрите раздел | ||
+ | "Standard Library Functions" справки компилятора CodeVisionAVR. | ||
+ | |||
+ | Советую вам скачать заголовочный файл | ||
+ | |||
+ | m8 128.h | ||
+ | |||
+ | Он содержит названия битов МК ATmega8 -16 -32 -64 -128 | ||
+ | и сокращенные названия типов данных как в компиляторе IAR. | ||
+ | |||
+ | вот отрывок из него: | ||
+ | <source lang="C"> | ||
+ | #define u8 unsigned char // 0 to 255 | ||
+ | #define s8 signed char // -128 to 127 | ||
+ | |||
+ | #define u16 unsigned int // 0 to 65535 | ||
+ | #define s16 signed int // -32768 to 32767 | ||
+ | |||
+ | #define u32 unsigned long int | ||
+ | // 0 to 4294967295 | ||
+ | |||
+ | #define s32 signed long int | ||
+ | // -2147483648 to 2147483647 | ||
+ | |||
+ | #define f32 float // ±1.175e-38 to ±3.402e38 | ||
+ | #define d32 double // ±1.175e-38 to ±3.402e38 | ||
+ | </source> | ||
+ | |||
+ | После включения моего "хидера" в текст вашей | ||
+ | программы вы сможете писать вместо длинного | ||
+ | |||
+ | unsigned long int <имя 32 битной переменной> | ||
+ | |||
+ | коротко : | ||
+ | |||
+ | u32 <имя 32 битной переменной> | ||
+ | |||
+ | u - без знаковая - значит не отрицательная | ||
+ | |||
+ | s - значит переменная со знаком | ||
+ | |||
+ | 32 - количество бит в переменной | ||
+ | |||
+ | |||
+ | |||
+ | ==КОНСТАНТЫ.== | ||
+ | |||
+ | flash и const ставятся перед объявлением констант - неизменяемых данных хранящихся во флэш памяти программ. Они позволяют вам использовать не занятую программой память МК. Обычно для хранения строковых данных - различные информационные сообщения, либо чисел и массивов чисел. | ||
+ | |||
+ | КОНСТАНТЫ ПРИМЕРЫ из CVAVR help | ||
+ | <source lang="C"> | ||
+ | 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”; | ||
+ | </source> | ||
+ | '''В других компиляторах могут быть отличия !''' | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | Следующий пункт в структуре программы на Си для МК | ||
+ | <source lang="C"> | ||
+ | пункт 5 | ||
+ | |||
+ | Описание функций-обработчиков прерываний. | ||
+ | |||
+ | |||
+ | Механизм прерываний подробно описан | ||
+ | на странице 3 - Прерывания в AVR | ||
+ | |||
+ | /* | ||
+ | |||
+ | Конкретно в ЭТОЙ программе - есть только одно прерывание | ||
+ | и значит одна функция обработчик прерывания. | ||
+ | |||
+ | Программа будет переходить на неё при возникновении прерывания : | ||
+ | |||
+ | 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; | ||
+ | |||
+ | } // закрывающая скобка обработчика прерывания | ||
+ | </source> | ||
+ | |||
+ | |||
+ | |||
+ | Функция обработчик прерывания может быть названа | ||
+ | вами произвольно - как и любая функция кроме 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 | ||
+ | >> n означает сдвинуть все биты числа вправо на n позиций | ||
+ | это равносильно делению на 2 в сепени n | ||
+ | |||
+ | << n | ||
+ | означает сдвинуть все биты числа влево на n позиций | ||
+ | это равносильно умножению на 2 в сепени n | ||
+ | |||
+ | |||
+ | Сдвиг используется очень часто ! | ||
+ | |||
+ | |||
+ | Светодиоды подключены так как написано выше - т.е. подключены правильно ! | ||
+ | |||
+ | Загораются (показывая "1") при "0" на соответствующем выводе МК - значит нам нужно выводить в PORTB число в котором "1" заменены "0" и наоборот - | ||
+ | это делает как я рассказал выше: | ||
+ | |||
+ | ~ операция побитного инвертирования - меняет значения битов. | ||
+ | |||
+ | Значит результатом этого выражения | ||
+ | |||
+ | ~(ADCW >> 2) | ||
+ | |||
+ | будут инвертированные 8 старших бит результата АЦП находящиеся | ||
+ | в младшем (правом - LB) байте двух байтового слова ADCW | ||
+ | |||
+ | |||
+ | Выше я уже говорил что : | ||
+ | |||
+ | в Си в переменную можно помещать только тот тип данных который она может хранить ! | ||
+ | |||
+ | |||
+ | Так как PORTB это байт, а ADCW - это два байта, то прежде чем выполнить оператор присваивания (это знак = ) нужно преобразовать слово (слово - word - значит два байта) ADCW в без знаковый байт. | ||
+ | Преобразование типов данных - делают так : | ||
+ | |||
+ | перед тем что надо преобразовать записывают в скобках ( ) | ||
+ | тип данных к которому нужно преобразовать. | ||
+ | |||
+ | ====Пишем ...==== | ||
+ | |||
+ | (unsigned char) (~(ADCW>>2)) | ||
+ | |||
+ | Результат этой строки - один байт и мы можем поместить его в PORTB | ||
+ | |||
+ | |||
+ | Если в регистре DDRB все биты равны "1" - т.е. все ножки порта_B выходы, мы безусловно увидим старшие 8 бит результата АЦП горящими светодиодами. | ||
+ | |||
+ | |||
+ | Вам должна быть абсолютно понятна разобранная строка: | ||
+ | |||
+ | PORTB = (unsigned char) (~(ADCW>>2)); | ||
+ | |||
+ | |||
+ | |||
+ | Если это не так то | ||
+ | |||
+ | повторите разбор, и перечитайте материал по Си по использованным операторам Си. | ||
+ | |||
+ | |||
+ | |||
+ | Разберем еще одну строчку | ||
+ | |||
+ | ADCSRA|=0x40; | ||
+ | |||
+ | Обратите внимание на необходимость ставить в конце | ||
+ | выражений точку с запятой - не забывайте ! | ||
+ | |||
+ | '''Эта строка означает следующее:''' | ||
+ | |||
+ | Двигаемся слева на право : | ||
+ | |||
+ | - берем значение переменной ADCSRA (это регистр МК - значит программа прочитает его, возьмет число из него) | ||
+ | |||
+ | - выполняем с этим числом операцию обозначаемую вертикальной черточкой | | ||
+ | ( это поразрядная операция ИЛИ - только "0" и "0" дают "0" ) с числом 0x40 | ||
+ | |||
+ | - присвоим или поместим результат поразрядного ИЛИ обратно в переменную ADCSRA - т.е. запишем результат в регистр ADCSRA | ||
+ | |||
+ | 0x40 это в двоичном виде: 0100 0000 | ||
+ | |||
+ | так как в результате поразрядного ИЛИ только два "0" дают "0" биты в ADCSRA напротив нулей не изменятся, а вот бит_6 в ADCSRA оказывается напротив "1" и теперь он станет "1" не зависимо от того каким он был до этого ! | ||
+ | |||
+ | т.е. смысл рассматриваемой строки программы | ||
+ | |||
+ | ADCSRA|=0x40; | ||
+ | |||
+ | "установить" (т.е. сделать "1") бит_6 в регистре ADCSRA | ||
+ | |||
+ | |||
+ | Число справа от составных операторов |= &= ^= обычно называют маской, | ||
+ | |||
+ | и говорят "наложить маску" - так как в результате меняются лишь те биты которые нужно изменить. | ||
+ | |||
+ | Управление отдельными битами в переменной или регистре. | ||
+ | |||
+ | Как изменить только некоторые биты не изменяя остальные. | ||
+ | |||
+ | Для обнуления нужных бит используют обозначаемое знаком & поразрядное логическое И - только | ||
+ | "1" и "1" дает "1" | ||
+ | |||
+ | PEREM &=(~0x04); // обнулить бит_2 в переменной PEREM Скобки здесь я добавил для улучшения читаемости кода. | ||
+ | |||
+ | |||
+ | Самоконтроль - важно: | ||
+ | |||
+ | а) обязательно разберитесь почему обнуляется бит_2 | ||
+ | |||
+ | б) Как в двоичном виде выглядит результат (~0x04) | ||
+ | |||
+ | |||
+ | |||
+ | А вот так более понятно: | ||
+ | |||
+ | PEREM &=(~(1 << 2)); // обнулить бит_2 в переменной PEREM | ||
+ | |||
+ | |||
+ | Обнулить биты 5, 3 и 0 в переменной PEREM | ||
+ | |||
+ | PEREM &=(~ ( (1 << 5)|(1 << 3)|(1 << 0) ) ); | ||
+ | |||
+ | конечно здесь вместо (1 << 0) можно написать просто (1) | ||
+ | |||
+ | |||
+ | |||
+ | "Установить" - сделать "1" - биты 7, 5 и 3 в переменной PEREM | ||
+ | |||
+ | PEREM |=(1 << 7)|(1 << 5)|(1 << 3); | ||
+ | |||
+ | |||
+ | Обязательно разберитесь как это работает - вы должны это понимать и ГЛАВНОЕ использовать в своих программах. | ||
+ | |||
+ | Например (1 << 4) означает: взять число 1 и все его биты сдвинуть в лево на 4 позиции - в итоге мы получим двоичное 10000. Эти вычисления компилятор сделает сам и в программе заменит всё, что правее = на число-результат. | ||
+ | |||
+ | Вместо номеров битов вы можете использовать их названия из даташит. | ||
+ | Но в CVAVR для этого надо включить мой заголовок о котором написано выше или самому сделать определения битов из архива выложеного выше. А в IAR надо отметить галочку "Элау бит дефинишнс" в свойствах проекта. | ||
+ | |||
+ | WDTCR |= (1 << WDTOE) | (1 << WDE); | ||
+ | |||
+ | эта строка программы "Установит" - сделает "1" биты WDTOE и WDE в регистре WDTCR | ||
+ | |||
+ | |||
+ | |||
+ | <source lang="C"> | ||
+ | /* Пункт 6 Функции используемые в программе */ | ||
+ | |||
+ | // их может быть столько сколько вам нужно. | ||
+ | |||
+ | // у нас будет одна, кроме main и | ||
+ | // обработчика прерывания. | ||
+ | |||
+ | /* ================================= | ||
+ | |||
+ | Это будет функция в которой описано начальное | ||
+ | конфигурирование МК в соответствии с | ||
+ | поставленной задачей | ||
+ | |||
+ | Удобно над функцией сделать заголовок | ||
+ | подробно поясняющий назначение функции ! | ||
+ | |||
+ | ===================================== */ | ||
+ | |||
+ | (void)_init_mk(void) { | ||
+ | /* Вначале любой функции объявляются | ||
+ | ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ - если конечно они вам нужны */ | ||
+ | |||
+ | /* void - означает пусто. | ||
+ | |||
+ | Перед названием функции - void - означает что функция не возвращает ни какого значения. А в скобках после названия означает что при вызове в функцию не передаются ни какие значения. */ | ||
+ | |||
+ | // инициализация Port_B | ||
+ | DDRB=0xFF; // все ножки сделать выходами | ||
+ | PORTB=0xFF; // вывести на все ножки "1" | ||
+ | |||
+ | |||
+ | |||
+ | /* настройка АЦП - производится записью определенного числа в регистр "ADC Control and Status Register A" – ADCSRA | ||
+ | |||
+ | посмотрите его описание в ДШ МК мега16. | ||
+ | |||
+ | Нам нужно: | ||
+ | |||
+ | - Включить модуль АЦП | ||
+ | |||
+ | - Установить допустимую частоту тактирования АЦП при частоте кварца 3.69 МГц - мы выберем коэф. деления 64 - это даст частоту такта для процессов в АЦП 57.656 КГц | ||
+ | |||
+ | - Включить прерывание по завершению АЦ преобразования. | ||
+ | |||
+ | По ДШ для этого нужно записать в регистр ADCSRA | ||
+ | число: 1000 1110 или 0х8E */ | ||
+ | |||
+ | // ADC initialization w Oscillator=3.69MHz | ||
+ | // ADC Clock frequency: 57.656 kHz | ||
+ | // ADC Interrupts: On | ||
+ | ADCSRA=0x8E; | ||
+ | |||
+ | |||
+ | |||
+ | /* Теперь выбираем вход АЦП ADC0 (ножка PA0) и внешнее опорное напряжение (это напряжение код АЦП которого будет 1023) с ножки AREF | ||
+ | |||
+ | Смотрим что нужно записать для этого в регистр | ||
+ | мультиплексора (выбора входа) АЦП ADMUX | ||
+ | |||
+ | см. ДШ */ | ||
+ | |||
+ | // Нужно записать 0 (он там по-умолчанию) | ||
+ | ADMUX=0; | ||
+ | |||
+ | |||
+ | |||
+ | /* Разрешаем ГЛОБАЛЬНО все прерывания | ||
+ | разрешенные индивидуально ! | ||
+ | |||
+ | Вы наверно поняли что индивидуально мы разрешили | ||
+ | лишь прерывание по завершении АЦП - вот оно то и | ||
+ | сможет возникать у нас. */ | ||
+ | #asm("sei") | ||
+ | </source> | ||
+ | |||
+ | '''Внимание !''' | ||
+ | |||
+ | Так делаются Ассемблерные вставки в CVAVR : | ||
+ | |||
+ | #asm ("инструкция на ассемблере") | ||
+ | |||
+ | Обратите внимание - точки с запятой в конце НЕТ ! Такие вставки принято иногда делать. НО они не являются необходимыми. | ||
+ | |||
+ | На языке Си можно управлять ВСЕМИ программно | ||
+ | изменяемыми битами в регистрах МК ! | ||
+ | |||
+ | |||
+ | Напоминаю ... | ||
+ | |||
+ | Все регистры МК перечислены в таблице в конце ДШ с указанием номеров страниц с подробным описанием регистра и его битов. | ||
+ | |||
+ | Часто используются такие ассемблерные вставки : | ||
+ | <source lang="C"> | ||
+ | #asm("sei") // Разрешить ГЛОБАЛЬНО все прерывания | ||
+ | |||
+ | #asm("cli") // Запретить ГЛОБАЛЬНО все прерывания | ||
+ | |||
+ | #asm("nop") // Пауза в 1 такт процессора | ||
+ | |||
+ | #asm("wdr") // Сбросить сторожевой таймер | ||
+ | |||
+ | |||
+ | // все функция закончена | ||
+ | } // скобка закрывающая для функции _init_mk() | ||
+ | |||
+ | Далее... | ||
+ | /* | ||
+ | Пункт 7 Главная функция main() - обязательная ! | ||
+ | */ | ||
+ | |||
+ | /* ================================= | ||
+ | Главная функция - | ||
+ | |||
+ | Си программа начинает выполнятся с нее! | ||
+ | |||
+ | ===================================== */ | ||
+ | |||
+ | void main(void){ | ||
+ | /* Вначале любой функции объявляются | ||
+ | (если нужны) ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ */ | ||
+ | |||
+ | _init_mk();/*Вызываем функцию инициализации, настроийки аппаратуры МК. Выполнив ее программа вернется сюда и будет выполнять следующую строку */ | ||
+ | |||
+ | // запускаем первое АЦП | ||
+ | ADCSRA|=0x40; | ||
+ | |||
+ | // бесконечный цикл в ожидании прерываний | ||
+ | while(1); | ||
+ | /* Программа будет крутится на этой строчке постоянно проверяя истинно ли условие в | ||
+ | скобках после while а так как там константа | ||
+ | 1 - то условие будет истинно всегда! | ||
+ | |||
+ | // функция main закончена | ||
+ | } // скобка закрывающая для функции main() | ||
+ | </source> | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | '''Эта программа на Си будет работать так :''' | ||
+ | |||
+ | По завершении АЦП будет возникать прерывание и программа будет перескакивать в функцию обработчик прерывания adc_isr() | ||
+ | |||
+ | При этом будут автоматически запрещены все прерывания ГЛОБАЛЬНО ! | ||
+ | |||
+ | В конце adc_isr() запускается новое АЦ преобразование и при выходе из обработчика прерывания снова разрешаются глобально прерывания, и программа возвращается опять в бесконечный цикл while(1) | ||
+ | |||
+ | Такая чехарда будет продолжаться пока есть питание МК и не будет сброса. | ||
+ | |||
+ | Светодиоды будут высвечивать 8-ми битный код АЦП напряжения на ножке PA0 | ||
+ | |||
+ | '''Всё - программа на Си написана и разобрана.''' | ||
+ | |||
+ | Вам должно быть все ясно и абсолютно понятно! | ||
+ | |||
+ | Если это не так то перечитайте, подумайте, | ||
+ | повторите разбор, почитайте рекомендованное ниже по Си. | ||
+ | |||
+ | ==Еще щепотка Си :== | ||
+ | |||
+ | =====Пример:===== | ||
+ | делать что-то пока на ножке PBn есть "1" | ||
+ | |||
+ | Как AVR переводит напряжения на выводах МК в логические уровни - рассказано с картинками, графиками на [[AVR123:Страница|2странице 2]] курса - Устройство микроконтроллера AVR. | ||
+ | <source lang="C"> | ||
+ | while(PINB & (1 << n)){ // для любого компилятора | ||
+ | Какой-то код программы | ||
+ | /* Какой-то код будет выполнятся снова и снова, пока проверка условия в скобках после while будет давать "истину" - значит пока на ножке PBn есть логическая единица | ||
+ | |||
+ | Проверка условия выполняется в начале и затем каждый раз по завершении выполнения какого-то кода | ||
+ | |||
+ | Пока выполняется какой-то код проверка того что на ножке PBn не производится. */ | ||
+ | }; | ||
+ | |||
+ | примечание - в CVAVR можно написать проще | ||
+ | while(PINB.n){ // но для регистров с адресом до 31 | ||
+ | |||
+ | |||
+ | |||
+ | Пример: выполнить что-то если на ножке PCn есть "0" | ||
+ | |||
+ | if((~PINC)&(1 << n)){ // для любого компилятора | ||
+ | что-то | ||
+ | /* что-то начнет выполняться если на ножке PCn "0" */ | ||
+ | }; | ||
+ | |||
+ | примечание - в CVAVR можно написать проще | ||
+ | if(!(PINC.n)){ | ||
+ | |||
+ | |||
+ | Помните ! Выполнение чего-то может быть прервано прерыванием. | ||
+ | После завершения обработки прерывания выполнение чего-то продолжится. | ||
+ | |||
+ | Примечание - Условие : | ||
+ | |||
+ | if((~PINC)&(1 << n)) { | ||
+ | |||
+ | можно записать и вот так : | ||
+ | |||
+ | if(!(PINC & (1 << n))) { | ||
+ | |||
+ | |||
+ | |||
+ | Пример: выполнить что-то если на ножке PBn есть "1" | ||
+ | |||
+ | if((PINB)&(1 << n)){ // для любого компилятора | ||
+ | что-то | ||
+ | /* что-то начнет выполняться если на ножке PBn "1" */ | ||
+ | }; | ||
+ | |||
+ | примечание - в CVAVR можно написать проще | ||
+ | if(PINB.n){ | ||
+ | </source> | ||
+ | К битам регистров с адресами от 0 до 31 в компиляторе CodeVisionAVR можно обратится (и читать и записывать) проще. | ||
+ | |||
+ | Вот так: REGISTR.BIT | ||
+ | |||
+ | Пример: | ||
+ | <source lang="C"> | ||
+ | PINB.2 или PORTA.5 или DDRC.7 | ||
+ | </source> | ||
+ | Пример: | ||
+ | <source lang="C"> | ||
+ | if(!PINB.2){ | ||
+ | этот код /* Если на ножке PB2 низкий | ||
+ | логический уровень - то выполнить этот код */ | ||
+ | }; | ||
+ | |||
+ | Пример: | ||
+ | PORTA.3 = 1; /* Сделать бит 3 в регистре | ||
+ | PORTA единицей - говорят: "установить бит" | ||
+ | по англ. "set bit" */ | ||
+ | |||
+ | Пример: | ||
+ | PORTB.6 = 0; /* Сделать бит 6 в регистре | ||
+ | PORTB нулем - говорят: "очистить, сбросить бит" | ||
+ | по англ. "clear bit" */ | ||
+ | </source> | ||
+ | |||
+ | Битовые операции подробно описаны в [[AVR123:задача_1|задаче 1]] | ||
+ | |||
+ | и конечно в справке - help - компиляторов ! | ||
+ | |||
+ | =====Теперь вы должны знать===== | ||
+ | |||
+ | - как записать число в регистр, в переменную | ||
+ | |||
+ | - как изменить бит в регистре | ||
+ | |||
+ | - как взять число из регистра | ||
+ | |||
+ | - как выполнить что-то в зависимости от | ||
+ | значения бита в регистре или в переменной | ||
+ | |||
+ | - записывайте возникающие вопросы ! | ||
+ | и лучше на бумагу - моторная память ! | ||
+ | |||
+ | - найдите в DataSheet (ДШ) регистры и устройства МК использованные в задаче, прочитайте о них подробней. | ||
+ | |||
+ | - если вопросы остались перечитайте задачу снова ! | ||
+ | |||
+ | - если вопросы не разрешены, ищите ответ: | ||
+ | |||
+ | 1) в help и документации компилятора, симулятора, других используемых программ! | ||
+ | |||
+ | 2) поиском Windows в папках и help компилятора и симулятора. | ||
+ | |||
+ | 3) поиском Windows в папке где сохранен у вас курс. | ||
+ | |||
+ | 4) в моем не структурированном мини-AVRFAQ - это сборник ответов на часто задаваемые мне по курсу вопросы и советы по применению МК от знающих людей. | ||
+ | |||
+ | |||
+ | Дальше - страница 6 | ||
+ | |||
+ | Задачи - Упражнения Курса | ||
+ | Практическая работа с Компилятором с Симулятором с МК и внешними устройствами | ||
+ | |||
+ | |||
+ | |||
+ | --[[Участник:Nekolex|Nekolex]] 20:47, 27 сентября 2009 (UTC) |
Текущая версия на 00:47, 29 сентября 2009
Содержание
Язык Си для AVR.
Как раз то, что необходимо и достаточно для микроконтроллеров.
По умолчанию компилятор CVAVR
В других компиляторах могут быть незначительные отклонения, нюансы не связанные с языком Си, а обусловленные стараниями и предпочтениями разработчиков этих компиляторов.
Минимальная программа на Си может быть такой: <source lang="c">main(){}</source> Эта программа не делает ни чего полезного — но это уже программа и она показывает что в программе на языке Си — должна быть главная функция main — обязательно !
Скачайте и Распечатайте.Памятка Си для МК на ОДНОЙ странице.
Рассказывая про МК я говорил вам, что: Задача программы МК:
- читать числа из регистров и памяти МК,
- делать что-то с числами, данными и
- записывать числа в регистры и память.
Только так программа может общаться с МК.
Как это делать на языке Си
- Регистры МК
- ( регистры - это ячейки-байты в памяти МК AVR ) в программе на Си имеют названия как и в ДШ и так как числа в большинстве из них можно менять - для программы регистры являются по сути переменными.
- Переменная
- - это набор ячеек в памяти в которых можно хранить число или числа и менять их. Переменная имеет адрес и имя.
- Константа
- - это как переменная но менять содержимое нельзя.
Подробней о переменных и константах написано ниже.
1) Чтобы поместить число в переменную (в регистр) в языке Си есть оператор присваивания
это знак "=" ( называемый в математике "равно" ) "=" в Си означает вычислить результат того что справа от оператора присваивания и поместить этот результат в переменную находящуюся левее оператора присваивания.
<source lang="c"> PORTB = PINB + 34;/* Эта строчка на Си означает взять (прочитать, считать) значение переменной (регистра) PINB, затем прибавить к нему число 34 и поместить результат в переменную PORTB */
ПЕРЕМЕННАЯ = PINC; /* Эта строчка на Си означает взять (прочитать, считать) значение переменной (регистра) PINC и поместить результат в переменную с именем ПЕРЕМЕННАЯ */ </source> Чтобы в Си взять (прочитать) число из регистра или значение переменной нужно написать его название НЕ непосредственно с лева от оператора присваивания !
Примеры :
- a) Строка где переменная стоит слева от = но через знак &
<source lang="c">PORTB & = 0x23;</source>
на Си означает - прочитать содержимое переменной PORTB, затем выполнить "поразрядное (побитное) логическое И" между прочитанным значением и числом 0x23 и поместить (записать, присвоить) результат в переменную PORTB
- b) Строка где переменная стоит непосредственно слева от =
<source lang="c">PORTB = 0x23;</source>
на Си означает - не читая содержимое переменной PORTB присвоить ей значение 0x23 уничтожив то что было там раньше.
:Вместо & "И" (AND - только 1 и 1 дают 1) могут быть и другие побитные логические операции: • | "ИЛИ" (OR только 0 и 0 дают 0) • ^ "Исключающее ИЛИ" (XOR изменить бит напротив "1") • ~ "инвертирование битов" (INV изменить биты регистра) • арифметические операции: + - * / %
С оператором присваивания используются вот такие сокращения:
ДЛИННАЯ ЗАПИСЬ | СМЫСЛ | СОКРАЩАЕТСЯ ДО |
---|---|---|
x = x + 1; | добавить 1 | x++; или ++x; |
x = x - 1; | вычесть 1 | x--; или --x; |
x = x + y; | прибавить y | x += y; |
x = x - y; | вычесть y | x -= y; |
x = x * y; | умножить на y | x *= y; |
x = x / y; | поделить на y | x /= y; |
x = x % y; | остаток от деления | x %= y; |
x--; | вычесть 1 | x -= 1; |
x++; | добавить 1 | x += 1; |
Примеры :
<source lang="c"> 00010010 | 01001111 // "ИЛИ" - только 0 и 0 дают 0 // англ. название OR
01011111 // это результат
// только биты_5 в обоих числах были нули
00010010 & 01001111 // "И" - только 1 и 1 дают 1 // англ. название AND
00000010 // это результат
// только биты_2 в обоих числах были единицы
00010010 ^ 01001111/* "исключающее ИЛИ" - результат любое из пары чисел в котором инвертированы (изменены) биты напротив битов равных "1" в другом числе. англ. название XOR */ 01011101 // это результат /* изменились биты во втором числе напротив установленных битов 4 и 1 первого числа. */
~ 01001111 /* инвертировать биты те что были "1" станут "0" и наоборот */ 10110000 // это результат</source>
Запомните ! |
---|
Результатом поразрядных (побитных)
логических операций : & | ^ ~ является число ! Которое может быть интерпретировано компилятором как "истина" если оно не ноль и "ложно" если число ноль. |
Числа
В компиляторе можно записывать в виде указанном в его Help ! Раздел - константы - Constants.
например - Целые числа могут быть записаны : <source lang="c"> - в десятичной форме - 1234
- в двоичной форме с префиксом 0b так: 0b101001
- в шестнадцатеричной форме с префиксом 0x так: 0x5А
- в восьмеричной форме с префиксом 0 так: 0775</source>
Числа с плавающей точкой имеют в записи эту точку и какое либо число после этой точки, так: <source lang="c">61.234 или так: 73.0 и так: .786
и могут иметь в конце F вот так: 61.234F</source> ТОЧКА ОБЯЗАТЕЛЬНА !
Различные представления числа D3h равно 0xD3 равно 0b1101 0011 равно 211 | |||||||||
---|---|---|---|---|---|---|---|---|---|
шестнадцатеричное число 0xD3 | |||||||||
0 | x | D | 3 | ||||||
двоичное представление - число 0b1101 0011 | |||||||||
0 | b | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
номера бита | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
два в степени равной номеру бита | |||||||||
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | ||
число 211 в десятичном виде это сумма степеней двойки где биты равны "1" | |||||||||
Сложите | +128 | +64 | +16 | +2 | +1 |
Четыре бита это 1 "нибл" (полубайт) или 1 символ в 16-ричной системе или десятичное число от 0 до 15.
"В уме" удобно оперировать ниблами:двоичный десятичный 16-ричный
двоичный | десятичный | 16-ричный |
---|---|---|
0000 | 0 | |
0001 | 1 | |
0010 | 2 | |
0011 | 3 | |
0100 | 4 | |
0101 | 5 | |
0110 | 6 | |
0111 | 7 | |
1000 | 8 | |
1001 | 9 | |
1010 | 10 | A |
1011 | 11 | B |
1100 | 12 | C |
1101 | 13 | D |
1110 | 14 | E |
1111 | 15 | F |
Для перевода чисел из одного вида в другой можно использовать калькулятор Windows в инженерном виде.
Есть в Си операции которые изменяют значение переменной и без оператора присваивания : <source lang="c"> PORTA++; /* Эта строчка на Си означает взять значение переменной PORTA добавить к ней 1 и записать результат обратно в PORTA говорят: Инкрементировать регистр PORTA */
PORTC--; /* Эта строчка на Си означает обратное действие! Декрементировать - вычесть 1 из значения регистра PORTC */ </source>
Инкремент и декремент удобно использовать для изменения значения различных переменных счетчиков.
Важно помнить что они имеют очень низкий приоритет - поэтому чтобы быть уверенными в порядке выполнения желательно писать их отдельной строчкой программы !
Длинные выражения можно писать в несколько строк. <source lang="c"> /* ЗЕЛЕНЫМ я пишу комментарий к программе в Си он может быть написан в несколько строк
и пустых строк тоже */
// или в одну после двух черточек </source> Компилятор игнорирует все что написано в комментариях !
Вы не компилятор !
Не игнорируйте, пишите комментарии и читайте !
Когда инкремент или декремент используется в выражении то важно где стоят два знака + или - перед переменной или после переменной : <source lang="c"> a=4; b=7;
a = b++; /* Эта строчка на Си означает Взять значение переменной b присвоить его переменной a затем добавить 1 к переменной b и сохранить результат в b
Теперь a будет содержать число 7 b будет содержать число 8 */
a=4; b=7;
a = ++b; /* Эта строчка на Си означает Взять значение переменной b затем добавить к нему 1 и сохранить результат в b и этот же результат присвоить переменной a
Теперь a будет содержать число 8 и b будет содержать число 8 */ </source>
2) Арифметические операции в Си
<source lang="c"> x + y // сложение x - y // вычитание x * y // умножение
x / y /* деление.
Если числа целые результат - целое число с отброшенной дробной частью - не округленное !
т.е. если в результате деления на калькуляторе получается 6.23411 или 6.94 то результат будет просто целое число 6 - запомните !
Если числа с плавающей точкой, то есть float или double и записываются с точкой и числом после точки, то и результат будет число с плавающей точкой без отбрасывания дробной части 131.9739 / 6.18 даст 21.355 */
x % y // вычислить остаток от деления нацело
// примеры:
5 / 2 // даст 2
5 % 2 // даст 1
75 / 29 // даст 2
75 % 29 // даст 17 </source>
3) Операторы сравнения (или отношения):
<source lang="c"> используются для сравнения переменных, чисел (констант) и выражений. x < y // X меньше Y x > y // больше x <= y // меньше или равно x >= y // больше или равно x == y // равно x != y /* не равно
Результат выполнения этих операторов:
"истина" это "1" (точнее "не ноль")
"ложно" это "0"
Значения хранимые в переменных (в регистрах) х и у НЕ изменяются!
Берутся (считываются) значения хранящиеся (или содержащиеся) в переменных и сравниваются */
! /* "НЕ" - логическое отрицание */ </source>
4) Логические операции :
<source lang="c"> | | // "ИЛИ" - только "ложь" и "ложь" // дают "ложь"
&& // "И" - только "истина" и "истина" // дают "истина"
! // "НЕ" - логическое отрицание
/* Правило - в Си считается:
"Ложь" (False) только ноль.
"Истина"(True)- не ноль. или так: (!0)
- /
!(истина) // дает "ложь"
!(ложь) // дает "истина" </source> В результате логической операции вы получаете НЕ ЧИСЛО, а логическое значение "истина" или "ложь"
Для логических операций && и || берутся результаты выражений слева и справа от знака операции преобразованные в "истину" или "ложь" и определяется логический результат операции.
Компилятор, для определенности наверно, результат "истина" превращает в 1 а не в любое отличное от 0 число.
Логические операции могут объединять несколько проверяемых условий.
Пример:
<source lang="c"> if((выражение1)&&((выражение2) || (выражение3))) {/* Код программы здесь будет выполняться если: Выражение1 "Истина" (значит не ноль) и хотя бы одно из выражений 2 и 3 тоже "Истина" (значит не ноль).
- /};</source>
Подробнее о логических операциях обязательно прочитайте по в низу этой страницы !
Приоритет операций в языке Си перечислены в порядке убывания приоритета. Операции, приведённые на одной строчке, имеют одинаковый приоритет. Операции, помеченные как R->L, исполняются справа налево.
- () [] -> .
- Унарные (R->L): ! ~ - * & sizeof (type) ++ --
- Бинарные арифметические: * / %
- Бинарные арифметические + -
- Сдвиг: << >>
- Сравнение: < <= > >=
- Сравнение: == !=
- Битовая: &
- Битовая: ^
- Битовая: |
- Логическая: &&
- Логическая: ||
- Тернарная (R->L): ?:
- Операции с присваиванием (R->L):
= | += | -= | = | /= | &= | |= | ^= | <<= | >>= |
{
{InfoBlock|Чтобы точно знать порядок выполнения операций программой используйте скобки ( )
( () + ( () * () ) ) Ведь скобки ( ) имеют наивысший приоритет.|Совет:}}
Самое интересное !
Ходовые конструкции на Си В компиляторе [CVAVR заготовки этих конструкций находятся под ярлыком "Code Templates" слева вверху. Вы можете выбирать нужные заготовки и вставлять их в свою программу.
5) if(){ }else{ }; идеальная конструкция если вам нужно выполнить какую то часть программы при наличии каких либо
условий или при их отсутствии :
<source lang="c"> if (выражение) { код на Си /* делать этот код если выражение "истина" - т.е. результат его вычисления не ноль */ } else { код на Си /* делать этот код если выражение "ложь" - т.е. результат его вычисления равен нулю */ };
// } else { это не обязательный элемент конструкции, без него так :
if (выражение) { код на Си /* делать этот код если выражение "истина" - т.е. результат его вычисления не ноль */ }; </source>
6)while(){ }; условный цикл ( цикл с условием ) - используйте если вам
нужно выполнять какой то код программы пока выполняется (существует,
справедливо, "истино" - значит "не ноль") некоторое условие, результат вычисления выражения :
<source lang="c"> while (выражение) { код на Си /* делать этот код если выражение "истина" - т.е. результат его вычисления не ноль. Пока выполняется этот код выражение не проверяется на истинность ! После выполнения кода происходит переход к строке while снова проверять истинность выражения */ }; </source> Цикл while имеет вариант
do - while при котором код в { } выполняется по меньшей мере один раз не зависимо от истинности условия в скобках : <source lang="c"> do { код на Си /* сделать этот код один раз
затем, если выражение есть "истина" - т.е. результат его вычисления не ноль - опять делать код с начала, и так до тех пор пока выражение истина */ } while (выражение); </source> 7) for(;;){ }; - этот цикл позволяет выполнить часть программы нужное число раз: <source lang="c"> char i; /* объявление переменной для for это обычная переменная Си и значит может иметь любое допустимое имя по вашему желанию и тип */ for (i=5; i i = 5 это начальное выражение, то что в начале будет в переменной i
Число 5 просто для примера, может быть таким, как позволяет объявление типа переменной i, в нашем случае это char в большинстве компиляторов по-умолчанию это без знаковый символьный тип - он может хранить числа от 0 до 255 i < 20 - контрольное выражение Может быть с разными операторами отношения, важно лишь чтобы по ходу цикла оно становилось когда-то "ложью" - иначе цикл "зациклится" т.е. ни когда не кончится.
i += 4 - это счетчик или изменение переменной цикла. Обычно это i++ т.е.к переменной добавляется 1 каждый "прогон" цикла. Но опять же может быть таким какое вам требуется.
- Начальным условием
- - может быть любое допустимое в Си выражение результатом которого является целое число.
- Контрольное выражение
- - определяет до каких пор будет выполнятся цикл.
- Счетчик
- - показывает как изменяется начальное выражение перед каждом новом выполнении цикла .
- Выражение
- - значит это может быть не просто переменная, а что-то посложней, например:
i =(7 + i*4) или i = (функция других переменных)
<source lang="c"> Циклы for(;;) и while()
часто используют вот так:
while(1);
for (;;);
/* Так написанные эти циклы означают :
МК выполнять эту строчку пока есть питание, нет сброса и нет прерывания. Когда возникает прерывание, программа переходит на обработчик прерывания и (если в обработчике нет перехода в другое место программы) по завершении кода обработчика опять возвращается в такой цикл. */
while(1){ код программы };
for (;;){ код программы };
/* Так написанные эти циклы означают :
МК выполнять код программы пока есть питание, нет сброса и нет прерывания. Когда возникает прерывание, программа переходит на обработчик прерывания и (если в обработчике нет перехода в другое место программы) по завершении кода обработчика опять возвращается сюда и продолжает выполнять код программы */ </source>
8)switch(){ }; - оператор множественного выбора,
позволяет вам сделать выбор из нескольких вариантов. <source lang="c"> switch (выражение) { case 5: код на Си /* этот код будет выполняться если результат вычисления выражения равен числу 5
на этом работа оператора switch закончится */ break;
case -32: код на Си /* этот код будет выполняться если результат вычисления выражения равен отрицательномц числу -32
на этом работа оператора switch закончится */ break;
case 'G': код на Си /* этот код будет выполняться если результат вычисления выражения равен числу соответствующему символу G в таблице ASCII
на этом работа оператора switch закончится */ break;
default: код на Си /* этот код будет выполняться если результат вычисления выражения не равен ни 5 ни -32 ни 'G'
А так же после выполнения кода не имеющего в конце break;
на этом работа оператора switch закончится */ };
/* switch закончен - выполняется дальнейший код программы */ </source>
- case
- - может быть столько сколько вам нужно, чтобы программа работала быстрее старайтесь наиболее вероятные варианты располагать выше!
- default
- - не обязателен. Его можно расположить и не в конце.
- break;
- - если его не использовать то найдя нужный вариант программа будет выполнять и следующие ниже условия case
[Прочитайтеподробней о switch с примерами.
[Скачайте и распечатайте таблицу символов ASCII на ОДНОЙ странице!
9) goto - оператор безусловного (немедленного) перехода. <source lang="C"> ... какой-то код нашей программы на Си ...
mesto_5: /* сюда мы попадем после выполнения строки программы goto mesto_5 */
код будет выполнятся после goto mesto_5;
... какой-то код нашей программы на Си ...
mesto_1: /* сюда мы попадем после выполнения строки программы goto mesto_1 */
код будет выполнятся после goto mesto_1;
... какой-то код нашей программы на Си ...
goto mesto_1; /* перейти в то место программы где в начале строки написано mesto_1: */
... какой-то код нашей программы на Си ...
goto mesto_5; /* перейти в то место программы где в начале строки написано mesto_5: */
... какой-то код нашей программы на Си ... </source>
Используйте goto с осторожностью! Думайте к чему может привести выполнение функций или конструкций вашей программы не до конца.
CVAVR помогает вам в контроле не позволяя где угодно втыкать goto
Оператор Си ? работает почти как if - вот так:
Пример функция в которую передается значение переменной val_d
Вызов функции и передач в нее числа хранящегося в переменной с именем peremennaya
resultat = funkziya(peremennaya);
В функции число из peremennaya будет помещено в val_d и обработано.
int funkziya(int val_d) {
return ((val_d>511)?(-1024+val_d):(val_d));
}
( (выражение) ? ( если выражение истина ) : ( если выражение ложно ) )
если val_d>511 то функция возвратит val_d уменьшенное на 1024
если val_d=<511 то функция возвратит val_d не меняя его.
Возвращаемое функцией значение val_d будет помещено в переменную в строке вызова функции - resultat
Подробнее о функциях будет написано ниже.
Ну вот - ПОЧТИ
всё что нужно нам из Си !
Как использовать описанное выше вы можете
посмотреть в примерах к компилятору !
Примеры в папке :
C:\CVAVR\EXAMPLES
Открывайте файлы исходников .с и разбирайте текст программ - что делает каждая строчка кода !
Это великолепный способ само-обучения программированию !
Новичку понадобятся для понимания программ написанных профи :
Указатели, Структуры и Союзы.
Прочитайте о них в он-лайн книгах по Си которые расположены ниже на страничке. Примеры применения указателей, структур и союзов в разных компиляторах вы найдете в FAQ к курсу по AVR - скачайте и читайте.
Структура программы на языке Си.
Программа на языке Си это текстовый файл, обычно с расширением .c
Текст программы называют исходным или "исходником" или "сурцом" от анг. source code - это вам ключевые слова для поиска
Весь исполняемый код программы на Си находится в функциях - т.е. в фигурных скобках { исполняемый код программы }
Программа на Си имеет определенную структуру :
1) заголовок
2) включение необходимых внешних файлов - #include
3) ваши определения для удобства работы - #define
4) объявление глобальных переменных и констант
Глобальные переменные и константы
- объявляются вне какой либо функции.
т.е. не после фигурной скобки {
- доступны в любом месте программы - значит можно читать их значения и присваивать переменным значения там где вам требуется - в любом месте программы после их объявления.
5) описание функций - обработчиков прерываний
6) описание других функций используемых в программе
7) функция main <- это единственный обязательный пункт !
Это не жесткий порядок а ориентировочный !
Иногда п.6 это прототипы функций, а сами функции описываются полностью после п.7
Прототип функции - показывает образец того как применять функцию в программе, какие значения в нее передаются и если она возвращает какое-то значение то прототип указывает тип возвращаемых данных. Прототип не имеет скобок { } а после скобок ( ) ставится ;
Функция - имеет { "тело" } в фигурных скобках. Тело это код на Си определяющий то что делает функция.
- после вызова функции не ставится !
Программа на Си начинает работу с функции main()
по необходимости из main()вызываются другие функции программы, из которых может быть вызов следующих функций, по завершении работы функции программа возвращается по той же цепочке как вызывались функции. <source lang="C"> main(){
... какой то код программы ...
вызов функции_1; //программа перейдет в функцию_1
строка программы; // будет выполнятся после
// возврата из функции_1
... какой то код программы ...
} </source> На странице 3. "Прерывания в AVR" - вы уже читали, что описанный выше ход программы может нарушаться прерываниями.
Пример программы на Си
с описанной выше структурой я буду писать на голубом фоне.
По мере надобности я буду разрывать голубой фон обычным текстом, затем голубой фон и программа будет продолжаться.
Ниже будет написан пример ОДНОЙ программы на Си.
<source lang="C">
/* пункт 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 <source lang="C"> Таким образом вместо того что бы помнить что вывод занятости AD7896 подключен у вас к ножке PB0 вы можете проверять значение осмысленного понятия ADC_BUSY - "АЦП занят"
а вместо управления абстрактной ножкой PB1 (через PORTB.1) вы можете управлять "НьюКонвешнСтат" - NCONVST - "стартовать новое АЦ преобразование" <source lang="C">
- define - Это удобно ! Но ВОВСЕ не обязательно.
- /
- define INIT_TIMER0 TCNT0=0x100L-F_XTAL/64L/500L
/* этот пример показывает что определения могут быть и сложней ! */ </source>
- 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.
Скачайте его и добавьте в программу вот так:
(Если вы читаете курс с начала и делаете то, что предлагается то этот файл у вас уже есть). <source lang="C">
- include <mega16.h>
/* сперва обычный "хидер"-заголовок для МК
используемого в вашей программе */
- include <m8_128.h>
/* затем мой "хидер"-заголовок с определениями
названий и номеров битов для используемого МК */
</source>
Теперь вы можете использовать примеры на Си из ДШ на ваш МК !
Мой файл m8 128.h содержит определения битов для микроконтроллеров ATmega8 ATmega16 ATmega32 ATmega64 ATmega128
Для всех AVR определения битов есть в .h заголовках WinAVR вот архив (127 Кб) скачайте и используйте если понадобится.
Мастер начального кода программы в компиляторе
ICC умеет по вашему желанию автоматически делать
- define для ножек для ножек МК !
Подробней про это и с картинкой смотри в соответствующей задаче курса.
Пункт 4.
Объявление переменных
Перед использованием переменной в программе на Си её необходимо объявить - т.е. указать компилятору какой тип данных она может хранить и как она называется.
Наиболее подробно об этом по ссылке:
1.2. ТИПЫ ДАННЫХ И ИХ ОБ ЯВЛЕНИЕ
Ниже сжато - самое главное:
Формат объявления переменной таков:
[<storage modifier>] <type definition> <identifier>;
- [<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".
Книги и учебники по радиоэлектронике и микроконтроллерам там
Скачать весь курс по AVR одним архивом
Глобальные переменные объявляются до появления их в тексте какой либо функции. После объявления, глобальные переменные доступны в любой функции программы.
Локальные переменные объявляются в самом начале функций - т.е. сразу после фигурной скобки {
Локальные переменные доступны только в той функции где они объявлены! В разных функциях могут быть объявлены локальные переменные с одинаковыми именами - я не советую вам так делать.
Советую не использовать ЛОКАЛЬНЫЕ переменные в главной функции main.
<source lang="C"> <type definition> - тип данных которые может хранить переменная.
наиболе часто используемые типы данных :
unsigned char - хранит числа от 0 до 255 (байт)
unsigned int - хранит числа от 0 до 65535 (слово == 2 байта)
unsigned long int - хранит от 0 до 4294967295 (двойное слово == 4 байта) </source>
Подробнее все типы данных посмотрите в Help
CVAVR\bin\CVAVR.HLP
Раздел "Data Types"
Уточняйте ТИПЫ данных в руководстве к вашему компилятору !
Вместо unsigned char - можно писать писать просто char, так как компилятор по-умолчанию считает char без знаковым байтом.
А если вам нужен знаковый байт то объявляйте его так :
signed char imya_peremennoi;
<identifier> - имя переменной - некоторый набор символов по вашему желанию, но не образующий зарезервированные слова языка Си.
Выше был уже пример идентификатора - имени переменной: imya_peremennoi
Давайте осмысленные имена переменным и функциям - напоминающие, "говорящие" вам об их назначении.
Принято использовать маленькие буквы, а для отличия имен переменных от названия функций - имена переменных можно например начинать с буквы, а названия функций (кроме main конечно) с символа подчеркивания.
Например так :
moya_peremennaya _vasha_funkziya
Внимание!
Глобальные переменные, а также локальные с модификатором static - при старте и рестарте программы равны 0 если вы не присвоили им (например оператором =) иное значение при их объявлении или по ходу программы.
Подробные примеры объявления переменных посмотрите пожалуйста в разделе Variables в "Хелп" компилятора CVAVR.
Вот несколько примеров объявления переменных :
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);
Вы можете преобразовать строковую переменную в число! Если исходная строка символов такая :
char stroka[]="3654694"; то вот так:
perem_1 = atoi(stroka);
мы поместим в переменную perem_1 (которую должны были ранее в программе объявить как "беззнаковую целую") число 36546. Это число влезет в переменную perem_1 которая может хранить числа от 0 до 65535. А вот 9 и 4 уже не поместятся. Для бОльших чисел есть функция - atol() Чтобы использовать эти функции необходимо включить в начале программы заголовочный файл :
- include <stdlib.h>
Для преобразования числа в строку
есть itoa() и ltoa()
и аналогичные функции для чисел с плавающей точкой.
Подробнее об этих и других полезных функциях смотрите раздел "Standard Library Functions" справки компилятора CodeVisionAVR.
Советую вам скачать заголовочный файл
m8 128.h
Он содержит названия битов МК ATmega8 -16 -32 -64 -128 и сокращенные названия типов данных как в компиляторе IAR.
вот отрывок из него: <source lang="C">
- define u8 unsigned char // 0 to 255
- define s8 signed char // -128 to 127
- define u16 unsigned int // 0 to 65535
- define s16 signed int // -32768 to 32767
- define u32 unsigned long int
// 0 to 4294967295
- define s32 signed long int
// -2147483648 to 2147483647
- define f32 float // ±1.175e-38 to ±3.402e38
- define d32 double // ±1.175e-38 to ±3.402e38
</source>
После включения моего "хидера" в текст вашей программы вы сможете писать вместо длинного
unsigned long int <имя 32 битной переменной>
коротко :
u32 <имя 32 битной переменной>
u - без знаковая - значит не отрицательная
s - значит переменная со знаком
32 - количество бит в переменной
КОНСТАНТЫ.
flash и const ставятся перед объявлением констант - неизменяемых данных хранящихся во флэш памяти программ. Они позволяют вам использовать не занятую программой память МК. Обычно для хранения строковых данных - различные информационные сообщения, либо чисел и массивов чисел.
КОНСТАНТЫ ПРИМЕРЫ из CVAVR help <source lang="C"> 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”; </source> В других компиляторах могут быть отличия !
Следующий пункт в структуре программы на Си для МК <source lang="C"> пункт 5
Описание функций-обработчиков прерываний.
Механизм прерываний подробно описан
на странице 3 - Прерывания в AVR
/*
Конкретно в ЭТОЙ программе - есть только одно прерывание и значит одна функция обработчик прерывания.
Программа будет переходить на неё при возникновении прерывания :
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;
} // закрывающая скобка обработчика прерывания </source>
Функция обработчик прерывания может быть названа вами произвольно - как и любая функция кроме 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
>> n означает сдвинуть все биты числа вправо на n позиций
это равносильно делению на 2 в сепени n
<< n
означает сдвинуть все биты числа влево на n позиций это равносильно умножению на 2 в сепени n
Сдвиг используется очень часто !
Светодиоды подключены так как написано выше - т.е. подключены правильно !
Загораются (показывая "1") при "0" на соответствующем выводе МК - значит нам нужно выводить в PORTB число в котором "1" заменены "0" и наоборот - это делает как я рассказал выше:
~ операция побитного инвертирования - меняет значения битов.
Значит результатом этого выражения
~(ADCW >> 2)
будут инвертированные 8 старших бит результата АЦП находящиеся в младшем (правом - LB) байте двух байтового слова ADCW
Выше я уже говорил что :
в Си в переменную можно помещать только тот тип данных который она может хранить !
Так как PORTB это байт, а ADCW - это два байта, то прежде чем выполнить оператор присваивания (это знак = ) нужно преобразовать слово (слово - word - значит два байта) ADCW в без знаковый байт.
Преобразование типов данных - делают так :
перед тем что надо преобразовать записывают в скобках ( ) тип данных к которому нужно преобразовать.
Пишем ...
(unsigned char) (~(ADCW>>2))
Результат этой строки - один байт и мы можем поместить его в PORTB
Если в регистре DDRB все биты равны "1" - т.е. все ножки порта_B выходы, мы безусловно увидим старшие 8 бит результата АЦП горящими светодиодами.
Вам должна быть абсолютно понятна разобранная строка:
PORTB = (unsigned char) (~(ADCW>>2));
Если это не так то
повторите разбор, и перечитайте материал по Си по использованным операторам Си.
Разберем еще одну строчку
ADCSRA|=0x40;
Обратите внимание на необходимость ставить в конце выражений точку с запятой - не забывайте !
Эта строка означает следующее:
Двигаемся слева на право :
- берем значение переменной ADCSRA (это регистр МК - значит программа прочитает его, возьмет число из него)
- выполняем с этим числом операцию обозначаемую вертикальной черточкой | ( это поразрядная операция ИЛИ - только "0" и "0" дают "0" ) с числом 0x40
- присвоим или поместим результат поразрядного ИЛИ обратно в переменную ADCSRA - т.е. запишем результат в регистр ADCSRA
0x40 это в двоичном виде: 0100 0000
так как в результате поразрядного ИЛИ только два "0" дают "0" биты в ADCSRA напротив нулей не изменятся, а вот бит_6 в ADCSRA оказывается напротив "1" и теперь он станет "1" не зависимо от того каким он был до этого !
т.е. смысл рассматриваемой строки программы
ADCSRA|=0x40;
"установить" (т.е. сделать "1") бит_6 в регистре ADCSRA
Число справа от составных операторов |= &= ^= обычно называют маской,
и говорят "наложить маску" - так как в результате меняются лишь те биты которые нужно изменить.
Управление отдельными битами в переменной или регистре.
Как изменить только некоторые биты не изменяя остальные.
Для обнуления нужных бит используют обозначаемое знаком & поразрядное логическое И - только "1" и "1" дает "1"
PEREM &=(~0x04); // обнулить бит_2 в переменной PEREM Скобки здесь я добавил для улучшения читаемости кода.
Самоконтроль - важно:
а) обязательно разберитесь почему обнуляется бит_2
б) Как в двоичном виде выглядит результат (~0x04)
А вот так более понятно:
PEREM &=(~(1 << 2)); // обнулить бит_2 в переменной PEREM
Обнулить биты 5, 3 и 0 в переменной PEREM
PEREM &=(~ ( (1 << 5)|(1 << 3)|(1 << 0) ) );
конечно здесь вместо (1 << 0) можно написать просто (1)
"Установить" - сделать "1" - биты 7, 5 и 3 в переменной PEREM
PEREM |=(1 << 7)|(1 << 5)|(1 << 3);
Обязательно разберитесь как это работает - вы должны это понимать и ГЛАВНОЕ использовать в своих программах.
Например (1 << 4) означает: взять число 1 и все его биты сдвинуть в лево на 4 позиции - в итоге мы получим двоичное 10000. Эти вычисления компилятор сделает сам и в программе заменит всё, что правее = на число-результат.
Вместо номеров битов вы можете использовать их названия из даташит. Но в CVAVR для этого надо включить мой заголовок о котором написано выше или самому сделать определения битов из архива выложеного выше. А в IAR надо отметить галочку "Элау бит дефинишнс" в свойствах проекта.
WDTCR |= (1 << WDTOE) | (1 << WDE);
эта строка программы "Установит" - сделает "1" биты WDTOE и WDE в регистре WDTCR
<source lang="C">
/* Пункт 6 Функции используемые в программе */
// их может быть столько сколько вам нужно.
// у нас будет одна, кроме main и // обработчика прерывания.
/* =================================
Это будет функция в которой описано начальное конфигурирование МК в соответствии с поставленной задачей
Удобно над функцией сделать заголовок подробно поясняющий назначение функции !
===================================== */
(void)_init_mk(void) { /* Вначале любой функции объявляются ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ - если конечно они вам нужны */
/* void - означает пусто.
Перед названием функции - void - означает что функция не возвращает ни какого значения. А в скобках после названия означает что при вызове в функцию не передаются ни какие значения. */
// инициализация Port_B DDRB=0xFF; // все ножки сделать выходами PORTB=0xFF; // вывести на все ножки "1"
/* настройка АЦП - производится записью определенного числа в регистр "ADC Control and Status Register A" – ADCSRA
посмотрите его описание в ДШ МК мега16.
Нам нужно:
- Включить модуль АЦП
- Установить допустимую частоту тактирования АЦП при частоте кварца 3.69 МГц - мы выберем коэф. деления 64 - это даст частоту такта для процессов в АЦП 57.656 КГц
- Включить прерывание по завершению АЦ преобразования.
По ДШ для этого нужно записать в регистр ADCSRA число: 1000 1110 или 0х8E */
// ADC initialization w Oscillator=3.69MHz // ADC Clock frequency: 57.656 kHz // ADC Interrupts: On ADCSRA=0x8E;
/* Теперь выбираем вход АЦП ADC0 (ножка PA0) и внешнее опорное напряжение (это напряжение код АЦП которого будет 1023) с ножки AREF
Смотрим что нужно записать для этого в регистр мультиплексора (выбора входа) АЦП ADMUX
см. ДШ */
// Нужно записать 0 (он там по-умолчанию) ADMUX=0;
/* Разрешаем ГЛОБАЛЬНО все прерывания
разрешенные индивидуально !
Вы наверно поняли что индивидуально мы разрешили лишь прерывание по завершении АЦП - вот оно то и сможет возникать у нас. */
- asm("sei")
</source>
Внимание !
Так делаются Ассемблерные вставки в CVAVR :
- asm ("инструкция на ассемблере")
Обратите внимание - точки с запятой в конце НЕТ ! Такие вставки принято иногда делать. НО они не являются необходимыми.
На языке Си можно управлять ВСЕМИ программно изменяемыми битами в регистрах МК !
Напоминаю ...
Все регистры МК перечислены в таблице в конце ДШ с указанием номеров страниц с подробным описанием регистра и его битов.
Часто используются такие ассемблерные вставки : <source lang="C">
- asm("sei") // Разрешить ГЛОБАЛЬНО все прерывания
- asm("cli") // Запретить ГЛОБАЛЬНО все прерывания
- asm("nop") // Пауза в 1 такт процессора
- asm("wdr") // Сбросить сторожевой таймер
// все функция закончена
} // скобка закрывающая для функции _init_mk()
Далее...
/* Пункт 7 Главная функция main() - обязательная !
- /
/* ================================= Главная функция -
Си программа начинает выполнятся с нее!
===================================== */
void main(void){ /* Вначале любой функции объявляются
(если нужны) ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ */
_init_mk();/*Вызываем функцию инициализации, настроийки аппаратуры МК. Выполнив ее программа вернется сюда и будет выполнять следующую строку */
// запускаем первое АЦП ADCSRA|=0x40;
// бесконечный цикл в ожидании прерываний while(1); /* Программа будет крутится на этой строчке постоянно проверяя истинно ли условие в скобках после while а так как там константа 1 - то условие будет истинно всегда!
// функция main закончена
} // скобка закрывающая для функции main() </source>
Эта программа на Си будет работать так :
По завершении АЦП будет возникать прерывание и программа будет перескакивать в функцию обработчик прерывания adc_isr()
При этом будут автоматически запрещены все прерывания ГЛОБАЛЬНО !
В конце adc_isr() запускается новое АЦ преобразование и при выходе из обработчика прерывания снова разрешаются глобально прерывания, и программа возвращается опять в бесконечный цикл while(1)
Такая чехарда будет продолжаться пока есть питание МК и не будет сброса.
Светодиоды будут высвечивать 8-ми битный код АЦП напряжения на ножке PA0
Всё - программа на Си написана и разобрана.
Вам должно быть все ясно и абсолютно понятно!
Если это не так то перечитайте, подумайте, повторите разбор, почитайте рекомендованное ниже по Си.
Еще щепотка Си :
Пример:
делать что-то пока на ножке PBn есть "1"
Как AVR переводит напряжения на выводах МК в логические уровни - рассказано с картинками, графиками на 2странице 2 курса - Устройство микроконтроллера AVR. <source lang="C"> while(PINB & (1 << n)){ // для любого компилятора
Какой-то код программы
/* Какой-то код будет выполнятся снова и снова, пока проверка условия в скобках после while будет давать "истину" - значит пока на ножке PBn есть логическая единица
Проверка условия выполняется в начале и затем каждый раз по завершении выполнения какого-то кода
Пока выполняется какой-то код проверка того что на ножке PBn не производится. */
};
примечание - в CVAVR можно написать проще while(PINB.n){ // но для регистров с адресом до 31
Пример: выполнить что-то если на ножке PCn есть "0"
if((~PINC)&(1 << n)){ // для любого компилятора
что-то /* что-то начнет выполняться если на ножке PCn "0" */ };
примечание - в CVAVR можно написать проще if(!(PINC.n)){
Помните ! Выполнение чего-то может быть прервано прерыванием.
После завершения обработки прерывания выполнение чего-то продолжится.
Примечание - Условие :
if((~PINC)&(1 << n)) {
можно записать и вот так :
if(!(PINC & (1 << n))) {
Пример: выполнить что-то если на ножке PBn есть "1"
if((PINB)&(1 << n)){ // для любого компилятора
что-то /* что-то начнет выполняться если на ножке PBn "1" */ };
примечание - в CVAVR можно написать проще if(PINB.n){ </source> К битам регистров с адресами от 0 до 31 в компиляторе CodeVisionAVR можно обратится (и читать и записывать) проще.
Вот так: REGISTR.BIT
Пример: <source lang="C"> PINB.2 или PORTA.5 или DDRC.7 </source> Пример: <source lang="C"> if(!PINB.2){
этот код /* Если на ножке PB2 низкий
логический уровень - то выполнить этот код */
};
Пример: PORTA.3 = 1; /* Сделать бит 3 в регистре PORTA единицей - говорят: "установить бит" по англ. "set bit" */
Пример: PORTB.6 = 0; /* Сделать бит 6 в регистре PORTB нулем - говорят: "очистить, сбросить бит" по англ. "clear bit" */ </source>
Битовые операции подробно описаны в задаче 1
и конечно в справке - help - компиляторов !
Теперь вы должны знать
- как записать число в регистр, в переменную
- как изменить бит в регистре
- как взять число из регистра
- как выполнить что-то в зависимости от
значения бита в регистре или в переменной
- записывайте возникающие вопросы !
и лучше на бумагу - моторная память !
- найдите в DataSheet (ДШ) регистры и устройства МК использованные в задаче, прочитайте о них подробней.
- если вопросы остались перечитайте задачу снова !
- если вопросы не разрешены, ищите ответ:
1) в help и документации компилятора, симулятора, других используемых программ!
2) поиском Windows в папках и help компилятора и симулятора.
3) поиском Windows в папке где сохранен у вас курс.
4) в моем не структурированном мини-AVRFAQ - это сборник ответов на часто задаваемые мне по курсу вопросы и советы по применению МК от знающих людей.
Дальше - страница 6
Задачи - Упражнения Курса Практическая работа с Компилятором с Симулятором с МК и внешними устройствами
--Nekolex 20:47, 27 сентября 2009 (UTC)