TFT touch screen MMSE-Based Multipoint Calibration Algorithm

Started by trastikata, Jul 09, 2024, 06:04 PM

Previous topic - Next topic

trastikata

Hi all,

TFT touch screen calibration is much easier done than it looks like. And it does not have to be with 3 points only, whether it's 5, 7, 9 .... it is all the same principle. The entire process is well described here for the Minimum mean square error method:

https://www.analog.com/en/resources/app-notes/an-1021.html

It looks scary and complicated but in reality it very easy, I'll break it up to what is important for the calibrations algorithm:

1. choose the coordinates for and print an odd number of points on the screen
2. touch on each point and get the touch coordinates (multiple times and average) - i.e. touch 5 times
3. optional before averaging the coordinates for each individual point to lower the noise , I sort the touch coordinates for each point in an array, get the median (not mean) and drop out anything that is say n pixels away from the median - thus sort the 5 coordinates for that particular point and remove noisy read-outs. Do that for the X and Y readout
4. average the remaining coordinates for that particular point
5. repeat for all points
6. For each point on the screen (x,y) you should have now one set of touch coordinates (x',y') coordinates - under coordinates we understand ADC readings for x and y plate on the touch screen, thuse pure ADC integers, usually 12b ADC.

Now the important math from the link above:

Equation17.PNG

7. Equation17 - calculate a0, a1, a2, ... , b0..., c0..., d0...d2 - to do that calculate the sums as in the equations above, where x' and y' indicate touch coordinates and x and y indicate actual screen draw coordinates. For example for a0 = S(x'^2) / S(x') we square the x' readouts for each point and sum the those squares, then sum the x' and divide the first sum by the second sum and so on ... don't forget "'" means touch coordinates, no apostrophe means actual screen coordinates.

k.jpg

8. Calculate k using the previously calculated intermittent variables a0, a1 ... b2

Equation23.PNG

9. Now using the previously calculated intermittent variables a0, a1 ... d2 calculate the calibration coefficients KX1, KX2 ... KY3 following Equation23
 
10. Store those coefficients for future use if you wish, these are the coefficients to convert the touch coordinates to screen pixel coordinates.

Equation25.PNG

11. Conversion is done using Equation25 where (x',y') are the touch ADC readouts for the x and y plate.

How to use the touch screen coordinates will depend much on what you need it for. But here's a basic code ... unfortunately it was a draft and I did not place any comments, but it is fully functional:

Symbol TftScreenWidth = 320
Symbol TftScreenHeight = 240
Symbol X_Command_Byte = 0xD0    'get X values
Symbol Y_Command_Byte = 0x90    'get Y values
Symbol Z1_Command_Byte = 0xB1   'get Z1 value
Symbol Z2_Command_Byte = 0xC1   'get Z2 value
Symbol Z_Threshold = 700        'Sensitivity
Symbol TouchADC = 4095          'ADC resolution
Symbol TouchAveragePoints = 5   'Number of point to average, should be odd number
Symbol TouchMedianDistance = 10 'What is the maximum distance from the median point to be considered a valid data point


'Calibrate the touch screen
'bPoints = number of point to be used for calibration (3, 5, 7, 9)
Proc TouchCalibration(bPoints As Byte)
    TouchDrawPoints(bPoints)
    TouchTestPoints(bPoints)
    TouchCalCoef(bPoints)
EndProc


Proc TouchReadPointAverage()
    Dim waX[TouchAveragePoints] As SWord
    Dim waY[TouchAveragePoints] As SWord
    Dim bTemp0 As Byte
    Dim bTemp1 As Byte
   
    bTemp0 = TouchAveragePoints
    While bTemp0 > 0
        If TouchScreenReadValues() = 1 Then
            waX[bTemp0 - 1] = wTouchX
            waY[bTemp0 - 1] = wTouchY
            Dec bTemp0
        Else
            Continue
        EndIf     
    Wend
   
    For bTemp0 = 0 To TouchAveragePoints - 1
        For bTemp1 = 0 To TouchAveragePoints - 1 - bTemp0
            If waX[bTemp1] > waX[bTemp1 + 1] Then
                wTouchX = waX[bTemp1]
                wTouchY = waX[bTemp1 + 1]
                waX[bTemp1] = wTouchY
                waX[bTemp1 + 1] = wTouchX
            EndIf
            If waY[bTemp1] > waY[bTemp1 + 1] Then
                wTouchX = waY[bTemp1]
                wTouchY = waY[bTemp1 + 1]
                waY[bTemp1] = wTouchY
                waY[bTemp1 + 1] = wTouchX
            EndIf
        Next
    Next
   
    bTemp1 = 0 : wTouchX = 0 : wTouchY = 0
    For bTemp0 = 0 To TouchAveragePoints - 1
        If waX[bTemp0] < waX[TouchAveragePoints / 2] + 20 And waX[bTemp0] > waX[TouchAveragePoints / 2] - 20 Then
            If waY[bTemp0] < waY[TouchAveragePoints / 2] + 20 And waY[bTemp0] > waY[TouchAveragePoints / 2]- 20 Then
                wTouchX = wTouchX + waX[bTemp0]
                wTouchY = wTouchY + waY[bTemp0]
                Inc bTemp1
            EndIf   
        EndIf       
    Next
   
    wTouchX = wTouchX / bTemp1
    wTouchY = wTouchY / bTemp1
EndProc

Proc TouchCalcPoint(wTcX As Word, wTcY As Word), Bit
    Global Dim TouchCalcPoint_X As Word Shared
    Global Dim TouchCalcPoint_Y As Word Shared
    Dim fTemp1 As Float
    Dim fTemp2 As Float
   
    fTemp1 = KX1 * wTcX
    fTemp2 = KX2 * wTcY
    fTemp1 = fTemp1 + fTemp2
    fTemp2 = fTemp1 + KX3
    TouchCalcPoint_X = fTemp2
    fTemp1 = KY1 * wTcX
    fTemp2 = KY2 * wTcY
    fTemp1 = fTemp1 + fTemp2
    fTemp2 = fTemp1 + KY3
    TouchCalcPoint_Y = fTemp2   
   
    If TouchCalcPoint_X < wTftScreenWidth - 1 And TouchCalcPoint_X >= 0 Then
        If TouchCalcPoint_Y < wTftScreenHeight - 1 And TouchCalcPoint_Y >= 0 Then
            Result = 1
        Else
            Result = 0
        EndIf
    EndIf
EndProc

Proc TouchTestPoints(bPoints As Byte)
    Global Dim KX1 As Float Shared
    Global Dim KX2 As Float Shared
    Global Dim KX3 As Float Shared
    Global Dim KY1 As Float Shared
    Global Dim KY2 As Float Shared
    Global Dim KY3 As Float Shared
   
    Global Dim wTouchX As Word Shared
    Global Dim wTouchY As Word Shared
    Global Dim wTouchZ As Word Shared
    Global Dim waPointCalX[9] As Word Shared
    Global Dim waPointCalY[9] As Word Shared
    Global Dim wTcPointX As Word Shared
    Global Dim wTcPointY As Word Shared
   
    Dim bCounter0 As Byte
    Dim bCounter1 As Byte
    Dim wAccX As Word
    Dim wAccY As Word
   
    For bCounter0 = 0 To bPoints - 1
        wAccX = 0
        wAccY = 0
        For bCounter1 = 5 DownTo 1
            TouchCalPointCount(bCounter0, bCounter1)
            DelayMS 500
            Do
                If T_IRQ = 0 Then
                    DelayMS 50
                    If TouchScreenReadValues() = 1 Then
                        TouchReadPointAverage()
                        wAccX = wAccX + wTouchX
                        wAccY = wAccY + wTouchY
                        Break
                        DelayMS 500
                    EndIf
                EndIf
            Loop
        Next
        TouchCalAverage(bCounter0, wAccX, wAccY)
    Next
EndProc

Proc TouchCalCoef(bPointNumber As Byte)
    Global Dim waPointCalX[9] As Word Shared
    Global Dim waPointCalY[9] As Word Shared
   
    Global Dim KX1 As Float Shared
    Global Dim KX2 As Float Shared
    Global Dim KX3 As Float Shared
    Global Dim KY1 As Float Shared
    Global Dim KY2 As Float Shared
    Global Dim KY3 As Float Shared
   
    Dim bTemp As Byte
    Dim fTemp1 As Float
    Dim fTemp2 As Float
    Dim fTemp3 As Float
   
    Dim Xp2 As Dword
    Dim XpYp As Dword
    Dim Yp2 As Dword
    Dim XpX As Dword
    Dim YpX As Dword
    Dim XpY As Dword
    Dim YpY As Dword
    Dim Xp As Dword
    Dim Yp As Dword
    Dim Xo As Dword
    Dim Yo As Dword
   
    Dim a_0 As Float
    Dim a_1 As Float
    Dim a_2 As Float
    Dim b_0 As Float
    Dim b_1 As Float
    Dim b_2 As Float
    Dim c_0 As Float
    Dim c_1 As Float
    Dim c_2 As Float
    Dim d_0 As Float
    Dim d_1 As Float
    Dim d_2 As Float
    Dim k_0 As Float
       

    Xp2 = 0 : XpYp = 0 : Yp2 = 0 : XpX = 0 : YpX = 0
    XpY = 0 : YpY = 0 : Xp = 0 : Yp = 0 : Xo = 0 : Yo = 0
    For bTemp = 0 To bPointNumber - 1
        TouchCalPointCenter(bTemp)
        Xp2 = Xp2 + waPointCalX[bTemp] * waPointCalX[bTemp]
        XpYp = XpYp + waPointCalX[bTemp] * waPointCalY[bTemp] 
        Yp2  = Yp2  + waPointCalY[bTemp] * waPointCalY[bTemp]
        XpX  = XpX  + waPointCalX[bTemp] * wTcPointX
        YpX  = YpX  + waPointCalY[bTemp] * wTcPointX
        XpY  = XpY  + waPointCalX[bTemp] * wTcPointY
        YpY  = YpY  + waPointCalY[bTemp] * wTcPointY
        Xp   = Xp   + waPointCalX[bTemp]
        Yp   = Yp   + waPointCalY[bTemp]
        Xo   = Xo   + wTcPointX
        Yo   = Yo   + wTcPointY
    Next
   
    a_0 = Xp2 / Xp
    a_1 = XpYp / Yp
    a_2 = Xp / bPointNumber
    b_0 = XpYp / Xp
    b_1 = Yp2 / Yp
    b_2 = Yp / bPointNumber
    fTemp1 = a_0 - a_2
    fTemp1 = fTemp1 * (b_1 - b_2)
    fTemp2 = (a_1 - a_2)
    fTemp2 = fTemp2 * (b_0 - b_2)
    k_0 = fTemp1 - fTemp2
    c_0 = XpX / Xp
    c_1 = YpX / Yp
    c_2 = Xo / bPointNumber
    d_0 = XpY / Xp
    d_1 = YpY / Yp
    d_2 = Yo / bPointNumber
   
    fTemp1 = c_0 - c_2
    fTemp1 = fTemp1 * (b_1 - b_2)
    fTemp2 = c_1 - c_2
    fTemp2 = fTemp2 * (b_0 - b_2)
    KX1 = fTemp1 - fTemp2
    KX1 = KX1 / k_0
   
    fTemp1 = c_1 - c_2
    fTemp1 = fTemp1 * (a_0 - a_2)
    fTemp2 = c_0 - c_2
    fTemp2 = fTemp2 * (a_1 - a_2)
    KX2 = fTemp1 - fTemp2
    KX2 = KX2 / k_0
   
    fTemp1 = d_0 - d_2
    fTemp1 = fTemp1 * (b_1 - b_2)
    fTemp2 = d_1 - d_2
    fTemp2 = fTemp2 * (b_0 - b_2)
    KY1 = fTemp1 - fTemp2
    KY1 = KY1 / k_0
   
    fTemp1 = d_1 - d_2
    fTemp1 = fTemp1 * (a_0 - a_2)
    fTemp2 = d_0 - d_2
    fTemp2 = fTemp2 * (a_1 - a_2)
    KY2 = fTemp1 - fTemp2
    KY2 = KY2 / k_0
   
    fTemp1 = a_2 * c_1 - a_1 * c_2
    fTemp1 = fTemp1 * b_0
    fTemp2 = a_0 * c_2 - a_2 * c_0
    fTemp2 = fTemp2 * b_1
    fTemp3 = a_1 * c_0 - a_0 * c_1
    fTemp3 = fTemp3 * b_2
    KX3 = fTemp1 + fTemp2
    KX3 = KX3 + fTemp3
    KX3 = KX3 / k_0
   
    fTemp1 = a_2 * d_1 - a_1 * d_2
    fTemp1 = fTemp1 * b_0
    fTemp2 = a_0 * d_2 - a_2 * d_0
    fTemp2 = fTemp2 * b_1
    fTemp3 = a_1 * d_0 - a_0 * d_1
    fTemp3 = fTemp3 * b_2
    KY3 = fTemp1 + fTemp2
    KY3 = KY3 + fTemp3
    KY3 = KY3 / k_0
EndProc

Proc TouchCalPointCount(bPointNumber As Byte, bPointCount As Byte)
    TouchCalPointCenter(bPointNumber)
    Select bPointNumber
        Case 0
            TftPrintInteger(bPointCount, 0, 1, wTcPointX - 4, wTcPointY + 6, 0, MAGENTA, BLACK, 0)   
        Case 1
            TftPrintInteger(bPointCount, 0, 1, wTcPointX - 4, wTcPointY - 16, 0, RED, BLACK, 0)   
        Case 2
            TftPrintInteger(bPointCount, 0, 1, wTcPointX - 4, wTcPointY - 16, 0, GREEN, BLACK, 0)
        Case 3
            TftPrintInteger(bPointCount, 0, 1, wTcPointX - 4, wTcPointY - 16, 0, WHITE, BLACK, 0)
        Case 4
            TftPrintInteger(bPointCount, 0, 1, wTcPointX - 4, wTcPointY - 16, 0, OLIVE, BLACK, 0)       
        Case 5
            TftPrintInteger(bPointCount, 0, 1, wTcPointX - 4, wTcPointY - 16, 0, BLUE, BLACK, 0) 
        Case 6
            TftPrintInteger(bPointCount, 0, 1, wTcPointX - 4, wTcPointY + 6, 0, CYAN, BLACK, 0)
        Case 7
            TftPrintInteger(bPointCount, 0, 1, wTcPointX - 4, wTcPointY - 16, 0, PURPLE, BLACK, 0)
        Case 8
            TftPrintInteger(bPointCount, 0, 1, wTcPointX - 4, wTcPointY - 16, 0, PINK, BLACK, 0)
    EndSelect
EndProc


Proc TouchCalAverage(bPointNumber As Byte, wAccX As Word, wAccY As Word)
    TouchCalPointCenter(bPointNumber)
   
    waPointCalX[bPointNumber] = wAccX / 5
    waPointCalY[bPointNumber] = wAccY / 5
   
    Select bPointNumber
        Case 0
            TftPrintInteger(waPointCalX[0], 0, 1, wTcPointX - 20, wTcPointY + 16, 0, MAGENTA, BLACK, 0) 
            TftPrintInteger(waPointCalY[0], 0, 1, wTcPointX - 20, wTcPointY +  6, 0, MAGENTA, BLACK, 0)   
        Case 1
            TftPrintInteger(waPointCalX[1], 0, 1, wTcPointX - 16, wTcPointY - 16, 0, RED, BLACK, 0)
            TftPrintInteger(waPointCalY[1], 0, 1, wTcPointX - 16, wTcPointY - 26, 0, RED, BLACK, 0)   
        Case 2
            TftPrintInteger(waPointCalX[2], 0, 1, wTcPointX - 16, wTcPointY - 16, 0, GREEN, BLACK, 0) 
            TftPrintInteger(waPointCalY[2], 0, 1, wTcPointX - 16, wTcPointY - 26, 0, GREEN, BLACK, 0)
        Case 3
            TftPrintInteger(waPointCalX[3], 0, 1, wTcPointX - 16, wTcPointY - 16, 0, WHITE, BLACK, 0) 
            TftPrintInteger(waPointCalY[3], 0, 1, wTcPointX - 16, wTcPointY - 26, 0, WHITE, BLACK, 0)
        Case 4
            TftPrintInteger(waPointCalX[4], 0, 1, wTcPointX - 16, wTcPointY - 16, 0, OLIVE, BLACK, 0) 
            TftPrintInteger(waPointCalY[4], 0, 1, wTcPointX - 16, wTcPointY - 26, 0, OLIVE, BLACK, 0)
        Case 5
            TftPrintInteger(waPointCalX[5], 0, 1, wTcPointX - 16, wTcPointY - 16, 0, BLUE, BLACK, 0) 
            TftPrintInteger(waPointCalY[5], 0, 1, wTcPointX - 16, wTcPointY - 26, 0, BLUE, BLACK, 0)
        Case 6
            TftPrintInteger(waPointCalX[6], 0, 1, wTcPointX - 16, wTcPointY + 16, 0, CYAN, BLACK, 0) 
            TftPrintInteger(waPointCalY[6], 0, 1, wTcPointX - 16, wTcPointY +  6, 0, CYAN, BLACK, 0)
        Case 7
            TftPrintInteger(waPointCalX[7], 0, 1, wTcPointX - 16, wTcPointY - 16, 0, PURPLE, BLACK, 0) 
            TftPrintInteger(waPointCalY[7], 0, 1, wTcPointX - 16, wTcPointY - 26, 0, PURPLE, BLACK, 0)
        Case 8
            TftPrintInteger(waPointCalX[8], 0, 1, wTcPointX - 16, wTcPointY - 16, 0, PINK, BLACK, 0) 
            TftPrintInteger(waPointCalY[8], 0, 1, wTcPointX - 16, wTcPointY - 26, 0, PINK, BLACK, 0)
    EndSelect
EndProc

Proc TouchCalPointCenter(bPointNumber As Byte)
    Select bPointNumber
        Case 0
            wTcPointX = wTftScreenWidth - wTftScreenWidth / 10
            wTcPointY = wTftScreenHeight / 10
        Case 1
            wTcPointX = wTftScreenWidth - wTftScreenWidth / 8
            wTcPointY = wTftScreenHeight - wTftScreenHeight / 5
        Case 2
            wTcPointX = wTftScreenWidth / 7
            wTcPointY = wTftScreenHeight / 2     
        Case 3 
            wTcPointX = wTftScreenWidth - 3 * wTftScreenWidth / 5
            wTcPointY = wTftScreenHeight / 7
        Case 4
            wTcPointX = wTftScreenWidth / 2
            wTcPointY = wTftScreenHeight - wTftScreenHeight / 6
        Case 5
            wTcPointX = wTftScreenWidth / 12
            wTcPointY = wTftScreenHeight - wTftScreenHeight / 9
        Case 6
            wTcPointX = wTftScreenWidth / 13
            wTcPointY = wTftScreenHeight / 8
        Case 7
            wTcPointX = 2 * wTftScreenWidth / 5
            wTcPointY = 3 * wTftScreenHeight / 5
        Case 8
            wTcPointX = 4 * wTftScreenWidth / 5
            wTcPointY = 2 * wTftScreenHeight / 5
    EndSelect       
EndProc

Proc TouchScreenReadValues(), Bit '0 = invalid, 1 = valid
    Dim wTemp1 As Word
    Dim wTemp2 As Word
   
    PinClear SCLK : PinClear T_CS
        DelayUS 10
        SpiSendTs(Z1_Command_Byte)
        PinClear MOSI
        wTemp1.Byte1 = SpiReceiveTs()
        wTemp1.Byte0 = SpiReceiveTs()
        SpiSendTs(Z2_Command_Byte)
        PinClear MOSI
        wTemp2.Byte1 = SpiReceiveTs()
        wTemp2.Byte0 = SpiReceiveTs()
        SpiSendTs(X_Command_Byte)
        PinClear MOSI
        wTouchX.Byte1 = SpiReceiveTs()
        wTouchX.Byte0 = SpiReceiveTs()
        SpiSendTs(Y_Command_Byte)
        PinClear MOSI
        wTouchY.Byte1 = SpiReceiveTs()
        wTouchY.Byte0 = SpiReceiveTs()
    PinSet T_CS
   
    wTemp1 = wTemp1 >> 3
    wTemp2 = wTemp2 >> 3
    wTouchX = wTouchX >> 3
    wTouchY = wTouchY >> 3
    wTouchZ = wTemp1 + 4095 - wTemp2
    If wTouchX < TouchADC And wTouchY < TouchADC And wTouchZ < TouchADC And wTouchZ > Z_Threshold Then
        Result = 1
    Else
        Result = 0
    EndIf
EndProc

 
'Draw the calibration points
Proc TouchDrawPoints(bPoints As Byte)
    If bPoints > 2 Then
        TouchCalPointCenter(0)
        TftCircleSolidQuick(wTcPointX, wTcPointY, 4, MAGENTA)
        TouchCalPointCenter(1)
        TftCircleSolidQuick(wTcPointX, wTcPointY, 4, RED)
        TouchCalPointCenter(2)
        TftCircleSolidQuick(wTcPointX, wTcPointY, 4, GREEN)
    EndIf
   
    If bPoints > 4 Then
        TouchCalPointCenter(3)
        TftCircleSolidQuick(wTcPointX, wTcPointY, 4, WHITE)   
        TouchCalPointCenter(4)
        TftCircleSolidQuick(wTcPointX, wTcPointY, 4, OLIVE)
    EndIf
   
    If bPoints > 6 Then
        TouchCalPointCenter(5)
        TftCircleSolidQuick(wTcPointX, wTcPointY, 4, BLUE) 
        TouchCalPointCenter(6)
        TftCircleSolidQuick(wTcPointX, wTcPointY, 4, CYAN)
    EndIf
   
    If bPoints > 8 Then
        TouchCalPointCenter(7)
        TftCircleSolidQuick(wTcPointX, wTcPointY, 4, PURPLE)
        TouchCalPointCenter(8)
        TftCircleSolidQuick(wTcPointX, wTcPointY, 4, PINK) 
    EndIf
EndProc

'Touch screen software SPI send
Proc SpiSendTs(bSpiByte As Byte)
    Dim bTemp As Byte
   
    For bTemp = 7 DownTo 0 Step -1
        MOSI = GetBit bSpiByte, bTemp
        DelayUS 1 : PinSet SCLK : DelayUS 1 : PinClear SCLK : DelayUS 1   
    Next
EndProc

'Touch screen software SPI receive
Proc SpiReceiveTs(), Byte
    Dim bTemp As Byte

    For bTemp = 7 DownTo 0 Step -1
        DelayUS 1 : PinSet SCLK : DelayUS 1
        LoadBit Result, bTemp, MISO
        PinClear SCLK : DelayUS 1
    Next
EndProc