PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Raspi mit C/C++: Encoder per pigpio und callback auslesen?



HaWe
11.11.2015, 16:57
hallo,
ich komme nicht weiter:

ich will 2 Rotationsencoder auslesen, dazu bräuchte ich eigentlich einen Timer-IRQ mit 100µs Frequenz.
Das gibt es aber nicht auf dem Raspi.
Andere Versuche sind gescheitert, da zu langsam:

http://www.mindstormsforum.de/viewtopic.php?f=78&t=8689&start=15#p68001

wie richte ich jetzt für meine 2 Encoder eine Callback-Funktion mit Pinchange-Interrupts ein, wie sie von "Joan" vorgeschlagen werden?
Mir ist der Code ein einziges Rätsel:

https://github.com/joan2937/pigpio/tree/master/EXAMPLES/C/ROTARY_ENCODER
bzw.
https://github.com/joan2937/pigpio/tree/master/EXAMPLES/CPP/ROTARY_ENCODER


meine Pinbelegung ist diese:


// motor 0
#define pigpio0A 23 // enc 0 A
#define pigpio0B 24 // enc 0 B

// motor 1
#define pigpio1A 25 // enc 1 A
#define pigpio1B 4 // enc 1 B

und meine 2 Encoderwerte werden in diesem Array gespeichert:


volatile static long motenc[2];


wer kann "Joan's" Code darauf anpassen helfen?

Mxt
11.11.2015, 17:16
Hallo,

siehe Datei "test_rotary_encoder.cpp".

Du brauchst wohl zwei Instanzen der Klasse und zwei Callbackfunktionen, etwa so


void callback1(int way)
{
static int pos = 0;

pos += way;

std::cout << "pos1=" << pos << std::endl;
}

void callback2(int way)
{
static int pos = 0;

pos += way;

std::cout << "pos2=" << pos << std::endl;
}

int main(int argc, char *argv[])
{
if (gpioInitialise() < 0) return 1;

re_decoder dec1(23, 24, callback1);
re_decoder dec2(25, 4, callback2);

// ...

HaWe
11.11.2015, 17:58
ach soooo - das mit den 2 Instanzen war mir bsolut nicht klar!

Danke, das probiere ich sofort mal aus!

was bedeutet dieser Teil...?


Pi_Renc_t * renc;


sleep(3000);
dec.re_cancel();

in



int main(int argc, char *argv[])

Pi_Renc_t * renc;

if (gpioInitialise() < 0) return 1;

renc = Pi_Renc(7, 8, callback);

sleep(300);

Pi_Renc_cancel(renc);

gpioTerminate();

}

- - - Aktualisiert - - -

und am Anfang was ist das: (ich nehme den C code, nicht cpp)

Pi_Renc_t * renc;

Mxt
11.11.2015, 17:59
Eigentlich ist das schlechtes C++, guter Stil wäre es, das in einem Destruktor zu machen. (Google mal nach RAII)

Im Konstruktor von re_decoder registriert er _pulseEx irgendwo in der Library

gpioSetAlertFuncEx(gpioA, _pulseEx, this);
wobei in _pulseEx

mySelf->_pulse(gpio, level, tick);
aufgerufen wird und dort der Callback auftaucht, z.B.

if (levB) (mycallback)(1);

D.h innerhalb des Librarycodes wird irgendwo der Callback aufgerufen, wahrscheinlich bei jeder Änderung des Pins.

Am Ende des Programms muss der Callback wieder abgeschaltet werden, das macht er in re_cancel()

gpioSetAlertFuncEx(mygpioA, 0, this);
dort übergibt er einen Nullzeiger als neuen Callback.

HaWe
11.11.2015, 19:59
ich kriegs nicht hin.

gefühlte 50 Seiten Error Meldungen.


Ich brauche echt jemanden, der mir den kompletten (!) C++ Code schreibt, für 2 unabhängige Rotationencoder.

- - - Aktualisiert - - -


// encoder test
// pigpio, pinchange IRQs

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <errno.h>
#include <pthread.h>
#include <termios.h>
#include <iostream>
#include <pigpio.h>

#include "VG/openvg.h"
#include "VG/vgu.h"
#include "fontinfo.h"
#include "shapes.h"




#define byte uint8_t;

#define MAXMOTORS 2 // max number of encoder motors at Arduino Uno=2 // Due=6 // Mega=8


// motor 0


#define pigpio0A 23
#define pigpio0B 24

#define pigpio0d1 11
#define pigpio0d2 14
#define pigpio0pwm 15


// motor 1


#define pigpio1A 25
#define pigpio1B 4

#define pigpio1d1 28
#define pigpio1d2 29
#define pigpio1pwm 30


volatile long motenc[MAXMOTORS],
oldenc[MAXMOTORS] ;




/************************************************** ***********
* Interrupt Handler Routine
************************************************** ***********/

typedef void (*re_decoderCB_t)(int);
class re_decoder
{
int mygpioA, mygpioB, levA, levB, lastGpio;
re_decoderCB_t mycallback;
void _pulse(int gpio, int level, uint32_t tick);
/* Need a static callback to link with C. */
static void _pulseEx(int gpio, int level, uint32_t tick, void *user);

public:
re_decoder(int gpioA, int gpioB, re_decoderCB_t callback);
/*
This function establishes a rotary encoder on gpioA and gpioB.
When the encoder is turned the callback function is called.
*/
void re_cancel(void);
/*
This function releases the resources used by the decoder.
*/
};




void re_decoder::_pulse(int gpio, int level, uint32_t tick)
{
if (gpio == mygpioA) levA = level; else levB = level;

if (gpio != lastGpio) /* debounce */
{
lastGpio = gpio;

if ((gpio == mygpioA) && (level == 1))
{
if (levB) (mycallback)(1);
}
else if ((gpio == mygpioB) && (level == 1))
{
if (levA) (mycallback)(-1);
}
}
}

void re_decoder::_pulseEx(int gpio, int level, uint32_t tick, void *user)
{
/*
Need a static callback to link with C.
*/

re_decoder *mySelf = (re_decoder *) user;

mySelf->_pulse(gpio, level, tick); /* Call the instance callback. */
}

re_decoder::re_decoder(int gpioA, int gpioB, re_decoderCB_t callback)
{
mygpioA = gpioA;
mygpioB = gpioB;

mycallback = callback;

levA=0;
levB=0;

lastGpio = -1;

gpioSetMode(gpioA, PI_INPUT);
gpioSetMode(gpioB, PI_INPUT);

/* pull up is needed as encoder common is grounded */

gpioSetPullUpDown(gpioA, PI_PUD_UP);
gpioSetPullUpDown(gpioB, PI_PUD_UP);

/* monitor encoder level changes */

gpioSetAlertFuncEx(gpioA, _pulseEx, this);
gpioSetAlertFuncEx(gpioB, _pulseEx, this);
}



void re_decoder::re_cancel(void)
{
gpioSetAlertFuncEx(mygpioA, 0, this);
gpioSetAlertFuncEx(mygpioB, 0, this);
}








void setup() {
int i;

// motor pin settings
// setup for L293D motor driver
// pullup resistors:
// wiringPi: pullUpDnControl (butpin, PUD_UP);
// pigpio: gpioSetPullUpDown(butpin, PI_PUD_UP);


// motor 0
gpioSetMode(pigpio0A, PI_INPUT); // 4 enc0A yellow
gpioSetPullUpDown(pigpio0A, PI_PUD_UP);
gpioSetMode(pigpio0B, PI_INPUT); // 5 enc0B blue
gpioSetPullUpDown(pigpio0B, PI_PUD_UP);

gpioSetMode(pigpio0d1, PI_OUTPUT); // dir0-1
gpioSetMode(pigpio0d2, PI_OUTPUT); // dir0-2
gpioSetMode(pigpio0pwm, PI_OUTPUT); // enable0

// motor 1
gpioSetMode(pigpio1A, PI_INPUT); // 6 enc1A yellow
gpioSetPullUpDown(pigpio1A, PI_PUD_UP);
gpioSetMode(pigpio1B, PI_INPUT); // 7 enc1B blue
gpioSetPullUpDown(pigpio1B, PI_PUD_UP);

gpioSetMode(pigpio1d1, PI_OUTPUT); // dir1-1
gpioSetMode(pigpio1d2, PI_OUTPUT); // dir1-2
gpioSetMode(pigpio1pwm, PI_OUTPUT); // enable1

for( i=0; i< MAXMOTORS; ++i) motenc[i] = 0;

}







void callback0(int way){
static int pos = 0;
pos += way;
std::cout << "pos=" << pos << std::endl;
}


void callback1(int way){
static int pos = 0;
pos += way;
std::cout << "pos=" << pos << std::endl;
}



int main(int argc, char *argv[])
{
if (gpioInitialise() < 0) return 1;

setup();

re_decoder dec0(pigpio0A, pigpio0B, callback0);
re_decoder dec1(pigpio1A, pigpio1B, callback1);



sleep(3000);

dec0.re_cancel();
dec1.re_cancel();

gpioTerminate();


}



g++ -Wall -I/opt/vc/include -I/opt/vc/include/interface/vmcs_host/linux -I/opt/vc/include/interface/vcos/pthreads -o "enc0005" "enc0005.c" -lshapes -L/opt/vc/lib -lOpenVG -lEGL -pthread -lrt -lwiringPi (im Verzeichnis: /home/pi/programs/test/encoder)
Kompilierung fehlgeschlagen.
/tmp/ccHKEpu6.o: In function `re_decoder::re_decoder(int, int, void (*)(int))':
enc0005.c:(.text+0x1ac): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x1bc): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x1cc): undefined reference to `gpioSetPullUpDown'
enc0005.c:(.text+0x1dc): undefined reference to `gpioSetPullUpDown'
enc0005.c:(.text+0x1f0): undefined reference to `gpioSetAlertFuncEx'
enc0005.c:(.text+0x204): undefined reference to `gpioSetAlertFuncEx'
/tmp/ccHKEpu6.o: In function `re_decoder::re_cancel()':
enc0005.c:(.text+0x240): undefined reference to `gpioSetAlertFuncEx'
enc0005.c:(.text+0x258): undefined reference to `gpioSetAlertFuncEx'
/tmp/ccHKEpu6.o: In function `setup()':
enc0005.c:(.text+0x278): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x284): undefined reference to `gpioSetPullUpDown'
enc0005.c:(.text+0x290): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x29c): undefined reference to `gpioSetPullUpDown'
enc0005.c:(.text+0x2a8): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x2b4): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x2c0): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x2cc): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x2d8): undefined reference to `gpioSetPullUpDown'
enc0005.c:(.text+0x2e4): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x2f0): undefined reference to `gpioSetPullUpDown'
enc0005.c:(.text+0x2fc): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x308): undefined reference to `gpioSetMode'
enc0005.c:(.text+0x314): undefined reference to `gpioSetMode'
/tmp/ccHKEpu6.o: In function `main':
enc0005.c:(.text+0x454): undefined reference to `gpioInitialise'
enc0005.c:(.text+0x4c8): undefined reference to `gpioTerminate'
collect2: error: ld returned 1 exit status

Mxt
12.11.2015, 07:52
Sieht so aus, als ob da ein include fehlt, wahrscheinlich

#include <pigpio.h>

Außerdem sehe ich da ein

-lwiringPi
wo sicher ein

-lpigpio
hingehört.

Ansonsten kenne ich mit pigpio nicht aus, Linux GPIOs stehen auf meiner Prioritätenliste weit unten.

HaWe
12.11.2015, 08:19
ja, stimmt, aber ich glaube, das war jetzt der Tropfen, der das Fass zum Überlaufen gebracht hat.
Ich hasse diese tonnenweisen -l und -I Parameter für compile, make und build,
dann dieser unglaublich langsame Bildschirmaufbau beim Raspi-Onboard-Programmieren,
dann dass es keine Windows-IDE mit Crosscompiler gibt wie bei der Arduino IDE,
dann die unglaublich - viel zu - wenigen Pins für IO, ADC, DAC, UART, I2C,
keine Interrupts auf Hardware-level,
und zu allem Überfluss noch nicht mal 2 pins mit PWM vorhanden.

Ich lass es bleiben mit dem Raspi, es bringt nichts außer Ärger und Frust.


Da kann ja ein Uno schon mehr.

peterfido
13.11.2015, 16:08
Da gebe ich Dir Recht. C zu kompilieren ist nicht immer nur gcc aufzurufen. Ich mag das auch nicht sonderlich, klappt aber bei meinen kleinen Projekten. Wo es auf Echtzeit ankommt, nutze ich immer einen AVR als 'Co-Prozessor'. Der kann Drehencoder, WS2812B LEDs und Interrupts problemlos. Die Kommunikation ist dann per UART zum Raspi. Der Raspi selbst ist für das Netzwerk und die Anbindung an USB-Hardware zuständig. Dafür sind die AVRs einfach zu lahm / ressourcenarm. Der große Vorteil ist, dass man den AVR bequem übers Netzwerk per Raspi flashen kann.

HaWe
13.01.2016, 15:38
update:
hatte es dann doch hingekriegt, dass es wenigstens läuft (B+).
A-Bär: wenn man normal schnell dreht (3 U/s), dann werden pro 360° nur 300 encoder ticks gelesen, also werden jede Menge übersehen und daher verschluckt.
Wenn man es nicht über Pinchange implementiert, sondern als Extra-pthread-Task im 100µs-Takt, klappt es noch schlechter (beim Arduino klappen sogar 8 Motoren im 200µs-Timer-IRQ-Takt!)

wenn man sehr langsam dreht (1U pro 2 sec), dann liest er korrekt alle 360 ticks.
Der Raspi ist also tatsächlich nicht in der Lage, schnelle Pin-Lese-Vorgänge durchzuführen.

neu:

Ich habe jetzt einen Raspi 2B Quadcore und werde es nochmals testen -
diesmal aber nicht per pinchange-Interrupt, denn da ändert sich ja nichts, sondern wieder als pthread-Task im 100µs-Takt und mit pthread-priority von 90%.
Ich habe nämlich festgestellt, dass das Betriebssystem und Posix die Tasks sehr geschickt auf die cpu-Kerne verteilen, daher habe ich die Hoffnung, dass einer der cores bevorzugt nur mit diesem 90% prio-Task beschäftigt sein wird, um wenigstens halbwegs ein taktgenaues Arbeiten zu ermöglichen (ohne wieder von user space tasks ausgebremst zu werden).

Ich werde berichten!

HaWe
17.02.2016, 20:09
update:

mit Raspi 2B klappt es! Keine Zähl-Ausfälle mehr, selbst bei recht schnellen Umdrehungen, getestet mit Lego-Encodermotoren
(Auflösung: 1/2, d.h. 360 ticks/360°. Drehgeschwindigkeit 5 U/sec.).


Es wird über einen High Priority pthread Task ein Timer-Interrupt im 100µs Takt simuliert, und der Pi Quadcore hat jetzt die Performance, dies auch im Linux User Space sicher durchzuhalten - die Simulation verwendet dazu bisher insgesamt 4 Tasks mit unterschiedlicher Priorität:

31315

Hier der Code :

share and enjoy! :cool:




// encoder test
// wiringPi,
// Encoder Timer High Priority Thread
// ver 0007

// protected under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
// http://creativecommons.org/licenses/by-nc-sa/3.0/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <errno.h>
#include <pthread.h>
#include <termios.h>

#include "VG/openvg.h"
#include "VG/vgu.h"
#include "fontinfo.h"
#include "shapes.h"

#include <wiringPi.h>
#include <wiringSerial.h>


#define byte uint8_t;

#define MAXMOTORS 2 // max number of encoder motors

typedef struct {
uint8_t pd1, pd2, pwm;
uint8_t pqa, pqb;
int32_t motenc, oldenc;

} tpimotor ;

tpimotor motor[MAXMOTORS];


/************************************************** ***********
* Encoder Handler Routine
************************************************** ***********/

volatile int8_t ISRab[MAXMOTORS];

// 1/2 resolution
int8_t enctab[16] = {0, 0,0,0,1,0,0,-1, 0,0,0,1,0,0,-1,0};

void updateEncoders() {
int i;
for( i=0; i<MAXMOTORS; ++i ) {
ISRab [ i] <<= 2;
ISRab [ i] &= 0b00001100;
ISRab [ i] |= (digitalRead( motor[ i].pqa ) << 1) | digitalRead( motor[ i].pqb );
motor[ i].motenc += enctab[ ISRab[ i] ];
}

}




void* thread3Go(void *) // encoder high priority thread
{
while(1) {
updateEncoders();
usleep(100);
}
return NULL;
}

void* thread2Go(void *)
{
while(1) {
delay(10);
}
return NULL;
}

void* thread1Go(void *) // low priority display thread
{
char sbuf[128];
while(1) {
delay(100);
}
return NULL;
}

void* thread0Go(void *)
{
char sbuf[128];
while(1) {
sprintf(sbuf, " 0=%6ld 1=%6ld \n ", motor[0].motenc, motor[1].motenc );
printf(sbuf);
delay(100);
}
return NULL;
}




void setup() {
int i;

// motor pins, wiringPi numbering (in parenthesis: BCM numbering)

motor[0].pinQa = 5; // (BCM 24) change for rotation direction
motor[0].pinQb = 4; // (BCM 23) change for rotation direction
motor[0].pind1 =24; // (BCM 19)
motor[0].pind2 =25; // (BCM 26)
motor[0].pinpwm= 1; // (BCM 18) hardware pwm

motor[1].pinQa = 0; // (BCM 17) change for rotation direction
motor[1].pinQb = 2; // (BCM 27) change for rotation direction
motor[1].pind1 =21; // (BCM 5)
motor[1].pind2 =22; // (BCM 6)
motor[1].pinpwm=23; // (BCM 13) hardware pwm


for( i=0; i< MAXMOTORS; ++i) {
pinMode(motor[i].pqa, INPUT); // encA
pinMode(motor[i].pqb, INPUT); // encB
pinMode(motor[i].pd1, OUTPUT); // dir-1
pinMode(motor[i].pd2, OUTPUT); // dir-2
pinMode(motor[i].pwm ,OUTPUT); // pwm

motor[i].motenc = 0;
motor[i].oldenc = 0;
ISRab[i] = 0;
}
}





int main() {
char sbuf[128];
pthread_t thread0, thread1, thread2, thread3;

wiringPiSetup();
if(wiringPiSetup() == -1) return 1;

setup();

struct sched_param param;


pthread_create(&thread0, NULL, thread0Go, NULL);
param.sched_priority = 10;
pthread_setschedparam(thread0, SCHED_RR, &param);

pthread_create(&thread1, NULL, thread1Go, NULL);
param.sched_priority = 25;
pthread_setschedparam(thread1, SCHED_RR, &param);

pthread_create(&thread2, NULL, thread2Go, NULL);
param.sched_priority = 50;
pthread_setschedparam(thread2, SCHED_RR, &param);

pthread_create(&thread3, NULL, thread3Go, NULL);
param.sched_priority = 90;
pthread_setschedparam(thread3, SCHED_RR, &param);


pthread_join(thread0, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join(thread3, NULL);

exit(0);

}




# build:
g++ -Wall -I/opt/vc/include -I/opt/vc/include/interface/vmcs_host/linux -I/opt/vc/include/interface/vcos/pthreads -o "%e" "%f" -pthread -lshapes -L/opt/vc/lib -lOpenVG -lEGL -lrt -lwiringPi -lpigpio