{"id":4316,"date":"2026-03-30T09:09:35","date_gmt":"2026-03-30T09:09:35","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4316"},"modified":"2026-03-30T09:11:57","modified_gmt":"2026-03-30T09:11:57","slug":"stm32-and-mongoose-web-server-part-5-linking-ui-to-peripherals","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4316","title":{"rendered":"STM32 and Mongoose Web server Part 5:\u00a0Linking UI to Peripherals"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"683\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/ChatGPT-Image-Jan-17-2026-at-10_13_02-AM-1-1024x683.png\" alt=\"\" class=\"wp-image-4317\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/ChatGPT-Image-Jan-17-2026-at-10_13_02-AM-1-1024x683.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/ChatGPT-Image-Jan-17-2026-at-10_13_02-AM-1-300x200.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/ChatGPT-Image-Jan-17-2026-at-10_13_02-AM-1-768x512.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/ChatGPT-Image-Jan-17-2026-at-10_13_02-AM-1-1150x767.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/ChatGPT-Image-Jan-17-2026-at-10_13_02-AM-1-750x500.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/ChatGPT-Image-Jan-17-2026-at-10_13_02-AM-1-400x267.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/ChatGPT-Image-Jan-17-2026-at-10_13_02-AM-1-250x167.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/ChatGPT-Image-Jan-17-2026-at-10_13_02-AM-1.png 1536w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>In this fifth part of the guide, we connect the web user interface to the STM32 hardware peripherals to enable real interaction with the system. This demonstrates how browser-based controls can monitor and manipulate embedded resources, bridging the gap between the device firmware and the user-facing interface.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>In this guide, we shall cover the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Hardware setup.<\/li>\n\n\n\n<li>STM32CubeMX configuration.<\/li>\n\n\n\n<li>Firmware Development.<\/li>\n\n\n\n<li>Results.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">1. Hardware Setup:<\/h2>\n\n\n\n<p>The hardware setup as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-1024x576.jpg\" alt=\"\" class=\"wp-image-4318\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-1024x576.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-300x169.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-768x432.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-1536x864.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-2048x1152.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-1150x647.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-750x422.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-400x225.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_10-02-27-250x141.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Note: Resistor value is 220Ohm.<\/p>\n\n\n\n<p>The connection as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"947\" height=\"1024\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-947x1024.jpg\" alt=\"\" class=\"wp-image-4319\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-947x1024.jpg 947w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-277x300.jpg 277w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-768x831.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-1420x1536.jpg 1420w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-1893x2048.jpg 1893w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-1150x1244.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-750x811.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-400x433.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20-250x270.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-35-20.jpg 1906w\" sizes=\"(max-width: 947px) 100vw, 947px\" \/><\/figure>\n\n\n\n<p>The external LED is connected to PD15 (D9 of Arduino header) while the potentiometer is connected to PA3 (A0 of Arduino header). <\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. STM32CubeMX Configuration:<\/h2>\n\n\n\n<p>Open the .ioc file using STM32CubeMX.<\/p>\n\n\n\n<p>Set PD15 as TIM4_CH4 as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"777\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-1024x777.jpg\" alt=\"\" class=\"wp-image-4320\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-1024x777.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-300x228.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-768x582.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-1536x1165.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-2048x1553.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-1150x872.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-750x569.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-400x303.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-38-28-250x190.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Additionally, configure PA3 as ADC1_IN3 as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"750\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-1024x750.jpg\" alt=\"\" class=\"wp-image-4321\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-1024x750.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-300x220.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-768x562.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-1536x1125.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-2048x1500.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-1150x842.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-750x549.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-400x293.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-08-250x183.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>From Pinout and Configuration, Timers, select TIM4 and configure it as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Clock Source to be internal Clock.<\/li>\n\n\n\n<li>Channel 3 set it as PWM Generation CH4.<\/li>\n\n\n\n<li>Prescaler to 2400-1.<\/li>\n\n\n\n<li>Counter Period to 100-1.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"959\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47-1024x959.jpg\" alt=\"\" class=\"wp-image-4322\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47-1024x959.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47-300x281.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47-768x719.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47-1536x1438.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47-1150x1077.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47-750x702.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47-400x375.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47-250x234.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-40-47.jpg 1950w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>For more information about how to configure PWM and the parameters, please refer to <a href=\"https:\/\/blog.embeddedexpert.io\/?p=3363\" data-type=\"link\" data-id=\"https:\/\/blog.embeddedexpert.io\/?p=3363\" target=\"_blank\" rel=\"noreferrer noopener\">this guide.<\/a><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Finally, click on Generate Code to generate the new configuration.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"76\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-1024x76.jpg\" alt=\"\" class=\"wp-image-4323\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-1024x76.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-300x22.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-768x57.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-1536x114.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-2048x152.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-1150x85.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-750x56.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-400x30.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/2026-03-30_11-44-48-250x19.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Firmware Development:<\/h2>\n\n\n\n<p>With the peripherals configured, we now move to the actual code. The generated project already has most of the Mongoose networking code in place. We only need to focus on two files:&nbsp;<strong><code>main.c<\/code><\/strong>&nbsp;and&nbsp;<strong><code>mongoose_glue.c<\/code><\/strong>.<\/p>\n\n\n\n<p>The&nbsp;<code>mongoose_glue.c<\/code>&nbsp;file is the bridge between the web UI and the hardware. It contains getter and setter functions for each API endpoint we defined in the wizard. These functions are called automatically by Mongoose whenever the UI state changes. We fill in the hardware control code inside them.<\/p>\n\n\n\n<p>The generated&nbsp;<code>mongoose_glue.c<\/code>&nbsp;file already has&nbsp;<code>glue_set_leds<\/code>&nbsp;and&nbsp;<code>glue_get_leds<\/code>&nbsp;functions. We could write our code directly inside them, but the problem is that&nbsp;<code>mongoose_glue.c<\/code>&nbsp;gets overwritten every time the Mongoose Wizard regenerates the project. Any code we wrote inside it will be lost.<\/p>\n\n\n\n<p>The better approach is to define our own getter and setter functions in&nbsp;<code>main.c<\/code>&nbsp;and register them using&nbsp;<code>mongoose_set_http_handlers<\/code>. Our code stays safe in&nbsp;<code>main.c<\/code>&nbsp;and is never touched by the wizard.<\/p>\n\n\n\n<p>We start by defining two function for led setter and led getter as follows:<\/p>\n\n\n\n<p>In user code begin 0 after mg_random function, declare the getter function as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void get_leds(struct leds * LEDs)<\/pre><\/div>\n\n\n\n<p>Within the function, read each LED state and store it in leds structure as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LEDs-&gt;led1=(HAL_GPIO_ReadPin(LD1_GPIO_Port, LD1_Pin));\nLEDs-&gt;led2=(HAL_GPIO_ReadPin(LD2_GPIO_Port, LD2_Pin));\nLEDs-&gt;led3=(HAL_GPIO_ReadPin(LD3_GPIO_Port, LD3_Pin));<\/pre><\/div>\n\n\n\n<p>LD1, LD2 and LD3 are the built-in LEDs in Nucleo board.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, getter function for PWM:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void get_pwm(struct PWM *pwm)\n{\n\tpwm-&gt;value=__HAL_TIM_GET_COMPARE(&amp;htim4, TIM_CHANNEL_4);\n\n}<\/pre><\/div>\n\n\n\n<p>Setter function for PWM:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void set_PWM(struct PWM *pwm)\n{\n\t__HAL_TIM_SET_COMPARE(&amp;htim4, TIM_CHANNEL_4, pwm-&gt;value);\n\n\n}<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>For the ADC, since the UI never reads the ADC gauge, only getter function is needed.<\/p>\n\n\n\n<p>To display the ADC value on the gauge, we need to follows these steps:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Read the ADC.<\/li>\n\n\n\n<li>Scale the ADC value to represent 0 to 100.<\/li>\n\n\n\n<li>Update the gauge.<\/li>\n<\/ul>\n\n\n\n<p>First, a function that will scale the ADC read:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">long map(long x, long in_min, long in_max, long out_min, long out_max) {\n  return (x - in_min) * (out_max - out_min + 1) \/ (in_max - in_min + 1) + out_min;\n}<\/pre><\/div>\n\n\n\n<p>Note: this is the same function used by Arduino which is called map.<\/p>\n\n\n\n<p>Next, read ADC function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">int readADC(void)\n{\n  HAL_ADC_Start(&amp;hadc1);\n  HAL_ADC_PollForConversion(&amp;hadc1, 100);\n  uint16_t ADC_VAL = HAL_ADC_GetValue(&amp;hadc1);\n  HAL_ADC_Stop(&amp;hadc1);\n  uint16_t val = map(ADC_VAL, 0, 4095, 0, 100);\n  return val;\n}<\/pre><\/div>\n\n\n\n<p>For more information how to read the ADC in polling mode, please refer to <a href=\"https:\/\/blog.embeddedexpert.io\/?p=3409\" data-type=\"link\" data-id=\"https:\/\/blog.embeddedexpert.io\/?p=3409\" target=\"_blank\" rel=\"noreferrer noopener\">this guide<\/a>.<\/p>\n\n\n\n<p>Getter function for the gauge:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void get_ADC_Value(struct ADC_Value * adcData)\n{\n\tadcData-&gt;adc_value=readADC();\n}<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, before mongoose initialization, start the timer in PWM mode as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">HAL_TIM_PWM_Start(&amp;htim4, TIM_CHANNEL_4);<\/pre><\/div>\n\n\n\n<p>After the mongoose initialization, link the led getter and setter function for the LEDs as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">mongoose_set_http_handlers(&quot;leds&quot;, get_leds, set_leds);<\/pre><\/div>\n\n\n\n<p>Please note that the &#8220;leds&#8221; should have the same structure name which is leds in this case.<\/p>\n\n\n\n<p>Link the getter and setter functions for PWM as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">mongoose_set_http_handlers(&quot;PWM&quot;, get_pwm, set_PWM);<\/pre><\/div>\n\n\n\n<p>For ADC:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">mongoose_set_http_handlers(&quot;ADC_Value&quot;, get_ADC_Value, NULL);<\/pre><\/div>\n\n\n\n<p>Since we want to update the gauge regularly, we need to use websocket to update the gauge as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">mongoose_add_ws_reporter(200, &quot;ADC_Value&quot;);<\/pre><\/div>\n\n\n\n<p>This will update the gauge each 200ms.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Thats all for the firmware.<\/p>\n\n\n\n<p>Save, build and run the project as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"34\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13-1024x34.jpg\" alt=\"\" class=\"wp-image-4132\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13-1024x34.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13-300x10.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13-768x26.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13-1536x51.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13-1150x38.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13-750x25.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13-400x13.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13-250x8.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/12\/2025-12-31_11-12-13.jpg 1986w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Results:<\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>Using the IP address provided by the router, enter it to the web browser and you should get the following:<\/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=\"Control UI and Peripheral using MongooseWS\" width=\"1170\" height=\"658\" src=\"https:\/\/www.youtube.com\/embed\/NgRQIsLhxwY?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>To download the project:<\/p>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-f6a0e34f-4eb6-477e-9f10-55f13b8c3856\" href=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/MongooseWS_Webserver.zip\">MongooseWS_Webserver<\/a><a href=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/03\/MongooseWS_Webserver.zip\" class=\"wp-block-file__button wp-element-button\" download aria-describedby=\"wp-block-file--media-f6a0e34f-4eb6-477e-9f10-55f13b8c3856\">Download<\/a><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Happy coding \ud83d\ude09<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this fifth part of the guide, we connect the web user interface to the STM32 hardware peripherals to enable real interaction with the system. This demonstrates how browser-based controls can monitor and manipulate embedded resources, bridging the gap between the device firmware and the user-facing interface. In this guide, we shall cover the following: [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,11,12],"tags":[],"class_list":["post-4316","post","type-post","status-publish","format-standard","hentry","category-embedded-systems","category-peripheral-drivers","category-stm32"],"_links":{"self":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4316"}],"collection":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4316"}],"version-history":[{"count":2,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4316\/revisions"}],"predecessor-version":[{"id":4327,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4316\/revisions\/4327"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4316"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4316"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4316"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}