Задача -
упражнение 5
Цель
задачи:
1)
Создать программу для МК ATmega16 принимающую
и передающую данные обмениваясь ими с ПК
через COM-порт
по интерфейсу rs232 с помощью USART
встроенного в МК
серии ATmega. Схемы
и компоненты для сопряжения МК с
COM-портом ПК смотрите в задаче
4 курса.
2)
углУбить навыки создания программы в
CVAVR и продОлжить учится
использовать
Си для микроконтроллеров
3)
Подключить и "порулить" символьным
ЖКИ - LCD на контроллере hd44780 - очень
популярны
Книги и
учебники по электронике и микроконтроллерам есть
тут
Делаем
: Для
того чтобы МК мог обмениваться данными с
ПК - т.е.
принимать и
отправлять данные (обычно это байты,
т.е. 8-ми
битные числа)
нужно : 1)
соединить его физически с ПК способами описанными
в задаче 4 или с симулятором
и 2)
настроить == сконфигурировать USART МК
т.е. -
установить скорость передачи в
бодах из перечня
скоростей стандартных
для ПК и
- установить формат передачи
данных. Стандартные
скорости обмена вашего ПК можно посмотреть
через
"панель управления" -> "система"
-> "COM-порт" -> "свойства"
|
|
|
|
Для
обеспечения с достаточной
точностью скорости обмена
обычно требуются кварцы
специальных UART'овских частот - их
можно найти в таблице ДШ в разделе
USART. Не
советую использовать внешний или встроенный
RC-генератор
для тактирования МК при обмене с ПК -
их точность слишком мала и
наверняка будут сплошные ошибки в
передаче.
|
|
|
|
|
Вот
пример настройки и включения USART ATmega16
на
прием и передачу для любого компилятора
Си
|
|
|
|
// USART initialization
// Communication Parameters: 8 Data, No Parity, 1 Stop 8N1
// USART Receiver: On
// USART Transmitter: On
// USART Mode: Asynchronous
UCSRA=0x00;
UCSRB=0x98;
UCSRC=0x86; // USART Baud rate: 115200
для кварца
11,059 МГц
UBRRH=0x00;
UBRRL=0x05;
|
|
|
|
|
Обязательно!
Найдите в ДШ все использованные
регистры
и посмотрите что мы в них
записали и зачем. Вы
должны знать значение каждого бита! Не
ленитесь !
Формат
передачи данных (см. ДШ Frame Formats) в примере:
// Communication Parameters: 8 Data, 1 Stop, No Parity
|
|
|
|
Сокращенно
называют 8N1 - это формат по умолчанию для
ПК. В
таком формате передача байта
начинается (естественно передающим
устройством!) со "стартового бита"
- это лог. "0" на ножке TXD для
USART МК и +5...+15 вольт для COM порта ПК на
время одного бода. Время
одного бода это результат
деления
1 сек на скорость передачи в бодах. Затем
на ножку TXD выводятся все 8 бит
передаваемого байта начиная с бит_0.
Каждый бит держится на TXD в течении
времени передачи одного бода - за
это время приемник должен
определить и запомнить этот уровень. Далее
идет "стоп бит" - это лог.
"1" на ножке TXD для USART МК и
-5...-15 вольт для COM порта ПК на время
одного бода. Таким
образом - на
передачу одного байта в формате
8N1 требуется время - 10 бод.
|
|
|
|
|
Кусок кода выше я
привел для примера.
Я очень советую вам
использовать Мастер начального кода
компилятора - Он сам сгенерирует
вам все в нужном виде и правильно
настроит USART.
Главное
- мастер покажет вам допустима ли ошибка
в скорости
передачи при использовании не UART'оских
кварцев.
От
слов к делу ... Запустите
компилятор CodeVisionAVR, затем генератор
начального, конфигурирующего кода
программы для МК - "CodeWizardAVR"
- кликнув серую шестеренку слева от
красного жучка...
|
|
|
|
Подробно
и с картинками работу с КодВизадом
CodeVisionAVR я описал в задаче 1
|
|
|
|
|
Выберите в разделе Chip
-
МК ATmega16 и частоту кварца 11.059 МГц и
Переходите на закладку USART
- сделайте
такие установки:
Поставьте 3 галочки :
- включить приемник (Ресивер),
- прерывание по приему символа и
- включить передатчик (Трансмиттер).
Число
100 означает - создать буфер в оперативной
памяти МК на 100 символов.
|
|
|
|
Чтобы
не терять приходящие от ПК символы
и не загружать МК постоянной
проверкой - не получен ли новый
символ в UDR удобно -
использовать прерывание
номер 12 "USART
Rx Complete" (стр. 43 ДШ)
его вызывает
завершение приёма символа в регистр UDR и
еще -
создать буфер в который МК может
в функции обработчике прерывания
12
положить принятый символ. А когда
по программе понадобится или МК освободится от
другой задачи,
он сможет брать символы уже из этого буфера. Буфер
устроен по принципу FIFO "фёст ин -
фёст аут" а по-русски: "первый
пришел первый вышел" это значит
что байт который пришел в FIFO буфер
первым, первым же и выходит из него. FIFO
- это что-то типа трубы с одной
стороны загружаемой шарикам
диаметром чуть меньше внутреннего
диаметра трубы и вынимаемыми с
другой стороны трубы. Емкость
такого FIFO будет равна целому
частному от деления длины трубы на
диаметр шарика.
Шарик - это байт. Все
это правильно и удобно делать с помощью
генератора
начального кода
компилятора - CodeVisionAVR
|
|
|
|
|
Буфер может быть
нужного вам размера - его ограничивает
лишь объем оперативной памяти МК.
Советую вам
сделать буфер в 2 раза больше того что вы
считаете нужным
по предварительным прикидкам.
Скорость
обмена данными - "БадРэйт" -
установили 115200. Скорость
USART может быть только одна в каждый
момент в МК и для приема и для
передачи. Если вам нужно принимать на
скорости отличной от передачи то вы не
сможете это делать одновременно и
должны будете по необходимости включать
либо передатчик либо приемник и
устанавливать нужную скорость. Но
я бы
посоветовал вам просто взять МК с двумя
USART
например ATmega64 и ATmega128. Больше
ни чего менять в панели USART не нужно. Переходим
к закладке LCD -
мы подключим жидкокристаллический
индикатор на 2 строки по 16 символов с
встроенным контроллером HD44780 - это очень
распространенные индикаторы (см. стр.
1 курса).
Для
подключения LCD вам достаточно
указать желаемый порт МК и количество
символов в строке. Количество строк
указывать не нужно. С
лева указаны выводы МК, а в правой
колонке указаны выводы LCD - их нужно
соединить между собой. Кроме того нужно
соединить "земли" и подать питание
на индикатор и подать напряжение
контраста - делать это надо в
соответствии с ДШ
на LCD.
|
|
|
|
ATMEL
сделал АпНоут
ANM069 "How to Interface a LCD Display to a TSC80251 Microcontroller"
советую
скачать и почитать.
|
|
|
|
|
В
закладке PORTA
сделайте PA3 выходом - мы можем
использовать
его для индикации
различных событий при отладке. Теперь В
меню файл "КодВизада" выберите "генерэйт
сэйв энд экзит", и создайте в папке компилятора
специальную папку для файлов задачи :
C:\CVAVR\z5 Затем
сохраните в ней созданный начальный
код программы usart.c
и файл проекта z5.prj Посмотрите
текст программы созданный
генератором
самостоятельно. Вам уже многое должно
быть понятно ! Си
для МК на стр. 5 курса Программа. Рассмотрим
участок программы создающий буфер
для принимаемых МК по USART
символов.
|
|
|
|
/*****************************************************
Chip type : ATmega16
Program type : Application
Clock frequency : 11,059000 MHz
Memory model : Small
External SRAM size : 0
Data Stack size : 256
*****************************************************/
#include <mega16.h>
// Alphanumeric LCD Module functions
#asm
.equ __lcd_port=0x1B ;PORTA
#endasm
#include <lcd.h>
#define RXB8
1
#define TXB8
0
#define UPE
2
#define OVR
3
#define FE
4
#define UDRE
5
#define RXC
7
#define FRAMING_ERROR (1<<FE)
#define PARITY_ERROR (1<<UPE)
#define DATA_OVERRUN (1<<OVR)
#define DATA_REGISTER_EMPTY (1<<UDRE)
#define RX_COMPLETE (1<<RXC)
|
|
|
|
|
Как я рассказывал на странице
5 "язык Си для МК" -
программа на Си начинается с заголовка,
затем вставляются тексты внешних файлов-библиотек
с помощью команды препроцессора #include <текстовый
файл>
Затем
с помощью первого блока определений #define
компилятору
сообщены номера битов по ДШ
используемых в программе при работе с
USART Определения
отдельных битов есть в компиляторах ICC,
IAR
и других, но их нет в CodeVisionAVR поэтому я
сделал для вас заголовочный
файл m8_128.h -
скачайте его добавьте
в программу вот так: #include <mega16.h>
//обычный хидер
#include <m8_128.h>
//мой хидер для битов Теперь
вы сможете использовать примеры на Си
из
ДШ прямо так как они там написаны ! Второй
блок определений #define
делает программу
более понятной и читаемой человеком
давая осмысленные названия
малопонятным битовым маскам например:
понятная человеку
фраза DATA_REGISTER_EMPTY
в
тексте программы на Си будет
заменена
препроцессором перед компиляцией на
битовую маску (1<<UDRE)
с помощью которой можно проверить событие "регистр данных пуст" определяя
состояние бита UDRE.
|
|
|
|
Битовая
маска (1
<< Название_бита_по_ДШ)
Это число у
которого все биты равны "0" а бит с
номером
как номер бита Название_бита_по_ДШ
равен "1".
Получается
маска сдвигом числа 1 из позиции бит_0
в лево на
количество бит как
номер бита Название_бита_по_ДШ
Подробнее
про операции над битами в задаче
1 курса.
Битовая
маска может иметь несколько единиц
и нулей.
|
|
|
|
|
Теперь
чтобы проверить свободен ли UDR
- регистр
данных USART - достаточно сделать
логическое "И" числа в регистре UCSRA
с маской DATA_REGISTER_EMPTY
"Истина"
будет означать что UDRE
равен "1" и значит регистр данных
пуст.
Внимание! Генератор начального кода CVAVR
выдает
бит UPE
- на самом деле в ATmega8 -16 -32 в регистре
UCSRA
бит_2 называется PE.
На
работу программы это не влияет.
Дальше
...
|
|
|
|
//
Буфер - USART Receiver buffer
#define
RX_BUFFER_SIZE
100
char rx_buffer[RX_BUFFER_SIZE];
#if RX_BUFFER_SIZE<256
unsigned char
rx_wr_index, rx_rd_index, rx_counter;
#else
unsigned int
rx_wr_index,
rx_rd_index, rx_counter;
#endif
// This flag is set on USART Receiver
bit rx_buffer_overflow;
// buffer overflow
|
|
|
|
|
В начале строка
задающая нужный пользователю размер
буфера - это
может быть число от 1 (0 - это бессмысленно
пожалуй) до 65535 - это максимальное
положительное число из двух байтов.
Благодаря использованию директивы
препроцессора
#define
мы сможем в случае необходимости
изменения размера буфера, сделать это
поменяв число 100
только в этой строке - не смотря на то,
что используется оно в нескольких
местах программы !
Вывод:
#define
- это удобно! и снижает вероятность ошибок.
Позволяет просто модифицировать
программу - используйте !
Следующей
строкой объявлен
символьный массив : char
rx_buffer[размер
массива в символах]; размером
по заказанному нами буферу. Символьный в
нашем случае - потому что char
указывает на типа данных - бес знаковый
символьный - это числа от 0 до 255 - по
таблице ASCII им соответствуют символы
- (таблицу ASCII см. стр. 5 курса).
Напомню,
что в массиве нумерация элементов
начинается с НУЛЯ ! Значит первый элемент этого массива
на 100 элементов:
rx_buffer[0]
а последний :
rx_buffer[99]
|
|
|
|
Далее
очень интересно !
Обратите внимание что и в
директивах препроцессора можно
использовать конструкцию -
if else #if
условие
подставить в текст программы если условие
"истина"
#else
подставить если условие "ложь"
#endif только
не нужны скобки ( ) и {
}, перед ключевыми
словами ставьте
знак # и
закончить конструкцию нужно так как
в примере ! Используйте!
в любом месте программы где нужно.
|
|
|
|
|
С помощью этой конструкции в
зависимости от размера буфера
выбирается подходящий тип (байт или два
байта) трех глобальных
переменных
необходимых для работы с
буфером.
Вспомните
и скажите вслух - что такое
глобальная переменная, каковы ее свойства, какие еще типы
переменных вы знаете. Последняя
строка обсуждаемого куска кода (выше, на голубом фоне)
объявляет
битовую переменную bit rx_buffer_overflow; это
флаг (можно сказать - признак) переполнения буфера - если
rx_buffer_overflow
"установится" т.е. станет
"1" - плохо дело !
- мы
долго не забирали символы из буфера и он
переполнился, значит мы потеряли
некоторые символы которые пришли раньше.
Битовые
переменные могут быть только
глобальными !
т.е. они объявляются в начале
программы не в теле какой либо функции
-
значит вне фигурных скобок
{ } и доступны программе
в любом ее месте.
Это в CVAVR, а в других компиляторах
может быть и по другому.
|
|
|
|
Совет
! Не
жидитесь! Используйте
под флаги переменные типа
char
вместо битовых.
Они во всех компиляторах одинаковы -
значит не запутаетесь.
Если
вы выбрали правильный
МК то с
недостатком памяти вы не столкнетесь.
|
|
|
|
|
Дальше
функция-обработчик
прерывания № 12 :
|
|
|
|
//
USART Receiver interrupt service routine
interrupt [USART_RXC]
void usart_rx_isr(void)
{
char status, data;
status=UCSRA;
data=UDR;
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0)
{
rx_buffer[rx_wr_index]=data;
if (++rx_wr_index == RX_BUFFER_SIZE)
{
// я
добавил эти скобки
rx_wr_index=0;
};
// для
лучшей читаемости
if (++rx_counter == RX_BUFFER_SIZE)
{
rx_counter=0;
rx_buffer_overflow=1;
};
};
}
|
|
|
|
|
Эта функция
называется usart_rx_isr()
и вызывается при возникновении
прерывания
USART_RXC
(см. ДШ на МК) по приему
символа в регистр UDR
это
регистр данных USART, причем два регистра
данных и приемника и
передатчика
называются одинаково.
Т.е.
программа МК перестает делать то что
делала, сохраняет некоторые
данные
необходимые для продолжения работы с
этого места после возврата
из
обработчика прерывания и начинает
выполнять функцию обработчик
прерывания - usart_rx_isr()
Разберем что
делает функция-обработчик прерывания 12.
Вначале объявлены две локальные
переменные
символьного типа
status
и
data.
Локальные
- значит они объявлены сразу после
открывающей скобки какой либо функции : {
объявление локальных переменных далее
остальное
тело функции
} Локальные
переменные
доступны только в той функции в
которой объявлены !
Значение
Локальных переменных без
модификатора static
не сохраняется к следующему
вызову функции, и нам не известно что в
них содержится при создании - т.е. им
нужно присвоить некоторое значение если
мы не сделали это при объявлении. Вот
программа и присваивает им значения в следующих двух
строках: status
= UCSRA;
data = UDR; В
status
копируется число из UCSRA
- это регистр
управления и статуса
- состояния USART (стр. 162 ДШ). В
data
копируется число из UDR
- это регистр для принимаемых данных.
(стр. 161 ДШ).
Затем
проводится проверка
правильно ли USART MK принял
то что пришло на ножку RXD от ПК или
другого устройства :
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0)
{
|
Код программы написанный здесь будет
выполнятся ТОЛЬКО если условие в
скобках if("истина") |
};
|
|
|
|
Условие
в скобках у if
довольно сложное
Используя
сведения со страницы 5
"Си для МК" - попробуем
разобраться что это условие
означает.
Это
важно!
Обязательно разберитесь, поймите !
Структура
условия такова:
if ( (
2 ( 1 ))
3 )
оно
состоит из нескольких выражений
заключенных в скобки, побитных
логических операций в 1 и 2 и
логической операции 3.
Начнем
с самых глубоких скобок - красных.
В них написано:
FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN
Если
вы внимательно читали задачу (я
очень надеюсь!) то знаете,
что каждое словосочетание
благодаря директивам #define
в начале программы означает сдвиг
единицы на некоторое количество
позиций в лево - т.е. установку
только некоторого одного бита в
нулевом байте.
Т.е. мы имеем :
число | число | число
Так как
эти три числа соединены побитным
"ИЛИ" (напомню: это
означает что только "0" и
"0" дают "0") то в результате
мы получим число где 3 бита будут
равны "1" - а номера этих
битов будут 4, 3 и 2.
Теперь разберем какой результат
даст
вычисление содержимого синих
скобок.
В них
проводится побитная операция
"И" (напомню: это означает
что только "1" и "1" дают
"1") содержимого переменной status
с тем числом которое является
результатом красных
скобок.
Значит
если в status
(а значит и в регистре UCSRA)
- хотя бы один из битов 4, 3 или 2
будет "установлен" - т.е. равен
"1" результат "И" будет
не равен нулю.
Нулю
результат красных
скобок может быть равен -
только если все три бита в UCSRA
были равны "0" - т.е. если
данные были приняты без аппаратно
детектируемой ошибки !
При
этом же условии третье
действие : логическая
операция ==0
даст результат "истина" так как
ноль и правда равен нулю,
Значит то что написано в скобках {
} после if()
будет выполняться при безошибочном
принятии данных.
|
|
|
|
|
Если
же при приеме байта возникла любая из
ошибок :
FRAMING_ERROR PARITY_ERROR DATA_OVERRUN
то факт принятия данных игнорируется
и следующий код функции обработки
прерывания закончен - т.е. программа
выходит из прерывания не помещая
"криво" принятый байт в буфер и
возвращается к тому месту где
возникло
прерывание № 12. Пусть
ошибок не было,
тогда ... Выполняется
код в скобках {
} после if()
Разберемся что он делает. Вот
он со скобками :
|
|
|
|
{
rx_buffer[rx_wr_index]=data;
if (++rx_wr_index == RX_BUFFER_SIZE)
{
// я
добавил эти скобки
rx_wr_index=0;
};
// для
лучшей читаемости
if (++rx_counter == RX_BUFFER_SIZE)
{
rx_counter=0;
rx_buffer_overflow=1;
};
};
|
|
|
|
|
Первая строка кода
помещает принятый байт в буфер -
конкретно в элемент массива rx_buffer
с порядковым номером rx_wr_index
- этот номер называют индексом массива.
В начале программы индекс rx_wr_index
равен нулю. Почему ?
А
потому что
переменная rx_wr_index
была объявлена как глобальная.
|
|
|
|
Глобальным
переменным при объявлении
компилятор присваивает значение 0
, если конечно вы не присвоили ей
явно другое значение,
например вот так: char
kakayto_peremennaya = 78; "Какой
то переменной" при объявлении
присвоено значение 78
|
|
|
|
|
При следующих
прерываниях по приему данных значение
индекса будет меняться ! Если мы будем
достаточно быстро забирать данные из
буфера то иногда индекс будет опять
становится нулем.
Далее идет
интересная конструкция :
if (++rx_wr_index == RX_BUFFER_SIZE)
{
// я
добавил эти скобки
rx_wr_index=0;
};
// для
лучшей читаемости
я бы не
советовал писать на Си начинающему так
как накрапал мастер - но КодВизад создан
профессионалом и потому нам нужно
просто разобрать и понять что этот код
делает. Во
первых я добавил фигурные скобки {
}; - чтобы четко
было видно
какой код выполняется при "истине"
в скобках ( ) за
if
Советую
вам ! Всегда
дополняйте
if() while() for()
Фигурными скобками
{ };
исключения
:
while(1);
и
for(;;);
Итак :
Выражение-условие в if()
означает: префиксный (так как ++
написаны перед переменной) инкремент
переменной rx_wr_index
- т.е. добавление 1 к тому,
что содержится в переменной. И затем
проверяется равенство уже нового
значения rx_wr_index
с установленным
нами размером буфера.
|
|
|
|
Напомню
что добавление некоторого числа к
содержимому переменной не всегда
увеличивает ее значение ! Если
к переменной типа char
хранящей число 255
прибавить хотя бы 1
и результат записать в эту
переменную - то в ней будет 0.
Вы
очевидно понимаете что 255 + 1 = 256
256 в двоичном виде 1 0000 0000 т.е.
только бит_8 равен "1" а биты_7_0
все равны "0". При записи числа
256 в восьми-битную переменную типа char
в нее поместятся как раз 8 нулей и
поэтому в ней будет НОЛЬ.
|
|
|
|
|
Если перед
if индекс rx_wr_index
был равен 99 то после инкремента индекс
станет равен 100 и проверка на
равенство с RX_BUFFER_SIZE
даст "истину" - значит будет
выполняться то, что написано в фигурных
скобках после
if - это строка:
rx_wr_index
= 0;
// обнуляет индекс. Как
видите индекс может стать нулем, как был
в начале программы, и в том
случае если
мы долго не забирали символы из буфера и
он переполнился !
|
|
|
|
Я бы
вот так написал этот участок кода
: rx_wr_index
++; //
инкремент индекса //
сравнение
уже увеличенного значения индекса
if (rx_wr_index == RX_BUFFER_SIZE)
{
rx_wr_index=0;
};
Мне
кажется, что так - по шагам -
писать и читать
программу проще, особенно
начинающим.
|
|
|
|
|
Далее
еще одна проверка условия и действие по
результату: if (++rx_counter == RX_BUFFER_SIZE)
{
rx_counter=0;
rx_buffer_overflow=1;
};
Здесь ++ стоят перед
переменной rx_counter
- значит это
префиксный инкремент - т.е. к
значению переменной добавляется 1 и
затем уже проверяется равно ли текущее
содержимое переменной размеру буфера.
Если равно -
значит буфер переполнился и будут
выполнены две строки кода rx_counter=0;
//
Обнулить счетчик принятых байтов в
буфере
rx_buffer_overflow=1;
// Установить
флаг переполнения буфера
|
|
|
|
Важно
понять !
Операции
++ и - - изменяют то, что хранится в
переменной ! Несмотря
на отсутствие оператора
присваивания.
|
|
|
|
|
Значит переменная
rx_counter
изменится на
1
не зависимо от истинности
условия в скобках у if()
Всё
- обработка прерывания завершена.
Теперь
программа отправится в то место где она
находилась
в момент возникновения прерывания №12 ГЛАВНОЕ
! Так как мы выполнили
функцию обработчик прерывания до конца,
опять установится бит I
в регистре SREG - это разрешит
прерывания в МК глобально и те прерывания которые
разрешены индивидуально опять смогут
прерывать ход программы. Далее
в тексте программы
идет созданная мастером начального кода
альтернативная обычно используемой в
CodeVisionAVR функция "получить символ" -
функция "получить символ из
созданного буфера" - getchar()
НО
! Будет ли
она включена препроцессором
компилятора в компилируемый текст
программы определяет конструкция из директив
препроцессора: #ifndef
#define ...
#endif Вы
уже должны понимать как работает эта
конструкция ! Поясню:
Если мы делаем программу не для отладки
на встроенном в CodeVisionAVR симуляторе
терминала, а для реального МК или для
внешнего симулятора - то в тексте
программы не было определено: #define _DEBUG_TERMINAL_IO_
поэтому
будет выполнена строка :
#define _ALTERNATE_GETCHAR_
и остальные
строки на голубом фоне ниже будут
включены в компилируемую программу
препроцессором.
|
|
|
|
#ifndef _DEBUG_TERMINAL_IO_
// Get a character from the USART Receiver buffer
// взять символ из созданного буфера USART
#define _ALTERNATE_GETCHAR_
#pragma used+
char getchar(void)
{
char data; // локальная
переменная
while (rx_counter==0);
data = rx_buffer[rx_rd_index];
if (++rx_rd_index == RX_BUFFER_SIZE)
{
rx_rd_index=0;
}; // я добавил
{ }; для вящей читаемости
#asm("cli")
--rx_counter;
#asm("sei")
return data;
}
#pragma used-
#endif
// Standard Input/Output functions
#include <stdio.h>
// Declare your global variables here
|
|
|
|
|
Подробно
рассмотрим "тело"
новой функции
Первая строчка :
char getchar(void)
говорит нам,
что функция возвращает значение
типа char
и при вызове не требует передачи в нее
значений - вот поэтому: (void)
Сразу после
скобки {
объявлена локальная (существующая и
доступная только в этой функции)
переменная символьного типа char
с названием: data
Следующая
строка:
while (rx_counter == 0);
это цикл
ожидания поступления символа в буфер. В
ней программа будет
"сидеть" а точнее "молотить"
пока нет прерывания и нет символа в
буфере,
ведь rx_counter
это счетчик символов находящихся в
буфере.
|
|
|
|
думаю
вы уже понимаете что
while (rx_counter == 0);
равносильно:
while (!(rx_counter));
Если
не понятно, перечитайте Си
для МК !
Не откладывайте на потом !
|
|
|
|
|
Если
в буфере были символы или появился
символ, программа перейдет
на следующую строку:
data = rx_buffer[rx_rd_index];
Тут все
просто: = означает
присвоить переменной слева от =
результат выражения справа от =
Значит программа
возьмет символ из буфера (из массива rx_buffer[])
с порядковым номером rx_rd_index
и поместит его в data
Далее происходит увеличение индекса
буфера на 1 и если результат будет равен
размеру буфера - индекс обнуляется.
if (++rx_rd_index == RX_BUFFER_SIZE)
{
rx_rd_index=0;
}; // я добавил { };
для вящей читаемости
Так
как мы считали символ из буфера нужно
уменьшить число символов в нем
подлежащих считыванию.
#asm("cli") // запретить
прерывания глобально
--rx_counter; // вычесть 1 из rx_counter
#asm("sei") //
разрешить прерывания глобально
Обратите
внимание что перед декрементом мы
запрещаем прерывания, а после
прерывания включаем опять.
Зачем
? Вот
фо ??? воскликнул бы англоязычный
читатель ...
А затем, что
в обработчике прерывания есть инкремент
rx_counter
- и если на декременте возникнет
прерывание мы можем зациклится в этом
месте программы и получить ошибку в
числе символов в буфере.
последняя
строчка функции
return data;
означает что функция
вернет то что находится в переменной data
т.е. если мы
напишем такой вызов функции: nechto
= getchar();
то после
возврата из функции
в переменной nechto
окажется то, что было помещено в
data
в функции. Я
подробно рассмотрел код создающий
буфер для поступающих в МК данных.
Далее
программа типична для создаваемых
КодВизадом.
Как
принимать и отправлять
данные по USART
Варианты кода для отправки и получения
данных с помощью USART, например на ПК.
Прочитайте
самостоятельно
раздел "Standard C Input/Output Functions"
help CodeVisionAVR (или того компилятора
который будете использовать) и
включите заголовок - хидер : // Standard Input/Output functions
#include <stdio.h> В
нашей программе этот хидер уже
подключен мастером ! 1)
отправить один символ типа char
можно так: putchar('G');
putchar(71);
putchar(0x47); Любая
из этих трех строк кода выводит на
ножку TXD число 71. На терминале ПК вы
увидите соответствующий этому числу
символ
G
В 16-тиричном виде это число 0x47 Запомните
форму записи чисел ! Таблица
символов и их кодов есть на странице Си
для МК Кроме
того один символ или число от 0 до 255
можно отправить с МК так: while(!(UCSRA & (1<<UDRE)));
// дождитесь освобождение
регистра передачи
// Отправьте число одним из 3-х
вариантов
UDR = 'G';
UDR = 71;
UDR = 0x47; Это
же можно написать в одну строчку:
while(!(UCSRA & (1<<UDRE))); UDR =
'G';
2) отправить
строку символов можно вот так:
putsf("Hello,
world!"); На
ножку TXD будут выведены все символы
между кавычками (в примере их 13) и еще
символ "конец
строки" - его код 0x0A а называется
он LF от англ. "лайн фид"
Следующие данные будут выводится на
терминале ПК (или на его симуляторе TTY
в VMLAB или в CVAVR) с начала
следующей строки.
|
|
|
|
Символ
"конец строки"
(его код 0x0A и называется он LF)
можно передать
вот так: putchar('\n');
или так putchar(0x0A); |
|
|
|
|
Строку символов
для отправки можно записать и в виде
последовательности шестнадцатеричных
значений:
putsf("\x41\x54\x44\x3e\x53\x4d\x31\x3b\x0D\x41"); На
ножку TXD будут выведены все символы
между кавычками (в примере их 10) и еще
символ "конец
строки" - его код 0x0A а называется
он LF от англ. "лайн фид"
Будет передаваться такая
последовательность из 11 символов: ATD>SM1;<CR>A<LF>
цветом
я отметил что это ОДИН
символ ! в
терминале VMLAB она будет выведена в две
строки так: ATD>SM1;<CR>
A<LF> В
терминале ПК не отображающем не
печатные символы будет такой результат: ATD>SM1;
A Символы
(данные) которые МК будет передавать
позже будут
печататься уже с начала новой строки.
3) отправить
информационные сообщения программы
можно так:
Вначале
нужно описать эти сообщения - они буду
размещаться во FLASH
(в EEPROM) MK - т.е. это
не переменные а КОНСТАНТЫ !
flash char string_1[]="Prog
Start";
flash char string_2[]="The
END";
// Declare your global variables here
Теперь
вы можете выводить сообщения в нужном
вам месте программы так:
printf(string_1);
На
ножку TXD будут выведены все символы
между кавычками (в примере их 10)
без
добавления символа "конец
строки"
|
|
|
|
Что
бы ваши сообщения гарантировано
выводились с начала строки -
добавьте символ
"конец строки"
(его код 0x0A и
называется он LF) перед вашим
сообщением вот так: flash
char string_1[]="\nProg
Start"; |
|
|
|
|
4)
Вывести данные и поясняющий,
оформляющий эти данные текст удобно
с помощью printf()
это мощная функция и полное ее описание
почитайте в Help компилятора. Примеры
использования
printf()
в папке C:\CVAVR\Examples\ Например
вывод температуры : char
sign='+'; //датчик дал
положительное число
int temp=578; //число с
датчика темп. LM75
printf("temp = %c%i.%u C\r\n",sign,temp/10,temp%10);
На
терминал ПК будет выведено :
temp = 57.8 C
и
курсор перейдет в начало новой строки. Происходит
это так:
На терминал выводится то что находится
между кавычками "
" -
текст temp = выводится
без изменений. Знак
=
окружают пробелы ! -
вместо %c будет
вставлен символ из переменной sign -
вместо %i будет
вставлен результат temp/10
без
дробной части -
точка так и будет точкой -
вместо %u будет
результат (temp%10)
это дробная часть от деления
- далее пробел - он так и будет
пробелом -
затем символ C - он им же и останется -
потом стоит комбинация
\r\n -
она
выводит символы CR и LF их коды 0x0D
0x0A. Благодаря этому следующие данные
будут выводится с начала строки через
строчку ниже. Прием
данных поступающих в
USART МК на ножку RXD
у нас происходит автоматически в буфер.
В том месте программы где вам нужно
вставить наиболее давно
пришедший в буфер символ (число) впишите
такую строчку: getchar()
Примеры
:
gdvix
= getchar();
/*
наиболее старый символ из буфера
поместить в переменную gdvix */ if
(getchar() == 'G'){
/*
выполнить ЭТОТ код если наиболее
старый
символ из буфера окажется буквой G
*/
}; while
(getchar() == 'F');
/* взять
из буфера наиболее старый
символ и если это не F то делать еще раз,
а если это F пойти на следующий код
программы */
|
|
|
|
Код
выше можно применить если вы
ожидаете получения какого
либо пакета данных, и знаете каким
числом он должен начинаться.
Несколькими строками аналогичного
кода можно определить нужную
последовательность поступающих
данных. |
|
|
|
|
if
(rx_counter)
{
/*
выполнить ЭТОТ код если в буфере есть не
считанные символы, данные */
};
if
(rx_counter > (RX_BUFFER_SIZE/2))
{
/*
выполнить ЭТОТ код если буфер уже
заполнен больше чем на половину */
}; Что
бы вывести самый старый символ из
буфера на ножку TXD напишите так:
putchar(getchar());
Эту
строчку и три первых примера выше -
можно использовать
и без буфера, но программа будет
засиживаться в них до тех
пор пока не будет получен символ ! Если
написать так : while(1){
putchar(getchar());
}; то
МК будет отправлять приходящее на ножку
RXD на ножку TXD Вы
сможете попробовать это в симуляции ! Как
выводить данные на
символьный LCD - ЖКИ Загрузите
1 страничку с описанием команд
управления ЖКИ LCD
на контроллере HD44780 и совместимых с ним. Советую
посмотреть АпНоут ANM069 от ATMEL
Для
использования LCD нужно указать
компилятору к каким ножкам МК он
подключен и сколько символов в строке он
имеет, для нас это любезно сделал
генератор начального кода:
// Alphanumeric LCD Module functions
#asm
.equ __lcd_port=0x1B ;PORTA
#endasm
#include <lcd.h>
// подключить хидер - библиотеку для LCD
А
уже в программе строка:
lcd_init(16); инициализирует
именно ЖКИ (LCD) где 16 символов на
строку При
необходимости, мы можем указать в
какую
позицию выводить символ вот так: lcd_gotoxy(4,1); и
вывод символов начнется с 5-й позиции
во второй строке. Счет
строк и символов начинается с НУЛЯ ! Давайте
добавим в нашу программу вывод
приветствия на LCD
lcd_gotoxy(5,0);
lcd_putsf("Hello");
lcd_gotoxy(2,1);
lcd_putsf("avr123.nm.r");
lcd_putsf("u");
В
результате на LCD будет выведен такой
текст : это
скриншот симуляции в VMLAB
Смотрите
- "чекбокс" "Break on com..."
позволяет останавливаться
симуляции при ошибке в командах на LCD.
Внимание
!
Я специально разделил вывод ru
чтобы показать, что следующий символ u
выводится в позицию следующую за
последним выведенным символом.
Прочитайте раздел LCD Functions в Хлпе
компилятора!
Очистить
экран ЖКИ вы можете вызвав функцию:
lcd_clear();
Вывести
символ, например F
на ЖКИ lcd_putchar('F');
Вывести
на LCD строку символов из EEPROM MK можно
вот так: lcd_putsf(string_1); Объявление
и текст этой строки были даны выше !
|
|
|
|
Вы
можете использовать один
LCD в нескольких устройствах !
Подключая
его через разъем при необходимости.
Для
этого вам нужно сделать любой
свободный вывод МК PxN - входом с
подтяжкой, и в разъеме от LCD
поставить перемычку с этого "пина"
на GND.
Если вы подключите LCD к вашему
устройству - ножка PxN заземлится -
значит на ней появится "0".
Теперь
нужно в программе поместить ВСЕ
строки работающие
с LCD внутрь скобок
{ } у
оператора if
вот так:
if(!(PINX
& (1 << N)){
/* тут код
работающий с LCD
Он будет выполняться только
если на PxN
есть "0" */
};
Если
LCD не подключен, то на PxN
будет "1" и программа не будет
выполнять код работающий с
ЖКИ индикатором.
|
|
|
|
|
Так
же вы можете работать с другими
опционально подключаемыми к МК
устройствами !
Все
файлы для компилятора и для симуляции
в VMLAB того что
рассмотрено в задаче вы можете скачать в
архиве - z5.rar При
остановках симуляции
в VMLAB просто нажимайте на светофор
...
Терпение! инициализация ЖКИ в
симуляторе дело не быстрое. Посмотрите
симулируя прогу в VMLAB осциллограммы
приема
и отправки данных по USART. Проанализируйте
то что вы будет получать
при симуляции и
убедитесь что это соответствует
тексту
программы.
ЗАДАЧА
ОКОНЧЕНА !
Если
вам что-то не понятно, пожалуйста
перечитайте
задачу еще раз и просмотрите
рекомендованный материал.
Дальше ->
Задача 6
|