Replacing a Rotary Encoder with a Magnetic Sensor and Potentiometer

I’ve been thinking if there is an easy way to make a simple and cheap control knob to replace those cheap quadrature rotary encoders in Arduino projects without the need to change the Arduino code or at least very minimal changes. This would only work if the angle sensor could produce a quadrature output like a standard mechanical encoder, and do it at a useful resolution.

The goal of this project was to see if I could find a magnetic angle sensor that has:

  • Quadrature outputs like a mechanical rotary encoder,
  • Can easily have the resolution (number of pulses per revolution) permanently programmed into its own built-in EEPROM
  • Can then be dropped into a project using ordinary rotary encoder code.

The short answer is, yes there is at least one cheap module, based on the MT6701 IC. The resolution can be programmed in it’s built in EEPROM and permanently stored using software below. Paired with a hacked potentiometer it makes a great encoder replacement.

This video shows it in action.

But why?

Why not just stick with one of those cheap quadrature rotary encoders? When I did the Tuning Wheel project I discovered how smooth and pleasant this method can be. The knob is silky smooth to turn, and its resolution can be programmed to whatever whatever you want.

These sensors already output data showing the angular position of the magnet, so they can be used with code changes in your sketch. I used this for my Building a Tuning Wheel For PC SDR Radio Using an Angle Sensor project. That worked well, but I thought of a couple of cases where having a quadrature output may be useful.

  1. It may make it easier to use the sensor in a project that has been coded to use a quadrature output encoder.
  2. One project I want to try it with is for tuning control of a radio receiver. Using I²C to get the angle data requires polling the sensor. I was concerned that the constant data transfer may add extra noise to the radio, whereas I thought using it with quadrature output could be quieter, particularly when the tuning knob is not being rotated.

Exploring options

It started when I was looking at angle sensors for a wind vane project. I ordered a couple of different sensors from AliExpress including a module with a MT6826S sensor. This is an interesting sensor. It is a magnetic angle/position encoder IC mounted on a breakout board. It works by having the included magnet suspended on a shaft 1 – 3mm above the IC. It can output the position data in several ways. These include SPI and PWM. There is also UVW which outputs three phase-shifted square wave signals used for brushless DC motor commutation and initial rotor positioning.

Finally there is ABZ mode: This provides a quadrature output similar to those cheap rotary encoders. It looked like the resolution could be set from 1- 4096 pulses per revolution (user programmable). While the datasheet mentioned programming it, I couldn’t see information about setting the resolution in EEPROM.

Meet the MT6701. This is another sensor from the same family and is very similar in many ways. Both the information page and datasheet say that the resolution is user programmable and that it can be set in EEPROM. Once programmed, it remembers the setting. This may be possible with the MT6826S but the MT6701 had more info about programming.

Existing code and libraries

I looked online to see if anyone had posted information about this, but came up empty, but I couldn’t find any. I did find two libraries.

The SimpleFOC Driver and Support Library has drivers and supporting code for a large number of Field-Oriented Control sensors including both the MT6826 and MT6701, but I couldn’t see that it had code to program the EEPROM.

The Noran Raskin’s MT6701 library supports the MT6701 on the ESP32. It supports reading angular positions in radians and degrees, calculating rotational velocity in RPM, and smoothing RPM values with a moving average filter. Sounds like a useful library, but it doesn’t appear to support programming the EEPROM either.

The module

This is the module I purchased. It cost AU$1.98 when I purchased it, but it looks like it is no longer on special. Many sellers stock it, so you may find a better deal. It’s not an affiliate link and I don’t make any money from this blog. Note the module is called MT6701QT and has a lot of the pins broken out. Some modules don’t and may not be possible to use this way.

Connections

The IC has 16 pins. 5 of them are not connected according to the datasheet. That leaves 11 pins used. The module has 21 pins so clearly something is going on. I believe they are all used, it is just some are connected together. It looks like each side of the PCB has the needed pins needed for different uses cases.

There are three pins marked VDD. This is for the power supply (3.3 – 5V). There are four ground pins, two marked GND and the other two just marked G.

Looking at just the pins on the I²C (left side of the image) and ABZ (top of the image) sides we have.

I²C side
for Programming
ABZ side
For Encoder
Function
VDDVDDPower supply 3.3 – 5V
ZDigital Input/output
Not used in this project
SCLBEncoder Signal A, or I²C data SDA
SDAAEncoder Signal B, or I²C clock SCL
GNDGGround
MSelects ABZ (Encoder) or I²C (for
programming in my case) mode
Connect to ground for ABZ (Encoder mode)
Has built-in 200KΩ Pull-up Resistor

Looking at this table you can see that not only are the power and ground connections shared. SCL and B go to the same pin on the IC and SDA and A go to the same pin. This means you could just use the pins on the ABZ side of the module for both programming or using it as an encoder. It’s the M pin that sets the function of the sensor. It has a built-in pull-up resistor. Leaving it unconnected puts it in I²C mode and connecting it to ground places it in ABZ mode.

The module didn’t come with header pins. I realised why when I tried to connect them. The pin spacing is less than the typical 2.54 mm spacing. I managed to solder 2.5mm pins but I had to bend the legs to make it work. I soldered pins to both edges, but this was partly because I didn’t realise they were shared at the time.

The knob setup

The module does not come any hardware for a knob. When I was working on the Building a Tuning Wheel For PC SDR Radio Using an Angle Sensor project, I found using bits from a potentiometer were quite a simple and very effective way to set it up. I have written a short article showing how I did this in this post Converting a Potentiometer For Use With a Magnetic Rotary Encoder.

Here is a shot of the setup I used for testing made from a gutted potentiometer.

My new friend Claude helps

I didn’t attempt to write this code myself as this is far beyond my knowledge. I had tried to see if anyone had done this, but couldn’t find anything. I decided this was another time to try vibe coding. I started coding by asking ChatGPT about the MT6826S sensor, but I didn’t get far, perhaps because the MT6826S datasheet didn’t have as much programming info. I’ve not used Claude before, but have heard good reports about it so gave it a try.

The initial prompt I gave Claude was “I have a MT6701QT angle sensor. I want to program in it so that I can change the ABZ Resolution in its EEPROM. I want to connect it to an ESP32 for programming it and use the Arduino IDE. Is this possible. This is the datasheet https://www.novosns.com/enfiles/MT6701_Rev.1.8.pdf“. I must have done this in a rush looking back at the poor grammar. Even so it seemed to understand what I was asking. It came back with:

“I’ll fetch the datasheet to understand the MT6701QT’s programming requirements…. Yes, this is absolutely possible! The MT6701QT has a built-in EEPROM that can be programmed via I²C interface, and the ESP32 is perfect for this task with Arduino IDE.”

I asked Claude to write some code to update the EEPROM so that a chosen resolution can be stored. There was a bit of going back and forth as I realised that I didn’t need to change the resolution of the Z output, only A and B.

I then got it to change from running on an ESP32 to an Arduino Nano. The datasheet says that the power supply for the module is 3.3~5.0V, but for programming the EEPROM the supply needs to be greater than 4.5V. Swapping to a 5V Nano seemed to be the simplest solution.

Next I asked it to add some diagnostics as I couldn’t get it to communicate with the sensor. This highlighted an issue with AI. It doesn’t work well with an incompetent user. I had connected the data pins to D4 and D5 instead of A4 and A5. I changed them over and it burst into life.

This is the final code. It is almost completely written by Claude, It is much longer than is strictly necessary as it still contains all the diagnostic bits.

/*
  MT6701QT EEPROM Programmer for Arduino Nano (5V)

  Generated with the assistance of Claude AI
 
  This program is free software: you can redistribute it and/or modify it under the 
  terms of the MIT License https://opensource.org/license/mit
 
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
  
  This sketch programs the AB Resolution in the MT6701's EEPROM
  The Z pulse width is NOT changed - it retains its current value
  
  Hardware Connections:
  - MT6701 VDD      -> Arduino 5V
  - MT6701 GND      -> Arduino GND
  - MT6701 MODE (M) -> Leave unconnected (enables I2C mode)
  - MT6701 A (SCA)  -> Arduino A4 (SDA)
  - MT6701 B (SCL)  -> Arduino A5 (SCL)
  - MT6701 Z        -> Leave unconnected
  
  IMPORTANT: Arduino Nano provides stable 5V which is perfect for EEPROM programming!

  How it works:
  1. Reads current configuration from MT6701 EEPROM
  2. Programs new AB resolution (while preserving Z pulse width and other settings)
  3. Sends programming command sequence per datasheet
  4. Waits 650ms for EEPROM write cycle to complete
  5. Verifies the programming was successful
  6. Requires power cycle for new settings to take effect

  How to use:
  -----------
  1. Change this line at the top of the code to your desired value:

    #define DESIRED_AB_PPR 64  // Change this to your desired value (1-1024)

  2. Uncomment the section in the setup function to enable programming. If left
      commented it the sketch will display the current resolution without updating.

  2. Upload to Arduino Nano/Uno

  3. The sketch will:
    - Show current AB resolution and Z pulse width
    - Program only the AB resolution
    - Leave Z pulse width unchanged
    - Display verification results

  4. Power cycle the MT6701 after successful programming

  5. Re-comment the section in the setup section and run the sketch again after power
      cycle to verify the new settings took effect.

  The code specifically preserves register 0x32 (which contains the Z_PULSE_WIDTH bits) and only modifies the AB 
  resolution bits in registers 0x30 and 0x31. Your Z index pulse configuration will remain exactly as it was!

  MT6701 Data sheet: https://www.novosns.com/enfiles/MT6701_Rev.1.8.pdf   
*/

#include <Wire.h>

// ========== USER CONFIGURATION ==========
// Set your desired AB resolution here (1-1024 PPR)
#define DESIRED_AB_PPR 64  // Change this value as needed
// ========================================

// MT6701 I2C Address (7-bit)
#define MT6701_ADDR 0x06

// EEPROM Register Addresses
#define REG_ABZ_RES_HIGH  0x30  // ABZ_RES[9:8] in bits [1:0], UVW_RES[3:0] in bits [7:4]
#define REG_ABZ_RES_LOW   0x31  // ABZ_RES[7:0]
#define REG_ZERO_MSB      0x32  // Z_PULSE_WIDTH[2:0] in bits [6:4], ZERO[11:8] in bits [3:0]
#define REG_PROG_KEY      0x09  // Programming key register
#define REG_PROG_CMD      0x0A  // Programming command register

// Angle reading registers
#define REG_ANGLE_HIGH    0x03  // Angle[13:6]
#define REG_ANGLE_LOW     0x04  // Angle[5:0]

void setup() {
  Serial.begin(9600);
  delay(2000);
  
  // Print header information
  Serial.println(F("MT6701 EEPROM Programmer"));
  Serial.println(F("========================"));
  Serial.println(F("Arduino Nano (5V) Version"));
  Serial.print(F("Target AB Resolution: "));
  Serial.print(DESIRED_AB_PPR);
  Serial.println(F(" PPR"));
  
  // Validate that the user has entered a valid PPR value
  // PPR must be between 1 and 1024 per datasheet specifications
  if (DESIRED_AB_PPR < 1 || DESIRED_AB_PPR > 1024) {
    Serial.println(F("\nERROR: DESIRED_AB_PPR must be between 1 and 1024!"));
    Serial.println(F("Please update the value in the code and re-upload."));
    while(1); // Halt
  }
  
  // Initialize I2C (Arduino Nano uses A4=SDA, A5=SCL by default)
  Wire.begin();
  Wire.setClock(100000); // Set I2C clock to 100kHz (standard speed)
  
  // Scan the I2C bus to find all connected devices
  // This helps diagnose connection issues
  Serial.println(F("\nScanning I2C bus..."));
  scanI2C();
  
  // Check specifically for MT6701 at address 0x06
  Serial.println(F("\nChecking MT6701 at address 0x06..."));
  
  // Attempt to communicate with MT6701
  if (!checkConnection()) {
    // If MT6701 not found, print detailed troubleshooting info
    Serial.println(F("\nERROR: MT6701 not detected at address 0x06!"));
    Serial.println(F("\nTroubleshooting checklist:"));
    Serial.println(F("  1. VDD (Pin 13) -> Arduino 5V"));
    Serial.println(F("  2. GND (Pin 16) -> Arduino GND"));
    Serial.println(F("  3. MODE (Pin 14) -> Leave unconnected (CRITICAL!)"));
    Serial.println(F("  4. A/SDA (Pin 6) -> Arduino A4 (with 4.7k pullup to 5V)"));
    Serial.println(F("  5. B/SCL (Pin 7) -> Arduino A5 (with 4.7k pullup to 5V)"));
    Serial.println(F("\nIf device shows at 0x66, check MODE pin connection!"));
    while(1); // Halt
  }
  
  Serial.println(F("MT6701 detected successfully\n"));
  
  // Read and display the current configuration stored in EEPROM
  Serial.println(F("=== Current Configuration ==="));
  displayCurrentConfig();
  
  /*// ****** This section updates the EEPROM ******
  // ****** Uncomment this section to enable programming ******

  // Program the new AB resolution into EEPROM
  Serial.println(F("\n=== Programming AB Resolution ==="));
  Serial.println(F("NOTE: Z pulse width will NOT be changed"));
  
  // Call the main programming function
  if (programABResolution(DESIRED_AB_PPR)) {
    Serial.println(F("\n=== Programming Successful! ==="));
    Serial.println(F("Please power cycle the MT6701 for changes to take effect."));
    Serial.println(F("After power cycle, run this sketch again to verify."));
  } else {
    Serial.println(F("\nERROR: Programming failed!"));
  }
  */
}

void loop() {
  // Nothing to do - programming happens once in setup()
  delay(1000);
}

/**
 * Check if MT6701 is responding on I2C bus
 * Returns true if device acknowledges at address 0x06
 */
bool checkConnection() {
  Wire.beginTransmission(MT6701_ADDR);
  return (Wire.endTransmission() == 0); // 0 = success
}

/**
 * Scan all I2C addresses from 0x01 to 0x7F
 * Prints any devices found to help with troubleshooting
 */
void scanI2C() {
  byte error, address;
  int nDevices = 0;
  
  Serial.println(F("Scanning I2C addresses 0x01-0x7F..."));
  
  // Loop through all possible 7-bit I2C addresses
  for(address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    
    // If device acknowledges (error == 0), it's present
    if (error == 0) {
      Serial.print(F("  Device found at 0x"));
      if (address < 16) Serial.print(F("0")); // Print leading zero for single digit hex
      Serial.println(address, HEX);
      nDevices++;
    }
  }
  
  // Print summary
  if (nDevices == 0) {
    Serial.println(F("  No I2C devices found!"));
    Serial.println(F("  Check wiring and pull-up resistors."));
  } else {
    Serial.print(F("  Total devices found: "));
    Serial.println(nDevices);
  }
}

/**
 * Display current MT6701 configuration from EEPROM
 * Shows AB resolution, Z pulse width, and current angle
 */
void displayCurrentConfig() {
  // Read raw register values for diagnostic purposes
  uint8_t reg30 = readRegister(REG_ABZ_RES_HIGH);
  uint8_t reg31 = readRegister(REG_ABZ_RES_LOW);
  uint16_t abRes = readABResolution();
  uint8_t zWidth = readZPulseWidth();
  
  // Display raw register values in hexadecimal
  Serial.print(F("Raw Register 0x30: 0x"));
  if (reg30 < 16) Serial.print(F("0")); // Print leading zero for single digit
  Serial.println(reg30, HEX);
  
  Serial.print(F("Raw Register 0x31: 0x"));
  if (reg31 < 16) Serial.print(F("0"));
  Serial.println(reg31, HEX);
  
  // Display human-readable AB resolution
  Serial.print(F("Current AB Resolution: "));
  if (abRes == 0) {
    // Handle special case where register reads as 0
    Serial.println(F("1 PPR (register value = 0)"));
  } else {
    Serial.print(abRes);
    Serial.println(F(" PPR"));
  }
  
  // Display Z pulse width configuration
  Serial.print(F("Current Z Pulse Width: "));
  switch(zWidth) {
    case 0: Serial.println(F("1 LSB")); break;
    case 1: Serial.println(F("2 LSB")); break;
    case 2: Serial.println(F("4 LSB")); break;
    case 3: Serial.println(F("8 LSB")); break;
    case 4: Serial.println(F("12 LSB")); break;
    case 5: Serial.println(F("16 LSB")); break;
    case 6: Serial.println(F("180°")); break;
    case 7: Serial.println(F("1 LSB")); break;
    default: Serial.println(F("Unknown")); break;
  }
  
  // Read and display current angle measurement
  float angle = readAngle();
  Serial.print(F("Current Angle: "));
  Serial.print(angle, 2);
  Serial.println(F("°"));
}

/**
 * Read AB Resolution from EEPROM registers
 * Returns the actual PPR value (1-1024)
 * Note: Register stores (PPR - 1), so we add 1 to get actual value
 */
uint16_t readABResolution() {
  uint8_t highByte = readRegister(REG_ABZ_RES_HIGH);
  uint8_t lowByte = readRegister(REG_ABZ_RES_LOW);
  
  // ABZ_RES is 10 bits total:
  // - Bits [1:0] of register 0x30 are the high 2 bits
  // - Register 0x31 is the low 8 bits
  uint16_t resolution = ((highByte & 0x03) << 8) | lowByte;
  
  // Register stores PPR-1, so add 1 for actual PPR
  // Example: Register value 0 = 1 PPR, Register value 999 = 1000 PPR
  return resolution + 1;
}

/**
 * Read Z Pulse Width configuration from EEPROM
 * Returns a 3-bit value (0-7) representing pulse width
 */
uint8_t readZPulseWidth() {
  uint8_t reg32 = readRegister(REG_ZERO_MSB);
  
  // Z_PULSE_WIDTH is stored in bits [6:4] of register 0x32
  // Shift right 4 bits and mask to get 3-bit value
  return (reg32 >> 4) & 0x07;
}

/**
 * Read current angle position from MT6701
 * Returns angle in degrees (0.00 to 360.00)
 */
float readAngle() {
  uint8_t highByte = readRegister(REG_ANGLE_HIGH);
  uint8_t lowByte = readRegister(REG_ANGLE_LOW);
  
  // Combine to 14-bit angle value
  // Register 0x03 contains bits [13:6] (8 bits)
  // Register 0x04 contains bits [5:0] in upper 6 bits [7:2]
  uint16_t rawAngle = (highByte << 6) | (lowByte >> 2);
  
  // Convert 14-bit value (0-16383) to degrees (0-360)
  // Formula: angle = (rawValue / 16384) * 360
  float angle = (rawAngle * 360.0) / 16384.0;
  
  return angle;
}

/**
 * Main programming function - writes new AB resolution to EEPROM
 * 
 * This follows the programming sequence from the MT6701 datasheet:
 * 1. Write configuration registers
 * 2. Write programming key (0xB3)
 * 3. Write programming command (0x05)
 * 4. Wait >600ms for EEPROM write cycle
 * 5. Verify the data
 * 
 * @param ppr - Desired pulses per revolution (1-1024)
 * @return true if programming successful, false otherwise
 */
bool programABResolution(uint16_t ppr) {
  // Validate range
  if (ppr < 1 || ppr > 1024) {
    return false;
  }
  
  // Convert PPR to register value
  // Register stores (PPR - 1), so 1000 PPR = register value 999
  uint16_t regValue = ppr - 1;
  
  // STEP 1: Read existing register values to preserve other settings
  Serial.println(F("\nStep 1: Reading existing registers..."));
  uint8_t reg30 = readRegister(REG_ABZ_RES_HIGH);
  uint8_t reg32 = readRegister(REG_ZERO_MSB);
  
  // Calculate new register values
  // Register 0x30 bit layout: [UVW_RES(7:4)] [reserved(3:2)] [ABZ_RES(1:0)]
  // We preserve UVW_RES bits [7:4] and only modify ABZ_RES bits [1:0]
  uint8_t newReg30 = (reg30 & 0xFC) | ((regValue >> 8) & 0x03);
  uint8_t newReg31 = regValue & 0xFF;
  
  // Display what we're about to write
  Serial.print(F("  Register 0x30 (contains UVW_RES and AB_RES high bits)"));
  Serial.print(F("\n    Original: 0x"));
  Serial.print(reg30, HEX);
  Serial.print(F(" -> New: 0x"));
  Serial.println(newReg30, HEX);
  
  Serial.print(F("  Register 0x31 (AB_RES low byte): 0x"));
  Serial.println(newReg31, HEX);
  
  Serial.print(F("  Register 0x32 (Z_PULSE_WIDTH unchanged): 0x"));
  Serial.println(reg32, HEX);
  
  // STEP 2: Write the new resolution values to registers
  Serial.println(F("\nStep 2: Writing AB resolution to registers..."));
  if (!writeRegister(REG_ABZ_RES_HIGH, newReg30)) {
    Serial.println(F("  Failed to write register 0x30"));
    return false;
  }
  if (!writeRegister(REG_ABZ_RES_LOW, newReg31)) {
    Serial.println(F("  Failed to write register 0x31"));
    return false;
  }
  Serial.println(F("  Registers written successfully"));
  
  // STEP 3: Write programming key
  // This unlocks the EEPROM for writing (security feature
  Serial.println(F("\nStep 3: Writing programming key (0xB3)..."));
  if (!writeRegister(REG_PROG_KEY, 0xB3)) {
    Serial.println(F("  Failed to write programming key"));
    return false;
  }
  Serial.println(F("  Key written"));
  
  // STEP 4: Send programming command
  // This triggers the actual EEPROM write cycle
  Serial.println(F("\nStep 4: Sending programming command (0x05)..."));
  if (!writeRegister(REG_PROG_CMD, 0x05)) {
    Serial.println(F("  Failed to write programming command"));
    return false;
  }
  Serial.println(F("  Command sent"));
  
  // STEP 5: Wait for EEPROM programming to complete
  // Datasheet requires >600ms, we use 650ms for safety margin
  Serial.println(F("\nStep 5: Waiting 650ms for EEPROM programming..."));
  Serial.println(F("  DO NOT disconnect power!"));
  delay(650); // Wait >600ms as per datasheet
  
  // STEP 6: Verify the programming was successful
  Serial.println(F("\nStep 6: Verifying written data..."));
  uint16_t verifyRes = readABResolution();
  uint8_t verifyZ = readZPulseWidth();
  
  Serial.print(F("  Read back AB Resolution: "));
  Serial.print(verifyRes);
  Serial.println(F(" PPR"));
  
  // Verify Z pulse width wasn't changed
  Serial.print(F("  Z Pulse Width still: "));
  switch(verifyZ) {
    case 0: Serial.println(F("1 LSB")); break;
    case 1: Serial.println(F("2 LSB")); break;
    case 2: Serial.println(F("4 LSB")); break;
    case 3: Serial.println(F("8 LSB")); break;
    case 4: Serial.println(F("12 LSB")); break;
    case 5: Serial.println(F("16 LSB")); break;
    case 6: Serial.println(F("180°")); break;
    case 7: Serial.println(F("1 LSB")); break;
  }
  
  // Check if verification matches what we programmed
  if (verifyRes == ppr) {
    Serial.println(F("  Verification successful!"));
    return true;
  } else {
    Serial.println(F("  WARNING: Verification failed!"));
    Serial.println(F("  This is normal - power cycle required for EEPROM changes."));
    return true; // Still return true as programming command was sent successfully
  }
}

/**
 * Read a single register from MT6701 via I2C
 * 
 * @param regAddr - Register address to read (0x00-0xFF)
 * @return Register value (0x00-0xFF), or 0 if read fails
 */
uint8_t readRegister(uint8_t regAddr) {
  // Start I2C transmission to MT6701
  Wire.beginTransmission(MT6701_ADDR);
  Wire.write(regAddr); // Send register address we want to read
  Wire.endTransmission(false); // End transmission but keep bus active (repeated start)
  
  // Request 1 byte from the specified register
  Wire.requestFrom(MT6701_ADDR, 1);
  
  // If data is available, read and return it
  if (Wire.available()) {
    return Wire.read();
  }
  
  // Return 0 if no data received (shouldn't happen if device is connected)
  return 0;
}

/**
 * Write a single register to MT6701 via I2C
 * 
 * @param regAddr - Register address to write (0x00-0xFF)
 * @param data - Data byte to write (0x00-0xFF)
 * @return true if write successful, false if I2C error occurred
 */
bool writeRegister(uint8_t regAddr, uint8_t data) {
  // Start I2C transmission to MT6701
  Wire.beginTransmission(MT6701_ADDR);
  Wire.write(regAddr);
  Wire.write(data);
  
  // End transmission and check for errors
  // Returns 0 if successful, non-zero if error
  return (Wire.endTransmission() == 0);
}

Trying it out

Wiring it up for programming

As mentioned in the module section the AB pins used for the quadrature output are connected to the same pins as the I²C data pins on the module. So make sure the AB pins are not connected to anything when the SDA and SCL pins are connected to the Arduino.

To set it up for programming I wired it up as follows:

  • MT6701 VDD -> Arduino 5V
  • MT6701 GND -> Arduino GND
  • MT6701 M (Mode) -> Leave unconnected
  • MT6701 SCA -> Arduino A4 (SDA)
  • MT6701 SCL -> Arduino A5 (SCL)

Note the Mode pin marked M on the module set the mode of the module. This sets the mode to ABZ or I²C. This has an internal 200K pullup resistor. To use the module with I²C mode, the Mode pin can be connected to VCC or left unconnected. When running it in ABZ mode the Mode pin needs to be connected to ground.

Checking the existing resolution

I uploaded the code to an Arduino Nano. I left the section in the code “This section updates the EEPROM” commented out. This ensures only the part of the code that check the EEPROMs current setting runs.

When I ran the code it reported that the current AB resolution was 1.

Programming the EEPROM

Next I set the value in the line “#define DESIRED_AB_PPR 64” to a value I wanted to try. For the radio I later tested with I found 50 was fairly good.

I also uncommented the section “This section updates the EEPROM” so that it would update the EEPROM. Then uploaded the code to the Nano and run it again.

In the serial monitor it showed the new value was set.

Checking the programming was successful

To ensure that the value is not lost when the module is powered down, I re commented the section “This section updates the EEPROM”, uploaded the code and ran it again.

I then powered the Nano and module down. Then powered it back up and checked the serial monitor. It showed the new value was set. So far so good.

At this point I tried it with some sample encoder code I have and then tried it in a radio circuit I had set up.

Testing it in an existing circuit

I have SI4732 radio set up on a breadboard. It uses the SI4735-Radio-ESP32-Touchscreen-Arduino Gert PE0MGB.

This table shows the change in connections going from the existing encoder to the MT6701. Basically, the only difference is the MT6701 has a connection to 3.3V. This is all I have done in the demo video at the top of this post.

Existing EncoderMT6701ESP32
GNDGNDGND
OUT AAD16
OUT BBD17
VCC3.3V

Potential issues

It occurs to ne there are a few potential issues. The magnetic absolute encoder outputs incremental quadrature signals, which is fundamentally different from a traditional incremental encoder with physical detents. This has at least two differences.

Firstly there is no tactile feedback. For some cases that is ok, but may not be for others.

Secondly, some code written for encoders with detents rely on one output always being high at each detent, creating stable resting states at each detent. I’ve tested with some of the code I use and it didn’t seem to be an issue. I asked Claude about it said, good code doesn’t rely on this—but some simple implementations might. “good code does NOT rely on this, but some simple implementations might have issues. Interrupt-Based Encoders (Most Common – Works Fine) Most Arduino encoder libraries use edge detection and don’t care about resting states. Polling-Based Encoders can have issues.

Finally, cheap rotary encoders often use pullup resistors or use inbuilt pullup resistors for the A and B pins. The MT6701 module doesn’t need these. Claude advised:

“Pull-up resistors on A and B pins are generally fine and won’t damage anything, but they’re unnecessary and could potentially cause issues in some situations.”

“Most likely scenario: Your existing pull-ups will work fine with the MT6701 without any changes. The MT6701’s push-pull outputs are strong enough to override typical pull-up resistors (10kΩ-47kΩ).
For best performance and lowest power consumption: Disable or remove the pull-ups since the MT6701 doesn’t need them.”

“The safest approach: Try it first with existing pull-ups. If you see any issues (jittery readings, poor high-speed performance), then remove them.”

A word of warning

If you don’t already know, I’m just a rando on the internet that enjoys tinkering. I’m not an engineer. In fact I don’t have any qualifications relating to electronics. If you do want to give this a go there are some risks, including;

  • I’ve used the code around 10 times and it appears to be working smoothly, however, there may be issues with the code, after all it was written by AI and just like me, AI makes mistakes. It may brick your module.
  • Another brand of angle sensor I investigated had a limit allowing the EEPROM to be programmed only 3 times. I couldn’t find any info for this sensor and I have already programmed mine many more times. Keep in mind there may be a limit.
  • Even though it worked when I used it with a radio project without code changes, if you use use this as a drop in replacement for a rotary encoder in another project, it may not work without some changes, or worse potentially damage that circuit.

Finally

So what do you think? Brilliant idea or there are better ways? Can you see a use for this or seen it done before? Have you found a reasonably priced dedicated component for this already?

Leave a comment

Website Powered by WordPress.com.

Up ↑