Курс AVR123.nm.ru


Electronic Banner Exchange (ElBE)
 


 

 
         
 

 

Задача - упражнение 5

 

Цель задачи: 

1)  Создать программу для  МК  ATmega16 принимающую и передающую данные обмениваясь ими с ПК через COM-порт 
по интерфейсу rs232  с помощью USART встроенного в МК 
серии ATmega.

Схемы и компоненты для сопряжения МК с 
COM-портом ПК  смотрите  в задаче 4 курса.

2)  углУбить навыки создания программы  в CVAVR и продОлжить учится 

 
             использовать Си для микроконтроллеров


3)  Подключить и "порулить" символьным ЖКИ - LCD на контроллере hd44780 - очень популярны 

 

 

поиск   GOOGLE   по 
Краткому Курсу AVR

Книги и учебники по электронике и микроконтроллерам есть тут

 

 

 

Делаем :

Для того чтобы МК мог обмениваться данными с ПК - т.е.  
принимать и отправлять данные  (обычно это байты, т.е. 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.

Проанализируйте то что вы будет получать 
при симуляции и убедитесь что это соответствует 
тексту программы.

 


 

ЗАДАЧА    ОКОНЧЕНА !


     
 

Если вы еще не скачали ВСЕ
примеры применения МК AVR
 

прошу вас, сделайте это ОБЯЗАТЕЛЬНО - 

ЭТО СПРАВОЧНИК для вас !

 
     

 

Если вам что-то не понятно, пожалуйста перечитайте 
задачу еще раз и просмотрите рекомендованный материал.  

 

 

Дальше ->    Задача 6

 

 

 

 

 

 

 

 

Hosted by uCoz