JC02-1 Laser Range Finder Module on ESP32

I new very cheap laser range finder, low resolution (0.6m) but if that is not a problem maybe worth a look. It seems to have a few different names like my seller calls everything L22A but on the unit it clearly states JC02-1 my on has -10 and I think that is 1000 meter range, it seems to come in 600, 800, 1000, 1200

The documentation is not great as usual for these type of chinese modules

Here is my ESP32 Arduino code, it has one small bug in that it displays xx.x off to one side but xxx and x.x numbers are good. Other wise you could use a TFT type display or something else.

#include <HardwareSerial.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_GFX.h>

#define LASER_RX 16
#define LASER_TX 17
#define MATRIX_PIN 13

#define BAUD_RATE 115200

// JC02-1 continuous measurement command
const uint8_t CONTINUOUS_CMD[] = {0xAE, 0xA7, 0x04, 0x00, 0x0E, 0x12, 0xBC, 0xBE};

HardwareSerial LaserSerial(2);

// YOUR WORKING MATRIX CONFIG (do not change)
#define PANEL_WIDTH 10
#define PANEL_HEIGHT 10
#define MATRIX_WIDTH (PANEL_WIDTH * 2) // 20 pixels
#define MATRIX_HEIGHT (PANEL_HEIGHT * 1) // 10 pixels

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(PANEL_WIDTH, PANEL_HEIGHT, 2, 1, MATRIX_PIN,
NEO_TILE_TOP + NEO_TILE_LEFT +
NEO_TILE_COLUMNS + NEO_MATRIX_ROWS,
NEO_GRB + NEO_KHZ800);

void setup() {
Serial.begin(115200);
LaserSerial.begin(BAUD_RATE, SERIAL_8N1, LASER_RX, LASER_TX);

matrix.begin();
matrix.setRotation(0);

matrix.setBrightness(60);
matrix.setTextWrap(false);

Serial.println("\n=== JC02-1 NeoMatrix Distance Display ===\n");

// Start continuous measurement
LaserSerial.write(CONTINUOUS_CMD, sizeof(CONTINUOUS_CMD));
delay(800);
}

void loop() {
readDistance();
delay(100);
}

void readDistance() {
if (LaserSerial.available() < 27) return;

uint8_t buffer[32];
int len = LaserSerial.readBytes(buffer, 27);

// Validate JC02-1 frame
if (len < 27 || buffer[0] != 0xAE || buffer[1] != 0xA7 || buffer[4] != 0x85) {
while (LaserSerial.available()) LaserSerial.read();
return;
}

// Extract distance (0.1m units)
int16_t distRaw = (buffer[7] << 8) | buffer[8];
float distance = distRaw * 0.1;

Serial.printf("Distance: %.1f m\n", distance);

// ---- FORMAT TEXT TO ALWAYS FIT ----
String text;
if (distance < 100.0) {
// xx.x (3 chars max, e.g. "9.8", "12.3", "99.9")
text = String(distance, 1);
} else {
// xxx (e.g. "100", "123")
text = String((int)distance);
}

// ---- DRAW ----
matrix.fillScreen(0); // hard clear every frame
matrix.setTextSize(1);
matrix.setTextColor(matrix.Color(255, 0, 0));

// Each char is 6px wide at size 1 (5 + 1 spacing)
int16_t textWidth = text.length() * 6;
int16_t x = (20 - textWidth) / 2;
if (x < 0) x = 0;

// Vertically: 8px tall font in 10px height → y = 1 looks good
matrix.setCursor(x, 1);
matrix.print(text);
matrix.show();
}

#include <HardwareSerial.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_GFX.h>

#define LASER_RX     16
#define LASER_TX     17
#define MATRIX_PIN   13

#define BAUD_RATE    115200

// JC02-1 continuous measurement command
const uint8_t CONTINUOUS_CMD[] = {0xAE, 0xA7, 0x04, 0x00, 0x0E, 0x12, 0xBC, 0xBE};

HardwareSerial LaserSerial(2);

// YOUR WORKING MATRIX CONFIG (do not change)
#define PANEL_WIDTH  10
#define PANEL_HEIGHT 10
#define MATRIX_WIDTH  (PANEL_WIDTH * 2)  // 20 pixels
#define MATRIX_HEIGHT (PANEL_HEIGHT * 1) // 10 pixels

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(PANEL_WIDTH, PANEL_HEIGHT, 2, 1, MATRIX_PIN,
  NEO_TILE_TOP + NEO_TILE_LEFT +
  NEO_TILE_COLUMNS + NEO_MATRIX_ROWS,
  NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(115200);
  LaserSerial.begin(BAUD_RATE, SERIAL_8N1, LASER_RX, LASER_TX);

  matrix.begin();
  matrix.setRotation(0);

  matrix.setBrightness(60);
  matrix.setTextWrap(false);

  Serial.println("\n=== JC02-1 NeoMatrix Distance Display ===\n");

  // Start continuous measurement
  LaserSerial.write(CONTINUOUS_CMD, sizeof(CONTINUOUS_CMD));
  delay(800);
}

void loop() {
  readDistance();
  delay(100);
}

void readDistance() {
  if (LaserSerial.available() < 27) return;

  uint8_t buffer[32];
  int len = LaserSerial.readBytes(buffer, 27);

  // Validate JC02-1 frame
  if (len < 27 || buffer[0] != 0xAE || buffer[1] != 0xA7 || buffer[4] != 0x85) {
    while (LaserSerial.available()) LaserSerial.read();
    return;
  }

  // Extract distance (0.1m units)
  int16_t distRaw = (buffer[7] << 8) | buffer[8];
  float distance = distRaw * 0.1;

  Serial.printf("Distance: %.1f m\n", distance);

  // ---- FORMAT TEXT TO ALWAYS FIT ----
  String text;
  if (distance < 100.0) {
    // xx.x (3 chars max, e.g. "9.8", "12.3", "99.9")
    text = String(distance, 1);
  } else {
    // xxx (e.g. "100", "123")
    text = String((int)distance);
  }

  // ---- DRAW ----
  matrix.fillScreen(0);                 // hard clear every frame
  matrix.setTextSize(1);
  matrix.setTextColor(matrix.Color(255, 0, 0));

  // Each char is 6px wide at size 1 (5 + 1 spacing)
  int16_t textWidth = text.length() * 6;
  int16_t x = (20 - textWidth) / 2;
  if (x < 0) x = 0;

  // Vertically: 8px tall font in 10px height → y = 1 looks good
  matrix.setCursor(x, 1);
  matrix.print(text);
  matrix.show();
}


3 Likes

Could be useful.
Struggling to read your matrix though, other than the first image.
Have you tried it against a target at a known distance from the device?

Here are measured distances 5, 10, 15 meters. The 26 meters in the first post was the neighbors garage wall not my fence or could have been there tree.

It's raining, now I'm wet.

Over all for the price it is very good. High precision range finders of similar distance are hundreds of $$$$ some are thousands. Do you know how I can change the code so the decimal point only uses 2 led rows rather than the default 5, it's very annoying? Absolut minimum measurable distance is 3.2, my lounge wall is 3.5 so pushing it at this short distance and it flicks between 3.2 and 3.8, changed it's temp housing so easier to read.

Hi there,

May I ask how you have the power wired? As I have a similar unit but it cannot seem to trigger any readings.

All the docs online I can find shows light blue and red are both 3V3, which would mean cna be wired together?

Correct, red and blue together, white is GND. Swap the re/tx lines, or If it is not working your distance may be less than 4 meters, your protocol code is not correct of phasing of the data is not correct etc. It will NOT work in small short distances, it is a LONG range sensor.

The easiest way (for me) would be to define my own characters, giving "6" columns to digits and "3" columns to the decimal.

The mathy way would be...

  • Read the distance.
  • Multiply by 10 to move the decimal to the right.
  • distance modulus 10 (distance % 10) will give the "decimal"
  • draw the decimal point
  • distance divided by 10 (distance / 10) will shift the value "right"
  • repeat for "units" and "tens" place
  • until distance < 0

[EDIT]

I changed my mind...

  • Get your distance
  • Multiply by 10 to move the decimal point off the right column
  • matrix.print() the number (no decimal point)
  • print your own decimal point.

#include <Adafruit_NeoPixel.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_GFX.h>

#define BAUD_RATE    115200
#define MATRIX_PIN    7
#define PANEL_WIDTH  10
#define PANEL_HEIGHT 10
#define MATRIX_WIDTH  (PANEL_WIDTH * 2)  // 20 pixels
#define MATRIX_HEIGHT (PANEL_HEIGHT * 1) // 10 pixels

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(PANEL_WIDTH, PANEL_HEIGHT, 2, 1, MATRIX_PIN,
                            NEO_TILE_TOP + NEO_TILE_LEFT +
                            NEO_TILE_COLUMNS + NEO_MATRIX_ROWS,
                            NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(115200);
  matrix.begin();
  matrix.setRotation(0);
  matrix.setTextSize(1);
  matrix.setTextColor(matrix.Color(255, 0, 0));
  matrix.setBrightness(255);
  matrix.setTextWrap(false);

  showNumbers();
  showDecimal();
  matrix.show();
}

void loop() {
}

void showNumbers() {
  matrix.setTextColor(matrix.Color(255, 0, 0));
  matrix.print("012");
}

void showDecimal() { // 
  matrix.setTextColor(matrix.Color(0, 0, 255));
  int decimalPoint[] = {150, 151, 160, 161}; // four pixel locations
  for (int i = 0; i < 4; i++) {
    matrix.setPixelColor(decimalPoint[i], matrix.Color(255, 0, 0));
  }
}

Many thanks.

I must not have had clean connections on the RxTxas it's now working.