{"id":321,"date":"2025-08-12T18:44:23","date_gmt":"2025-08-12T18:44:23","guid":{"rendered":"https:\/\/cascajolabs.es\/?p=321"},"modified":"2025-08-12T18:44:24","modified_gmt":"2025-08-12T18:44:24","slug":"stm32-oled-oscilloscope-music","status":"publish","type":"post","link":"https:\/\/cascajolabs.es\/?p=321","title":{"rendered":"STM32 OLED &#8211; Oscilloscope music"},"content":{"rendered":"\n<p>Today I\u2019m writing  about a small project I made to test some peripherals of the STM32 microcontroller.<br>In this case, I\u2019m using an I2C OLED display and two of the board\u2019s ADCs. The goal is to display the input signals on the screen as if it were an oscilloscope in XY mode.<\/p>\n\n\n\n<p>Here\u2019s a demo video:<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Oscilloscope Music | OLED | STM32F4 #electronics\" width=\"750\" height=\"422\" src=\"https:\/\/www.youtube.com\/embed\/HrGwj-HIsLo?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>For this, this library from <a href=\"https:\/\/github.com\/afiskon\/stm32-ssd1306\">Github<\/a> will be included.<br>After that, the ADCs (same configuration) and the I2C interface will be configured.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>static void MX_ADC1_Init(void)\n{\n\n  \/* USER CODE BEGIN ADC1_Init 0 *\/\n\n  \/* USER CODE END ADC1_Init 0 *\/\n\n  ADC_ChannelConfTypeDef sConfig = {0};\n\n  \/* USER CODE BEGIN ADC1_Init 1 *\/\n\n  \/* USER CODE END ADC1_Init 1 *\/\n\n  \/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)\n  *\/\n  hadc1.Instance = ADC1;\n  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;\n  hadc1.Init.Resolution = ADC_RESOLUTION_12B;\n  hadc1.Init.ScanConvMode = DISABLE;\n  hadc1.Init.ContinuousConvMode = DISABLE;\n  hadc1.Init.DiscontinuousConvMode = DISABLE;\n  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;\n  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;\n  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;\n  hadc1.Init.NbrOfConversion = 1;\n  hadc1.Init.DMAContinuousRequests = DISABLE;\n  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;\n  if (HAL_ADC_Init(&amp;hadc1) != HAL_OK)\n  {\n    Error_Handler();\n  }\n\n  \/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.\n  *\/\n  sConfig.Channel = ADC_CHANNEL_1;\n  sConfig.Rank = 1;\n  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;\n  if (HAL_ADC_ConfigChannel(&amp;hadc1, &amp;sConfig) != HAL_OK)\n  {\n    Error_Handler();\n  }\n  \/* USER CODE BEGIN ADC1_Init 2 *\/\n\n  \/* USER CODE END ADC1_Init 2 *\/\n\n}\nstatic void MX_I2C1_Init(void)\n{\n\n  \/* USER CODE BEGIN I2C1_Init 0 *\/\n\n  \/* USER CODE END I2C1_Init 0 *\/\n\n  \/* USER CODE BEGIN I2C1_Init 1 *\/\n\n  \/* USER CODE END I2C1_Init 1 *\/\n  hi2c1.Instance = I2C1;\n  hi2c1.Init.ClockSpeed = 100000;\n  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;\n  hi2c1.Init.OwnAddress1 = 0;\n  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;\n  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;\n  hi2c1.Init.OwnAddress2 = 0;\n  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;\n  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;\n  if (HAL_I2C_Init(&amp;hi2c1) != HAL_OK)\n  {\n    Error_Handler();\n  }\n  \/* USER CODE BEGIN I2C1_Init 2 *\/\n\n  \/* USER CODE END I2C1_Init 2 *\/\n\n}<\/code><\/pre>\n\n\n\n<p>The most important part is adapting the audio jack output to the ADC inputs, you need to get rid of the negative part of the signal.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>And here\u2019s the code for <strong>main.c<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/* USER CODE BEGIN PV *\/\n#define CLEAR_INTERVAL 200\n\/* USER CODE END PV *\/\n..........\n...\n.\n\/* Private includes ----------------------------------------------------------*\/\n\/* USER CODE BEGIN Includes *\/\n#include \"ssd1306.h\"\n#include \"ssd1306_fonts.h\"\n#include \"math.h\"\n#include &lt;stdio.h>\n\n\/* USER CODE END Includes *\/\n.........\n...\n.\n\/* Private user code ---------------------------------------------------------*\/\n\/* USER CODE BEGIN 0 *\/\n#define NUM_SAMPLES 128\nuint16_t adc1_buffer&#91;NUM_SAMPLES];\nuint16_t adc2_buffer&#91;NUM_SAMPLES];\n\/* USER CODE END 0 *\/\n.........\n...\n.\n  \/* USER CODE BEGIN 2 *\/\n  ssd1306_Init();\n  ssd1306_Fill(Black);\n  ssd1306_SetCursor(2, 10);\n  ssd1306_WriteString(\"Cascajo\", Font_11x18, White);\n  ssd1306_SetCursor(2, 30);\n  ssd1306_WriteString(\"Labs\", Font_11x18, White);\n  ssd1306_UpdateScreen();\n  \/* USER CODE END 2 *\/\n\n  \/* USER CODE BEGIN WHILE *\/\n  HAL_ADC_Start(&amp;hadc1);\n  HAL_ADC_Start(&amp;hadc2);\n\n  ssd1306_Fill(Black);\n  uint32_t counter = 0;\n  uint16_t valL = 0;\n  uint16_t valR = 0;\n  while (1)\n  {\n\t    \/\/ Read L (ADC1)\n\t    HAL_ADC_Start(&amp;hadc1);\n\t    if (HAL_ADC_PollForConversion(&amp;hadc1, 1) == HAL_OK) {\n\t        valL = HAL_ADC_GetValue(&amp;hadc1);\n\t    }\n\t    HAL_ADC_Stop(&amp;hadc1);\n\n\t    \/\/ Read R (ADC2)\n\t    HAL_ADC_Start(&amp;hadc2);\n\t    if (HAL_ADC_PollForConversion(&amp;hadc2, 1) == HAL_OK) {\n\t        valR = HAL_ADC_GetValue(&amp;hadc2);\n\t    }\n\t    HAL_ADC_Stop(&amp;hadc2);\n\n\t    \/\/ OLED\n\t    int x = ((int)valL - <strong>1400<\/strong>) \/ 32 + 64;\n\t    int y = 32 - ((int)valR - <strong>1300<\/strong>) \/ 32;\n\n\t    if (x >= 0 &amp;&amp; x &lt; 128 &amp;&amp; y >= 0 &amp;&amp; y &lt; 64) {\n\t        ssd1306_DrawPixel(x, y, White);\n\t    }\n\n\t    counter++;\n\t    if (counter >= CLEAR_INTERVAL) {\n\t        ssd1306_UpdateScreen();\n\t        ssd1306_Fill(Black);\n\t        counter = 0;\n\t    }\n    \/* USER CODE END WHILE *\/\n\n    \/* USER CODE BEGIN 3 *\/\n  }\n  \/* USER CODE END 3 *\/<\/code><\/pre>\n\n\n\n<p>These values (1400 and 1300) are used to center it on the screen and were determined by simple visual adjustment.<\/p>\n\n\n\n<p>With the same logic, you can create other simple projects that use different I2C devices, such as a gyroscope \u2014 like in this demo video:<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"STM32F4 | MPU6050 | OLED  #electronics\" width=\"750\" height=\"422\" src=\"https:\/\/www.youtube.com\/embed\/jPVGoTOkaLg?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>Today I\u2019m writing about a small project I made to test some peripherals of the STM32 microcontroller.In this case, I\u2019m using an I2C OLED display and two of the board\u2019s ADCs. The goal is to display the input signals on the screen as if it were an oscilloscope in XY [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":327,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[50,84,80,82],"class_list":["post-321","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","tag-oled","tag-oscilloscope","tag-stm32","tag-stm32f7"],"_links":{"self":[{"href":"https:\/\/cascajolabs.es\/index.php?rest_route=\/wp\/v2\/posts\/321","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cascajolabs.es\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cascajolabs.es\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cascajolabs.es\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cascajolabs.es\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=321"}],"version-history":[{"count":4,"href":"https:\/\/cascajolabs.es\/index.php?rest_route=\/wp\/v2\/posts\/321\/revisions"}],"predecessor-version":[{"id":328,"href":"https:\/\/cascajolabs.es\/index.php?rest_route=\/wp\/v2\/posts\/321\/revisions\/328"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cascajolabs.es\/index.php?rest_route=\/wp\/v2\/media\/327"}],"wp:attachment":[{"href":"https:\/\/cascajolabs.es\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=321"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cascajolabs.es\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=321"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cascajolabs.es\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=321"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}