>Use Cases>
/N32G031_SERVO_POTENTIOMETER

Drive a servo with a potentiometer

N32G031_SERVO_POTENTIOMETER

Introduction

An interactive automation project designed to teach analog signal reading and hardware mapping using the Nations N32G031 (Cortex-M0). This project highlights how to control a servo motor's position in real-time by reading a potentiometer (knob) via the internal ADC. This project is fully optimized for cross-platform workflows using UnityMbed.

Target MCUNations N32G031 (Cortex-M0)
Analog & PWM PinsPA0 (ADC) + PA1 (Servo)
Filter Engine1D Kalman Filter (Anti-Jitter)

Video Demonstration

Wiring & Hardware Connection

DevicePinN32G031 Board PinNotes & Safety
Potentiometer (Analog Knob)SignalPA0Analog Input (ADC Channel 0)
Potentiometer (Analog Knob)VCC3.3V / 5VPower supply for the knob
Potentiometer (Analog Knob)GNDGNDCommon system ground
Servo Motor (e.g., SG90)Signal (Orange/Yellow)PA1Control Signal Pin (PWM Bit-Banging)
Servo Motor (e.g., SG90)VCC (Red)5VMotor main power supply (Requires stable 5V)
Servo Motor (e.g., SG90)GND (Brown/Black)GNDCommon system ground

Behaviour & Execution Logic

The microcontroller reads the analog voltage from the potentiometer, filters noise using Kalman Filter, and tracks the servo position in real-time:

Knob Turned Left

Moves towards 180° (Pulse: 5500)

As the ADC reading approaches 0, the servo moves smoothly towards 180° (Pulse: 5500).

Knob Turned Right

Moves towards 0° (Pulse: 2250)

As the ADC reading approaches 4095, the servo moves smoothly towards 0° (Pulse: 2250).

Real-time Tracking

Mirrors Knob Position

Servo arm continuously mirrors the potentiometer's exact position instantly.

main.c
// UnityMbed — Real-time Servo Control with Potentiometer (ADC) @ N32G031
#include "n32g031.h"
#include "n32g031_gpio.h"
#include "n32g031_rcc.h"
#include "n32g031_adc.h"
#include "n32g031_usart.h"
#include <stdio.h>

/* ====================================================================
 * [Hardware Configuration Zone]
 * ==================================================================== */
#define PULSE_MIN     2250
#define PULSE_MAX     5500
#define KNOB_MAX      4095

/* Simple software delay loop */
void Delay_Loop(volatile uint32_t count) {
  while(count--) { __NOP(); }
}

/* Kalman Filter function for sensor noise rejection (1D Simple Kalman) */
float kalman_update(float measurement, float* state, float* pc) {
  float k_gain = *pc / (*pc + 0.1f);     // 0.1 is R (Measurement Noise)
  *pc = (1.0f - k_gain) * (*pc) + 0.01f; // 0.01 is Q (Process Noise)
  *state = *state + k_gain * (measurement - *state);
  return *state;
}

/* Initialize Potentiometer ADC on PA0 (ADC Channel 0) */
void Setup_Knob(void) {
  RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
  RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_ADC, ENABLE);

  GPIO_InitType GPIO_InitStructure;
  GPIO_InitStruct(&GPIO_InitStructure);
  GPIO_InitStructure.Pin = GPIO_PIN_0;
  GPIO_InitStructure.GPIO_Mode = GPIO_MODE_ANALOG;
  GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);

  ADC_InitType ADC_InitStructure;
  ADC_InitStruct(&ADC_InitStructure);
  ADC_InitStructure.MultiChEn      = DISABLE;
  ADC_InitStructure.ContinueConvEn = DISABLE;
  ADC_InitStructure.ExtTrigSelect  = ADC_EXT_TRIGCONV_NONE;
  ADC_InitStructure.DatAlign       = ADC_DAT_ALIGN_R;
  ADC_InitStructure.ChsNumber      = 1;
  ADC_Init(ADC, &ADC_InitStructure);
  ADC_Enable(ADC, ENABLE);
}

/* Configure PA9 as USART1 TX for sending data to the Serial Monitor */
void Setup_UART(void) {
  RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_USART1, ENABLE);

  GPIO_InitType GPIO_InitStructure;
  GPIO_InitStruct(&GPIO_InitStructure);
  GPIO_InitStructure.Pin            = GPIO_PIN_9;
  GPIO_InitStructure.GPIO_Mode      = GPIO_MODE_AF_PP;
  GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_USART1;
  GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);

  USART_InitType USART_InitStructure;
  USART_InitStructure.BaudRate            = 115200;
  USART_InitStructure.WordLength          = USART_WL_8B;
  USART_InitStructure.StopBits            = USART_STPB_1;
  USART_InitStructure.Parity              = USART_PE_NO;
  USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
  USART_InitStructure.Mode                = USART_MODE_TX;
  USART_Init(USART1, &USART_InitStructure);

  USART_Enable(USART1, ENABLE);
}

/* Read analog value from potentiometer knob (Returns 0 to 4095) */
uint32_t Read_Knob(void) {
  ADC_ConfigRegularChannel(ADC, ADC_CH_0, 1, ADC_SAMP_TIME_56CYCLES5);
  ADC_EnableSoftwareStartConv(ADC, ENABLE);
  while(ADC_GetFlagStatus(ADC, ADC_FLAG_ENDC) == RESET);
  ADC_ClearFlag(ADC, ADC_FLAG_ENDC);
  return ADC_GetDat(ADC);
}

/* Send a string character by character over UART */
void UART_SendString(const char* str) {
  while(*str) {
      USART_SendData(USART1, (uint8_t)*str++);
      while (USART_GetFlagStatus(USART1, USART_FLAG_TXDE) == RESET);
  }
}

/* Transmit control signal pulse to servo motor */
void Servo_Step(uint32_t pulse_width) {
  GPIO_SetBits(GPIOA, GPIO_PIN_1);
  Delay_Loop(pulse_width);
  GPIO_ResetBits(GPIOA, GPIO_PIN_1);
  Delay_Loop(100000);
}

/* ====================================================================
 * Main Application Loop
 * ==================================================================== */
int main(void) {
  /* --- 1. System Initialization --- */
  Setup_UART();
  UART_SendString("\r\n[DEBUG] UART Initialized! Wiring is correct.\r\n");

  Setup_Knob();
  UART_SendString("[DEBUG] ADC Initialized. Reading baseline...\r\n");

  // Initialize Servo Motor Control Pin (PA1) as Push-Pull Output
  GPIO_InitType GPIO_InitStructure;
  GPIO_InitStruct(&GPIO_InitStructure);
  GPIO_InitStructure.Pin = GPIO_PIN_1;
  GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);

  /* --- 2. Variable Setup --- */
  uint32_t raw_knob = 0;
  uint32_t target_pulse = PULSE_MIN;

  // Read initial baseline to prevent the servo from jerking on startup
  float k_state = (float)Read_Knob();
  float k_pc = 0.0f;

  char msg_buffer[64];
  uint32_t print_counter = 0; // Used to limit UART output frequency

  UART_SendString("System Initialized. Starting Servo Control...\r\n");

  /* --- 3. Main Control Loop --- */
  while(1) {
      // Step A: Read raw analog value from the potentiometer
      raw_knob = Read_Knob();

      // Step B: Apply Kalman Filter to remove electrical noise/jitter
      kalman_update((float)raw_knob, &k_state, &k_pc);

      // Step C: Map the filtered ADC value (0-4095) to Servo Pulse Width (2250-5500)
      // Note: The math is inverted here (PULSE_MAX - ...) to match the physical knob direction
      target_pulse = PULSE_MAX - (((uint32_t)k_state * (PULSE_MAX - PULSE_MIN)) / KNOB_MAX);

      // Step D: Send the calculated pulse to physically move the servo
      Servo_Step(target_pulse);

      // Step E: Print telemetry data to Serial Monitor (Only once every 50 loops to avoid spam)
      if (++print_counter >= 50) {
          sprintf(msg_buffer, "Raw ADC: %4lu | Filtered: %4d | Pulse: %4lu\r\n",
                  raw_knob, (int)k_state, target_pulse);
          UART_SendString(msg_buffer);
          print_counter = 0; // Reset counter
      }
  }
}

Learning & AI Extension Ideas

Students can use the built-in AI Assistant in the IDE to explore, debug, and modify this project. Try pasting these example prompts:

1

To Learn Formula Mapping

Prompt: "Explain how the inverted formula PULSE_MAX - ((knob_value * (PULSE_MAX - PULSE_MIN)) / KNOB_MAX) translates the 12-bit ADC reading into the correct servo angle."

2

To Experiment Moving Average

Prompt: "How can I modify the Read_Knob() function to average 10 readings so the servo motor stops jittering when my hand is still?"

3

To Debug Wiring Issues

Prompt: "My ADC is always returning 4095 or 0. What could be wrong with my potentiometer wiring on PA0?"

4

Kalman Filter Exploration

Prompt AI to explain how the 1D Kalman Filter dynamically removes measurement jitter compared to simple moving average filtering.

Build and Flash Guide

1

Open Project: Open this project folder directly in the IDE.

2

Build & Flash: Simply click the Build and Flash buttons on the interface.