{"id":4299,"date":"2019-10-16T20:00:08","date_gmt":"2019-10-16T18:00:08","guid":{"rendered":"https:\/\/msalamon.pl\/?p=4299"},"modified":"2025-12-27T18:38:00","modified_gmt":"2025-12-27T17:38:00","slug":"adc-on-stm32-in-several-ways-joystick","status":"publish","type":"post","link":"https:\/\/msalamon.pl\/en\/adc-on-stm32-in-several-ways-joystick\/","title":{"rendered":"ADC on STM32 in Several Ways \u2013 Joystick"},"content":{"rendered":"\n<p>Microcontroller projects often need to measure analog signals. Unfortunately, they themselves only understand digital sequences that can take only two states. Most often, these are the ground and supply levels. To measure an analog value, you need to use an analog-to-digital converter \u2014 ADC for short. Practically every modern microcontroller is equipped with such a converter. STM32, of course, too.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h1 class=\"wp-block-heading\">ADC in STM32<\/h1>\n\n\n\n<p>The analog-to-digital converter subsystem in STM32 microcontrollers is fairly extensive. Some MCUs even have two or more such converters. The main features of the built-in ADCs include:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Configurable resolution up to 12 bits<\/li>\n\n\n\n<li>A variety of interrupts, e.g., end of regular conversion, injected conversion, or analog watchdog<\/li>\n\n\n\n<li>Single or continuous conversion mode<\/li>\n\n\n\n<li>Automatic channel scanning<\/li>\n\n\n\n<li>Programmable sampling time<\/li>\n\n\n\n<li>Generating read requests via DMA<\/li>\n<\/ul>\n\n\n\n<p>These are just the most important items I picked from the documentation. Okay, but how does such a converter work?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Sampling<\/h3>\n\n\n\n<p>An analog signal can take various voltage values. Moreover, it is continuous in time. Sampling consists in determining the measurement moments of the analog signal fed to the ADC input. As a result of sampling, we get discrete points that belong to the continuous waveform.<\/p>\n\n\n\n<p>What matters here is the <strong>sampling theorem (Kotelnikov\u2013Shannon)<\/strong>. It says that to be able to satisfactorily reconstruct the original analog signal from the sampled digital signal, <strong>you must sample at a frequency at least twice as high as the highest frequency component of the signal<\/strong>.<\/p>\n\n\n\n<p>In short \u2014 when sampling a 1 kHz sine signal, you should do it at least at 2 kHz. A more life-like example \u2014 basic audio sampling is done at 44.1 kHz, because it is assumed that the audio signal has a highest frequency component of about 20 kHz.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Quantization<\/h3>\n\n\n\n<p>This is the so\u2011called digital measurement. The sampled quantity is represented as the nearest digital value. <strong>Quantization strongly depends on the converter\u2019s resolution.<\/strong> The measured value is expressed as a number of quanta, i.e., the number of the smallest parts into which the full voltage range of the converter can be divided.<\/p>\n\n\n\n<p>With a 12-bit converter, its resolution is 4096 and that&#8217;s how many quanta we have. Let\u2019s say the reference voltage, i.e., the one against which we compare our signal, is 3.3 V. A measured value of 4095 corresponds to that voltage. Feeding 1 V to the ADC input, the sample will have the value 1\/3.3 * 4095 = 1240.909. As you can see, this value does not fall exactly on an even quantum, so the converter will assign it to <strong>1241<\/strong>. That\u2019s what quantization is about \ud83d\ude42<\/p>\n\n\n\n<p>The method the converter uses is somewhat different than multiplying voltages like that. The converter does not know the value of the reference voltage. It can, for example, compare the analog signal with a rising staircase signal within the reference voltage range, and then store the number of steps. Remember that after this stage we get a number of quanta, not a voltage value.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Encoding<\/h3>\n\n\n\n<p>The last stage is <strong>adapting the measured number of quanta to the encoding<\/strong> used in the application. In microcontrollers, this is most often the binary representation of the number of quanta. In the ADC settings, for example, you can align this value to the left or right in the 16-bit ADC output register.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reading a joystick signal on STM32<\/h2>\n\n\n\n<p>Since you now roughly know how the ADC works, we can move on to using it. The <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-3-osiowy-joystick-analogowy\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=joystick&amp;utm_content=Text\">Joystick<\/a> is nothing more than two potentiometers operating as a resistive divider. The two extreme legs of the potentiometers are connected to + and \u2212 supply. The center pin is brought out, and the voltage on it reflects the position of the stick.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_potentiometers.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"145\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_potentiometers-300x145.jpg\" alt=\"\" class=\"wp-image-1213\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_potentiometers-300x145.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_potentiometers-1024x494.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_potentiometers-768x371.jpg 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_potentiometers-24x12.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_potentiometers-36x17.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_potentiometers-160x77.jpg 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_potentiometers.jpg 1104w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>Since we have two axes, there are two such potentiometers. As you can guess, we will measure two independent signals. One for the X-axis position, the other for the Y-axis.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-3-osiowy-joystick-analogowy\/?utm_source=blog&amp;utm_medium=banner&amp;utm_campaign=joystick&amp;utm_content=joystick\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"400\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/Joystick-baner.jpg\" alt=\"\" class=\"wp-image-1224\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/Joystick-baner.jpg 1200w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/Joystick-baner-300x100.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/Joystick-baner-1024x341.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/Joystick-baner-768x256.jpg 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/Joystick-baner-24x8.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/Joystick-baner-36x12.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/Joystick-baner-160x53.jpg 160w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Today I\u2019m working on a <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/nucleo-f411re\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=joystick&amp;utm_content=Text\">Nucleo F411RE<\/a> together with STM32CubeIDE and the HAL F4 libraries version 1.24.1.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Schematic<\/h3>\n\n\n\n<p>I connected the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-3-osiowy-joystick-analogowy\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=joystick&amp;utm_content=Text\">joystick<\/a> to channels 6 and 7 of the analog-to-digital converter.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><figure><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_schematic.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_schematic-1024x533.png\" alt=\"\" width=\"750\" height=\"390\" class=\"aligncenter wp-image-1214 size-large\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_schematic-1024x533.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_schematic-300x156.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_schematic-768x400.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_schematic-24x12.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_schematic-36x19.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_schematic-154x80.png 154w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_schematic.png 1230w\" sizes=\"auto, (max-width: 750px) 100vw, 750px\"><\/a><\/figure>ADC readout code \u2013 Polling<\/h2>\n\n\n\n<p>First I\u2019ll show you the simplest way to read from the ADC, i.e., \u201cmanually\u201d triggering ADC conversion and reading the value.<\/p>\n\n\n\n<p>Let\u2019s go to Cube. Create a project for the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/nucleo-f411re\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=joystick&amp;utm_content=Text\">Nucleo F411RE<\/a> board. Accept the default configuration of the peripherals on the board. This speeds things up a bit \u2014 UART is ready out of the box and HCLK is set to a fairly high value of 84 MHz.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><figure><a href=\"https:\/\/sklep.msalamon.pl\/kategoria-produktu\/dev-boardy\/stm32-nucleo\/?utm_source=blog&amp;utm_medium=banner&amp;utm_campaign=joystick&amp;utm_content=nucleo\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner.jpg\" alt=\"\" width=\"1200\" height=\"400\" class=\"aligncenter wp-image-1593 size-full\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner.jpg 1200w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-300x100.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-1024x341.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-768x256.jpg 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\"><\/a><\/figure><\/h1>\n\n\n\n<p>I mentioned I connected to channels IN6 and IN7, so select them in the ADC1 configuration.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_input_config.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"623\" height=\"323\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_input_config.jpg\" alt=\"\" class=\"wp-image-1215\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_input_config.jpg 623w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_input_config-300x156.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_input_config-24x12.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_input_config-36x19.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_input_config-154x80.jpg 154w\" sizes=\"auto, (max-width: 623px) 100vw, 623px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>You will notice that the pins on the model next to it light up. Now you can go to the ADC settings. You can set the clock prescaler as you like. I have it set to divide by 4. Make sure you have 12-bit resolution, although of course a lower one will also work correctly. The rest is default as in the screenshot below.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_polling_config.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"429\" height=\"536\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_polling_config.jpg\" alt=\"\" class=\"wp-image-1216\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_polling_config.jpg 429w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_polling_config-240x300.jpg 240w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_polling_config-19x24.jpg 19w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_polling_config-29x36.jpg 29w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_polling_config-64x80.jpg 64w\" sizes=\"auto, (max-width: 429px) 100vw, 429px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>And that\u2019s it for the configuration. You can generate the code and go to the editor.<\/p>\n\n\n\n<p>As I mentioned earlier, polling is \u201cmanual\u201d querying of the ADC. In such a case you need to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Turn on the ADC if needed<\/li>\n\n\n\n<li>Trigger a conversion, i.e., sampling, quantization, and encoding<\/li>\n\n\n\n<li>Wait for the conversion to complete<\/li>\n\n\n\n<li>Read the measured value<\/li>\n<\/ol>\n\n\n\n<p>The first and second points are handled by the function<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc)<\/pre>\n\n\n\n<p>You implement the third point using<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout)<\/pre>\n\n\n\n<p>The last step is simply reading from the register. There is of course a function for that<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc)<\/pre>\n\n\n\n<p>To get the next sample, repeat the operation. Alright, alright! I don\u2019t know if you know, but the ADC can convert only one channel at a time. That means it must have specified somewhere which one to handle. By default it\u2019s the first one selected, in our case IN6. To read other channels, before triggering the conversion you need to indicate which channel to use.<\/p>\n\n\n\n<p>I wrote a simple function for this based on the ADC initialization code that Cube generated.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void ADC_SetActiveChannel(ADC_HandleTypeDef *hadc, uint32_t AdcChannel)\n{\n  ADC_ChannelConfTypeDef sConfig = {0};\n  sConfig.Channel = AdcChannel;\n  sConfig.Rank = 1;\n  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;\n  if (HAL_ADC_ConfigChannel(hadc, &amp;amp;sConfig) != HAL_OK)\n  {\n   Error_Handler();\n  }\n}<\/pre>\n\n\n\n<p>As usual, the code is in the fourth user section.<\/p>\n\n\n\n<p>Ultimately, reading two channels and sending them over UART2 looks like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/* USER CODE BEGIN 2 *\/\n  HAL_ADC_Start(&amp;amp;hadc1); \/\/ You have to start ADC\n\/* USER CODE END 2 *\/\n\n\/* Infinite loop *\/\n\/* USER CODE BEGIN WHILE *\/\n  while (1)\n  {\n    if(HAL_ADC_PollForConversion(&amp;amp;hadc1, 10) == HAL_OK)\n    {\n      Joystick[0] = HAL_ADC_GetValue(&amp;amp;hadc1); \/\/ Get X value\n      ADC_SetActiveChannel(&amp;amp;hadc1, ADC_CHANNEL_7);\n      HAL_ADC_Start(&amp;amp;hadc1);\n    }\n\n    if(HAL_ADC_PollForConversion(&amp;amp;hadc1, 10) == HAL_OK)\n    {\n      Joystick[1] = HAL_ADC_GetValue(&amp;amp;hadc1); \/\/ Get Y value\n      ADC_SetActiveChannel(&amp;amp;hadc1, ADC_CHANNEL_6);\n      HAL_ADC_Start(&amp;amp;hadc1);\n    }\n\n    sprintf((char*)UartMessage, \"X: %d, Y: %d\\n\\r\", Joystick[0], Joystick[1]);\n    UART2_Print(UartMessage);\n\/* USER CODE END WHILE *\/\n\n\/* USER CODE BEGIN 3 *\/\n}<\/pre>\n\n\n\n<p>As a result, you get ADC values in the terminal. Without touching the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-3-osiowy-joystick-analogowy\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=joystick&amp;utm_content=Text\">joystick<\/a> knob, it is in the center position. Half of 12-bit resolution is 2048, so we should expect a value around that.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_middle.png\"><img loading=\"lazy\" decoding=\"async\" width=\"688\" height=\"440\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_middle.png\" alt=\"\" class=\"wp-image-1217\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_middle.png 688w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_middle-300x192.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_middle-24x15.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_middle-36x23.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_middle-125x80.png 125w\" sizes=\"auto, (max-width: 688px) 100vw, 688px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>As you can see, it varies, but it\u2019s always somewhere around 2048. Unfortunately, ADC measurements can fluctuate quite a bit. Many factors affect this, but that\u2019s not today\u2019s topic. You can, for example, collect 10 samples and average them. That will certainly reduce the fluctuations.<\/p>\n\n\n\n<p>I don\u2019t think it\u2019s worth examining the conversion time here. I\u2019ll just mention that its length is affected by factors such as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sampling time<\/li>\n\n\n\n<li>ADC clocking<\/li>\n\n\n\n<li>Resolution<\/li>\n<\/ul>\n\n\n\n<p>Converters can be very fast. In STM32, some reach a maximum sampling of, for example, 5 MSPS (Megasamples per Second) in the STM32F303. External devices can be even faster.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/lamik\/Joystick_ADC\/tree\/Polling\" target=\"_blank\" rel=\"noopener\">You can find the code with polling here<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">ADC readout code \u2013 Continuous reading DMA<\/h2>\n\n\n\n<p>We can\u2019t skip DMA \ud83d\ude42 Moreover, you can start the ADC once and forget about it completely. Continuous Conversion Mode enables this. It means that the ADC starts the next measurement right after the previous one finishes, without our intervention.<\/p>\n\n\n\n<p>Additionally, if you want to measure more than one channel, Scan Conversion Mode will be useful. Thanks to it, the ADC will switch channels by itself. Nice, isn\u2019t it?<\/p>\n\n\n\n<p>On top of that, let\u2019s add DMA, which will read the measurements on its own and write them to the proper place in memory. No more manual polling and reading!<\/p>\n\n\n\n<p>Let\u2019s do it. Let\u2019s configure this magic. Let\u2019s also slightly improve the fluctuations of the readings. Here\u2019s the configuration.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_continuous_config.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"431\" height=\"628\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_continuous_config.jpg\" alt=\"\" class=\"wp-image-1218\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_continuous_config.jpg 431w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_continuous_config-206x300.jpg 206w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_continuous_config-16x24.jpg 16w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_continuous_config-25x36.jpg 25w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_continuous_config-55x80.jpg 55w\" sizes=\"auto, (max-width: 431px) 100vw, 431px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>From the top. I increased the ADC prescaler. It will run a bit longer, but it should improve the stability of the reading (longer sampling).<\/p>\n\n\n\n<p>I enabled <strong>Scan Conversion Mode and Continuous Conversion Mode<\/strong> for obvious reasons.<\/p>\n\n\n\n<p>I enabled <strong>DMA Continuous Requests<\/strong>. Let\u2019s allow the ADC to prod the DMA itself that it has data ready to read.<\/p>\n\n\n\n<p>Make sure that <strong>End of Conversion Selection<\/strong> is set to end of single-channel conversion.<\/p>\n\n\n\n<p>Now in the <strong>ADC_Regular_ConversionMode set the number of conversions to 2<\/strong>. We want the ADC to automatically scan two channels.<\/p>\n\n\n\n<p>Here I also set the sampling of each channel to the maximum value of 480 clock cycles. This will affect the stability of the readings.<\/p>\n\n\n\n<p>In the <strong>DMA Settings<\/strong> tab you also need to set the DMA write to the buffer to be circular.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"736\" height=\"507\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_dma_config.jpg\" alt=\"\" class=\"wp-image-1399\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_dma_config.jpg 736w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_dma_config-300x207.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_dma_config-24x17.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_dma_config-36x25.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_dma_config-116x80.jpg 116w\" sizes=\"auto, (max-width: 736px) 100vw, 736px\" \/><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>With this configuration prepared, you can generate the code and move on to code.<\/p>\n\n\n\n<p>We\u2019ve done a lot, but what to put into the code? Well, the entire code that will convert the analog signal and write it to the pre-prepared variables is, attention:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/* USER CODE BEGIN 2 *\/\n  HAL_ADC_Start_DMA(&amp;amp;hadc1, Joystick, 2); \/\/ You have to start ADC with DMA\n\/* USER CODE END 2 *\/\n\n\/* Infinite loop *\/\n\/* USER CODE BEGIN WHILE *\/\nwhile (1)\n{\n\n  sprintf((char*)UartMessage, \"X: %d, Y: %d\\n\\r\", (int)Joystick[0], (int)Joystick[1]);\n  UART2_Print(UartMessage);\n\/* USER CODE END WHILE *\/\n\n\/* USER CODE BEGIN 3 *\/<\/pre>\n\n\n\n<p>Yes! It\u2019s enough to start the ADC in DMA mode and the readings from both channels will flow into the chosen array (for me uint16_t Joystick[2]). It\u2019s worth mentioning here that they flow at the maximum possible speed.<\/p>\n\n\n\n<p>How fast in this configuration? Let\u2019s check by toggling a test pin on each completed DMA transfer.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"98\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling-1024x98.jpg\" alt=\"\" class=\"wp-image-1219\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling-1024x98.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling-300x29.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling-768x74.jpg 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling-1536x147.jpg 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling-24x2.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling-36x3.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling-160x15.jpg 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_max_dma_sampling.jpg 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>Both channels are read every 92 \u00b5s, which comes out to about 10.8 kHz. Nothing crazy, but remember that right now we have a very long sampling time.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/lamik\/Joystick_ADC\/tree\/DMA_Continuous\" target=\"_blank\" rel=\"noopener\">You can find the continuous readout code here<\/a>.<\/p>\n\n\n\n<p>Alright, but if I wanted to get 44.1 kHz, should I just shorten the sampling and try to hit 44.1 kHz this way? Reduce sampling time \u2014 yes, but should you aim blindly so that it\u2019s the full ADC speed? Not necessarily. There\u2019s a better method!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">ADC readout code \u2013 Timer triggering conversion<\/h2>\n\n\n\n<p>You can harness one of the timers to trigger conversion at equal time intervals. Thanks to this, by setting the timer to 44.1 kHz we will be sure that subsequent samples are taken at the right times. Let\u2019s do this!<\/p>\n\n\n\n<p>I\u2019ll leave the conversion time as it was. Let\u2019s try to aim the timer at 200 Hz regular sampling. That will be sufficient for working with the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-3-osiowy-joystick-analogowy\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=joystick&amp;utm_content=Text\">joystick<\/a>. Such a device does not require frequent reading. Let\u2019s move to Cube. First, the ADC.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_config.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"501\" height=\"642\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_config.jpg\" alt=\"\" class=\"wp-image-1221\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_config.jpg 501w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_config-234x300.jpg 234w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_config-19x24.jpg 19w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_config-28x36.jpg 28w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_config-62x80.jpg 62w\" sizes=\"auto, (max-width: 501px) 100vw, 501px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>As you can see, I <strong>disabled Continuous Conversion Mode<\/strong>. This time it won\u2019t be the ADC deciding when to start a conversion.<\/p>\n\n\n\n<p>In the <strong>ADC_Regular_ConversionMode<\/strong> section there is a setting called <strong>External Trogger Conversion Source<\/strong>. You can choose several timers and the trigger type. It\u2019s either a Capture Compare Event or Out Event. <strong>Set Timer 2 Trigger Out Event.<\/strong> It will occur on TIM2 counter overflow.<\/p>\n\n\n\n<p>Now go to the TIM2 settings.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_timerconfig.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"503\" height=\"643\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_timerconfig.jpg\" alt=\"\" class=\"wp-image-1220\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_timerconfig.jpg 503w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_timerconfig-235x300.jpg 235w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_timerconfig-19x24.jpg 19w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_timerconfig-28x36.jpg 28w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_trig_timerconfig-63x80.jpg 63w\" sizes=\"auto, (max-width: 503px) 100vw, 503px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>Set <strong>Clock Source<\/strong> to <strong>Internal Clock<\/strong>. With the clock supplied, now configure the overflow.<\/p>\n\n\n\n<p>We want to get 200 Hz, so each overflow should occur exactly every 5 ms. First set the Prescaler. The \u201cmain\u201d frequency is 84 MHz. Dividing by 84, you get a 1 \u00b5s tick. Divide further. A division by 8400 gives 0.1 ms. Sounds good, right? Enter 8400-1 or 8399.<\/p>\n\n\n\n<p>Now set the counting period to 49 to get 5 ms per counter overflow.<\/p>\n\n\n\n<p>It\u2019s important to set TRGO, which is the trigger for the ADC. Set <strong>Trigger Event Selection<\/strong> to <strong>Update Event<\/strong> to pass the appropriate event to the ADC. Now let\u2019s move to the code.<\/p>\n\n\n\n<p>The code is very similar to the one that handles ADC + DMA in continuous mode.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/* USER CODE BEGIN 2 *\/\n  HAL_TIM_Base_Start(&amp;amp;htim2);\n  HAL_ADC_Start_DMA(&amp;amp;hadc1, (uint32_t*)Joystick, 2); \/\/ You have to start ADC with DMA\n\/* USER CODE END 2 *\/\n\n\/* Infinite loop *\/\n\/* USER CODE BEGIN WHILE *\/\nwhile (1)\n{\n\n  sprintf((char*)UartMessage, \"X: %d, Y: %d\\n\\r\", (int)Joystick[0], (int)Joystick[1]);\n  UART2_Print(UartMessage);\n\/* USER CODE END WHILE *\/\n\n\/* USER CODE BEGIN 3 *\/\n}\n\/* USER CODE END 3 *\/<\/pre>\n\n\n\n<p>The only difference is starting the timer before starting the ADC. Now sampling should happen every 5 ms.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"100\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling-1024x100.jpg\" alt=\"\" class=\"wp-image-1222\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling-1024x100.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling-300x29.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling-768x75.jpg 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling-1536x150.jpg 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling-24x2.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling-36x4.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling-160x16.jpg 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/10\/joystick_adc_timer_dma_sampling.jpg 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>It came out to 4.93 ms, i.e., about 202.8 Hz, so very close. One might wonder why not exactly 5. Setting the overflow to 50, I got 5.03 ms per the analyzer, so a bit better. The question is how accurate my analyzer is. I suspect it\u2019s not high-end equipment \ud83d\ude42<\/p>\n\n\n\n<p>Try to achieve audio sampling, i.e., 44.1 kHz. <a href=\"https:\/\/github.com\/lamik\/Joystick_ADC\/tree\/DMA_timer_triger\" target=\"_blank\" rel=\"noopener\">You can find the timer-triggered code here<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>As you can see, the ADCs built into STM32 have huge capabilities. Their configuration is really extensive, and at the same time simple with Cube. When configured well, using the ADC can come down to just starting it, which is a huge convenience. Arduino doesn\u2019t have this \ud83d\ude09<\/p>\n\n\n\n<p><strong>What do you think about the ADC in STM32? Share your opinion in the comments.<\/strong><\/p>\n\n\n\n<p>You can find the full project along with the library on my GitHub as usual: <a href=\"https:\/\/github.com\/lamik\/Joystick_ADC\" target=\"_blank\" rel=\"noopener\">link<\/a><\/p>\n\n\n\n<p><span>If you noticed any error, disagree with something, would like to add something important, or simply want to discuss this topic, write a comment. Remember that the discussion should be polite and in accordance with the rules of the Polish language.<\/span><\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;4299&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;0&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;0&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;0&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;0\\\/5 - (0 votes)&quot;,&quot;size&quot;:&quot;24&quot;,&quot;title&quot;:&quot;ADC on STM32 in Several Ways \u2013 Joystick&quot;,&quot;width&quot;:&quot;0&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} - ({count} {votes})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 0px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 19.2px;\">\n            <span class=\"kksr-muted\"><\/span>\n    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Microcontroller projects often need to measure analog signals. Unfortunately, they themselves only understand digital sequences that can take only two states. Most often, these are the ground and supply levels. To measure an analog value, you need to use an analog-to-digital converter \u2014 ADC for short. Practically every modern microcontroller [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3317,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[160],"tags":[175,176,174,177],"class_list":["post-4299","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-stm32","tag-electronics","tag-programming","tag-stm32","tag-stm32cubemx"],"_links":{"self":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4299","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/comments?post=4299"}],"version-history":[{"count":3,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4299\/revisions"}],"predecessor-version":[{"id":4398,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4299\/revisions\/4398"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media\/3317"}],"wp:attachment":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media?parent=4299"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/categories?post=4299"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/tags?post=4299"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}