Не обновляются данные на вольтметре из семисегментника и atmega328
- Войдите на сайт для отправки комментариев
Приветсвую, товарищи. Озадачился проникновением в самую глубь авр микроконтроллеров путем изучения азов и колупания даташита. Инфы в сети в виде видеоуроков или текстовых уроков достаточно, хотя они в основном по atmega8.
Так вот, попытка сообразить вольтметр на 328 меге пока не удается в полной мере.
Проблема заключается в том, что показация ацп, выводимые на трехразрядный(другого не было под рукой)семисегментник, обновляются только в случае, если перезагрузить микроконтроллер.
Хотелось бы получить совета в решении проблемы. Код привожу ниже.
Не знаю, важно ли это, но укажу, что использую ардуино нано, которую шью с помощью уно в атмелстудио(ардуиновский загрузчик не давал работать с PD0 и PD1, а мне нужен был полный порт для подключения семисегментника на моей колхозной платке-обучалке)
#define F_CPU 16000000UL #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> unsigned int z=0;//переменая для икpементирования unsigned char x=1;//переменная для функции ISR unsigned int R1=0,R2=0, R3=0; unsigned int y=0;//переменая для икpементирования unsigned int zzz; float Uvh;//переменная для записи напряжения const int Koef=1;//коэффициент int K1; unsigned int chisla[10]= { ~0b00111111,//0 ~0b00000110,//1 ~0b01011011,//2 ~0b01001111,//3 ~0b01100110,//4 ~0b01101101,//5 ~0b01111101,//6 ~0b00000111,//7 ~0b01111111,//8 ~0b01101111,};//9 /*настраиваем таймер-счетчик 0*/ void init_dynamic(void){ TCCR0A|=(0<<WGM00)|(0<<WGM01);//режим normal(сброс по переполнению) TCCR0B |=(1<<CS02)|(0<<CS01)|(0<<CS00);//делитель 256 TIMSK0|=(1<<TOIE0);//сброс по переполнению TIFR0|=(1<<TOV0); TCNT0=0; } /*настраиваем порты*/ void init_ports(void){ DDRC|=(1<<PC0)|(1<<PC1)|(1<<PC2);//аноды семисегментника DDRC&=~(1<<PC4);//adc4 //DDRB|=(1<<PB0)|(1<<PB2)|(1<<PB4);//leds //DDRB&=~(1<<PB5);//button DDRD=0xFF;//весь порт Д на выход(катоды семисегментника) //PORTB|=(0<<PB0)|(0<<PB2)|(0<<PB4)|(1<<PB5);//leds&&button PORTC|=(1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC4); PORTD=0xFF;//логическая единица на порту Д,чтобы семисегментник не светился } /*настраиваем ацп*/ void init_adc(void){ ADCSRA|=(1<<ADEN);//разрешаем работу ацп(7ой бит) ADCSRB|=(0<<ADTS2)|(0<<ADTS1)|(0<<ADTS0);//включаем режим постоянного измерения(5 бит) ADCSRA|=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);//делитель 128(частота дискретизации 16000000/128=125кГц) ADMUX|=(0<<REFS1)|(1<<REFS0);//источник опорного напряжения 5 вольт ADMUX|=(0<<MUX3)|(1<<MUX2)|(0<<MUX1)|(0<<MUX0);//включаем ADC4 0100 ADMUX|=(0<<ADLAR);//выравнивание ADCSRA|=(1<<ADSC);//запускаем работу ацп } /*обрабатываем прерывания при переполнении счетчика*/ ISR(TIMER0_OVF_vect){ if(x==1){PORTC=0b00000100;PORTD=chisla[R1];} //при первом прерывании на порту С включаем третий бит, в порт д выводим число, высчитанное в функции vse_chislo if(x==2){PORTC=0b00000010;PORTD=chisla[R2];}/*if(Uvh>=10){PORTD=(1<<PD7);} else {PORTD|=(0<<PD7);}};*/ if(x==3){PORTC=0b00000001;PORTD=chisla[R3];}/*if(Uvh<10){PORTD=(1<<PD7);} else{PORTD|=(0<<PD7);}};*/ x++; if(x>3){x=1;}; } void vse_chislo(unsigned int razbivka_chisla){ R1=razbivka_chisla%10; //единицы R2=razbivka_chisla%100/10;//десятки R3=razbivka_chisla/100;}//сотни int main(void){ init_ports(); init_adc(); init_dynamic(); sei(); while(1){ zzz=ADC; /*Uvh=(zzz*5.00)/1024; if(Uvh<10) { K1=100; } else {K1=10; }*/ vse_chislo(zzz); //вывод в попугаях //vse_chislo(Uvh*K1); //вывод в вольтах ADCSRA|=(1<<ADIF); } }
не знаю, почему так происходит всегда, но после заданного вопроса еще разок залез в даташит и понял, что нужно установить бит ADATE в регистре ADCSRA( ADCSRA|=(1<<ADATE);) и все заработало))
Ну, для начала, Вы неправильно инициализируете АЦП. Вы хотите, чтобы он у Вас запускался от внешнего события, но при этом не установили бит "ADC Auto Trigger Enable" в регистре ADCSRA.
Кроме того, вызывает сомнения сама идея такого измерения. По мне так лучше сделать измерения равномерными (не по взведению флага, а по таймеру или free-running) - главное, чтобы измерения происходили строго через равные интервалы с прерыванием. Лучше по таймеру, чтобы можно было отправлять МК в сон на время измерения, как советует даташит в разделе "подавление шума". В обработчике прерывания расположить разумный фильтр с крутым срезом и нормальной фазовой характеристикой (чтобы не сильно запаздывали показания). Собственно равномерность измерений как раз и нужна для построения нормального фильтра. Результат фильтра нужно пихать в переменную. А основная программа просто тупо показывает эту переменную и ни о чём не думает. Получается. что процесс измерения и процесс показа идут параллельно и никак друг на друга не влияют.
Опа, пока я писал, Вы и сами разобрались! Отлично!
Но вторую часть моего поста почитайте всё равно.
Не хочу показаться умным, но мой вам совет. Выучите 10-к слов по английски - не пишите, типа, razbivka_chisla - это просто колхоз(. В чём плюс высокоуровневого языка от низкоуровневого - вы не зависите от железа! А у вас почти в каждом операторе эта зависимость. А у вас ведь только инициализация и чтение АЦП! Ну, ещё таймер для индикации. Оформите их отдельными макро или inline функциями в самом начале. С тем что бы любой другой мог изменив ТОЛЬКО их, использовать код для своего (ЛЮБОГО) контроллера.
Не хочу показаться умным, но мой вам совет. Выучите 10-к слов по английски - не пишите, типа, razbivka_chisla - это просто колхоз
а как это будет по английски? :)
Вторую часть спича безусловно поддерживаю. В первую очередь надо учиться грамотно программировать на Си - а уже потом, даже не во вторую - а в пятую очередь - лезть в регистры конкретного МК. Грамотный программист, пользуясь только методами Ардуино - скорее всего напишет код лучше, чем человек, закопавшийся в регистры. но не умеющий писать хороший код на си
Спасибо за советы, но я пока еще в самом начале пути и многое из того, что вы мне посоветовали, пока еще темный лес густой) Подавление шума и фильтры - дело будущего, думается. Пока же я просто пытаюсь понять, как работать с богатым внутренним миром микроконтроллера и что он, этот мир, вообще из себя представляет.
А вот сообразить измерение по таймеру уже ближе к моему уровню, который ушел очень недалеко от нулевого.
Спасибо за советы, но я пока еще в самом начале пути и многое из того, что вы мне посоветовали, пока еще темный лес густой)
мой вам совет - отложите регистры на полгодика, для начала напишите это же самое методами ардуино. будет раза в 3 короче и раз в 10 понятнее
Колхоз как колхоз) Для новичка в СИ и в в микроконтроллерах вполне себе приемлемые наименования, даже просто для того, чтобы не особо путаться на первом этапе.
<<Оформите их отдельными макро или inline функциями в самом начале.>>
Научите, как это сделать)
Научите, как это сделать)
Гугль научит!)
Я предпочитаю так.
Дело в том, что в ардуино, что в микроконтроллерах, в любом случае я буду писать на чистом листе своих мозгов. Две недели назад я истерил по поводу всяких непонятных галочек и крючочков вместо понятного pinMode, и абсолютно не представлял, как работать с семисегментником без соответсвующей библиотеки для ардуино ИДЕ и так далее.
У меня есть некоторая уверенность в том, что надобно начинать, изучая азы, сам принцип работы.
Даже если не все сложится, то по крайней мере, в ардуино я смогу, применяя соответсвующие приемы , некоторым образом экономить память.
У меня есть некоторая уверенность в том, что надобно начинать, изучая азы, сам принцип работы.
не смешите.
"Азы" - это например как дефайны писать, а не регистрами жонглировать. И пока вы не знаете "азов" в языке - в регистрах вам делать нечего.
Основная беда многих - что то экономить. Вы часом не из блокадного Ленинграда?
Основная экономия - это время. Ибо время - это деньги. Хотя, приоритеты у каждого могут быть разными.)
Я так понимаю, что хедер это файл с раширением .h, который потом просто подключаешь к основному файлу проекта.
То есть вместо горожения функции можно просто сделать такое объявление?
Спасибо, буду знать.
Я этим не собираюсь зарабатывать(по крайней мере на данный момент) .Пока это желание поупражнять закосневшие мозги, получить новые знания, собрать полезный девайс .
Тем более. Нужно делать по уму, а не выглядеть лохом (извините).
Как-то мы скатились к обсуждению моей личности, а не вопросов, связанных с программированием.
не смешите.
"Азы" - это например как дефайны писать, а не регистрами жонглировать. И пока вы не знаете "азов" в языке - в регистрах вам делать нечего.
Совершенно не согласен. Отделяйте мух от котлет. Это совершенно разные, хоть и связанные вещи. И то и то азы, но в разных областях. Програмирование и програмирование микроконтроллеров. Регистрами жонглировать можно по разному. Например в STM Cube просто галочками и обойтись вообще без дефайнов.
Но с тем что учить надо всё согласен на 100%.
осталось только паровоза дождаться :-D
Как-то мы скатились к обсуждению моей личности, а не вопросов, связанных с программированием.
Нет, это вы скатились к своей личности, а мы продолжили.))
Но, обычно, это всё взаимосвязано. Ибо от человека всё зависит.)
Новичок вполне себе может начать параллельно изучать и то, и другое.
Предполагаю, что впоследствии он, дойдя до некой точки, осознает, что ему для дальнейшего движения нужно "подтянуть" знание СИ.
Это как Ленинское "нельзя вполне понять «Капитал» Маркса, особенно его 1ой главы, не проштудировав и не поняв всей Логики Гегеля".
Однако данное изречение Ленина абсолютно не говорит о том, что вообще невозможно ничего понять в "Капитале" Маркса без понимания "Науки логики" Гегеля. Часто бывало совсем наоборот: после прочтения "Капитала" начинали читать "Логику", а затем уже снова "Капитал" на новом качественном уровне.
Круто вы загнули!) Это ж надо. Вольтемтр, семисегментный индикатор и победа коммунизма!)
Продвигаю "Капитал" Маркса)
теперь тебе вместо капитала надлежит прочитать библию от Кернигана и Ритчи.
Спасибо, скачал.
Только "Капитал" не библия.
Это научная работа, основанная на всем предыдущем развитии политической экономии и развивающая политическую экономию как науку на новом качественном уровне.
Мож лучше к вольтметру вернуться? К тому, что я написал в #2 ещё бы добавить замер питающего напряжения. И будет нормальный прибор. Чудес ждать не надо, до флюка не дотянет, но вполне так себе нормальный показометр. Работайте.
Только "Капитал" не библия.
А никто и не утверждал. Это "K&R" - библия.
Я. Я в #23 утверждал. Каюсь.
Перечитал. Про библию от K&R там есть, а про то, что капитал == библия так и не разглядел. "Мартышка к старости слаба глазами стала" :(
Мда. Я спьяну прочитал: <А никто и не утверждал что "K&R" - библия.>
Прошу прощения. Это я на голову слаб стал.
В общем, поправил код вот так:
По фильтру вопросов нет, так как не понимаю, как он может быть реализован.
Замер питающего напряжения в практическом смысле нужен для того, чтобы делать поправку при пересчете показаний ацп в вольты?
Для начала можно сгенерировать готовый программой WinFilter - она прямо готовый код выдаёт. Там же в ней можно посмотреть частотные и фазовые характеристики получившегося фильтра. Только для нормальной фильтрации важно, чтобы измерения проводились через равные промежутки времени (там один из главных параметров - частота измерений).
Есть некоторая функция, приведенная по вашим ссылкам.
Я ее немного "адаптировал" в меру своего понимания
Некоторые моменты мне не понятны.
Как может измерять что-то строка while (ADCSRA&(1<<ADSC));, да и еще именно внутренний источник опорного напряжения ?? О нем вообще ведь не идет никакой речи в этой функции!
В общем, попытка вывести на семисегментник выдала по нулям. В смысле семисегментник отображает нули.
Можете разъяснить, какова логика измерения?
Ну, Вы текст Гэммона читали? Он же там всё подробно словами объясняет.
Нужно сделать две вещи:
1. Как можно точнее измерить мультиметром внутреннее опорное напряжение. Так-то оно 1,1В, но по даташиту может быть от 1В до 1,2В - зависит от экземпляра, а это даст 10% погрешности. Но, для данного экземпляра оно довольно стабильно. Допустим измерили и забили константой или зашили в EEPROM.
2. Теперь уже программно измерить внутренне опорное напряжение, используя в качестве опорного напряжение пиатния. Сравнивая полученный результат с уже известным правильным внутренним опорным, можно определить точное напряжение питания.
Какого из этих пунктов Вы не понимаете? Вам дать пример второго, считая, что опорное Вы уже измерили? Или что?
И да, кстати, очень не советую использовать конструкции типа Ваших строк №№ 9-12. Эта демонстрация "умности" часто приводит к путанице и ошибкам. Регистры то читать и писать нужно в разном порядке. В даташите же написано, что компилятор с этим отлично справляется сам. Ну и пишите вместо этих строк просто:
long
result = ADC;
Больше ничего не нужно
Просто так получить требуемый код не особо интересно. Только если только как пример, в котором нужно разобраться.
<<Допустим измерили и забили константой>>
то есть я ввожу константу, равную измеренному мультиметром опорному напряжению(мультиметр тоже, кстати, имеет погрешность) Vref =1,15
<<Теперь уже программно измерить внутренне опорное напряжение, используя в качестве опорного напряжение пиатния>>
Не понятно, как это программно реализовывается. Есть строка, которая этим занимается(вроде как)while (ADCSRA&(1<<ADSC));
Мне бы яснее стало если была бы примерно вот такая запись:
while (ADCSRA&(1<<ADSC)){long result=ADC;}
Эквивалетна ли такая запись оригинальной записи у Гэммона?
Не понятно, как это программно реализовывается. Есть строка, которая этим занимается(вроде как)while (ADCSRA&(1<<ADSC));
Мне бы яснее стало если была бы примерно вот такая запись:
while (ADCSRA&(1<<ADSC)){long result=ADC;}
Не знаю, что именно Вам яснее. В даташите явно написано, что бит ADSC остаётеся 1 до тех пор, пока идет преобразование. Когда преобразование заканчивается, в этом бите появляется 0. Поэтому цикл
просто ожидание пока преобразование не закончится. Как только оно закончится, там появится 0 и этот цикл завершится. Тогда (когда преоброзование закончилось) пора читать результат из ADC.
А какой смысл в том, что Вы дергаетесь читать ADC в процессе преобразования, когда оно еще не закончено, я не понимаю.
Эквивалетна ли такая запись оригинальной записи у Гэммона?
а просто сравнить код Гаммона из вашего сообщения №32 и этот - не пробовали? инициализация АЦП (ваша строка 3) разве совпадает с оригинальной? - посмотрите, какие биты устанавливает в ADMUX Гаммон - и какие вы.
Измерение внутреннего опрного напряжения - элементарная конструкция. по следам Гаммона ею пользуются чуть ли не первоклашки. А вы тут что-то жуете который день...
Просто даже если тупо скопировать код Гаммона, то все по нулям. То есть не работает оно у меня.
Может первоклашки и справляются, а я еще детсадовец, получается, коль не справляюсь.
UPD.
Запоказывало. Только результат странный. 0.83 вольта.
Ну, я не знаю, что оно у Вас там запоказывало. Вы же свой скетч секретите. Я вот прямо сейчас по двум своим пунктам из #33 за три минуты написал скетч. Ардуина питается от лабораторного БП (на пин 5V). запускаю, показывает правильное напряжение питания, кручу ручку на БП (понижаю питание до 4 вольт) адекватно показывает 4 вольта, не знаю в чём у Вас заморочка.
Вот мой скетч:
Не секречу ничего.
в общем, пойду дальше читать\изучать.
терять время на том, что пока еще недоступно, не имеет смысла.
Мой-то код измеряет питание у Вас?
Кажет 5.2 вольта.
Ну и дела, понимаешь.
Кстати, атмелстудио ругается на конструкцию bit().
Кажет 5.2 вольта.
Покрутите питание - будет реагировать. Всё нормально работает.
Кстати, атмелстудио ругается на конструкцию bit().
Ну, нету у неё такого макроса. Вы ж не сказали, что студией пользуетесь. Я пример под ИДЕ писал.
Добавьте макрос - не будет ругаться
Да, все работает. Осталось только осмыслить ваш код в полной мере и переработать всю свою программку. Получается, однажды создав static inline void adcEnable(void), его можно использовать в каждом новом задействовании ацп без лишней писанины. Круто.
Осталось только осмыслить
Словами всё написано в посте #33 - больше в коде ничего нет.
По идее, полученное значение напряжения питания микроконтроллера я должен использовать для вычисления измеряемого напряжения.
В чем моя ошибка?
В таком виде показывает внутреннее опорное напряжение
Две проблемы
1.
нигде не чистите регистры. Везде испольуете |, а старые значения не чистите (значит, старые единицы там остаются).
2.
Вы уж определитесь, что Вам нужно режим постоянного измерения или измерения по очереди напряжения из разных источников. Режим постоянного измерения работает и одним источником. А Вы и его включаете, и с питания на 4-ый пин постоянно скачете.
Нафига Вам это "постоянное измерение" вообще нужно?
Вообще, я просто хотел использовать измеренное в фуннкции getAVccVoltage напряжение питания при расчете замеряемого напряжения на четвертом пине ацп.
Чистить регистр это значит после преобразования в ацп выключить(сбросить) его?
вообще, я не понимаю, почему вся программа ломается, если поэкспериментировать в каком-либо месте, а затем вернуть все на свои места?
это ппц как напрягает.
рпосто все перестает работать напрочь.
вот почему у меня навернулся замер внешнего напряжения?? что изменилось??