/ simpleio.py
simpleio.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2017 Scott Shawcroft for Adafruit Industries.
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21  # THE SOFTWARE.
 22  """
 23  `simpleio` - Simple, beginner friendly IO.
 24  =================================================
 25  
 26  The `simpleio` module contains classes to provide simple access to IO.
 27  
 28  * Author(s): Scott Shawcroft
 29  """
 30  import time
 31  import sys
 32  try:
 33      import audioio
 34  except ImportError:
 35      pass # not always supported by every board!
 36  import array
 37  import digitalio
 38  import pulseio
 39  
 40  def tone(pin, frequency, duration=1, length=100):
 41      """
 42      Generates a square wave of the specified frequency on a pin
 43  
 44      :param ~microcontroller.Pin Pin: Pin on which to output the tone
 45      :param float frequency: Frequency of tone in Hz
 46      :param int length: Variable size buffer (optional)
 47      :param int duration: Duration of tone in seconds (optional)
 48      """
 49      if length * frequency > 350000:
 50          length = 350000 // frequency
 51      try:
 52          # pin with PWM
 53          #pylint: disable=no-member
 54          with pulseio.PWMOut(pin, frequency=int(frequency), variable_frequency=False) as pwm:
 55              pwm.duty_cycle = 0x8000
 56              time.sleep(duration)
 57          #pylint: enable=no-member
 58      except ValueError:
 59          # pin without PWM
 60          sample_length = length
 61          square_wave = array.array("H", [0] * sample_length)
 62          for i in range(sample_length / 2):
 63              square_wave[i] = 0xFFFF
 64          if sys.implementation.version[0] >= 3:
 65              square_wave_sample = audioio.RawSample(square_wave)
 66              square_wave_sample.sample_rate = int(len(square_wave) * frequency)
 67              with audioio.AudioOut(pin) as dac:
 68                  if not dac.playing:
 69                      dac.play(square_wave_sample, loop=True)
 70                      time.sleep(duration)
 71                  dac.stop()
 72          else:
 73              sample_tone = audioio.AudioOut(pin, square_wave)
 74              sample_tone.frequency = int(len(square_wave) * frequency)
 75              if not sample_tone.playing:
 76                  sample_tone.play(loop=True)
 77                  time.sleep(duration)
 78              sample_tone.stop()
 79  
 80  
 81  
 82  def bitWrite(x, n, b): #pylint: disable-msg=invalid-name
 83      """
 84      Based on the Arduino bitWrite function, changes a specific bit of a value to 0 or 1.
 85      The return value is the original value with the changed bit.
 86      This function is written for use with 8-bit shift registers
 87  
 88      :param x: numeric value
 89      :param n: position to change starting with least-significant (right-most) bit as 0
 90      :param b: value to write (0 or 1)
 91      """
 92      if b == 1:
 93          x |= 1<<n & 255
 94      else:
 95          x &= ~(1 << n) & 255
 96      return x
 97  
 98  
 99  
100  def shift_in(data_pin, clock, msb_first=True):
101      """
102      Shifts in a byte of data one bit at a time. Starts from either the LSB or
103      MSB.
104  
105      .. warning:: Data and clock are swapped compared to other CircuitPython libraries
106        in order to match Arduino.
107  
108      :param ~digitalio.DigitalInOut data_pin: pin on which to input each bit
109      :param ~digitalio.DigitalInOut clock: toggles to signal data_pin reads
110      :param bool msb_first: True when the first bit is most significant
111      :return: returns the value read
112      :rtype: int
113      """
114  
115      value = 0
116      i = 0
117  
118      for i in range(0, 8):
119          if msb_first:
120              value |= ((data_pin.value) << (7-i))
121          else:
122              value |= ((data_pin.value) << i)
123          # toggle clock True/False
124          clock.value = True
125          clock.value = False
126          i += 1
127      return value
128  
129  def shift_out(data_pin, clock, value, msb_first=True, bitcount=8):
130      """
131      Shifts out a byte of data one bit at a time. Data gets written to a data
132      pin. Then, the clock pulses hi then low
133  
134      .. warning:: Data and clock are swapped compared to other CircuitPython libraries
135        in order to match Arduino.
136  
137      :param ~digitalio.DigitalInOut data_pin: value bits get output on this pin
138      :param ~digitalio.DigitalInOut clock: toggled once the data pin is set
139      :param bool msb_first: True when the first bit is most significant
140      :param int value: byte to be shifted
141      :param unsigned bitcount: number of bits to shift
142  
143      Example for Metro M0 Express:
144  
145      .. code-block:: python
146  
147          import digitalio
148          import simpleio
149          from board import *
150          clock = digitalio.DigitalInOut(D12)
151          data_pin = digitalio.DigitalInOut(D11)
152          latchPin = digitalio.DigitalInOut(D10)
153          clock.direction = digitalio.Direction.OUTPUT
154          data_pin.direction = digitalio.Direction.OUTPUT
155          latchPin.direction = digitalio.Direction.OUTPUT
156  
157          while True:
158              valueSend = 500
159              # shifting out least significant bits
160              # must toggle latchPin.value before and after shift_out to push to IC chip
161              # this sample code was tested using
162              latchPin.value = False
163              simpleio.shift_out(data_pin, clock, (valueSend>>8), msb_first = False)
164              latchPin.value = True
165              time.sleep(1.0)
166              latchPin.value = False
167              simpleio.shift_out(data_pin, clock, valueSend, msb_first = False)
168              latchPin.value = True
169              time.sleep(1.0)
170  
171              # shifting out most significant bits
172              latchPin.value = False
173              simpleio.shift_out(data_pin, clock, (valueSend>>8))
174              latchPin.value = True
175              time.sleep(1.0)
176              latchpin.value = False
177              simpleio.shift_out(data_pin, clock, valueSend)
178              latchpin.value = True
179              time.sleep(1.0)
180      """
181      if bitcount < 0 or bitcount > 32:
182          raise ValueError('bitcount must be in range 0..32 inclusive')
183  
184      if msb_first:
185          bitsequence = lambda: range(bitcount-1, -1, -1)
186      else:
187          bitsequence = lambda: range(0, bitcount)
188  
189      for i in bitsequence():
190          tmpval = bool(value & (1<<i))
191          data_pin.value = tmpval
192          # toggle clock pin True/False
193          clock.value = True
194          clock.value = False
195  
196  class DigitalOut:
197      """
198      Simple digital output that is valid until reload.
199  
200        :param pin microcontroller.Pin: output pin
201        :param value bool: default value
202        :param drive_mode digitalio.DriveMode: drive mode for the output
203      """
204      def __init__(self, pin, **kwargs):
205          self.iopin = digitalio.DigitalInOut(pin)
206          self.iopin.switch_to_output(**kwargs)
207  
208      @property
209      def value(self):
210          """The digital logic level of the output pin."""
211          return self.iopin.value
212  
213      @value.setter
214      def value(self, value):
215          self.iopin.value = value
216  
217  class DigitalIn:
218      """
219      Simple digital input that is valid until reload.
220  
221        :param pin microcontroller.Pin: input pin
222        :param pull digitalio.Pull: pull configuration for the input
223      """
224      def __init__(self, pin, **kwargs):
225          self.iopin = digitalio.DigitalInOut(pin)
226          self.iopin.switch_to_input(**kwargs)
227  
228      @property
229      def value(self):
230          """The digital logic level of the input pin."""
231          return self.iopin.value
232  
233      @value.setter
234      def value(self, value): #pylint: disable-msg=no-self-use, unused-argument
235          raise AttributeError("Cannot set the value on a digital input.")
236  
237  def map_range(x, in_min, in_max, out_min, out_max):
238      """
239      Maps a number from one range to another.
240      Note: This implementation handles values < in_min differently than arduino's map function does.
241  
242      :return: Returns value mapped to new range
243      :rtype: float
244      """
245      in_range = in_max - in_min
246      in_delta = x - in_min
247      if in_range != 0:
248          mapped = in_delta / in_range
249      elif in_delta != 0:
250          mapped = in_delta
251      else:
252          mapped = .5
253      mapped *= out_max - out_min
254      mapped += out_min
255      if out_min <= out_max:
256          return max(min(mapped, out_max), out_min)
257      return min(max(mapped, out_max), out_min)