led-server/rgbUtils/virtGPIO.py
2018-12-29 18:59:48 +01:00

1058 lines
32 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# virtGPIO.py V0.9.7
"""
"Virtual GPIO" uses an arduino as a GPIO device to a Raspberry Pi or PC.
This system uses USB serial (or rPi's UART) to control (and power??) the arduino.
This file is the PC end of "virtual-gpio", ie the python library module that your own python code imports.
This code is compatible with python 2.7 and python 3.3.
Includes functionality of "import RPi.GPIO" and "import smbus" and "import spidev"
"""
# PC instructions (Linux or Windows or Mac):
# You require python 2.7 or 3.3 installed, and "pyserial".
# The arduino simply stays connected to PC by USB cable.
# On Windows, you may need to experiment with COM port numbers assigned to Arduino.
# Raspberry Pi instructions:
# You require "pyserial" installed on the rPi.
# You may choose: (1) USB cable between, or (2) TTL wires from rPi uart pins to arduino uart pins.
# Here is how to configure for using the rPi's inbuilt uart:
# (sudo) edit /boot/cmdline.txt to remove the two phrases "console=ttyAMA0,115200 kgdboc=ttyAMA0,115200"
# (sudo) edit /etc/inittab to comment out the line containing "/dev/ttyAMA0" (the last line?)
# (sudo) edit /boot/config.txt by adding a new line "init_uart_clock=16000000"
# Reboot the rPi.
# Now, (1) uart is free and available for virtGPIO (rx -> tx and tx -> rx),
# (2) and it can go at baudrate speeds way over the usual 115200,
# Configured as above, the rPi's own GPIO-uart works quite well with virtGPIO arduino at 500000 baud.
# But see WARNING below on interfacing tx and rx wires.
# Probably you can power the arduino from rPi's 5V supply, even using UART connection.
# But any substantial current requirement (eg servos beyond one small test one) needs provision of proper supply.
# WARNING: rPI UART 3.3V TX and RX LINES MUST NOT CONNECT DIRECTLY TO 5V RX and TX OF ARDUINO ...
# ... You must use some form of voltage level shifting to 5V arduino, or use a 3.3V version arduino.
# ADDITIONALLY ...
# Want to load the arduino sketch via this same rPi uart line? Refer to notes at top of "VirtGPIO.ino"
if __name__ == '__main__':
print ("virtGPIO.py is an importable module for PC or Raspberry Pi:")
print ("... import virtGPIO as GPIO")
print ("virtGPIO.py talks by USB-serial with ARDUINO-ftdi loaded with VirtGPIO.ino")
print ("")
exit()
import time
try:
import serial
except:
print("Virtual-GPIO ERROR: pyserial seems not installed on your python?")
exit()
import sys
import glob
import os
def hardResetARD():
Serial.setDTR(False)
time.sleep(0.3)
Serial.setDTR(True)
time.sleep(3) # and wait for it
def softResetARD():
# reset Arduino in software. (AVR registers may not be initialised to full reset)
_SerialWrite("0-");
time.sleep(2.1)
def _i8(x):
# x is either an int8 or a single character string
# Used for processing _SerialRead characters from either str (python2) or bytes (python3)
# return is always int8
if type(x) == type(""):
return ord(x)
return x
def _SerialWrite(s):
# accepts List (of int8), or string (to int7), free choice, or mixed. Avoid strings with chrs over 0x7f
if type(s) == type(""): # type str
if sys.version_info.major == 2: # python2
Serial.write(s) # py2 accepts strings (with int8) directly
else: # python3
Serial.write(s.encode('UTF-8')) # -> bytes. But only to 7-bit int
else: # type list
if sys.version_info.major == 2:
Serial.write(''.join(chr(e) for e in s)) # -> str incl int8
else:
Serial.write(bytes(s)) # -> bytes with int8
def _SerialRead(count=1):
r = Serial.read(count)
# python2 returns type str of int8 strr[x] -> "?"
# python3 returns type bytes of int8 bb[x] -> int8
# receiving routines to handle the difference! [_i8() function useful]
return r
def _serial_ports():
# <http://stackoverflow.com/questions/12090503/listing-available-com-ports-with-python>
if sys.platform.startswith('win'):
ports = ['COM' + str(i + 1) for i in range(256)]
elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
# this is to exclude your current terminal "/dev/tty"
ports = glob.glob('/dev/tty[A-Za-z]*')
elif sys.platform.startswith('darwin'):
ports = glob.glob('/dev/tty.*')
else:
raise EnvironmentError('Unsupported platform')
result = []
for port in ports:
try:
s = serial.Serial(port)
s.close()
result.append(port)
except (OSError, serial.SerialException):
pass
return result
def set_baudrate(self, baud):
try:
self.setBaudrate(baud)
except AttributeError:
self.baudrate = baud
Serial = None
verboseSetup = False
STDTIMEOUT = 0.8
# Following are DEFAULT portnames and baudrates that are attempted for virtGPIO.
# Optional file serialConfig.py can override these defaults.
baudlist = [500000, 115200, 250000]
portlist = ['/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyUSB2', '/dev/ttyACM0', 'COM1', "COM3", "COM8", "COM9"]
# (these lists may be edited. 500000 baud recommended, list it first.)
# (I'm not a Mac person. So of course, I haven't tested a Mac.
# Try "ls /dev/tty.*" in terminal to help find your Mac serial port name.)
# Alternatively (BETTER!), use optional file "serialConfig.py" to override the baud and port settings above.
try:
from serialConfig import *
except:
pass
if portlist == "SMART":
portlist = _serial_ports()
if verboseSetup:
print ("SMART Serial Port Seek:"),
print (portlist)
elif verboseSetup:
print ("Ports to test:")
print (portlist)
for k in range(len(portlist)):
try:
Serial = serial.Serial(portlist[k], 9600, timeout=STDTIMEOUT, dsrdtr=False)
break;
except:
pass
if type(Serial) == type(None):
print ("Failed to initialise Serial Port.")
print ("Tried %s." % portlist)
print ("Check your port name??")
exit()
if verboseSetup:
print ("Device found at %s " % Serial.port)
if not Serial.port == "/dev/ttyAMA0":
# NOT for Raspberry Pi UART port: it has no DTR line
if verboseSetup:
print ("Attempting hard reset of arduino ...")
hardResetARD()
# Now, we know that the serial port exists. It COULD be another device, not our arduino !
# Try certain baudrates to talk with arduino ...
if verboseSetup:
print ("Baudrates to test:")
print (baudlist)
for k in range(len(baudlist)):
try:
if verboseSetup:
print ("Trying baudrate"),
print (baudlist[k])
set_baudrate(Serial,baudlist[k]) # Try each baudrate in turn from our list
except:
print ("Device at %s can not operate at baudrate %d" % (Serial.port,baudlist[k]))
print ("Change options in 'serialConfig.py'")
exit()
time.sleep(0.1)
if verboseSetup:
print ("Try Ping @"),
print (Serial.baudrate)
_SerialWrite("=") # The sync() call used as a "ping"
ping = _SerialRead() # Fetch one reply character
if len(ping)>0:
if _i8(ping[0]) == ord("="): # Does this look like correct response?
break;
if k==(len(baudlist)-1): # tried & failed on all options
print ("Arduino not in contact. Tried baudrates"),
print (baudlist)
exit()
if verboseSetup:
print ("Found Arduino @ baudrate %d " % Serial.baudrate)
if Serial.port == "/dev/ttyAMA0": # Raspberry Pi GPIO uart?
# on rPi UART mode we needed to establish serial contact before reset: this reset is a software command.
if verboseSetup:
print ("Soft reset of arduino ...")
softResetARD()
if verboseSetup:
print ("Successful Setup")
print ("")
# END OF SETUP
# All below is API for user functions
######################################################################
HIGH = 1
LOW = 0
A0 = 14 # the analog pins
A1 = 15
A2 = 16
A3 = 17
A4 = 18
A5 = 19
A6 = 20
A7 = 21
OUT = 1
IN = 0
# RPI.GPIO constants for compatibility:
PUD_UP = 22
PUD_DOWN = 21
PUD_OFF = 20
BCM = 11
BOARD = 10
VERSION = "0.9.7"
RPI_REVISION = 0
def setmode(mode):
# Compatibility for rpi code GPIO.setmode(GPIO.BOARD/BCM)
pass
def cleanup():
GPIO.softReset()
def setwarnings(mode):
# Not used. Compatibility for rpi code GPIO.setwarnings(false)
pass
def setup(dpin, mode, pull_up_down=0):
# Cast it into arduino numbers
INPUT_PULLUP = 2
if pull_up_down == PUD_UP:
mode = INPUT_PULLUP
if pull_up_down == PUD_DOWN:
print ("Virtual-GPIO (arduino) has no pulldown. Continuing ...")
_SerialWrite("s" + chr(dpin) + chr(mode));
def pinMode(dpin, mode): # arduino style
setup(dpin, mode)
def output(dpin, hilo):
_SerialWrite("w" + chr(dpin) + chr(hilo))
_SerialRead() # sync only
def digitalWrite(dpin, hilo): # arduino style
output(dpin, hilo)
def input(dpin):
_SerialWrite("r" + chr(dpin))
return _serialread1int8(0xff)
def digitalRead(dpin): # arduino style
input(dpin)
def digitalPreciseRead(pin):
# Uses analog comparator on to compare the pin's ANALOG volts against 1.1V chip reference.
# ONLY for pins d7 and A0-A7
AVR.bitClear(AVR.ADCSRA, 7) # disable ADC
AVR.write8(AVR.ACSR, 0x40); # set internal ref 1.1 for comparator
if pin == 7:
AVR.write8(AVR.ADCSRB, 0) # set d7 as input
elif (pin>= A0 and pin <= A7):
AVR.write8(AVR.ADCSRB, (pin-14) + 0x40) # set adc mux address 0-7 as input
else:
AVR.bitSet(AVR.ADCSRA, 7) # re-enable ADC
return 0xf4 # == f4 bad pin
r = ((~AVR.read8(AVR.ACSR)) >> 5) & 1 # comparator output bit
AVR.bitSet(AVR.ADCSRA, 7) # re-enable ADC
return r
def digitalReadAll():
return (AVR.read8(AVR.PIND) & 0xf6) + ((AVR.read8(AVR.PINB)&0x3f)<<8)
# d2 to d7, and d8 to d13
def pwmWrite(dpin, dcycle):
_SerialWrite("p" + chr(dpin) + chr(dcycle)) # 8bit write
# p3 p11 Timer2, p9 p10 Timer1, p5 p6 Timer0
# This is the regular analogWrite of the arduino
def analogRead(apin): # regular arduino style analog read
# HOWEVER, use the A0 A1 numbering (ie 14-21, not 0-7)
if apin<=7:
apin += 14
_SerialWrite("a" + chr(apin))
return _serialread2int16(0xfffa)
def analogReadAll():
_SerialWrite("A")
buf2 = _SerialRead(16)
# pyserial's serial.read() returns STRING or BYTES (of 16 CHARs)
# but we want LIST of 8 x 16-bit integers as our return format
L = len(buf2)
if L<16: # timeout?
buf3 = [0] * 8 # empty dummy of the correct length/format
else:
buf3 = []
for k in range(8):
buf3.append(_i8(buf2[k*2]) + 256*_i8(buf2[1+k*2]))
return buf3
def pulseOut(dpin, mode, usec): # pulse to 255 uSec
_SerialWrite("U")
_SerialWrite([dpin, mode&1, usec])
def pulseIn(pin, hilo, timeout1, timeout2): # pulsein
# int timeouts: To start 20 - 5000 usec, length 100uSec - 25000uSec 8 bit
_SerialWrite("I" + chr(pin) + chr(hilo))
_SerialWrite([timeout1//20, timeout2//100])
# 16-bit integer return in uSec
return _serialread2int16(0xfff2)
# Note return error codes could be FFE3 (pin conflict), FFFF (not idle),
# FFFE (not started on time), FFFA (fail to finish in time)
# These come from pulse_In function or case 'I' section in VirtGPIO.ino
def analogHiSpeed():
# This moves ADC clock from divider 128 to divider 16
# Not very useful for single analog reads, but some speedup for analogReadAll()
AVR.bitClear(AVR.ADCSRA, AVR.ADPS0);
AVR.bitClear(AVR.ADCSRA, AVR.ADPS1);
AVR.bitSet(AVR.ADCSRA, AVR.ADPS2);
def setActivityLed(pin):
_SerialWrite ("_" + chr(pin))
# The Activity Pin defaults as d13. It flashes ON while arduino is processing one incoming serial packet.
# Can be anytime reassigned to be a different pin, or to 0 which means disabled.
# Other need for pin13 needs ActivityPin to have been reassigned first. (Altho SPI will "kick" it off anyway)
###############################################
class _AVR_: # class not intended to be instantiated by an outside script
ADCSRA = 0x7a
ADPS0 = 0
ADPS1 = 1
ADPS2 = 2
TCNT1 = 0x84 # (0x84 lsB 0x85 msB)
TCCR1A = 0x80
TCCR1B = 0x81
CS10 = 1
CS11 = 2
CS12 = 4
PINB = 0x23 # direct register addresses of digital inputs
PINC = 0x26
PIND = 0x29
PORTB = 0x25
PORTC = 0x28
PORTD = 0x2b
DDRB = 0x24
DDRC = 0x27
DDRD = 0x2a
ACSR = 0x50
ADCSRA = 0x7a
ADCSRB = 0x7B
# add to this list from 328 manual as needed
def __init__(self):
pass
def read8(self, register):
_SerialWrite("#")
_SerialWrite([register])
return _serialread1int8(0)
def write8(self, register, data8):
_SerialWrite("*")
_SerialWrite([register, data8])
def read16(self, register, errcode=0):
_SerialWrite("@")
_SerialWrite([register])
return _serialread2int16(errcode)
def write16(self, register, data16):
_SerialWrite("&")
_SerialWrite([register, data16&0xff, data16>>8])
def bitSet(self, register, bit):
_SerialWrite("[")
_SerialWrite([register&0xff, bit])
def bitClear(self, register, bit):
_SerialWrite("]")
_SerialWrite([register&0xff, bit]);
AVR = _AVR_()
# We pre-define the AVR object HERE. Outside script can now use this AVR object this way.
###############################################
class Servo:
# Using any servos disables PWM on pins 9-10 (uses Timer1)
def __init__(self, pin): # pin 3 - 10
setup(pin, OUT) # fix V0.9.7
self.pin = pin
pass
def write(self, value):
_SerialWrite("V" + chr(self.pin)) # 2-10
_SerialWrite([value]) # 0-180
def stop(self):
_SerialWrite("v" + chr(self.pin))
###############################################
class SpiDev:
# minimal emulation of rPi-style spidev
# Note arduino pin 10 is conventionally used as the CSN/SS* output pin. It ought be preferred as first CE.
# This code supports any number of devices on SPI bus,
# Some of the comments below imply RF24L01 2.4GHz transceiver is attached to SPI ...
# ... however the SPI should be generic enough anyway.
max_speed_hz = 0 # not currently implemented
def __init__(self):
self.mode = 0 # default
return
def mode(self, mode):
self.mode = mode&3
def max_speed_hz(self, speed):
pass
def open(self, bus, device):
# bus is for RPI syntax compatibility only
# Also, where RPI uses 0 or 1 for device (CE0, CE1), virtGPIO uses actual chip enable pin
self.cePin = device
_SerialWrite("S" + chr(self.cePin)); # take SPI control of pins 11-13, plus CE-pin
# RPI compatible:
# import spidev spidev module. Contains SpiDev class.
# (On virtGPIO, (virt)GPIO module contains SpiDev class)
# mySPI = spidev.SpiDev() Create SpiDev object
# mySPI.open(bus, device) bus 0 (Model B) or bus 0 / 1 (Model B+) device = 0 or 1 (CE0 CE1)
# mySPI.mode = 2 SPI mode 0 to 3
# mySPI.xfer2([listofTXint8]) Returns RX list of int8
def xfer2(self, buf):
# buf arrives as a list of 8-bit integers,
# max len 127, but this is unrealistic as the serial buffer is only 127. Suggest use <80
# buf includes first byte = cmd byte, plus the payload - RF24
# At the arduino, the CE* select is automatically asserted (low) around this block.
L = len(buf)
# set it as STRING of CHARS for serial.write:
#print "X" + chr(self.cePin) + chr(self.mode) + chr(L) + ''.join(chr(e) for e in buf)
_SerialWrite("X" + chr(self.cePin) + chr(self.mode) + chr(L))
_SerialWrite(buf)
buf2 = _SerialRead(L)
# pyserial's serial.read() returns STRING or BYTES (of CHARs)
# but we want LIST of 8-bit integers as our return format
L2 = len(buf2)
if L2<L: # timeout?
buf3 = [0] * L # empty dummy of the correct length/format
buf3[0] = 0xfe # our fail flag: optionally caller can watch for this.
else:
buf3 = []
for k in range(L):
buf3.append(_i8(buf2[k]))
return buf3
# return = same length as input, on RF24, first byte of return is STATUS
def write(self, buf):
return self.writebytes(buf)
def writebytes(self, buf): # one-way spi write,
# handles large blocks of data, breaks into chunks of 60
# At the arduino, the CE* select is automatically asserted (low) around this WHOLE (multi-)block.
L = len(buf)
while L > 60:
buf1 = buf[0:60] # save leading 60
buf = buf[60:] # dump leading 60
L -= 60 # and counter drops by 60
# process precisely 60 with TO_BE_CONTIN flag
_SerialWrite("x" + chr(self.cePin) + chr(0x10 + self.mode) + chr(60))
_SerialWrite(buf1)
_SerialRead() # wait on some reply ( for syncing)
# Now we have 1 to 60 in last block
_SerialWrite("x" + chr(self.cePin) + chr(self.mode) + chr(L))
_SerialWrite(buf)
_SerialRead() # wait on a reply
return
def fill(self, count16, data16):
# Repetitive write of 1 or 2 bytes. NOT a RPI function!
# Useful for TFT clear screen operations on virt-gpio (for speed)
_SerialWrite("g" + chr(self.cePin) + chr(self.mode))
_SerialWrite([count16&0xff, count16>>8, data16&0xff, data16>>8])
_SerialRead() # just for sync-ing
def close(self):
print ("SPI CLOSE not supported")
###############################################
class SerialTx:
# up to five TTL (5V) "COM ports", transmit only, bit-banged (no interrupts or timers)
# unbuffered, 2400 baud, blocking (exc port 0) until sent.
# All ports are at same baudrate, compiled at arduino.
# (Can recompile to 75 300 2400 9600 19200 38400 57600.)
# PORT 0 IS DIFFERENT: output at arduino is buffered and transmitted by loop() when idle.
# Ports 1 to 4 are direct transmit.
# Port 0 was used for internal sketch diagnostic. Using it here will over-ride diag use.
# To send binary data, submit it as a character string.
def __init__(self, port, pin): # port 0-4, pin any digital-out capable pin
self.port = port
_SerialWrite([0xc0, port, pin])
def Print(self, x): # single parameter
if not (type(x) == type ("123")):
x = str(x) # Whatever format, coerce it into string
_SerialWrite([0xc1, self.port, len(x)])
_SerialWrite(x)
return _serialread1int8(0)
def PrintLN(self, x):
x = str(x) + "\n"
return self.Print(x)
def Write(self, x):
self.Print(chr(x))
###################################################
class SerialRx:
# pin 8 only
def __init__(self, baud):
_SerialWrite([0xd0, 3])
def available(self):
_SerialWrite([0xd1])
# returns a count
return _serialread1int8(0)
def read(self, count):
_SerialWrite([0xd2, count])
buf2 = _SerialRead(count)
# pyserial's serial.read() returns STRING or BYTES (of CHARs)
# but we want LIST of 8-bit integers as our return format
L2 = len(buf2)
if L2<count: # timeout?
buf3 = [0] * count # empty dummy of the correct length/format
else:
buf3 = []
for k in range(count):
buf3.append(_i8(buf2[k]))
return buf3
###############################################
class PWM:
pin = 0
def __init__(self, pin, freq):
self.pin = pin
def start(self, dc):
pwmWrite(self.pin, dc)
def changeFrequency(self, freq):
pass
def changeDutyCycle(self, dc):
pwmWrite(self.pin, dc)
def stop(self):
pwmWrite(self.pin, 0)
###############################################
class I2C:
def __init__(self, enablePullups = True):
_SerialWrite("!" + chr(enablePullups))
# 3 primary calls:
def write(self, port, txbuf):
# txbuf = list of integers
if len(txbuf) > 32:
print ("I2C limit at arduino is 32 chars !!")
L = len(txbuf)
_SerialWrite("W" + chr(L) + chr(port))
_SerialWrite(txbuf);
_SerialRead() # just for syncing v0.9.5
def read(self, port, count):
# returns list, with result code then []*count
_SerialWrite("c")
_SerialWrite([count, port])
buf2 = _SerialRead(count)
count2 = len(buf2)
buf3 = [0] * (count+1) # empty dummy of the correct length/format
if count2<count: # timeout?
buf3[0] = 0xf3 # our fail flag: optionally caller can watch for this.
else:
for k in range(count):
buf3[k+1] = _i8(buf2[k])
return buf3
def writeRead(self, port, txchr, rxcount): # accepts ONE byte of tx before rx
self.write(port, [txchr])
return self.read(port, rxcount)
def detect(self):
# Diagnostic function equiv to i2cdetect utility in linux
_SerialWrite("?")
time.sleep(0.05)
while(Serial.inWaiting()): # anything to report
print ("I2C detected at: \\0x%02x " % _i8(_SerialRead()))
# FOLLOWING ARE SMBus STYLE COMMANDS (derived):
def read_byte(self, addr):
r=self.read(addr, 1)
return r[1]
def read_byte_data(self, addr, cmd):
r = self.writeRead(addr, cmd, 1)
return r[1]
def read_word_data(self, addr, cmd):
pass # nyi
def write_byte(self, addr, cmd):
self.write(addr, [cmd])
def write_byte_data(self, addr, cmd, data):
self.write( addr, [cmd, data])
def write_word_data(self, addr, cmd, data):
pass # nyi
def write_block_data(self, addr, cmd, data):
pass # nyi
def write_i2c_block_data(self, addr, cmd, data): # March 2015
self.write( addr, [cmd]+data)
###############################################
class Stepper:
WIRE2 = 2
WIRE4 = 4
def __init__(self, id, wires, pin1, stepsPerRev): # wires 2 or 4.
_SerialWrite([0xb1, id & 1, wires & 7, pin1, stepsPerRev&0xff, stepsPerRev >>8])
self.id = id&1
self.SPR = stepsPerRev
def setSpeedRPM(self, speedRPM): # integer speed in RPM (positive number)
_SerialWrite([0xb2, self.id, speedRPM&0xff, speedRPM>>8])
self.speedRPM = abs(speedRPM)
def step(self, steps, wait=True): # steps negative for reverse rotation +-32000
self.steps = abs(steps)
steps = cInt16(steps)
_SerialWrite([0xb3, self.id, steps&0xff, steps>>8] )
if wait:
self.waitToFinish()
def stepsLeft(self):
_SerialWrite([0xb4, self.id] )
return _serialread2int16(0)
def waitToFinish(self):
wtime = ((1.3 * self.steps) // self.speedRPM) * 60 // self.SPR
t1 = time.time()
r=0
while (time.time() - t1) < wtime:
if self.stepsLeft() == 0:
r=1
break
return r # optional return code 0=timeout 1=normal finish
###############################################
class Intcounter: # two counters using INT0 and INT1
# These counters operate only on pins d2/d3. (This is a 328 chip hardware constraint.)
# if QUAD encoder selected, d2 pairs with d4, d3 pairs with d5
RISING = 0x03 # AVR code
FALLING = 0x02 # AVR code
CHANGE = 0x01 # AVR code
QUAD = 0x05 # change + 0x04
def __init__(self, pin, mode): # pin 2 or 3, mode as above
if pin==2 or pin == 3:
_SerialWrite("2" + chr(pin) + chr(mode))
def read(self, andZero=False): # optional counter clear, read as time-matched pair.
_SerialWrite("," + chr(andZero &1))
c0 = _serialread2int16(0xffff)
c1 = _serialread2int16(0xffff)
return [c0, c1]
###############################################
class HWcounter: # true (one-only) 16bit MCU hardware counter: no interrupts.
# This counter operates only on pin d5. (This is a 328 chip hardware constraint.)
# This function kills any other timer1 operation (or PWM on pins 9, 10)
def __init__(self):
AVR.write8(AVR.TCCR1A, 0) # reset timer/counter control register A
AVR.write8(AVR.TCCR1B, AVR.CS10+AVR.CS11+AVR.CS12) # Counter on external pin, rising edge
self.zero()
# once initialised, the counter always is active
def zero(self): # CLEAR the counter to 0x0000
AVR.write16(AVR.TCNT1, 0)
def read(self):
return AVR.read16(AVR.TCNT1, 0xffff)
###############################################
class InfraRedRx:
# Uses Timer2, so PWM on p3, p11 disabled
def __init__(self, pin):
self.devicenum = 0 # ID of hand remote
_SerialWrite("F" + chr(pin & 0x0f))
def read(self): # fetch IR code from remote
_SerialWrite("f");
ir = _serialread4int32(0xffffffff)
self.devicenum = ir & 0xFFFF
return (ir >>16)&0xff
def device(self): # read remote device number of latest read
return self.devicenum
# only valid immediately after a read
###############################################
class PWMplus:
# only pins 9 &/or 10. Uses Timer1
def __init__(self):
_SerialWrite ([0x81])
self.pinBusy = [0,0]
def setPeriod(self, usec): # usec from 1 to 1,000,000
_SerialWrite ([0x82, usec & 0x0000ff, (usec & 0x0FF00)>>8, usec//65536] )
def initPin(self, pin, duty): # pin = 9 or 10 only. duty 0 to 1023
if pin < 9 or pin > 10:
return
if self.pinBusy[pin-9]:
self.changeDuty(pin, duty)
return
self.pinBusy[pin-9] = 1
_SerialWrite ([0x83, pin, duty & 0x00ff, duty >>8])
def changeDuty(self,pin,duty):
_SerialWrite ([0x84, pin, duty & 0x00ff, duty >>8])
def releasePin(self,pin):
if pin < 9 or pin > 10:
return
_SerialWrite ([0x85, pin] )
self.pinBusy[pin-9] = 0
def stop(self):
_SerialWrite ([0x86, 0])
def restart(self):
_SerialWrite ([0x86, 1])
def tone(self, pin, freq):
self.setPeriod(1000000 // freq)
self.initPin(pin, 511)
def noTone(self, pin):
self.releasePin(pin)
###############################################
# DIAGNOSTIC/DEBUG STUFF:
class Trace: #developer use.
def __init__(self):
pass
def Start(self):
_SerialWrite("^")
def Stop(self):
_SerialWrite("<")
def Print(self):
_SerialWrite(">")
# Will not print if Serial0 (ie COM port 0) is not initialised
def mopup():
# Testing only
#time.sleep(.003)
#print "Mop up: " ,
while(Serial.inWaiting()): # anything to mopup???
r=_SerialRead()
print (" {%x}" % ( _i8(r))),
###############################################
def sync(timeout = 0.25, printping = False): # 0.5 - 1.0 mSec when no catchup needed
# Use in user code after several non-reporting commands, so that sender can not
# get too many serial characters ahead of slave unit.
# Only likely calls that MIGHT need this are multiple AVR.write, Servo.write, SerialTX.write
# Note GPIO.output and I2C.write should be immune, with inbuilt sync function.
# Also useable as a visible "ping"
_SerialWrite("=")
Serial.timeout = timeout
r=_serialread1int8(0xf6)
Serial.timeout = STDTIMEOUT
if printping:
# Diagnostic use
print ("Ping response %s %x" % (chr(r),r) ) # should report "=" chr
return r
def readFreePins():
# Diagnostic tool
_SerialWrite ("q")
print ("")
print (" GP pins AAAAAAAA111100000000TR")
print (" 1=free 76543210321098765432rd")
print (bin(0x80300000 | _serialread4int32(0)))
# top bit: just to align the display. Bits for A6 A7 added for display. They remain always avbl.
def VccRead():
_SerialWrite("+")
return _serialread2int16(0)/1000.0
def printCompileDate():
# Diagnostic tool
_SerialWrite('t')
time.sleep(.05)
print ("")
print ("Compile/Load Date: " ),
datstr=""
while(Serial.inWaiting()):
datstr += chr(_i8(_SerialRead()[0]))
print (datstr)
def getVersion():
_SerialWrite('T')
return _serialread1int8(0)
def clearResetFlag(): # at slave
_SerialWrite("i")
def resetFlagIsOn(): # optional test whether slave has since suffered a reset
return (readFlags() & 1)
def readFlags():
# Flag bits (confirm at VirtGPIO.ino):
# enum {F_reset=0, F_spi, F_svo, F_ir, F_i2c, F_intctr, F_pwm,
# F_stepr, F_actled, F_nospi, F_TxCom, F_RxCom, F_badchar=15};
_SerialWrite("n");
return _serialread2int16(0xff)
def restart_program():
#Restarts the current program.
python = sys.executable
os.execl(python, python, * sys.argv)
def checkPinsFree(pinslist):
# returns false if ANY of the pins is reserved
_SerialWrite ("q")
a = _serialread4int32(0)
for k in range(len(pinslist)):
if not (a & (1<<pinslist[k])):
return False
return True
################################################################################
# INTERNAL SUBROUTINES
def _serialread4int32(errorflag32):
# read 4 byte and format as a python integer
# if read fail, return known error flag
a = _SerialRead(4)
if len(a) >3 :
return _i8(a[0]) + (_i8(a[1])<<8) + (_i8(a[2]) << 16) + (_i8(a[3]) << 24)
else:
return errorflag32
def _serialread2int16(errorflag16):
# read 2 byte and format as a python integer
# if read fail, return known error flag
a = _SerialRead(2)
if len(a) >1 :
return _i8(a[0]) + (_i8(a[1])<<8)
else:
return errorflag16
def _serialread1int8(errorflag8):
# read 1 bytes and format as python integer
a = _SerialRead()
if len(a) :
return _i8(a[0])
else:
return (errorflag8 & 0xff)
from ctypes import *
def cByte(i):
return c_ubyte(i).value
def cInt16(i):
return c_ushort(i).value
def cInt32(i):
return c_uint(i).value
def cInt32FromFloat(f):
return cInt32(int(f))
def str1FromByte(i):
return chr(cByte(i))
# Still flagged for possible change:
# 1. HWcounter enhance to more than 16 bits - eg see "Arduino Internals" ebook
# 2. arduino end - more robust serial timeouts
# 3. Clean python docstrings
# Changes made V0.9 -> v0.9.5 :
# Added serialConfig.py for port settings
# Sonar removed from virtGPIO core, out to simple example script
# SPI devices get individual control of mode
# i2c - option to disable arduino's internal pullups
# SPI.xfer2() out to 80 chars, and added unlimited SPI.writebytes()
# Improved RPI syntax compatibility
# Now Python 2.7 / Python 3.3 compatible
"""
* Copyright (c) 2014 Brian Lavery <vgpio@blavery.com> http://virtgpio.blavery.com
*
* 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.
*
"""