Skip to content

Commit fbe872b

Browse files
committed
Add support for TCD1304 linear CCD
1 parent f772711 commit fbe872b

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

pslab/external/tcd1304.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""Proof of concept TCD1304 driver.
2+
3+
This driver can only drive the TCD1304 in normal mode, not electronic shutter
4+
mode. This is because the PSLab can (currently) only output two different PWM
5+
frequencies simultaneously, and electronic shutter mode requires three.
6+
7+
Furthermode, the clock frequencies are locked to 2 MHz (master) and 125 Hz (SH
8+
and ICG). The reason is the following:
9+
10+
The ICG period must be greater than the readout period, which is:
11+
master clock period * 4 * number of pixels = 7.4 ms
12+
7.4 ms -> 135 Hz, which is therefore the fastest the ICG clock can run.
13+
14+
The lowest possible frequency the PSLab can generate with sufficient
15+
clock precision is 123 Hz. Below that the 16-bit timer that drives the
16+
PWM must be prescaled so much that we can no longer satisfy the TCD1304's
17+
timing requirements.
18+
19+
Thus, the range of possible ICG frequencies is [123, 135], which is so small
20+
that it makes more sense to just lock it to 125 Hz, which has the added
21+
advantage of being an even divisor of the PSLab's MCU frequency (64 MHz).
22+
23+
It should be possible to increase the master clock to 4 MHz, which would also
24+
make ICG frequencies up to 250 Hz possible. However, the readout period would
25+
be 3.7 ms, which the PSLab's oscilloscope might struggle to capture with good
26+
quality.
27+
"""
28+
29+
from typing import List
30+
31+
from numpy import ndarray
32+
33+
from pslab import Oscilloscope
34+
from pslab import PowerSupply
35+
from pslab import PWMGenerator
36+
from pslab.instrument.waveform_generator import _get_wavelength
37+
from pslab.protocol import MAX_SAMPLES
38+
from pslab.serial_handler import SerialHandler
39+
40+
41+
class TCD1304:
42+
def __init__(self, device: SerialHandler):
43+
self._pwm = PWMGenerator(device)
44+
self._oscilloscope = Oscilloscope(device)
45+
self._sh_frequency = 125
46+
47+
def start_clocks(self, inverted: bool = True):
48+
"""Start the Master, SH, and ICG clocks.
49+
50+
Parameters
51+
----------
52+
inverted : bool, optional
53+
The TCD1304 datasheet recommends placing a hex inverter between the
54+
sensor and the MCU. By default, the clocks are therefore inverted
55+
relative to what they should be to drive the sensor. If you do not
56+
use a hex inverter, set this to False.
57+
58+
Returns
59+
-------
60+
None.
61+
62+
"""
63+
self._pwm.map_reference_clock("SQ1", 6) # 2 MHz
64+
65+
resolution = _get_wavelength(self._sh_frequency)[0] ** -1
66+
# Timing requirements:
67+
# (1) The SH clock must go high between 100 ns to 1000 ns after the ICG
68+
# clock goes low.
69+
# (2) The SH clock must stay high for at least 1 µs.
70+
# (3) The ICG clock must stay low at least 1 µs after the SH clock goes
71+
# low.
72+
# I got the following numbers through trial and error. They meet the
73+
# above requirements.
74+
# TODO: Calculate appropriate duty cycles and phases.
75+
magic_numbers = [
76+
12 * resolution,
77+
48 * resolution,
78+
16 * resolution,
79+
1 - 42 * resolution,
80+
]
81+
82+
if inverted:
83+
self._pwm.generate(
84+
["SQ2", "SQ3"],
85+
frequency=self._sh_frequency,
86+
duty_cycles=[1 - magic_numbers[0], magic_numbers[1]],
87+
phases=[magic_numbers[2], 0],
88+
)
89+
else:
90+
self._pwm.generate(
91+
["SQ2", "SQ3"],
92+
frequency=self._sh_frequency,
93+
duty_cycles=[magic_numbers[0], 1 - magic_numbers[1]],
94+
phases=[magic_numbers[3], 0],
95+
)
96+
97+
def stop_clocks(self):
98+
"""Stop the Master, SH, and ICG clocks.
99+
100+
Returns
101+
-------
102+
None.
103+
104+
"""
105+
self._pwm.set_state(sq1=0, sq2=0, sq3=0)
106+
107+
def read(self, analogin: str = "CH1", trigger: str = "CH2") -> List[ndarray]:
108+
"""Read the sensor's analog output.
109+
110+
Connect one of the PSLab's analog inputs to the sensor's analog output.
111+
112+
Parameters
113+
----------
114+
channel : str, optional
115+
The analog input connected to the sensor's OS pin.
116+
Defaults to "CH1".
117+
trigger : str, optional
118+
The analog input connected to the sensor's ICG pin.
119+
Defaults to "CH2".
120+
121+
Returns
122+
-------
123+
List[ndarray]
124+
Timestamps and corresponding voltages.
125+
126+
"""
127+
return self._oscilloscope.capture(
128+
analogin, 8000, 1, trigger=3, trigger_channel=trigger
129+
)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy