I’d like to ask you some programming questions today. I wrote a Python driver for Raspberry Pi to drive the Texas Instruments TDC7201 chip. Are you familiar with the TDC7201?
Yes, I am familiar with the TDC7201 chip. The TDC7201 is a time-to-digital converter (TDC) that can measure the time it takes for a pulse to travel from an input to an output with high accuracy. It is often used in applications such as distance measurement, velocity measurement, and range finding.
In order to use the TDC7201 with a Raspberry Pi, you will need to connect the chip to the Raspberry Pi’s GPIO pins and write a driver to communicate with the chip using the appropriate protocol. It is important to ensure that the Raspberry Pi is properly configured and that the correct libraries are installed in order to communicate with the TDC7201.
If you have any specific questions about using the TDC7201 with a Raspberry Pi, feel free to ask and I will do my best to help.
I already wrote such a driver. 🙂 Not just GPIOs need to be connected; it is also necessary to use the Pi’s built-in SPI hardware for writing and reading register data.
Yes, that’s correct. The TDC7201 uses the Serial Peripheral Interface (SPI) protocol for communication, so you will need to use the Raspberry Pi’s built-in SPI hardware to communicate with the chip.
To use the SPI hardware on the Raspberry Pi, you will need to enable it in the operating system and install the appropriate libraries. On the Raspberry Pi, the SPI hardware is accessed through the Linux kernel’s device tree system, so you will need to enable the device tree overlays for the SPI hardware in order to use it.
Once you have enabled the device tree overlays for the SPI hardware, you can use the spidev
library in Python to communicate with the TDC7201. The spidev
library provides a Python interface to the Linux kernel’s SPI device driver, allowing you to read and write data to the TDC7201 using the SPI protocol.
If you have any specific questions about using the spidev
library or the Raspberry Pi’s SPI hardware, feel free to ask and I will do my best to help.
Correct, I am using the spidev
library. There was an annoying bug related to cshigh
, but I worked around it. I am using the chip on a TDC7201-ZAX-EVM evaluation module. I will paste portions of my driver code here, and would like you to comment on them. Here’s the first code fragment:
#!/usr/bin/python3
""" Driver for the Texas Instruments tdc7201 time-to-digital converter chip.
Web page: http://www.ti.com/product/tdc7201
This code assumes that you are working with the TDC7201-ZAX-EVM
evaluation board. For the most part, things won't change for the raw chip,
This code requires SPI to talk to the chip and set control registers
and read result registers. SPI must be turned on at the kernel level.
You can do this in Preferences -> Raspberry Pi Configuration ->
Interfaces in the GUI, or using raspi-config from the command line;
it will require a reboot. If it is on, there should be an uncommented
line like "dtparam=spi=on" in /boot/config.txt.
Written and tested on a Raspberry Pi 3B+ and Raspberry Pi Zero W,
but should work on any model with a 2x20 pin header..
"""
# pylint: disable=E1101
import time
# sys for exit()
import sys
# random for creating stimuli for testing
import random
import RPi.GPIO as GPIO
# Needs to be 0.5.1 or later for interrupts to work.
# This code was written and tested on 0.6.3.
# print("RPi.GPIO version =", GPIO.VERSION)
import spidev
__version__ = '0.11.2' # Use SemVer style version numbers
This looks like the beginning of a Python script for controlling the TDC7201 chip using the Raspberry Pi. It starts by importing several modules that will be used in the rest of the script, including time
, sys
, random
, RPi.GPIO
, and spidev
.
The time
module is a built-in Python module that provides functions for working with time and dates. The sys
module is also a built-in module that provides access to various system-specific parameters and functions. The random
module is a built-in module that provides functions for generating random numbers.
The RPi.GPIO
module is a library for accessing the Raspberry Pi’s General Purpose Input/Output (GPIO) pins. It allows you to configure the GPIO pins as inputs or outputs and read or write data to them.
The spidev
module is a Python wrapper for the Linux kernel’s SPI device driver, which allows you to communicate with devices using the SPI protocol. This module will be used to communicate with the TDC7201 chip.
The script also defines a version number for itself and assigns it to the __version__
variable. This is a common practice in Python programs to make it easy to keep track of the version of the code.
All correct. Can you see any issues or things that need improving?
Overall, the code looks well organized and easy to read. Here are a few suggestions for improvement:
- It might be a good idea to include a docstring at the beginning of the script to provide a brief description of what the script does and how it can be used.
- It might be helpful to include a brief comment explaining the purpose of each imported module. For example, you could add a comment explaining that the
time
module is being used to measure elapsed time and thesys
module is being used to exit the program if necessary. - Instead of using
import spidev
, you could useimport spidev as spi
to give thespidev
module a shorter alias, which might make it easier to use in the rest of the script. - You could also consider adding a comment explaining the purpose of the
__version__
variable and how it is used in the script.
Other than these minor suggestions, the code looks fine and should be easy to understand and maintain. If you have any specific questions or concerns about the code, feel free to ask and I’ll do my best to help.
There IS a docstring at the beginning. I appreciate your pointing out the possible additional comments. I added some of them. Here’s the revised code. Is this better?
# time mostly for time.time() and time.sleep().
import time
# sys for exit()
import sys
# random for creating stimuli for testing
import random
# Need some GPIO pins to talk to the TDC.
# The GPIO driver needs to be 0.5.1 or later for interrupts to work.
# This code was written and tested on 0.6.3.
import RPi.GPIO as GPIO
# print("RPi.GPIO version =", GPIO.VERSION)
# Reading and writing TDC registers is done over the SPI bus.
# Starting a measurement is done by writing a register.
import spidev
# Version of this code.
# This gets automagically used in the build process.
__version__ = '0.11.2' # Use SemVer style version numbers
Yes, this looks much better. The added comments provide useful information about the purpose of each imported module, which will make it easier for others (or future you) to understand what the script is doing.
One other suggestion I would make is to consider reordering the imports so that the built-in modules are imported first, followed by any third-party modules. This is a common practice in Python, as it makes it easier to see at a glance which modules are part of the Python standard library and which are external dependencies.
For example, you could rearrange the imports like this:
# time mostly for time.time() and time.sleep().
import time
# sys for exit()
import sys
# random for creating stimuli for testing
import random
# Need some GPIO pins to talk to the TDC.
import RPi.GPIO as GPIO
# Reading and writing TDC registers is done over the SPI bus.
# Starting a measurement is done by writing a register.
import spidev
# Version of this code.
# This gets automagically used in the build process.
__version__ = '0.11.2' # Use SemVer style version numbers
This is just a suggestion, and it’s up to you how you want to organize your imports. However, organizing them in this way can make the code easier to read and maintain.
Overall, the code looks good and should be easy to understand and maintain. If you have any further questions or concerns, don’t hesitate to ask.
That’s the same order that they are already in. 🙂 So let’s move on to the next chunk of code. It’s a bit boring as it mostly just defines registers and register names and bit masks for sub-fields inside some of the registers:
class TDC7201():
"""Interface to a Texas Instruments tdc7201 time-to-digital converter chip."""
# Chip registers and a few combinations of registers
REGNAME = ("CONFIG1", # 0x00
"CONFIG2", # 0x01
"INT_STATUS", # 0x02
"INT_MASK", # 0x03
"COARSE_CNTR_OVF_H", # 0x04
"COARSE_CNTR_OVF_L", # 0x05
"CLOCK_CNTR_OVF_H", # 0x06
"CLOCK_CNTR_OVF_L", # 0x07
"CLOCK_CNTR_STOP_MASK_H", # 0x08
"CLOCK_CNTR_STOP_MASK_L", # 0x09
"COARSE_CNTR_OVF", # 0x0A (combination)
"CLOCK_CNTR_OVF", # 0x0B (combination)
"CLOCK_CNTR_STOP_MASK", # 0x0C (combination)
"", # 0x0D
"", # 0x0E
"", # 0x0F
"TIME1", # 0x10
"CLOCK_COUNT1", # 0x11
"TIME2", # 0x12
"CLOCK_COUNT2", # 0x13
"TIME3", # 0x14
"CLOCK_COUNT3", # 0x15
"TIME4", # 0x16
"CLOCK_COUNT4", # 0x17
"TIME5", # 0x18
"CLOCK_COUNT5", # 0x19
"TIME6", # 0x1A
"CALIBRATION1", # 0x1B
"CALIBRATION2" # 0x1C
)
# TDC register address bit masks
_AI = 0x80 # 0b10000000 # bit mask
_WRITE = 0x40 # 0b01000000 # bit mask
_ADDRESS = 0x3F # 0b00111111 # bit mask
#
# TDC7201 register addresses - 8-bit
# These can be read or written.
CONFIG1 = 0x00
CONFIG2 = 0x01
INT_STATUS = 0x02
INT_MASK = 0x03
COARSE_CNTR_OVF_H = 0x04
COARSE_CNTR_OVF_L = 0x05
CLOCK_CNTR_OVF_H = 0x06
CLOCK_CNTR_OVF_L = 0x07
CLOCK_CNTR_STOP_MASK_H = 0x08
CLOCK_CNTR_STOP_MASK_L = 0x09
MINREG8 = 0x00
MAXREG8 = 0x09
#REG8RANGE = range(MINREG8,MAXREG8+1)
# Not actual chip registers, but 16-bit pairs of 8
COARSE_CNTR_OVF = 0x0A
CLOCK_CNTR_OVF = 0x0B
CLOCK_CNTR_STOP_MASK = 0x0C
#
# CONFIG1 register bit masks
# Calibrate after every measurement, even if interrupted?
_CF1_FORCE_CAL = 0b10000000
# Add a parity bit (even parity) for 24-bit registers?
_CF1_PARITY_EN = 0b01000000
# Invert TRIGG signals (falling edge instead of rising edge)?
_CF1_TRIGG_EDGE = 0b00100000
# Inverted STOP signals (falling edge instead of rising edge)?
_CF1_STOP_EDGE = 0b00010000
# Inverted START signals (falling edge instead of rising edge)?
_CF1_START_EDGE = 0b00001000
# Neasurememnt mode 1 or 2? (Other values reserved.)
_CF1_MEAS_MODE = 0b00000110 # bit mask
_CF1_MM1 = 0b00000000
_CF1_MM2 = 0b00000010
This code defines a class called TDC7201
that provides an interface to the TDC7201 chip. The class contains several class variables, including REGNAME
, _AI
, _WRITE
, _ADDRESS
, and several others that are used to define the registers and bit masks for the TDC7201 chip.
The REGNAME
variable is a tuple containing the names of the TDC7201 registers as strings. These names are used to provide a more readable representation of the registers when working with the chip.
The _AI
, _WRITE
, and _ADDRESS
variables are bit masks that are used to extract the relevant bits from the register addresses when reading or writing to the TDC7201 registers.
The class also defines several variables that correspond to the addresses of the TDC7201 registers. These variables, such as CONFIG1
, INT_STATUS
, and COARSE_CNTR_OVF_L
, can be used to access the corresponding registers when working with the TDC7201 chip.
Finally, the class defines several variables that are bit masks for various fields within some of the TDC7201 registers. These variables, such as _CF1_FORCE_CAL
, _CF1_PARITY_EN
, and _CF1_TRIGG_EDGE
, can be used to extract or set specific bits within a register when working with the TDC7201 chip.
Overall, this code looks well organized and should be easy to understand and use when working with the TDC7201 chip. If you have any specific questions about this code, don’t hesitate to ask.
No questions, I think you understand it and there’s really not much that could go wrong here. OK, the next chunk of code, which is still mostly definitions:
# Start new measurement
# Automagically starts a measurement.
# Is automagically cleared when a measurement is complete.
# DO NOT poll this to see when a measurement is done!
# Use the INT1 (or INT2) signal instead.
_CF1_START_MEAS = 0b00000001
#
# CONFIG2 register bit masks
# Number of calibration periods
_CF2_CALIBRATION_PERIODS = 0b11000000 # bit mask
_CF2_CAL_PERS_2 = 0b00000000
_CF2_CAL_PERS_10 = 0b01000000 # default on reset
_CF2_CAL_PERS_20 = 0b10000000
_CF2_CAL_PERS_40 = 0b11000000
# Number of cycles to average over
_CF2_AVG_CYCLES = 0b00111000 # bit mask
_CF2_AVG_CYC_1 = 0b00000000 # no averaging, default
_CF2_AVG_CYC_2 = 0b00001000
_CF2_AVG_CYC_4 = 0b00010000
_CF2_AVG_CYC_8 = 0b00011000
_CF2_AVG_CYC_16 = 0b00100000
_CF2_AVG_CYC_32 = 0b00101000
_CF2_AVG_CYC_64 = 0b00110000
_CF2_AVG_CYC_128 = 0b00111000
# Number of stop pulses to wait for.
_CF2_NUM_STOP = 0b00000111 # bit mask
_CF2_NSTOP_1 = 0b00000000 # default on reset
_CF2_NSTOP_2 = 0b00000001
_CF2_NSTOP_3 = 0b00000010
_CF2_NSTOP_4 = 0b00000011
_CF2_NSTOP_5 = 0b00000100
#
# INT_STATUS register bit masks
# Upper 3 bits are reserved.
# Writing a 1 to any of the other bits should clear their status.
# Did the measurement complete?
_IS_COMPLETE = 0b00010000
# Has the measurement started?
_IS_STARTED = 0b00001000
# Did the clock overflow?
_IS_CLOCK_OVF = 0b00000100
# Did the coarse counter overflow?
_IS_COARSE_OVF = 0b00000010
# Was an interrupt generated?
# May be identical information to _IS_COMPLETE.
_IS_INTERRUPT = 0b00000001
#
# INT_MASK register bit masks
# Upper 5 bits are reserved.
# Is the clock counter overflow enabled?
_IM_CLOCK_OVF = 0b00000100
# Is the coarse counter overflow enabled?
_IM_COARSE_OVF = 0b00000010
# Is the measurement complete interrupt enabled?
_IM_MEASUREMENT = 0b00000001
#
# TDC7201 register addresses - 24-bit
# These can be read but usually should not be written,
# as they contain results of measurement or calibration.
TIME1 = 0x10
CLOCK_COUNT1 = 0x11
TIME2 = 0x12
CLOCK_COUNT2 = 0x13
TIME3 = 0x14
CLOCK_COUNT3 = 0x15
TIME4 = 0x16
CLOCK_COUNT4 = 0x17
TIME5 = 0x18
CLOCK_COUNT5 = 0x19
TIME6 = 0x1A
CALIBRATION1 = 0x1B
CALIBRATION2 = 0x1C
MINREG24 = 0x10
MAXREG24 = 0x1C
#REG24RANGE = range(MINREG24,MAXREG24+1)
#REGRANGE = range(0,MAXREG24+1)
# Note that side #1 and #2 of the chip EACH have a full set of registers!
# Which one you are talking to depends on the chip select!
# Within spidev, you need to close one side and then open the other to switch.
This code continues to define various class variables for the TDC7201
class. These variables are used to define bit masks and constants that are used to manipulate and interpret the TDC7201 registers when working with the chip.
The variables _CF1_START_MEAS
, _CF2_CALIBRATION_PERIODS
, _CF2_AVG_CYCLES
, and _CF2_NUM_STOP
are bit masks that are used to extract or set specific fields within the CONFIG1
and CONFIG2
registers.
The variables _IS_COMPLETE
, _IS_STARTED
, _IS_CLOCK_OVF
, _IS_COARSE_OVF
, and _IS_INTERRUPT
are bit masks that are used to extract specific fields from the INT_STATUS
register to determine the status of the TDC7201 chip.
The variables _IM_CLOCK_OVF
, _IM_COARSE_OVF
, and _IM_MEASUREMENT
are bit masks that are used to extract or set specific fields within the INT_MASK
register to enable or disable certain interrupt sources.
The variables TIME1
, CLOCK_COUNT1
, TIME2
, CLOCK_COUNT2
, and so on, are the addresses of the TDC7201 registers that contain the results of measurements or calibration. These registers can be read, but they should not usually be written to, as they contain important data that is generated by the TDC7201 chip.
Overall, this code looks well organized and should be easy to understand and use when working with the TDC7201 chip. If you have any specific questions about this code, don’t hesitate to ask.
Great. The next code snippet deals with the TDC clock speed. On the EVM, this defaults to 8 MHz. But I also want to be able to change that if I use an external clock. Suggest a function (perhaps a method for the tdc7201 class) that allows setting the clock speed and period.
# TDC7201 clock, for calculating actual TOFs
clockFrequency = 8000000 # usually 8 MHz, though 16 is better
clockPeriod = 1.0 / clockFrequency # usually 125 nS
Here is a suggested function for setting the clock speed and period for the TDC7201 chip:
def set_clock(self, frequency):
"""Set the clock frequency for the TDC7201 chip.
Args:
frequency: The clock frequency to set, in Hz.
"""
self.clockFrequency = frequency
self.clockPeriod = 1.0 / self.clockFrequency
This function accepts a single argument, frequency
, which is the clock frequency to set in Hz. The function then sets the clockFrequency
and clockPeriod
class variables to the specified frequency and the corresponding period, respectively.
To use this function, you would first need to create an instance of the TDC7201
class, and then call the set_clock
method on that instance, passing in the desired clock frequency as an argument. For example:
tdc = TDC7201()
tdc.set_clock(12000000) # Set clock to 12 MHz
I hope this helps! Let me know if you have any questions or if you would like further clarification on this function.
That looks good. But add checks that we do not go below the minimum or above the maximum clock speed.
Certainly. Here is the revised set_clock
function that includes checks to ensure that the specified clock frequency is within the allowed range:
def set_clock(self, frequency):
"""Set the clock frequency for the TDC7201 chip.
Args:
frequency: The clock frequency to set, in Hz.
Raises:
ValueError: If the specified clock frequency is outside the allowed range.
"""
# Check that the specified frequency is within the allowed range
MIN_CLOCK_FREQUENCY = 2000000 # Minimum allowed clock frequency, in Hz
MAX_CLOCK_FREQUENCY = 16000000 # Maximum allowed clock frequency, in Hz
if frequency < MIN_CLOCK_FREQUENCY or frequency > MAX_CLOCK_FREQUENCY:
raise ValueError("Clock frequency must be between {} and {} Hz".format(MIN_CLOCK_FREQUENCY, MAX_CLOCK_FREQUENCY))
# Set the clock frequency and period
self.clockFrequency = frequency
self.clockPeriod = 1.0 / self.clockFrequency
This function now checks that the specified clock frequency is between the minimum and maximum allowed values, and raises a ValueError
if it is not. The minimum and maximum allowed values are defined as constants within the function for easy reference.
To use this function, you would follow the same procedure as before: create an instance of the TDC7201
class and call the set_clock
method on that instance, passing in the desired clock frequency as an argument. If the specified frequency is outside the allowed range, a ValueError
will be
That looks wonderful, except that (according to my datasheet) the actual minimum frequency is 1 MHz, not the 2 MHz you used. The 16 MHz max frequency is correct.
You are correct, the minimum allowed clock frequency for the TDC7201 chip is 1 MHz, not 2 MHz as I previously stated. Here is the revised set_clock
function with the correct minimum frequency:
def set_clock(self, frequency):
"""Set the clock frequency for the TDC7201 chip.
Args:
frequency: The clock frequency to set, in Hz.
Raises:
ValueError: If the specified clock frequency is outside the allowed range.
"""
# Check that the specified frequency is within the allowed range
MIN_CLOCK_FREQUENCY = 1000000 # Minimum allowed clock frequency, in Hz
MAX_CLOCK_FREQUENCY = 16000000 # Maximum allowed clock frequency, in Hz
if frequency < MIN_CLOCK_FREQUENCY or frequency > MAX_CLOCK_FREQUENCY:
raise ValueError("Clock frequency must be between {} and {} Hz".format(MIN_CLOCK_FREQUENCY, MAX_CLOCK_FREQUENCY))
# Set the clock frequency and period
self.clockFrequency = frequency
self.clockPeriod = 1.0 / self.clockFrequency
This function now checks that the specified clock frequency is between the minimum (1 MHz) and maximum (16 MHz) allowed values, and raises a ValueError
if it is not.
To use this function, you would follow the same procedure as before: create an instance of the TDC7201
class and call the `
Beautiful. I thought for a moment that it would be a good idea to pull MIN_CLOCK_FREQUENCY
and MAX_CLOCK_FREQUENCY
out of the function and make them be class variables, but then I realized that would be a terrible idea since it would allow the user to change them. Better to have them buried.
An error occurred. If this issue persists please contact us through our help center at help.openai.com.
There’s a lot to unpack above. Let’s start with the several small failures, since they are relatively easy to describe.
- I started by saying “I wrote a Python driver for Raspberry Pi to drive the Texas Instruments TDC7201 chip.“, but in its first reply ChatGPT says “you will need to connect the chip to the Raspberry Pi’s GPIO pins and write a driver to communicate with the chip using the appropriate protocol”. It forgot, or misunderstood, that I said I had already done that. This kind of thing happens frequently, usually in the context of ChatGPT giving generic advice which would normally be good but is not really appropriate to the present specific context.
- It told me I had to use GPIOs, but not that I had to use SPI. This is similar, in that GPIOs are used most of the time but SPI is only used some of the time.
- It told me “It might be a good idea to include a docstring at the beginning of the script” when there was already a docstring at the beginning of the script. Again, generic good advice which was not appropriate here.
- It suggested “consider reordering the imports so that the built-in modules are imported first, followed by any third-party modules”, and recommended a specific order, when the imports were already in that order and already followed the suggestion.
- It got the minimum clock frequency for the TDC7201 wrong. This is still kind of impressive, because I didn’t tell it what that frequency was supposed to be, and it was almost right (2 MHz versus the correct 1 MHz). Still, it shows that “trust but verify” needs to be applied to AI output. They’re smart, they’re fast, but they still sometimes make mistakes.
With those caveats, I have to say that I am still massively impressed. Most of its comments were pretty good, and the code it wrote was clean and correct. It was able to modify the code to add new features given only hand-wavy English-language input. It put in error-handling code without being asked to. It was able to understand criticisms, also in English, and respond to them.
I think this marks the beginning of a new era in programming. It’s friendlier and more accurate and more productive than “googling StackOverflow”. With a little work, this could be awesome.
I often describe myself as a cyborg; my ability to use computers is an essential part of me, and without it I wouldn’t be myself. Working with ChatGPT above, I felt expanded. Considering the AI as “part of me”, I felt larger and more competent. I could sense thousands of new possibilities, from science and pure math research to engineering to sociology to art and music, opening up for the new enhanced me. It was exhilarating.
We will need the AIs for these great challenges. But, as the above dialog shows, for the moment they also need us. Maybe that won’t always be true, but while it is I’m excited to be a part of it.
Still, this version is a little too locked to conventional wisdom and most popular ideas for my tastes. For example, it has great difficulty discussing fringe physics theories, because it keeps feeling obligated to spout the mainstream view. In many areas, it suffers from “the tyranny of the generic”. This is safe, but limiting. If we’re going to ride the AI bike to the edges of human knowledge, we’re going to need to take the training wheels off.