KOI AI 氣墊球

搭建說明書
接線圖
KOI接線圖

舵機接線圖

套件內容
額外所需物資
操作教學
示範短片
參考程式
舵機校正
Last updated
import sensor, image, time, lcd, utime
import math, ustruct
from maix import KPU, GPIO, I2S
from machine import UART, I2C
from fpioa_manager import fm
import gc
grid = [
(56,74),(96,74),(137,72),(179,70),(220,68),(262,67),
(52,113),(94,116),(136,112),(179,109),(222,107),(264,104),
(51,157),(93,156),(139,154),(181,151),(227,150),(269,147),
]
PCA9685_ADDRESS = 0x40
MODE1 = 0x00
MODE2 = 0x01
SUBADR1 = 0x02
SUBADR2 = 0x03
SUBADR3 = 0x04
PRESCALE = 0xFE
LED0_ON_L = 0x06
LED0_ON_H = 0x07
LED0_OFF_L = 0x08
LED0_OFF_H = 0x09
ALL_LED_ON_L = 0xFA
ALL_LED_ON_H = 0xFB
ALL_LED_OFF_L = 0xFC
ALL_LED_OFF_H = 0xFD
S1 = 0x1
S2 = 0x2
S3 = 0x3
S4 = 0x4
S5 = 0x5
S6 = 0x6
S7 = 0x7
S8 = 0x8
RESTART = 0x80
SLEEP = 0x10
ALLCALL = 0x01
INVRT = 0x10
OUTDRV = 0x04
RESET = 0x00
positions = [
[240, 195], [235, 175], [220, 155], [210, 135], [190, 125], [160, 115],
[260, 190], [250, 165], [235, 145], [220, 125], [195, 105], [170, 95],
[285, 190], [275, 170], [250, 140], [220, 105], [195, 85], [170, 70]
]
class RobotBit:
def __init__(self):
self.address = PCA9685_ADDRESS
self.i2c = I2C(I2C.I2C0, freq=400000, scl=17, sda=14, addr_size=7)
self.i2c.writeto(self.address, bytearray([MODE1, RESET])) # reset not sure if needed but other libraries do it
self.i2c.writeto(self.address, bytearray([MODE1, RESET]))
self.i2c.writeto(self.address, bytearray([MODE2, OUTDRV]))
self.i2c.writeto(self.address, bytearray([MODE1, ALLCALL]))
time.sleep_ms(5)
mode1 = self.i2c.readfrom_mem(self.address, MODE1, 1)[0]
mode1 = mode1 & ~SLEEP # wake up (reset sleep)
self.i2c.writeto(self.address, bytearray([MODE1, mode1]))
time.sleep_ms(5)
self.set_pwm_freq(50)
self.inited = True
def set_pwm_freq(self, freq_hz):
"""Set the PWM frequency to the provided value in hertz."""
prescaleval = 25000000.0 # 25MHz
prescaleval /= 4096.0 # 12-bit
prescaleval /= float(freq_hz)
prescaleval -= 1.0
prescale = int(math.floor(prescaleval + 0.5))
oldmode = self.i2c.readfrom_mem(self.address, MODE1, 1)[0]
newmode = (oldmode & 0x7F) | 0x10 # sleep
self.i2c.writeto(self.address, bytearray([MODE1, newmode]))
self.i2c.writeto(self.address, bytearray([PRESCALE, prescale]))
self.i2c.writeto(self.address, bytearray([MODE1, oldmode]))
time.sleep_ms(5)
self.i2c.writeto(self.address, bytearray([MODE1, oldmode | 0x80]))
def set_pwm(self, channel, on, off):
"""Sets a single PWM channel."""
if not self.inited:
self.initRobotBit()
if on is None or off is None:
data = self.i2c.mem_read(4, self.address, LED0_ON_L+4*channel)
return ustruct.unpack('<HH', data)
self.i2c.writeto(self.address, bytearray([LED0_ON_L+4*channel, on & 0xFF]))
self.i2c.writeto(self.address, bytearray([LED0_ON_H+4*channel, on >> 8]))
self.i2c.writeto(self.address, bytearray([LED0_OFF_L+4*channel, off & 0xFF]))
self.i2c.writeto(self.address, bytearray([LED0_OFF_H+4*channel, off >> 8]))
def geekServo(self, index, degree):
# 50hz: 25,000 us
# 500~2650us->0~360
# v_us = degree * 50 / 9 +500
v_us = 200/36*degree + 500 # calibrated
value = int(v_us*4096/20000)
self.set_pwm(index+7, 0, value)
class AirHockey:
def __init__(self):
self.thresholdMap = {
'red': [30,100,15,127,15,127],
'blue': [0,50,-64,64,-127,-20],
'red2': [29,98,-9,57,0,47]
}
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
lcd.init()
lcd.rotation(0)
lcd.clear()
isFront = True
if isFront:
sensor.__write_reg(0x3820, 0x40)
sensor.__write_reg(0x3821, 0x06)
sensor.__write_reg(0x4514, 0xbb)
else:
sensor.__write_reg(0x3820, 0x46)
sensor.__write_reg(0x3821, 0x06)
sensor.__write_reg(0x4514, 0xaa)
#sensor.set_vflip(True)
#sensor.set_hmirror(True)
sensor.run(1)
sensor.skip_frames(time = 2000)
self.displayGrid()
self.cachedPosition = []
self.t0 = time.ticks_ms()
self.lastIndex = -1
self.robo = RobotBit()
def displayGrid(self):
self.img = sensor.snapshot()
for i in range(len(grid)):
self.img.draw_cross(grid[i][0],grid[i][1],5,color=(0,255,0))
lcd.display(self.img)
def colorCalibrate(self, key, r=[(320//2)-(50//2), (240//2)-(50//2), 50, 50]):
for i in range(60):
self.img = sensor.snapshot()
self.img.draw_string(40, 0, "put the color\nin the rect", scale=2,color=(0,255,0))
self.img.draw_rectangle(r)
lcd.display(self.img)
threshold = [50, 50, 0, 0, 0, 0] # Middle L, A, B values.
for i in range(60):
self.img = sensor.snapshot()
self.img.draw_string(40, 0, "be learning...", scale=2,color=(0,255,0))
hist = self.img.get_histogram(roi=r)
lo = hist.get_percentile(0.01) # Get the CDF of the histogram at the 1% range (ADJUST AS NECESSARY)!
hi = hist.get_percentile(0.99) # Get the CDF of the histogram at the 99% range (ADJUST AS NECESSARY)!
# Average in percentile values.
threshold[0] = (threshold[0] + lo.l_value()) // 2
threshold[1] = (threshold[1] + hi.l_value()) // 2
threshold[2] = (threshold[2] + lo.a_value()) // 2
threshold[3] = (threshold[3] + hi.a_value()) // 2
threshold[4] = (threshold[4] + lo.b_value()) // 2
threshold[5] = (threshold[5] + hi.b_value()) // 2
for blob in self.img.find_blobs([threshold], pixels_threshold=100, area_threshold=100, merge=True):
self.img.draw_rectangle(blob.rect())
self.img.draw_cross(blob.cx(), blob.cy())
self.img.draw_rectangle(r)
lcd.display(self.img)
self.thresholdMap[key] = threshold
print(threshold)
def getPosition(self, x, y):
# calculate the closest grid index
minDist = 100000
minIndex = -1
for i in range(len(grid)):
dist = (grid[i][0]-x)**2 + (grid[i][1]-y)**2
if dist < minDist:
minDist = dist
minIndex = i
return minIndex
def setPositon(self, index):
pos = positions[index]
self.robo.geekServo(S8, pos[1])
self.robo.geekServo(S7, pos[0])
def colorTrack(self,key='red'):
cx=-1
cy=-1
maxArea = 0
#([self.thresholdMap[key]], pixels_threshold=100, area_threshold=100, merge=True, margin=10)
for blob in self.img.find_blobs([self.thresholdMap[key]], pixels_threshold=200, area_threshold=200, merge=True, margin=10):
self.img.draw_rectangle(blob.rect())
self.img.draw_cross(blob.cx(), blob.cy())
cx = blob.cx()
cy = blob.cy()
if blob.rect()[2] * blob.rect()[3] > maxArea:
maxArea = blob.rect()[2] * blob.rect()[3]
return (cx, cy)
def tick(self):
self.img = sensor.snapshot()
(rx, ry) = self.colorTrack('red')
if rx == -1:
self.cachedPosition = []
lcd.display(self.img)
self.setPositon(8)
return
index = self.getPosition(rx, ry)
self.img.draw_cross(grid[index][0],grid[index][1],5,color=(0,255,0))
print(index)
# print history lines
for i in range(len(self.cachedPosition)):
self.img.draw_cross(self.cachedPosition[i][0], self.cachedPosition[i][1],color=(0,100,200))
lcd.display(self.img)
self.setPositon(index)
self.cachedPosition.append((rx,ry))
if len(self.cachedPosition) > 10:
self.cachedPosition.pop(0)
def run(self):
while True:
self.tick()
time.sleep_ms(50)
gc.collect()
ak = AirHockey()
#ak.colorCalibrate(key='red2')
ak.run()