|
АЦП и прерывания ATmega48, помогите начинающему... |
|
|
|
Sep 22 2008, 10:29
|
Участник

Группа: Новичок
Сообщений: 28
Регистрация: 22-09-08
Пользователь №: 40 380

|
Добрый день. Я занялся изучением AVR, использую AVRStudio в связке с WinAVR. И у меня появились затруднения определённого плана. Я имею представление о том, что есть прерывание, что есть стек, указатель команд и вообще имею хорошее знание касательно теории... Но дошло дело до практики... И попытался я написать простейший код... Итак ситуация: в качестве входа АЦП использоваться ADC7, надо будет на 3 выхода (PB0..2) выдавать адрес на мультиплексер, который будет переключать свои входы, на входв АДС7 будет как раз приходить сигнал с демультеплексора, АЦП должен оцифровать и записать эти данные в массив... Вот примерный код: Цитата #include <avr/io.h> #include <avr/signal.h> #include <avr/interrupt.h>
void port_B_settings (void); void ADC_settings (void); void port_C_settings (void);
int help_reg=0;
ISR (ADC_vect) { help_reg = 1; }
void main (void) { int Ubat[4]; int Tbat[4]; int Icur;
port_B_settings(); ADC_settings();
for (int i=0;i<3;i++) { ADCSRA = 0xC8; sei();
while (!help_reg) {}
Ubat[i] = help_reg; } }
void port_B_settings (void) { DDRB = 0x07; //PB0..1 - outputs }
void ADC_settings (void) { ADMUX = 0x07; //ADC7 ADCSRA = 0x88; //ADEN=1 | ADIE = 1 } В нём правда ещё не выдаётся адрес никуда и т.д. Не суть... А суть в том, что у меня по-левому работает прерывание. Я хочу, чтобы после начала прерывания шло ожидание прерывания, после чего его обработка и после чего запись..., а у меня выходит всякая ересь и в симуляторе вконце концов вообще после первого "прохода" получается бесконечный цикл....=/ Что я делаю неправильно? Вопрос касательно языка С - можно ли обрабатывать прерывание не по этому макросу, а что называется ручками... Просто я не знаю как мне в такой обработчик прерывания отправить например указатель на мой массив, чтобы прям в обработчике осуществлять запись в массив, а не вводить для этого пресловутый help_reg=/ И ещё вопрос: я перед запуском АЦП настраиваю только его вход в ADMUX, а надо ли настраивать ещё первые байты, которые отвечают за настройку напряжения если не ошибаюсь... Вообще если можно, то посвятите немного о том, как настраивать и запускать АЦП, поскольку из даташита к сожалению не всё понял на английском, например мне не понятно немного что такое некий "непрерывный режим работы" АЦП, который настраивается через запись бита ADATA в регистре ADCSRA... Ах да, последнее: речь идёт о ATmega48 Заранее огромное спасибо ответившим, просто уже 2 дня мучаюсь с этим АЦП=/
Сообщение отредактировал NikitoS-86 - Sep 22 2008, 10:31
|
|
|
|
|
Sep 22 2008, 11:45
|
Участник

Группа: Новичок
Сообщений: 28
Регистрация: 22-09-08
Пользователь №: 40 380

|
1) Так я помоему как раз в функции ADC_settings (); и инициализирую регистры... 2) Так ведь я как раз собираюсь сделать так, чтобы шло ожидание прерывания и после чего была обработка прерывания.... Кроме того, при входе в прерывание в SREG автоматом сбросится бит глобальных прерываний и ничего больше не вызовется до момента окончания прерывания....
вопрос пока остаётся=//
Сообщение отредактировал NikitoS-86 - Sep 22 2008, 11:47
|
|
|
|
|
Sep 22 2008, 12:18
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Давайте по-порядку. 1) Для начала очень даже неплохо.  2) Использовать 1 вход АЦП и 3 выхода для демультиплексирования. Может сразу использовать 8 входов? Если ног не хватает микруху взять другую? 3) При вашей инициализации АЦП выдаст прерывание 1 раз. У вас установлен запуск прерывания. Для того чтобы АЦП опять замерило сигнал, надо опять сделать старт измерения (в прерывании после чтения предыдущего значения). ADATE указывает что надо непрерывно измерять сигнал. То есть не требуется перезапуска. Период измерения задаётся в других регистрах. Здесь надо учесть, что у вас не связаны измерение сигнала и смена канала. Следовательно в этом режиме вам придётся отбрасывать одно показание с момента переключения канала. 4) Вы можете перевести АЦП в "автоматический" режим (ADATE). Применить прерывание по таймеру. Настроить таймер так, чтобы Ттаймера > 3*Tацп. Прерывание от АЦП не обрабатывать, а значение читать в прерывании от таймера (после этого сменять канал). 5) Естественно целесообразно прямо из прерывания заполнять массив. Не требуется никаких макросов. Такой как вы указали я не знаю. Для этого Вам надо объявить массив глобальным. Тоесть он должен быть объявлен там где вы объявили help_reg. В этом случае, он будет виден и в прерывании ив голове. Если вы хотите чтобы переменная была видна только в прерывании и в тоже время сохраняла своё значение м/у входами в прерывание (например текущий номер канала), то необходимо объявить её в прерывании с квалификатором static. Вот мелкий пример. Правда компилятор другой, ну и вырвано из контекста по-живому, но в общем то представление сложить удастся. Код ... // Из main.h #define KADMUX (1<<ADLAR) // работа от внешнего опорного источника с выравниванием "влево" //#define KADMUX 0 // работа от внешнего опорного источника 10 бит #define KADCSRA ((1<<ADEN)|(1<<ADSC)|(1<<ADATE)|(1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2))// ADC разрешено, стартовать преобразование, на частоте Fclk/128 //#define KADCSRB ((1<<ADHSM) // скоростной режим ADC разрешен #define KADCSRB 0 // скоростной режим ADC запрещён #define VIRTCHAN 0x3 // "Виртуальный" аварийный канал ... // Инициализация // Инициализация АЦП ADMUX = KADMUX; // работа от внешнего опорного источника с выравниванием "влево" ADCSRA = KADCSRA; // разрешить режим "одиночный" с частотой 125кГц ADCSRB = KADCSRB; // запретить режим "high speed" DIDR0 = 0xf; // закрыть 4 первых бита канала АЦП ... // Прерывание #pragma vector=TIMER1_COMPA_vect __interrupt static void pvPWWLvl1(void) { uint8_t static TekChan,InADCChan=1; // Текущий канал, текущий измеряемый канал АЦП uint8_t static ChngChan; // Смена канала АЦП uint8_t i; ... ChngChan++; // увеличить счётчик прерываний if(ChngChan==5) // На пятый цикл сменить канал и прочитать АЦП { ChngChan=0; // начать сначала InpAdc[InADCChan]=(InpAdc[InADCChan]+ADCH)>>1; // Считать канал и усреднить InADCChan++; // Сменить канал if(InADCChan==CAN_T_CHAN) { InADCChan=1; // Переход по кольцу InpAdc[MAXADC-1]=InpAdc[VIRTCHAN]; // Переприсвоить виртуальный канал } ADMUX=KADMUX | InADCChan; // Выбрать канал ADCSRA =KADCSRA; // Начать отсчёт заново } } ...
|
|
|
|
|
Sep 22 2008, 12:43
|
Участник

Группа: Новичок
Сообщений: 28
Регистрация: 22-09-08
Пользователь №: 40 380

|
1) К сожалению выбрать другую микруху - уже нельзя, схему делал не я, и у меня на руках уже готовая схема, плата будет в скором времени изготовлена - уже не пойдёт=/ 2) Да, согласен, в этом месте я ступил.... 3) Как можно всётаки обойтись без макросов на С? МОжно ли сделать так, чтобы вызов прерывания был похож на вызов функции? Потому что даже если я сделаю массивы гнлобальными, то мне как-то надо ведь им сообщать какое по счёту это прерывание? Можно конечно завести ещё вдобавок глобальную переменную-счётчки... Но я если честно вообще не очень жалую глобальные переменные именно потому, что доступ к ним - откуда хочешь... Если не сложно можно получить пример простейший как вообще выполнять обработчик что называется по-простому.... 4) Переделал немножко код, и теперь он выглядит вот так вот... и вроде даже что-то делает: Цитата int help_reg;
SIGNAL (SIG_ADC) { help_reg = 1; }
void main (void) { int Ubat[4]; int Tbat[4]; int Icur; cli();
port_B_settings(); ADC_settings();
for (int i=0;i<3;i++) { ADCSRA = 0xC8; sei(); for (;;) { if (ADCSRA == 0x98) break; }
Ubat[i] = help_reg; help_reg=0; } }
void port_B_settings (void) { DDRB = 0x07; //PB0..1 - outputs }
void ADC_settings (void) { ADMUX = 0x07; //ADC7 ADCSRA = 0x88; //ADEN=1 | ADIE = 1 } У меня только возник ещё вопросы: поскольку мы пишем на С, а не на ассемблере, то при прогоне программы дебагер проскакивает те месте, где я работаю со своими переменными! Совершенно очевидно, что когда я пишу intX = ...; на ассемблере это может занять больше строчек... Но всётаки можно как-нибудь заставить его показывать что вообще происходит с моими переменными? Как мне например после прохода программы определить что он поместил в мой массив? как это увидеть? И ещё один вопрос: как средствами языка мне получить доступ к отдельным битам регистров? Как видно в исправленном варианте я просто ставлю условие того, что изменится значение всего регистра! А как мне проверить изменение только одного бита в этом регистре? Всем большое спасибо! Пока писал ответ Вы добавили код=) Но тем лучше... МОжно только поподробнее по поводу синтаксиса прерывания... #pragma.......... как работает? И ещё одно... В примере в самом начале есть строчка: #define KADMUX (1<<ADLAR) // работа от внешнего опорного источника с выравниванием "влево" Разве ADLAR имеет какое-то отношение к тому, какой источник используется? Помоему он отвечает только за то, в каком виде представлять результат=/ Или это как раз из-за того, что вырвано из контекста?
|
|
|
|
|
Sep 22 2008, 12:58
|
Частый гость
 
Группа: Участник
Сообщений: 169
Регистрация: 31-08-05
Из: New York
Пользователь №: 8 118

|
Для манипуляции битами регистров в библиотеке avr-libc WinAVR есть модуль <avr/sfr_defs.h>.
--------------------
ASB
|
|
|
|
|
Sep 22 2008, 13:13
|
Участник

Группа: Новичок
Сообщений: 28
Регистрация: 22-09-08
Пользователь №: 40 380

|
Цитата(Aleksandr Baranov @ Sep 22 2008, 16:58)  Для манипуляции битами регистров в библиотеке avr-libc WinAVR есть модуль <avr/sfr_defs.h>. А как она работает, если не секрет? Подключил, попробовал следующие варианты на примере 6го бита ADCSRA (ADSC - запуск АЦП): ADCSRA.6 = 0x01; ADCSRA.ADSC = 0x01; ADSC = 0x01; и ни один он не проглотил... Спасибо.
|
|
|
|
|
Sep 22 2008, 13:43
|
Частый гость
 
Группа: Участник
Сообщений: 169
Регистрация: 31-08-05
Из: New York
Пользователь №: 8 118

|
Цитата(NikitoS-86 @ Sep 22 2008, 09:13)  А как она работает, если не секрет? Подключил, попробовал следующие варианты на примере 6го бита ADCSRA (ADSC - запуск АЦП): ADCSRA.6 = 0x01; ADCSRA.ADSC = 0x01; ADSC = 0x01; и ни один он не проглотил...
Спасибо. Там в доке все написано: Bit manipulation#define _BV(bit) (1 << (bit)) IO register bit manipulation#define bit_is_set(sfr, bit) (_SFR_BYTE(sfr) & _BV(bit)) #define bit_is_clear(sfr, bit) (!(_SFR_BYTE(sfr) & _BV(bit))) #define loop_until_bit_is_set(sfr, bit) do { } while (bit_is_clear(sfr, bit)) #define loop_until_bit_is_clear(sfr, bit) do { } while (bit_is_set(sfr, bit)) Значит, для проверки бита, надо написать нечто вроде: Код if(bit_is_set(PINB,3)){
} По аналогии можно сделать: Код #define set_bit(sfr,bit) (sfr |= _BV(bit)) #define clear_bit(sfr,bit) (sfr &= ~(_BV(bit))) У меня дока лежит тут: file:///C:/WinAVR-20071221/doc/avr-libc/avr-libc-user-manual-1.6.1/index.html. У Вас, видимо, примерно в таком же месте.
--------------------
ASB
|
|
|
|
|
Sep 22 2008, 15:48
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(NikitoS-86 @ Sep 22 2008, 15:43)  3) Как можно всётаки обойтись без макросов на С? МОжно ли сделать так, чтобы вызов прерывания был похож на вызов функции? Потому что даже если я сделаю массивы гнлобальными, то мне как-то надо ведь им сообщать какое по счёту это прерывание? Можно конечно завести ещё вдобавок глобальную переменную-счётчки... Но я если честно вообще не очень жалую глобальные переменные именно потому, что доступ к ним - откуда хочешь... Если не сложно можно получить пример простейший как вообще выполнять обработчик что называется по-простому.... 1) А... Вот вы о чём.  То есть вы вообще хотите обойтись без прерываний, на сколько я понял. Да - можно. И, по-моему это понятно из моего текста. У меня ведь прерывание по таймеру, а я там читаю АЦП. А вы можете вызывать чтение АЦП непосредственно в main. Либо прямо в теле, либо с помощью п/п. В этом случае в п/п вам надо проверить флаг готовности АЦП и ждать его выставления и прочитать значение. Опять таки (см. выше) первое, после переключ. канала отбросить как не верное. 2) Доступ к глобальным переменным можно ограничить тем же static. В этом случае обращение к ним будет возможно только из данного файла. Посмотрите также мой текст внимательно. Так номер канала у меня размещён в озу (не портится от вызова к вызову) и в тоже время обращение к этой переменной возможно только из прерывания. Всё это, я предвидя ваши вопросы уже писал - почитайте внимательно. Цитата 4) Переделал немножко код, и теперь он выглядит вот так вот... и вроде даже что-то делает: У меня только возник ещё вопросы: поскольку мы пишем на С, а не на ассемблере, то при прогоне программы дебагер проскакивает те месте, где я работаю со своими переменными! Совершенно очевидно, что когда я пишу intX = ...; на ассемблере это может занять больше строчек... Но всётаки можно как-нибудь заставить его показывать что вообще происходит с моими переменными? Как мне например после прохода программы определить что он поместил в мой массив? как это увидеть? 1) Вы можете расскрыть этот оператор при необходимости. Для этого в AVR Studio есть View/Disasmebler. И ... идите по асмовому тексту 2) Для просмотра содержимого переменных (в том числе массивов, структур и даже указателей) есть View/Watch. Кстати если на переменной щёлкнуть правой кн. мыши, то сразу можно добавить в окно отладки. И ... пожалуйста - смотри - меняй. В любом виде. Цитата Пока писал ответ Вы добавили код=) Но тем лучше... МОжно только поподробнее по поводу синтаксиса прерывания... #pragma.......... как работает? Я работаю под IAR. Там это фактически указание компилятору по какому вектору расположить данное прерывание ну и тому подобное (например закончить RETI вместо RET) Цитата И ещё одно... В примере в самом начале есть строчка: #define KADMUX (1<<ADLAR) // работа от внешнего опорного источника с выравниванием "влево"
Разве ADLAR имеет какое-то отношение к тому, какой источник используется? Помоему он отвечает только за то, в каком виде представлять результат=/ Или это как раз из-за того, что вырвано из контекста? Просто в данном регистре также кодируется источник ОН. Если соотв. бит=0 (как у меня) так - внутр. опора. Я это написал в коментариях, чтобы видеть. Возможно правильнее было бы написать #define KADMUX (1<<ADLAR) |(0<<REFS0)|(0<<REFS1) // работа от внешнего опорного источника с Всё в ваших руках. Я коментирую для себя. Сам пишу - сам читаю.  Поэтому местами упрощаю.
|
|
|
|
Guest_Цыкетчик_*
|
Sep 23 2008, 10:50
|
Guests

|
Надо не забыть ещё сделать задержку примерно на 1мС после включения модуля АЦП установкой бита ADEN. Хоть это и не написано в даташифтах, но длительность переходных процессов сосотавляет порядка 500мкС. Если делать АЦП до истечения этого временного интервала - показания сильно врут. Узнал это опытным путём Цитата(SasaVitebsk @ Sep 22 2008, 19:48)  Для этого в AVR Studio есть View/Disasmebler. Который , кстати, глючит
|
|
|
|
|
Sep 23 2008, 12:25
|
Участник

Группа: Новичок
Сообщений: 28
Регистрация: 22-09-08
Пользователь №: 40 380

|
Итак, во-первых, ОГРОМНОЕ спасибо всем откликнувшимся в данной теме=). Отдельное спасибо товарищам SasaVitebsk, Aleksandr Baranov и Цыкетчик за подробные ответы и сведения касающиеся практики!!! Я наконец разобрался!=))) Как промежуточный конечный результат я сваял следующий код: Цитата #include <avr/io.h> #include <avr/sfr_defs.h> #include <avr/delay.h>
#define MYADMUX_U_T (1<<REFS1) | (1<<REFS0) | (0<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0); #define MYADMUX_I (1<<REFS1) | (1<<REFS0) | (0<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
void port_B_settings (void); void port_C_settings (void); void measure_Ubat (int *, int *); void measure_Tbat (int *, int *); void measure_Icur (int *);
void main (void) { int Ubat[4]; int Tbat[4]; int Icur;
const int channel_number[8] = {0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111}; port_B_settings(); ADCSRA = 0x80; _delay_ms(10); measure_Ubat (&Ubat, &channel_number); measure_Tbat (&Tbat, &channel_number);
measure_Icur (&Icur); return; }
void port_B_settings (void) { DDRB = 0x07; return; }
void measure_Ubat (int *Ubat, int *channel_number) { for (int i=0;i<4;i++) { ADMUX = MYADMUX_U_T; PORTB = ((channel_number[i]>>2)<<PB2) | ((channel_number[i]>>1)<<PB1) | ((channel_number[i])<<PB0); _delay_ms(10);
ADCSRA=ADCSRA | (1<<ADSC); for (;;) { if (bit_is_set(ADCSRA,4)) break; }
*(Ubat+i) = ADCH; *(Ubat+i) = (*(Ubat+i)<<8) + ADCL;
ADCSRA = ADCSRA | (1<<ADIF); }
return; }
void measure_Tbat (int *Tbat, int *channel_number) { for (int i=0;i<4;i++) { ADMUX = MYADMUX_U_T; PORTB = ((channel_number[i+4]>>2)<<PB2) | ((channel_number[i+4]>>1)<<PB1) | ((channel_number[i+4])<<PB0); _delay_ms(10);
ADCSRA=ADCSRA | (1<<ADSC); for (;;) { if (bit_is_set(ADCSRA,4)) break; }
*(Tbat+i) = ADCH; *(Tbat+i) = (*(Tbat+i)<<8) + ADCL;
ADCSRA = ADCSRA | (1<<ADIF); }
return; }
void measure_Icur (int *Icur) { ADMUX = MYADMUX_I ADCSRA=ADCSRA | (1<<ADSC);
for (;;) { if (bit_is_set(ADCSRA,4)) break; }
*Icur = ADCH; *Icur = (*Icur<<8) + ADCL;
ADCSRA = ADCSRA | (1<<ADIF);
return; } Который вроде даже работает=) У меня только вот что "вылезло": 1) В последнем ответе SasaVitebsk на мой пост есть фраза: Цитата В этом случае в п/п вам надо проверить флаг готовности АЦП и ждать его выставления и прочитать значение. Опять таки (см. выше) первое, после переключ. канала отбросить как не верное. этот момент мне не очень понятен... Или таким образом реализуется "устаканивание" сигнала после смены канала? своего рода задержка между подачей сигнала на оцифровку и оцифровкой дабы получить более точное значение? Если так, то можно ли это реализовать функцией delay()? 2) На счёт встроенного дизасемблера и функции watch - спасибо, что подсказали... Удобно... НО почему она мне не позволяет наблюдать за метаморфозами моих переменных во время нахождения в вызываемых функциях? А я могу видеть состояние массивов только после возвращения из этих функций... Кроме того, переменную Icur она вообще наотрез отказывается отображать, всё время пишет либо "Location not valid" либо "Not in scope"... Таким образом я даже не вижу что с ней происходит... Может я как-то неправильно осуществляю вызов функций? Вроде бы всё что называется как по книжке... или может быть я где-то ошибся? Кстати при компиляции она мне пишет различного рода вонингсы как раз на моменты вызова функции... Они звучат следующим образом: "../first.c:29: warning: passing argument 1 of 'measure_Ubat' from incompatible pointer type" что я примерно перевожу как: "впервый аргумент функции 'measure_Ubat' несовместим с сылочным типом..." Я в чём-то заблуждаюсь при вызове функций? Спасибо! Чёрт, эта зараза не понимает рускую кодировку каментов из студии, пришлось все каменты удалить, ибо каракули отобразились при вставке...
Сообщение отредактировал NikitoS-86 - Sep 23 2008, 12:27
|
|
|
|
|
Sep 23 2008, 13:25
|
Участник

Группа: Новичок
Сообщений: 28
Регистрация: 22-09-08
Пользователь №: 40 380

|
Да, так и есть по идее, и сначала я пользовал именно так, но он тоже писал: ../first.c:36: warning: passing argument 2 of 'measure_Ubat' from incompatible pointer type т.е. и так и так, ворнинги...
|
|
|
|
|
Sep 23 2008, 13:46
|

Беспросветный оптимист
     
Группа: Свой
Сообщений: 4 640
Регистрация: 26-12-07
Из: Н.Новгород
Пользователь №: 33 646

|
Цитата(NikitoS-86 @ Sep 23 2008, 17:25)  warning: passing argument 2 of 'measure_Ubat' from incompatible pointer type т.е. и так и так, ворнинги... 1-й то аргумент в порядке. а со вторым - может вот это... Цитата void measure_Ubat (int *, int * ); ... const int channel_number[8] = {0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111};
--------------------
Программирование делится на системное и бессистемное. ©Моё :) — а для кого-то БГ — это Bill Gilbert =)
|
|
|
|
|
Sep 23 2008, 16:09
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Высскажу несколько соображений общего характера. В принципе они не принципиальны, и если они вас заденут, то просто проигнорируйте их. Во всяком случае не пытаюсь вас задеть либо обидеть, а хочу помочь осмыслить. Итак... 1) Вот такая конструкция мне не понравилась. "*(Ubat+i) = ADCH;" Лучше перед циклом осуществить начальное присваивание, а в цикле применить конструкцию "*Ubat++ = ADCH;". Ну и далее по тексту несколько аналогичных мест будет. Поймите, у вас в руках не IBM370 и 2 десятка кластеров, а мааааленький камушек. И к ресурсам надо бережно относится.  Посмотрите ради любопытства что ваш проект весит до модификации и после. Посмотрите как именно операторы превращаются в АСМ. Это очень полезно для дела. Да и интересно чёрт возьми.  2) Или я чего-то просмотрел, или ваш main выполнится 1 раз и станет колом. Или каждый раз переинициализация, короче даже не знаю. Я так не делаю. Обычно в main идёт инициализация и главный цикл. Бесконечный. 3) по АЦП. В общем то у вас всё правильно, только как бы вам сказать, так обычно стараются не делать. Стараются из камня выжать всё возможное и производительность важна. Как правило измерение АЦП - это не самоцель. По результату измерения что-то совершается. Если исходить из этого, то цикл "Переключение канала-запуск измерения-ожидание завершения-чтение результата" не совсем удачен. Обычно делают цикл "чтение результата-переключение канала-запуск измерения-ожидание". Вы спросите - в чём разница - разница в том, что ожидание можно выкинуть. Точнее во время ожидания - можно заниматься другим делом, ну например фильтрацией результата и выводом его на ЖК дисплей. 4) Ответ на вопрос по АЦП. Я писал про случай когда АЦП работает в автоматическом режиме. Тогда ваше переключение канала попадает на произвольное место цикла измерения АЦП, что, в свою очередь, делает неточным первый результат измерения после процесса переключения.
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|