Адъ меломана Vinyl RPi T.B.D.
Ретроградство в процессе, или как сделать что-то виниловый проигрыватель из мусора. Накидан кое-какой код для проекта винилового проигрывателя, обкатка проводится на старом столике Unitra 602 с двигателем PRM-33-1.9. Работает, как и ожидалось, в свете скорого выхода свежего Кипелова на LP – более чем актуально 😉
Есть два варианта (даже три): на Raspberry Pi B+, на Arduino Pro Mini, на stm8s. Вариант на RPi мне наиболее симпотичен, но на борту не хватает простенького 8-10 битного АЦП, зато есть два аппаратных канала ШИМ (что активно используется в проекте). Arduino и stm8s интересны прежде всего ограниченностью ресурсов, то есть закатать код управления двигателем, правильный обработчик прерываний (таки парочку), да еще и ПИД-регулятор – это модно, затратно и интересно 😉 Но рассматриваться в паблике не будет, ибо скучно и долго, в догонку, плюс ко всему, некое подобие “разработки” для stm8 ведется с помощью sdcc, без использования HAL, что существенно увеличивает градус 😉
Сия фото – процесс подготовки для затравки.
Разгон диска работает без регуляции, просто “проскоком” выше нужных оборотов. Как только диск стартовал – подключается контроллер, благо дури много и нет необходимости уделять излишне много внимания. На “коротких” (миллисекундных) интервалах удержание оборотов великолепное. В процессе стали понятны особенности реализации – почему, например, в энкодере напилено столько дырок (простите, отверстий) – я вот перебрался на датчики Холла и крошечные магниты. Регулятор оборотов работает на L298, “рулится” ШИМом, просто и понятно.
Короче, итерации софта будут обновлятся 😉
#!/usr/bin/env python
import time
import pigpio
import sys
pin_motor_dir = 16 # 17
pin_motor_pwm = 19 # 18
pin_strobe_dir = 17 # 16
pin_strobe_pwm = 18 # 19
pin_hall_speed = 4
pin_hall_tonearm = 12
pin_relay_tonearm = 21
pin_relay_aux = 20 # power
pin_speed_33 = 23
pin_speed_45 = 24
fixed_strobe_pwm = 50 # AC freq
fixed_motor_pwm = 32000
#gearratio=23.69
gearratio=23.82 # you own values!!
#max_duty = 880000 # 5V NOT ENOUGH for SOLENOID tonearm LOCK!!!
max_duty = 280000 # 12V
time_per_rev_33=60000000.0/(33.3*gearratio)
time_per_rev_45=60000000.0/(45.0*gearratio)
time_per_rev = 0 # selected by speed button
duty = max_duty
tickdiff = 0
lasttick = 0
tonearmlock = False
motorrunning = False
stroberunning = False
powermains = False
motorrpm = 0 #??
sleep_time=0.001 # cycle lenght for loop and pid calcs
duty_correction=5000 #???
cmd_speed = 0
menutime=0
menutimelast=round(time.time())
menutimeout=3
def cbf_hall_speed(gpio,level,tick):
global tickdiff
global lasttick
if not lasttick==0:
tickdiff = tick-lasttick
lasttick=tick
def cbf_hall_tonearm(gpio,level,tick):
#global tonearmlock
#pi.write(pin_relay_tonearm, 1)
#tonearmlock = False
global time_per_rev
time_per_rev = 0
tonearm(False)
def cbf_button_speed(gpio,level,tick):
global time_per_rev
if (motorrunning):
print(gpio,level,tick)
if (gpio == pin_speed_33):
time_per_rev=time_per_rev+100
elif (gpio == pin_speed_45):
time_per_rev=time_per_rev-100
def init():
print(time.ctime(), "Vinyl_RPi init:", end='')
if not pi.connected:
print("\npigpiod not running")
exit()
pi.set_mode(pin_speed_33, pigpio.INPUT)
pi.set_pull_up_down(pin_speed_33, pigpio.PUD_UP)
#pi.callback(pin_speed_33, pigpio.FALLING_EDGE,cbf_button_speed)
pi.set_mode(pin_speed_45, pigpio.INPUT)
pi.set_pull_up_down(pin_speed_45, pigpio.PUD_UP)
#pi.callback(pin_speed_45, pigpio.FALLING_EDGE,cbf_button_speed)
pi.set_mode(pin_motor_dir, pigpio.OUTPUT)
pi.write(pin_motor_dir, 0)
pi.set_mode(pin_motor_pwm, pigpio.OUTPUT)
pi.hardware_PWM(pin_motor_pwm, fixed_motor_pwm, 0)
pi.set_mode(pin_strobe_dir, pigpio.OUTPUT)
pi.write(pin_strobe_dir, 0)
pi.set_mode(pin_strobe_pwm, pigpio.OUTPUT)
pi.hardware_PWM(pin_strobe_pwm, fixed_strobe_pwm, 0)
pi.set_mode(pin_relay_tonearm, pigpio.OUTPUT)
pi.write(pin_relay_tonearm, 1)
pi.set_mode(pin_relay_aux, pigpio.OUTPUT)
pi.write(pin_relay_aux, 1)
pi.set_mode(pin_hall_speed, pigpio.INPUT)
pi.set_pull_up_down(pin_hall_speed, pigpio.PUD_UP)
pi.callback(pin_hall_speed, pigpio.RISING_EDGE,cbf_hall_speed)
pi.set_mode(pin_hall_tonearm, pigpio.INPUT)
pi.set_pull_up_down(pin_hall_tonearm, pigpio.PUD_UP)
pi.callback(pin_hall_tonearm, pigpio.EITHER_EDGE,cbf_hall_tonearm)
print(" done.")
def motor(duty):
if (duty>max_duty): # never overrun motor!!
duty=max_duty
elif (duty<0): # negative values impossible ;)
duty=0
pi.hardware_PWM(pin_motor_pwm,fixed_motor_pwm,duty)
def tonearm(lock):
global tonearmlock
if (lock and not tonearmlock):
pi.write(pin_relay_tonearm,0)
print(time.ctime(), "Tonearm locked")
tonearmlock=True
elif (tonearmlock):
pi.write(pin_relay_tonearm,1)
tonearmlock=False
print(time.ctime(), "Tonearm unlocked")
def strobe(on):
global stroberunning
if (on and not stroberunning):
pi.hardware_PWM(pin_strobe_pwm,fixed_strobe_pwm,120000)
print(time.ctime(), "Strobe on")
stroberunning=True
elif (stroberunning):
pi.hardware_PWM(pin_strobe_pwm,fixed_strobe_pwm,0)
print(time.ctime(), "Strobe off")
stroberunning=False
def power(on):
global powermains
if (on and not powermains):
print(time.ctime(), "Power: ", end='')
pi.write(pin_relay_aux,0)
powermains=True
time.sleep(1)
print(" on")
elif (powermains):
print(time.ctime(), "Power: ", end='')
pi.write(pin_relay_aux,1)
powermains=False
#time.sleep(1)
print(" off")
def pid_controller(y,yc,h=sleep_time,Ti=0.002,Td=0.8,Kp=0.001,u0=1420,e0=560): # https://gist.github.com/chaosmail/8372717
#def pid_controller(y,yc,h=sleep_time,Ti=0.2,Td=0.8,Kp=0.2,u0=0,e0=0): # https://gist.github.com/chaosmail/8372717
k = 0
ui_prev = u0
e_prev = e0
while 1:
e=yc-y
ui=ui_prev+1.0/Ti*float(h)*e
ud=1.0/Td*(e-e_prev)/float(h)
e_prev=e
ui_prev=ui
u=Kp*(e+ui+ud)
k+=1
#print(e,ui,ud,u)
yield u
def state_machine(pin1,pin2):
global menutime
global menutimelast
global time_per_rev
now=round(time.time())
if (menutime==menutimelast):
return
if (menutimeout<(menutime-menutimelast)):
menutime=0
return
#print(now,menutime,menutimelast)
pin1_state=pi.read(pin1)
pin2_state=pi.read(pin2)
if (pin1_state==0 and pin2_state==0):
if (menutime > menutimeout):
time_per_rev = 0
menutime=0
else:
menutime=menutime+(now-menutimelast)
menutimelast=now
elif (pin1_state==0):
if (menutime > menutimeout):
if (time_per_rev == time_per_rev_33):
return
time_per_rev = time_per_rev_45
print(time.ctime(), "Speed selected: 45", )
menutime=0
else:
menutime=menutime+(now-menutimelast)
menutimelast=now
elif (pin2_state==0):
if (menutime > menutimeout):
if (time_per_rev == time_per_rev_45):
return
time_per_rev = time_per_rev_33
print(time.ctime(), "Speed selected: 33", )
menutime=0
else:
menutime=menutime+(now-menutimelast)
menutimelast=now
def init_motor(time_per_rev):
global motorrunning
print(time.ctime(), "Motor:", end='')
motor(max_duty)
time.sleep(1) # minimal delay
while (tickdiff*0.95>time_per_rev):
#print(tickdiff,time_per_rev,max_duty)
pass
motorrunning=True
motor(0)
print(" running")
def readcmd():
global cmd_speed
if (len(sys.argv) == 2):
cmd_readspeed = str(sys.argv[1])
if (cmd_readspeed == '33' or cmd_readspeed == '45'):
cmd_speed = cmd_readspeed
print(time.ctime(), "Cmd speed selected: ", cmd_speed)
pi = pigpio.pi()
init()
readcmd()
# loop
try:
while True: # loop
time.sleep(sleep_time)
state_machine(pin_speed_33,pin_speed_45) # check speed button and later encoder for speed correction
if (time_per_rev==0):
motor(0)
motorrunning=False
tonearm(False)
strobe(False)
power(False)
tickdiff=0
sleep_time=1 # cycle lenght for loop and pid calcs
elif (not motorrunning):
power(True)
init_motor(time_per_rev)
strobe(True)
tonearm(True)
duty=max_duty
sleep_time=0.001 # cycle lenght for loop and pid calcs
if (motorrunning):
duty=duty+int(next(pid_controller(180000-tickdiff,180000-time_per_rev)))
#print("desTime:{:f}".format(time_per_rev),"chaTime:{:f}".format(tickdiff),"duty:{:f}".format(duty),"mRPM:{:f}".format(motorrpm))
motor(duty)
except KeyboardInterrupt:
motor(0)
tonearm(False)
strobe(False)
power(False)
pi.stop()