{"id":2161,"date":"2015-08-11T10:22:00","date_gmt":"2015-08-11T08:22:00","guid":{"rendered":"https:\/\/playembedded.org\/?p=2161"},"modified":"2021-07-29T11:02:20","modified_gmt":"2021-07-29T09:02:20","slug":"reading-a-joystick-on-stm32-using-chibios","status":"publish","type":"post","link":"https:\/\/playembedded.org\/blog\/reading-a-joystick-on-stm32-using-chibios\/","title":{"rendered":"Reading a Joystick on STM32 using ChibiOS"},"content":{"rendered":"<h3 id=\"1_2_axis_and_a_key_button\" class=\"level_1\">2-axis and a key button<\/h3>\n<p>The joystick proposed here is much known between makers. It provides two axis and a key button and every axis is actually a potentiometer: that means axis data is analogue and we need <a href=\"https:\/\/playembedded.org\/sampling-and-dimming\/\">to use <b>ADC<\/b><\/a> to read its positioning.<\/p>\n<figure id=\"attachment_2595\" aria-describedby=\"caption-attachment-2595\" style=\"width: 1092px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_017_joypad.jpg\" rel=\"attachment wp-att-2595\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2595 size-full\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2016\/04\/art_017_joypad.jpg\" alt=\"Joystick schematic\" width=\"1092\" height=\"348\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_017_joypad.jpg 1092w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_017_joypad-150x48.jpg 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_017_joypad-300x96.jpg 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_017_joypad-1024x326.jpg 1024w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_017_joypad-24x8.jpg 24w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_017_joypad-36x11.jpg 36w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/art_017_joypad-48x15.jpg 48w\" sizes=\"auto, (max-width: 1092px) 100vw, 1092px\"><\/a><figcaption id=\"caption-attachment-2595\" class=\"wp-caption-text\">Internal schematic of a Joystick with key.<\/figcaption><\/figure>\n<p>Potentiometers are provided of springs so, without forcing, wipers are approximately positioned in the centre of the two resistive elements. As this device is very simple to use, it is not easy find a related datasheet. Indeed, for the most of the applications, it would be useless. Anyway, joystick used in this demo is a very cheap one marked as &ldquo;Keyes_SJoys&rdquo; (See Fig.1).<\/p>\n<h3 id=\"2_Schematic_and_pin_out\" class=\"level_1\">Schematic and pin out<\/h3>\n<p>Our device has 5 pins:<\/p>\n<ol>\n<li><b>GND<\/b>, connection to ground;<\/li>\n<li><b>+5V<\/b>, should be meant as connection to VCC;<\/li>\n<li><b>VRx<\/b>, X-axis wiper ;<\/li>\n<li><b>VRy<\/b>, Y-axis wiper;<\/li>\n<li><b>SW<\/b>, switch terminal.<\/li>\n<\/ol>\n<p>Note that pins are almost the same for every 2-axis Joystick you can buy online. There are some variant which have two or no switch.<!--more--><\/p>\n<p>Referring to Fig.1 we can see that the pins of resistive elements are connected to <b>+5V<\/b> and <b>GND<\/b>, and switch is connected between <b>SW<\/b> and <b>GND<\/b>. As the ADC on <i>STM32<\/i> samples voltages from 0 to 3.0V, <b>+5V<\/b> should be connected to DC 3V. Since the switch has no pull up resistor we must provide it: we should connect a resistor across <b>VCC<\/b>(with a resistance greater of that exhibited by switch, as example 20k&Omega;). The <b>GPIO<\/b> from STM32 could be configured to provide an internal pull-up or pull-down so we will just act on software.<\/p>\n<h3 id=\"3_Proposed_demo\" class=\"level_1\">Proposed demo<\/h3>\n<p>Proposed demo has three threads. One just makes a LED blinking, another samples voltage on <b>VRx<\/b> and <b>VRy<\/b>and read <b>SW<\/b> status and the last one (the <b>main()<\/b>) prints data on a sequential stream.<\/p>\n<h4 id=\"4_Notes_on_sampled_data_handling\" class=\"level_2\">Notes on sampled data handling<\/h4>\n<p>We want to spent some words on sampled data as this case is a little bit more complicated than that faced in&nbsp;<a href=\"https:\/\/playembedded.org\/sampling-and-dimming\/\" target=\"_blank\" rel=\"noopener noreferrer\">main ADC tutorial<\/a> or <a href=\"https:\/\/playembedded.org\/reading-a-slider\/\" target=\"_blank\" rel=\"noopener noreferrer\">slider potentiometer article<\/a>. Indeed, here we are sampling data from two different channel pretending to handle data from each channel in separately. Let&rsquo;s take a look to&nbsp;<b>ADCConversionGroup<\/b>:<\/p>\n<pre>\/*\n * ADC conversion group.\n * Mode:        Linear buffer, SW triggered.\n * Channels:    IN0 IN1.\n *\/\nstatic const ADCConversionGroup my_conversion_group = {\n  FALSE,                            \/*NOT CIRCULAR*\/\n  MY_NUM_CH,                        \/*NUMB OF CH*\/\n  NULL,                             \/*NO ADC CALLBACK*\/\n  NULL,                             \/*NO ADC ERROR CALLBACK*\/\n  0,                                \/* CR1 *\/\n  ADC_CR2_SWSTART,                  \/* CR2 *\/\n  0,                                \/* SMPR1 *\/\n  ADC_SMPR2_SMP_AN0(ADC_SAMPLE_144) |\n  ADC_SMPR2_SMP_AN1(ADC_SAMPLE_144),\/* SMPR2 *\/\n  ADC_SQR1_NUM_CH(MY_NUM_CH),       \/* SQR1 *\/\n  0,                                \/* SQR2 *\/\n  ADC_SQR3_SQ1_N(ADC_CHANNEL_IN0) |\n  ADC_SQR3_SQ2_N (ADC_CHANNEL_IN1)  \/* SQR3 *\/\n};<\/pre>\n<p>Our conversion group contemplates a non-circular sampling on a sequence of two channel (ADC_CHANNEL_IN0, ADC_CHANNEL_IN1). Looking at <b>adcConver()<\/b> calling we will see that we sample that group for 10 times. As our sample buffer has been declared as:<\/p>\n<pre class=\"\">#define MY_NUM_CH                                              2\n#define MY_SAMPLING_NUMBER                                    10\nstatic adcsample_t sample_buff[MY_SAMPLING_NUMBER * MY_NUM_CH];<\/pre>\n<p>when conversion is done, we have a buffer of 20 samples organized in this way:<\/p>\n<table style=\"height: 27px;\" width=\"649\">\n<tbody>\n<tr>\n<td style=\"text-align: center;\">IN0_1<\/td>\n<td style=\"text-align: center;\">IN1_1<\/td>\n<td style=\"text-align: center;\">IN0_2<\/td>\n<td style=\"text-align: center;\">IN1_2<\/td>\n<td style=\"text-align: center;\">&hellip;<\/td>\n<td style=\"text-align: center;\">IN0_10<\/td>\n<td style=\"text-align: center;\">IN1_10<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For convenience of use a good idea is declaring sample buffer in this way:<\/p>\n<pre class=\"\">#define MY_NUM_CH                                              2\n#define MY_SAMPLING_NUMBER                                    10\nstatic adcsample_t sample_buff[MY_SAMPLING_NUMBER] [MY_NUM_CH];<\/pre>\n<p>this way our buffer is organizes as:<\/p>\n<table style=\"height: 152px;\" width=\"297\">\n<tbody>\n<tr>\n<td style=\"text-align: center;\">IN0_1<\/td>\n<td style=\"text-align: center;\">IN1_1<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center;\">IN0_2<\/td>\n<td style=\"text-align: center;\">IN1_2<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center;\">&hellip;<\/td>\n<td style=\"text-align: center;\">&hellip;<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center;\">IN0_10<\/td>\n<td style=\"text-align: center;\">IN1_10<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In the first column we have ten samples from IN0 (i.e. PA0 and in my case X-axis), in the second from IN1 (PA1, Y-axis). At that point there are some self question that should be answered:<\/p>\n<ol>\n<li>What kind of value we should expect?<\/li>\n<li>Why we have sampled ten times each axis?<\/li>\n<li>What exactly means &ldquo;the wipers are approximately positioned in the centre of resistive element&rdquo;?<\/li>\n<\/ol>\n<p>STM32 has 12-bit ADC that means sampled values are in the range 0 and 2<sup>12<\/sup> &ndash; 1 (i.e. 4095). We sampled data many times to find means and remove noise. If we are not applying any force on the cursor then wipers are in the rest position and we expect to read 0 from both axis. Smarter reader should figure out that this in not what happens: without noise nor mechanical inaccuracies (i.e. inaccuracies due to the springs that do not place the cursor exactly in the middle of the resistive element) we will read 2047 (2047.5 if it were possible). Indeed since the absolute ratings are [0, 4095] we are placed exactly in the center. To obtain something more &ldquo;pleasant&rdquo; we should subtract 2047 to read data. In this case absolute ratings will become [-2047, 2048]. In this case we could say that negative and positive available value ranges are balanced.<\/p>\n<p>Unfortunately, since there are mechanical inaccuracies even subtracting 2047 we still have an offset. If we want make a good calibration we should get this offset before to start using joystick. We can define the offset like a difference between real and expected value. The offset is a systematic error and that means it is predictable and typically constant to the true value. That means we have to calculate offset just for once and than we will compensate it during the following measurements.<\/p>\n<p>To make things more clear let&rsquo;s do an example. We are reading 2029 from x-axis when cursor is in the rest position (but we was expecting 2047): that means offset is -18, indeed subtracting this offset and 2047 to our value we will obtain 0. After this operation absolute ratings are not anymore [-2047, 2048] but they become [-2029, 2066]. We can balance negative and positive ranges choosing two different multiplying factors. As example if we would to have a range like [-10000,+10000] we can achieve this goal multiplying negative values by (10000\/2029) and positive values by (10000\/2066). If everything has been explained well, next code should be clear:<\/p>\n<pre class=\"lang:c decode:true\">\/*\n    PLAY Embedded demos - Copyright (C) 2014-2016 Rocco Marco Guglielmi\n\n    This file is part of PLAY Embedded demos.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http:\/\/www.apache.org\/licenses\/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n*\/\n\n\/*\n    Tested under ChibiOS\/RT 3.0.1, Project version 1.0\n *\/\n#include \"ch.h\"\n#include \"hal.h\"\n#include \"chprintf.h\"\n\n#define MAX_VALUE            1000\n#define HALF_ADC             2047\n\nBaseSequentialStream * chp = (BaseSequentialStream *) &amp;SD2;\nstatic bool flag = FALSE;\nstatic int32_t x_raw, y_raw, x_offset, y_offset, x_scaled, y_scaled;\n\/*===========================================================================*\/\n\/* ADC related code                                                          *\/\n\/*===========================================================================*\/\n\/*\n * In this demo we want to use a single channel to sample voltage across\n * the potentiometer.\n *\/\n#define MY_NUM_CH                                              2\n#define MY_SAMPLING_NUMBER                                    10\n\nstatic adcsample_t sample_buff[MY_SAMPLING_NUMBER] [MY_NUM_CH];\n\n\/*\n * ADC conversion group.\n * Mode:        Linear buffer, SW triggered.\n * Channels:    IN0 IN1.\n *\/\nstatic const ADCConversionGroup my_conversion_group = {\n  FALSE,                            \/*NOT CIRCULAR*\/\n  MY_NUM_CH,                        \/*NUMB OF CH*\/\n  NULL,                             \/*NO ADC CALLBACK*\/\n  NULL,                             \/*NO ADC ERROR CALLBACK*\/\n  0,                                \/* CR1 *\/\n  ADC_CR2_SWSTART,                  \/* CR2 *\/\n  0,                                \/* SMPR1 *\/\n  ADC_SMPR2_SMP_AN0(ADC_SAMPLE_144) |\n  ADC_SMPR2_SMP_AN1(ADC_SAMPLE_144),\/* SMPR2 *\/\n  ADC_SQR1_NUM_CH(MY_NUM_CH),       \/* SQR1 *\/\n  0,                                \/* SQR2 *\/\n  ADC_SQR3_SQ1_N(ADC_CHANNEL_IN0) |\n  ADC_SQR3_SQ2_N (ADC_CHANNEL_IN1)  \/* SQR3 *\/\n};\n\n\/*===========================================================================*\/\n\/* Generic code.                                                             *\/\n\/*===========================================================================*\/\n\nstatic THD_WORKING_AREA(waThd1, 256);\nstatic THD_FUNCTION(Thd1, arg) {\n\n  (void) arg;\n  chRegSetThreadName(\"LED blinker\");\n  while(TRUE) {\n    palTogglePad(GPIOA, GPIOA_LED_GREEN);\n    chThdSleepMilliseconds(250);\n  }\n}\n\nstatic THD_WORKING_AREA(waThd2, 512);\nstatic THD_FUNCTION(Thd2, arg) {\n  unsigned ii;\n  (void) arg;\n  chRegSetThreadName(\"Joystick sampler\");\n  \/*\n   * Setting up VRx and VRy pins.\n   *\/\n  palSetPadMode(GPIOA, GPIOA_PIN0, PAL_MODE_INPUT_ANALOG);\n  palSetPadMode(GPIOA, GPIOA_PIN1, PAL_MODE_INPUT_ANALOG);\n  \n  \/*\n   * Activates the ADC1 driver.\n   *\/\n  adcStart(&amp;ADCD1, NULL);\n\n  \/* Sampling data for offset computing *\/\n  adcConvert(&amp;ADCD1, &amp;my_conversion_group, (adcsample_t*) sample_buff,\n             MY_SAMPLING_NUMBER);\n  \/* Computing mean removing noise *\/\n  x_raw = 0;\n  y_raw = 0;\n  for(ii = 0; ii &lt; MY_SAMPLING_NUMBER; ii++){\n    x_raw += sample_buff[ii][0];\n    y_raw += sample_buff[ii][1];\n  }\n  x_raw \/= MY_SAMPLING_NUMBER;\n  y_raw \/= MY_SAMPLING_NUMBER;\n  \/* Computing offset *\/\n  x_offset = x_raw - HALF_ADC;\n  y_offset = y_raw - HALF_ADC;\n  while(TRUE) {\n\t\/* Sampling data for demo purpose *\/\n    adcConvert(&amp;ADCD1, &amp;my_conversion_group, (adcsample_t*) sample_buff,\n               MY_SAMPLING_NUMBER);\n\t\/* Computing mean removing noise *\/\n    x_raw = 0;\n    y_raw = 0;\n    for(ii = 0; ii &lt; MY_SAMPLING_NUMBER; ii++){\n      x_raw += sample_buff[ii][0];\n      y_raw += sample_buff[ii][1];\n    }\n    x_raw \/= MY_SAMPLING_NUMBER;\n    y_raw \/= MY_SAMPLING_NUMBER;\n\t\/* Removing offset and centring range *\/\n    x_raw -= (HALF_ADC + x_offset);\n    y_raw -= (HALF_ADC + y_offset);\n\n\t\/* Resizing value properly *\/\n    if(x_raw &lt; 0){\n      x_scaled = x_raw * MAX_VALUE \/ (HALF_ADC + x_offset);\n    }\n    else{\n      x_scaled = x_raw * MAX_VALUE \/ (HALF_ADC - x_offset);\n    }\n    if(y_raw &lt; 0){\n      y_scaled = y_raw * MAX_VALUE \/ (HALF_ADC + y_offset);\n    }\n    else{\n      y_scaled = y_raw * MAX_VALUE \/ (HALF_ADC - y_offset);\n    }\n\t\/* Data ready *\/\n    flag = TRUE;\n  }\n}\n\n\n\n\/*\n * Application entry point.\n *\/\nint main(void) {\n\n  \/*\n   * System initializations.\n   * - HAL initialization, this also initializes the configured device drivers\n   *   and performs the board-specific initializations.\n   * - Kernel initialization, the main() function becomes a thread and the\n   *   RTOS is active.\n   *\/\n  halInit();\n  chSysInit();\n  \n  \/*\n   * Activates the serial driver 2 using the driver default configuration.\n   *\/\n  sdStart(&amp;SD2, NULL);\n\n  chThdCreateStatic(waThd1, sizeof(waThd1), NORMALPRIO + 1, Thd1, NULL);\n  chThdCreateStatic(waThd2, sizeof(waThd2), NORMALPRIO + 1, Thd2, NULL);\n  \n  \/*\n   * Setting up SW pin.\n   *\/\n  palSetPadMode(GPIOA, GPIOA_PIN4, PAL_MODE_INPUT_PULLUP);\n  \n  \/*\n   * Normal main() thread activity, in this demo it checks flag status. If flag\n   * is true, last value is printed and then flag is lowered. \n   *\/\n  while (TRUE) {\n\n    if (flag) {\n      chprintf(chp, \"PLAY Embedded + ChibiOS\\\\RT: Slider demo \\r\\n\");\n      chprintf(chp, \"X:%4d, Y:%4d \", x_scaled, y_scaled);\n      if(palReadPad(GPIOA, 4) == PAL_LOW){\n        chprintf(chp, \"BUTTON PRESSED\");\n      }\n      chprintf(chp, \"\\r\\n\");\n      flag = FALSE;\n      chThdSleepMilliseconds(150);\n      chprintf(chp, \"\\033[2J\\033[1;1H\");\n    }\n    chThdSleepMilliseconds(1);\n  }\n}\n<\/pre>\n<h3 id=\"5_Project_download\" class=\"level_1\">Project download<\/h3>\n<p>The attached demo has been tested under ChibiOS 20.3.x.<\/p>\n<p><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2021\/07\/RT-STM32F401RE-NUCLEO64-ADC-Joystick-216.7z\">RT-STM32F401RE-NUCLEO64-ADC-Joystick-216<\/a><\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>2-axis and a key button The joystick proposed here is much known between makers. It provides two axis and a key button and every axis is actually a potentiometer: that means axis data is analogue and we need to use ADC to read its positioning. Potentiometers are provided of springs so, without forcing, wipers are [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":2398,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1225],"tags":[],"coauthors":[241],"class_list":["post-2161","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-interfacing-externals","red"],"views":13821,"jetpack_featured_media_url":"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2016\/04\/Reading-a-Joystick-on-STM32-using-ChibiOS.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts\/2161","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/comments?post=2161"}],"version-history":[{"count":0,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts\/2161\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/media\/2398"}],"wp:attachment":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/media?parent=2161"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/categories?post=2161"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/tags?post=2161"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/coauthors?post=2161"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}