Форум программистов, компьютерный форум, киберфорум
Микроконтроллеры ATmega AVR
Войти
Регистрация
Восстановить пароль
 
3 / 3 / 4
Регистрация: 04.12.2015
Сообщений: 54
1

АЦП Atmega8

17.06.2020, 21:12. Просмотров 300. Ответов 12

Здравствуйте, столкнулся с проблемой оцифровки сигнала. Сигнал идет на 3 канала(Датчики света) PC3 PC4 PC5, кварц стоит на 11.0592 МГц.
Суть проблемы в том, что в терминал приходят очень странные данные, даже при засвете, значение не меняется.

Инициализирую ADC вот так
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ADC_Init(void){
    DDRC &= ~((1<<3) | (1<<4) | (1<<5));// Регистр на вход  PC5 PC4 PC3
    //ADC init
    ADCSRA |= (1<<ADEN);//Вкл АЦП
 
    ADCSRA |= 1<<ADPS2 | 1<<ADPS1 | 1<<ADPS0; // предделитель на 128
    
    ADCSRA |= (1<<ADIE);// разрешаем прерывание
    
    ADMUX |= (1<<REFS0); //AVCC with external capacitor at AREF pin
    
    ADMUX |= (1<<ADLAR); //лев выравнивание
    
    ADMUX |= (1<<MUX1) | (1<<MUX0); // PC3 запуск по 1 каналу
    
    // запускаем первое АЦ-преобразование
    ADCSRA |= 1<<ADSC;  
}
Меняю каналы АЦП вот так
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ISR(ADC_vect) {
    
    switch (ADMUX) {
        case 0b01100011://PC3
        ADMUX |= 0b01100100;
        break;
        case 0b01100100://PC4
        ADMUX = 0b01100101;
        break;
        case 0b01000101://PC5
        ADMUX = 0b01100011;             
        break;
        default:
        break;
    }
}
Устанавливаю частоту дискретизации сигнала 120 Гц по таймеру, тут не уверен верно ли делаю:
1)Тактовая частота = 11059200 Гц ( 11.0592 МГц )
Делитель = 1024;
2)Частота таймера счетчика = Тактовая частота/Делитель = 11059200/1024 = 10800 Гц;
3)Длительность периода( тика ) = 1/10800 = 92.593*10^-6 секунды = 92.593 мкс
4)Моя частота дескр = 120 Гц ->1/120 = 0.008 сек = 8 мсек=8000мксек
5)так как у меня длительность периода тиков(п.3) = 92.593, то чтоб мне посчитать число до которого считать, мне нужно пунк 4/пунк 3 -> 8000 мксек/92.593 мксек = 86 тактов, а в число до которого считать,я записываю меньше(85) так как отсчет идет с нуля
C
1
2
3
4
5
6
7
8
void timer_ini(void){
    TCCR1B |= (1<<WGM12); // устанавливаем режим СТС (сброс по совпадению)
    TCCR1B |= (1<<CS10) | (1<<CS12);//установим делитель. на 1024
    TIMSK |= (1<<OCIE1A);   //устанавливаем бит разрешения прерывания 1ого счетчика по совпадению с OCR1A
    
    OCR1A = 85;
    
}
Ну и по прерыванию таймера отправляю посылку по UART на ПК
C
1
2
3
4
5
6
7
char buffer[10];
ISR(TIMER1_COMPA_vect){ 
    *itoa(ADCH,buffer,10);
    USARTWriteStr(buffer);  
    USART_Transmit(13);
    ADCSRA |= 1<<ADSC;
}
Полный листинг под сполером
Кликните здесь для просмотра всего текста

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#ifndef F_CPU
#define F_CPU 11059200UL    //  рабочая частота
#endif
 
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
 
void ADC_Init(void){
    DDRC &= ~((1<<3) | (1<<4) | (1<<5));// Регистр на вход  PC5 PC4 PC3
    //ADC init
    ADCSRA |= (1<<ADEN);//Вкл АЦП
 
    ADCSRA |= 1<<ADPS2 | 1<<ADPS1 | 1<<ADPS0; // предделитель на 128
    
    ADCSRA |= (1<<ADIE);// разрешаем прерывание
    
    ADMUX |= (1<<REFS0); //AVCC with external capacitor at AREF pin
    
    ADMUX |= (1<<ADLAR); //лев выравнивание
    
    ADMUX |= (1<<MUX1) | (1<<MUX0); // PC3 запуск по 1 каналу
    
    // запускаем первое АЦ-преобразование
    ADCSRA |= 1<<ADSC;  
}
 
 
void USART_Init( unsigned int ubrr)
{
    /* Set baud rate */
    UBRRH = (unsigned char)(ubrr>>8);
    UBRRL = (unsigned char)ubrr;
    /* Enable receiver and transmitter */
    UCSRB = (1<<RXEN)|(1<<TXEN);    
    UCSRC |=(1<< URSEL)|(1<< UCSZ0)|(1<< UCSZ1); //Устанавливем формат 8 бит данных
  
}
 
void USART_Transmit( unsigned char data )
{
    /* Wait for empty transmit buffer */
    while ( !( UCSRA & (1<<UDRE)) )
    ;
    /* Put data into buffer, sends the data */
    UDR = data;
}
void USARTWriteStr(char *data)
{
    while(*data){
        USART_Transmit(*data++);
    }
}
 
ISR(ADC_vect) {
    
    switch (ADMUX) {
        case 0b01100011://PC3
        ADMUX |= 0b01100100;
        break;
        case 0b01100100://PC4
        ADMUX = 0b01100101;
        break;
        case 0b01000101://PC5
        ADMUX = 0b01100011;             
        break;
        default:
        break;
    }
}
void timer_ini(void){
    TCCR1B |= (1<<WGM12); // устанавливаем режим СТС (сброс по совпадению)
    TCCR1B |= (1<<CS10) | (1<<CS12);//установим делитель. на 1024
    TIMSK |= (1<<OCIE1A);   //устанавливаем бит разрешения прерывания 1ого счетчика по совпадению с OCR1A
    
    OCR1A = 85;
    
}
char buffer[10];
ISR(TIMER1_COMPA_vect){ 
    *itoa(ADCH,buffer,10);
    USARTWriteStr(buffer);  
    USART_Transmit(13);
    ADCSRA |= 1<<ADSC;
}
int main(void)
{
    USART_Init(71);//9600
    ADC_Init();
    timer_ini();
    sei();
    while (1) 
    {
 
    }
}
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
17.06.2020, 21:12
Ответы с готовыми решениями:

Atmega8 АЦП
Подключены к портам PC0-PC5 шесть потенциометров. С портов PC0-PC2 приходит нормальная информация...

АЦП Atmega8
Код АЦП вывод на LCD чета не фурычит,не судите строго только изучаю АЦП. пишу только на ASM. В...

АЦП ATmega8
Доброго времени суток. Написал программу, в которой при выходе из обработчика прерывания по...

Atmega8 и внешний АЦП
Существует внешний программируемый 24 разрядный АЦП, который подключен к atmega8. Необходимо...

12
901 / 541 / 81
Регистрация: 15.05.2012
Сообщений: 3,168
18.06.2020, 09:12 2
А вот здесь
C
1
ADCSRA |= 1<<ADPS2 | 1<<ADPS1 | 1<<ADPS0; // предделитель на 128
по-аккуратнее в плане синтаксиса.
0
2514 / 1559 / 335
Регистрация: 09.09.2017
Сообщений: 6,301
18.06.2020, 13:40 3
Изменение канала у вас неправильно:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ISR(ADC_vect) {
  switch(ADMUX & 0b111) {
    case 3:
      ADMUX = (ADMUX &~ 0b111) | 4;
      break;
    case 4:
      ADMUX = (ADMUX &~ 0b111) | 5;
      break;
    case 5:             
      ADMUX = (ADMUX &~ 0b111) | 3;
      break;
    default:;
  }
}
0
3 / 3 / 4
Регистрация: 04.12.2015
Сообщений: 54
18.06.2020, 19:14  [ТС] 4
Поправил
C
1
ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // предделитель на 128
Добавлено через 27 минут
Цитата Сообщение от COKPOWEHEU
Изменение канала у вас неправильно:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ISR(ADC_vect) {
  switch(ADMUX & 0b111) {
    case 3:
      ADMUX = (ADMUX &~ 0b111) | 4;
      break;
    case 4:
      ADMUX = (ADMUX &~ 0b111) | 5;
      break;
    case 5:             
      ADMUX = (ADMUX &~ 0b111) | 3;
      break;
    default:;
  }
}
Немного непонятно: после инициализации АЦП регистр ADMUX стал
REFS1-0
REFS0-1
ADLAR-1
– -0
MUX3-0
MUX2-0
MUX1-1
MUX0-1
Вы получается, как заходите в прерывание регистр ADMUX = 01100011 & 111 = 11, идете в 3 кейс
в нем ADMUX =( 11 &~111) |4 = и вот эта запись немного непонятна, можете объяснить пожалуйста.
0
2514 / 1559 / 335
Регистрация: 09.09.2017
Сообщений: 6,301
18.06.2020, 20:40 5
ADMUX & 0b111 - по маске вырезаем все кроме битов, отвечающих за номер канала. Соответственно, остается только номер, его и проверяем.
ADMUX &~ 0b111За счет побитовой инверсии тут, наоборот, вырезаем биты канала, сохраняя все остальные. Ну а следующей операцией на освободившиеся места записываем номер канала.
Тут я воспользовался двумя тайными знаниями:
биты канала соответствуют младшим битам регистра
биты канала идут подряд и соответствуют номеру канала. То есть запись 0b101 соответствует именно пятому каналу.
Поэтому проверка по "магическим числам" в данном случае нагляднее, чем по "магическим битовым маскам".
1
3 / 3 / 4
Регистрация: 04.12.2015
Сообщений: 54
19.06.2020, 19:51  [ТС] 6
Цитата Сообщение от COKPOWEHEU
ADMUX & 0b111 - по маске вырезаем все кроме битов, отвечающих за номер канала. Соответственно, остается только номер, его и проверяем.
ADMUX &~ 0b111За счет побитовой инверсии тут, наоборот, вырезаем биты канала, сохраняя все остальные. Ну а следующей операцией на освободившиеся места записываем номер канала.
Тут я воспользовался двумя тайными знаниями:
биты канала соответствуют младшим битам регистра
биты канала идут подряд и соответствуют номеру канала. То есть запись 0b101 соответствует именно пятому каналу.
Поэтому проверка по "магическим числам" в данном случае нагляднее, чем по "магическим битовым маскам".
ааа, как раз получается
C
1
(ADMUX &~ 0b111) | 4 = 0b01100000 | 4(0100) = 0b01100100
как раз соответствует 4 каналу.
Спасибо большое, что показали, так намного проще записывать.

А еще не подскажите, верно ли я инициализирую таймер на частоту 120 Гц?
0
2514 / 1559 / 335
Регистрация: 09.09.2017
Сообщений: 6,301
19.06.2020, 22:53 7
Цитата Сообщение от Lagasy Посмотреть сообщение
А еще не подскажите, верно ли я инициализирую таймер на частоту 120 Гц?
Даташита под рукой нет, так что правильность битов уж сами перепроверьте, но кое-какие советы все же дам:
Цитата Сообщение от Lagasy Посмотреть сообщение
#define F_CPU 11059200UL // рабочая частота
Тактовую частоту стоит прописывать в настройках проекта или makefile. На нее могут быть завязаны несколько исходников (*.c) и у вас будет лишний шанс ошибиться если будете прописывать ее в каждом независимо. Лучше уж добавить флаг -DF_CPU=11059200UL
Цитата Сообщение от Lagasy Посмотреть сообщение
TCCR1B |=
Здесь вы только изменяете значение. Лучше бы его вначале проинициализировать нулем, а уж потом менять.
И регистр TCCR1A тоже стоит инициализировать.
Цитата Сообщение от Lagasy Посмотреть сообщение
TCCR1B |= (1<<CS10) | (1<<CS12);//установим делитель. на 1024
Поскольку биты идут подряд, лично мне кажется более наглядным объединить их: TCCR1B |= (0b101 << CS10);
Цитата Сообщение от Lagasy Посмотреть сообщение
OCR1A = 85;
Магические чиселки - плохая идея. Лучше воспользоаться формулой, что-то вроде такого:
C
1
2
#define FREQ_HZ 120
OCR1A = (F_CPU / 1024 / FREQ_HZ) - 1;
Еще раз предупреждаю: писал из головы, лучше перепроверьте. Интересно, что у вас написано 85, а по моим расчетам должно быть 89. Это точно надо перепроверить.
Еще один момент: в моей формуле нет округления, а для большей точности хорошо бы его добавить. Сделать это можно, например, так:
OCR1A = (2 * F_CPU / 1024 / FREQ_HZ + 1)/2 - 1;
или, если добавить знания арифмтики,
OCR1A = (2 * F_CPU / 1024 / FREQ_HZ - 1)/2;
И еще один момент: макроконстанта FREQ_HZ содержит единицы идмерения, герцы. Это полезная практика, когда работаете с величинами реального мира.
0
404 / 192 / 40
Регистрация: 21.09.2008
Сообщений: 661
20.06.2020, 06:28 8
Мне не совсем понятен мотив автора темы использовать прерывание по аналого-цифровому преобразованию. Понимаю, когда нужна очень большая и быстрая выборка, но 120 Гц - это не такая уж большая частота и вполне можно обойтись просто чтением значения из требуемого канала. Код и логика становятся проще.
Инициализацию АЦП я делал парой строк:
C
1
2
3
DDRC = 0; // аналоговые контакты на вход
// делим частоту МК на 128 для оцифровки аналоговых значений
ADCSRA = _BV(ADEN) | _BV(ADIF) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
Чтение из требуемого канала (0..7):
C
1
2
3
4
5
6
7
8
9
10
11
uint16_t getADC(uint8_t channel) {
  if (channel > 7)
    return 0;
  ADMUX = channel | _BV(REFS0); // используется AVCC
  _delay_us(10);        //Задержка для успокоения АЦП
  ADCSRA |= _BV(ADSC);      //запуск преобразования
  while ((ADCSRA & _BV(ADIF)) == 0)
    {}; //Ожидание завершения 
  ADCSRA |= _BV(ADIF);      //АЦП преобразования
  return ADCW;
}
Ещё мне не понравилась инициализация UART "волшебной" константой. Изменится частота "камня" и самому придётся вычислять правильное значение. Я делаю так:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void uartInit(uint32_t baudrate) {
  //Вычислить скорость порта для выбранной скорости тактирования
  uint16_t trate = ((uint32_t)F_CPU/16/baudrate-1);
#if defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__)
  UBRR0H = (uint8_t)(trate >> 8);
  UBRR0L = (uint8_t)trate;
  // Разрешить передачу и прием, прерывание по приему
  UCSR0B = _BV(TXEN0) | _BV(RXEN0);
  // 8 битный режим, нет проверки на чётность, 1 стоп-бит
  UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
#else
  UBRRH = (uint8_t)(trate >> 8);
  UBRRL = (uint8_t)trate;
  // Разрешить передачу и прием, прерывание по приему
  UCSRB = _BV(TXEN) | _BV(RXEN);
  // 8 битный режим, нет проверки на чётность, 1 стоп-бит
  UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0);
#endif
}
через вызов
C
1
uartInit(57600); //57600 бит/с
1
2514 / 1559 / 335
Регистрация: 09.09.2017
Сообщений: 6,301
20.06.2020, 09:41 9
Цитата Сообщение от sharpey Посмотреть сообщение
Мне не совсем понятен мотив автора темы использовать прерывание по аналого-цифровому преобразованию
Чтобы не ждать в прерывании. На самом деле, правильно делает что осваивает прерывания.
Другое дело, что можно обойтись вообще без прерываний, по принципу конечного автомата:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while(1){
...
  if( TIFR & (1<<OCIF1A) ){ //проверяем флаг прерывания (само прерывание запрещено)
    TIFR |= (1<<OCIF1A); //сбрасываем флаг (рекомендую уточнить: большинство флагов сбрасываются записью 1, а не 0. Но не все)
    switch(ADMUX & 0b111){
      ...
    }
    //обратите внимание, после изменения канала АЦП я даю небольшую задержку чтобы прошли переходные процессы при переключении.
    //задержка образуется за счет времени выполнения других полезных команд
    itoa(ADCH, buffer, 10);
    USARTWriteStr(buffer);  
    USART_Transmit(13);
    ADCSRA |= (1<<ADSC);
  }
}
0
404 / 192 / 40
Регистрация: 21.09.2008
Сообщений: 661
20.06.2020, 14:58 10
Цитата Сообщение от Lagasy Посмотреть сообщение
C
1
2
3
int main(void)
{
 USART_Init(71);//9600
Вот она, ошибка! В USART_Init всё верно. Если хотите скорость 9600 бит/с, так и передавайте это число в параметре функции.
0
3 / 3 / 4
Регистрация: 04.12.2015
Сообщений: 54
20.06.2020, 21:07  [ТС] 11
Цитата Сообщение от sharpey Посмотреть сообщение
Сообщение от Lagasy
C
1
2
3
int main(void)
{
 USART_Init(71);//9600
Вот она, ошибка! В USART_Init всё верно. Если хотите скорость 9600 бит/с, так и передавайте это число в параметре функции.
Разве не это значение я должен посылать в функцию
0
Миниатюры
АЦП Atmega8  
3 / 3 / 4
Регистрация: 04.12.2015
Сообщений: 54
20.06.2020, 21:13  [ТС] 12
Сделал вот так вот, если вдруг кому-то пригодиться, вроде как работает.
Огромное спасибо COKPOWEHEU и sharpey.
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
 
#define FREQ_HZ 120
 
void ADC_Init(void){
    DDRC = 0; 
    // Вкл АЦП|предделитель на 128|разрешаем прерывание
    ADCSRA |= (1<<ADEN)|(1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0) | (1<<ADIE); 
    //AVCC with external capacitor at AREF pin | PC3 init
    ADMUX |= (1<<REFS0)| (1<<MUX1) | (1<<MUX0); 
    // запускаем первое АЦ-преобразование
    ADCSRA |= 1<<ADSC;  
}
 
 
void USART_Init( uint32_t baudrate) //Credits:sharpey
{
    uint16_t trate = ((uint32_t)F_CPU/16/baudrate-1);
    UBRRH = (uint8_t)(trate >> 8);
    UBRRL = (uint8_t)trate;
    // Разрешить передачу и прием, прерывание по приему
    UCSRB = (1<<TXEN) | (1<<RXEN);
    // 8 битный режим, нет проверки на чётность, 1 стоп-бит
    UCSRC = (1<<URSEL) | (1<<UCSZ1) | (1<<UCSZ0);
}
 
void USART_Transmit( unsigned char data )
{
    while ( !( UCSRA & (1<<UDRE)) )
    ;
    UDR = data;
}
void USARTWriteStr(char *data)
{
    while(*data){
        USART_Transmit(*data++);
    }
}
 
ISR(ADC_vect) {//Credits:COKPOWEHEU
    switch(ADMUX & 0b111) {
        case 3:
        ADMUX = (ADMUX &~ 0b111) | 4;
        break;
        case 4:
        ADMUX = (ADMUX &~ 0b111) | 5;
        break;
        case 5:
        ADMUX = (ADMUX &~ 0b111) | 3;
        break;
        default:;
    }
    _delay_us(10);
}
void timer_ini(void){ // Credits:COKPOWEHEU
    TCCR1B = 0;
    TCCR1A = 0;
    
    TCCR1B |= (1<<WGM12); // устанавливаем режим СТС (сброс по совпадению)
    TCCR1B |= (0b101 << CS10);//установим делитель. на 1024
    TIMSK |= (1<<OCIE1A);   //устанавливаем бит разрешения прерывания 1ого счетчика по совпадению с OCR1A
    OCR1A = (2 * F_CPU / 1024 / FREQ_HZ - 1)/2;
}
char buffer[10];
ISR(TIMER1_COMPA_vect){ 
    itoa(ADCW, buffer, 10);
    USARTWriteStr(buffer);
    USART_Transmit(13);
    ADCSRA |= 1<<ADSC;
}
int main(void)
{
    USART_Init(9600);
    ADC_Init();
    timer_ini();
    sei();
    while (1) 
    {
 
    }
}
0
2514 / 1559 / 335
Регистрация: 09.09.2017
Сообщений: 6,301
20.06.2020, 22:04 13
Цитата Сообщение от Lagasy Посмотреть сообщение
C
1
2
3
ISR(ADC_vect) {//Credits:COKPOWEHEU
...
 _delay_us(10);
Это еще зачем?
Упоминания авторства тоже вряд ли нужны - это достаточно стандартные куски кода. Кстати, для UART можете еще глянуть util/setbaud.h Там, правда, все на препроцессоре, но ведь и скорость обмена на лету обычно не меняют.
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
20.06.2020, 22:04

Заказываю контрольные, курсовые, дипломные и любые другие студенческие работы здесь.

ШИМ и АЦП в Atmega8
Мне нужно управлять током полевого транзистора. Для этого я снимаю напряжение с резистора,...

Не читается значение АЦП atmega8
Суть в том,что если крутить ползунок потенциометра RV! то должно менятся значение АЦП от до 1023.А...

Минимальные значения АЦП (Atmega8)
Вобщем в поставленной задаче есть строчка,... показывать минимальное значение результата...

Цепь ОС с использованием ШИМ и АЦП (ATMEGA8)
Доброе утро ! Прошу помощи у более опытных товарищей . В один мой проект необходимо внедрить...

Работа с АЦП (ATMega8, Atmel Studio 6.2)
Есть схема, которая выводит уровень дыма в воздухе, то есть показывает уровень пожарной опасности....

Первое измерение с АЦП как эталон (ATmega8)
Я пишу программу на С++ в АВР студио, в которой идет опрос аналоговых сигналов на мультиплексор, а...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
13
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2020, vBulletin Solutions, Inc.