Data transformation of analog joystick to 8 discrete ON-OFF positions

Started by SebaG, Dec 28, 2021, 11:25 AM

Previous topic - Next topic

SebaG

Hi,

I have a problem with coming up with a program for an analog joystick that would act as a joystick with micro switches (which would have 8 discrete positions / swings operating on the ON-OFF principle). It's hard to put into words. I mean a soft that will transform data from two inputs of 8 bit ADC to 8 discrete positions (using the geographic nomenclature N, NE, E, SE, S, SW, W, NW).

It seemed simple to me, but it turned out to be problematic. I tried to write conditions for certain ADC values but it doesn't work as it should. The joystick positions overlap. The joystick should have eight discrete positions that do not overlap each other, but at the same time each position must have hysteresis (+ - of a given position) so that you do not have to "hit" the joystick position with extreme accuracy at certain ADC values, because such a program will not work. This is contradictory, I have a hard time explaining it, but I hope you understand what I mean.

In one sentence: I want to convert analog joystick to 8 positions rocker-style joystick.

Do you have an idea how to do it?




trastikata

Put pull-up resistors and connect to a 8-bit port, then read the port in one cycle to a 8-bit variable and deal with the individual bits as required.

Or I misunderstood the question?

SebaG

Quote from: trastikata on Dec 28, 2021, 11:42 AMPut pull-up resistors and connect to a 8-bit port, then read the port in one cycle to a 8-bit variable and deal with the individual bits as required.

I know how to connect it, I don't know how to program it. I have a program with ADC support for the X and Y axes, I read the values and store them in variables - I have no problem with that. I have a problem with writing the conditions for ADC data handling intelligently to get eight discrete positions simulating a rocker-style joystick.

trastikata

Ok I see. How are the analog voltages being generated for each axis?

SebaG

Quote from: trastikata on Dec 28, 2021, 12:05 PMOk I see. How are the analog voltages being generated for each axis?

Just like here, below, except for the 8-bit converter. Sorry, but I don't have the equipment with me right now, I can't post the code or photo (I'm on vacation).

https://www.electroduino.com/dual-axis-joystick-module-how-its-works/

SebaG

I think I found the answer to my question a moment ago, but in an incomprehensible C # language. Could someone please implement this into Proton? It's too complicated for me, and it might be useful to people in this forum.

https://blackdoor.github.io/blog/thumbstick-controls/

joesaliba

Seba,

You should do something similar to below: -

If VRx = 1023 And VRy = 512 Then position is North
If VRx = 1023 And VRy = 1023 Then position is North-East
If VRx = 512 And VRy = 1023 Then position is East
If VRx = 0 And VRy = 1023 Then position is South-East
If VRx = 0 And VRy = 512 Then position is South
If VRx = 0 And VRy = 0 Then position is South-West
If VRx = 512 And VRy = 0 Then position is West
If VRx = 1023 And VRy = 0 Then position is North-West

The only thing you should do is to first print and take note of VRx and VRy values when they are in the position you need them.

For example, it might that when you go North-East, the values might be less than 1023 for both axis because potentiometer is not to it's full.

Regards

Joe

SebaG

Quote from: joesaliba on Dec 28, 2021, 01:36 PMSeba,

You should do something similar to below: -

If VRx = 1023 And VRy = 512 Then position is North
If VRx = 1023 And VRy = 1023 Then position is North-East
If VRx = 512 And VRy = 1023 Then position is East
If VRx = 0 And VRy = 1023 Then position is South-East
If VRx = 0 And VRy = 512 Then position is South
If VRx = 0 And VRy = 0 Then position is South-West
If VRx = 512 And VRy = 0 Then position is West
If VRx = 1023 And VRy = 0 Then position is North-West

The only thing you should do is to first print and take note of VRx and VRy values when they are in the position you need them.

For example, it might that when you go North-East, the values might be less than 1023 for both axis because potentiometer is not to it's full.

Regards

Joe

Thank you Joe, I also thought it was that simple, but it won't work. Hitting the exact position described by the variables is unlikely for a human. A slight shift (offset) to the left or right (or up and down depending on the axis) and the whole thing will not work. Please take a look at the link I have posted below, it describes exactly what I mean and need. Only the creation of virtual eight areas in which the joystick can move is justified, but it is too complicated for me, hence my big request for help.

Thank you again and I kindly ask all of you for help.

https://blackdoor.github.io/blog/thumbstick-controls/

joesaliba

I can understand what you are saying, but, what you need is 8 positions.

So you have to determine what is correct for your use.

For example, if you need South-East, how much can you deflect from South-East for you to be ok?

So, if for South-East you need: -

If VRx = 0 And VRy = 1023 Then position is South-East

you can change that to: -

If VRx < 200 and VRy > 823 Then position is South-East.

The above is for not so complicated math.

I do not have much time to convert what you posted, but looks simple to convert.

Regards

Joe

SebaG

I understand you don't have time. Thank you for taking the time to try to resolve this problem. The solution described in the link is perfect for my applications. Perfect because there are no other positions than the eight defined. My device is to ultimately control the drive in two axes. The N and S directions are one axis. The W and E directions are the second axis. The intermediate directions actuate both axes. There can be no other positions and there cannot be a situation that an inaccurate "hit" in a given area does not activate any of the axes.

trastikata

Quote from: SebaG on Dec 28, 2021, 12:40 PMJust like here, below, except for the 8-bit converter. Sorry, but I don't have the equipment with me right now, I can't post the code or photo (I'm on vacation).

https://www.electroduino.com/dual-axis-joystick-module-how-its-works/

Just divide the compass in 8 segments, calculate the coordinates for each segment in terms of x and y ADC values. Then use if-then statements for one axis and another if-then for the second axis. Make sure you include all segments and all possible ADC readings.

Don't have time to write all but should be something like this:

    If y_ADC >= z And y_ADC < y Then
        If x_ADC >= c And x_ADC < d Then position = 1 
        If x_ADC >= e And x_ADC < f Then position = 2 
    EndIf
   
    If y_ADC >= y And y_ADC < x Then
        If x_ADC >= c And x_ADC < d Then position = 3
        If x_ADC >= e And x_ADC < f Then position = 4 
    EndIf

Stephen Moss

For an 8 bit ADC you can assume the center point for the X and Y axis will be round 128, then it is a question of how much slack you want in the system, too much and you may get an erroneous result, too mush nad hte use will haveto be more precise with the movement.
So to start with I would use halve of 128 = 64 and modify it up or down to tighent or loosen the accuracy of the user input. So...
Dim X as byte    'Hold X Position
Dim Y as Byte    'Hold Y position
Dim Slack as Byte = 64
Dim Direction as byte
Dim North as Direction.0 = False
Dim South as Direction.1 = False
Dim East as Directrion.2 = False
Dim West as Direction.3 = False

 
If X >= (128 + Slack) then
  East = True
  West = False
 ElseIf X <= (128 - Slack) then
  East = False
  West = True
 Else
  East = False
  West = False
End If

If Y >= (128 + Slack) then
  North = True
  South = False
 ElseIf Y <= (128 - Slack) then
  North = False
  South = True
 Else
  North = False
  South = False
End If

Then simply do a Select Case on the value of Direction as...
1 = North
2 = South
4 = East
5 = North East (1 + 4)
6 = South East
8 = West
9 = North West and
10 = South West

Anthing else = no direction selected- might have to replace True with 1 and False with 0

top204

Here's another method using Cartesian to Polar Coordinates, the same method as used in the C# code in the above link, but a whole lot more efficient in the Positron compiler. :-)

I have used the mechanism below a few times for a few 3D maze game programs I was creating on a graphic LCD. It is quite large because it uses floating point to calculate the angle of the joystick's direction, then divides that by the segments required. However, it was implemented in a set of programs that also used floating point for the maze coordinates and drawing etc, so they all use each other's library routines, and the program's size stabilises.

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Conversion of an analogue joystick to a digital joystick using Cartesian to Polar Coordinates
' Written for the Positron8 BASIC compiler by Les Johnson
'
' The ADC setups and config fuses are for a PIC18F25K20 device only
'
    Device = 18F25K20
    Declare Xtal = 64 
'
' Setup USART1
'
    Declare Hserial_Baud = 9600
    Declare HRSOut1_Pin = PORTC.6

    Include "Median_Filters.inc"                ' Load the median filter library into the program
    Include "Trig.inc"                          ' Load the trigonometry library into the program
    Include "Scale.inc"                         ' Load the scaling library into the program
'
' Set the pins used for the analogue joystick's X and Y potentiometers
'   
    Symbol X_Axis_Pin = PORTA.0                 ' Connects to the X axis of the analogue joystick (0 to VDD)
    Symbol Y_Axis_Pin = PORTA.1                 ' Connects to the Y axis of the analogue joystick (0 to VDD)
'
' Create some variables for the analogue joystick conversion
'   
    Dim Joystick_fXValue As Float               ' Holds the joystick's X axis value (-1.0 to 1.0)
    Dim Joystick_fYValue As Float               ' Holds the joystick's Y axis value (-1.0 To 1.0)
'
' Create some variables for the demo
'      
    Dim bPrevValue  As Byte                     ' Holds the previous joystick value for the display
    Dim Global_bDir As Byte                     ' Holds the values for joystick up, down, left, right or combinations

'----------------------------------------------------------------------
' The main program starts here
' Read an analogue joystick and convert its positions to a value
' Display the joystick positions on a serial terminal, when they change
'
Main:
    Joy_Setup()                                 ' Setup the program and peripherals
   
    bPrevValue = $FF                            ' Give the previous value a value that will not be seen
    Do                                          ' Create a loop
        Global_bDir = Joy_Dir()                 ' Read the analogue joystick and return a direction value
        If bPrevValue <> Global_bDir Then       ' Is the previous value the same as the current value
            JoyStick_Visualise()                ' No. So display the joystick positions on a serial terminal
            bPrevValue = Global_bDir            ' Keep a copy of the current joystick value
        EndIf
    Loop                                        ' Do it forever

'----------------------------------------------------------------------
' Visualise the joystick's position on a serial terminal
' Input     : Global_bDir holds the values of the digital joystick movement
' Output    : None
' Notes     : None
'
Proc JoyStick_Visualise()   
    HRSOutLn Dec Global_bDir
 
    If Global_bDir = 0 Then
        HRSOutLn "O I O"
        HRSOutLn "O   O"
        HRSOutLn "O O O"
   
    ElseIf Global_bDir = 1 Then
        HRSOutLn "O O I"
        HRSOutLn "O   O"
        HRSOutLn "O O O"

    ElseIf Global_bDir = 2 Then
        HRSOutLn "O O O"
        HRSOutLn "O   I"
        HRSOutLn "O O O"

    ElseIf Global_bDir = 3 Then
        HRSOutLn "O O O"
        HRSOutLn "O   O"
        HRSOutLn "O O I"

    ElseIf Global_bDir = 4 Then
        HRSOutLn "O O O"
        HRSOutLn "O   O"
        HRSOutLn "O I O"
          
    ElseIf Global_bDir = 5 Then
        HRSOutLn "O O O"
        HRSOutLn "O   O"
        HRSOutLn "I O O"
   
    ElseIf Global_bDir = 6 Then
        HRSOutLn "O O O"
        HRSOutLn "I   O"
        HRSOutLn "O O O"
   
    ElseIf Global_bDir = 7 Then
        HRSOutLn "I O O"
        HRSOutLn "O   O"
        HRSOutLn "O O O"
 
    Else
        HRSOutLn "O O O"
        HRSOutLn "O   O"
        HRSOutLn "O O O"
    EndIf
    HRSOutLn "-----------------"
EndProc

'----------------------------------------------------------
' Convert the analogue joystick's X and Y movements into a direction value
' Input     : None
' Output    : Returns the joystick direction value:
'             0 = Up
'             1 = Up-Right          
'             2 = Right
'             3 = Down-Right    
'             4 = Down
'             5 = Down-Left
'             6 = Left  
'             7 = Up-Left
'             8 or higher = Centre
' Notes     : None
'
Proc Joy_Dir(), Byte
    Symbol cPI = 3.14159265359
    Symbol cPIMul2 = (cPI * 2.0)
    Symbol cSectSize = (360.0 / 8.0)                ' 8 sectors required, so get the size of each sector in degrees
    Symbol cHalfSectSize = (360.0 / 16.0)           ' Holds the size of half a sector
   
    Dim fAngleRads As Float                         ' Holds an angle value in radians
    Dim fAngleDegs As fAngleRads                    ' Holds an angle value in degrees
    Dim fConvAngle As fAngleRads                    ' Holds the converted angle
   
    Joy_GetXY()                                     ' Read and convert the joystick's X and Y ADC values
    fAngleRads = ATan2(Joystick_fXValue, Joystick_fYValue) ' Calculate the angle based upon the joystick's X and Y values
    If fAngleRads < 0.0 Then                        ' Is fAngleRads holding a negative value?
        fAngleRads = fAngleRads + cPIMul2           ' Yes. So add 2 x PI to make it a positive value
    EndIf   
    fAngleDegs = RadToDeg(fAngleRads)               ' Convert radians to degrees 
    fConvAngle = fAngleDegs + cHalfSectSize         ' Rotate the angle to match the offset of the sectors   
    Result = fConvAngle / cSectSize                 ' Return the direction value by dividing the angle by the size of the sectors
EndProc

'----------------------------------------------------------------------
' Read the X and Y axis ADC values of the analogue joystick
' Input     : None
' Output    : Joystick_fXValue holds the X value (-1.0 to 1.0)
'           : Joystick_fYValue holds the Y value (-1.0 To 1.0)
' Notes     : Uses a median filter to average the ADC readings
'
Proc Joy_GetXY()
    Dim bIndex As Byte
    Dim wXValue As Word
    Dim wYValue As Word
    Dim wXSamples[10] As Word Heap
    Dim wYSamples[10] As Word Heap
   
    For bIndex = Bound(wXSamples) DownTo 0          ' Create a loop to sample the joystick ADC readings
        wXSamples[bIndex] = ADIn(0)                 ' Read the X axis joystick from the 10-bit ADC
        wYSamples[bIndex] = ADIn(1)                 ' Read the Y axis joystick from the 10-bit ADC
        DelayUS 40                                  ' A small delay between samples
    Next
    wXValue = Median16(wXSamples, SizeOf(wXSamples))   ' Get the average X ADC value
    wYValue = Median16(wYSamples, SizeOf(wYSamples))   ' Get the average Y ADC value  
    Joystick_fXValue = fScale(wXValue, 0, 1023, -1.0, 1.0) ' Scale the X ADC value ranging from -1.0 to 1.0
    Joystick_fYValue = fScale(wYValue, 0, 1023, -1.0, 1.0) ' Scale the Y ADC value ranging from -1.0 to 1.0
EndProc

'----------------------------------------------------------------------
' Setup the program and peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Joy_Setup()
'
' Setup the ADC peripheral on a PIC18F25K20 device
'       
    ADCON0 = %00000000              ' Clear the ADCON0 SFR
    ADCON1 = %00000000              ' +Vref is VDD. -Vref is VSS
    ADCON2 = %10001111              ' ADC right justified for 10-bit operation, 2 TAD, FRC
    ANSEL = %00000011               ' Make PORTA.0 and PORTA.1 into analogue pins
    PinInput X_Axis_Pin             ' Set the X axis ADC pin as an input
    PinInput X_Axis_Pin             ' Set the Y axis ADC pin as an input
EndProc

'-------------------------------------------------------------
' Setup the fuses for an external crystal and 4xPLL on a PIC18F25K20 device
'
Config_Start
    FOSC = HSPLL        ' HS oscillator, PLL enabled and under software control
    Debug = Off         ' Background debugger disabled' RB6 and RB7 configured as general purpose I/O pins
    XINST = Off         ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
    STVREN = Off        ' Reset on stack overflow/underflow disabled
    WDTEN = Off         ' WDT disabled (control is placed on SWDTEN bit)
    FCMEN = Off         ' Fail-Safe Clock Monitor disabled
    IESO = Off          ' Two-Speed Start-up disabled
    WDTPS = 128         ' Watchdog is 1:128
    BOREN = Off         ' Brown-out Reset disabled in hardware and software
    BORV = 18           ' VBOR set to 1.8 V nominal
    MCLRE = On          ' MCLR pin enabled, RE3 input pin disabled
    HFOFST = Off        ' The system clock is held Off until the HF-INTOSC is stable.
    LPT1OSC = Off       ' T1 operates in standard power mode
    PBADEN = Off        ' PORTB<4:0> pins are configured as digital I/O on Reset
    CCP2MX = PORTC      ' CCP2 input/output is multiplexed with RC1
    LVP = Off           ' Single-Supply ICSP disabled
    Cp0 = Off           ' Block 0 (0x000800-0x001FFF) not code-protected
    CP1 = Off           ' Block 1 (00x02000-0x003FFF) not code-protected
    CPB = Off           ' Boot block (0x000000-0x0007FFh) not code-protected
    CPD = Off           ' Data eeprom not code-protecte
    WRT0 = Off          ' Block 0 (0x000800-0x001FFF) not write-protected
    WRT1 = Off          ' Block 1 (0x002000-0x003FFF) not write-protected
    WRTB = Off          ' Boot block (0x000000-0x0007FF) not write-protected
    WRTC = Off          ' Configuration registers (0x300000-0x3000FF) not write-protected
    WRTD = Off          ' Data eeprom not write-protected
    EBTR0 = Off         ' Block 0 (0x000800-0x001FFF) not protected from table reads executed in other blocks
    EBTR1 = Off         ' Block 1 (0x002000-0x003FFF) not protected from table reads executed in other blocks
    EBTRB = Off         ' Boot block (0x000000-0x0007FF) not protected from table reads executed in other blocks
Config_End


The program above also has a demo in the main loop that displays the joystick direction on a serial terminal. Below is a screenshot of the program running in a simulator:

Joystick screenshot.jpg

SebaG

Thank you very much! I am very grateful (to everyone) for your help. I will test it at the earliest opportunity (after returning from vacation). Best wishes!

SebaG

Hello all,

One more question. In the brilliant program that top204 included, there is a "call" to convert radians to degrees (RadToDeg), but the procedure itself is gone. Since I have never written procedures (although I do have a Proton compiler add-on to support them), I have a question for you. How to write a procedure for converting [degrees = radians × 180 ° / π]? The code only contains:

fThumbAngle = RadToDeg(fAngleRads)              ' Convert the radians to degrees.  Degrees are easier to visualise

where fAngleRads is a Float-type variable.

top204

The RadToDeg procedure is within the "Trig.inc" library that is installed with the compilers, along with many other libraries. They can be found here: "C:\Users\User Name\PDS\Includes\"

The RadToDeg procedure is:

' Converts Radians to Degrees
' Input     : pRadValue holds the value in radians
' Output    : Returns the converted degrees value
' Notes     : None
'
Proc RadToDeg(pRadValue As Float), pRadValue
    Result = pRadValue * 57.29577951    ' (180 / PI)
EndProc

And DegToRad is:

' Converts Degrees to Radians
' Input     : pDegValue holds the value in degrees
' Output    : Returns the converted radians value
' Notes     : None
'
Proc DegToRad(pDegValue As Float), pDegValue
    Result = pDegValue * 0.01745329     ' (PI / 180)
EndProc


SebaG

I use Proton IDE 3.7.5.5 and there is only the ATan2 procedure inside the Trig.inc

PS Now everything compiles without errors. Thank you very much!

top204

Please remember, all future updates and upgrades are for the Positron compilers, and the old Proton compilers are now, very much, "gone". And good ridence to what, and who, they remind me of. :-)

All code examples I provide on the web sites are for the new compiler versions, so not all new programs will work on the original compilers because they will not contain the new libraries I create or the new commands and devices being added. The same for all questions... Any answers provided will be for the current compiler versions.

SebaG

I see. Please let me know if there is any test version of the Positron? I'd like to see what it looks like.

top204

There are no free trial versions of the Positron compilers, because they are priced so low, it would not make sense to have one. :-) They operate exactly the same as the Proton compilers, but will continue to be added too and improved upon. There are no syntax changes and all the original commands and functions will always be available, so to test the compiler's operations, the free Proton compilers are available to download.