HP 3456A Repair, Part 1

What did you do last summer? It was kinda a long time ago, right? Well, I did some stuff that I wrote up and never got around to publishing. For me, last summer was the summer of aspirational Volt-Nuttery, which involved skimming a lot of EEVBlog Forum posts and NIST papers. The truth eventually sank in, though: Josephson Junction voltage standards are super expensive. Calibrations are expensive. Anything you buy cheap from eBay is not going to have a recent cal. Basically, no free lunch.

Regardless, I decided to dip my toe in the waters by getting a 6.5 digit meter for my home lab. Although a 6.5 is pretty pedestrian these days in real labs, the HP 3456A is a big step up for me, whose ‘nice’ multimeter is an Extech EX505 with 0.5% basic accuracy. Looking on eBay, I thought about getting a newer, standard HP/Agilent 34401A, but the prices put me off (and the vacuum fluorescent display eventually wears out). The 3457A, an older model 7.5 digit meter, was trending at $300, whereas the 3456A is south of $150 if you’re lucky. I took a cursory look at Keithleys but they are not really my style.

I ended up making an offer on one of those “we plugged it in and it turns on but didn’t do any further testing cough cough” listings. It was shipped in a huge box, wrapped up in bubble wrap and ensconced in a nest of packing foam. The first look was not promising — Self-Test produced the dreaded “-3” error:

HP 3456A Self-Test error “-3”

The “-3” error means that the Outguard can’t talk to the Inguard. [Brief overview of the 3456A architecture: The Outguard handles the front panel and GPIB. The Inguard handles the A/D. The Inguard floats with respect to the Outguard, which is referenced to instrument ground. The Inguard and Outguard communicate over a transformer-coupled serial scheme.] The service manual contains quite detailed step-by-step troubleshooting instructions, helpfully. So by unplugging the Inguard comms and sticking in a loopback connection, the problem could be isolated to the Outguard comms section on board A3. With a bit of poking around, I could see that the transistor array U21 on the receiver was not interpreting the signals correctly. For example, the recovered clock looked quite thin, and lacking in sufficient TTL-level amplitude.

Trace 1: A3 at the receiver; Trace 2: A3 U21 pin 14, recovered receive clock

Now U21, a CA3046 transistor array, is, shockingly, no longer commercially available. There is old stock rattling around out there, but I didn’t want to bother tracking one down. What exactly does this part do in this circuit, and can I come up with an alternative repair? It appears that this chip is set up to interpret the serial input data; clock and data are extracted, which go to a shift register. For the clock recovery circuit, two transistors are set up to activate when the input voltage exceeds one V_BE from zero, positive (Q1) or negative (Q2). If either of these transistors are on, the following transistor (Q3) will not be turned on, letting the overall output float high.

Clock recovery at A3 U21

The other connection at U21 (not shown, sorry) is a little mystifying at first glance: base and emitter hanging off the data line, +5V to the collector. As connected, the transistor doesn’t do anything, so what’s the point? My guess is this connection serves as a diode clamp, since the BC junction will act as a clamp to +5V. Additionally, what’s not shown on the CA3046 schematic are the parasitic diodes that are a fact of life in IC design. The receiver circuit must handle an input voltage that goes lower than -0.6V, which would be a problem for the subsequent TTL shift register, so it stands to reason that any negative excursions should be clamped to ground.

What does this mean? This means that we can replace a CA3046 with some discrete transistors and diodes. Nothing special, just 2N3904 NPNs and 1N914 small-signal diodes. The pinout on the 2N3904s is not a perfect match for two of the transistors, but it’s not a big deal. The CA3046 contains a matched pair, but the design doesn’t appear to utilize it in any way.

Replacing A3 U21 with discretes

I would love to tell you that this fix was an immediate success, but the truth is harsher: it didn’t work. Scratching my head, the signal appeared to be right, but the slopes were a little weak. It took me a while to realize that my loopback jumper, constructed from two pairs of Pomona minigrabber/banana cables, represented a considerable inductance, slowing down the slew rate. Lacking the appropriate jumper, I cut to the chase and hooked up the Inguard. The “-3” was vanquished!

This is how I received it…

Well, the “-3” turned into a “-4”, actually. So I did some poking around the analog section. A fuse was loose and the big relay was marked “BAD”, but nothing seemed terribly amiss. It was evidence that someone had already been inside, though. Flipping the box upside down, back and forth, eventually I noticed that the Inguard logic board was skewed. Whoever had been inside the box last hadn’t plugged it in all the way! (It’s possible it came loose in shipping, due to those blasted plastic Nylatch things falling apart.)

I also replaced the two caps on the Inguard power supply that support the unregulated +33V supply. At the time, I thought this made a difference, but now I don’t think it was necessary.

So we come to the present day. This box mostly works. If you leave it alone for a while, or look at it wrong, it locks up and the main relay does a twitch of death. So the next suspect is this DIP socket. There was actually a Service Note issued by HP, saying to get rid of the socket and solder the AM8048 directly to the board, so I feel somewhat confident in my suspicions. Removing that 40-pin thing is going to be a major pain though, so I’ll save that work for a rainy day.

Nice red socket causing issues

Since I’m a glutton for punishment, I ended up buying a second 3456A which I fixed recently, so stay tuned for part 2.

Posted in electronics, repair, test equipment | Leave a comment

Repairing a Canon S100 ‘Lens Error’

A few weeks ago, over Labor Day weekend, my Canon S100 stopped working. The barrel wouldn’t retract — something ticked rapidly inside for a second and the back panel reported a Lens Error. I tried turning it on while twisting one way, pushing down, etc. but aside from one time where it retracted and came back out, nothing seemed to change.

Canon S100 Lens Error

lousy webcam image, new assembly in foreground

This doesn’t appear to be the same error experienced by certain serial number ranges of the S100; mine is a 45xxx whereas the original lens error recall was for serials 29xxx through 41xxx. Also the original error seemed to be related to a flex cable getting loose.

Searching led me to a couple posts by bigboss97 on the DPReview forums. He appeared to have the same error, and more importantly, fixed it by getting a new lens assembly.

Thus I went to eBay and ordered a replacement lens assembly for about $20 (including shipping). What’s interesting is that what I received appears to be a remanufactured/refurbished part, complete with QC stickers. I was only able to preserve the bottom sticker, but I tried to write down the characters on the top one. Intriguingly, the top sticker was dated 9/20/2015, and the bottom one 9/18/2015, which implies to me that these refurb assemblies are produced in relatively small batches. It would be interesting to find out how exactly the pipeline works.

qc sticker

notesTaking apart the camera was pretty straightforward; I mostly followed the video but took off the front early on. A pair of tweezers helps with the flex cable snaps and such. The click ring on the front of the camera is much more substantial mechanism-wise than I expected it to be.

in progress (blurrycam)


As you can see, my replacement assembly works great. Cosmetically it’s got a few scratches, and the top part of the I in ‘IS’ is scratched off. It’s also missing the top screw in the metal slug that’s attached to the sensor.

While I was at it, I decided to tear apart my original assembly.


It’s remarkable what’s inside this thing, and it’s also remarkable that it’s made out of plastic. Unfortunately that also means eventually something will wear down inside the zoom assembly and create too much resistance for the motor to drive. That’s my impression based upon playing with mine, as it required quite a bit of force to move at a certain point (specifically, around the zoom level where the inner barrel backtracks a little before going back out).

I’m a little disappointed that my camera developed this problem after only two years (I bought it used). My previous camera was an SD1000, and it’s still working just fine. My upgrade cycle is closer to 5 years, so I guess I’ll pick up another lens assembly, just in case.

Posted in consumer electronics, repair | Leave a comment


Now for something a little different. A couple years ago I heard about the USB-DVB dongles that you could use for software radio, so I picked one up. Of course it sat around in a box until a few weeks ago, when I scratched my computer-building itch by getting an Intel NUC. The NUC DN2820 is kind of annoying due to the UEFI BIOS, but waiting around for a BIOS update paid off and I was able to install Mint 16 (Mate) using unetbootin. I installed gnuradio from the Mint repository, and librtlsdr 0.5.3 from source.

mjng@nuc ~/librtlsdr-0.5.3/src $ lsusb
 Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
 Bus 001 Device 002: ID 8087:07dc Intel Corp.
 Bus 001 Device 003: ID 046d:c063 Logitech, Inc. DELL Laser Mouse
 Bus 001 Device 005: ID 413c:2110 Dell Computer Corp.
 Bus 001 Device 004: ID 413c:1010 Dell Computer Corp.
 Bus 001 Device 007: ID 0bda:2832 Realtek Semiconductor Corp. RTL2832U DVB-T
 Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

So there it is. One additional wrinkle was that the kernel’s DVB driver was interfering with the RTLSDR library, so I had to turn that off.

mjng@nuc ~ $ rtl_test -t
 Found 1 device(s):
 0:  Generic, RTL2832U, SN: 777711111x

Using device 0: Generic RTL2832U
 Found Elonics E4000 tuner
 Supported gain values (14): -1.0 1.5 4.0 6.5 9.0 11.5 14.0 16.5 19.0 21.5 24.0 29.0 34.0 42.0
 Sampling at 2048000 S/s.
 Benchmarking E4000 PLL...
 [E4K] PLL not locked for 51000000 Hz!
 [E4K] PLL not locked for 2210000000 Hz!
 [E4K] PLL not locked for 1105000000 Hz!
 [E4K] PLL not locked for 1239000000 Hz!
 E4K range: 52 to 2209 MHz
 E4K L-band gap: 1105 to 1239 MHz
mjng@nuc ~ $ rtl_fm -f 103.7e6 -M wbfm -s 200000 -r 44100 - | aplay -r 44100 -f S16_LE
 Found 1 device(s):
 0:  Generic, RTL2832U, SN: 777711111x

Using device 0: Generic RTL2832U
 Found Elonics E4000 tuner
 Tuner gain set to automatic.
 Tuned to 104016000 Hz.
 Oversampling input by: 6x.
 Oversampling output by: 1x.
 Buffer size: 6.83ms
 Sampling at 1200000 S/s.
 Output at 200000 Hz.
 Playing raw data 'stdin' : Signed 16 bit Little Endian, Rate 44100 Hz, Mono
 underrun!!! (at least 16487.192 ms long)

Playing FM radio takes 9-10% of the CPU, which although it is a Celeron, it’s a Bay Trail dual-core Celeron…

One can look for LTE signals and use them to do crystal calibration. I thought this was pretty interesting.

./CellSearch --freq-start 715e6 --freq-end 768e6 --correction 0.9999721 --ppm 10
 Detected the following cells:
 A: #antenna ports C: CP type ; P: PHICH duration ; PR: PHICH resource type
 CID A      fc   foff RXPWR C nRB P  PR CrystalCorrectionFactor
 257 2    739M  1.63k -12.9 N  50 N one 0.99997430722744262699
 161 2    751M   1.6k -30.7 N  50 N one 0.99997423474091573503

There seems to be an amazing amount of info out there now. I’m quite interested to dig deeper.

Posted in linux, sdr | Leave a comment

Xilinx PlanAhead/XPS annoyances

So I know Xilinx wants everybody to move on to Vivado now, but being wary of new software, I am still using PlanAhead. One very annoying thing about the whole IP core business is figuring out how to roll your own without failing the implementation stage. I just did one where I could swear that the presence of an underscore in the filename killed it. Yep.


When I make AXI peripherals, I’ve been putting my logic into a hierarchy that I then instantiate inside a Xilinx-generated AXI template. Otherwise it’s hard to test without using a BFM. In order to do it this way, I have to add one line to the MPD with the name of the module. It’s the bottom one here:

lib proc_common_v3_00_a  all 
lib axi_lite_ipif_v1_01_a  all 
lib fifotest_v1_00_a user_logic vhdl
lib fifotest_v1_00_a fifotest vhdl
lib fifotest_v1_00_a fifotestcore vhdl

And would you believe that when the module was named “fifotest_core”, it didn’t want to work?

That one was a pure HDL module (it has a Xilinx FIFO instance, but that’s easy). What’s really annoying is when you want to put a Core Generator module into your peripheral. Apparently you need to put your NGCs into a /netlist folder, but I haven’t figured out how to get the PAO and MPD and such correct by hand. So far I’ve ended up completely re-importing everything in the XPS wizard, which is a little messy and annoying, but works. Par for the course I suppose.

Posted in Uncategorized | Leave a comment

MicroZed & Linaro: odds and ends

It’s been a bit quiet here because I screwed up my board and MicroZed. The PS still works but I seem to have blown out parts of the FPGA and maybe the digital side of one ADC. How did this happen? I put a terminator on DAC channel B while it was active. Well, that’s what I thought was the cause; testing found the DAC to be fine. But it seems that in putting on the terminator I flexed the board enough to upset the 2.5V powergood signal, which promptly turned off everything else. My guess is that the unclean shutdown put all the energy through a nasty path. Anyway, I bought a new MicroZed (the ethernet lights don’t blink maniacally on this one) and fixed up my backup board. Before I get back into the deep end, though, I wanted to organize some notes on some various aspects of the platform.

Avahi for great laziness

Avahi is a protocol for enabling network devices to be discoverable. That means no more figuring out the IP address through the serial terminal and writing it down so you can type it out repeatedly in the course of development. Whew. It turned out to be really easy to set up once I found the right writeup to follow. It’s just a matter of installing the daemon on the MicroZed:

# apt-get install avahi-daemon

At this point, assuming you have the right utilities on your client machine, and everything is on the same subnet, the device should be discoverable (avahi-browse -a). Unfortunately I didn’t confirm this for myself, but powered ahead and set ssh to be a discoverable service:

# cp /usr/share/doc/avahi-daemon/examples/ssh.service /etc/avahi/services/
# restart avahi-daemon

Then I can see the following on another machine:

mjng@X200s ~ $ avahi-browse -a | grep aleph
+   eth0 IPv6 aleph                                         SSH Remote Terminal  local
+   eth0 IPv4 aleph                                         SSH Remote Terminal  local
+   eth0 IPv6 aleph [00:0a:35:00:01:22]                     Workstation          local
+   eth0 IPv4 aleph [00:0a:35:00:01:22]                     Workstation          loca

And now I can ssh directly to aleph.local. At least I can from my Mint laptop; still have to ask the admin to install the avahi utils on the Red Hat box.

Boot file updating like a boss

After a few iterations I got tired of updating binfiles and devicetrees “by hand”: physically removing the microSD and putting it into a card reader. It may be blindingly obvious to you, dear reader, but it took me a while to realize that I could probably update the boot files from inside Linux.

Following the ADI/Jan Gray method, my SD card is set up with two partitions: one contains the boot files, and the other contains the rootfs. It stands to reason that I should be able to mount the boot partition and modify it. Thanks to some clues in the Zedboard forums, I was able to figure out how to do this.

The key is identifying the block devices that correspond to the SD card partitions.

# ls -l /sys/dev/block | grep mmcblk
lrwxrwxrwx 1 root root 0 Mar 15 00:08 179:0 -> ../../block/mmcblk0
lrwxrwxrwx 1 root root 0 Mar 15 00:08 179:1 -> ../../block/mmcblk0/mmcblk0p1
lrwxrwxrwx 1 root root 0 Mar 15 00:08 179:2 -> ../../block/mmcblk0/mmcblk0p2

Then we can create a device entry for the first partition and mount it as a filesystem. I wrote a little script for this:


if [ -e /dev/mmcblk0p1 ]; then
    echo "device already exists, mounting"
    mount /dev/mmcblk0p1 /root/boot
    echo "mknoding device and mounting"
    mknod /dev/mmcblk0p1 b 179 1 && mount /dev/mmcblk0p1 /root/boot

(At the time I wasn’t sure if mknod was persistent across reboots, but it appears that it is.) So now I mount the boot partition to the folder /root/boot, where I can copy files to it from inside Linaro. I also have a staging area and a few scripts that are set up to scp a new bootfile, dtb, uImage, etc. over from my development machine. When the new files are in place, it’s a simple umount and shutdown -r now to complete the cycle.

Much network performance

I installed iperf 2.0.5 on both the MicroZed and my laptop (X200s running Mint 14), and thought I’d share the results.

root@aleph:~/iperf-2.0.5/src# ./iperf -c 129.79.x.x -t 60
Client connecting to 129.79.x.x, TCP port 5001
TCP window size: 20.0 KByte (default)
[  3] local 129.79.x.x port 53334 connected with 129.79.x.x port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-60.0 sec   674 MBytes  94.2 Mbits/sec

root@aleph:~/iperf-2.0.5/src# ./iperf -c 129.79.x.x -P 2 -t 60  
Client connecting to 129.79.x.x, TCP port 5001
TCP window size: 44.1 KByte (default)
[  3] local 129.79.x.x port 53337 connected with 129.79.x.x port 5001
[  4] local 129.79.x.x port 53336 connected with 129.79.x.x port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-60.0 sec   336 MBytes  46.9 Mbits/sec
[  4]  0.0-60.0 sec   339 MBytes  47.4 Mbits/sec
[SUM]  0.0-60.0 sec   675 MBytes  94.3 Mbits/sec

root@aleph:~/iperf-2.0.5/src# ./iperf -c 129.79.x.x -u -b 100m -t 60
Client connecting to 129.79.x.x, UDP port 5001
Sending 1470 byte datagrams
UDP buffer size:  160 KByte (default)
[  3] local 129.79.x.x port 40334 connected with 129.79.x.x port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-60.0 sec   685 MBytes  95.7 Mbits/sec
[  3] Sent 488345 datagrams
[  3] Server Report:
[  3]  0.0-60.0 sec   684 MBytes  95.7 Mbits/sec   0.124 ms   82/488344 (0.017%)
[  3]  0.0-60.0 sec  1 datagrams received out-of-order

This was through a ProCurve 408 (10/100). Watching top during transfer, CPU utilization hit 14% but stayed mostly around 9%. I’m impressed. Will have to find a gigabit switch to try out.

One weird trick that symlinks hate

This is just a note to myself. When copying over the kernel module build products, one can’t simply scp ’em over without bringing along the entire kernel tree for the ride. So a trick is to pipe tar through ssh:

cd /destination/directory
ssh user@remote.host "cd /original/directory; tar cf - ./" | tar xvf
Posted in Uncategorized | Tagged | Leave a comment

MicroZed: SPI

I did two arguably dumb things that kept my SPI experience from being smooth sailing. The first was putting in a 3-8 decoder on the slave select lines and assuming there was driver support for it. The second was not connecting the MISO line for all of the devices on the bus save one; when I was designing, this had seemed like one less net to route, but during bring-up it was like driving a car with the windshield blacked out.


Well, let’s start at the beginning. In XPS, I activated SPI 1 and assigned it to the MIO range which corresponds to the PMOD on the MicroZed. Looking at various devicetree examples, it seemed that the generic spidev driver was the way to go. Of course this meant rebuilding the kernel with the spidev option (I have gotten quite accustomed to building the kernel nowadays), which I opted to do as a module. Figuring out the proper devicetree entry for the PS SPI device was, as Jan Gray put it, very cargo-cult. I still haven’t bothered to figure out what an aper_clock is, but I guess I picked the right one.

I decided to go with Python for testing the SPI bus. (Coming from a FPGA/microcontroller background, part of me finds this faintly ridiculous. But gosh, it’s so convenient.) In order to install py-spidev, I had to apt-get the python-dev package first. Then with a bit of 3rd-party documentation, I was ready to move on to the next step.

I needed to see if my devices were being talked to, and like I mentioned at the top, I had helpfully left the MISO pins disconnected for the most important chips. Since readback was impossible (without annoying board surgery) I needed some other approach. Thankfully there was one straightforward solution: chip 0 on my bus is an AD9512 clock distribution chip that has an output to the PL. By default, the output in question has a divide-by-4. Going back to Jan Gray’s uio counter example, I made a version with a register clocked from an external source. With one counter clocked internally and one counter clocked externally, I could check whether the AD9512 was taking SPI writes.

Of course, there was another wrinkle. Working from the loop example in the tightdev.net doc, I spammed the bus in order to see which SS pins were going down for device 0. It was easy enough to see with a multimeter. In this way I found that the default driver only supports one-hot (or is that one-cold?) SS. This was a problem, since I’ve got six devices total and a 74HC138 on decode duty. Ultimately I had to sit down with the driver code and figure out where to modify it; thankfully it was just a couple lines once I understood enough of what was going on. The file in question is drivers/spi/spi-xilinx-ps.c; I’m still on the 3.10 kernel whereas the ADI kernel repo has moved on. (I backported a couple of bug fixes, but beyond a certain point there was a reorganization that I haven’t bothered to look through.) The first change is in the xspips_init_hw function, where the PERI_SEL bit needs to be set.

//xspips_write(regs_base + XSPIPS_CR_OFFSET, 0x0000FC01);
xspips_write(regs_base + XSPIPS_CR_OFFSET, 0x0000FE01);

Next is the function that handles the chip select, xspips_chipselect. As it stands, the code just left-shifts by the bus number. It is also possible, I might add, to set 0b0111, which is reserved according to the documentation in the back of UG585. Naturally the docs don’t bother to explain how things work in 3-8 mode, so we guess that the lower three bits of the CS field map directly to the SS lines.

/*ctrl_reg |= (((~(0x0001 << spi->chip_select)) << 10) &
ctrl_reg |= (((0x7 & spi->chip_select) << 10) &

Anyway, after modifying the driver and compiling yet another kernel, SPI was working correctly. Writes to the AD9512 had the expected effect.

root@aleph:~# python
 Python 2.7.3 (default, Sep 26 2012, 22:50:53)
 [GCC 4.7.2] on linux2
 Type "help", "copyright", "credits" or "license" for more information.
 >>> import spidev
 >>> spi = spidev.SpiDev()
 >>> spi.open(32766,0)
 >>> spi.xfer2([0x00,0x52,0x00]) # set clk/2
 [255, 255, 255]
 >>> spi.xfer2([0x00,0x5a,0x01]) # update
 [255, 255, 255]
 >>> spi.xfer2([0x00,0x53,0x80]) # set clk/1
 [255, 255, 255]
 >>> spi.xfer2([0x00,0x5a,0x01]) # update
 [255, 255, 255]

While in another terminal:

root@aleph:~./a.out -d /dev/uio0
Estimate clock speed 1:100.012672 MHz
Estimate clock speed 2:31.255272 MHz      // divide by 4
root@aleph:~# ./a.out -d /dev/uio0 
Estimate clock speed 1:100.011963 MHz
Estimate clock speed 2:62.511162 MHz      // divide by 2
root@aleph:~# ./a.out -d /dev/uio0
Estimate clock speed 1:100.012581 MHz
Estimate clock speed 2:125.020943 MHz     // bypass divider

It was also possible to do a DAC -> ADC test with the MCP4822 and MCP3202 on the bus.

 >>> spi.open(32766,4)
 >>> spi.xfer2([0x30,0xff]) # write 0x0FF to DAC
 [255, 255]
 >>> spi.close()
 >>> spi.open(32766,5)
 >>> spi.xfer2([0x01,0xa0,0x00]) # read out ADC
 [255, 224, 156]                 # take last 12 bits

It turns out that the mapping isn’t 1:1 because the DAC uses a 2.048 V reference. Oh well.

It’s a little annoying to think about how much time it took to get to this point, compared to running on bare metal, but I guess them’s the breaks. At least I can move on to the FPGA side of things now.

Posted in linux, zedboard | Leave a comment

MicroZed: I2C through the EMIO

Sometimes it feels like this project goes really slowly. I guess it’s because I’m juggling a number of things and don’t always have the time to do a deep dive into Zynq stuff. Anyway, the next goal is some low-hanging fruit: use the PS I2C block to talk to my I2C peripherals, on Linux. My custom carrier board uses up the MIO/PMOD for SPI (which is the next goal I guess), so I2C has to go through the EMIO to the PL.


I went back to my MicroZed project, which actually already had the I2C selected in XPS — without constraints, the EMIO doesn’t get connected. You can see my MIO config below; a few other things in there are specific to my design.

Screenshot-Zynq PS MIO Configurations

To get the I2C through the EMIO to the PL, first have to set them as external pins in XPS.

Screenshot-Xilinx Platform Studio (EDK_P.68d) - -nfs-apiucf3-scratch-mjng-zedboard-microzedlinux-microzedlinux.srcs-sources_1-edk-sys-sys.xmp - [System Assembly View]

We also need to make some constraints, which we do by creating a new constraint source.

# connect PS I2C to PL through EMIO
# SCL on microzed JX2 13 -> bank35 G14
NET processing_system7_0_I2C0_SCL_pin IOSTANDARD=LVCMOS25 | LOC=G14 | PULLUP;
# SDA on microzed JX2 14 -> bank35 J15
NET processing_system7_0_I2C0_SDA_pin IOSTANDARD=LVCMOS25 | LOC=J15 | PULLUP;

(I forgot to add pullup resistors to my I2C lines, so I’d like to see if I can get by with the weak built-in pullups and a reduced bus speed.) I imagine that setting the constraint file “as target” is important… I didn’t bother to try it without. And don’t forget to regenerate the top HDL. From there, it’s straightforward to rebuild the FSBL and boot binfile (I should figure out how to script this).

Device Tree shenanigans

It’s time to take a closer look at the DTS. I started by looking at zynq-zed-adv7511-xcomm.dts, which includes zynq-zed.dtsi at the top and adi-fmcomms1.dtsi at the bottom; this is the default device tree for the ADI/Zedboard demo. Looking at zynq-zed.dtsi, we need everything here (since the MicroZed has 1GB of RAM, we could increase the memory address range here, I think, but that comes later). Looking at zynq.dtsi, and comparing to the Address Map in SDK, we currently don’t have a number of things that are in the file, yet Linux works fine (file this under ‘things to look into later’).

Anyhow, I hacked together a DTS file with only one include, going mostly from the ADI/Zedboard example and taking the I2C portion from the ZC702 example. I tried adding a line to set a local-mac-address for the ethernet, but the last two octets show up as 01:22; I think I’ve seen a post somewhere online with similar results.

Let’s take a closer look into how the device tree works. During boot, the “i2c-clk” field in the device tree gets punted to the appropriate driver (defined by the “compatible” field). By doing a bit of grepping in the linux/drivers directory, we find the appropriate file (i2c-xilinx_ps.c). There appears to be a bit of code in the driver dedicated to calculating the right divider settings from i2c-clk, which turns out to be the desired bus speed in Hz. So I’ll set that to 100000 to start out with.


There’s a package called i2c-tools that contains some useful utilities (here are some useful examples).

root@aleph:~# i2cdetect -l
i2c-0    i2c           XILINX I2C at e0004000              I2C adapter
root@aleph:~# i2cdetect -F 0
Functionalities implemented by /dev/i2c-0:
I2C                              yes
SMBus Quick Command              no
SMBus Send Byte                  yes
SMBus Receive Byte               yes
SMBus Write Byte                 yes
SMBus Read Byte                  yes
SMBus Write Word                 yes
SMBus Read Word                  yes
SMBus Process Call               yes
SMBus Block Write                yes
SMBus Block Read                 yes
SMBus Block Process Call         no
SMBus PEC                        yes
I2C Block Write                  yes
I2C Block Read                   yes

It seems that the proper driver was loaded, thanks to the device tree entry. Now time to probe the bus (read about the -r flag before you do this):

root@aleph:~# i2cdetect -r 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0 using read byte commands.
I will probe address range 0x03-0x77.
Continue? [Y/n] 
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: 30 31 -- -- 34 35 36 -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- 51 -- -- -- -- 56 -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

Hmm, I wasn’t expecting this many devices… Let’s check the datasheets.

I have two chips on my bus. First up is a RTC-8564, which should be at 0xA3/0xA2. I’m going to guess that i2c-tools drops the R/W bit and shifts everything over, giving us 0x51. Great, it’s in there.

The other is an Atmel AT30TSE004A EEPROM/temp sensor. It has three addresses (the three LSB are user-selected):

1010 110x -> 0101 0110 -> 0x56 (EEPROM R/W)
0110 110x -> 0011 0110 -> 0x36 (EEPROM other)
0011 110x -> 0001 1110 -> 0x1e (temp sensor)

So what are the remaining ones: 30, 31, 34, 35? I’m pretty sure there’s nothing else on this bus. It’s probably due to the weak pullups. Anyway, I’ll try reducing the bus frequency in the device tree and see if they go away.

But for the time being I’d like to see if it’s possible to read a temperature or manufacturer ID. The temperature part of the Atmel chip uses the annoying LM75-style “pointer register”, so it doesn’t look like i2cget will work for this. I’ll use py-smbus, for which documentation appears to be rather hard to find.

root@aleph:~# python
Python 2.7.3 (default, Sep 26 2012, 22:50:53) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from smbus import SMBus
>>> bus = SMBus(0) # i2c bus 0
>>> bus.read_i2c_block_data(0x1e,0x07,2) # addr, reg, bytes
[34, 0]
>>> bus.read_i2c_block_data(0x1e,0x06,2)
[17, 20]
>>> bus.read_i2c_block_data(0x1e,0x05,2)
[195, 220]
>>> bus.read_i2c_block_data(0x1e,0x05,2)
[195, 222]
>>> bus.read_i2c_block_data(0x1e,0x05,2)
[195, 220]

I was scared for a moment, but then I realized that the numbers were decimal. Register 0x07, the device ID, is supposed to be 0x2200, which checks out. Reg 0x06, the manufacturer ID, is supposed to be 0x1114, which also looks good. The temperature is a little annoying, since it runs from bit 11 down to bit 1. I calculate about 62 degrees C, which… (ouch, yes, it does seem to be that hot. I ran a fan on it a little, which got it down to 40.)

Well, even with the mystery addresses, I2C seems to work okay. The next thing to try is to activate the real-time clock’s entry in the device tree. And then onward to SPI.

Addendum: Reducing the bus clock to 25 kHz didn’t make a difference in terms of addresses responding to queries… I should take a look at what the code actually does. Reducing the bus to 1 kHz just fails.

Posted in linux, zedboard | 2 Comments