ADC и PWM в ATTiny45 - вопросы начинающего

Обсуждаем контроллеры компании Atmel.
Ответить
Первый раз сказал Мяу!
Сообщения: 35
Зарегистрирован: Сб июн 23, 2012 12:43:14

Сообщение karambas »

Доброго всем времени суток. Прошу совета.

Я пытаюсь потихоньку самостоятельно научиться работать с микроконтроллерами семейства ATTiny без Arduino, взаимодействуя через AVR-GCC. По пути спотыкаюсь о различные камни, которые в процессе изучения документации удается расколоть и пройти дальше, но вот с ADC зашел в тупик. Почему-то не получается корректно настроить контроллер на работу с сенсорами. Проверьте, пожалуйста, код, укажите на ошибки. Комментарии пытаюсь писать на английском, поэтому не пеняйте за грамматику. Заранее спасибо.

Код: Выделить всё

#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>

#define setHIGH(pin) PORTB |= (1 << pin)
#define setLOW(pin) PORTB &= ~(1 << pin)

#define isHIGH(pin) PINB & (1 << pin)
#define isLOW(pin) !(PINB & (1 << pin))

//aliases for pins
#define ledPin   PB0

void initADC(){
    ADMUX =
        (0 << REFS1) |  //set REFS1 to 0 to use VCC as VREF (attiny24/45/85)
        (0 << REFS0) |  //set REFS0 to 0 to use VCC as VREF (attiny13/24/45/85)
        (1 << ADLAR) |  //shift in a 1 and follow 8bit procedure
        (1 << MUX0);    //set MUX0 to 1 to use PB2 as analog input pin (ADC1)

    ADCSRA =
        (1 << ADEN) |   //enable ADC
        (0 << ADSC);    //set ADSC to 0 to sure no conversion are happening
}

void initIO(){
    DDRB =
        (1 << PB0);    //set OUTPUT
}

unsigned int analogRead(){
    unsigned int result = 0;
    ADCSRA |= (1 << ADSC);          //start ADC conversation
    while(ADCSRA & (1 << ADSC));    //wait for conversation complete
    result = ADCW;                  //get the result of conversation
    return result;                  //return the result
}

int main(void){
    initADC();  //setup ADC    
    initIO();   //setup Input/Output system
    while(1){
        if(analogRead() < 1023){
            setHIGH(ledPin);
        }else{
            setLOW(ledPin);
        }
    }
    return 0;
}
когда все то же самое выполняю на wiring:

Код: Выделить всё

void setup(){
    pinMode(0, OUTPUT);
    pinMode(2, INPUT);
}
void loop(){
    if(analogRead(2) < 1023){
         digitalWrite(0, HIGH);
    }else{
         digitalWrite(0, LOW);
    }
}
то все отрабатывает корректно.
Последний раз редактировалось karambas Ср фев 25, 2015 20:42:57, всего редактировалось 1 раз.
Реклама
Друг Кота
Аватара пользователя
Сообщения: 20094
Зарегистрирован: Чт сен 18, 2008 12:27:21
Откуда: Столица Мира Санкт-Петербург

Сообщение Gudd-Head »

Какими ещё сенсорами? Это же АЦП. Схему подключений нарисуйте, что вы хотите получить и что вы получаете в итоге.
[ Всё дело не столько в вашей глупости, сколько в моей гениальности ] [ Правильно заданный вопрос содержит в себе половину ответа ]
Реклама
Вымогатель припоя
Аватара пользователя
Сообщения: 672
Зарегистрирован: Ср фев 29, 2012 01:58:32
Откуда: Харьков, Украина

Сообщение eess9 »

Сразу бросаются в глаза две вещи.
Первое, вы не задали предделитель. По умолчанию он у вас 2. Это очень быстро, преобразование тупо не успело выполнится. У вас АЦП 15kSPS. Пусть контроллер работает на 1МГц, частота тактирования АЦП 500кГц. Преобразование 25 тактов по даташит, т.е. 20 тыс за секунду. Дохрена. Уменьшайте из рассчета частоты тактирования АЦП 50...200кГц. Что-то вроде этого:

Код: Выделить всё

ADCSRA |= 0x07
Второе:

Код: Выделить всё

while(ADCSRA & (1 << ADSC));    //wait for conversation complete
может надо флаг конца преобразования ждать :)) ?:

Код: Выделить всё

while(!(ADCSRA & (1 << ADIF))) ;
ADCSRA |= (1 << ADIF); 
Первый раз сказал Мяу!
Сообщения: 35
Зарегистрирован: Сб июн 23, 2012 12:43:14

Сообщение karambas »

На PB2 (ADC1) висит выход с делителя напряжения, состоящего из фоторезистора и резистора.
Реклама
Эиком - электронные компоненты и радиодетали
Друг Кота
Аватара пользователя
Сообщения: 3872
Зарегистрирован: Пт янв 29, 2010 10:27:40
Откуда: Москва

Сообщение Engineer_Keen »

karambas писал(а): if(analogRead(2) < 1023)
А это точно работало? У AVR АЦП всего 10 бит, там больше 1023 по определению быть не должно, остается только один отсчет (максимум). Или это так и задумано?
Неправильно собранная из неисправных деталей схема нуждается в отладке и сразу не работает... (С)
Реклама
Друг Кота
Аватара пользователя
Сообщения: 20094
Зарегистрирован: Чт сен 18, 2008 12:27:21
Откуда: Столица Мира Санкт-Петербург

Сообщение Gudd-Head »

(1 << ADLAR)
С этим вроде должно прокатить?
[ Всё дело не столько в вашей глупости, сколько в моей гениальности ] [ Правильно заданный вопрос содержит в себе половину ответа ]
Реклама
Вымогатель припоя
Аватара пользователя
Сообщения: 672
Зарегистрирован: Ср фев 29, 2012 01:58:32
Откуда: Харьков, Украина

Сообщение eess9 »

Gudd-Head писал(а):С этим вроде должно прокатить?
Это же костыль. Может все таки вместо 1023 записать необходимый порог срабатывания фотодатчика?
Друг Кота
Аватара пользователя
Сообщения: 3872
Зарегистрирован: Пт янв 29, 2010 10:27:40
Откуда: Москва

Сообщение Engineer_Keen »

Gudd-Head писал(а):ADLAR
Мда, как-то не подумал о таком варианте, привык что ADLAR используют чтобы обойтись 8-ю битами.
Неправильно собранная из неисправных деталей схема нуждается в отладке и сразу не работает... (С)
Первый раз сказал Мяу!
Сообщения: 35
Зарегистрирован: Сб июн 23, 2012 12:43:14

Сообщение karambas »

Возился, возился, заставил заработать, сбросив бит ADLAR. В связи с этим вопрос: практически заучив даташит, все же не могу понять предназначение бита ADLAR регистра ADMUX в практическом смысле. В даташите говорится, что:
выставляя бит ADLAR мы устанавливаем порядок записи результата преобразования в регистры ADCL и ADCH; когда ADLAR = 1 два младших бита пишутся в ADCL, остальные в ADCH, когда ADLAR = 0, два старших байта в ADCH, остальные в ADCL.
Как это в практическом смысле работает, не могу понять.
Допустим на входе АЦП есть 4В как они будут преобразованы в том и в другом случае?
eess9 писал(а):Сразу бросаются в глаза две вещи.
Первое, вы не задали предделитель. Уменьшайте из рассчета частоты тактирования АЦП 50...200кГц.
Поправил initADC() с учетом Вашего замечания по prescaler:

Код: Выделить всё

void initADC(){
    ADMUX =
        (0 << REFS1) |  //set REFS1 to 0 to use VCC as VREF (attiny24/45/85)
        (0 << REFS0) |  //set REFS0 to 0 to use VCC as VREF (attiny13/24/45/85)
        (0 << ADLAR) |  //bit ADLAR remains clear to follow 10bits procedure
        (1 << MUX0);    //set MUX0 to 1 to use PB2 as analog input pin (ADC1)

    ADCSRA =
        (1 << ADEN) |   //enable ADC
        (0 << ADSC) |   //set ADSC to 0 to sure no conversion are happening
        (1 << ADPS1) |  //set ADPS1 and ADPS0 bits, which set a prescaler to 8  
        (1 << ADPS0);   //for 1MHz system clock to set ADC input clock equal 125KHz
}
eess9 писал(а): Второе:

Код: Выделить всё

while(ADCSRA & (1 << ADSC));    //wait for conversation complete
может надо флаг конца преобразования ждать :)) ?:

Код: Выделить всё

while(!(ADCSRA & (1 << ADIF))) ;
ADCSRA |= (1 << ADIF); 
Попробовал Ваш вариант с ADIF и свой без. Разницы в работе не заметил.
Если смотреть в даташит, то увидим, что:
ADSC will read as one as long as a conversion is in progress. When the conversion is complete, it returns to zero.
Writing zero to this bit has no effect.
Какие приемущества дает использование конструкции с ожиданием флага прерывания АЦП в режиме однократного преобразования?
Первый раз сказал Мяу!
Сообщения: 35
Зарегистрирован: Сб июн 23, 2012 12:43:14

Сообщение karambas »

Следом вопрос про PWM, который с моими настройками тоже отрабатывает со странностями.
Код на wiring:

Код: Выделить всё

void setup() {
  // put your setup code here, to run once:
  pinMode(0, OUTPUT);
  pinMode(4, INPUT);
  digitalWrite(4, HIGH);
}

void loop() {
  // put your main code here, to run repeatedly:
  if(digitalRead(4) == LOW){
    analogWrite(0,50);
  }else{
    analogWrite(0,0);
  }
}
Отрабатывает корректно.
Код на AVR-GCC:

Код: Выделить всё

#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>

#define setHIGH(pin) PORTB |= (1 << pin)
#define setLOW(pin) PORTB &= ~(1 << pin)
#define setPWM(pin, dutycycle) pin = dutycycle

#define isHIGH(pin) PINB & (1 << pin)
#define isLOW(pin) !(PINB & (1 << pin))

//aliases for pins
#define ledPin   OCR0A    //PB0 led is using PWM
#define buttonPin    PB4  //PB4 on the button

void initIO(){
    DDRB =
        (1 << PB0) |   //set OUTPUT
    PORTB =
        (1 << PB4);    //switch ON pull-up resistor on PB4
}

void initPWM(){
    TCCR0A =
        (0 << COM0A0) | //bit COM0A0 remains clear and
        (1 << COM0A1) | //set bit COM0A1, which (in Fast PWM mode) clears OC0A on compare-match, and sets OC0A at BOTTOM
        (1 << WGM00) |  //set bits WGM00 and WGM01, which (when combined with WGM02
        (1 << WGM01);   //from TCCR0B below) enables Fast PWM mode
    TCCR0B =
        (0 << WGM02) |  //bit WGM02 remains clear, which (when combined with WGM00 and WGM01 from TCCR0A above) enables Fast PWM mode
        (1 << CS00);    //set bits CS01 (leaving CS01 and CS02 clear), which tells Timer/Counter-0 to not use a prescalar
}

int main(void){
    initIO();   //setup Input/Output system
    initPWM();  //setup PWM
    while(1){
        if(isLOW(buttonPin)){
            setPWM(ledPin, 50);
        }else{
            setPWM(ledPin, 0);
        }
    }
    return 0;
}
тоже отрабатывает корректно, однако сразу же после включения питания на PB0 появляется паразитное напряжение, которое едва заметно зажигает светодиод. Это гасится включением между PB0 и землей керамического конденсатора на 0.1uF, однако после заливки в МК скетча из Ардуино такого эффекта не возникает.
Друг Кота
Сообщения: 19495
Зарегистрирован: Чт фев 20, 2014 18:57:55

Сообщение Morroc »

karambas писал(а):Возился, возился, заставил заработать, сбросив бит ADLAR. В связи с этим вопрос: практически заучив даташит, все же не могу понять предназначение бита ADLAR регистра ADMUX в практическом смысле. В даташите говорится, что:
выставляя бит ADLAR мы устанавливаем порядок записи результата преобразования в регистры ADCL и ADCH; когда ADLAR = 1 два младших бита пишутся в ADCL, остальные в ADCH, когда ADLAR = 0, два старших байта в ADCH, остальные в ADCL.
вы учите даташит на русском ?

The ADC generates a 10-bit result which is presented in the ADC Data Registers, ADCH and ADCL. By default, the result is presented right adjusted, but can optionally be presented left adjusted by setting the ADLAR bit in ADMUX.
If the result is left adjusted and no more than 8-bit precision is required, it is sufficient to read ADCH. Otherwise, ADCL must be read first, then ADCH, to ensure that the content of the Data Registers belongs to the same conversion.
"Вся военная пропаганда, все крики, ложь и ненависть исходят от людей, которые на эту войну не пойдут !" / Джордж Оруэлл /
"Война - это,когда за интересы других,гибнут совершенно безвинные люди." / Уинстон Черчилль /
Первый раз сказал Мяу!
Сообщения: 35
Зарегистрирован: Сб июн 23, 2012 12:43:14

Сообщение karambas »

Morroc писал(а):вы учите даташит на русском ?
Спецификация на английском. Я своими словами описал процесс. Вот диаграмма регистров:

Изображение
Подскажите, пожалуйста, правильный принцип использования бита ADLAR в ADMUX.

C PWM, к слову, разобрался. Порывшись достаточное время в сети, наткнулся на данный ресурс:
http://letsmakerobots.com/content/robot ... on-journal описывающий подобное поведение МК.

Изменил немного макрос setPWM и функцию initPWM():

Код: Выделить всё


#define setPWM(pin, dutycycle) \
    if (dutycycle > 0){ \
        TCCR0A |= (1 << COM0A1); \
        pin = dutycycle; \
    }else{ \
        TCCR0A &= ~(1 << COM0A1); \
    }

void initPWM(){
    TCCR0A =
        (0 << COM0A0) | //bits COM0A0 remains clear and COM0A1 should be set as 1, which (in Fast PWM mode) clears OC0A 
        (0 << COM0A1) | //on compare-match, and sets OC0A at BOTTOM; bit COM0A1 will be set later by setPWM macro in code
        (1 << WGM00) |
        (1 << WGM01);   //set bits WGM00 and WGM01, which (when combined with WGM02 from TCCR0B below) enables Fast PWM mode
    TCCR0B =
        (0 << WGM02) |  //bit WGM02 remains clear, which (when combined with WGM00 and WGM01 from TCCR0A above) enables Fast PWM mode
        (1 << CS00);    //set bits CS01 (leaving CS01 and CS02 clear), which tells Timer/Counter-0 to not use a prescalar
}
И ШИМ заработал должным образом.
Друг Кота
Аватара пользователя
Сообщения: 3872
Зарегистрирован: Пт янв 29, 2010 10:27:40
Откуда: Москва

Сообщение Engineer_Keen »

karambas писал(а):Подскажите, пожалуйста, правильный принцип использования бита ADLAR в ADMUX.
Если вам нужно все 10 бит, то ADLAR не используете (=0), читаете ADCL и ADCH (или сразу ADCW, если это не ассемблер) в 16-битную переменную. Если вам нужно только 8 бит, ставите ADLAR и читаете только ADCH (или ADCW и сдвигаете вправо на 8 бит) в 8-битную переменную.
Неправильно собранная из неисправных деталей схема нуждается в отладке и сразу не работает... (С)
Первый раз сказал Мяу!
Сообщения: 35
Зарегистрирован: Сб июн 23, 2012 12:43:14

Сообщение karambas »

Еще такой вопрос. При написании кода на С и компиляции его с помощью AVR-GCC посредством CodeBlocks не верно задается частота тактирования МК.

Например я выставляю фьюзы на 1MHz для ATTiny45: extended fuse: 0xFF high fuse:0xDF low fuse:0x62 ? что устанавливает делитель 8 для частоты в 8МHz, на выходе должен получиться 1MHz. В коде пишем #define F_CPU 1000000UL для _delay_ms(), однако при создании мигалки на светодиоде необходимая задержка не выполняется.

Если исходник скопировать в среду Arduino IDE, то все билдится должным образом и светодиод мигает с заданным интервалом.
Какие я не учитываю настройки в проекте? Заранее всем спасибо.
Вымогатель припоя
Аватара пользователя
Сообщения: 672
Зарегистрирован: Ср фев 29, 2012 01:58:32
Откуда: Харьков, Украина

Сообщение eess9 »

По умолчанию в delay.h стоит 10МГц. Убедитесь что дефайн у вас раньше библиотеки:

Код: Выделить всё

#define F_CPU 1000000UL
 
#include <util/delay.h>
Это гарантирует, что библиотека вашу частоту "подхватит" правильно. В противном случае будет warning, который должен быть у вас в окне Error List:
"F_CPU not defined for <util/delay.h>"
Посмотрите внимательно, скорей всего, он у вас есть.
Первый раз сказал Мяу!
Сообщения: 35
Зарегистрирован: Сб июн 23, 2012 12:43:14

Сообщение karambas »

Нет, все корректно.

Код: Выделить всё

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
Однако тактируется не корректно. Я все же думаю, что в параметрах сборки что-нибудь не учитываю, но отсутствие опыта не дает понять, что именно.
Первый раз сказал Мяу!
Сообщения: 35
Зарегистрирован: Сб июн 23, 2012 12:43:14

Сообщение karambas »

Проверил работу avrdude с разными файлами конфига. HEX файл, собранный в Arduino IDE корректно работает с тактированием, файл, собранный в C::B некорректно. Устал гуглить на эту тему, не могу сообразить, что в опциях сборщика указано не корректно или не указано. Вот логи сборщиков.

Arduino IDE:

Код: Выделить всё

/opt/arduino-1.0.6/hardware/tools/avr/bin/avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=attiny45 -DF_CPU=1000000L -MMD -DUSB_VID=null -DUSB_PID=null -DARDUINO=106 -I/opt/arduino-1.0.6/hardware/arduino/cores/arduino -I/home/me/sketchbook/hardware/attiny/variants/tiny8 /tmp/build6896905435354427440.tmp/TestFCPU.cpp -o /tmp/build6896905435354427440.tmp/TestFCPU.cpp.o
...
opt/arduino-1.0.6/hardware/tools/avr/bin/avr-gcc -Os -Wl,--gc-sections -mmcu=attiny45 -o /tmp/build6896905435354427440.tmp/TestFCPU.cpp.elf /tmp/build6896905435354427440.tmp/TestFCPU.cpp.o /tmp/build6896905435354427440.tmp/core.a -L/tmp/build6896905435354427440.tmp -lm 
/opt/arduino-1.0.6/hardware/tools/avr/bin/avr-objcopy -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 /tmp/build6896905435354427440.tmp/TestFCPU.cpp.elf /tmp/build6896905435354427440.tmp/TestFCPU.cpp.eep 
/opt/arduino-1.0.6/hardware/tools/avr/bin/avr-objcopy -O ihex -R .eeprom /tmp/build6896905435354427440.tmp/TestFCPU.cpp.elf /tmp/build6896905435354427440.tmp/TestFCPU.cpp.hex 
Binary sketch size: 132 bytes (of a 4,096 byte maximum)
Code::Blocks:

Код: Выделить всё

avr-gcc -mmcu=attiny45 -Os -Wall -DF_CPU=1000000L -Os -I/usr/include -c main.c -o obj/Release/main.o
avr-g++ -L/usr/lib -o bin/Release/TestFCPU.elf obj/Release/main.o  -mmcu=attiny45  
Output file is bin/Release/TestFCPU.elf with size 3,66 KB
Running project post-build steps
avr-size bin/Release/TestFCPU.elf
   text       data        bss        dec        hex    filename
     82          0          0         82         52    bin/Release/TestFCPU.elf
avr-objcopy -R .eeprom -R .fuse -R .lock -R .signature -O ihex bin/Release/TestFCPU.elf bin/Release/TestFCPU.hex
avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex bin/Release/TestFCPU.elf bin/Release/TestFCPU.eep
Первый раз сказал Мяу!
Сообщения: 35
Зарегистрирован: Сб июн 23, 2012 12:43:14

Сообщение karambas »

Решил проблему. Заключалась она в дефолтных настройках компилятора Code::Blocks.
В Settings => Compiler... изменил:
  1. Linker for dynamic libs в Toolchain executables с изначального "avr-g++" на "avr-gcc":
    Изображение
  2. Search directories для Linker и Compiler с "/usr/include" на "/usr/lib/avr/include" и с "/usr/lib" на "/usr/lib/avr/lib":
    Изображение
Всем спасибо за помощь. Пойду дальше постигать многогранные коротконожки.
Ответить

Вернуться в «AVR»