Skip to main content

Адъ меломана 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()