
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.
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.
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.

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.

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.
This completes the calibration measurements.
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.

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.
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.
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.