Практикум по микроконтроллерам Atmel AVR. Пошаговое создание проекта в CodeVision AVR

Intro

Меня попросили рассказать про Code Vision AVR на примере разработки несложного, но в тоже время не совсем бесполезного как светодиодная мигалка, устройства.

Ну что ж, рассказываю все по порядку...

Схемотехника

В качестве такой поделки был выбран термометр в двумя датчиками, например для измерения внутренней и наружной температуры. Для отображения температуры использован семисегментный светодиодный индикатор. Он может быть абсолютно любого типа, даже и составленный из отдельных светодиодов, главное - раздельные катоды и восемь знакомест. Для сокращения количества проводников будет использоваться динамическая индикация. В качестве датчиков температуры использованы Dallas DS1820 (забегая вперед скажу, что 18B20 тоже годятся).

Техзадание готово, рисуем схему. AVR можно взять любой, необходимо лишь наличие достаточного количества портов. Поскольку у меня в ящике валялась ATMEGA164, я использовал ее. Кварц опять же любой 4-10MHz, я взял первый попавшийся.

Вот, что у меня получилось:

схема принципиальная

Слева - разъем для ISP программирования. Аноды светодиодного индикатора подключены на PORTA микроконтроллера через токоограничительные резисторы R3-R9, 220 Ом, катоды непосредственно в PORTC. Спрашивали почему не годятся индикаторы с общими катодами, отвечаю, что годятся в принципе, но поскольку вытекающий ток портов (при лог. 1) меньше втекающего (лог. 0), то подключение раздельных анодов на независимые линии порта дает больший ток и соответственно большую яркость. Это особенно важно при динамической индикации.

Температурные датчики включены обычным образом. Понятно, что не помешает поставить блокировочный конденсатор непосредственно около датчика если длина соединительного провода превышает 3-5м.

Я предусмотрел еще Jumper S1.
Дело в том, что нет никакого способа заранее узнать, какой температурный датчик будет первым, в какой вторым. При сканировании очередность зависит от серийного номера. При использовании двух датчиков удобно, чтобы, например, уличная температура всегда была первой вне зависимости от конкретных экземпляров DS1820.
Вот этим S1 и можно программно поменять местами два датчика.

Программная часть

Запускаем Code Vision AVR (далее cvavr). Выбираем 'Create New...'

и 'Project'.

Далее соглашаемся использовать Wizard, потому как штука очень удобная:

Выбираем тип микроконтроллера (у меня, как я уже говорил ATMEGA164PV) и частоту используемого кварца:

Для динамической индикации удобно (и правильно) использовать встроенный таймер. Используем прерывание по переполнению таймера, устанавливаем источник импульсов для счета 'System Clock' и предделитель. В 'Timer Value' я поместил 0xEC. В совокупности с 'Clock Value' это даст 360 прерываний в секунду (7200 / (0x100-0xEC)). При восьми разрядах индикатора частота индикации будет 360/8 = 45Гц, то есть вполне незаметно для глаза. Для других значений кварца потребуется пересчитать параметры для того, чтобы частота индикации была 40-70Гц:

Далее программируем PORTA на вывод с начальными значениями 0:

PORTC тоже программируем на вывод с начальными значениями 1. Это гарантирует, что после сброса индикатор будет потушен.

Определяем куда подключен температурный датчик DS1820 и не забываем указать 'Multiple Devices':

Вот теперь можно 'Generate, Save & Exit':

Сохраняем С файл с именем main.c

Для двух последующих используем имя 'avrtemp':

После того как все сохранено, cvavr будет выглядеть вот так:

Теперь можно колдовать над программой.

Весь код инициализации микроконтроллера удобнее вынести в отдельный файл main_initialization.inc, который включается через #include в main.c:


main_initialization.inc:

#ifndef __MAIN_INITIALIZATION_INC__
#define __MAIN_INITIALIZATION_INC__
// Crystal Oscillator division factor: 1
#pragma optsize-
CLKPR=0x80;
CLKPR=0x00;
...
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif
#endif

Я обещал поддержку DS1820 и DS18B20, хорошо, для этого в config.h выносим определение какой датчик использовать:


config.h:

#ifndef __CONFIG_H_
#define __CONFIG_H_
//#define USE_DS18S20
#define USE_DS18B20
#endif

В avr_types.h я определил типы в соответствии с POSIX. В таком виде все гораздо лучше читается.

Теперь файл main.c, здесь собственно вся логика работы, я прокоментирую лишь некоторые места. У нас два DS1820:


#define MAX_DS1820 2

То, что выводится на индикатор находится в массиве disp_buffer. По прерываниям таймера переключаем индикатор на один сегмент и выводим значение буфера. Таким образом через восемь прерываний весь буфер будет выведен на индикатор. Поскольку все происходит довольно быстро, создается впечатление, что все разряды индикатора горят одновременно (если быстро помахать индикатором, зажатым в руке, то можно увидеть мерцание отдельных разрядов):


// Declare your global variables here
volatile uint8_t pos_buf;
volatile uint8_t disp_buffer[8];
volatile uint8_t ticks;

// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
        // Reinitialize Timer 0 value

        TCNT0=0xec;

        PORTC = ( ~ (0b00000001 << pos_buf) );
        PORTA= disp_buffer[pos_buf];

        pos_buf++;
        pos_buf &= 0x7;
        ticks++;
}


Для печати (последующего вывода на индикатор) используем вот такой код. Он может выводить значение температуры в левом или правом месте. Для удобства работы с семисегментным индикатором сделаны некоторые #define:


#define ALS_MINUS 0b01000000
#define ALS_GRAD  0b01100011
#define ALS_GRAD_LO  0b01011100
#define ALS_GRAD_INC 0b00100011
#define ALS_DOT   0b10000000
#define ALS_NONE  0b00000000
#define ALS_0     0b00111111
#define ALS_1     0b00000110
#define ALS_2     0b01011011
#define ALS_3     0b01001111
#define ALS_4     0b01100110
#define ALS_5     0b01101101
#define ALS_6     0b01111101
#define ALS_7     0b00000111
#define ALS_8     0b01111111
#define ALS_9     0b01101111
#define ALS_E     0b01111001
#define ALS_R     0b01010000

const uint8_t als_numbers[10] = { ALS_0, ALS_1, ALS_2, ALS_3, ALS_4,
                              ALS_5, ALS_6, ALS_7, ALS_8, ALS_9 };

void print_number (uint8_t pos, int16_t number)
{
        uint8_t num_des;
        uint8_t dpos = (pos) ? 3 : 7;

        if (number < 0)
        {
                disp_buffer[dpos--] = ALS_MINUS;
                number = -number;
        }
        else 
        {
                disp_buffer[dpos--] = ALS_NONE;
        }

        num_des = number / 10;
        if (num_des > 9)
                num_des = 9;

        disp_buffer[dpos--] = (num_des) ? als_numbers[num_des] : 0;
        disp_buffer[dpos] = als_numbers[(number % 10)];
}

Собственно подпрограмма измерения температуры. Одна хитрость заключается вызове ds18xx_temperature() сразу после прерывания, при использовании DS18B20 снижается вероятность ошибок (запретить прерывания нельзя по причине динамической индикации). Дополнительно ошибки подавляются путем игнорирования одиночных ошибочных показаний.

DS1820 и DS18B20 опрашиваются различными функциями, что сделано через config.h.
Я очень долго смеялся как знатоки cvavr копировали на ряде форумов страшный код, который что-то отнимал и прибавлял от 4096... Ужас... Думайте всегда своей головой, ну и читайте доки, в них все расписано!


void measure_temp(void)
{
...
#ifdef USE_DS18S20
                my_temps[i] = ds1820_temperature_10(&ds1820_rom_codes[i][0]);
#endif

#ifdef USE_DS18B20
                my_temps[i] = ds18b20_temperature (&ds1820_rom_codes[i][0]);
#endif
...
}

Ну и собственно сама функция main(). Поиск датчиков после сброса с помощью w1_search(). В бесконечном цикле замер температуры и вывод значения на индикатор. В момент измерений символ градуса справа меняем на подковку:


void main(void)
{
...
ds1820_devices=w1_search(0xf0,ds1820_rom_codes);
...
while (1)
        {
                disp_buffer[0] = ALS_GRAD_INC;
                measure_temp();
                disp_buffer[0] = ALS_GRAD;

                ind1 = (PINB & 0x01) ? 0 : 1;

                print_number(ind1, temps[0]);
                print_number((!ind1), temps[1]);

                delay_ms(5000);
        }



Я опустил описание WD. В принципе для такого простого устройства использование сторожевой собаки неоправданно, но пусть будет. В любом случае всегда можно закомментировать настройку WD в main_initialization.inc и пересобрать проект.

Для получения файла "паршивки" нужно сказать 'Build All'.

Любопытные могут заглянуть в файл распределения памяти:


avrtemp.map:


RAM Allocation
Variable                                                          Address   Size
--------------------------------------------------------------------------------
__ds1820_scratch_pad                                              0200h        9
__ds18b20_scratch_pad                                             0209h        9
ds1820_rom_codes                                                  0212h       18
pos_buf                                                           0224h        1
disp_buffer                                                       0225h        8
ticks                                                             022Dh        1
als_numbers                                                       022Eh       10
temps                                                             0238h        4
temp_failures                                                     023Ch        1
flg_ds1820_enabled                                                023Dh        1

EEPROM Allocation
Variable                                                          Address   Size
--------------------------------------------------------------------------------

Register Allocation
Variable                                                          Register  Size
--------------------------------------------------------------------------------
ds1820_devices                                                    R4           1

Ресурсов использовано всего ничего! Попутно отмечу, что все 'volatile' переменные попали в RAM.

Программирование AVR

Понятно, что для записи программы в микроконтроллер нужно использовать программатор. На мой взгляд дотаточно удобно использовать программатор, который подключается через параллельный порт LPT. Я пользуюсь программатором, собранным вот по такой схеме:

Используется всего одна микросхема 555АП5.

Важным моментом является то, что перед началом работы нужно сначала подключить программатор к контроллеру и только потом подавать питание на AVR!
Отключать программатор можно после полной отладки устройства, т.к. он никак не влияет на работу микроконтроллера после программирования.

Конфигурим тип программатора в cvavr. Естественно 'Printer Port' должен быть именно тем, к которому подключен программатор. 'Delay Multiplier' зависит от скорости компьютера и на практике бывает 3-10:

Правильный тип программатора - STK200+/300, который записывается в заголовок окна:

Для проверки работы читаем тип микроконтроллера:

Все Ok, у нас именно ATMEGA164:

Дальше можно зачитать значения fuse битов. Замечу что заводские установки не годятся для работы с кварцем и fuse'ы нужно перепрошить заново. Для этого после копирования значения fuse'ов нужно снять все галочки (см. картинку выше).

Теперь можно прошить AVR через соответствующее меню (не забыть собрать проект 'Build All'):

Фотографии

Один из возможных вариантов, собранный на макетке

Индикаторы - АЛС314, распаянные общие аноды - раздельные катоды

Еще один вариант на более крупных индикаторах, за окном уже зима

Так выглядит индикатор в работе, когда горит подковка вместо символа градуса, производится замер температуры



Вот вкратце и вся рассказка.

Приложения


Tags: avr microcontrollers electronic


Назад



[Home] [TTL] [Unix] [Sdictionary] [ROW Programmer] [Symbian] [Misc] [News] [Search] [Contacts] [Guestbook]


Copyright (c) 1999-2024 Alexey Semenoff