Scratchbuilding a turntable


Quick update - I started to dust off the project and realized I had "misappropriated" the Arduino for another project as well :-/

Oh well, that processor board was overkill for this project anyway. I have been tinkering with Atmel's lower end processors, the ATtiny series, in particular the ATTiny13A. It only has 8 pins, 5 of which can be used for I/O. One more can be added with some extra challenges. My project currently needs the 2 Analog inputs from the sensor board, 1 analog input from the direction control potentiometer, and 2 digital output pins for the motor. That's 5 I/O pins, so the ATtiny13A should be a perfect match for this project! These processors cost about 70 cents a piece - compare that to the $30 cost of the Arduino :)

I have built a simple processor board with the ATtiny13A, and a somewhat more complex programmer board (needed to write the program into the ATtiny13A). I'm in the process of bringing up the turntable to the level of functionality where it was when I set the project aside for a bit almost 4 years ago :)

The challenge is, the ATtiny13A has only 1024 bytes of program memory and 64 bytes of RAM. I have some work to do to fit the necessary functionality into those constraints. Once I get there, I will post photos and updated code listings.

-ik
 
Finally had some time to tinker with the ATtiny13A. I was going to build a board with the ATtiny13A processor, Voltage regulator and motor driver / H-bridge chip. However I noticed that I can get ready made motor controller boards from Ebay for less than what I would have to pay for the components. So I ordered the Motor controller boards from Ebay and built a small processor board with just an ICSP programming header, plus headers for motor, potentiometer & sensor boards. The Motor controller board had a 5V regulator in it so I used that to power the processor board. It turned out very simple, will post pictures shortly.

The next step was a lot of fun, I converted the C program by hand to AVR assembly code. For some reason I really like writing assembly code - you get to know the processor on a much more intimate level :)

The AVRs have 32 registers, and I was able to put all the variables I needed into those registers, leaving the 64 byte RAM space completely untouched (except for the program stack). The key was to use only 8 bit unsigned variables. The only area where I was a bit dubious about this was the AD conversions. I am using only 8 bit of resolution from the converters but it seems to be working just fine. The total code size is currently 430 bytes! (not kilobytes or Megabytes:) It fits well into the available 1024 byte available space.

I am using the AVR assembler in Linux, avra. To compile (Assemble) the code, I use the simple command line

> avra Turntable.asm

No need for any make files since everything is contained within one source file Turntable.asm and there is one include file tn13def.inc. I will include the current listings of each shortly. This command line generates a Intel format HEX file called Turntable.hex. The HEX file is uploaded using the Arduino as a programmer (see <http://arduino.cc/en/Tutorial/ArduinoISP>). Instead of using the arduino for the actual target development, I just use the AVRDUDE programmer interface program directly from the command line to upload the HEX file.

> avrdude -C/usr/share/arduino/hardware/tools/avrdude.conf -v -v -v -v -pattiny13 -cstk500v1 -P/dev/ttyUSB0 -b19200 -Uflash:w:Turntable.hex:i

It took a while to figure all of this out. If I hadn't insisted on developing on Linux, I would have had a simpler experience just using the free AVR studio environment from Atmel, together with a commercial AVRISP programmer. But where's the fun and challenge in that?

The biggest problem of developing for ATtiny vs Arduino is the debugging. Inevitably you will have made mistakes and having no debugging interface to the ATtiny makes it much more challenging to find & remove the bugs!

Finally I seem to be getting comparable repeatability & accuracy with this new setup. I will be doing some fine turning with motor speeds and delay values and will post the final software after I'm satisfied.

Cheers,
ik
 
That's some nice work. I have an old scrathbuilt turntable on my layout, but it's no where as complex. It was scratchbuilt out of brass, and powered by a motor from an old player piano. No indexing on this antique. I think is was built about 35 years ago by the late Pete Ellis of Cascade, MT. Back then much of what we have today was a pipe dream. He donated it to me about 20 some years ago when he built an addition onto his model railroad building and had to do away with a town during his expansion and the turntable was in the way.

attachment.php


His entire layout was all code 70 hand laid track and when he found out that I was going to be going code 70, he thought he found a good home for it.
 
Oooo, code 70, that looks so nice!

Are those approach tracks 8 or 9 degrees apart? That's one area where I have had a nagging doubt - my current design uses 6 degree spacing as in the prototype. I had a feeling the approach tracks would get a bit too close together given the diameter of the turntable. I do not know why I didn't take the time to do a bit of simple math; the distance between track centers should be very easily calculated with the simple formula x = r * 2 * sin (a / 2), where r is the radius of the turntable pit, a is the angle between approach tracks. See graph below.

Geometry.jpg

In my turntable, r = 173mm, a = 6, therefore x = 18.1mm! The HO gauge is 16.9mm, add to that double the code 85 rail head width 2 * 1.016, I get 18.932mm distance between outer edges of the rail heads! Gah! I would need to file the outsides of the approach tracks to about half the width, insulate the gap between adjacent rails. The wheels would of course bridge the gap and short circuit the adjacent left & right rails, so I would need to isolate the sections right next to the pit! This is sounding more complex than I want.

I am considering going back to the drawing board for a redesign of the area where I have the approach track spaced 6 degrees apart. If I use 7.5 degree separation in that area, the approach tracks would have 22.6mm of space, plenty enough to avoid such problems. I can still use the same stall geometry if I move the roundhouse a bit closer to the pit and add a slight curve to the approach tracks, starting maybe 2 or 3 inches from the pit. This would affect 11 tracks, and would mean moving 22 positioning vanes :-( Gah, should have done my homework better during the design phase! What to do, what to do?

-ik

ps. could anyone recommend a deck girder bridge kit? I was going to scratchbuild the bridge but I've come to the point where I'm ready allow myself this little "cheat" :) The bridge needs to be about 13.5 scale inches long (98 prototype feet). See the image below for the style I'm looking for - the girders are placed right under the rails, with 8x8s or 8x12s on top.

AuburnTT.jpg
 
Last edited by a moderator:
In case someone is looking for the Assembly (ASM) code for the ATtiny13A, here it is. It is 428 bytes in length so using a bit more than 40 percent of the available code space. The ASM was hand assembled from the original C code. The original C code is left as comments at the end of each ASM line so you'll need a wide editor window to easily read the long lines. I also had to split the code into to parts due to maximum post length limitations.

So here is part 1: (all of this code is in the file turntable.asm)

Code:
; /*
;
;  Turntable control
;  =================
;
;  Physical inputs:
;  - Potentiometer for user to request turning direction
;    and speed
;  - Analog inputs from two phototransistors (used to determine
;    location of the positioning vanes)
;
;  Physical outputs:
;  - 2 wires to send PWM pulses to a DC motor
;  
;  System description:
;  - Turntable bridge has a positioning disk mounted rigidly
;    on the same axle. Positioning disk is under the layout.
;    Positioning vanes mounted on the positioning disk are
;    used to accurately stop the turntable at the correct
;    location for the approach tracks on the layout. DC motor
;    drive wheel bears on the positioning disk to turn the
;    turntable bridge.
;
;  Functional description:
;  - The control panel has a single potentiometer. The user
;    rotates it in the direction, (CW or CCW), that he wants
;    the bridge to rotate. This makes the bridge rotate at its
;    standard RUN speed in the chosen direction. When the user
;    returns the potentiometer to its center position, the
;    bridge decelerates to APPROACH speed and continues to
;    the next track. As it nears the track, The bridge decelerates
;    to HUNT speed and zeroes in to place.
;  - APPROACH mode ends when the leading edge phototransistor
;    is occluded
;  - HUNT mode ends when the light falling on the phototransistors
;    is balanced. System enters STOP mode
;  - Speed changes are never abrupt, the control software gently
;    accelerates/decelerates transition between RUN, APPROACH
;    and HUNT speeds
;  - A really fast turn speed called LUDICROUS speed is used
;    when the potentiometer is turned all the way to the
;    extreme CW or CCW position
;
; */
;

.include "tn13def.inc"

; Global variables (allocated to registers)
.DEF Speed               =R18
.DEF TargetSpeed         =R19
.DEF Direction           =R20
.DEF TargetDirection     =R21
.DEF Mode                 =R22
.DEF PotValue             =R23
.DEF LeadingPT           =R24
.DEF TrailingPT          =R25

; ADC channel MUX values
.equ ADC0                   =0
.equ ADC1                   =1
.equ ADC2                   =2
.equ ADC3                   =3

; I/O pin allocations
.equ MOTOR1                 =PB0
.equ MOTOR2                 =PB1
.equ POTPINIO               =PB2     ; ADC1
.equ PTCCWIO                =PB3     ; ADC3
.equ PTCWIO                 =PB4     ; ADC2
.equ POTPIN                 =ADC1
.equ PTCCW                  =ADC3
.equ PTCW                   =ADC2

; mode definitions
.equ STOP                   =0       ; Bridge is stopped
.equ RUN                    =1       ; Bridge is turning at normal speed
.equ LUDICROUS              =2       ; Bridge is turning at ludicrous speed
.equ APPROACH               =3       ; slow down and start looking for positioning vane
.equ HUNT                   =4       ; Positioning vane detected, move slowly until light input balanced

; turning directions
.equ CW                     =0       ; Clockwise direction
.equ CCW                    =1       ; Clockwise direction

; Below are the potentiometer treshold values used to enter the different operating modes
.equ CW_TRESHOLD_LUDICROUS  =0xF0
.equ CW_TRESHOLD            =0x50
.equ APPROACH_TRESHOLD_HIGH =0x40
.equ APPROACH_TRESHOLD_LOW  =0x12
.equ CCW_TRESHOLD           =0x0D
.equ CCW_TRESHOLD_LUDICROUS =0x03

; Below are the PWM output values for setting different speeds
.equ RUN_SPEED              =56
.equ LUDICROUS_SPEED        =125
.equ APPROACH_SPEED         =45
.equ HUNT_SPEED             =40
.equ MINIMUM_SPEED          =35              ; motor stalls at this voltage, no point in spending time decelerating from or accelerating to this value
.equ STD_ACCEL              =1               ; Standard Acceleration (change in speed per 50ms tick when accelerating or decelerating)

; Below are the treshold values from the phototransistors used to determine mode changes
.equ HUNT_TRESHOLD          =150
.equ BALANCE_TRESHOLD       =23
.equ FINE_BALANCE_LIMIT     =3


; Interrupt Vectors
; =================
; Vector Program   Source       Interrupt Definition
; Number Address
; 1      0x0000    RESET        External Pin, Power-on Reset, Brown-out Reset, Watchdog Reset
; 2      0x0001    INT0         External Interrupt Request 0
; 3      0x0002    PCINT0       Pin Change Interrupt Request 0
; 4      0x0003    TIM0_OVF     Timer/Counter Overflow
; 5      0x0004    EE_RDY       EEPROM Ready
; 6      0x0005    ANA_COMP     Analog Comparator
; 7      0x0006    TIM0_COMPA   Timer/Counter Compare Match A
; 8      0x0007    TIM0_COMPB   Timer/Counter Compare Match B
; 9      0x0008    WDT Watchdog Time-out
; 10     0x0009    ADC          ADC Conversion Complete
.org 0x0000
    rjmp init          ; The reset-vector on Address 0000
    reti               ; IRQ0 Handler
    reti               ; Pin change interrupt (PCINT0) Handler
    reti               ; Timer0 Overflow Handler
    reti               ; EEPROM Ready Handler
    reti               ; Analog Comparator Handler
    reti               ; Timer0 CompareA Handler
    reti               ; Timer0 CompareB Handler
    reti               ; Watchdog Interrupt Handler
    reti               ; ADC Conversion Handler

; *********************************  
; System initialization after RESET
; *********************************
init:

; Init stack
    ldi r16,LOW(RAMEND) ; RAMEND to SPL
    out SPL,r16

; Set the motor 1 & 2 pins low
    cbi PORTB, PB0
    cbi PORTB, PB1

; Set up the data direction register
; Inputs:   PB2 (ADC1) direction pot
;           PB3 (ADC3) IR sensor 1
;           PB4 (ADC2) IR sensor 2
; Outputs:  PB0 (OC0A) pwm out 1 to motor
;           PB1 (OC0B) pwm out 2 to motor
    ldi r16,(1<<DDB0)|(1<<DDB1)     ; PB0 & PB1 are outputs, rest are inputs
    out DDRB,r16

    ; configure & enable the ADC
    ldi r16,(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0) ; ADC Enable, ADC prescaler 110 = 1/16
    out ADCSRA,r16

    ; initialize global variables
    ldi Mode,STOP
    ldi Direction,CW
    ldi TargetDirection,CCW
    ldi Speed,0
    ldi TargetSpeed,0

    ; Start ADC1
    ldi r16, (1<<ADLAR)|(1<<MUX0)  ; select ADC1 (MUX[1:0] = 01), Vcc reference & ADLAR (only need 8 bit resolution)
    out ADMUX,r16
    sbi ADCSRA,ADSC                   ; ADC start conversion

    ; Jump to start of main program loop
    rjmp Main

; *************************
;    Drive:
; *************************
;
; Program the "8-bit Timer/Counter0 with PWM" to
; generate phase-correct PWM on either PB0 (OC0A) or
; PB1 (OC0B). The duty cycle controls how fast the
; motor turns.
;
; Inputs: Direction
;         Speed  
;
Drive:
IFDRIVE1:
    tst Speed                 ; if (Speed==0)
    brne ELSEIFDRIVE1         ; {
                              ;     // stop motor
    ldi r16,0                 ;     // Set to normal mode
    out TCCR0A,r16
    cbi PORTB,PORTB0          ;     // Set PORTB0 output low
    cbi PORTB,PORTB1          ;     // Set PORTB1 output low
    rjmp ENDIFDRIVE1          ; }
ELSEIFDRIVE1:                            
    cpi Direction,CCW         ; else if (Direction==CCW)
    brne ELSEDRIVE1           ; {
    cbi PORTB,PORTB0          ;     // Set PORTB0 output low
    ldi r16,0b00100001        ;     // Set to PWM mode 1 (Phase correct PWM), OC0B
    out TCCR0A,r16
    ldi r16,0b00000010        ;     // Set prescaler to 1/8
    out TCCR0B,r16      
    out OCR0B,Speed           ;     // Program Speed as the duty cycle
    rjmp ENDIFDRIVE1          ; }
ELSEDRIVE1:                   ; else
                              ; {
    cbi PORTB,PORTB1          ;     // Set PORTB1 output low
    ldi r16,0b10000001        ;     // Set to PWM mode 1 (Phase correct PWM), OC0A
    out TCCR0A,r16
    ldi r16,0b00000010        ;     // Set prescaler to 1/8
    out TCCR0B,r16      
    out OCR0A,Speed           ;     // Program Speed as the duty cycle
ENDIFDRIVE1:                   ; }
    ret

; **************************************************************
;                         analogRead
; **************************************************************
;
; Input: r16, selects AD channel 0,1,2 or 3 
; Output r16 returns the result of the AD conversion
;
analogRead:   
    andi r16,3                   ; mask away any extra bits from AD channel selection just in case
    ori r16,(1<<ADLAR)|(0<<REFS0); Left adjust (only need 8 bit resolution), Vcc used as analog reference 
    out ADMUX,r16
    sbi ADCSRA,ADSC                 ; start ADC conversion 

waitadc:                         ; Wait until conversion done
    sbic ADCSRA,ADSC             ; test ADSC bit
    rjmp waitadc

; read the value
    in r16,ADCH                     ; reading only the high byte since we requested left adjust (ADLAR)
    ret    

; *************************
;    delay(time [r17])
; *************************  
; This routine delays the program by counting from 1 to 256 * r17.
; uses the r16 and r17 registers as a 16 bit counter. The r16 counts to 256 
; as many times as specified in r17
delay:
    ldi r16, 0
dec_loop:
    nop              ; Note: calibrate number of nop's to make one count of r17 == 1ms
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    dec r16
    brne dec_loop
    dec r17
    brne dec_loop
    ret

; continued in part 2
 
Last edited:
Code:
; part 2
; **************************
;          Main
; **************************
    
Main:
Loop:
    ldi r16,POTPIN                  ; // read the user input (CW/CCW) potentiometer
    rcall analogRead                ; PotValue = analogRead(PotPin);           
    mov PotValue,r16
 
IF1:
    cpi PotValue,CW_TRESHOLD        ; // if CW requested, then go to RUN mode in the CW direction
    brlo ELSEIF1                    ; if (PotValue >= CW_TRESHOLD) {
    ldi TargetDirection,CW          ;     TargetDirection = CW;
    ldi Mode,RUN                    ;     Mode = RUN;
IF2:                                ;     if (PotValue >= CW_TRESHOLD_LUDICROUS)
    cpi PotValue,CW_TRESHOLD_LUDICROUS
    brlo ENDIF2
    ldi Mode,LUDICROUS              ;         Mode = LUDICROUS;
ENDIF2:   
    rjmp ENDIF1                     ; }
ELSEIF1:                            ; // else if CCW, then go to RUN mode in the CCW direction
    cpi PotValue,CCW_TRESHOLD       ; else if (PotValue < CCW_TRESHOLD)
    brsh ENDIF1                     ; {
    ldi TargetDirection,CCW         ;     TargetDirection = CCW;
    ldi Mode,RUN                    ;     Mode = RUN;
IF3:                                ;     if (PotValue < CCW_TRESHOLD_LUDICROUS)
    cpi PotValue,CCW_TRESHOLD_LUDICROUS
    brsh ENDIF3
    ldi Mode,LUDICROUS              ;         Mode = LUDICROUS;
ENDIF3:
ENDIF1:                             ; }

                                    ; // do all the mode-specific stuff
                                    ;
                                    ; switch (Mode) {
    cpi Mode,STOP                   ;   case STOP:
    brne RUNTEST
    ldi TargetSpeed,0               ;     TargetSpeed = 0;
    rjmp ENDSWITCH                  ;     break;

RUNTEST:                              ;     
    cpi Mode,RUN                    ;   case RUN:
    brne LUDTEST
    ldi TargetSpeed,RUN_SPEED       ;     TargetSpeed = RUN_SPEED;
IF4:
    cpi r16,APPROACH_TRESHOLD_LOW   ;     // if potentiometer returned to middle, then go to APPROACH mode
    brlo ENDIF4                     ;     if (PotValue >= APPROACH_TRESHOLD_LOW && PotValue < APPROACH_TRESHOLD_HIGH)
    cpi r16,APPROACH_TRESHOLD_HIGH
    brsh ENDIF4                     ;     {
    ldi Mode,APPROACH               ;       Mode = APPROACH;
    ldi TargetSpeed,APPROACH_SPEED  ;       TargetSpeed = APPROACH_SPEED;
ENDIF4:                             ;     }
    rjmp ENDSWITCH                  ;     break;

LUDTEST:
    cpi Mode,LUDICROUS              ;   case LUDICROUS:
    brne APPRTEST
    ldi TargetSpeed,LUDICROUS_SPEED ;     TargetSpeed = LUDICROUS_SPEED;
IF5:
    cpi r16,APPROACH_TRESHOLD_LOW   ;     // if potentiometer returned to middle, then go to APPROACH mode
    brlo ENDIF5                        ;     if (PotValue >= APPROACH_TRESHOLD_LOW && PotValue < APPROACH_TRESHOLD_HIGH)
    cpi r16,APPROACH_TRESHOLD_HIGH  ;     {
    brsh ENDIF5
    ldi Mode,APPROACH               ;       Mode = APPROACH;
    ldi TargetSpeed,APPROACH_SPEED  ;       TargetSpeed = APPROACH_SPEED;
ENDIF5:                             ;     }
    rjmp ENDSWITCH                  ;     break;

APPRTEST:                           ;     
    cpi Mode,APPROACH               ; case APPROACH:
    brne HUNTTEST
IF6:
    cp Direction,TargetDirection    ; // wait until approach speed and direction have stabilized
    brne ENDIF6                     ;     if (Direction == TargetDirection && Speed == TargetSpeed)
    cp Speed,TargetSpeed            ;     {
    brne ENDIF6
IF7:                                ;       // read the leading edge phototransistor
    cpi Direction,CW                ;       if (Direction == CW)
    brne ELSE7                      ;       {
    ldi r16,PTCW                    ;         LeadingPT = analogRead(PTCW);
    rjmp ENDIF7                     ;       }
ELSE7:                              ;       else
                                    ;       {
    ldi r16,PTCCW                   ;         LeadingPT = analogRead(PTCCW);
ENDIF7:                             ;       }
    rcall analogRead
    mov LeadingPT,r16   

IF8:                                ;       // if occluded, then go to HUNT mode
    cpi LeadingPT,HUNT_TRESHOLD    ;       if (LeadingPT >= HUNT_TRESHOLD)
    brlo ENDIF8                     ;       {
    ldi Mode,HUNT                   ;         Mode = HUNT;
    ldi TargetSpeed,HUNT_SPEED      ;         TargetSpeed = HUNT_SPEED;
ENDIF8:                             ;       }
ENDIF6:                             ;     }
    rjmp ENDSWITCH                  ;     break;
    
HUNTTEST:                           ;   case HUNT:
    cpi Mode,HUNT
    brne ENDSWITCH
    ldi r16,PTCW                    ;     // read phototransistors
    cpi Direction,CW                ;     LeadingPT = analogRead(Direction==CW?PTCW:PTCCW);
    breq A1
    ldi r16,PTCCW
A1: rcall analogRead
    mov LeadingPT,r16
    ldi r16,PTCCW                   ;     TrailingPT = analogRead(Direction==CW?PTCCW:PTCW);
    cpi Direction,CW
    breq A2
    ldi r16,PTCW
A2: rcall analogRead                ;     
    mov TrailingPT,r16
IF9:                                ;     // if values close enough, then go to STOP mode
    ldi r16,BALANCE_TRESHOLD        ;     if (LeadingPT<TrailingPT+BALANCE_TRESHOLD)
    add TrailingPT,r16
    cp LeadingPT,TrailingPT
    brsh ENDIF9                     ;     {
    ldi Mode,STOP                   ;       Mode = STOP;
    ldi Speed,0                     ;       Speed = 0;
    ldi TargetSpeed,0               ;       TargetSpeed = 0;
DO1:                                ;       // Fine hunt loop == might not be necessary in the ASM version
                                    ;       do
                                    ;       {
    ldi r16,PTCW                    ;         LeadingPT = analogRead(Direction==CW?PTCW:PTCCW);
    cpi Direction,CW
    breq A3
    ldi r16,PTCCW
A3: rcall analogRead
    mov LeadingPT,r16
    ldi r16,PTCCW                     ;         TrailingPT = analogRead(Direction==CW?PTCCW:PTCW);
    cpi Direction,CW
    breq A4
    ldi r16,PTCW
A4: rcall analogRead
    mov TrailingPT,r16
    ldi r16,FINE_BALANCE_LIMIT
    add TrailingPT,r16              ;       } while(LeadingPT>=TrailingPT+FINE_BALANCE_LIMIT)
    cp LeadingPT,TrailingPT
    brsh DO1
    ldi r17,1                        ;       // deliberately overshoot for 0.20ms
    ldi r16,51
;    rcall dec_loop
    rcall Drive                     ;       // perform immediate motor stop       
                                    ;       analogWrite(Motor1, 0);           
                                    ;       analogWrite(Motor2, 0);           
ENDIF9:                             ;     }
     rjmp ENDSWITCH                 ;     break;
                                    ; }
ENDSWITCH:

    ; // Handle acceleration/deceleration:
    ; // =================================
                                    ;
IF10:                               ; // if target speed/direction not yet attained, then adjust speed
    cp Direction,TargetDirection    ; if (Direction != TargetDirection)
    breq ELSEIF10                   ; {
IF11:                               ;   // Decelerate to 0 speed
    cpi Speed,MINIMUM_SPEED         ;   if (Speed > MINIMUM_SPEED)
    brlo ELSE11                     ;   {
    subi Speed,STD_ACCEL            ;     Speed -= STD_ACCEL;
    rjmp ENDIF11                    ;   }
ELSE11:                             ;   else
                                    ;   {
                                    ;     // 0 speed attained, time to start accelerating in the other direction
    ldi Speed,0                     ;     Speed = 0;
    mov Direction,TargetDirection   ;     Direction = TargetDirection;
ENDIF11:                            ;   }
    rjmp ENDIF10                    ; }
ELSEIF10:                           ; else if (Speed < TargetSpeed)
    cp Speed,TargetSpeed            ; {
    brsh ELSEIF10b
IF12:                               ;   // Accelerate
    cpi Speed,MINIMUM_SPEED         ;   if (Speed < MINIMUM_SPEED)
    brsh ELSEIF12
    ldi Speed,MINIMUM_SPEED         ;     Speed = MINIMUM_SPEED;       // jump straight to MINIMUM_SPEED
    rjmp ENDIF12
ELSEIF12:                           ;   else
    ldi r16,STD_ACCEL               ;     Speed += STD_ACCEL;
    add Speed,r16
ENDIF12:   
    rjmp ENDIF10                    ; }
ELSEIF10b:                          ; else if (Speed >= TargetSpeed+1 && Speed >= MINIMUM_SPEED+1)
    cpi Speed,MINIMUM_SPEED+1       ; {
    brlo ENDIF10
    mov r16,TargetSpeed
    inc r16
    cp Speed,r16
    brlo ENDIF10   
                                     ;   // Decelerate
    subi Speed,STD_ACCEL            ;   Speed -= STD_ACCEL;
ENDIF10:                            ; }

    rcall Drive                     ; // Update motor speed
                                    ; if (Direction == CW)
                                    ; {
                                    ;   analogWrite(Motor1, Speed);           
                                    ;   analogWrite(Motor2, 0);           
                                    ; }
                                    ; else
                                    ; {
                                    ;   analogWrite(Motor1, 0);           
                                    ;   analogWrite(Motor2, Speed);           
                                    ; }
 IF13:                              ; // Delay 50 ms (Control program runs 20 times per second), except only 10ms in HUNT mode
    cpi Mode,HUNT                   ; if (Mode == HUNT)
    brne ELSE13                     ; {
    ldi r17,5                       ;   delay (10);
    rjmp ENDIF13                    ; }
ELSE13:                             ; else
                                    ; {
    ldi r17,10                      ;   delay(25);
ENDIF13:                            ; }
    rcall delay                     

    rjmp Loop                       ; }
 
The good thing about having a long hiatus in a project is that technology can catch up to what you need :) That is indeed what has happened in the 6 years this project lay waiting for a better day: 3D printing came out! I have designed the turntable bridge in OpenScad and printed out my first revision on my Creality CR10 3d printer using PLA filament. It came about 1mm too long, but I should be able to sand it into place!

OpenScad is a really neat 3D design program in that it is akin to a programming language. You write a script that defines your object, and you have all sorts of fantastic programming features available to you (as the designer). You can use variables, loops, conditional statements, procedures etc. The good thing is I can write the script in such a way that it can be easily modified to generate a turntable bridge for different diameter pit, using different size timbers and so on. I.e. I can parameterisize the script. Here is a solid model of the result:
my_turntable_bridge03.jpgmy_turntable_bridge05.jpg

My feeling is that OpenScad can be especially good for generating plate girder beams. I shall endeavor to write a script that I can use after this project to generate whatever size and shape plate girder beams I need for a future model bridge, another turntable etc. Hopefully others can also make use of it.

In the next few weeks I will be working on the girder beams and support structures that will allow me to mount it on the rest of the turntable mechanism, attaching the rails, and of course painting the model. I use translucent PLA here and it really doesn't look too impressive until after it has been painted. I will keep this thread updated.

ik
 
Last edited:
Hehe, I would never drink decaf!

But now I have a tapered plate girder beam!
girder.jpg
I had to massively exaggerate the size of the rivets in order for them to come through at all in the slicer (I'm using Cura) in preparation for the 3d printing process. We'll see how well it actually comes out - it is printing out right now.

ik
 
Rather than messing with those sensors, have you considered just 3d printing a geneva drive? That should allow you to get precise centering at each index point, without having to have the motor stop at precisely the right spot.

Something like: https://www.instructables.com/id/3D-Printed-Geneva-Drive/ when combined with an appropriate gearing should allow you to precisely line up the tracks with minimal fuss. It's how the turntable I bought lines them up.
 
I think employing 3d printing for this purpose is a great idea! However, the Geneva drive is fairly limiting in the sense that you have to divide the circle into a certain number of stops, say every N degrees. It would stop at every N degree spot, so in operation it would not look very prototypical. With the positioning vanes I have full freedom to position the approach tracks just like a real world designer had. For example the approach tracks to the round house could be at 8.5 degree intervals and the garden tracks at 12 degree intervals. Or whatever positions work for the prototype you're modeling.

I am now considering scrapping the old soldered-on metal vanes and printing out new positioning vanes. The vanes would be printed attached to a rigid disk body, so their registration in relation to each other would have mathematical accuracy, but I would still retain the full freedom of placing the vanes freely on the circle. No post installation measurements and fine tuning should be necessary! And the head vs tail end of the bridge would be able to land accurately in the right place provided the bridge was mounted right.

Also if I ever wanted to move approach tracks or add new ones, I could simply do it in my OpenScad design model - adding new vanes in there or moving old ones, then print out the new positioning disk system complete with the vanes. Mounting holes would remain in the same place, allowing me to accurately position the new disk in place after removing the old one.

ik
 
I think employing 3d printing for this purpose is a great idea! However, the Geneva drive is fairly limiting in the sense that you have to divide the circle into a certain number of stops, say every N degrees. It would stop at every N degree spot, so in operation it would not look very prototypical. With the positioning vanes I have full freedom to position the approach tracks just like a real world designer had. For example the approach tracks to the round house could be at 8.5 degree intervals and the garden tracks at 12 degree intervals. Or whatever positions work for the prototype you're modeling.
ik
Yes, this is true, If for some reason you don't want them all evenly spaced, this approach likely wouldn't work. At least not without having unnatural pauses in various places. If you want it to be unevenly spaced or you want to skip over positions, it might make more sense to have a fly wheel and stops that raise and lower to lock the mechanism in the place you want, with the sensor just being used to cut power to the motor and place a locking block on the opposite side of the track.

And I'm totally with you on OpenSCAD, for things that can be modeled using geometric shapes, it seems to work better than other options I've tried.
 
Wow, what a long time frame you have been building your turntable (2010-2020) !......but you are doing a marvelous job !

I chose a much more more simplistic manner when I built my first one,..
http://www.modelrailroadforums.com/forum/index.php?threads/ho-turn-table-from-scratch.27998/#post-378173

partial quote...
Turntable Project
I ended up building my own turntable for my layout. Basically I utilized Atlas bridge components for the rotating center span. The track and walkway portions came from the bases of Atlas (Roco) curved cord bridges, and these were rigidly mounted onto upside down plate girder bridge sections. Made a wonderfull rigid turntable bridge with wooden walks and handrails on the either side. .....

Some other photos here,..
http://www.modelrailroadforums.com/forum/index.php?threads/ho-turn-table-from-scratch.27998/#post-378175
 
I'm in a club where we've built two turntables over the years (first one was in 1980), but we treated it as heavy engineering, with the bridge, bearings and frame designed to be very precise. It's interesting that in the design by ikallio1, the bridge rides on the pit rail, and gets its rotation but not its vertical alignment from the shaft coming up from below. I think that's actually a better arrangement, and certainly easier to build.

Unlike Wayne Roderick and ikallio1 but similar to the Tapiola Parish Model Railway Club, my control system only uses a single photocell for alignment (instead of two). And I attached the blades that break the light beam with a single screw each deliberately so they could be adjusted easily. See it in action (this video needs some editing):

 
John, I like that continuous turn potentiometer for rough positioning...I assume you can then assign numbers to each of the approach tracks and command the table to go to a specific one? I have been wondering how I could accomplish that without resorting to complicated optical encoder systems. Are such potentiometers available to buy or did you modify a regular one for continuous rotation?

I see that in operation your positioning system is very accurate! How stable is the setup, do you ever need to come back and readjust the positioning blades?
 
Wow, what a long time frame you have been building your turntable (2010-2020) !......but you are doing a marvelous job !
Thank you :) Life & other interests get in the way of model railroading - hence the lengthy timeframe. Even model RR hobby itself can cause delays in projects - so many interesting things have happened in the meanwhile. For instance, I have been reading up on DCC++ while my 3d printer was working furiously. My layout has a NCE PowerCab DCC and for a while it has been clear I need more power than the Powercab can supply. DCC++ would seem a good avenue to provide more power without having to shell out big $$ for a booster and at the same time add some amazing features!
 
Just a quick question from as far out in left field as one can get: why not drive the turntable with a stepper?
I did think about it, but a stepper motor can lose steps and is therefore not really reliable enough to provide accurate alignment to the approach tracks by itself. I would need to provide some other alignment/indexing mechanism anyway. And driving steppers requires a dual H-bridge. Also (and this is a matter of opinion, others may disagree) steppers have an annoying whine which I would prefer not to hear when operating the turntable - I want to hear railroad noises :) The DC motor I'm using is very quiet.
-ik
 
Another point about steppers is that you have to have a known "zero" point to start from. Then to maintain the accuracy all the way out to the bridge, you have to use a geared or timing-belt drive, not a friction wheel like ikallio1 and I both used. That's much easier to set up, but wouldn't be repeatable enough for a stepper. Then if you do have gearing, you have to worry about backlash, where there's effectively some uncertainty about where the rail ends are going to be (a timing belt would be better). Yes, you could do an extra jog at the end of travel, so that the approach would always be from the same direction, but I'm not sure that it would work well enough. A DC motor with local sensors seems like a better plan. Our setup hasn't needed adjustment in several years.

Continuous-turn pots like the one I used are available on eBay. But I got mine from the stockroom at a robotics lab, where it looked as if someone had bought a few for some project and then left them in need of love. Setting up the pot was kind of a pain, and would be very hard without access to machine tools (which I had back then, but not now). You might consider some kind of bar-code device, where a reflective sensor would read a few black marks on a label as each track was approached; you'd only need a few bits of data. If I had to do it again, I think that's what I'd try first.

And oh yes, each track has a number. Control is via a keypad which has the numerals plus the letters A-D down the side, and we set it up so that "End B" is the end of the bridge with the control cabin, and "End A" is the other end. To make a move, you enter the track number (one or two digits) and then A or B. It's pretty intuitive.
 
Last edited:



Back
Top