06-01-2019, 15:33
(Ten post był ostatnio modyfikowany: 06-01-2019, 15:34 przez BourbonKid.)
Dziękuję bardzo za te wskazówki.
Zastosowałem się do nich i aktualnie kod wygląda następująco:
Udało się zlikwidować problem wyjścia z pętli while. Powodem było operowanie przez kompilator zawartością rejestru pamięci, zamiast, zamiast odwoływać się do zawartości komórki pamięci (optymalizacja). Zastosowanie specyfikatora volatile do zmiennych A i B rozwiązało problem. Dodatkowo wydłużyłem ich nazwy do A_enc i B_enc, dzięki czemu teraz można je łatwiej wyszczególnić poprzez ctrl+f.
Jeśli chodzi o oszczędzanie pamięci RAM, to zwróciłem na to uwagę, jednak tablicy "macierz" specjalnie przypisałem typ 16-bit int, ponieważ zapisywane są w niej wartości zliczonych impulsów enkoderów, a więc są to duże wartości przy rozdzielczości 1440 imp/obr. Dodałem nawet znacznik unsigned, aby wydłużyć zakres do 65535, ponieważ wartości te zawsze przyjmują wartości dodatnie. Może okazać się nawet, że przy dłuższych przejechanych odcinkach wartość do zapisania będzie większa niż zakres uint16_t, więc będzie trzeba zastosować inny typ. Oczywiście wszystko w miarę rozsądku, aby zmieścić się w ilości dostępnej pamięci.
Aktualnie sprawa z pamięcią wygląda tak:
"Szkic używa 9852 bajtów (34%) pamięci programu. Maksimum to 28672 bajtów.
Zmienne globalne używają 699 bajtów (27%) pamięci dynamicznej, pozostawiając 1861 bajtów dla zmiennych lokalnych. Maksimum to 2560 bajtów."
Wracając do problemu błędnego odtwarzania drogi powrotu, to podejrzewam jedną przyczynę:
Impulsy z enkoderów są zliczane w podprogramach przerwać zewnętrznych. W danym momencie może wykonywać się tylko jeden podprogram od jednego przerwania, ponieważ na czas procedury obsługi przerwania procesor wyłącza możliwość wystąpienia innych przerwań. Być może poszczególne zbocza z dwóch enkoderów nakładają się w czasie lub są oddalone od siebie o czas krótszy niż czas wykonania procedury przerwania i wtedy zliczenie impulsu z drugiego enkodera zostaje pominięte? Tylko jak taką sytuację wyeliminować?
P.S. Uśrednianie pomiaru napięcia wykonam w późniejszym czasie, ponieważ nie jest to dla mnie aktualnie priorytetem. Jednak na pewno to poprawię w swoim czasie
Zastosowałem się do nich i aktualnie kod wygląda następująco:
Kod:
#define B_ENC 7 //enkoder silnika A
#define A_ENC 3 //enkoder silnika B
#define STDBY 4 //uśpienie sterownika silników
#define ALR 5 //sygnał alarmu
#define A_IN1 2 //zmiana kierunku obrotów silnika prawego
#define A_IN2 6 //
#define A_PWM 9 //wyjście PWM na silnik prawy
#define B_IN1 8 //zmiana kierunku obrotów silnika prawego
#define B_IN2 11 //
#define B_PWM 10 //wyjście PWM na silnik lewy
volatile int A_enc = 0; // impulsy enkodera silnika A
volatile int B_enc = 0; // impulsy enkodera silnika B
int line = 0; //numer wiersza macierzy (krok wykonania ruchu powrotnego)
unsigned int macierz [40][4] = {}; //tablica danych trasy przejazdu
int Speed1 = 120; //prędkość obrotów silników w trybie normalnym
int Speed2 = 65; //prędkość obrotów silników w trybie nauki
int Speed3 = 65; //prędkość obrotów silników w trybie powrotu
const int w_zad = 0; //wartość zadana
const int k_p = 3; //współczynnik wzmocnienia członu P regulatora
const int k_d = 1; //stała różniczkowania regulatora
volatile int p; //wartość wyjściowa członu typu P
volatile int d; //wartość wyjściowa członu typu D
volatile int u; //aktualny uchyb regulacji
volatile int u_p = 0; //poprzedni uchyb regulacji
volatile int out; // sygnał wyjściowy regulatora
volatile int diff = 0; //różnica wskazań enkoderów
volatile int x; // regulowana wartość PWM A
volatile int y; // regulowana wartość PWM B
boolean Forward = 0; //znacznik jazdy do przodu
boolean Backward = 0; //znacznik jazdy do tyłu
boolean Left = 0; //znacznik jazdy w lewo
boolean Right = 0; //znacznik jazdy w prawo
char state = 0; //odczyt z bluetooth(SerialPort1)
int meas = 0; // wartość odczytana z ADC
float volt; // obliczona wartość napięcia
void setup() {
pinMode(A_ENC, INPUT_PULLUP); //kofiguracja wejść i wyjść
pinMode(B_ENC, INPUT_PULLUP);
pinMode(ALR, OUTPUT);
pinMode(STDBY, OUTPUT);
pinMode(A_IN1, OUTPUT);
pinMode(A_IN2, OUTPUT);
pinMode(A_PWM, OUTPUT);
pinMode(B_IN1, OUTPUT);
pinMode(B_IN2, OUTPUT);
pinMode(B_PWM, OUTPUT);
Serial1.begin(9600); //włączenie transmisji szeregowej i ustawienie prędkości
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(A_ENC), COUNT_A, CHANGE); // włączenie przerwań zewnętrznych
attachInterrupt(digitalPinToInterrupt(B_ENC), COUNT_B, CHANGE);
}
void loop() {
meas = analogRead(A0); // pomiar napięcia baterii
volt = meas * (0.0097752); // przeliczenie na wartość dziesiętną
Serial1.print(" "); // wysłanie wartości przez bluetooth
Serial1.print(volt); //
while (volt < 5.0){ //kontrola napięcia
digitalWrite(ALR, !digitalRead(ALR)); // napięcie poniżej 5V włącza alarm
Stop();
delay(500);
}
digitalWrite(ALR, LOW); // wyłączenie alarmu
if (state == '0') { // czyszczenie odczytów z enkoderów
clearenc();
}
if (Serial1.available()) { // odczyt danych z bluetooth
state = Serial1.read(); // przypisanie odebranej danej do zmiennej
}
switch (state) {
case '0': //stop
Stop();
clearenc();
break;
case '1': //
forward(Speed1); //do przodu
break;
case '2':
backward(Speed1); //do tyłu
break;
case '3':
left(Speed1); //w lewo
break;
case '4':
right(Speed1); //w prawo
break;
case '5': // nauka
clearenc();
break;
case '6': // powrót
for (char i = 39; i >= 0; i--) {
clearenc();
if (macierz[i][0] > 0) {
while (A_enc < macierz[i][0]) { //do tyłu
backward(Speed3);
}
Stop();
macierz[i][0] = 0;
}
else if (macierz[i][1] > 0) { // do przodu
while (A_enc < macierz[i][1]) {
forward(Speed3);
}
Stop();
macierz[i][1] = 0;
}
else if (macierz[i][2] > 0) { // w prawo
while (A_enc < macierz[i][2]) {
right(Speed3);
}
Stop();
macierz[i][2] = 0;
}
else if (macierz[i][3] > 0) { // w lewo
while (A_enc < macierz[i][3]) {
left(Speed3);
}
Stop();
macierz[i][3] = 0;
}
}
line = 0;
break;
case '7': //do przodu w trybie nauki
forward(Speed2);
Forward = 1;
break;
case '8': //do tyłu w trybie nauki
backward(Speed2);
Backward = 1;
break;
case '9': //w lewo w trybie nauki
left(Speed2);
Left = 1;
break;
case 'A': //w prawo w trybie nauki
right(Speed2);
Right = 1;
break;
case 'B':
Stop(); // zatrzymanie w trybie nauki + zapamiętanie przejechanej trasy
if (Forward == 1) { //zapisanie drogi do przodu
macierz [line] [0] = A_enc;
}
else if (Backward == 1) { //zapisanie drogi do tyłu
macierz [line] [1] = A_enc;
}
else if (Left == 1) { //zapisanie obrotu w lewo
macierz [line] [2] = A_enc;
}
else if (Right == 1) { //zapisanie obrotu w prawo
macierz [line] [3] = A_enc;
}
line ++;
Forward = 0;
Backward = 0;
Left = 0;
Right = 0;
clearenc();
break;
}
}
void COUNT_A() { //zliczanie impulsów ennkodera silnika A
A_enc++;
}
void COUNT_B() { //zliczanie impulsów ennkodera silnika B
B_enc++;
}
void forward(int Speed) { //funkcja jazdy do przodu + synchronizacja prędkości obrotowej silników
diff = A_enc - B_enc;
u = w_zad - diff;
p = k_p * u;
d = k_d * (u_p - u);
out = p + d;
u_p = u;
x = Speed - out;
y = Speed + out;
if(x > 255){
x = 255;
}else if(x < 35){
x = 0;
}
if(y > 255){
y = 255;
}else if(y < 35){
y = 0;
}
digitalWrite(A_IN1, HIGH);
digitalWrite(A_IN2, LOW);
digitalWrite(B_IN1, LOW);
digitalWrite(B_IN2, HIGH);
analogWrite(B_PWM, x);
analogWrite(A_PWM, y);
digitalWrite(STDBY, HIGH);
}
void backward(int Speed) { //funkcja jazdy do tyłu + synchronizacja prędkości obrotowej silników
diff = A_enc - B_enc;
u = w_zad - diff;
p = k_p * u;
d = k_d * (u_p - u);
out = p + d;
u_p = u;
x = Speed - out;
y = Speed + out;
if(x > 255){
x = 255;
}else if(x < 35){
x = 0;
}
if(y > 255){
y = 255;
}else if(y < 35){
y = 0;
}
digitalWrite(A_IN1, LOW);
digitalWrite(A_IN2, HIGH);
digitalWrite(B_IN1, HIGH);
digitalWrite(B_IN2, LOW);
analogWrite(B_PWM, x);
analogWrite(A_PWM, y);
digitalWrite(STDBY, HIGH);
}
void left(int Speed) { //funkcja jazdy w lewo + synchronizacja prędkości obrotowej silników
diff = A_enc - B_enc;
u = w_zad - diff;
p = k_p * u;
d = k_d * (u_p - u);
out = p + d;
u_p = u;
x = Speed - out;
y = Speed + out;
if(x > 255){
x = 255;
}else if(x < 35){
x = 0;
}
if(y > 255){
y = 255;
}else if(y < 35){
y = 0;
}
digitalWrite(A_IN1, HIGH);
digitalWrite(A_IN2, LOW);
digitalWrite(B_IN1, HIGH);
digitalWrite(B_IN2, LOW);
analogWrite(B_PWM, x);
analogWrite(A_PWM, y);
digitalWrite(STDBY, HIGH);
}
void right(int Speed) { //funkcja jazdy w prawo + synchronizacja prędkości obrotowej silników
diff = A_enc - B_enc;
u = w_zad - diff;
p = k_p * u;
d = k_d * (u_p - u);
out = p + d;
u_p = u;
x = Speed - out;
y = Speed + out;
if(x > 255){
x = 255;
}else if(x < 35){
x = 0;
}
if(y > 255){
y = 255;
}else if(y < 35){
y = 0;
}
digitalWrite(A_IN1, LOW);
digitalWrite(A_IN2, HIGH);
digitalWrite(B_IN1, LOW);
digitalWrite(B_IN2, HIGH);
analogWrite(B_PWM, x);
analogWrite(A_PWM, y);
digitalWrite(STDBY, HIGH);
}
void Stop() {
digitalWrite(A_IN1, HIGH); // stop
digitalWrite(A_IN2, HIGH);
digitalWrite(B_IN1, HIGH);
digitalWrite(B_IN2, HIGH);
analogWrite(A_PWM, 0);
analogWrite(B_PWM, 0);
delay(100);
digitalWrite(STDBY, LOW);
}
void clearenc() { //funkcja czyszczenia odczytów z enkoderów
A_enc = 0;
B_enc = 0;
}
Udało się zlikwidować problem wyjścia z pętli while. Powodem było operowanie przez kompilator zawartością rejestru pamięci, zamiast, zamiast odwoływać się do zawartości komórki pamięci (optymalizacja). Zastosowanie specyfikatora volatile do zmiennych A i B rozwiązało problem. Dodatkowo wydłużyłem ich nazwy do A_enc i B_enc, dzięki czemu teraz można je łatwiej wyszczególnić poprzez ctrl+f.
Jeśli chodzi o oszczędzanie pamięci RAM, to zwróciłem na to uwagę, jednak tablicy "macierz" specjalnie przypisałem typ 16-bit int, ponieważ zapisywane są w niej wartości zliczonych impulsów enkoderów, a więc są to duże wartości przy rozdzielczości 1440 imp/obr. Dodałem nawet znacznik unsigned, aby wydłużyć zakres do 65535, ponieważ wartości te zawsze przyjmują wartości dodatnie. Może okazać się nawet, że przy dłuższych przejechanych odcinkach wartość do zapisania będzie większa niż zakres uint16_t, więc będzie trzeba zastosować inny typ. Oczywiście wszystko w miarę rozsądku, aby zmieścić się w ilości dostępnej pamięci.
Aktualnie sprawa z pamięcią wygląda tak:
"Szkic używa 9852 bajtów (34%) pamięci programu. Maksimum to 28672 bajtów.
Zmienne globalne używają 699 bajtów (27%) pamięci dynamicznej, pozostawiając 1861 bajtów dla zmiennych lokalnych. Maksimum to 2560 bajtów."
Wracając do problemu błędnego odtwarzania drogi powrotu, to podejrzewam jedną przyczynę:
Impulsy z enkoderów są zliczane w podprogramach przerwać zewnętrznych. W danym momencie może wykonywać się tylko jeden podprogram od jednego przerwania, ponieważ na czas procedury obsługi przerwania procesor wyłącza możliwość wystąpienia innych przerwań. Być może poszczególne zbocza z dwóch enkoderów nakładają się w czasie lub są oddalone od siebie o czas krótszy niż czas wykonania procedury przerwania i wtedy zliczenie impulsu z drugiego enkodera zostaje pominięte? Tylko jak taką sytuację wyeliminować?
P.S. Uśrednianie pomiaru napięcia wykonam w późniejszym czasie, ponieważ nie jest to dla mnie aktualnie priorytetem. Jednak na pewno to poprawię w swoim czasie