Calibration Memory

The HP 3478A has a battery backed calibration memory. It is recommended to create a backup of that memory in case the battery fails. This library supports backing up the calibration memory and also modifying it. This allows the user to change the calibration constants while the library does all the checksum calculations in the background.

Backing up the calibration memory

To back up the calibration memory use the following python scrip also found in the examples folder. This script will copy the calibration memory to a file called calram.bin. Note: It will not overwrite the file and error out if it already exists.

#!/usr/bin/env python3
# pylint: disable=duplicate-code
# ##### BEGIN GPL LICENSE BLOCK #####
#
# Copyright (C) 2021  Patrick Baus
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####
"""This example shows how to pull the calibration memory from the HP 3478A"""
import asyncio
import logging
import sys
import typing
import warnings

import aiofiles

# Devices
from hp3478a_async import HP_3478A
from hp3478a_async.hp_3478a_helper import decode_cal_data, format_cal_string

if typing.TYPE_CHECKING:
    from async_gpib import AsyncGpib
    from prologix_gpib_async import AsyncPrologixGpibEthernetController, EosMode
else:
    # Uncomment if using a Prologix GPIB Ethernet adapter
    from prologix_gpib_async import AsyncPrologixGpibEthernetController, EosMode

    # Uncomment if using linux-gpib
    # from async_gpib import AsyncGpib

# Create the gpib device. We need a timeout of > 10 PLC (20 ms), because the DMM might reply to a conversion request
# and unable to reply to a status request during conversion (maximum time 10 PLC)

if "prologix_gpib_async" in sys.modules:
    IP_ADDRESS = "127.0.0.1"
    # pylint: disable=used-before-assignment  # false positive
    gpib_device = AsyncPrologixGpibEthernetController(IP_ADDRESS, pad=27, timeout=1, eos_mode=EosMode.APPEND_NONE)
elif "async_gpib" in sys.modules:
    # Set the timeout to 1 second (T1s=11)
    # NI GPIB adapter
    gpib_device = AsyncGpib(name=0, pad=27, timeout=11)  # pylint: disable=used-before-assignment  # false positive
else:
    raise ImportWarning("No GPIB module loaded. Please check your imports")


# This example will read the calibration memory and write it to a file named 'calram.bin'
async def main():
    """Read the calibration memory from the DMM"""
    async with HP_3478A(connection=gpib_device) as hp3478a:
        await hp3478a.clear()  # flush all buffers
        logging.getLogger(__name__).info("Reading calibration memory. This will take about 10 seconds.")
        result, filehandle = await asyncio.gather(hp3478a.get_cal_ram(), aiofiles.open("calram.bin", mode="x"))
        is_cal_enabled, data = decode_cal_data(result)  # decode to a tuple of dicts
        logging.getLogger(__name__).info("Calibration switch is enabled: %(enabled)s", {"enabled": is_cal_enabled})
        logging.getLogger(__name__).info("Calibration data: %(data)s", {"data": data})
        await filehandle.write(format_cal_string(result))
        await filehandle.close()
        logging.getLogger(__name__).info("Calibration data written to calram.bin")


# Report all mistakes managing asynchronous resources.
warnings.simplefilter("always", ResourceWarning)
logging.basicConfig(
    format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s",
    level=logging.INFO,  # Enable logs from the ip connection. Set to debug for even more info
    datefmt="%Y-%m-%d %H:%M:%S",
)

try:
    asyncio.run(main(), debug=False)
except KeyboardInterrupt:
    # The loop will be canceled on a KeyboardInterrupt by the run() method, we just want to suppress the exception
    pass

Writing the backup back to the DMM

The backup can be written back to the dmm using another script. To do so, the CAL switch on the front panel must be set to enable. Otherwise the memory cannot be written to.

#!/usr/bin/env python3
# pylint: disable=duplicate-code
# ##### BEGIN GPL LICENSE BLOCK #####
#
# Copyright (C) 2021  Patrick Baus
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####
"""This example shows how to write the calibration memory from a file to the HP 3478A"""
import asyncio
import logging
import sys
import typing
import warnings

import aiofiles

# Devices
from hp3478a_async import HP_3478A
from hp3478a_async.hp_3478a_helper import decode_cal_data, encode_cal_data

if typing.TYPE_CHECKING:
    from async_gpib import AsyncGpib
    from prologix_gpib_async import AsyncPrologixGpibEthernetController, EosMode
else:
    # Uncomment if using a Prologix GPIB Ethernet adapter
    from prologix_gpib_async import AsyncPrologixGpibEthernetController, EosMode

    # Uncomment if using linux-gpib
    # from async_gpib import AsyncGpib


# Create the gpib device. We need a timeout of > 10 PLC (20 ms), because the DMM might reply to a conversion request
# and unable to reply to a status request during conversion (maximum time 10 PLC)
if "prologix_gpib_async" in sys.modules:
    IP_ADDRESS = "127.0.0.1"
    # pylint: disable=used-before-assignment  # false positive
    gpib_device = AsyncPrologixGpibEthernetController(IP_ADDRESS, pad=27, timeout=1, eos_mode=EosMode.APPEND_NONE)
elif "async_gpib" in sys.modules:
    # Set the timeout to 1 second (T1s=11)
    # NI GPIB adapter
    gpib_device = AsyncGpib(name=0, pad=27, timeout=11)  # pylint: disable=used-before-assignment  # false positive
else:
    raise ImportWarning("No GPIB module loaded. Please check your imports")


async def main():
    """Read the calibration memory from a file and write it to the DMM"""

    # Read the calibration memory file
    async with aiofiles.open("calram.bin", mode="r") as filehandle:
        result = (await filehandle.read()).replace("\n", "")

    is_cal_enabled, data = decode_cal_data(result)  # decode to dict

    async with HP_3478A(connection=gpib_device) as hp3478a:
        await hp3478a.clear()  # flush all buffers
        is_cal_enabled, data = decode_cal_data(result)  # decode to dict
        data[5]["gain"] = 1.0  # Modify entry 5 (Note: This entry is not used, adjust to your liking)
        result = encode_cal_data(cal_enable=is_cal_enabled, data_blocks=data)  # re-encode caldata

        await hp3478a.set_cal_ram(result)
        logging.getLogger(__name__).info("Calibration data written to DMM")


# Report all mistakes managing asynchronous resources.
warnings.simplefilter("always", ResourceWarning)
logging.basicConfig(
    format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s",
    level=logging.INFO,  # Enable logs from the ip connection. Set to debug for even more info
    datefmt="%Y-%m-%d %H:%M:%S",
)

try:
    asyncio.run(main(), debug=False)
except KeyboardInterrupt:
    # The loop will be canceled on a KeyboardInterrupt by the run() method, we just want to suppress the exception
    pass

Technical details

The calibration memory dump is a human-readable ASCII string, that contain only printable characters. This was done by adding 0x40 to each byte. After subtracting 0x40, the result is a mix of bytes and nibbles (4 bit, half-bytes). The calibration data is stored as nibbles, while the checksum and the status of the CAL switch have 8 bit boundaries. The first byte of the memory dump contain said CAL switch position and is not part of the checksum-protected calibration data. The calibration memory is stored as nibbles which contain Binary-coded decimal (BCD) encoded digits. The encoding is similar to BCD 8421. At the end there are two bytes for the checksum. The memory layout is:

Memory Layout

0

1:247

CAL switch

Calibration data

13 bytes per entry

The calibration data consists of 19 entries, that have 11 bytes (22 nibbles) of data and 2 bytes for checksum:

Calram entry

0:5

6:10

11:12

Offset

Gain

Checksum

The offset is standard BCD 8421 encoded. The gain field is a little more complicated. The gain is stored as a 4-bit two’s complement signed number. Once decoded it gives the gain deviation from 1 in units of ppm. Finally the checksum is 0xFF minus the sum over the 11 data bytes. For implementation details check the source code of the hp3478a_async.hp_3478a_helper.

The 19 calibration memory entries are the following functions:

Index

Function

0

30 mV DC

1

300 mV DC

2

3 V DC

3

30 V DC

4

300 V DC

5

Not used

6

V AC

7

30 Ω 2W/4W

8

300 Ω 2W/4W

9

3 kΩ 2W/4W

10

30 kΩ 2W/4W

11

300 kΩ 2W/4W

12

3 MΩ 2W/4W

13

30 MΩ 2W/4W

14

300 mA DC

15

3 A DC

16

Not used

17

300 mA/3 A AC

18

Not used

The device ignores the unused entries and does not complain about invalid data. Typically these entries are set to offset=0 and gain=1.0.