Servo Clock Calibration

The first servo clock was built using a name-brand servo known for its linearity.  The second iteration was built using a low-cost mini-servo.  Its linearity was not as good; uncorrected, the time readout was not acceptable.

In the ideal case, locating the delay value at the starting and ending points would be enough for the clock to read properly across the range.  The angle of rotation would vary linearly across the scale.  If the servo is not extremely linear, the pointer will not always point to correct location in the scale.  As an example, if the actual time was 6:05, a non-linear servo might point at 5:55.

The low cost servo has some areas that are highly non-linear.  At one point in the scale, the difference between quarter-hour marks was a 20 ms change in pulse width.  At a different point in the scale, the change was over 40 ms.

 

Improved Method

It's most important that the servo clock pointer line up correctly with the scale marks at quarter-hour intervals.  The exact position between the marks isn't as important.  This improved method calibrates each quarter-hour mark, and linearly interpolates between the marks.  A look-up table approach is used to set the position at each of the 720 minutes across the span.

The hardware used for this clock is the TAP-28 board with an 18F242 microcontroller.  The hardware is not critical and other implementations can be used.  This code was developed using Swordfish.

A potentiometer is used to set the servo position to read the calibration values.  I used a 10-turn pot but a regular single turn pot or a multi-turn trim pot could be used.  The value of the pot isn't critical - anything between 1k and 100k ohms will work fine.  Make sure the pot is linear taper. 

 

Steps

1.  Mount the servo in position behind the clock scale but do not install the pointer yet.

2.  Connect the pot to PORTA.0.  The wiper terminal goes to the port pin.  The ends of the pot are connected to +5V and ground.

3.  Connect the servo to PORTC.2.

4.  Load the calibration code to the PIC.

 

{
****************************************************************
* Name : Servo Test and Calibration Program *
* Author : Jon Chandler *
* Notice : Copyright (c) 2010 Jon Chandler *
* : All Rights Reserved *
* Date : 1/29/2010 *
* Version : 1.0 *
* Notes : Program to test and calibrate servos. Servo *
* position is controlled by a potentiometer *
* connected to ADC(1). Position is sent via UART *
* to the PC to read the delay at each position. *
* This program uses the TAP-28 Board with a PIC *
* 18F242. *
****************************************************************
}

 
'Servo output = PORTC.2
  'UART output = UART connector
  'Pot connection = PORTA.1
  'UART Baudrate = 9600
 

Device = 18F242
Clock = 20
Include "utils.bas"
 
Include "USART.bas"
Include "convert.bas"
Include "ADC.bas"
 
 
Dim Posit As Word
Dim ADVal As Word
 
 
Function ADInAsVolt() As Word
 
result = (ADC.Read(1) + 1) * 500 / 1024
 
End Function
 
ADCON1 = %10000000
ADCON0.7 = 1
ADCON0.6 = 1
 
ADC.RightJustify = True
ADC.SetConvTime(FOSC_16)
 
USART.SetBaudrate(br9600)
 
While 1=1
 
ADVal = ADInAsVolt
Posit = 500 + 4* ADVal
USART.Write("ADC Value = ", DecToStr(ADVal)," Posit: ",DecToStr(Posit), 13,10)
 
High(PORTC.2) 'create servo pulse, repeatedly send approximately
  DelayUS(Posit) 'every 200 milliseconds
  Low (PORTC.2)

DelayUS(20000-Posit)
 
Wend
 

5.  Connect a PICkit2 to the UART connector and launch the PICkit UART tool on a PC.  Alternatively, use a UART (5V TTL) - RS232 converter or a UART - USB converter and terminal software on the PC.  Set the baud rate to 9600/N/1.

6.  Power the board using the PICkit or connect a 5V power supply.

7.  Adjust the pot and verify that the servo turns.  Adjust the pot until the position readout on the PC is 1500.  This is the middle of the servo's range.

UART_screen

8.  Install the pointer on the servo's splined shaft, as close as possible to midscale on the clock face.

9.  Adjust the pot to position the pointer over the left hand 12.  Read the position on the PC and record the position in the spreadsheet.

cal_clock_face

10  Adjust the pot to position the pointer over the next mark on the clock face and record the position.  Continue the process for each mark on the clock face.

 

clock_cal_spreadsheet

This completes the calibration measurements.

 

Using The Results

Now, open the clock program in Swordfish.  Select the spreadsheet region from D2 - R13.  Press <CTRL> C to copy this region.  Paste this tabular data into the constant data in Swordfish, replacing the existing data.

{
***********************************************************************
* Name : Servo_Clock 2.bas *
* Author : Jon Chandler *
* Notice : Copyright (c) 2010 Jon Chandler *
* : All Rights Reserved *
* Date : 3/11/2010 *
* Version : 1.0 *
* Notes : Servo-driven geeky clock with Real Time Clock *
* : fucntions based on Warren Schroeder's methods *
* : using PR2 Free Running Timer *
* http://www.sfcompiler.co.uk/wiki/pmwiki.php?n=SwordfishUser.SoftRTC *
* *
* Presented to: http://digital-diy.com/ by Jon Chandler *
* *
* Timekeeping depends on a 20 MHz crystal *
* *
* This version uses a small servo from Hobby Partz and the TAP-28 *
* board with a PIC18F242 installed. *
* A constant array is used as a lookup table to overcome non-linearity*
* in the servo. Calibrated position values used for each minute. *
* See digital-diy.com for more information. *
***********************************************************************
}

 
Device = 18F242
 
Clock = 20
Include "utils.bas"
 
Include "USART.bas"
Include "convert.bas"
 
Const TimeValues(721) As Word = (
//delete the values below and paste in the calculated resuls from the spreadsheet.
 
2240,2237,2235,2233,2231,2229,2227,2225,2222,2220,2218,2216,2214,2212,2210,
2208,2206,2204,2203,2201,2200,2198,2196,2195,2193,2192,2190,2188,2187,2185,
2184,2181,2179,2176,2174,2172,2169,2167,2164,2162,2160,2157,2155,2152,2150,
2148,2146,2144,2142,2140,2138,2136,2134,2133,2131,2129,2127,2125,2123,2121,
2120,2117,2115,2113,2111,2109,2107,2105,2102,2100,2098,2096,2094,2092,2090,
2088,2086,2085,2084,2082,2081,2080,2078,2077,2076,2074,2073,2072,2070,2069,
2068,2066,2064,2062,2060,2058,2056,2054,2053,2051,2049,2047,2045,2043,2041,
2040,2037,2034,2032,2029,2026,2024,2021,2018,2016,2013,2010,2008,2005,2002,
2000,1997,1994,1992,1989,1986,1984,1981,1978,1976,1973,1970,1968,1965,1962,
1960,1959,1958,1957,1956,1956,1955,1954,1953,1952,1952,1951,1950,1949,1948,
1948,1946,1944,1942,1940,1938,1936,1934,1933,1931,1929,1927,1925,1923,1921,
1920,1917,1915,1912,1910,1908,1905,1903,1900,1898,1896,1893,1891,1888,1886,
1884,1882,1880,1878,1876,1874,1872,1870,1869,1867,1865,1863,1861,1859,1857,
1856,1853,1851,1848,1846,1844,1841,1839,1836,1834,1832,1829,1827,1824,1822,
1820,1818,1817,1816,1814,1813,1812,1810,1809,1808,1806,1805,1804,1802,1801,
1800,1797,1795,1792,1790,1788,1785,1783,1780,1778,1776,1773,1771,1768,1766,
1764,1761,1759,1757,1755,1753,1751,1749,1746,1744,1742,1740,1738,1736,1734,
1732,1730,1728,1726,1724,1722,1720,1718,1717,1715,1713,1711,1709,1707,1705,
1704,1702,1700,1698,1696,1694,1692,1690,1689,1687,1685,1683,1681,1679,1677,
1676,1673,1671,1668,1666,1664,1661,1659,1656,1654,1652,1649,1647,1644,1642,
1640,1637,1635,1632,1630,1628,1625,1623,1620,1618,1616,1613,1611,1608,1606,
1604,1602,1600,1598,1596,1594,1592,1590,1589,1587,1585,1583,1581,1579,1577,
1576,1573,1571,1569,1567,1565,1563,1561,1558,1556,1554,1552,1550,1548,1546,
1544,1542,1540,1538,1536,1534,1532,1530,1529,1527,1525,1523,1521,1519,1517,
1516,1513,1511,1508,1506,1504,1501,1499,1496,1494,1492,1489,1487,1484,1482,
1480,1478,1476,1474,1472,1470,1468,1466,1465,1463,1461,1459,1457,1455,1453,
1452,1449,1447,1445,1443,1441,1439,1437,1434,1432,1430,1428,1426,1424,1422,
1420,1418,1416,1414,1412,1410,1408,1406,1405,1403,1401,1399,1397,1395,1393,
1392,1390,1388,1386,1384,1382,1380,1378,1377,1375,1373,1371,1369,1367,1365,
1364,1361,1359,1357,1355,1353,1351,1349,1346,1344,1342,1340,1338,1336,1334,
1332,1329,1327,1325,1323,1321,1319,1317,1314,1312,1310,1308,1306,1304,1302,
1300,1297,1295,1293,1291,1289,1287,1285,1282,1280,1278,1276,1274,1272,1270,
1268,1266,1265,1264,1262,1261,1260,1258,1257,1256,1254,1253,1252,1250,1249,
1248,1246,1244,1242,1240,1238,1236,1234,1233,1231,1229,1227,1225,1223,1221,
1220,1218,1216,1214,1212,1210,1208,1206,1205,1203,1201,1199,1197,1195,1193,
1192,1189,1187,1184,1182,1180,1177,1175,1172,1170,1168,1165,1163,1160,1158,
1156,1154,1152,1150,1148,1146,1144,1142,1141,1139,1137,1135,1133,1131,1129,
1128,1126,1125,1124,1122,1121,1120,1118,1117,1116,1114,1113,1112,1110,1109,
1108,1105,1102,1100,1097,1094,1092,1089,1086,1084,1081,1078,1076,1073,1070,
1068,1066,1064,1062,1060,1058,1056,1054,1053,1051,1049,1047,1045,1043,1041,
1040,1038,1036,1035,1033,1032,1030,1028,1027,1025,1024,1022,1020,1019,1017,
1016,1014,1013,1012,1011,1010,1009,1008,1007,1006,1005,1004,1003,1002,1001,
1000,997,994,992,989,986,984,981,978,976,973,970,968,965,962,
960,958,956,954,952,950,948,946,945,943,941,939,937,935,933,
932,930,928,926,924,922,920,918,917,915,913,911,909,907,905,
904,902,900,899,897,896,894,892,891,889,888,886,884,883,881,
880,878,877,876,874,873,872,870,869,868,866,865,864,862,861,
860,858,856,854,852,850,848,846,845,843,841,839,837,835,833,
832
 
 
//Paste values from spreadsheet above.
)
 
 
 
 
Dim Set_Fast As PORTB.5 'S1 = Fast Set
  Dim Set_Slow As PORTB.4 'S2 = Slow Set
 

Dim Servo As PORTC.2 '(bottom 3 pin connector)
 

 
{
For One Second Update:
 
8MHz Fosc = 2MHz internal clock = 0.5us per cycle (timer count)
Use 16-bit Timer1, No Prescaler
Set CCPR1 = 50000; Timer1 resets on match every 50000 counts = 25000us
Each Timer1 reset requires 1 cycle compensation... so set CCPR1 = 49999
40 interrupts x 25000us each = 1 second
}

 
{
For One Second Update: 20 MHz
 
20MHz Fosc = 5MHz internal clock = 0.2us per cycle (timer count)
Use 16-bit Timer1, No Prescaler
Set CCPR1 = 50000; Timer1 resets on match every 50000 counts = 10000us
Each Timer1 reset requires 1 cycle compensation... so set CCPR1 = 49999
100 interrupts x 10000us each = 1 second
}

 
 
Dim C1 As Word Absolute $0FBE ' CCPR1L + CCPR1H
  Dim Int_Counter As LongWord

Dim update As Boolean
 
Dim secs,mins,hrs As Word
Dim Posit As Word
Dim PositLeft As Word
Dim PositRight As Word
 
Interrupt RTC()
Dec(Int_Counter)
If Int_Counter = 0 Then
Int_Counter = 100 ' each interrupt = 10000us x 100 int's = 1 second
  update = true

End If
PIR1.2 = 0 ' clear CCP1 interrupt flag
  End Interrupt

 
Sub Clock24()
Dim clk As String
Inc(secs)
 
If secs = 43200 Then ' check each tally for rollover
  secs = 0

 
End If
 
update = false
 
mins = secs/60
Posit = TimeValues(mins) 'select constant value for each minute
  USART.Write("Seconda = ", DecToStr(secs)," Minutes = ", DecToStr(mins)," Posit: ",DecToStr(Posit), 13,10)

'UART output can be used to monitor clock operation
 

'Note: For the servo used, maxium rotation was counter-clockwise.
  End Sub

 
Sub Initialize() 'timekeeping routine
  ADCON1 = 15

secs = 0
mins = 0
hrs = 0
Int_Counter = 100
update = false
INTCON = 192 ' enable GIE & PEIE
  T1CON = 0 ' no prescaler timer OFF
  TMR1H = 0 ' clear TMR1
  TMR1L = 0

CCP1CON = 11 ' enable special trigger event
  C1 = 49999 ' set match value
  PIE1.2 = 1 ' enable CCP1 interrupt
  PIR1.2 = 0 ' clear CCP1 interrupt flag
  T1CON.0 = 1 ' Timer1 ON
  Enable(RTC) ' enable jump to RTC ISR
  End Sub

 
Sub settime()
Clock24
 
End Sub
 
PositLeft = 2292
PositRight = 984
 
Posit = 1500
 
 
Initialize
 
secs = 0 'initialize to 12:00.
  USART.SetBaudrate(br9600)

 
 
SetAllDigital
 
While 1=1
 
 
 
If update = true Then
Clock24 ' update 24H Clock output
  End If

 
High(Servo) 'create servo pulse corresponding to position and
  DelayUS(Posit) 'repeatedly send approximately every 200 milliseconds
  Low (Servo)

DelayUS(20000-Posit)
 
'clock set procedure
  If Set_Fast = 0 Then 'increment seconds at 100x and free run
  secs = secs +900

update = true
EndIf
 
If Set_Slow = 0 Then 'increment seconds at 10x and free run
  secs = secs +9

update = true
EndIf
 
 
 
Wend
 

 

Compile the program and load it into the PIC.

 

servo_clock_2_-_1-sm

This picture shows the servo clock being calibrated.  The 10-turn pot is in the gray box at the bottom, and is connected to PORTA.1 on the TAP-28 board.  The servo cable is the yellow/orange/brown cable disappearing at the top right, connected to the PORTC.2 connector on the TAP-28 board.  The only thing better than one PICkit-2 is two PICkit-2s.  The lower one with the white cable is connected to the ICSP connector on the TAP-28 and the upper one is connected to the UART connector to monitor position readings.  The TAP-28 board is powered by one of the PICkit-2s.  A single PICkit-2 could be switched between connectors also.

 

servo_clock_2_-_tap-28-sm

This is a closeup of the TAP-28 board.  This board is completely stuffed with a full set of parts.  The TAP-28 board that will be inside the clock won't need the LEDs, the USB connector or the sockets for the daughter board, I2C/SPI or even UART, keeping the cost to a minimum.  A salvaged 5V cell-phone power supply will be hard-wired to the TAP-28 to power the clock.

 

servo_clock_2_-_tap-28_min

This is the TAP-28 board to be included in the clock.  It has the minimum components for this application.  Other than the micro and support components, it has switches to set the time, along with their pullup resistors and connectors for ICSP, the servo and the pot if needed for future calibration.

 

Forum Activity

Member Access