From ef74e60b27bfa645465ad12bcca28ad87b99a869 Mon Sep 17 00:00:00 2001 From: Tony DiCola Date: Fri, 1 Dec 2017 01:38:25 -0800 Subject: [PATCH] Initial commit. --- .gitignore | 3 + .travis.yml | 53 +++++++++++ CODE_OF_CONDUCT.md | 74 +++++++++++++++ LICENSE | 21 +++++ README.rst | 44 +++++++++ adafruit_max31865.py | 219 +++++++++++++++++++++++++++++++++++++++++++ api.rst | 5 + conf.py | 142 ++++++++++++++++++++++++++++ readthedocs.yml | 2 + requirements.txt | 1 + 10 files changed, 564 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 LICENSE create mode 100644 README.rst create mode 100644 adafruit_max31865.py create mode 100644 api.rst create mode 100644 conf.py create mode 100644 readthedocs.yml create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92d3065 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +_build +*.pyc diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4941ced --- /dev/null +++ b/.travis.yml @@ -0,0 +1,53 @@ +# Travis CI configuration for automated .mpy file generation. +# Author: Tony DiCola +# License: Public Domain +# This configuration will work with Travis CI (travis-ci.org) to automacially +# build .mpy files for CircuitPython when a new tagged release is created. This +# file is relatively generic and can be shared across multiple repositories by +# following these steps: +# 1. Copy this file into a .travis.yml file in the root of the repository. +# 2. Change the deploy > file section below to list each of the .mpy files +# that should be generated. The config will automatically look for +# .py files with the same name as the source for generating the .mpy files. +# Note that the .mpy extension should be lower case! +# 3. Commit the .travis.yml file and push it to GitHub. +# 4. Go to travis-ci.org and find the repository (it needs to be setup to access +# your github account, and your github account needs access to write to the +# repo). Flip the 'ON' switch on for Travis and the repo, see the Travis +# docs for more details: https://docs.travis-ci.com/user/getting-started/ +# 5. Get a GitHub 'personal access token' which has at least 'public_repo' or +# 'repo' scope: https://help.github.com/articles/creating-an-access-token-for-command-line-use/ +# Keep this token safe and secure! Anyone with the token will be able to +# access and write to your GitHub repositories. Travis will use the token +# to attach the .mpy files to the release. +# 6. In the Travis CI settings for the repository that was enabled find the +# environment variable editing page: https://docs.travis-ci.com/user/environment-variables/#Defining-Variables-in-Repository-Settings +# Add an environment variable named GITHUB_TOKEN and set it to the value +# of the GitHub personal access token above. Keep 'Display value in build +# log' flipped off. +# 7. That's it! Tag a release and Travis should go to work to add .mpy files +# to the release. It takes about a 2-3 minutes for a worker to spin up, +# build mpy-cross, and add the binaries to the release. +language: generic + +sudo: true + +deploy: + provider: releases + api_key: $GITHUB_TOKEN + file: + - "adafruit_max31865.mpy" + skip_cleanup: true + on: + tags: true + +before_install: +- sudo apt-get -yqq update +- sudo apt-get install -y build-essential git python python-pip +- git clone https://github.com/adafruit/circuitpython.git -b 2.x +- make -C circuitpython/mpy-cross +- export PATH=$PATH:$PWD/circuitpython/mpy-cross/ +- sudo pip install shyaml + +before_deploy: +- shyaml get-values deploy.file < .travis.yml | sed 's/.mpy/.py/' | xargs -L1 mpy-cross diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1617586 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at support@adafruit.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..36bc237 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Tony DiCola for Adafruit Industries + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..f5fbec2 --- /dev/null +++ b/README.rst @@ -0,0 +1,44 @@ + +Introduction +============ + +.. image:: https://readthedocs.org/projects/adafruit-circuitpython-max31865/badge/?version=latest + :target: https://circuitpython.readthedocs.io/projects/max31865/en/latest/ + :alt: Documentation Status + +.. image :: https://img.shields.io/discord/327254708534116352.svg + :target: https://discord.gg/nBQh6qu + :alt: Discord + +TODO + +Dependencies +============= +This driver depends on: + +* `Adafruit CircuitPython `_ +* `Bus Device `_ + +Please ensure all dependencies are available on the CircuitPython filesystem. +This is easily achieved by downloading +`the Adafruit library and driver bundle `_. + +Usage Example +============= + +TODO + +Contributing +============ + +Contributions are welcome! Please read our `Code of Conduct +`_ +before contributing to help this project stay welcoming. + +API Reference +============= + +.. toctree:: + :maxdepth: 2 + + api diff --git a/adafruit_max31865.py b/adafruit_max31865.py new file mode 100644 index 0000000..fe25916 --- /dev/null +++ b/adafruit_max31865.py @@ -0,0 +1,219 @@ +# The MIT License (MIT) +# +# Copyright (c) 2017 Tony DiCola for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`adafruit_max31865` +==================================================== + +CircuitPython module for the MAX31865 platinum RTD temperature sensor. See +examples/simpletest.py for an example of the usage. + +* Author(s): Tony DiCola +""" +import math +import time + +import adafruit_bus_device.spi_device as spi_device + + +# Register and other constant values: +_MAX31865_CONFIG_REG = const(0x00) +_MAX31865_CONFIG_BIAS = const(0x80) +_MAX31865_CONFIG_MODEAUTO = const(0x40) +_MAX31865_CONFIG_MODEOFF = const(0x00) +_MAX31865_CONFIG_1SHOT = const(0x20) +_MAX31865_CONFIG_3WIRE = const(0x10) +_MAX31865_CONFIG_24WIRE = const(0x00) +_MAX31865_CONFIG_FAULTSTAT = const(0x02) +_MAX31865_CONFIG_FILT50HZ = const(0x01) +_MAX31865_CONFIG_FILT60HZ = const(0x00) +_MAX31865_RTDMSB_REG = const(0x01) +_MAX31865_RTDLSB_REG = const(0x02) +_MAX31865_HFAULTMSB_REG = const(0x03) +_MAX31865_HFAULTLSB_REG = const(0x04) +_MAX31865_LFAULTMSB_REG = const(0x05) +_MAX31865_LFAULTLSB_REG = const(0x06) +_MAX31865_FAULTSTAT_REG = const(0x07) +_MAX31865_FAULT_HIGHTHRESH = const(0x80) +_MAX31865_FAULT_LOWTHRESH = const(0x40) +_MAX31865_FAULT_REFINLOW = const(0x20) +_MAX31865_FAULT_REFINHIGH = const(0x10) +_MAX31865_FAULT_RTDINLOW = const(0x08) +_MAX31865_FAULT_OVUV = const(0x04) +_RTD_A = 3.9083e-3 +_RTD_B = -5.775e-7 + + +class MAX31865: + + # Class-level buffer for reading and writing data with the sensor. + # This reduces memory allocations but means the code is not re-entrant or + # thread safe! + _BUFFER = bytearray(3) + + def __init__(self, spi, cs, rtd_nominal=100, ref_resistor=430.0, wires=2): + self.rtd_nominal = rtd_nominal + self.red_resistor = ref_resistor + self._device = spi_device.SPIDevice(spi, cs) + # Set wire config register based on the number of wires specified. + if wires not in _WIRE_MAP: + raise ValueError('Wires must be a value of 2, 3, or 4!') + t = self._read_u8(_MAX31865_CONFIG_REG) + if wires == 3: + t |= _MAX31865_CONFIG_3WIRE + else: + # 2 or 4 wire + t &= ~_MAX31865_CONFIG_3WIRE + self._write_u8(_MAX31865_CONFIG_REG, t) + # Default to no bias and no auto conversion. + self.bias = False + self.auto_convert = False + + def _read_u8(self, address): + # Read an 8-bit unsigned value from the specified 8-bit address. + with self._device as device: + device.configure(baudrate=500000, phase=1, polarity=0) + _BUFFER[0] = address & 0x7F + device.write(self._BUFFER, end=1) + device.readinto(self._BUFFER, end=1) + return self._BUFFER[0] + + def _read_u16(self, address): + # Read a 16-bit BE unsigned value from the specified 8-bit address. + with self._device as device: + device.configure(baudrate=500000, phase=1, polarity=0) + self._BUFFER[0] = address & 0x7F + device.write(self._BUFFER, end=1) + device.readinto(self._BUFFER, end=2) + return (self._BUFFER[0] << 8) | self._BUFFER[1] + + def _write_u8(self, address, val): + # Write an 8-bit unsigned value to the specified 8-bit address. + with self._device as device: + device.configure(baudrate=500000, phase=1, polarity=0) + self._BUFFER[0] = (address & 0xFF) | 0x80 + self._BUFFER[1] = val & 0xFF + self._device.write(self._BUFFER, end=2) + + @property + def bias(self): + """Get and set the boolean state of the sensor's bias (True/False).""" + return bool(self._read_u8(_MAX31865_CONFIG_REG) & _MAX31865_CONFIG_BIAS) + + @bias.setter + def bias(self, val): + t = self._read_u8(_MAX31865_CONFIG_REG) + if val: + t |= _MAX31865_CONFIG_BIAS # Enable bias. + else: + t &= ~MAX31865_CONFIG_BIAS # Disable bias. + self._write_u8(_MAX31865_CONFIG_REG, t) + + @property + def auto_convert(self): + """Get and set the boolean state of the sensor's automatic conversion + mode (True/False). + """ + return bool(self._read_u8(_MAX31865_CONFIG_REG) & _MAX31865_CONFIG_MODEAUTO) + + @auto_convert.setter + def auto_convert(self, val): + t = self._read_u8(_MAX31865_CONFIG_REG) + if val: + t |= _MAX31865_CONFIG_MODEAUTO # Enable auto convert. + else: + t &= ~_MAX31865_CONFIG_MODEAUTO # Disable auto convert. + self._write_u8(_MAX31865_CONFIG_REG, t) + + @property + def fault(self): + """Get the fault state of the sensor. Use the clear_faults function + to clear the fault state. Returns a 6-tuple of boolean values which + indicate if any faults are present: + - HIGHTHRESH + - LOWTHRESH + - REFINLOW + - REFINHIGH + - RTDINLOW + - OVUV + """ + faults = self._read_u8(_MAX31865_FAULTSTAT_REG) + highthresh = bool(faults & _MAX31865_FAULT_HIGHTHRESH) + lowthresh = bool(faults & _MAX31865_FAULT_LOWTHRESH) + refinlow = bool(faults & _MAX31865_FAULT_REFINLOW) + refinhigh = bool(faults & _MAX31865_FAULT_REFINHIGH) + rtdinlow = bool(faults & _MAX31865_FAULT_RTDINLOW) + ovuv = bool(faults & _MAX31865_FAULT_OVUV) + return (highthresh, lowthresh, refinlow, refinhigh, rtdinlow, ovuv) + + def clear_faults(self): + """Clear any fault state previously detected by the sensor.""" + t = self._read_u8(_MAX31865_CONFIG_REG) + t &= ~0x2C + t |= _MAX31865_CONFIG_FAULTSTAT + self._write_u8(_MAX31865_CONFIG_REG, t) + + def read_rtd(self): + """Perform a raw reading of the thermocouple and return its 15-bit + value. You'll need to manually convert this to temperature using the + nominal value of the RTD and some math. If you just want temperature + use the temperature property instead. + """ + self.clear_faults() + self.bias = True + time.sleep(0.01) + t = self._read_u8(_MAX31865_CONFIG_REG) + t |= _MAX31865_CONFIG_1SHOT + self._write_u8(_MAX31865_CONFIG_REG, t) + time.sleep(0.065) + rtd = self._read_u16(_MAX31865_RTDMSB_REG) + # Remove fault bit. + rtd >>= 1 + return rtd + + @property + def temperature(self): + """Read the temperature of the sensor and return its value in degrees + Celsius. + """ + Rt = self.read_rtd() + Rt //= 32768 + Rt *= self.ref_resistor + Z1 = -_RTD_A + Z2 = _RTD_A * _RTD_A - (4 * _RTD_B); + Z3 = (4 * _RTD_B) / self.rtd_nominal; + Z4 = 2 * _RTD_B + temp = Z2 + (Z3 * Rt); + temp = (math.sqrt(temp) + Z1) / Z4 + if temp >= 0: + return temp + rpoly = Rt + temp = -242.02 + temp += 2.2228 * rpoly + rpoly *= Rt # square + temp += 2.5859e-3 * rpoly + rpoly *= Rt # ^3 + temp -= 4.8260e-6 * rpoly + rpoly *= Rt # ^4 + temp -= 2.8183e-8 * rpoly + rpoly *= Rt # ^5 + temp += 1.5243e-10 * rpoly + return temp diff --git a/api.rst b/api.rst new file mode 100644 index 0000000..cc47e6d --- /dev/null +++ b/api.rst @@ -0,0 +1,5 @@ + +.. If you created a package, create one automodule per module in the package. + +.. automodule:: adafruit_max31865 + :members: diff --git a/conf.py b/conf.py new file mode 100644 index 0000000..a60f3fa --- /dev/null +++ b/conf.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- + +import os +import sys +sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', +] + +intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'BusDevice': ('https://circuitpython.readthedocs.io/projects/bus_device/en/latest/', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'README' + +# General information about the project. +project = u'Adafruit MAX31865 Library' +copyright = u'2017 Tony DiCola' +author = u'Tony DiCola' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'1.0' +# The full version, including alpha/beta/rc tags. +release = u'1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +default_role = "any" + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +add_function_parentheses = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + try: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.'] + except: + html_theme = 'default' + html_theme_path = ['.'] +else: + html_theme_path = ['.'] + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = 'AdafruitMAX31865Librarydoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'AdafruitMAX31865Library.tex', u'Adafruit MAX31865 Library Documentation', + author, 'manual'), +] + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'adafruitMAX31865library', u'Adafruit MAX31865 Library Documentation', + [author], 1) +] + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'AdafruitMAX31865Library', u'Adafruit MAX31865 Library Documentation', + author, 'AdafruitMAX31865Library', 'One line description of project.', + 'Miscellaneous'), +] diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 0000000..a3a16c1 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,2 @@ +requirements_file: requirements.txt + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c47d35a --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +adafruit-circuitpython-bus-device