제 구매처 : https://www.aliexpress.com/item/4001253880158.html?spm=a2g0s.9042311.0.0.51cd4c4dFsfQSj&fbclid=IwAR1rvYLt_6CMDVgvjOSD-1njVUQhOB2RBI6-sXNhqkfqUt-XzrxOjSF72IM 

 

13.55US $ |laser radar 360 degree laser radar scanning distance measuring sensor diy wireless transmission infrared data transmi

Smarter Shopping, Better Living! Aliexpress.com

www.aliexpress.com

윗 제품을 한 번 사서 살려봤는데, 제 능력보다는 어떤 이가 어제 딱 성공을 해서 웹에 올려놓은 덕에 저도 따라서 베껴서 성공했습니다.

 

동영상 페북 링크 : https://www.facebook.com/permalink.php?story_fbid=4071094949677690&id=100003316754962&notif_id=1626497694343936&notif_t=feedback_reaction_generic&ref=notif 

 

로그인 또는 가입하여 보기

Facebook에서 게시물, 사진 등을 확인하세요.

www.facebook.com

 

 

 

중요한 내용 : 

     제품 마크 : Radar_MB_1R2T_V1.5.8  or V.1.5.9 <==  제품 밑에 반드시 이 마크가 있는 제품에 국한된 내용입니다.

     헤더 : AA 55 28 28

     시리얼 속도 : 153600

     처음 테스트용 라이다와 PC의 연결은 흔한 $2짜리 USB<=>UART 모듈을 사용하시면 됩니다.

     핀 연결 : 저기 속도 조정 핀은 연결 안해도 됩니다.   

     Serial은 3.3V

     전원은 5V

중요한 화일들 :  화일 이름은 편하게 각자 정하면 됩니다.  저는 my_lidar.py라고 부릅니다.

   이게 메인 Working Code입니다. 

 

   Credit : Vidicon from Discord, Subject : zRGJcqa, #radar_mb_1r2t

 

아래 코드는 matplotlib를 사용해서 너무 느려서, 아래 코드를 사용하지 말고  PyQtGraph 라이브러리를 사용해서 완전히 다시 만든 코드를 사용하십시요.   속도가 100배는 차이가 나는 것 같습니다. 제가 직접 PyQtGraph를 배워가며 만들었습니다.

엄청 빠른 새 코드는 다음 링크에 있습니다. https://wise-self.tistory.com/78?category=1044077

 

아래 코드는 그냥 구조를 보기 좋으니 데이타 구조가 어떤가를 보기용으로만 쓸만합니다.

주의사항은 serial 라이브러리 설치시 pip3 install pyserial 을 해야지, pip3 install serial을 하시면 안됩니다.  (중요)

#!/usr/bin/env python3
import matplotlib.pyplot as plt
import sys
import serial
import time
from enum import Enum
import math

SERIAL_PORT = "/dev/ttyUSB0"

class State(Enum):
	START1 = 0
	START2 = 1
	HEADER = 2
	DATA = 3


def readbytes(file, count):
	data = ser.read(count)
	if len(data) != count:
		print("End of file")
		return False
	return data


if __name__ == "__main__":
		
	try:
		ser = serial.Serial(SERIAL_PORT, 153600, timeout=0.1) 
	except:
		print("could not connect to device")
		exit()
	counter = 0
	rmax = 10.0
	
	dist = plt.subplot(111, polar = True)
	
	data_lenght = 0
	start_angle = 0
	stop_angle = 0


	run = True
	step = (-math.pi*2) 
	try:
		state = State.START1
		while run:

			if state == State.START1:
				data = ser.read(1)
				#if data == False:
				#	break
				if data[0] == 0xAA:
					state = State.START2
				# else:
					# print("sync")
				continue
			elif state == State.START2:
				data = ser.read(1)
				#if data == False:
				#	break
				if data[0] == 0x55:
					state = State.HEADER
				else:
					state = State.START1
					# print("sync2")
				continue
			elif state == State.HEADER:
				data = ser.read(8)
				pack_type = data[0]
				data_lenght = int(data[1])
				start_angle = int(data[3] << 8) + int(data[2])
				stop_angle = int(data[5] << 8) + int(data[4])
				#unknown = int(data[7] << 8) + int(data[6])

				diff = stop_angle - start_angle
				if stop_angle < start_angle:
					diff =  0xB400 - start_angle + stop_angle

				angle_per_sample = 0
				if diff > 1 and (data_lenght-1) > 0:
					angle_per_sample = diff / (data_lenght-1)
				
				# print("[{}]\ttype:{},\tlenght {},\tstart: {},\tstop: {}, \tdiff: {} \tdiff: {}".format(counter, pack_type, data_lenght, start_angle, stop_angle, diff, angle_per_sample), end="\n")
				
				counter += 1
				if pack_type != 40:
					counter = 0

				state = State.DATA
				continue
				
			elif state == State.DATA:
				state = State.START1
				#read data
				data = ser.read(data_lenght * 3)
				#data = readbytes(f, data_lenght * 3) 
				if data == False:
					break
		
				angle_list = []
				distance_list = []
				quality_list = []
		
				for i in range(0, data_lenght):
					
					data0 = int(data[i*3 + 0])
					data1 = int(data[i*3 + 1])
					data2 = int(data[i*3 + 2]) 
					distance = (data2  << 8) + data1

					angle = (start_angle + angle_per_sample * i)
					anglef = step * (angle / 0xB400) 
					angle_list.append(anglef)

					distance_list.append(distance / 1000) # div to convert mm to meter
					quality_list.append(data0) 

				
				if pack_type != 40:
					plt.cla()
					continue # package contance no data skip plot

				dist.scatter(angle_list, distance_list, c = "purple", s = 3) # dot
				# dist.plot(angle_list, distance_list, c = "purple") # line 
				dist.scatter(angle_list, quality_list, c = "red", s = 1)

				dist.set_theta_offset(math.pi / 2)
				dist.set_rmax(rmax)
				dist.set_rmin(0.0)
				plt.pause(0.01)
				rmax = dist.get_rmax()
				
			else:
				print("error")
	

	except KeyboardInterrupt:
		run = False
		exit()

최근에 물건을 3개를 더 받았는데 그 중 2개가 Radar_MB_1R2T_V1.5.8  이 아니고 Radar_MB_1R2T_V1.5.9 이었습니다.

그리고 그 중 1개는 윗 코드로는 작동을 안해서 원인을 찾아보니, 오는 신호에서 type 이 다른 제품들은 40이었는데 이 한개만 60번으로 나오더군요.   찾아낸 방법은 코드의 라인 84번의 #를 풀면 라이다에서 나오는 신호를 분석해서 스크린에 뿌려줍니다.

그래서 그 제품 하나는 따로 코드를 만들었습니다.  라인 87과 120에 type의 값을 40에서 60으로 바꿨더니 작동하더군요.

======================================================================

저번에 이 라이다의 문제를 해결한 이가 이 라이다의 ROS v.1용 드라이버도 완성했습니다. 현재는 초벌입니다.

 https://github.com/Vidicon/mb_1r2t_ros

 

GitHub - Vidicon/mb_1r2t_ros: ROS1 driver for the mb_1r2t lidar

ROS1 driver for the mb_1r2t lidar. Contribute to Vidicon/mb_1r2t_ros development by creating an account on GitHub.

github.com

 

'폐품 Lidar 살리기' 카테고리의 다른 글

ESP32로 곧바로 라이다에 연결하기.  (0) 2022.02.24

관련 Facebook 포스팅 링크입니다.    카카오톡을 안쓰기 때문에 모든 영상은 페북에 올리고 있습니다.

www.facebook.com/permalink.php?story_fbid=4051251598328692&id=100003316754962

 

프로젝트 원본 : https://stereopi.com/blog/opencv-and-depth-map-stereopi-tutorial

 

OpenCV and Depth Map on StereoPi tutorial | StereoPi - DIY stereoscopic camera based on Raspberry Pi

UPD> We have updated version of this article, including C++ code, here: OpenCV: comparing the speed of C++ and Python code on the Raspberry Pi for stereo vision Today we’re pleased to share with you a series of Python examples for OpenCV development. Th

stereopi.com

윗 프로젝트는 원래 Raspberry Pi용으로 만들어져서 PiCam라이브러리를 사용하기 때문에 일반 리눅스 용으로 사용하기에는 불편한데, 코드를 일반 Linux의 USB Cam 용으로 바꿨습니다.

 

첫 코드 1_test.py를 바꾼 내용을 올립니다.   이 코드와 저 사이트에서 다운받는 코드를 비교해 보면 나머지도 어떻게 바꾸는 지 쉽게 나옵니다.   그리고 초저가 USB Dual CAM 이 Dual 파이 캠보다 사용이 훨씬 좋고 resize를 안해서 속도도 훨씬 빨랐습니다.  

바뀐 1_test.py

# Modified by 현자 to work with non-Raspberry Pi PC's
# Cam used: OV9732 Binocular Sync Camera Module, 72 Degree, 1 Million Pixel

import time
import cv2
import os
from datetime import datetime


# File for captured image
filename = './scenes/photo.png'

# Camera settimgs (at 640x240, its default frame rate = 25)
cam_width  = 640  # Width must be dividable by 32
cam_height = 240  # Height must be dividable by 16

print ("Camera Resolution: "+str(cam_width)+" x "+str(cam_height))

# Initialize the camera
camera = cv2.VideoCapture(0)
# Must set camera WORKING camera resolution to get Left/Right side by side
camera.set(cv2.CAP_PROP_FRAME_WIDTH, cam_width)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, cam_height)

t2 = datetime.now()
counter = 0
avgtime = 0
# Capture frames from the camera
while camera.isOpened():  
    ret, frame = camera.read()
    counter+=1
    t1 = datetime.now()
    timediff = t1-t2
    avgtime = avgtime + (timediff.total_seconds())
    cv2.imshow("Both Eyes", frame)
    key = cv2.waitKey(1) & 0xFF
    t2 = datetime.now()
    # if the `q` key was pressed, break from the loop and save last image
    if key == ord("q") :
        avgtime = avgtime/counter
        print ("Average time between frames: " + str(avgtime))
        print ("Average FPS: " + str(1/avgtime))
        if (os.path.isdir("./scenes")==False):
            os.makedirs("./scenes")
        cv2.imwrite(filename, frame)
        break
   
camera.release()

 바뀐 2_chess_cycle.py

# Copyright (C) 2019 Eugene Pomazov, <stereopi.com>, virt2real team
#
# This file is part of StereoPi tutorial scripts.
#
# StereoPi tutorial is free software: you can redistribute it 
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the 
# License, or (at your option) any later version.
#
# StereoPi tutorial is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with StereoPi tutorial.  
# If not, see <http://www.gnu.org/licenses/>.
#
# Most of this code is updated version of 3dberry.org project by virt2real
# 
# Thanks to Adrian and http://pyimagesearch.com, as there are lot of
# code in this tutorial was taken from his lessons.
# 
# ================================================
# Modified by 현자 to work with non-Raspberry Pi PC's
# Cam used: OV9732 Binocular Sync Camera Module, 72 Degree, 1 Million Pixel

import os
import time
from datetime import datetime
import cv2
import numpy as np

# Photo session settings
total_photos = 30             # Number of images to take
countdown = 5                 # Interval for count-down timer, seconds
font=cv2.FONT_HERSHEY_SIMPLEX # Cowntdown timer font
 
# Camera settimgs (at 640x240, its default frame rate = 25)
cam_width  = 640  # Width must be dividable by 32
cam_height = 240  # Height must be dividable by 16

#capture = np.zeros((img_height, img_width, 4), dtype=np.uint8)
print ("Final resolution: "+str(cam_width)+" x "+str(cam_height))

# Initialize the camera
camera = cv2.VideoCapture(0)
# Must set camera WORKING camera resolution to get Left/Right side by side
camera.set(cv2.CAP_PROP_FRAME_WIDTH, cam_width)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, cam_height)

# Lets start taking photos! 
counter = 0
t2 = datetime.now()
print ("Starting photo sequence")
while camera.isOpened():  
   ret, frame = camera.read()
   t1 = datetime.now()
   cntdwn_timer = countdown - int ((t1-t2).total_seconds())
   # If cowntdown is zero - let's record next image
   if cntdwn_timer == -1:
      counter += 1
      filename = './scenes/scene_'+str(cam_width)+'x'+str(cam_height)+'_'+\
                  str(counter) + '.png'
      cv2.imwrite(filename, frame)
      print (' ['+str(counter)+' of '+str(total_photos)+'] '+filename)
      t2 = datetime.now()
      time.sleep(1)
      cntdwn_timer = 0      # To avoid "-1" timer display 
      next
   # Draw cowntdown counter, seconds
   cv2.putText(frame, str(cntdwn_timer), (50,50), font, 2.0, (0,0,255),4, cv2.LINE_AA)
   cv2.imshow("pair", frame)
   key = cv2.waitKey(1) & 0xFF
    
   # Press 'Q' key to quit, or wait till all photos are taken
   if (key == ord("q")) | (counter == total_photos):
      break

 
print ("Photo sequence finished")
camera.release()

아래 화일들은 카메라를 열지 않기때문에 바뀔 필요가 없습니다.

3_pairs_cut.py

4_calibration.py

5_dm_tune.py

 

바뀐 6_dm_video.py

# Copyright (C) 2019 Eugene Pomazov, <stereopi.com>, virt2real team
#
# This file is part of StereoPi tutorial scripts.
#
# StereoPi tutorial is free software: you can redistribute it 
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the 
# License, or (at your option) any later version.
#
# StereoPi tutorial is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with StereoPi tutorial.  
# If not, see <http://www.gnu.org/licenses/>.
#
# Most of this code is updated version of 3dberry.org project by virt2real
# 
# Thanks to Adrian and http://pyimagesearch.com, as there are lot of
# code in this tutorial was taken from his lessons.
# 
# ================================================
# Modified by 현자 to work with non-Raspberry Pi PC's
# Cam used: OV9732 Binocular Sync Camera Module, 72 Degree, 1 Million Pixel

import time
import cv2
import numpy as np
import json
from stereovision.calibration import StereoCalibrator
from stereovision.calibration import StereoCalibration
from datetime import datetime

# Depth map default preset
SWS = 5
PFS = 5
PFC = 29
MDS = -30
NOD = 160
TTH = 100
UR = 10
SR = 14
SPWS = 100

# Camera settimgs (at 640x240, its default frame rate = 25)
cam_width  = 640  # Width must be dividable by 32
cam_height = 240  # Height must be dividable by 16

#capture = np.zeros((img_height, img_width, 4), dtype=np.uint8)
print ("Final resolution: "+str(cam_width)+" x "+str(cam_height))

# Initialize the camera
camera = cv2.VideoCapture(0)
# Must set camera WORKING camera resolution to get Left/Right side by side
camera.set(cv2.CAP_PROP_FRAME_WIDTH, cam_width)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, cam_height)

# Implementing calibration data
print('Read calibration data and rectifying stereo pair...')
calibration = StereoCalibration(input_folder='calib_result')

# Initialize interface windows
cv2.namedWindow("Image")
cv2.moveWindow("Image", 50,100)
cv2.namedWindow("left")
cv2.moveWindow("left", 450,100)
cv2.namedWindow("right")
cv2.moveWindow("right", 850,100)


disparity = np.zeros((cam_width, cam_height), np.uint8)
sbm = cv2.StereoBM_create(numDisparities=0, blockSize=21)

def stereo_depth_map(rectified_pair):
    dmLeft = rectified_pair[0]
    dmRight = rectified_pair[1]
    disparity = sbm.compute(dmLeft, dmRight)
    local_max = disparity.max()
    local_min = disparity.min()
    disparity_grayscale = (disparity-local_min)*(65535.0/(local_max-local_min))
    disparity_fixtype = cv2.convertScaleAbs(disparity_grayscale, alpha=(255.0/65535.0))
    disparity_color = cv2.applyColorMap(disparity_fixtype, cv2.COLORMAP_JET)
    cv2.imshow("Image", disparity_color)
    key = cv2.waitKey(1) & 0xFF   
    if key == ord("q"):
        quit();
    return disparity_color

def load_map_settings( fName ):
    global SWS, PFS, PFC, MDS, NOD, TTH, UR, SR, SPWS, loading_settings
    print('Loading parameters from file...')
    f=open(fName, 'r')
    data = json.load(f)
    SWS=data['SADWindowSize']
    PFS=data['preFilterSize']
    PFC=data['preFilterCap']
    MDS=data['minDisparity']
    NOD=data['numberOfDisparities']
    TTH=data['textureThreshold']
    UR=data['uniquenessRatio']
    SR=data['speckleRange']
    SPWS=data['speckleWindowSize']    
    #sbm.setSADWindowSize(SWS)
    sbm.setPreFilterType(1)
    sbm.setPreFilterSize(PFS)
    sbm.setPreFilterCap(PFC)
    sbm.setMinDisparity(MDS)
    sbm.setNumDisparities(NOD)
    sbm.setTextureThreshold(TTH)
    sbm.setUniquenessRatio(UR)
    sbm.setSpeckleRange(SR)
    sbm.setSpeckleWindowSize(SPWS)
    f.close()
    print ('Parameters loaded from file '+fName)


load_map_settings ("3dmap_set.txt")

# capture frames from the camera
while camera.isOpened():  
    ret, frame = camera.read()
    t1 = datetime.now()
    pair_img = cv2.cvtColor (frame, cv2.COLOR_BGR2GRAY)
    imgLeft = pair_img [0:cam_height,0:int(cam_width/2)] #Y+H and X+W
    imgRight = pair_img [0:cam_height,int(cam_width/2):cam_width] #Y+H and X+W
    rectified_pair = calibration.rectify((imgLeft, imgRight))
    disparity = stereo_depth_map(rectified_pair)
    # show the frame
    cv2.imshow("left", imgLeft)
    cv2.imshow("right", imgRight)    

    t2 = datetime.now()
    print ("DM build time: " + str(t2-t1))

camera.release()

파이나 기타 리눅스 계열의 SBC에서 C나 Python을 사용하지 않으면 요즘 나오는 대부분의 센서를 다루기가 여간 골치가 아닌데, 이걸 간단한 파이션으로 만든 UDP 서버 프로그램을 수정해서  아주 간단하게 socket으로 센서값을 전달받으면 단순하게 풀릴 듯 하다.

 

아래 코드는 파이션 UDP 서버 코드이다.

  출처는 : pythontic.com/modules/socket/udp-client-server-example

아래 코드를 실행시키고 혹시라도 CPU와 RAM의 부하를 봤는데 거의 변화가 없었다.   ㅎㅎㅎ.

  

import socket

 

localIP     = "127.0.0.1"

localPort   = 20001

bufferSize  = 1024

 

msgFromServer       = "Hello UDP Client"

bytesToSend         = str.encode(msgFromServer)

 

# Create a datagram socket

UDPServerSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)

 

# Bind to address and ip

UDPServerSocket.bind((localIP, localPort))

 

print("UDP server up and listening")

 

# Listen for incoming datagrams

while(True):

    bytesAddressPair = UDPServerSocket.recvfrom(bufferSize)

    message = bytesAddressPair[0]

    address = bytesAddressPair[1]

    clientMsg = "Message from Client:{}".format(message)
    clientIP  = "Client IP Address:{}".format(address)
    
    print(clientMsg)
    print(clientIP)

   

    # Sending a reply to client

    UDPServerSocket.sendto(bytesToSend, address)

위의 채팅 서버에 거리측정 센서 VL53L0X를 읽는 코드를 섞어서 센서값을 보내라고 명령하면 보내게만 만들면 컨트롤 프로그램은 통신만 잘 관리하면 된다.   아래 코드는 이 센서를 읽는 코드이다.   

출처 : github.com/johnbryanmoore/VL53L0X_rasp_python

#!/usr/bin/python

# MIT License
# 
# Copyright (c) 2017 John Bryan Moore

import time
import VL53L0X

# Create a VL53L0X object
tof = VL53L0X.VL53L0X()

# Start ranging
tof.start_ranging(VL53L0X.VL53L0X_BETTER_ACCURACY_MODE)

timing = tof.get_timing()
if (timing < 20000):
    timing = 20000
# print ("Timing %d ms" % (timing/1000))

for count in range(1,2):
    distance = tof.get_distance()
    if (distance > 0):
        print ("%d mm, %d cm, %d" % (distance, (distance/10), count))

    time.sleep(timing/1000000.00)

tof.stop_ranging()

====================================================================

For Lazarus IDE's UDP Component for Orange Pi Zero or Raspberry Pi, download from github.com/almindor/lnet

====================================================================

 

몇시간만에 뚝딱뚝딱 간단하게 만들어 본 실험 모델인데 기능들을 넣고 많이 다듬으면 쓸만할 듯 하다.

 

==============================================================================

Completed Basic Structure : 현재 VL53L0X library에 관한 부분들은 모두 #로 막아놨습니다. 

#!/usr/bin/python

import socket
import time
#import VL53L0X

localIP     = "127.0.0.1"
localPort   = 6767
bufferSize  = 1024

msgFromServer       = "Sensor service server connected!"
bytesToSend         = str.encode(msgFromServer)
cmd                 = ""
respStr             = ""

# Create a datagram socket
UDPServerSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)

# Bind to address and ip
UDPServerSocket.bind((localIP, localPort))

print("UDP server up and listening")

# Create a VL53L0X object
#tof = VL53L0X.VL53L0X()

# Start ranging
#tof.start_ranging(VL53L0X.VL53L0X_BETTER_ACCURACY_MODE)

#timing = tof.get_timing()
#if (timing < 20000):
#    timing = 20000

# Listen for incoming datagrams
while(True):
    data, client = UDPServerSocket.recvfrom(bufferSize) 
    cmd = data.decode('utf-8')
    
    #print(cmd)  # for debug purpose

    if cmd == '1' :  # Test by sending '1' from Client Application 
       #for count in range(1,2):
          #distance = tof.get_distance()
          #if (distance > 0):
          #    print ("%d mm, %d cm, %d" % (distance, (distance/10), count))
          #    respStr = f"{distance} mm, {distance/10}"

          #time.sleep(timing/1000000.00)
       respStr = "VL53L0X result"
       bytesToSend = str.encode(respStr)
       UDPServerSocket.sendto(bytesToSend, client)
    else :
       # Sending a reply to client
       respStr = "Server received %s " % cmd 
       bytesToSend = str.encode(respStr)
       UDPServerSocket.sendto(bytesToSend, client)

 

'Orange Pi > O-Pi Zero (H2)' 카테고리의 다른 글

HW-290 (짝퉁 GY-87)  (0) 2023.10.01

https://escapequotes.net/esp8266-wemos-d1-mini-pins-and-diagram/

 

WeMos D1 mini pins and diagram

Diagram Pin Pin Function ESP-8266 Pin TX TXD TXD RX RXD RXD A0 Analog input, max 3.3V input A0 D0 IO GPIO16 D1 IO, SCL GPIO5 D2 IO, SDA GPIO4 D3 IO,10k Pull-up GPIO0 D4 IO, 10k pull-up, BUILTIN_LED GP

escapequotes.net

Pin

PinFunctionESP-8266 PinRXRXDRXDD0IOGPIO16D2IO, SDAGPIO4D4IO, 10k pull-up, BUILTIN_LEDGPIO2D6IO, MISOGPIO12D8IO,10k pull-down, SSGPIO155V5V–RSTResetRST

TX TXD TXD
A0 Analog input, max 3.3V input A0
D1 IO, SCL GPIO5
D3 IO,10k Pull-up GPIO0
D5 IO, SCK GPIO14
D7 IO, MOSI GPIO13
G Ground GND
3V3 3.3V 3.3V

*All IO have interrupt/pwm/I2C/one-wire supported(except D0)

d1 mini shematics (updated schematics):

http://www.wemos.cc/en/latest/_static/files/sch_d1_mini_v3.0.0.pdf

Programming

The D1 mini has a micro USB for auto programming. Also you can programming it using OTA

Warnings

All IO is work at 3.3V.

Source wemos.cc

이건 미완성 코드인데 부분적으로 필요하신 분들은 사용이 가능합니다.   인용을 허합니다.

 

esp32_drone_code.7z
0.01MB

 

이 안에는 3개의 화일이 있는데 모두 아두이노 IDE에서 열면 쉽게 작업할 수 있게 여러 화일탭으로 열립니다.

 

 

현재 델파이로 PC 프로그램에서 직접 ESP32의 bluetooth기능을 이용해서 조종하는 기술은 예제를 거의 찾을 수 없는데 그 이유는 주로 OS 자체의 서포트가 거의 막혀있기 때문입니다.   하지만 스마트폰에는 열려있습니다.

 

한글 사이트 링크 : http://tech.devgear.co.kr/delphi_news/403194

 

Bluetooth Paired Devices on Android with RAD Studio and Delphi

 - http://edn.embarcadero.com/article/43602

 

Bluetooth remote control vehicles.

 - https://github.com/jimmckeeth/BeeMiniCtrl

 

Detecting connected Bluetooth devices on Android

 - http://community.embarcadero.com/index.php/article/technical-articles/146-data/929-detecting-connected-bluetooth-devices

 

 

이건 저의 데모입니다.  BLE가 아니고 그냥 단순한 Bluetooth Classic 데모입니다.  

(ESP32 to Bluetooth to serial 예제)

 

(동영상을 올리려니 kakao에 록인하라고 하는군요.  이런 적이 없었는데.

저는 kakao를 안씁니다.   중국과 관련된 것은 가능한 한 피합니다.

다음이 중국자본에 넘어간 이후 정말 모든 것이 이상해졌습니다.

전에는 제 티스토리에 옆 창에 QR-CODE가 뜨길래 들어가 봤더니 

이상한 불법사이트로 이동되길래 그 이후로 티스토리 사용이 뜸해졌었습니다.

아마 이제 구글.doc으로 옮겨가야 할라나 봅니다.

좌우지간 중국애들이 끼면 다 거지같아지는 건 시간문제입니다.)

와!!  코드가  포멧에 맞춰서 들어간다~~~

 

 

#include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"`

#define _CIRCLE_RAD 10

// (PIN NO) - 4 PhotoResistors (Front, Back, Left, Right)
const int prFrontPin = 36; // default A/D
const int prBackPin  = 39; // default A/D
const int prLeftPin  = 2;  // (*) this analog port dies if Wifi is enabled
const int prRightPin = 14; // (*) this analog port dies if Wifi is enabled
    // Also need to call on setup() to enable as A/D. "adcAttachPin(prLeftPin)";
    // Also need to call on setup() to enable as A/D. "adcAttachPin(prRightPin)";
/* PROBLEM WITH ESP32 A/D : GPIO25, GPIO26, GPIO4, GPIO0 did not work as A/D */  

// For Two Buttons
const int btnRUN      = 13; // Run
const int btnTraining = 15; // Training Neural Network

bool IsRunning = false;
bool IsTraining = false;

// For TB6612FNG Motor Driver

// Currently problem with short number of pins, I will use GPIO16 for all following 
// three pins on motor controller to put it HIGH,
// Will not use PWM, since this board sucks, and PWM has too much trouble.
// The OLED board that I used for this project is by far worst in all the ESP32 boards that I have used so far.
const int md_STBY_pin  = 16;  // Enables whole motor drive.
const int md_A_PWM_pin = 16; // Speed of Motor A 
const int md_B_PWM_pin = 16; // Speed of Motor B 

// I can disable motor A by in1 & in2 both LOW
const int md_AIN1_pin  = 26; // Direction of Motor A
const int md_AIN2_pin  = 0;  // Direction of Motor A

// I can disable motor B by in1 & in2 both LOW
const int md_BIN1_pin  = 12; // Direction of Motor B
const int md_BIN2_pin  = 25; // Direction of Motor B


// (VALUES) 4 PhotoResistors (Front, Back, Left, Right) 
int prFrontValue = 0;
int prBackValue  = 0;
int prLeftValue  = 0;
int prRightValue = 0;

SSD1306Wire  display(0x3c, 5, 4);

int MotorTestL = 0;  // 0 : stop, 1 : Forward, 2 : Backward
int MotorTestR = 0;  // 0 : stop, 1 : Forward, 2 : Backward

const int WheelLeft  = 32; // motor L on my design sheet
const int WheelRight = 27; // motor R on my design sheet

int r = 0; // Simple Counter to replace delay() in the main loop

String s; // Temporary string for general purpose

String sendbuff;
String commandstring;
String cs = ""; // Command String
String rs = ""; // Respond String = 'R' + Command String

char ReplyBuffer[] = "acknowledged"; // a string to send back
unsigned long preMillis = 0;
unsigned long curMillis = 0;

void drawTop(void) {
   display.drawCircle(10, display.getHeight()/2, _CIRCLE_RAD);
   display.display();
}

void drawBottom(void) {
   display.drawCircle(50, display.getHeight()/2, _CIRCLE_RAD);
   display.display();
}

void drawLeft(void) {
   display.drawCircle(30, display.getHeight()-12, _CIRCLE_RAD);
   display.display();
}

void drawRight(void) {
   display.drawCircle(30, 10, _CIRCLE_RAD);
   display.display();
}

void drawTest() {
   display.init();
   display.setContrast(255);
   display.clear();
   display.display();
   
   delay(1000);
   drawTop();
   delay(500);
   drawBottom();
   delay(500);
   drawLeft();
   delay(500);
   drawRight();
   delay(500);  
}

void IRAM_ATTR onRunPressed() {
   if (IsRunning == false) {
      Serial.println("RUN"); 
      IsRunning = true;     
   } else {
      Serial.println("OFF RUN");   
   } 
   MotorTestL += 1;
   if (MotorTestL > 2) { MotorTestL = 0; }
   
}

void IRAM_ATTR onTrainingPressed() {
   if (IsTraining == false) {
      Serial.println("TRAINING");   
      IsTraining = true;  
   } else {
      Serial.println("OFF TRAINING");   
   }  
   MotorTestR += 1;
   if (MotorTestR > 2) { MotorTestR = 0; }

   
}

void turn_right() {  // Turn Right
   digitalWrite(md_AIN1_pin, HIGH);
   digitalWrite(md_AIN2_pin, LOW);
   digitalWrite(md_BIN1_pin, LOW);
   digitalWrite(md_BIN2_pin, HIGH);
}

void turn_left() {  // Turn Left
   digitalWrite(md_AIN1_pin, LOW);
   digitalWrite(md_AIN2_pin, HIGH);
   digitalWrite(md_BIN1_pin, HIGH);
   digitalWrite(md_BIN2_pin, LOW);
}

void move_forward() {  // 
   digitalWrite(md_AIN1_pin, HIGH);
   digitalWrite(md_AIN2_pin, LOW);
   digitalWrite(md_BIN1_pin, HIGH);
   digitalWrite(md_BIN2_pin, LOW);
}

void move_backward() {  // 
   digitalWrite(md_AIN1_pin, LOW);
   digitalWrite(md_AIN2_pin, HIGH);
   digitalWrite(md_BIN1_pin, LOW);
   digitalWrite(md_BIN2_pin, HIGH);
}

void wheel_stop() {
   digitalWrite(md_AIN1_pin, LOW);
   digitalWrite(md_AIN2_pin, LOW);
   digitalWrite(md_BIN1_pin, LOW);
   digitalWrite(md_BIN2_pin, LOW); 
}
void setup() { 
   r = 0;
   
   pinMode(btnRUN,   INPUT);     // BUTTON B1
   pinMode(btnTraining, INPUT);  // BUTTON B2

   attachInterrupt(btnRUN, onRunPressed, RISING);
   attachInterrupt(btnTraining, onTrainingPressed, RISING);

   pinMode(md_STBY_pin, OUTPUT); // 16; // (6 will CRASH) Enables whole motor drive.

   pinMode(md_AIN1_pin, OUTPUT); // 26;// Direction of Motor A
   pinMode(md_AIN2_pin, OUTPUT); // 0; // Direction of Motor A
//   pinMode(md_A_PWM_pin, OUTPUT);// 16; // Speed of Motor A 

   pinMode(md_BIN1_pin, OUTPUT); // 12;// Direction of Motor A
   pinMode(md_BIN2_pin, OUTPUT); // 25; // Direction of Motor A
//   pinMode(md_B_PWM_pin, OUTPUT);// 16; // Speed of Motor A 
   
   Serial.begin(112500);

   drawTest();

   adcAttachPin(prLeftPin);
   adcAttachPin(prRightPin);

   digitalWrite(md_STBY_pin, HIGH);  // it will make ON for both PWMA, PWMB also
//   digitalWrite(md_A_PWM_pin, HIGH);
//   digitalWrite(md_B_PWM_pin, HIGH);

   move_forward();  delay(1000);

   wheel_stop();    delay(1000);

   move_backward(); delay(1000);
   
   wheel_stop();    delay(1000);

   turn_right(); delay(1000);
   
   wheel_stop();    delay(1000);

   turn_left(); delay(1000);
   
   wheel_stop();    delay(1000);
}

void loop() {
   r++;
   if (r > 650000){ // THIS IS DESCENT DELAY  // about 1 sec
      prFrontValue = analogRead(prFrontPin);   // delay in between reads for stability
      prBackValue = analogRead(prBackPin);    // delay in between reads for stability
      prLeftValue = analogRead(prLeftPin);   // delay in between reads for stability
      prRightValue = analogRead(prRightPin);  // delay in between reads for stability 
     // delay(50);  // delay in between reads for stability 

      s = " ";
      s += prFrontValue;
      s += ", ";
      s += prBackValue;
      s += ", ";
      s += prLeftValue;
      s += ", ";
      s += prRightValue;
      Serial.println(s);
      
      r = 0;
   }  
}

WorkFocusClock.exe 는 요즘 유행하는 Deadline Driven Development (마감시간에 쫒기는 듯 개발하기?) 에 사용하기 편하도록 아주 단순하게 만들어진 PC용 모래시계입니다.

   

이 프로그램은 PC에 어떤 화일도 생성하거나 고치지 않고 네트웍을 사용하지 않고 윈도우 자체에 따라오는 딱 3개의 음악 wav 화일만 알람용으로 읽기 전용으로 사용합니다.  그래서 따로 사용자 세팅을 저장하지 않습니다.   도움말도 없고 무지무지 단순하게 몇시간만에 뚝딱뚝딱 만든 공유하기도 민망한 프로그램인데 나름 쓸만 하더군요.   

진짜 모래시계는 시간을 못바꾸는데 이건 시간을 그냥 마음대로 지정해 줄 수 있고 단순하게라도 집중할 내용을 간단하게 적어놓을수 있게 만들었습니다.


처음 시작할때 1초 음악이 흐르고, 

종료 5분 전에 1초 음악이 흐르고,

종료때 15초간 긴 음악이 흐릅니다. 


화일 : WorkFocusClock.zip

      이 버전은 분(minute)의 값을 1~100000까지로 제한해서 에러확률을 줄인 버전입니다.

      이 버전은 마지막 15초 알람때 15초간은 잘 안꺼지던문제를 해결했습니다.  

      그리고 한글화작업을 조금 더 했습니다.

      그리고 도서실같은 곳 용으로 소리 묵음 (No Sound)기능 추가했습니다.




설치 : 다운받아 Zip 화일을 푸시면 그냥 WorkFocusClock.exe 화일 하나 딸랑 있고 다른 설치도 필요 없고 아무데나 원하시는 위치에 넣고 쓰시면 됩니다.


사용법 : 

실행하시면 다음 창이 뜹니다.


그럼 마우스로 아래 메모창을 쓱 긁어서 

첫줄에는 몇분을 원하시는 숫자만를 넣어 주시고 

그 다음줄부터는 아무 내용이나 

마음대로 넣으시면 됩니다.


예로 200분 동안을 모래시계의 시간으로 정하셨고,

밑에 내용은 본인에게 자기암시용

다짐같은걸 적어 놓으시면 됩니다.


그리고 (RESET-시작)을 누르시는 순간 모래시계가 작동하기 시작합니다.

중간에 잠시 멈추시거나 할때는 (PAUSE-멈춤)를 누르시면 됩니다.


참 쉬입죠잉~~



아래는 여러개의 사항을 프로그램을 상황의 수에 맞게 동시에 실행시킨 경우입니다.



-------------------------------------------------------------------------------

소스코드는 Delphi 10.2로 만들어져 있습니다.  혹시 필요하신 분들을 위해서. 

WorkFocusTimer_DelphiCode.zip


NodeMCU w/ Motor Sheild 예문의 실제 실험 사진들

원본은 2015년 11월 제작.



코드 : code_rev.txt




예문의 소스는 브라우저에서뿐만이 아니라 USB Serial로도 동시에 컨트롤이 가능한 코드입니다.


실행시 양방향에서 명령을 받도록 만들어졌습니다


USB Serial로 직접 컨트롤할때는 명령어가 달라집니다.


예)  메인 라잇일 키고 끌때 ㅣ #ML:000>    #ML:001>


오른쪽 서보 속도   #RS:450>   450은 중립, 350이나 550은 각 방향대로 회전합니다.


왼쪽 서보 속도   #LS:450>   450은 중립, 350이나 550은 각 방향대로 회전합니다.


모터 스탑    #ST:000>   


서보 양쪽 바퀴의 속도를 동시에 명령할때     #MW:450,450>   


보드 위의 불을 킬때 #HL:000>   끌때는 001


전체 프로그램의 구조를 이해하면 자신의 명령어와 실행 코드를 만들어 넣을수 있읍니다.   기본적으로 두글자 명령어에 두개까지의 숫자 파라메터를 받는 구조입니다.


(위 : USB Serial 로 컨트롤하는 프로그램.  

속도가 빠르고 복잡한 코딩이 가능해서,  로봇용으로는 더 적합합니다..)

It is far easier and far faster to control through USB serial.  

Also extremely complex coding is possible on desktop PC application.



(아래부터는 웹 브라우저로 맨 밑의 로봇 제어하는 예.   자동화주택에 적합합니다)


Controlling through web browser is rather slow and dull.   

It can be good for home automation.








(아래 로봇은 최소한의 장비로 교육용으로 간단하게 만들었습니다.)

BOM (Bill of material)

NodeMCU w/ Motorshield  ~ $13

1 Main Light (12V DC Flashlight) - 폐품이용

2 Continous Servo w/ rubber tire wheel  ~ $25

3 Metal plates from dead hard-disk drives - 폐품이용

Some nuts and bolts & 2-side tape (나사랑 볼트랑 그리고 양면테잎)

Cellphone Power Bank 셀폰용 파워팩(5V 1.2A)  ~ $10 cheap one.

Cut-off USB Cable for Power Connection (한쪽을 짤라서 파워 연결용으로 사용) - 폐품이용

앞바퀴는 일반 싸구려서랍장에 따라오는 바퀴(주로 안쓰고 놔뒀다가 이런데 사용) - 폐품이용


(*) 가장 힘든 부분은 메탈판에 나사용 구멍뚫기 (드레멜 드릴 사용)


대략 비용 $50 아래  (오만원 이하)

보통 익숙하면 하루면 대충 조립가능.  작동실험중으로 아직 조립도 완벽히 끝나지 않은 상태 사진입니다.)













(추가) 사진 보충설명과 ESP32에 코드만 공요합니다.  

 

PC쪽은 너무 복잡해서 공유와 설명이 도저히 불가능합니다.

 

(PC쪽에 가장 중요한 내용은 firewall에서 통신을 막으니 실험중에 firewall을 중지시키는 방법과 아니면 inbound와 outbound 룰을 사용하실 포트번호로 추가하여 여는 방법이 있습니다.)

 

 

 

 

칩에 1 써있는 모듈이 ESP32 Development Board입니다.

바로 옆에 있는 모듈은 Step-Down Variable Buck Converter로 

 9V 밧데리의 전압을 3V3으로 조정해 주는 파워모듈입니다.

그 위에 녹색 모듈이 MPU9250 (9축 센서),

그 옆에 보라색 모듈이 VL53L0X (레이저 거리측정센서) 

 

이번에는 VL53L0X는 사용을 안했고, 

  MPU9250의 기능도 딱 3D-Compass만 사용했습니다.

현재 모든 시리얼 관련코드들은 //로 막아놨는데 그냥 풀면 됩니다.

 

 

각각의 라이브러리는 

MPU9250 : https://github.com/asukiaaa/MPU9250_asukiaaa

VL53L0X : https://github.com/pololu/vl53l0x-arduino/releases

 

// Copyright : FREE for ALL. 맘껏 복사하고 고치고 필요에 맞게 쓰세요.

 

#include <WiFi.h>

#include <WiFiUdp.h>

#include <Wire.h>

#include <MPU9250_asukiaaa.h>

 

#define _ESP32_HAL_I2C_H_

 

#ifdef _ESP32_HAL_I2C_H_

#define SDA_PIN 21

#define SCL_PIN 22

#endif

 

#define BUFFER_SIZE 100

 

// WiFi network name and password:

const char * networkName = "******";  // 집이나 사무실 와이파이 아이디

const char * networkPswd = "******";  // 와이파이 연결 비밀번호

 

//IP address to send UDP data to:

// either use the ip address of the server or 

// a network broadcast address

const char * udpAddress = "192.168.1.200";   // 저의 PC의 고정주소

const int udpPort = 6767;                    // 제가 로봇용으로 주로 사용하는 포트번호

 

//The udp library class

WiFiUDP udp;

 

//Are we currently connected?

boolean connected = false;

 

// 9 DOF Gyro, Accel, Mag Sensor; here I only use 3 Axis Mag

MPU9250_asukiaaa DOF9Sensor;

 

float mDirection, mx, my, mz;

 

int r = 0; // Simple Counter to replace delay() in the main loop

 

String s; // Temporary string for general purpose

 

void setup()

{

//   while(!Serial);

//   Serial.begin(19200);

 

   //Connect to the WiFi network

   connectToWiFi(networkName, networkPswd);   

 

#ifdef _ESP32_HAL_I2C_H_ // For ESP32

   Wire.begin(SDA_PIN, SCL_PIN); // SDA, SCL

#else

   Wire.begin();

#endif

 

   //////////////// MPU9250 9-DOF SENSOR //////////////////////// 

   DOF9Sensor.setWire(&Wire);

   DOF9Sensor.beginMag();

 

   // 이 오프셋은 제 책상에서 사용하는 장비의 위치에서 나오는 기본 오차량

   DOF9Sensor.magXOffset = 66;

   DOF9Sensor.magYOffset = 18;

   DOF9Sensor.magZOffset = 41;

 

//   Serial.println("SensorId: " + String(DOF9sensorId));

 

   delay(1000);

}

 

void connectToWiFi(const char * ssid, const char * pwd){

//   Serial.println("Connecting to WiFi network: " + String(ssid));

 

   // delete old config

   WiFi.disconnect(true);

   //register event handler

   WiFi.onEvent(WiFiEvent);

  

   //Initiate connection

   WiFi.begin(ssid, pwd);

 

   connected = true;

 

//   Serial.println("Waiting for WIFI connection...");

}

 

//wifi event handler

void WiFiEvent(WiFiEvent_t event){

    switch(event) {

      case SYSTEM_EVENT_STA_GOT_IP:

          //When connected set 

//          Serial.print("WiFi connected! IP address: ");

//          Serial.println(WiFi.localIP());  

          //initializes the UDP state

          //This initializes the transfer buffer

          udp.begin(WiFi.localIP(),udpPort);

          connected = true;

          break;

      case SYSTEM_EVENT_STA_DISCONNECTED:

//          Serial.println("WiFi lost connection");

          connected = false;

          break;

    }

}

 

void print9DOF() {

   s = "<R=3Mag:4,";

   s += mx; s += ","; s += my; s += ","; s += mz; s += ",";

   s += mDirection; s += ">";

//   Serial.println(s);

 

   //only send data when connected

   if(connected){

      //Send a packet

      udp.beginPacket(udpAddress,udpPort);

      udp.print(s);

      udp.endPacket();

   }  

}

 

void loop() {

 

   r++;

   if (r == 22000){ // THIS IS DESCENT DELAY

 

      DOF9Sensor.magUpdate();

      mx = DOF9Sensor.magX();

      my = DOF9Sensor.magY();

      mz = DOF9Sensor.magZ();

      mDirection = DOF9Sensor.magHorizDirection();

      

      print9DOF();

 

      r = -22000;

   }

 

}

+ Recent posts