Но ведь нельзя останавливаться на достигнутом! Пора сделать что-то посложнее!
Хотя, на самом деле, это только кажется сложным. И к концу статьи Вы скажете, что измерить температуру или напряжение при помощи AVR – сущий пустяк.
Список всех частей:
Грызём микроконтроллеры. Урок 1. Моргаем 8-ю светодиодами. CodeVision, Proteus, ISIS
Грызём микроконтроллеры. Урок 2. CodeVision и С
Грызём микроконтроллеры. Урок 3. Циклы, прерывания и массивы
Грызём микроконтроллеры. Урок 4. Мерим температуру или напряжение
Грызём микроконтроллеры. Урок 5. Кодовый замок
Грызём микроконтроллеры. Урок 6. Прошиваем МК
Грызём микроконтроллеры. Урок 7. Подключение к МК кнопок, клавиатуры, энкодера
Грызём микроконтроллеры. Урок 8. Программирование кнопок, клавиатуры, энкодера
Грызём микроконтроллеры. Урок 9. Клавиатура вглубину
В связи с усложнением задачи, будет неплохо иметь под рукой более “навороченный” микроконтроллер. Можно конечно этого не делать, но у tiny2313 маловато памяти и нет Аналого-Цифрового преобразователя. Поэтому мы переезжаем на МК серии Mega!
В виду того, что конструкции дальше будут только усложняться, выбор пал на ATmega16.
А теперь задачи: нам нужно измерить напряжение, подаваемое с движка переменного резистора, измерить температуру в 4 точках и как-нибудь это показать.
Измерить напряжение довольно просто. Наш микроконтроллер имеет 8 входов аналого-цифрового преобразователя (АЦП). Сам АЦП имеет разрядность 10 бит, что дает 1024 ступени измерения.
Например, если измерения проводить в интервале 0-10 Вольт, то результат можно получить с точностью примерно до 0,01 вольта (а если быть абсолютно точным, то до 0,009765625)!
Температуру измерить можно подключив к одному из входов АЦП терморезистор или термопару. Но тут могут возникнуть сложности с программной реализацией, ведь нужно будет по каким-либо формулам пересчитывать полученное значение напряжения в температуру. Поверьте, это не так уж и просто!
К великому счастью, мы живем в мире развитом и нам не нужно изобретать велосипед
Есть чудесная мелкосхемка под названием DS18S20. Заключена она в корпус TO-92 (как маломощный транзистор) и имеет всего три вывода.
Зато внутри этого скромного корпуса содержится цифровой датчик температуры с точностью до 0,1С и диапазоном от -55 до +125 С.
С микроконтроллером всё это сопрягается по протоколу 1wire. О его принципе рассказывать не буду. Кому интересно – может поискать в интернете.
А вот о его прелестях не сказать не смогу.
1wire – однопроводной интерфейс, позволяющий подключить к одной линии данных практически неограниченное количество периферии. Каждое устройство имеет уникальный 64-х битный идентификационный номер. Т.е., максимальное число устройств на одной шине – 2 в степени 64. Единственный недостаток – если использовать максимальное число датчиков, опрос всех может занять лет 10
В общем, берем 4 таких датчика и не задумываясь соединяем их все параллельно и шину DQ цепляем к микроконтроллеру.
Таким образом, для всех датчиков будем использовать только 1 вывод МК. По-моему, очень даже красивое схемное решение
А как же нам посмотреть на результаты всех этих измерений?
Можно выводить это всё на наши любимые светодиоды, а потом сидеть с блокнотиком или калькулятором и всё это расшифровывать
А можно взять LCD дисплей и поднять нашу конструкцию на недосягаемую высоту. Предлагаю дисплей на основе контроллера HD44780.
И, в нагрузку, научимся использовать Watchdog (дословно - сторожевой пес), или, так называемый, сторожевой таймер. Предназначен он для того, чтобы сбрасывать МК при зависании программы.
Он имеет свой собственный тактовый генератор на 1 МГц и предделитель, который позволяет выставить интервал времени от 0,016 до 2,048 секунды. При переполнении этого таймера происходит принудительный сброс МК. Этот таймер нужно периодически сбрасывать в каком-либо месте программы, не допуская его переполнения при нормальном исполнении программы.
В протоколы, команды и прочую ерунду погружаться пока не будем. С компилятором CVAvr этого можно и не знать, так что, создаем новый проект!
Указываем тип и частоту МК.
Настраиваем порты.
Теперь на вкладке LCD выбираем порт для подключения дисплея.
А на вкладке 1 Wire указываем вывод для подключения датчиков и ставим галочки Enabled (включено), и Multiple Devices (множество устройств), чтобы включить использование датчиков DS18S20.
Переходим на вкладку ADC и включаем аналого-цифровой преобразователь.
И еще заходим на вкладку Timers -> Watchdog и выставляем следующее:
Генерируем, сохраняем и приступаем к разбору.
// 1 Wire Bus functions
#asm
.equ __w1_port=0x12;PORTD
.equ __w1_bit=0
#endasm
Эта часть кода указывает компилятору, какой вывод МК должен использоваться для интерфейса 1wire.
Код этот записан на ассемблере.
Ассемблерные вставки можно делать так:
#asm(“sei”)
Команда ассемблера пишется в ковычкай. Удобно, если нужно выполнить только одну команду.
“sei” – команда разрешения всех прерываний
А если необходимо выполнить несколько команд, написанных на ассемблере, их удобнее будет вставить между директивами #asm (начало кода) и #endasm (конец кода).
#include <1wire.h>
// DS1820 Temperature Sensor functions
#include <ds1820.h>
Подключение файлов с функциями, необходимыми для работы с интерфейсом 1wire и датчиками DS18S20.
// maximum number of DS1820 devices
// connected to the 1 Wire bus
#define MAX_DS1820 8
Константа, определяющая максимальное число датчиков, подключенных к МК.
// number of DS1820 devices
// connected to the 1 Wire bus
unsigned char ds1820_devices;
Переменная, в которую записывается число найденных датчиков.
// DS1820 devices ROM code storage area,
// 9 bytes are used for each device
// (see the w1_search function description in the help)
unsigned char ds1820_rom_codes[MAX_DS1820][9];
Переменная, в которой хранятся идентификационные коды найденных датчиков.
// Alphanumeric LCD Module functions
#asm
.equ __lcd_port=0x15;PORTC
#endasm
#include <lcd.h>
Тут определяется порт, используемый для дисплея, и подключается библиотека для работы с ним.
#define ADC_VREF_TYPE 0xC0
Эта переменная указывает на источник опорного напряжения для АЦП. У нас это внутренний стабилизатор 2,56В. Следовательно, одно деление АЦП будет равно 0,0025В
// Read the AD conversion result
unsigned int read_adc(unsigned char adc_input)
{
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
// Delay needed for the stabilization of the ADC input voltage
delay_us(10);
// Start the AD conversion
ADCSRA|=0x40;
// Wait for the AD conversion to complete
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10;
return ADCW;
}
Функция, возвращающая результат аналого-цифрового преобразования для указанного входа.
// Determine the number of DS1820 devices
// connected to the 1 Wire bus
ds1820_devices=w1_search(0xf0,ds1820_rom_codes);
Запуск функции поиска 1wire устройств и запись их количества в переменную ds1820_devices.
// LCD module initialization
lcd_init(16);
Инициализация дисплея
// Global enable interrupts
#asm("sei")
Разрешение прерываний.
Теперь будем добавлять свой код.
Во-первых, нам надо сбрасывать сторожевой таймер.
Это выполняется командой #asm(“wdr”)
Поместим ее в начало основного цикла.
Теперь разберем функции, необходимые для работы с нашими периферийными устройствами.
void lcd_clear(void);
Очистка дисплея
void lcd_gotoxy(unsigned char x, unsigned char y);
Установка курсора на символ с номером x в строке y
void lcd_putchar(char c);
Записать символ c туда, где находится курсор. После записи курсор смещается на следующую ячейку.
void lcd_puts(char *str);
Записать в дисплей строку str, начиная с того места, где находится курсор. После записи курсор смещается на следующую ячейку после конца строки.
void lcd_putsf(char flash *str);
То же самое, что и lcd_puts(), только используется для вывода строк, хранящихся в памяти программ (флеш-памяти).
Например
flash char text[]=”Hi Datagor.ru!”;
Переменные, хранящиеся во флеш-памяти не могут быть изменены, т.е., являются константами. Их удобно использовать для хранения больших строк, чтобы не забивать ими оперативную память.
int ds1820_temperature_10(unsigned char *addr);
Эта функция возвращает значение температуры, считанное из датчика с идентификационным кодом addr, и умноженное на 10. Т.е., если температура 13,5 градусов, то функция вернет значение 135.
unsigned int read_adc(unsigned char adc_input)
Возвращает результат АЦ преобразования для выбранного входа ADC 0-7.
Но вот выводить числа сразу на дисплей не получится. Они будут восприниматься его контроллером как коды символов и ничего хорошего мы не увидим.
В этой ситуации можно использовать библиотеку stdio.h, которая содержит функцию sprint()
void sprintf(char *str, char flash *fmtstr,...);
Первым параметром мы передаем переменную, в которую будет записан результат выполнения функции.
Второй параметр – форматированная строка.
Остальные – переменные.
Вот пример использования:
char text[17];
char day=22, month=12, year=2008;
sprint(text,“Data: %d.%d.%d”,day,month,year);
В результате выполнения этой команды, в переменную text будет помещена строка “dаta: 10.12.2008”.
%d – в этом примере является маской, означающей вывод целого числа. Эти числа берутся по порядку из переменных, которые мы передаем после строки.
Маски:
'%d' – целое десятичное число
'%u' – целое бесзнаковое десятичное число (unsigned)
'%f' – число с плавающей точкой, выводимое в формате [-]ddd.dddddd
'%X' – вывод в виде шестнадцатичного числа
чтобы вывести знак процента (%) его нужно продублировать, т.е., написать”%%”.
Так же, маску можно расширить, указав формат отображения числа
%[указатель_знака][кол-во_разрядов_до_точки][. кол-во_разрядов_после_точки]маска
Квадратные скобки указывают, что эти параметры не обязательны.
Указатель_знака:
“+” – выводить знак всегда (плюс для положительных, минус для отрицательных);
“ “ (пробел) – выводить минус для отрицательных и пробел для положительных.
Например, если мы хотим вывести дробное число с тремя знаками после запятой, то нужно написать
%.3f
или, если ширина числа должна быть постоянной (чтобы цифры не сползали влево-вправо при разном количестве разрядов в них), то пишем:
%3d
И последнее. Чтобы использовать маски в CVAvr необходимо указать разрешенные типы в окне Project -> Configure на вкладке C Compiler
int – вывод только целых (char, int) чисел в простой форме или с указанием знака
int, width – вывод целых чисел (char, int) с возможностью указать количество символов и знак
float, width, precision – вывод целых и дробных чисел с использованием всех возможностей
Чем больше возможностей используете, тем больше получается размер программы, поэтому не стоит включать режим чисел с плавающей точкой, если выводить собираетесь только целые числа.
С функциями вроде разобрались. Осталось лишь написать саму программу.
Вот, что получилось у меня:
/*****************************************************
This program was produced by the
CodeWizardAVR V1.25.9 Standard
Automatic Program Generator
© Copyright 1998-2008 Pavel Haiduc, HP InfoTech s.r.l.
http://www.hpinfotech.com
Project :
Version :
Date : 22.12.2008
Author : Spirit
Company : Spirit
Comments:
Chip type : ATmega16
Program type : Application
Clock frequency : 8,000000 MHz
Memory model : Small
External SRAM size : 0
Data Stack size : 256
*****************************************************/
#include <mega16.h>
#include <stdio.h>
// 1 Wire Bus functions
#asm
.equ __w1_port=0x12;PORTD
.equ __w1_bit=0
#endasm
#include <1wire.h>
// DS1820 Temperature Sensor functions
#include <ds1820.h>
// maximum number of DS1820 devices
// connected to the 1 Wire bus
#define MAX_DS1820 8
// number of DS1820 devices
// connected to the 1 Wire bus
unsigned char ds1820_devices;
// DS1820 devices ROM code storage area,
// 9 bytes are used for each device
// (see the w1_search function description in the help)
unsigned char ds1820_rom_codes[MAX_DS1820][9];
// Alphanumeric LCD Module functions
#asm
.equ __lcd_port=0x15;PORTC
#endasm
#include <lcd.h>
// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
// Place your code here
}
// External Interrupt 1 service routine
interrupt [EXT_INT1] void ext_int1_isr(void)
{
// Place your code here
}
#include <delay.h>
#define ADC_VREF_TYPE 0xC0
// Read the AD conversion result
unsigned int read_adc(unsigned char adc_input)
{
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
// Delay needed for the stabilization of the ADC input voltage
delay_us(10);
// Start the AD conversion
ADCSRA|=0x40;
// Wait for the AD conversion to complete
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10;
return ADCW;
}
// Declare your global variables here
void main(void)
{
// Declare your local variables here
// Input/Output Ports initialization
// Port A initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTA=0x00;
DDRA=0x00;
// Port B initialization
// Func7=Out Func6=Out Func5=Out Func4=Out Func3=Out Func2=Out Func1=Out Func0=Out
// State7=0 State6=0 State5=0 State4=0 State3=0 State2=0 State1=0 State0=0
PORTB=0x00;
DDRB=0xFF;
// Port C initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTC=0x00;
DDRC=0x00;
// Port D initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=P State6=P State5=P State4=P State3=P State2=P State1=P State0=P
PORTD=0xFF;
DDRD=0x00;
// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: Timer 0 Stopped
// Mode: Normal top=FFh
// OC0 output: Disconnected
TCCR0=0x00;
TCNT0=0x00;
OCR0=0x00;
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: Timer 1 Stopped
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer 1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: Off
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0x00;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;
// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: Timer 2 Stopped
// Mode: Normal top=FFh
// OC2 output: Disconnected
ASSR=0x00;
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;
// External Interrupt(s) initialization
// INT0: On
// INT0 Mode: Falling Edge
// INT1: On
// INT1 Mode: Falling Edge
// INT2: Off
GICR|=0xC0;
MCUCR=0x0A;
MCUCSR=0x00;
GIFR=0xC0;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x00;
// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;
// ADC initialization
// ADC Clock frequency: 1000,000 kHz
// ADC Voltage Reference: Int., cap. on AREF
// ADC Auto Trigger Source: None
ADMUX=ADC_VREF_TYPE & 0xff;
ADCSRA=0x83;
// Watchdog Timer initialization
// Watchdog Timer Prescaler: OSC/2048k
#pragma optsize-
WDTCR=0x1F;
WDTCR=0x0F;
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif
// Determine the number of DS1820 devices
// connected to the 1 Wire bus
ds1820_devices=w1_search(0xf0,ds1820_rom_codes);
// LCD module initialization
lcd_init(16);
// Global enable interrupts
#asm("sei")
while (1)
{
char text[16];
float volt;
#asm("wdr") // сброс сторожевого таймера
lcd_gotoxy(0,0); // перемещение курсора
volt=read_adc(0); // считывание значения АЦП
volt*=0.0025; // перевод в напряжение
sprintf(text,"Voltage: %.4f",volt); // форматирование строки
lcd_puts(text); // вывод напряжения
lcd_gotoxy(0,1);
sprintf(text,"%+3d ",ds1820_temperature_10(&ds1820_rom_codes[0][0])/10); // считывание датчика и деление на 10
lcd_puts(text); // вывод температуры
sprintf(text,"%+3d ",ds1820_temperature_10(&ds1820_rom_codes[1][0])/10);
lcd_puts(text);
sprintf(text,"%+3d ",ds1820_temperature_10(&ds1820_rom_codes[2][0])/10);
lcd_puts(text);
sprintf(text,"%+3d ",ds1820_temperature_10(&ds1820_rom_codes[3][0])/10);
lcd_puts(text);
};
}
Можно компилировать и проверять.
При моделировании в ISIS собираем такую схему.
Указываем файл прошивки и параметры тактового генератора (!).
Если задать слижком маленькую частоту (можете попробовать 1МГц), может срабатывать сторожевой таймер (из-за редких сбросов) и не будут работать датчики.
Если частота будет слишком высокой – не будут считываться датчики температуры.
А датчикам присваиваем разные ROM Serial Number
Вот так вот всё на самом деле просто! А Вы мне, наверное, не верили…
В следующий раз мы отойдем от программирования и будем учиться прошивать микроконтроллеры.
До скорой встречи!
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.