VMAC PiHat v2 Repeater Logic

VMAC PiHat V2 was originally designed as a ATV repeater controller and whilst it has seen other applications, the general overview as a controller is shown below.

Typical application:

 

Repeater example code will be developed in Python, but most of which is already available in the Wiki sections.

The below is a very work in progress version of what I am using on GB3JT and testing various functions – however, it may help others so will keep it updated.  You may be able to see the result at:  https://batc.org.uk/live/gb3jt

# HAMKit VMAC PiHat v2.4 - ATV Repeater - Dave Williams
# Hardware: PiHat v2.4, Pi3, EasyCap Video Capture
# Initialise all GPIO inputs, outputs, I2C, SPI and UART
# Reset, DTMF Decoder, Squelch Detect, Video Detect, Matrix, OSD, PTT
# 23rd August 2018.  GB3JT TEST
# Thanks also to Paul Theunissen, PA5PT, for OSD code template
# GB3JT stream can be found at https://batc.org.uk/live/gb3jt
#!/usr/bin/python

#DRAFT!!! VERY WIP ==========================

#========================== | Imports / Libraries | ==========================

import RPi.GPIO as GPIO #GPIO
import smbus #I2C
import spidev #SPI
#import serial #UART

import time #Time
import datetime #RTC
from ctypes import *

#========================== | Definitions | ==========================

#====== GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
#GPIO.cleanup()

#Define GPIO Outputs
GPIO_OSD_RST = 25 # OSD Reset / Enable
GPIO_PTT = 27 # Red LED = PTT & FET
GPIO_Status = 22 # Blue LED = Status
GPIO_User = 17 # Amber LED = User (also defined as RTS)

#Setup Outputs
GPIO.setup(GPIO_OSD_RST,GPIO.OUT)
GPIO.setup(GPIO_PTT,GPIO.OUT)
GPIO.setup(GPIO_Status,GPIO.OUT)
GPIO.setup(GPIO_User,GPIO.OUT)

#Define GPIO Inputs
GPIO_Button = 4 # PCB User Button
GPIO_D0 = 26 # DTMF D0 8870 Data Bit 0
GPIO_D1 = 19 # DTMF D1 8870 Data Bit 1
GPIO_D2 = 13 # DTMF D2 8870 Data Bit 2
GPIO_D3 = 6 # DTMF D3 8870 Data Bit 3
GPIO_DINT = 5 # DTMF DINT 8870 StD Latch Output
GPIO_SQ1 = 12 # PCB I/O 1 User Input SQ1
GPIO_SQ2 = 16 # PCB I/O 1 User Input SQ2
GPIO_SQ3 = 20 # PCB I/O 1 User Input SQ3
GPIO_SQ4 = 21 # PCB I/O 1 User Input SQ4
GPIO_VD_1 = 18 # VideoDetect from Video Input RCA AV1
GPIO_VD_2 = 23 # VideoDetect from Video Input RCA AV2
GPIO_VD_3 = 24 # VideoDetect from Video Input RCA AV3

#Setup Inputs with pull-ups enabled
GPIO.setup(GPIO_Button, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_D0, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_D1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_D2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_D3, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_DINT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_SQ1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_SQ2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_SQ3, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_SQ4, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_VD_1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_VD_2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_VD_3, GPIO.IN, pull_up_down=GPIO.PUD_UP)


#====== I2C Bus (FMS6501 / EEPROM)
DEVICE_BUS = 1 # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
i2cbus = smbus.SMBus(DEVICE_BUS)

DEVICE_ADD_Matrix = 0x43 #0x43 or 0x03 FMS6501 Matrix
DEVICE_ADD_EEPROM = 0x50 #0x50 EEPROM


#====== SPI bus (MAX7456)
#spi = spidev.SpiDev() 


#====== UART
#"/dev/ttyAMA0"  # Raspberry Pi 2
#"/dev/ttyS0"    # Raspberry Pi 3
##UARTSerial = serial.Serial(
##  port='/dev/ttyS0',
##  baudrate = 9600,
##  parity=serial.PARITY_NONE,
##  stopbits=serial.STOPBITS_ONE,
##  bytesize=serial.EIGHTBITS,
##  timeout=1
##)

# sp = serial.Serial ("/dev/tty01")   
# sp.baudrate = 9600                   
# data = sp.read(10)      # 10 characters            
# sp.write(data)          # echo all 10 characters back
# sp.close()  


#====== Define Program Variables
DValueStore = 0
PiHatAVin = {1:'V', 2:'V', 3:'V', 4:'A', 5:'A', 6:'A', 7:'A', 8:'A', 9:'V'}
PiHatAVout = {1:'V', 2:'V', 3:'A', 4:'A'}
dateString = '%H %M %S'

#====== Define User Variables
UserCallsign = "GB3JT"
UserFreqTX = "1318 MHz"
UserFreqRX = "1249 MHz"
UserCTCSS = ""
UserQRA = "JO00HV"
UserNGR = "TQ825120"
UserPostcode = "TN34 2AF"
UserOverChar = "K"
UserTX247 = True

#Define DTMF Symbols
DTMFLookup = ["1","2","3","4","5","6","7","8","9","0","*","#","A","B","C","D"]

#Define Morse Symbols
MorseLookup = {
    'A': '.-',
    'B': '-...',
    'C': '-.-.',
    'D': '-..',
    'E': '.',
    'F': '..-.',
    'G': '--.',
    'H': '....',
    'I': '..',
    'J': '.---',
    'K': '-.-',
    'L': '.-..',
    'M': '--',
    'N': '-.',
    'O': '---',
    'P': '.--.',
    'Q': '--.-',
    'R': '.-.',
    'S': '...',
    'T': '-',
    'U': '..-',
    'V': '...-',
    'W': '.--',
    'X': '-..-',
    'Y': '-.--',
    'Z': '--..',

    '1': '.----',
    '2': '..---',
    '3': '...--',
    '4': '....-',
    '5': '.....',
    '6': '-....',
    '7': '--...',
    '8': '---..',
    '9': '----.',
    '0': '-----',

    '!': '-.-.--',
    '"': '.-..-.',
    '$': '...-..-',
    '&': '.-...',
    '(': '-.--.',
    ')': '-.--.-',
    ',': '--..--',
    '.': '.-.-.-',
    '/': '-..-.',
    '-': '-....-',
    ':': '---...',
    ';': '-.-.-.',
    '?': '..--..',
    '@': '.--.-.',
    '\'': '.----.',
    '_': '..--.-',
    '+': '.-.-.',
    '=': '-...-'

}

#========================== | Initialise | ==========================

#====== GPIO 
GPIO.output(GPIO_OSD_RST,GPIO.HIGH) #Enable OSD Reset High

#====== Serial UART 
#UARTSerial.write("VMAC PiHat")

#Initiate LEDs
GPIO.output(GPIO_Status,GPIO.HIGH)
GPIO.output(GPIO_User,GPIO.HIGH)
GPIO.output(GPIO_PTT,GPIO.HIGH)
time.sleep(1)
GPIO.output(GPIO_Status,GPIO.LOW)
GPIO.output(GPIO_User,GPIO.LOW)
if UserTX247 == False:
    GPIO.output(GPIO_PTT,GPIO.LOW)
    
time.sleep(1)

#Read 8870 StD Latch Output
DINT = GPIO.input(GPIO_DINT)

#========================== | Routines | ==========================

def PowerDownAllPorts():
    print ("PowerDownAllPorts")
    i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x01, 0x0) #Mute
    time.sleep(.01)
    i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x02, 0x0) #Mute
    time.sleep(.01)    
    i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x03, 0x0) #Mute
    time.sleep(.01)
    i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x04, 0x0) #Mute
    time.sleep(.01)
    i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x05, 0x0) #Mute
    time.sleep(.01)
    i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x06, 0x0) #Mute
    time.sleep(.01)
    i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x07, 0x0) #Mute
    time.sleep(.01)
    i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x08, 0x0) #Mute
    time.sleep(.01)	
    i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x09, 0x0) #Mute
    time.sleep(.01)                  

def ReadDTMF():
    #Check for High Pulse from the 8870 StD Latch Output
    if DINT == True:
        
        #Only Check GPIO if 8870 Data Bits have changed 
        D0 = GPIO.input(GPIO_D0)
        D1 = GPIO.input(GPIO_D1)
        D2 = GPIO.input(GPIO_D2)
        D3 = GPIO.input(GPIO_D3)
        DValue = D0+(D1*2)+(D2*4)+(D3*8)
        
        #Only Display if DTMF Value has changed
        if DValueStore != DValue:
            DValueStore = DValue
        
            #Print Output
            print ("DTMF Value: %02d \tSymbol: " % (DValue) + DTMFLookup[DValue - 1])
            
            
#========================== | Main | ==========================

#Disable and Powerdown All Matrix Outputs
PowerDownAllPorts()

# Setup Matrix Video Switching
#i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x1d, 0xff) #clamp
#i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x1e, 0xff) #clamp
#i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x07, 0x82) #Input 1 to OSD In
i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x01, 0x86) #Input OSD to Output 1

#OSD MAX7456
class max7456():
    
    # Create SPI bus (MAX7456)
    spi = spidev.SpiDev() 

    # MAX7456 opcodes
    DMAH     = 0x05
    DMAL     = 0x06
    DMDI     = 0x07
    DMM_reg  = 0x04
    HOS_reg  = 0x02
    HOS_reg  = 0x02
    OSDM     = 0x0C
    RB0      = 0x10
    STATUS   = 0xA0
    VM0_reg  = 0x00
    VM1_reg  = 0x01
    VOS_reg  = 0x03

    # PAL - VM0_reg commands
    ENABLE_display      = 0x48
    ENABLE_display_vert = 0x4c
    MAX7456_reset       = 0x42
    DISABLE_display     = 0x40

    # Read command
    READ = 0x80
    MAX_screen_rows = 16

    # White levels
    WHITE_level_80  = 0x03
    WHITE_level_90  = 0x02
    WHITE_level_100 = 0x01
    WHITE_level_120 = 0x00

    chars = {' ':0, '1':1, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9,
        '0':10, 'A':11, 'B':12, 'C':13, 'D':14, 'E':15, 'F':16, 'G':17, 'H':18, 'I':19,
        'J':20, 'K':21, 'L':22, 'M':23, 'N':24, 'O':25, 'P':26, 'Q':27, 'R':28, 'S':29,
        'T':30, 'U':31, 'V':32, 'W':33, 'X':34, 'Y':35, 'Z':36, 'a':37, 'b':38, 'c':39,
        'd':40, 'e':41, 'f':42, 'g':43, 'h':44, 'i':45, 'j':46, 'k':47, 'l':48, 'm':49,
        'n':50, 'o':51, 'p':52, 'q':53, 'r':54, 's':55, 't':56, 'u':57, 'v':58, 'x':59,
        'y':60, 'z':61, '(':62, ')':63, '.':64, '?':65, ';':66, ':':67, ',':68, '\'':69,
        '/':70, '"':71, '-':72, '<':73, '>':74, '@':75, '\xa9':76
    }

    def __init__(self):
        # Open a SPI port - max7456 connected on SPI0
        self.spi.open(0, 0)
        self.spi.max_speed_hz = 1000000
        self.spi.bits_per_word = 8
        self.spi.cshigh = False
        self.spi.lsbfirst = False
        self.spi.mode = 0
  
        # On init, reset max7456
        self.reset()

        # Set all rows at the same white level
        for x in range (0, self.MAX_screen_rows):
          self.spi.xfer2([(self.RB0 + x), self.WHITE_level_90])

        # Enable max7456
        self.spi.xfer2([self.VM0_reg, self.ENABLE_display]);

    def printStr(self, X, Y, string, enable = True):
        disp = []
        for char in string:
                 disp.append(self.chars[char])

        print (string)

        if enable == False:
            self.spi.xfer([self.VM0_reg, self.Disable_display])
        
        # Enable 8 bit mode:
        dmm = self.spi.xfer2([self.DMM_reg + self.READ, 0x00])
        dmm = self.setBit(dmm[1], 6)
        self.spi.xfer2([self.DMM_reg, dmm])

        start = X * 30 + Y
        
        # Clear position
        self.spi.xfer2([self.DMAH, 0x00])
        self.spi.xfer2([self.DMAL, 0x00])

        for char in disp:
            # Write char
            dmah = self.spi.xfer2([self.DMAH + self.READ, 0x00])
            dmah = self.clearBit(dmah[1], 1)
            self.spi.xfer2([self.DMAH, dmah])

            dmah_pos = ((start >> 8) & 0x01)
            dmal = (start & 0xff)
            dmah = dmah | dmah_pos
            start = start + 1

            # Select MSB
            self.spi.xfer2([self.DMAH, dmah])
            self.spi.xfer2([self.DMAL, dmal])
        
            self.spi.xfer2([self.DMDI, (char)])

    def reset(self):
        self.spi.xfer2([self.VM0_reg, self.MAX7456_reset])
        time.sleep(0.1)
        while True:
            r = self.spi.xfer([self.STATUS, 0x00])
            stable = self.testBit(r[1], 1)
            if stable == 0:
                print ("Reset MAX7456 Ok...")
                break 
            break

    def testBit(self, value, offset):
        mask = 1 << value
        return(value & mask)
 
    def setBit(self, value, offset):
        mask = 1 << offset
        return(value + mask)

    def clearBit(self, int_type, offset):
        mask = ~(1 << offset)
        return(int_type & mask)
    
try:
    max7456 = max7456()
    
    #Display OSD Text
    max7456.printStr(2,10, "Hastings ATV", enable = True)
    #max7456.printStr(5,5, "OSD Test ABCDEFGHIJ", enable = True)
    #max7456.printStr(9,1, "1234567890123456789012345678", enable = True)
    max7456.printStr(13,3, "GB3JT VMAC PiHat Testing", enable = True)
    
except KeyboardInterrupt:
    spi.close()
    
    
i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x07, 0x85) #Input 3 to OSD In

#========================== | LOOP | ==========================#

#Loop
while True:
    
    VideoDetect_1 = GPIO.input(GPIO_VD_1)
    VideoDetect_2 = GPIO.input(GPIO_VD_2)
    VideoDetect_3 = GPIO.input(GPIO_VD_3)
    
       
    if VideoDetect_1 == True:
        print ("VideoDetect_1 23cm FM Analogue")
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x07, 0x81) #Input 1 to OSD In
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x06, 0x87) #Input 4 to TX A 
        max7456.printStr(14,28, "1", enable = True)
        time.sleep(5)
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x06, 0x00) #Mute
    
    elif VideoDetect_2 == True:
        print ("VideoDetect_2 6cm FM Analogue")
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x07, 0x83) #Input 2 to OSD In
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x06, 0x88) #Input 5 to TX A 
        max7456.printStr(14,28, "2", enable = True)
        time.sleep(5)
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x06, 0x00) #Mute        
    
    elif VideoDetect_3 == False:
        print ("VideoDetect_3 23cm Digital")
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x07, 0x85) #Input 3 to OSD In
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x06, 0x89) #Input 6 to TX A 
        max7456.printStr(14,28, "3", enable = True)
        time.sleep(5)
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x06, 0x00) #Mute        

    else:
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x07, 0x85) #Input 2 to OSD In
        i2cbus.write_byte_data(DEVICE_ADD_Matrix, 0x06, 0x00) #Mute
        max7456.printStr(14,28, "0", enable = True)        

    
    #Heatbeat User LED
    GPIO.output(GPIO_User,GPIO.HIGH)
    time.sleep(.2)
    GPIO.output(GPIO_User,GPIO.LOW)
    time.sleep(1)
    max7456.printStr(14,1, datetime.datetime.now().strftime(dateString), enable = True)     


Please feel free to share our product love through the below