Pat Beirne 2 years ago
parent
commit
1947eea878
3 changed files with 940 additions and 2 deletions
  1. 127 2
      README.md
  2. 592 0
      opio
  3. 221 0
      opio.1

+ 127 - 2
README.md

@@ -1,3 +1,128 @@
-# opio
+% OPIO(1) opio v1.0
+% Pat Beirne <patb@pbeirne.com>
+% Aug 2021
 
-A small command-line tool to display and control GPIO pins on the OrangePi-i96 and OrangePi-2Giot boards. Both are based on the RDA8810. Replaces WiringPi/gpio
+# NAME
+opio - Control GPIO pins on OrangePi. A replacement for WiringPi
+
+# SYNOPSIS
+**opio** [-2] readall
+
+**opio** [-2] readallx
+
+**opio** [-2] exports
+
+**opio** leds
+
+**opio** mode *pin* [ in | out | alt ]
+
+**opio** [ -d ] read *pin*
+
+**opio** [ -d ] write *pin* [ 1 | 0 | on | off ]
+
+# DESCRIPTION
+**opio** allows access to the GPIO pins of OrangePi single-board computers. This version is designed speciifically for the
+
+- OrangePi i96
+- OrangePi 2G-iot
+
+Running **opio** without any parameters will show its usage. **opio** requires 'su' permissions, so must be run as 'root' or via 'sudo'.
+
+# COMMANDS
+**readall** 
+: Displays the state of the gpio pins in a grid format. The list includes all the pins used in the on-board 40 pin connector. For each pin, the listing shows the gpio pin number, its alternate function, its i96 pin name, its current _mode_ and _value_, and the corresponding pin number on the 40 pin connector.
+
+**readallx** 
+: Creates a similar chart, but includes the RDA pin names. 
+
+**leds** 
+: Creates a smaller chart, for the interesting I/O pins which are _not_ part of the 40 pin connector. On the i96 board, there are 3 LEDs which can be controlled via **opio**
+
+**exports**
+: Print a list of entries in /sys/class/gpio, indicating which pins have been exported (prepared for read/write). If a gpio pin exists on the 40 pin connector, the pin number is listed.
+
+**mode**
+: Sets the _mode_ for a pin as either 'in', 'out' or 'alternate-function'. Normally **opio** will create an export for this gpio pin, and then set the direction. If you set the 'alt' function, the export will be removed. With the **-d** option, the export is not created, but the 'in'/'out'/'alt' _mode_ setting will still be done.
+
+: **mode** with a pin number and no set-mode request, will simply return the current _mode_ (in, out, alt, in\*, alt\*).
+
+**read**
+: Returns the current _value_ (1/0) of the gpio pin, if possible. If the pin is in 'alt' _mode_, it is changed to 'in' before the _value_ is read.
+
+**write**
+: Attempts to write the given _value_ into the given pin. If the pin is in 'alt' _mode_, it is changed to 'out' before the _value_ is asserted.
+
+# OPTIONS
+**-d**
+: Use low-level access to control the pins. Without the **-d** option, **opio** tries to use the 'export' gpio mechanism (at /sys/class/gpio). The **-d** option only applies to **mode, read** and **write**
+
+**-2**
+: Use the pin assignments for the OrangePi-2G-iot. The default is to use the pins for the OrangePi-i96. If you wish to make this option persistent, create a file named /etc/OrangePi_2G_IOT.
+
+## Mode
+The microcontroller used on these boards presents i/o pins which can be set to 'general usage' GPIO as 'input' or 'output, or alternatively, set to operate in a specific way (uart, i2c, i2s, pcm, etc). **opio** refers the the current _mode_ of each pin one of these: either GPIO 'in', GPIO 'out', or 'alt'ernate function. 
+
+The 'in\*' or 'out\*' are marked with an asterisk to indicate that the pin is in GPIO _mode_, but _not_ listed in the current 'export' list.
+
+As a convenience, the **read** and **write** commands will automatically set the _mode_ on the GPIO pin and create an export. So, generally, the **mode** command is only *required* if you wish to change a pin back to its 'alt' function.
+
+# HERITAGE
+This program is styled after the **gpio** program written by Gordon Henderson for the Raspberry Pi.
+Unlike the original **gpio** program, this one does not implement:
+
+- export...........exports are created automatically by **mode, read** and **write**
+- pwm, clk.........this microcontroller does not have PWM or CLK type pins
+- aread, awrite....these boards have not connected and ADC/DAC pins
+
+# NUMBERING
+There are 4 naming schemes used to identify pins in these boards. **opio** uses exclusively the Linux gpio device driver numbers, the first on this list:
+
+- Linux gpio numbers (from /sys/class/gpio)
+- I/O connector pin numbers (1-40)
+- RDA microcontroller pin names (like GPIOA_C23)
+- i96 pin names (like GPIOB)
+
+You can explore the correspondence between these naming schemes with **opio readall** and **opio readallx**
+
+# HIGH LEVEL/LOW LEVEL ACCESS
+Normally, **opio** will access the GPIO pins through the Linux gpio device driver, and the corresponding files at `/sys/class/gpio`. This is _high level_ access.
+
+If you wish to bypass the Linux gpio driver, add the `-d` option to the **opio** command line and the reads/writes will be done at a _low level_, directly on the machine's registers.
+
+The commands **readall, readallx** and **leds** are always done using _low level_ access. The **exports** command is always done with _high level_ commands.
+
+Be careful about making changed with the **-d** option. Some linux gpio drivers will direction and value, so changes you make with the **-d** option may not be reflected in the export folder.
+
+# EXAMPLES
+
+display a chart of the pin assignments of the 40 pin connector:
+
+    opio readall     
+
+list the currently 'exported' pins: 
+
+    opio exports
+
+
+set gpio 15 to an output; also create an export for gpio15
+...then flash the pin (gpio15 Linux number): 
+
+    opio mode 15 out    
+    opio write 15 on
+    sleep 2
+    opio write 15 off   
+
+set gpio 15 to an output; do *not* create an export
+
+    opio -d mode 15 out 
+
+write to gpio15 pin, bypassing the export mechanism
+
+    opio -d write 15 on  
+
+
+**NOTE** The more recent /dev/gpio driver is not yet available on these boards, since they're running the 3.xx kernels.
+
+**NOTE** The 2G-IOT board uses the I2C1 bus to communicate with the modem chip. This is i2c-0 in the kernel, and is pins 3 & 5 on the 40 pin connector. Do not use these pins to connect to peripherals. And do not use **opio** to modify the _mode_ of these pins.
+
+**NOTE** The 2G-IOT board uses the I2C3 bus to communicate with the LCD. If you are using an LCD in the socket, do _not_ change the mode on pins 38 & 40.

+ 592 - 0
opio

@@ -0,0 +1,592 @@
+#!/usr/bin/python3
+# TODO: add board detection /var/local (but check /etc and /usr/local/etc as well)
+"""
+  This is a clone of the 'gpio/WiringPi' project, written specifically for the
+  OrangePi i96.
+
+  Not all of gpio is re-implemented. The 'mode' command allows the user to 
+  select any pin a GPIO or its special-funtion (i2c, spi, uart, pcm, etc)
+
+  Pat Beirne <patb@pbeirne.com>
+
+  Implemented:
+    gpio read <gpio pin number>    => displays a value, returns 0 on success
+    gpio write <gpio pin number> <value>    # set a pin high or low
+    gpio mode [in | out | alt]	    # set the pin for in/out or special-function
+    gpio readall                    # display all current pin settings
+                                    # and the pin assignments on the 40 pin i96
+    gpio readallx                   # similar to above, but shows RDA pinout
+    gpio exports                    # simply dump the state of all the exports
+                                    # which exist	
+    gpio leds                       # like readall, but for LEDs, switches  
+                                    # and other on-board devices 
+
+  NOTE: pin numbers are always gpio numbers.
+     use 'readall' to see the connector assignment
+
+  By default, the 'mode' function will create/delete an export. By default, 
+    the 'read' and 'write' function will set the mode and create an export.
+    To disable this default, use the '-d' option.
+
+  Variable plan: functions which are "is_" or "has_" return boolean
+    functions which involve 'in' 'out' 'alt' pass-&-return strings
+    functions which involve values pass-&-return ints....-1 means invalid
+    gpio is always a short int
+
+  Attack plan:
+    mode set: check iomux, then create-&-use export
+        -d set: check iomux, then low-level set
+    read: check iomux, then check export, then use export
+        -d: check iomux, then low-level read
+    write: check iomux, then check export then set mode=out then use export
+        -d: check iomux, then set direction=out, tne low-level write
+    NOTE: 'read' and 'write' will auto-set the GPIO mode and disable 'alt'
+    NOTE: 'read' and 'write' will auto-set the GPIO mode and disable 'alt'
+  
+  Classes: GPIO to access a pin through the export-gpio methods
+     GPIO_DIRECT to access only through low-level calls
+     read/write/mode will use GPIO unless the -d flag is used
+     exports will always use GPIO
+     readall/readallx/leds will use GPIO_DIRECT
+"""
+from mmap import mmap
+from struct import pack, unpack
+import os, sys, argparse, pathlib, re, logging
+
+
+
+############ board specific
+
+class Board:
+  def __init__(self,full_name,short_name):
+    self.name = full_name
+    self.short_name = short_name
+  def __repr__(self):
+    return self.name
+
+board_i96 = Board("OrangePi i96",  "OrangePi i96")
+board_2g = Board("OrangePi 2G-IOT","OrgPi 2G-iot")
+
+# pins, arranged according to i96 connector
+# i96 label, rda_port number, pio number, rda special function
+board_i96.PINS = ( 
+  ("GND",		"",	   -1,	"", ""),  # 1
+  ("GND",		"",	   -1,	"", ""),
+  ("UART2.CTS", "B8",  40,	"CTS", "ttyS1.cts"),
+  ("PWR_BTN_N", "",	   -1,	"", ""),
+  ("UART2.TX",  "C8", 104,  "TX", "ttyS1.tx"),
+  ("RST_BTN_N", "",    -1,  "1.4v", ""),    
+  ("UART2.RX",	"C7", 103,  "RX", "ttyS1.rx"),
+  ("SPI2.CLK",	"A2", 	2,  "CLK","spi2.clk"),
+  ("UART2.RTS", "B9", 	41, "RTS","ttyS1.rts"),
+  ("SPI2.DI", 	"A4", 	4,  "DI", "spi2.di"), # 10
+  ("UART1.TX",	"A14", 14,  "TX", "ttyS0.tx"), 
+  ("SPI2.CS",	"A6",	6,	"CS", "spi2.cs"),
+  ("UART1.RX",	"C6", 102,  "RX", "ttyS0.rx"),
+  ("SPI2.DO", 	"A3",   3,  "DO", "spi2.do"),
+  ("I2C2.SCL",	"A0", 	0,  "SCL", "i2c-1.scl"),
+  ("I2S.LRCK",	"A10", 10,  "LRCK", "pcm.fp"), 
+  ("I2C2.SDA",	"A1",	1,	"SDA", "i2c-1.sda"),
+  ("I2S.BCK",	"A9", 	9,  "BCK", "pcm.clk"),
+  ("I2C3.SCL", 	"B6", 	38, "SCL", "i2c-2.scl"),
+  ("I2S.DO", 	"A13",  13, "DO",  "pcm.do"), # 20 
+  ("I2C3.SDA",	"B7",	39, "SDA", "i2c-2.sda"),
+  ("I2S.DI",	"A11",  11, "DI",  "pcm.di"),
+  ("GPIO.A",	"A15",	15, "CTS", "ttyS0.cts"), 
+  ("GPIO.B", 	"A20",	20, "LCD", "lcd"),
+  ("GPIO.C",	"B24",	56, "ROM", "n/a"), 
+  ("GPIO.D", 	"D2",	66, "CTS", "ttyS2.cts"),
+  ("GPIO.E",	"D3",	67, "RTS", "ttyS2.rts"), 
+  ("GPIO.F", 	"A22",	22, "LCD", "lcd"),
+  ("GPIO.G",	"A30",	30, "LCD", "lcd"), 
+  ("GPIO.H", 	"A29",	29, "LCD", "lcd"), # 30
+  ("GPIO.I",	"A28",	28, "LCD", "lcd"), 
+  ("GPIO.J", 	"A27",	27, "LCD", "lcd"),
+  ("GPIO.K",	"A26",	26, "LCD", "lcd"), 
+  ("GPIO.L", 	"A25",	25, "LCD", "lcd"),
+  ("V_PAD", 	"", 	-1, "1.8v", ""),
+  ("SYS_DCIN",	"",	    -1, "n/c",  ""),
+  ("VDD_IN",	"", 	-1, "5V",   ""),
+  ("SYS_DCIN",	"",	    -1, "n/c",  ""),
+  ("GND", 		"", 	-1, "",     ""),
+  ("GND", 		"", 	-1, "",     "") # 40
+)	
+
+deleted_board_2g_14_PINS = (
+  ("VCC",       "",     -1,""),
+  ("I2C1.SDA",  "B31",     63, "SDA"),
+  ("I2C1.SCL",  "B30",  62, "SCL"),
+  ("GPIO.B24",  "B24",  56, ""),
+  ("GND",       "",     -1, ""),
+  ("UART1.RX",  "C6",   102, "RX"),
+  ("UART1.TX",  "A14",  14, "TX"),
+  ("UART1.CT",  "A15",  15,"CTS"),
+  ("VCC",       "", 	-1,""),
+  ("SPI2.DI",   "A4",   4,  "DI"),  #10
+  ("SPI2.DIO",  "A3",   3,  "DIO"),
+  ("SPI2.CLK",  "A2",   2,  "CLK"),
+  ("GND",       "",     -1, ""),
+  ("I2C2.SDA",  "A1",   1,  ""),
+  ("GPIO.C26",  "C26",  122, "SIM"),
+  ("GPIO.C27",  "C27",  123, "SIM"),
+  ("GPIO.C28",	"C28",	124, "LCD"), 
+  ("GPIO.C29", 	"C29",	125, "LCD"), 
+  ("GPIO.C30",	"C30",	126, "LCD"), 
+  ("GND",       "",     -1, ""), #20
+  ("I2C3.SDA",  "B7",   39, "SDA"),
+  ("I2C3.SCL",  "B6",   38, "SCL"),
+  ("UART2.CT",  "B8",   40, "CTS"),
+  ("GND",       "",     -1, ""), 
+  ("UART2.RT",  "B9",   41, "RTS"),
+  ("GND",       "",     -1, ""), 
+  ("I2C2.SCL",  "A0",   0,  "SCL"),
+  ("SPI2.CS0",  "A5",   5,  "CS0"),
+  ("SPI2.CS1",  "A6",   6,  "CS1"),
+  ("UART1.RT",  "A16",  16, "RTS"),  #30
+  ("GND",       "",     -1, ""), 
+  ("GPIO.C25",  "C25",  121, "SIM"),
+  ("GPIO.C5",   "C5",   101, ""),
+  ("GND",       "",     -1, ""), 
+  ("GPIO.B5",   "B5",   37, ""),
+  ("UART2.RX",  "C7",   103,""),
+  ("UART2.TX",  "C8",   104,""),
+  ("GND",       "",     -1, ""), 
+  ("VCC",       "",     -1,""),
+  ("VCC",       "",     -1,""), #40
+)
+
+board_2g.PINS = (
+  ("V_PAD", 	"", 	-1, "2.8v", ""),
+  ("VDD_IN",	"", 	-1, "5V",   ""),
+  ("I2C1.SDA",  "B31",     63, "SDA","i2c-0.sda"),
+  ("VDD_IN",	"", 	-1, "5V",   ""),
+  ("I2C1.SCL",  "B30",  62, "SCL",   "i2c-0.scl"),
+  ("GND",       "",     -1, "", ""), 
+  ("GPIO.B24",  "B24",  56, "", ""),
+  ("UART2.TX",  "C8",   104,"", "ttyS1.tx"),
+  ("GND",       "",     -1, "", ""), 
+  ("UART2.RX",  "C7",   103,"", "ttyS1.rx"), #10
+  ("UART1.RX",  "C6",   102, "RX", "ttyS0.rx"),
+  ("GPIO.B5",   "B5",   37, "", ""),
+  ("UART1.TX",  "A14",  14, "TX", "ttyS0.tx"),
+  ("GND",       "",     -1, "", ""), 
+  ("UART1.CT",  "A15",  15,"CTS", "ttyS0.cts"),
+  ("GPIO.C5",   "C5",   101, "", "",""),
+  ("V_PAD", 	"", 	-1, "2.8v", ""),
+  ("GPIO.C25",  "C25",  121, "SIM", ""),
+  ("SPI2.DI",   "A4",   4,  "DI", "spi-1.di"),  
+  ("GND",       "",     -1, "", ""), #20
+  ("SPI2.DIO",  "A3",   3,  "DIO", "spi-1.do"),
+  ("UART1.RT",  "A16",  16, "RTS", "ttyS0.rts"), 
+  ("SPI2.CLK",  "A2",   2,  "CLK", "spi-1.clk"),
+  ("SPI2.CS0",  "A5",   5,  "CS0", "spi-1.cs"),
+  ("GND",       "",     -1, "", ""), 
+  ("SPI2.CS1",  "A6",   6,  "CS1", "spi-1.cs1"),
+  ("I2C2.SDA",  "A1",   1,  "", "i2c-1.sda"),
+  ("I2C2.SCL",  "A0",   0,  "SCL", "i2c-1.scl"),
+  ("GPIO.C26",  "C26",  122, "SIM", ""),
+  ("GND",       "",     -1, "", ""), #30
+  ("GPIO.C27",  "C27",  123, "SIM", ""),
+  ("UART2.RT",  "B9",   41, "RTS", "ttyS1.rts"),
+  ("GPIO.C28",	"C28",	124, "LCD", "lcd"), 
+  ("GND",       "",     -1, "", ""), 
+  ("GPIO.C29", 	"C29",	125, "LCD", "lcd"), 
+  ("UART2.CT",  "B8",   40, "CTS", "ttyS1.cts"),
+  ("GPIO.C30",	"C30",	126, "LCD", "lcd"), 
+  ("I2C3.SCL",  "B6",   38, "SCL", "i2c2-scl"),
+  ("GND",       "",     -1, "", ""), 
+  ("I2C3.SDA",  "B7",   39, "SDA", "i2c2-sda"), #40
+)
+
+board_2g.other = (
+)
+
+board_i96.other = ( 
+  ("LED2", "C30", 126, ""),
+  ("LED3", "C29", 125, ""),
+  ("LED5", "C5", 101, ""),
+  ("J2", "C2", 98, "boot sd"),
+  ("OTGPWR","A17",17,""),
+#  ("DBG_TX","D1",65,"TX"),
+#  ("DBG_RX","D0",64,"RX"),
+)
+
+########################## cpu specific
+class Cpu:
+  def __init__(self,name):
+    self.name = name
+
+RDA_PORTC_IOMUX = 0x11a09008
+RDA_PORTA_IOMUX = 0x11a0900c
+RDA_PORTB_IOMUX = 0x11a09010
+RDA_PORTD_IOMUX = 0x11a09014
+
+cpu_rda = Cpu("RDA8810")
+cpu_rda.IOMUX_ADDRESSES = (RDA_PORTA_IOMUX, RDA_PORTB_IOMUX, RDA_PORTD_IOMUX, RDA_PORTC_IOMUX)
+
+cpu = cpu_rda
+board = board_i96
+#board = board_2g
+
+GPIO_PER_PORT = 32
+
+PAGE_MASK   = ~0xFFF
+PAGE_OFFSET = 0xFFF
+
+SYS_PATH = "/sys/class/gpio/"
+
+def pin_has_export(gpio):
+  return pathlib.Path('/sys/class/gpio/gpio{}'.format(gpio)).exists()
+
+#################### memory access
+
+def mem_set(address, bitmask, value):
+  try:
+    with open("/dev/mem","w+b") as m:
+      mem = mmap(m.fileno(), 32, offset = address & PAGE_MASK)
+      address &= PAGE_OFFSET 
+      data = unpack("<L",mem[address:address+4])[0] 
+      logging.debug("mem_set: current data is {} mask is {} value is {}".format(hex(data),hex(bitmask),value))
+      if value:
+        logging.debug("!! wtf? value is {} {} {}".format(value,bool(value),type(value)))
+        data |= bitmask
+      else:
+        data &= ~bitmask
+      logging.debug("mem_set: resulting data is {} ~mask is {} {}".format(hex(data),hex(~bitmask),hex(data & ~bitmask)))
+      mem[address:address+4] = pack("<L",data)
+  except PermissionError:
+    print("failed to open /dev/mem.....you must execute this script as root")
+
+def mem_get(address, bitmask):
+  try:
+    with open("/dev/mem","r+b") as m:
+      mem = mmap(m.fileno(), 32, offset = address & PAGE_MASK)
+      address &= PAGE_OFFSET 
+      return unpack("<L",mem[address:address+4])[0] & bitmask
+  except PermissionError:
+    print("failed to open /dev/mem.....you must execute this script as root")
+
+
+############################ pin access
+""" decide which kind of GPIO """
+def GPIO_factory(gpio,direct):
+  if direct:
+    return GPIO_DIRECT(gpio)
+  else:
+    return GPIO(gpio)
+
+""" via the gpio/export system """
+class GPIO:
+  ''' this object owns a pin, assures it has an export and accesses it through the gpio-export mechanism'''
+  def __init__(self, gpio):
+    self.gpio = gpio
+    self.set_iomux(True)
+    create_export(gpio)
+
+  def get(self):
+    logging.debug("GPIO.get")
+    # we don't care about the export/direction
+    with open(SYS_PATH + "gpio{}/value".format(self.gpio)) as f:
+      r = int(f.read())
+    logging.debug("pin_read of {} returns {}".format(self.gpio,r))
+    return r
+  def set(self, value):
+    logging.debug("GPIO.set {} {}".format(self.gpio, value))
+    self.set_mode('out')
+    with open(SYS_PATH + "gpio{}/value".format(self.gpio),"w+b") as f:
+      f.write(bytes(str(value),"utf-8"))
+
+  def set_mode(self,mode_string):
+    with open('/sys/class/gpio/gpio{}/direction'.format(self.gpio),'w') as f:
+      f.write(mode_string) 
+  def get_mode_value(self):
+    with open('/sys/class/gpio/gpio{}/direction'.format(self.gpio)) as f:
+      return f.read().strip(), self.get()
+
+  def set_iomux(self, gpio_mode):
+    ''' set value=True for this pin to be GPIO, 
+       False for this pin to be special function'''
+    logging.debug("GPIO: set iomux for pin {} to {}".format(self.gpio,gpio_mode))
+    port_group = self.gpio // GPIO_PER_PORT
+    port_offset = self.gpio % GPIO_PER_PORT
+    mem_set(cpu.IOMUX_ADDRESSES[port_group], 1 << port_offset, gpio_mode)
+    if gpio_mode == False:
+      logging.debug("GPIO.set_iomux with mode {}".format(gpio_mode))
+      remove_export(self.gpio)
+  def get_iomux(self):
+    ''' returns 1 when it's in GPIO mode, 0 for alt mode '''
+    logging.debug("GPIO check the IOMUX for gpio {}".format(self.gpio))
+    port_group = self.gpio // GPIO_PER_PORT
+    port_offset = self.gpio % GPIO_PER_PORT
+    logging.debug("check IOMUX at address {}".format(hex(cpu.IOMUX_ADDRESSES[port_group])))
+    return mem_get(cpu.IOMUX_ADDRESSES[port_group], 1 << port_offset)
+  
+  def __repr__(self):
+    return("gpio"+str(self.gpio))       
+
+class GPIO_DIRECT(GPIO):
+  def __init__(self,gpio):
+    self.gpio = gpio
+
+  def set(self,value):
+    logging.debug("GPIO_DIRECT.set {} value: {}".format(self.gpio,value))
+    if self.get_iomux()==0:
+      self.set_iomux(1)
+      self.set_mode('out')
+    low_level_write(self.gpio,IO_DATA,value)
+  def get(self):
+    if self.get_iomux() == 0:
+      self.set_iomux(1)
+      self.set_mode('in')
+    return low_level_read(self.gpio,IO_DATA)
+
+  def get_mode_value(self):
+    iomux = self.get_iomux() 
+    if not iomux:
+      return 'alt','?' 
+    d = low_level_read(self.gpio, IO_DIR)
+    m = 'in' if d else 'out'
+    if not pin_has_export(self.gpio):
+       m = m+'*'
+    return m, low_level_read(self.gpio,IO_DATA)  
+
+  def set_mode(self, mode_string):
+    low_level_set_mode(self.gpio, mode_string);
+
+  def set_iomux(self, gpio_mode):
+    super().set_iomux(gpio_mode)
+
+def create_export(gpio):
+  ''' make sure this gpio has an export set in the /sys/class/gpio dir '''
+  if not pin_has_export(gpio):
+    with open(SYS_PATH + 'export','w') as f:
+      f.write(str(gpio))
+    if not pin_has_export(gpio):
+      print("could not create the gpio-export for pin {}".format(gpio))
+      print("perhaps you should run this program as root")
+      sys.exit(1)
+  return
+
+def remove_export(gpio):
+  if pin_has_export(gpio):
+    with open(SYS_PATH + 'unexport','w') as f:
+      f.write(str(gpio))
+  return
+ 
+################## low level pin control ################
+""" bypass the gpio/export system """ 
+PORTA_IOBASE = 0x20930000
+PORTB_IOBASE = 0x20931000
+PORTC_IOBASE = 0x11a08000
+PORTD_IOBASE = 0x20932000
+IOBASE_ADDRESSES = (PORTA_IOBASE, PORTB_IOBASE, PORTD_IOBASE, PORTC_IOBASE)
+
+IO_DIR = 0   # [0=out,1=in] +0 direct access, +4 for the clr-reg, +8 for the set-reg 
+IO_DATA = 0xc # +0 direct access, +4 for set-reg, +8 for clr-reg
+
+def low_level_read(gpio, address_offset):
+  port_group = gpio // GPIO_PER_PORT
+  port_offset = gpio % GPIO_PER_PORT
+  address = IOBASE_ADDRESSES[port_group] + address_offset
+  r = mem_get(address, 1 << port_offset)
+  logging.debug("low_level_read of address {} and bit {} returns {}".format(hex(address),port_offset,hex(r)))
+  return 1 if r else 0
+
+def low_level_write(gpio, address_offset, data):
+  port_group = gpio // GPIO_PER_PORT
+  port_offset = gpio % GPIO_PER_PORT
+  address = IOBASE_ADDRESSES[port_group] + address_offset
+  mem_set(address, 1 << port_offset, data)
+  
+def low_level_set_mode(gpio, mode):
+  if mode == 'alt':
+    print("illegal call to low_level_set_mode")
+    sys.exit(1)
+  low_level_write(gpio, IO_DIR, 0 if mode=='out' else 1)
+  
+def low_level_get_mode(gpio):
+  return low_level_read(gpio, IO_DIR)  
+
+
+########################### actions #######################
+
+def do_readall():
+    exports_dirty = False
+    print("""
++-----+-----+----------+------+-+ {} +-+------+----------+-----+-----+
+| gpio| alt | i96 Name | Mode | V | Physical | V | Mode | i96 Name | alt | gpio|
++-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+""".format(
+       board.short_name))
+    for i in range(20):
+      left_gpio = board.PINS[2*i][2]
+      if left_gpio != -1:
+        left_g = GPIO_DIRECT(left_gpio)
+        left_mode, left_value = left_g.get_mode_value()
+        left_gpio_str = str(left_gpio) 
+        if '*' in left_mode: exports_dirty = True
+      else:
+        left_gpio_str = left_mode = left_value = ""      
+
+      right_gpio = board.PINS[2*i+1][2]
+      if right_gpio != -1:
+        right_g = GPIO_DIRECT(right_gpio)
+        right_mode, right_value = right_g.get_mode_value()
+        right_gpio_str = str(right_gpio) 
+        if '*' in right_mode: exports_dirty = True
+      else:
+        right_gpio_str = right_mode = right_value = ""
+      left_value,right_value = str(left_value),str(right_value)
+
+      print("| {:3s} | {:4s}| {:9s}| {:4s} | {:1s} | {:2d} || {:2d} | {:1s} | {:4s} | {:9s}| {:4s}| {:3s} |".format(
+        left_gpio_str,board.PINS[2*i][3],board.PINS[2*i][0],
+        left_mode,left_value,2*i+1,
+        2*i+2,right_value,right_mode,board.PINS[2*i+1][0],
+        board.PINS[2*i+1][3],right_gpio_str))
+    print("+-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+")
+    if exports_dirty:
+      print("Note: *these pins are set to GPIO mode but do NOT have exports in /sys/class/gpio")
+
+def do_exports():
+  print("GPIO Pins  Mode Value  Located")
+  for f in os.listdir(SYS_PATH):
+    if re.match('gpio\d',f):
+      gpio = GPIO(int(f[4:]))
+      m,v = gpio.get_mode_value()
+      iop = ""
+      for i in range(len(board.PINS)):
+        if board.PINS[i][2]==gpio.gpio: 
+          iop = (board.short_name + " I/O: {}").format(i+1)
+          break
+      for i in range(len(board.other)):
+        if board.other[i][2]==gpio:
+          iop = board.other[i][0]
+          break
+      print("{:>9s}  {:3}  {:<6} {}".format(f,m,v,iop))
+
+def do_leds():
+    print("+------+-------+------+-----+")
+    print("| gpio | func  | mode |value|")
+    print("+------+-------+------+-----+")
+    for p in board.other:
+      #print("led function:", p)
+      g = GPIO_DIRECT(p[2])
+      m,v = g.get_mode_value()
+      print("| {:3}  | {:6}| {:4} |  {:1}  |".format(
+        p[2],p[0],m,v))
+    print("+------+-------+------+-----+")
+
+def do_read():
+  logging.debug("do_read: %d",args.gpio)
+  gpio = args.gpio
+  g = GPIO_factory(gpio,args.direct)
+  print(g.get())
+
+def do_write():
+  logging.debug("do_write: %d %s",args.gpio, args.extra)
+  gpio = args.gpio
+  g = GPIO_factory(gpio, args.direct)
+  if args.extra in ('1', 'on', 'ON'):
+    v = 1
+  elif args.extra in ('0', 'off', 'OFF'):
+    v = 0
+  else:
+    print("the 'write' command requires a 2nd argument, either 0 or 1")
+    sys.exit(1)
+  g.set(v)
+
+def do_mode():
+  logging.info("do_mode %d %s", args.gpio, args.extra)
+  logging.info("the -d flag is {}".format(args.direct))
+  if not args.gpio or args.gpio<0 or args.gpio>127:
+    print("the {} command requires a gpio number".format(args.cmd))
+    sys.exit(1)
+
+  if args.extra == None:
+    m,v = GPIO_DIRECT(args.gpio).get_mode_value()
+    print(m)    
+    return
+
+  if args.extra == "alt": 
+    args.direct = True
+  gpio = GPIO_factory(args.gpio,args.direct)
+  if args.extra in ('in','out'):
+    logging.info("set mode: %s",args.extra)
+    gpio.set_iomux(True)
+    gpio.set_mode(args.extra)
+  elif args.extra=='alt':
+    logging.info("set alt mode")
+    gpio.set_iomux(False)
+
+def do_readallx():
+    exports_dirty = False
+    print("""
++-----+-----+----------+------+-+ {} +-+------+----------+-----+-----+
+| gpio| RDA | alt name | Mode | V | Physical | V | Mode | alt name | RDA | gpio|
++-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+""".format(
+       board.short_name))
+    for i in range(20):
+      left_gpio = board.PINS[2*i][2]
+      if left_gpio != -1:
+        left_g = GPIO_DIRECT(left_gpio)
+        left_mode, left_value = left_g.get_mode_value()
+        left_gpio_str = str(left_gpio) 
+        if '*' in left_mode: exports_dirty = True
+      else:
+        left_gpio_str = left_mode = left_value = ""      
+
+      right_gpio = board.PINS[2*i+1][2]
+      if right_gpio != -1:
+        right_g = GPIO_DIRECT(right_gpio)
+        right_mode, right_value = right_g.get_mode_value()
+        right_gpio_str = str(right_gpio) 
+        if '*' in right_mode: exports_dirty = True
+      else:
+        right_gpio_str = right_mode = right_value = ""
+      left_value,right_value = str(left_value),str(right_value)
+
+      print("| {:3s} | {:4s}| {:9s}| {:4s} | {:1s} | {:2d} || {:2d} | {:1s} | {:4s} | {:9s}| {:4s}| {:3s} |".format(
+        left_gpio_str,board.PINS[2*i][1],board.PINS[2*i][4],
+        left_mode,left_value,2*i+1,
+        2*i+2,right_value,right_mode,board.PINS[2*i+1][4],
+        board.PINS[2*i+1][1],right_gpio_str))
+    print("+-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+")
+    if exports_dirty:
+      print("Note: *these pins are set to GPIO mode but do NOT have exports in /sys/class/gpio")
+    print("Note: the alt names are based on the Linux naming scheme")
+
+args = None
+
+if __name__ == "__main__":
+  #logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+  #logging.basicConfig(stream=sys.stderr, level=logging.INFO)
+  parser = argparse.ArgumentParser()
+  parser.add_argument('-v','--version', action='version', version='gpio 2.0\n   python rewrite by Pat Beirne')
+  parser.add_argument('cmd',help="one of: read, write, mode, readall, readallx")
+  parser.add_argument('gpio',nargs='?',type=int,help="gpio number 0...126")
+  parser.add_argument('extra',nargs='?')
+  parser.add_argument('-d',"--direct",help="use low-level access",action="store_true")
+  parser.add_argument('-2',"--op2giot",help="configure for OrangePi 2G-iot",action="store_true")
+  args = parser.parse_args()
+
+  if pathlib.Path('/etc/OrangePi_2G_IOT').exists():
+    board = board_2g
+  if args.op2giot:
+    board = board_2g
+ 
+  switcher = {"readall":do_readall, 
+         "readallx":do_readallx,
+         "status":do_readall, 
+         "statusx":do_readallx, 
+         "leds":do_leds,
+         "mode":do_mode,
+         "read":do_read,
+         "write":do_write,
+         "exports":do_exports}
+  f = switcher.get(args.cmd)
+  if not f:
+    print("the available commands are:", ", ".join(sorted(list(switcher.keys()))))
+    print("function",args.cmd,"not found")
+    sys.exit(1)
+  f()
+
+  

+ 221 - 0
opio.1

@@ -0,0 +1,221 @@
+.\" Automatically generated by Pandoc 2.9.2.1
+.\"
+.TH "OPIO" "1" "Aug 2021" "opio v1.0" ""
+.hy
+.SH NAME
+.PP
+opio - Control GPIO pins on OrangePi.
+A replacement for WiringPi
+.SH SYNOPSIS
+.PP
+\f[B]opio\f[R] [-2] readall
+.PP
+\f[B]opio\f[R] [-2] readallx
+.PP
+\f[B]opio\f[R] [-2] exports
+.PP
+\f[B]opio\f[R] leds
+.PP
+\f[B]opio\f[R] mode \f[I]pin\f[R] [ in | out | alt ]
+.PP
+\f[B]opio\f[R] [ -d ] read \f[I]pin\f[R]
+.PP
+\f[B]opio\f[R] [ -d ] write \f[I]pin\f[R] [ 1 | 0 | on | off ]
+.SH DESCRIPTION
+.PP
+\f[B]opio\f[R] allows access to the GPIO pins of OrangePi single-board
+computers.
+This version is designed speciifically for the
+.IP \[bu] 2
+OrangePi i96
+.IP \[bu] 2
+OrangePi 2G-iot
+.PP
+Running \f[B]opio\f[R] without any parameters will show its usage.
+\f[B]opio\f[R] requires `su' permissions, so must be run as `root' or
+via `sudo'.
+.SH COMMANDS
+.TP
+\f[B]readall\f[R]
+Displays the state of the gpio pins in a grid format.
+The list includes all the pins used in the on-board 40 pin connector.
+For each pin, the listing shows the gpio pin number, its alternate
+function, its i96 pin name, its current \f[I]mode\f[R] and
+\f[I]value\f[R], and the corresponding pin number on the 40 pin
+connector.
+.TP
+\f[B]readallx\f[R]
+Creates a similar chart, but includes the RDA pin names.
+.TP
+\f[B]leds\f[R]
+Creates a smaller chart, for the interesting I/O pins which are
+\f[I]not\f[R] part of the 40 pin connector.
+On the i96 board, there are 3 LEDs which can be controlled via
+\f[B]opio\f[R]
+.TP
+\f[B]exports\f[R]
+Print a list of entries in /sys/class/gpio, indicating which pins have
+been exported (prepared for read/write).
+If a gpio pin exists on the 40 pin connector, the pin number is listed.
+.TP
+\f[B]mode\f[R]
+Sets the \f[I]mode\f[R] for a pin as either `in', `out' or
+`alternate-function'.
+Normally \f[B]opio\f[R] will create an export for this gpio pin, and
+then set the direction.
+If you set the `alt' function, the export will be removed.
+With the \f[B]-d\f[R] option, the export is not created, but the
+`in'/`out'/`alt' \f[I]mode\f[R] setting will still be done.
+\f[B]mode\f[R] with a pin number and no set-mode request, will simply
+return the current \f[I]mode\f[R] (in, out, alt, in*, alt*).
+.TP
+\f[B]read\f[R]
+Returns the current \f[I]value\f[R] (1/0) of the gpio pin, if possible.
+If the pin is in `alt' \f[I]mode\f[R], it is changed to `in' before the
+\f[I]value\f[R] is read.
+.TP
+\f[B]write\f[R]
+Attempts to write the given \f[I]value\f[R] into the given pin.
+If the pin is in `alt' \f[I]mode\f[R], it is changed to `out' before the
+\f[I]value\f[R] is asserted.
+.SH OPTIONS
+.TP
+\f[B]-d\f[R]
+Use low-level access to control the pins.
+Without the \f[B]-d\f[R] option, \f[B]opio\f[R] tries to use the
+`export' gpio mechanism (at /sys/class/gpio).
+The \f[B]-d\f[R] option only applies to \f[B]mode, read\f[R] and
+\f[B]write\f[R]
+.TP
+\f[B]-2\f[R]
+Use the pin assignments for the OrangePi-2G-iot.
+The default is to use the pins for the OrangePi-i96.
+If you wish to make this option persistent, create a file named
+/etc/OrangePi_2G_IOT.
+.SS Mode
+.PP
+The microcontroller used on these boards presents i/o pins which can be
+set to `general usage' GPIO as `input' or `output, or alternatively, set
+to operate in a specific way (uart, i2c, i2s, pcm, etc). \f[B]opio\f[R]
+refers the the current \f[I]mode\f[R] of each pin one of these: either
+GPIO \[cq]in', GPIO `out', or \[cq]alt\[cq]ernate function.
+.PP
+The `in*' or `out*' are marked with an asterisk to indicate that the pin
+is in GPIO \f[I]mode\f[R], but \f[I]not\f[R] listed in the current
+`export' list.
+.PP
+As a convenience, the \f[B]read\f[R] and \f[B]write\f[R] commands will
+automatically set the \f[I]mode\f[R] on the GPIO pin and create an
+export.
+So, generally, the \f[B]mode\f[R] command is only \f[I]required\f[R] if
+you wish to change a pin back to its `alt' function.
+.SH HERITAGE
+.PP
+This program is styled after the \f[B]gpio\f[R] program written by
+Gordon Henderson for the Raspberry Pi.
+Unlike the original \f[B]gpio\f[R] program, this one does not implement:
+.IP \[bu] 2
+export\&...\&...\&.....exports are created automatically by \f[B]mode,
+read\f[R] and \f[B]write\f[R]
+.IP \[bu] 2
+pwm, clk\&...\&...\&...this microcontroller does not have PWM or CLK
+type pins
+.IP \[bu] 2
+aread, awrite\&....these boards have not connected and ADC/DAC pins
+.SH NUMBERING
+.PP
+There are 4 naming schemes used to identify pins in these boards.
+\f[B]opio\f[R] uses exclusively the Linux gpio device driver numbers,
+the first on this list:
+.IP \[bu] 2
+Linux gpio numbers (from /sys/class/gpio)
+.IP \[bu] 2
+I/O connector pin numbers (1-40)
+.IP \[bu] 2
+RDA microcontroller pin names (like GPIOA_C23)
+.IP \[bu] 2
+i96 pin names (like GPIOB)
+.PP
+You can explore the correspondence between these naming schemes with
+\f[B]opio readall\f[R] and \f[B]opio readallx\f[R]
+.SH HIGH LEVEL/LOW LEVEL ACCESS
+.PP
+Normally, \f[B]opio\f[R] will access the GPIO pins through the Linux
+gpio device driver, and the corresponding files at
+\f[C]/sys/class/gpio\f[R].
+This is \f[I]high level\f[R] access.
+.PP
+If you wish to bypass the Linux gpio driver, add the \f[C]-d\f[R] option
+to the \f[B]opio\f[R] command line and the reads/writes will be done at
+a \f[I]low level\f[R], directly on the machine\[cq]s registers.
+.PP
+The commands \f[B]readall, readallx\f[R] and \f[B]leds\f[R] are always
+done using \f[I]low level\f[R] access.
+The \f[B]exports\f[R] command is always done with \f[I]high level\f[R]
+commands.
+.PP
+Be careful about making changed with the \f[B]-d\f[R] option.
+Some linux gpio drivers will direction and value, so changes you make
+with the \f[B]-d\f[R] option may not be reflected in the export folder.
+.SH EXAMPLES
+.PP
+display a chart of the pin assignments of the 40 pin connector:
+.IP
+.nf
+\f[C]
+opio readall     
+\f[R]
+.fi
+.PP
+list the currently `exported' pins:
+.IP
+.nf
+\f[C]
+opio exports
+\f[R]
+.fi
+.PP
+set gpio 15 to an output; also create an export for gpio15 \&...then
+flash the pin (gpio15 Linux number):
+.IP
+.nf
+\f[C]
+opio mode 15 out    
+opio write 15 on
+sleep 2
+opio write 15 off   
+\f[R]
+.fi
+.PP
+set gpio 15 to an output; do \f[I]not\f[R] create an export
+.IP
+.nf
+\f[C]
+opio -d mode 15 out 
+\f[R]
+.fi
+.PP
+write to gpio15 pin, bypassing the export mechanism
+.IP
+.nf
+\f[C]
+opio -d write 15 on  
+\f[R]
+.fi
+.PP
+\f[B]NOTE\f[R] The more recent /dev/gpio driver is not yet available on
+these boards, since they\[cq]re running the 3.xx kernels.
+.PP
+\f[B]NOTE\f[R] The 2G-IOT board uses the I2C1 bus to communicate with
+the modem chip.
+This is i2c-0 in the kernel, and is pins 3 & 5 on the 40 pin connector.
+Do not use these pins to connect to peripherals.
+And do not use \f[B]opio\f[R] to modify the \f[I]mode\f[R] of these
+pins.
+.PP
+\f[B]NOTE\f[R] The 2G-IOT board uses the I2C3 bus to communicate with
+the LCD.
+If you are using an LCD in the socket, do \f[I]not\f[R] change the mode
+on pins 38 & 40.
+.SH AUTHORS
+Pat Beirne <patb@pbeirne.com>.