Forum: Mikrocontroller und Digitale Elektronik Ultraschall-Sensor Firmware funktioniert nicht


von Markus B. (markus_b95)


Lesenswert?

Nun, da mir bereits bei der Schaltung an sich viel geholfen wurde hier 
im Forum, versuche ich nun auch mit der Software mein Glück hier :)

Konkret geht es um folgendes: An einem, mit 16MHz laufenden ATMega16 
hängt eine Platine, die vom Pin PD5 vom µC mit Hilfe des 8-Bit-Timers 
ein 40kHz Rechteck-Signal bekommt, das zum Senden benutzt wird, 
jedenfalls, das funktioniert alles.

Das Funktionskonzept an sich ist, das ich einen Ultraschall-Impuls 
erzeuge durch definiertes auf Ausgang-/Eingang-Schalten des Pins.
Das funktioniert ebenfalls wie es soll.

Das vom Ultraschall-Empfänger empfangene Signal wird verstärkt und durch 
einen Kondensator zu GND hin geglättet, das verstärkte Signal an sich 
ist zwar nicht ideal (Oberwellen etc.), aber sollte schon mal 
einigermaßen als reflektiertes Signal erkennbar sein.

Nun zu meinem Problem: Ich habe unten folgende Software geschrieben und, 
nun ja, sie funktioniert scheinbar hinten und vorne nicht, bis auf das 
die grüne Status-LED leuchtet wie sie soll passiert absolut nichts, 
keine Reaktion auf Objekte vor dem Sensor :(

Wie aus dem Code ersichtlich ist, wird das Empfangssignal abgetastet mit 
dem ADC, ein Mittelwert gebildet über eine definierte Anzahl an 
Messungen, und sobald dieser einen definierten Schwellenwert 
überschreitet der 16-Bit-Timer angehalten, dessen Zählerstand abgelesen 
u. resettet, in eine Zeit umgerechnet und daraus wiederum der Abstand 
errechnet.

Ich bin noch relativ neu in der ganzen µC-Programmierung, ich kann mir 
gut vorstellen das der Code zum "Hände-über-den-Kopf-zusammenschlagen" 
für die Profis hier ist, aber ich mache das ja eben um zu lernen :)

Was folgt ist der AVR-GCC-Code, momentan besteht dieser nur aus einer 
Datei, später werde ich die eine oder andere Funktion in eine seperate 
Datei 'auslagern'
1
/*
2
 */
3
#ifndef F_CPU
4
#define F_CPU = 16000000UL
5
#endif
6
7
#include <avr/io.h>
8
#include <avr/interrupt.h>
9
#include <util/delay.h>
10
#include <avr/iom16.h>
11
#include <stdint.h>
12
13
//Variablen
14
uint16_t timer1 = 0;
15
char pulse_received = 0;
16
int pulselenght = 4; //Zahl der Perioden
17
int messungen = 5; //Zahl der Messungen des ADCs bevor das Ergebnis ausgewertet wird
18
unsigned long i = 0;
19
char nothing_received = 0;
20
uint32_t result = 0;
21
int z = 0;
22
unsigned long grenzwert = 50000ULL;
23
unsigned long timeout = 500000ULL;
24
uint8_t distance = 0;
25
uint16_t runtime = 0;
26
27
28
//Funktionsprototypen
29
void ultrasonic(int a);
30
uint16_t time_meassurement(void);
31
void init_adc(void);
32
uint16_t adc_single_conversion(void);
33
uint16_t adc_average_conversion(int);
34
void led_ausgabe(uint8_t z);
35
void enable_led_portc(int y);
36
37
38
39
int main(void)
40
{
41
42
    //Grüne Status-LED anschalten
43
    DDRD |= (1<<DDD1);
44
    PORTD |= (1<<PD1);
45
    DDRC = 0xFF;
46
    init_adc();
47
48
49
/* Timer Konfiguration:
50
Target Timer Count = (1 / Target Frequency) / (1 / Timer Clock Frequency) - 1
51
=> Target Timer Count = 1/80.000 Hz (T/2) / (1/16.000.000 Hz -1
52
    = 199
53
Following up: Register-configuration, CTC-modus with top-value of 199 */
54
55
  // Timer 0 konfigurieren (8-Bit-Timer)
56
  TCCR0 = (1<<WGM01); // CTC Modus
57
  TCCR0 |= (1<<CS00); // CPU-Takt,d.h. prescaler = 1
58
  OCR0 = 199;
59
60
  // Compare Interrupt erlauben und Zähler starten
61
  TIMSK |= (1<<OCIE0);
62
63
  // Global Interrupts aktivieren
64
  sei();
65
66
67
68
    while(1)
69
    {
70
            ultrasonic(pulselenght *2 ); //Multiplikation mit 2, da in einer Periode 2x getoggelt werden muss
71
            _delay_ms(1000); //Verzögerung zwischen den Pulsen
72
            //Ausschalten für permanentes Pulsen zum Messen des Empfangsignals*/
73
74
    }
75
76
    return 0;
77
}
78
79
void ultrasonic(a)
80
{
81
82
    //PD5 als Ausgang
83
    DDRD |= (1<< DDD5);
84
    _delay_ms(a);
85
    DDRD &= ~(1<<DDD5); //ebenfalls für permanentes Senden des Signals auszuschalten
86
    if(!nothing_received) timer1 = time_meassurement();
87
88
    /*Umrechnung des Zählerstandes in Sekunden
89
    16 MHz getaktet => 16 Mio. Takte/s
90
    => Zählerstand = Takte
91
    => x Takte bis zum Zählerstand x
92
    Vergangene Zeit = Zählerstand / CPU-Takt
93
    */
94
    runtime = (uint16_t)(timer1 / F_CPU);
95
    distance = 172 * runtime * 100; //Schallgeschwindigkeit bei Raumtemperatur in etwa 343 m/s, geht in die Formel mit 1/2 ein; distance in cm, daher der Faktor 100
96
    led_ausgabe(distance);
97
}
98
99
100
uint16_t time_meassurement(void)
101
{
102
        TCCR1A = 0x00; // Kein PWM oder sonstiges Gedöhns, lediglich als 16-Bit-Timer verwenden
103
        TCCR1B = (1<<CS11); // Timer läuft mit CPU-Takt/8
104
/*
105
Sofern der durchschnittliche Messwert keinen gewissen Schwellwert überschreitet, gilt das als
106
wäre kein Echo erkannt.
107
Weiterhin wird eine Variable i als Zählvariable verwendet, um eine Obergrenze festzulegen, ab
108
wann definitiv kein Echo empfangen wurde.
109
Der Wert 500000 entspricht etwas mehr als berechnete 464.000 Takte die der AVR für 29ms braucht,
110
was in etwa einem Abstand von 5 m entsprechen würde.
111
*/
112
        i = 0;
113
        while(!pulse_received)
114
        {
115
            if(adc_average_conversion(messungen) <= grenzwert)
116
                {
117
                    i++;
118
                    if(i >= timeout) nothing_received = 1;
119
                }
120
            else pulse_received = 1;
121
        }
122
        cli(); //Interrupts deaktivieren
123
        uint16_t b = TCNT1; //Lese den Zählerstand ab
124
        TCCR1B &= ~(1<<CS11); //Timer anhalten
125
        TCNT1 = 0x00; //Timerstand zurücksetzen
126
        sei(); //und wieder die Interrupts aktivieren
127
        return b;
128
}
129
130
void init_adc(void)
131
{
132
    ADMUX |= (1<<REFS1) | (1<<REFS0); //interne 2,56V als Referenz benutzen
133
    ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2); //ADC-Frequenz: 16MHz/128 = 125kHz
134
    ADCSRA |= (1<<ADEN); //Aktiviere den ADC
135
    //Das erste Ergebnis der Wandlung nach dem Initalisieren ist Murks, also:
136
    ADCSRA |= (1<<ADSC); //Führe eine Wandlung durch
137
    while(ADCSRA & (1<<ADSC)) {} //So lange wie das Bit ADSC nicht auf 0, d.h. logisch false zurück springt, tu nix und warte
138
    result = ADCW; //Lese das Ergebnis, nun ist der Wandler bereit
139
}
140
141
uint16_t adc_single_conversion(void)
142
{
143
    ADMUX = 0x00; //Wandlung auf Kanal 0, Pin PA0;
144
    ADCSRA |= (1<<ADSC); //starte Einzelwandlung
145
    while(ADCSRA & (1<<ADSC)) {}
146
    return ADCW;
147
}
148
149
uint16_t adc_average_conversion(int d)
150
{
151
    for(z = 0; z < d; z++)
152
    {
153
        result =+ adc_single_conversion();
154
    }
155
    result = (uint16_t) (result/d);
156
    return result;
157
}
158
159
//Timer-Interrupt-Routine: Wechsel von High auf Low; der Puls kann ausgegeben werden durch Toggeln von DDRD
160
ISR (TIMER0_COMP_vect)
161
{
162
    //PD5 toggeln
163
    PORTD ^= (1 << PD5);
164
}
165
166
void led_ausgabe(uint8_t z)
167
{
168
    if(z <= 20) enable_led_portc(7);
169
    if(z <= 40) enable_led_portc(6);
170
    if(z <= 60) enable_led_portc(5);
171
    if(z <= 80) enable_led_portc(4);
172
    if(z <= 100) enable_led_portc(3);
173
    if(z <= 120) enable_led_portc(2);
174
    if(z <= 140) enable_led_portc(1);
175
    if(z > 140) enable_led_portc(0);
176
}
177
178
void enable_led_portc(int y)
179
{
180
    DDRC = 0xFF;
181
    PORTC = 0x00; //alle LEDs ausschalten zu Beginn
182
    switch(y)
183
    {
184
        case 0 : PORTC = (1<<PC0);break;
185
        case 1 : PORTC = (1<<PC1);break;
186
        case 2 : PORTC = (1<<PC2);break;
187
        case 3 : PORTC = (1<<PC3);break;
188
        case 4 : PORTC = (1<<PC4);break;
189
        case 5 : PORTC = (1<<PC5);break;
190
        case 6 : PORTC = (1<<PC6);break;
191
        case 7 : PORTC = (1<<PC7);break;
192
    }
193
}

von Krapao (Gast)


Lesenswert?

Was mir auf die Schnelle auffällt:

> uint16_t adc_average_conversion(int d)

Result ist nur beim ersten Aufruf initialisiert. result wird besser als 
lokale Variable in der Funktion implementiert. Aufpassen auf Overflow 
beim addieren.

> void enable_led_portc(int y)

Maximal aufwändig implementiert. Besser: statt switch/case nur PORTC = 
(1<<y);

> uint16_t adc_single_conversion(void)
>     ADMUX = 0x00; //Wandlung auf Kanal 0, Pin PA0;

Zerstört die Einstellung

> void init_adc(void)
>    ADMUX |= (1<<REFS1) | (1<<REFS0); //interne 2,56V als Referenz benutzen

> uint16_t time_meassurement(void)
>         while(!pulse_received)

Was setzt pulse_received? volatile Schlüsselwort bei dessen Definition 
fehlt.

> uint8_t distance = 0;
> uint16_t runtime = 0;
>    distance = 172  runtime  100;

Overflow im uint8_t Datentyp.

von Markus B. (markus_b95)


Lesenswert?

Hey!
Danke, das sollte schon mal helfen, aber weiterhin leider keinerlei 
sichtbare Reaktion der LEDs :(

Ich habe soweit versucht deine Anmerkungen 1:1 umzusetzen, der 
veränderte Code folgt unten...

Schätze ich werd wohl später mal nach und nach mir die Variablen via LED 
ausgeben lassen müssen :/
1
/*
2
 */
3
#ifndef F_CPU
4
#define F_CPU = 16000000UL
5
#endif
6
7
#include <avr/io.h>
8
#include <avr/interrupt.h>
9
#include <util/delay.h>
10
#include <avr/iom16.h>
11
#include <stdint.h>
12
13
//Variablen
14
uint16_t timer1 = 0;
15
volatile char pulse_received = 0;
16
int pulselenght = 4; //Zahl der Perioden
17
int messungen = 1; //Zahl der Messungen des ADCs bevor das Ergebnis ausgewertet wird
18
unsigned long i = 0;
19
char nothing_received = 0;
20
uint32_t result = 0;
21
int z = 0;
22
unsigned long grenzwert = 10000ULL;
23
unsigned long timeout = 500000ULL;
24
uint16_t distance = 0;
25
uint16_t runtime = 0;
26
27
28
//Funktionsprototypen
29
void ultrasonic(int a);
30
uint16_t time_meassurement(void);
31
void init_adc(void);
32
uint16_t adc_single_conversion(void);
33
uint16_t adc_average_conversion(int);
34
void led_ausgabe(uint8_t z);
35
void enable_led_portc(int y);
36
37
38
39
int main(void)
40
{
41
42
    //Grüne Status-LED anschalten
43
    DDRD |= (1<<DDD1);
44
    PORTD |= (1<<PD1);
45
    DDRC = 0xFF;
46
    init_adc();
47
48
49
/* Timer Konfiguration:
50
Target Timer Count = (1 / Target Frequency) / (1 / Timer Clock Frequency) - 1
51
=> Target Timer Count = 1/80.000 Hz (T/2) / (1/16.000.000 Hz -1
52
    = 199
53
Following up: Register-configuration, CTC-modus with top-value of 199 */
54
55
  // Timer 0 konfigurieren (8-Bit-Timer)
56
  TCCR0 = (1<<WGM01); // CTC Modus
57
  TCCR0 |= (1<<CS00); // CPU-Takt,d.h. prescaler = 1
58
  OCR0 = 199;
59
60
  // Compare Interrupt erlauben und Zähler starten
61
  TIMSK |= (1<<OCIE0);
62
63
  // Global Interrupts aktivieren
64
  sei();
65
66
67
68
    while(1)
69
    {
70
            ultrasonic(pulselenght *2 ); //Multiplikation mit 2, da in einer Periode 2x getoggelt werden muss
71
            _delay_ms(1000); //Verzögerung zwischen den Pulsen
72
            //Ausschalten für permanentes Pulsen zum Messen des Empfangsignals*/
73
74
    }
75
76
    return 0;
77
}
78
79
void ultrasonic(a)
80
{
81
82
    //PD5 als Ausgang
83
    DDRD |= (1<< DDD5);
84
    _delay_ms(a);
85
    DDRD &= ~(1<<DDD5); //ebenfalls für permanentes Senden des Signals ausgeschaltet
86
    if(!nothing_received) timer1 = time_meassurement();
87
88
    /*Umrechnung des Zählerstandes in Sekunden
89
    16 MHz getaktet => 16 Mio. Takte/s
90
    => Zählerstand = Takte
91
    => x Takte bis zum Zählerstand x
92
    Vergangene Zeit = Zählerstand / CPU-Takt
93
    */
94
    runtime = (uint16_t)(timer1 / F_CPU);
95
    distance = 172 * runtime * 100; //Schallgeschwindigkeit bei Raumtemperatur in etwa 343 m/s, geht in die Formel mit 1/2 ein; distance in cm, daher der Faktor 100
96
    led_ausgabe(distance);
97
}
98
99
100
uint16_t time_meassurement(void)
101
{
102
        TCCR1A = 0x00; // Kein PWM oder sonstiges Gedöhns, lediglich als 16-Bit-Timer verwenden
103
        TCCR1B = (1<<CS11); // Timer läuft mit CPU-Takt/8
104
/*
105
Sofern der durchschnittliche Messwert keinen gewissen Schwellwert überschreitet, gilt das als
106
wäre kein Echo erkannt.
107
Weiterhin wird eine Variable i als Zählvariable verwendet, um eine Obergrenze festzulegen, ab
108
wann definitiv kein Echo empfangen wurde.
109
Der Wert 500000 entspricht etwas mehr als berechnete 464.000 Takte die der AVR für 29ms braucht,
110
was in etwa einem Abstand von 5 m entsprechen würde.
111
*/
112
        i = 0;
113
        while(!pulse_received)
114
        {
115
            if(adc_average_conversion(messungen) <= grenzwert)
116
                {
117
                    i++;
118
                    if(i >= timeout) nothing_received = 1;
119
                }
120
            else pulse_received = 1;
121
        }
122
        cli(); //Interrupts deaktivieren
123
        uint16_t b = TCNT1; //Lese den Zählerstand ab
124
        TCCR1B &= ~(1<<CS11); //Timer anhalten
125
        TCNT1 = 0x00; //Timerstand zurücksetzen
126
        sei(); //und wieder die Interrupts aktivieren
127
        return b;
128
}
129
130
void init_adc(void)
131
{
132
    ADMUX |= (1<<REFS1) | (1<<REFS0); //interne 2,56V als Referenz benutzen
133
    ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2); //ADC-Frequenz: 16MHz/128 = 125kHz
134
    ADCSRA |= (1<<ADEN); //Aktiviere den ADC
135
    //Das erste Ergebnis der Wandlung nach dem Initalisieren ist Murks, also:
136
    ADCSRA |= (1<<ADSC); //Führe eine Wandlung durch
137
    while(ADCSRA & (1<<ADSC)) {} //So lange wie das Bit ADSC nicht auf 0, d.h. logisch false zurück springt, tu nix und warte
138
    result = ADCW; //Lese das Ergebnis, nun ist der Wandler bereit
139
}
140
141
uint16_t adc_single_conversion(void)
142
{
143
    /*
144
    ADMUX = 0x00; //Wandlung auf Kanal 0, Pin PA0;
145
    Nicht notwendig, da ADMUX entsprechend mit Kanal 0 initalisiert wird
146
    */
147
    ADCSRA |= (1<<ADSC); //starte Einzelwandlung
148
    while(ADCSRA & (1<<ADSC)) {}
149
    return ADCW;
150
}
151
152
uint16_t adc_average_conversion(int d)
153
{
154
    result = 0;
155
    for(z = 0; z < d; z++)
156
    {
157
        result =+ adc_single_conversion();
158
    }
159
    result = (uint16_t) (result/d);
160
    return result;
161
}
162
163
//Timer-Interrupt-Routine: Wechsel von High auf Low; der Puls kann ausgegeben werden durch Toggeln von DDRD
164
ISR (TIMER0_COMP_vect)
165
{
166
    //PD5 toggeln
167
    PORTD ^= (1 << PD5);
168
}
169
170
void led_ausgabe(uint8_t z)
171
{
172
    if(z <= 20) enable_led_portc(7);
173
    if(z <= 40) enable_led_portc(6);
174
    if(z <= 60) enable_led_portc(5);
175
    if(z <= 80) enable_led_portc(4);
176
    if(z <= 100) enable_led_portc(3);
177
    if(z <= 120) enable_led_portc(2);
178
    if(z <= 140) enable_led_portc(1);
179
    if(z > 140) enable_led_portc(0);
180
}
181
182
void enable_led_portc(int y)
183
{
184
    DDRC = 0xFF;
185
    PORTC = 0x00; //alle LEDs ausschalten zu Beginn
186
    PORTC = (1<<y);
187
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.