Algorithmus - Verzerrtes Viereck "geradebiegen"

Kurzbeschreibung

Dieser Algorithmus liefert eine nahezu pixelgenaue Positionsangabe für ein Touch-LCD zurück. Nötig wurde dieser Vorgang, da bei einem LCD mit Touch-Screen die X/Y-Koordinaten bzw. deren AD-Werte nicht unabhängig voneinander waren. So änderte sich bei einer Bewegung parallel zur X-Achse auch der Y-Wert. Mit diesem Algorithmus können die X/Y-Koordinaten nun sozusagen "entkoppelt" werden. Zusätzlich ist durch die Angabe der Displayabmessungen eine Umrechnung auf eine pixelgenaue Position inklusive.

"Ausrutscher" mit berechneten Werten ausserhalb der Displaygrösse (Wie sie z.B. bei zu leichtem Druck auf resistive Displays auftreten) können als "invalid" (ungültig) markiert und ignoriert werden.

Thx an mare_crisium vom Roboternetz, der 2008 die mathematische Grundlage dieser Problemlösung ausgegraben hat.

Rot: Ist-Viereck, gemessen durch ADC. Grün: Soll-Viereck mit unabhängigen X/Y-Koordinaten
Rot: Ist-Viereck, gemessen durch ADC. Grün: Soll-Viereck mit unabhängigen X/Y-Koordinaten
Die Punkte R0...R3 werden an ihre korrekte Position "verschoben"
Die Punkte R0...R3 werden an ihre korrekte Position "verschoben"

Erklärung

Um mit dem Algorithmus arbeiten zu können, muss zuerst eine Kalibrierung durchgeführt werden; d.h. man braucht von den 4 Eckpunkten des Displays die ADC-Werte. Wie man zu diesen kommt, bleibt jedem selbst überlassen. Ich hab die ausgelesenen Werte im EEPROM abgelegt und lade sie von dort.

Danach wird die Funktion InitAlgorithm aufgerufen, die die für die Iteration konstant bleibenden Werte berechnet. Dies muss nur 1x getan werden.
Mit GetPositionPrecise erhält man dann die korrigierten Werte.

Die zusätzlichen Zeilen mit delay(5); waren bei der damaligen Hardware (ATmega128) aus noch ungeklärten Gründen nötig. Ohne diese kurzen Pausen kam es zu einer Art Pipeline-Effekten o.ä., die dafür sorgten, dass die (Zwischen-)Ergebnisse falsch waren. Wenn die Pausen für die jeweilige Hardware nicht nötig sind, kann das Define delay(us) einfach als leeres Define angelegt werden:

#define delay(us)

Aktuell sind diese Funktionen nur für LCDs bis max. 255x255 Pixel geeignet. Ein Umbau auf grössere Displays kann aber durch einfaches Vergrössern der 8-Bit-Typen auf 16 Bit bewerkstelligt werden.

Defines

Globale Variablen

Funktionen

InitAlgorithm

Initialisiert und berechnet Werte im Voraus, die während der Berechnung der Punkte konstant bleiben.

Parameter: keine
Rückgabewerte: keine

GetPositionPrecise

Parameter: Rückgabewerte:

Code

// constant defines
#define R0 0
#define R1 1
#define R2 2
#define R3 3
#define X  0
#define Y  1
#define POSITION_INVALID 0
#define POSITION_VALID   1
 
// configuration defines
#define DETERMINANT_MULTIPLICATION
//#define DETERMINANT_DIVISION
 
#define CHECK_LCD_DIMENSIONS  TRUE
#define LCD_WIDTH   128   // Display width in pixels
#define LCD_HEIGHT   64   // Display height in pixels
#define ITERATIONS    5   // Iteration steps for determining the exact position
 
// global variables
int16_t R_Calibrated[4][2]; // holds coordinates of all 4 corners, measured by ADC value.
int16_t r1_r0[2];    // solution vector for R1 - R0
int16_t r3_r0[2];    // solution vector for R3 - R0
int16_t r01_r32[2];  // solution vector for (r0 - r1) - (r3 - r2)
int16_t raw_r0[2];   // solution vector for rraw - r0
int32_t D;           // determinant
double  W = 0;       // Iteration parameter; Start value = 0
double  factor[2];   // solution vector factors
double  solVec[2];   // soluton vector
double  D_XY[2];     // determinant D_X, D_Y
 
#ifdef DETERMINANT_MULTIPLICATION
  double  D_inv;       // 1 / determinant;
#endif
 
#define delay(us) _delay4Cycles(((F_CPU / 4000)*us)/1000)
 
static inline void _delay4Cycles(uint16_t __count)
{
  if (__count == 0)
  {
    __asm__ __volatile__("rjmp 1f\n 1:");
  }
  else
  {
    __asm__ __volatile__ (
      "1: sbiw %0,1" "\n\t"
      "brne 1b"
      : "=w" (__count)
      : "0" (__count)
      );
  }
}
 
void InitAlgorithm(void)
{
  // The used delay-command is needed to minimize pipeline-effects
  // and therefore to prevent calculation errors.
 
  // Constant values, needed for calculation; constant for an iteration:
  // Vector r1-r0
  r1_r0[X] = R_Calibrated[R1][X] - R_Calibrated[R0][X];
  delay(5);
  r1_r0[Y] = R_Calibrated[R1][Y] - R_Calibrated[R0][Y];
  delay(5);
 
  // Vector r3-r0
  r3_r0[X] = R_Calibrated[R3][X] - R_Calibrated[R0][X];
  delay(5);
  r3_r0[Y] = R_Calibrated[R3][Y] - R_Calibrated[R0][Y];
  delay(5);
 
 
  // Determinant
  D = ((int32_t) r1_r0[X] * (int32_t) r3_r0[Y]) - ((int32_t) r1_r0[Y] * (int32_t) r3_r0[X]);
  delay(5);
 
  #ifdef DETERMINANT_MULTIPLICATION
    D_inv = (double) 1 / (double) D;
    delay(5);
  #endif
 
  // Result vector (r0 - r1) - (r3 - r2)
  r01_r32[X] = (R_Calibrated[R0][X] - R_Calibrated[R1][X]) - (R_Calibrated[R3][X] - R_Calibrated[R2][X]);
  delay(5);
  r01_r32[Y] = (R_Calibrated[R0][Y] - R_Calibrated[R1][Y]) - (R_Calibrated[R3][Y] - R_Calibrated[R2][Y]);
  delay(5);
}
 
uint8_t GetPositionPrecise(uint8_t *xp, uint8_t *yp)
{
  uint8_t step;
  uint8_t retval;
  int16_t posRaw[2];
 
  // Get here your touch position / ADC values:
  // posRaw[X] = ...
  // posRaw[Y] = ...
  // or F(&posRaw[X], &posRaw[Y] etc...
  // for Simulation purposes we use this values:
  posRaw[X] = 400;
  posRaw[Y] = 500;
 
  // Vector posRaw - r0
  raw_r0[X] = posRaw[X] - R_Calibrated[R0][X];
  raw_r0[Y] = posRaw[Y] - R_Calibrated[R0][Y];
 
  // Iteration loop
  for (step = 0 ; step < ITERATIONS ; step++)
  {  
    // Solution vector
    solVec[X] = (double) raw_r0[X] - (W * (double) r01_r32[X]);
    solVec[Y] = (double) raw_r0[Y] - (W * (double) r01_r32[Y]);
 
    // Determinant
    D_XY[X] = -(((double) r3_r0[X] * solVec[Y]) - ((double) r3_r0[Y] * solVec[X]));
    D_XY[Y] =   ((double) r1_r0[X] * solVec[Y]) - ((double) r1_r0[Y] * solVec[X]);
 
    // factor
    #ifdef DETERMINANT_DIVISION
      factor[X] = (double) D_XY[X] / (double) D;
      factor[Y] = (double) D_XY[Y] / (double) D;
    #endif
 
    #ifdef DETERMINANT_MULTIPLICATION
      factor[X] = (double) D_XY[X] * (double) D_inv;
      factor[Y] = (double) D_XY[Y] * (double) D_inv;
    #endif
 
    // New iteration parameter:
    W = factor[X] * factor[Y];
  }
 
  *xp = (uint8_t) (factor[X] * ((double) (LCD_WIDTH - 1)));
  *yp = (uint8_t) (factor[Y] * ((double) (LCD_HEIGHT - 1)));
 
  #if (CHECK_LCD_DIMENSIONS == TRUE)
    // check if values are valid  
    if ((*xp > (LCD_WIDTH - 1)) || (*yp > (LCD_HEIGHT - 1)))
    {
      // values invalid: return 0
      retval = POSITION_INVALID;
    }
    else
    {
      // values valid: return 1
      retval = POSITION_VALID;
    }
  #else
    retval = POSITION_VALID;
  #endif
 
  return(retval);
}

Beispiel

int main(void)
{
  uint8_t x;
  uint8_t y;
 
  // Set calibration values; may also be read from EEPROM or calibration procedure
  R_Calibrated[R0][X] = 101;
  R_Calibrated[R0][Y] = 145;
  R_Calibrated[R1][X] = 867;
  R_Calibrated[R1][Y] = 150;
  R_Calibrated[R2][X] = 848;
  R_Calibrated[R2][Y] = 802;
  R_Calibrated[R3][X] = 102;
  R_Calibrated[R3][Y] = 759;
 
  InitAlgorithm();
 
  uint8_t result = GetPositionPrecise(&x, &y);
  if (result == POSITION_VALID) // or simply if (result)
  {
    // do something with x, y
  }
 
  return 0;
}


Letzte Änderung: 2015-02-22 13:16:34
Seite erzeugt in 0.031 Sekunden (33.2 kB)