Updates for initial timer and pwm test on piccadae.

This commit is contained in:
kevin.larke 2019-08-03 19:42:59 -04:00
parent ffd46bf3b6
commit de586ca2e3
6 changed files with 832 additions and 268 deletions

270
control/app/picadae_api.py Normal file
View File

@ -0,0 +1,270 @@
import os,sys,argparse,yaml,types,select,serial,logging,time,datetime
from enum import Enum
from multiprocessing import Process, Pipe
# Message header id's for messages passed between the application
# process and the microcontroller and video processes
TinyOpD = {
'setPwmOp': 0,
'noteOnVelOp': 1,
'noteOnUsecOp': 2,
'noteOffOp': 3,
'readOp': 4,
'writeOp': 5
}
class TinyRegAddr(Enum):
kRdRegAddrAddr = 0,
kRdTableAddrAddr = 1,
kRdEEAddrAddr = 2,
kRdSrcAddr = 3,
kWrRegAddrAddr = 4,
kWrTableAddrAddr = 5,
kWrEEAddrAddr = 6,
kWrDstAddr = 7,
kTmrCoarseAddr = 8,
kTmrFineAddr = 9,
kTmrPrescaleAddr = 10,
kPwmEnableAddr = 11,
kPwmDutyAddr = 12,
kPwmFreqAddr = 13,
kModeAddr = 14,
kStateAddr = 15,
kErrorCodeAddr = 16
class TinyConst(Enum):
kRdRegSrcId = TinyRegAddr.kRdRegAddrAddr, # 0
kRdTableSrcId = TinyRegAddr.kRdTableAddrAddr, # 1
kRdEESrcId = TinyRegAddr.kRdEEAddrAddr # 2
kWrRegDstId = TinyRegAddr.kWrRegAddrAddr, # 4
kWrTableDstId = TinyRegAddr.kWrTableAddrAddr, # 5
kWrEEDstId = TinyRegAddr.kWrEEAddrAddr, # 6
kWrAddrFl = 0x08, # first avail bit above kWrEEAddr
class SerialMsgId(Enum):
QUIT_MSG = 0xffff
DATA_MSG = 0xfffe
def _serial_process_func( serial_dev, baud, pipe ):
reset_N = 0
drop_N = 0
noSync_N = 0
with serial.Serial(serial_dev, baud) as port:
while True:
# get the count of available bytes in the serial port buffer
bytes_waiting_N = port.in_waiting
# if no serial port bytes are available then sleep ....
if bytes_waiting_N == 0:
time.sleep(0.01) # ... for 10 ms
else: # read the serial port ...
v = port.read(bytes_waiting_N)
pipe.send((SerialMsgId.DATA_MSG,v)) # ... and send it to the parent
msg = None
if pipe.poll(): # non-blocking check for parent process messages
try:
msg = pipe.recv()
except EOFError:
break
# if an incoming message was received
if msg != None:
# this is a shutdown msg
if msg[0] == SerialMsgId.QUIT_MSG:
pipe.send(msg) # ... send quit msg back
break
# this is a data xmit msg
elif msg[0] == SerialMsgId.DATA_MSG:
port.write(msg[1])
class SerialProcess(Process):
def __init__(self,serial_dev,serial_baud):
self.parent_end, child_end = Pipe()
super(SerialProcess, self).__init__(target=_serial_process_func, name="Serial", args=(serial_dev,serial_baud,child_end,))
self.doneFl = False
def quit(self):
# send quit msg to the child process
self.parent_end.send((SerialMsgId.QUIT_MSG,0))
def send(self,msg_id,value):
# send a msg to the child process
self.parent_end.send((msg_id,value))
def recv(self):
#
x = None
if not self.doneFl and self.parent_end.poll():
x = self.parent_end.recv()
if x[0] == SerialMsgId.QUIT_MSG:
self.doneFl = True
return x
def is_done(self):
return self.doneFl
class Picadae:
def __init__( self, key_mapL, i2c_base_addr=1, serial_dev='/dev/ttyACM0', serial_baud=38400 ):
"""
key_mapL = [{ index, board, ch, type, midi, class }]
serial_dev = /dev/ttyACM0
serial_baud = 38400
i2c_base_addr = 1
"""
self.serialProc = SerialProcess( serial_dev, serial_baud )
self.keyMapD = { d['midi']:d for d in key_mapL }
self.i2c_base_addr = i2c_base_addr
self.prescaler_usec = 32
self.serialProc.start()
def close( self ):
self.serialProc.quit()
def write( self, i2c_addr, reg_addr, byteL ):
return self._send( 'w', i2c_addr, reg_addr, [ len(byteL) ] + byteL )
def set_read_addr( self, i2c_addr, src, addr ):
return self. write(i2c_addr, TinyOpD['readOp'], src, addr )
def set_reg_read_addr( self, i2c_addr, addr ):
return self.set_read_addr(i2c_addr, TinyRegAddr.kRdRegAddrAddr, addr )
def set_table_read_addr( self, i2c_addr, addr ):
return self.set_read_addr(i2c_addr, TinyRegAddr.kRdTableAddrAddr, addr )
def set_eeprom_read_addr( self, i2c_addr, addr ):
return self.set_read_addr(i2c_addr, TinyRegAddr.kRdEEAddrAddr, addr )
def read_request( self, i2c_addr, reg_addr, argL ):
return self._send( 'r', i2c_addr, reg_addr, [ byteOutN, len(argL) ] + argL )
def read_poll( self ):
return self.serialProc.recv()
def read_block( self, i2c_addr, reg_addr, argL, byteOutN, time_out_ms ):
self.read_request( self, i2c_addr, reg_addr, argL, byteOutN )
ts = datetime.datetime.now() + datetime.timeDelta(milliseconds=time_out_ms)
retL = []
while datetime.datetime.now() < ts and len(retL) < byteOutN:
x = self.serialProc.recv()
if x is not None:
retL.append(x)
time.sleep(0.01)
return retL
def note_on_vel( self, midi_pitch, midi_vel ):
return self.write( self._pitch_to_i2c_addr( midi_pitch ),
TinyOpD['noteOnVelOp'],
[self._validate_vel(midi_vel)] )
def note_on_us( self, midi_pitch, pulse_usec ):
return self.write( self._pitch_to_i2c_addr( midi_pitch ),
TinyOpD['noteOnUsecOp'],
list(self._usec_to_coarse_and_fine(pulse_usec)) )
def note_off( self, midi_pitch ):
return self.write( self._pitch_to_i2c_addr( midi_pitch ),
TinyOpD['noteOffOp'],[0] ) # TODO: sending a dummy byte because we can handle sending a command with no data bytes.
def set_velocity_map( self, midi_pitch, midi_vel, pulse_usec ):
pass
def get_velocity_map( self, midi_pitch, midi_vel, time_out_ms=250 ):
pass
def set_pwm_duty( self, midi_pitch, duty_cycle_pct, enableFl=True ):
return self.write( self._pitch_to_i2c_addr( midi_pitch ),
TinyOpD['setPwmOp'],
[enableFl, int( duty_cycle_pct * 255.0 /100.0 )])
def get_pwm_duty( self, midi_pitch, time_out_ms=250 ):
return self.read_block( self._pitch_to_i2c_addr( midi_pitch ),
TinyRegAddr.kPwmDutyAddr,
[], 1, time_out_ms )
def set_pwm_freq( self, midi_pitch, freq_div_id ):
# pwm frequency divider 1=1,2=8,3=64,4=256,5=1024
assert( 1 <= freq_div_id and freq_div_id <= 5 )
pass
def get_pwm_freq( self, midi_pitch, time_out_ms=250 ):
return self.read_block( self._pitch_to_i2c_addr( midi_pitch ),
TinyRegAddr.kPwmFreqAddr,
[], 1, time_out_ms )
def set_flags( self, midi_pitch, flags ):
return self.write( self._pitch_to_i2c_addr( midi_pitch ),
TinyOpD['writeOp'],
[ 12, 14, flags ])
def make_note( self, midi_pitch, atk_us, dur_ms ):
# TODO: handle error on note_on_us()
self.note_on_us(midi_pitch, atk_us);
time.sleep( dur_ms / 1000.0 )
return self.note_off(midi_pitch)
def _pitch_to_i2c_addr( self, pitch ):
return self.keyMapD[ pitch ]['index'] + self.i2c_base_addr
def _validate_vel( self, vel ):
return vel
def _usec_to_coarse_and_fine( self, usec ):
coarse = int( usec / (self.prescaler_usec*255))
fine = int((usec - coarse*self.prescaler_usec*255) / self.prescaler_usec)
print(coarse,fine)
assert( coarse <= 255 )
assert( fine <= 255)
return coarse,fine
def _send( self, opcode, i2c_addr, reg_addr, byteL ):
self._print( opcode, i2c_addr, reg_addr, byteL )
byteA = bytearray( [ord(opcode), i2c_addr, reg_addr ] + byteL )
return self.serialProc.send(SerialMsgId.DATA_MSG, byteA )
def _print( self, opcode, i2c_addr, reg_addr, byteL ):
s = "{} {} {}".format( opcode, i2c_addr, reg_addr )
for x in byteL:
s += " {}".format(x)
print(s)

View File

@ -3,6 +3,108 @@
{
serial_dev: "/dev/ttyACM0",
serial_baud: 38400,
i2c_base_addr: 21,
prescaler_usec: 32,
key_mapL: [
{ index: 0, board: 1, ch: 1, type: 'wB', midi: 21, class: 'A' },
{ index: 1, board: 1, ch: 2, type: 'Bl', midi: 22, class: 'A#' },
{ index: 2, board: 1, ch: 3, type: 'wF', midi: 23, class: 'B' },
{ index: 3, board: 1, ch: 4, type: 'wB', midi: 24, class: 'C' },
{ index: 4, board: 1, ch: 5, type: 'Bl', midi: 25, class: 'C#' },
{ index: 5, board: 1, ch: 6, type: 'wF', midi: 26, class: 'D' },
{ index: 6, board: 1, ch: 7, type: 'Bl', midi: 27, class: 'D#' },
{ index: 7, board: 1, ch: 8, type: 'wB', midi: 28, class: 'E' },
{ index: 8, board: 1, ch: 9, type: 'wF', midi: 29, class: 'F' },
{ index: 9, board: 1, ch: 10, type: 'Bl', midi: 30, class: 'F#' },
{ index: 10, board: 1, ch: 11, type: 'wB', midi: 31, class: 'G' },
{ index: 11, board: 2, ch: 1, type: 'Bl', midi: 32, class: 'G#' },
{ index: 12, board: 2, ch: 2, type: 'wF', midi: 33, class: 'A' },
{ index: 13, board: 2, ch: 3, type: 'Bl', midi: 34, class: 'A#' },
{ index: 14, board: 2, ch: 4, type: 'wB', midi: 35, class: 'B' },
{ index: 15, board: 2, ch: 5, type: 'wF', midi: 36, class: 'C' },
{ index: 16, board: 2, ch: 6, type: 'Bl', midi: 37, class: 'C#' },
{ index: 17, board: 2, ch: 7, type: 'wB', midi: 38, class: 'D' },
{ index: 18, board: 2, ch: 8, type: 'Bl', midi: 39, class: 'D#' },
{ index: 19, board: 2, ch: 9, type: 'wF', midi: 40, class: 'E' },
{ index: 20, board: 2, ch: 10, type: 'wB', midi: 41, class: 'F' },
{ index: 21, board: 2, ch: 11, type: 'Bl', midi: 42, class: 'F#' },
{ index: 22, board: 3, ch: 1, type: 'wF', midi: 43, class: 'G' },
{ index: 23, board: 3, ch: 2, type: 'Bl', midi: 44, class: 'G#' },
{ index: 24, board: 3, ch: 3, type: 'wB', midi: 45, class: 'A' },
{ index: 25, board: 3, ch: 4, type: 'Bl', midi: 46, class: 'A#' },
{ index: 26, board: 3, ch: 5, type: 'wF', midi: 47, class: 'B' },
{ index: 27, board: 3, ch: 6, type: 'wB', midi: 48, class: 'C' },
{ index: 28, board: 3, ch: 7, type: 'Bl', midi: 49, class: 'C#' },
{ index: 29, board: 3, ch: 8, type: 'wF', midi: 50, class: 'D' },
{ index: 30, board: 3, ch: 9, type: 'Bl', midi: 51, class: 'D#' },
{ index: 31, board: 3, ch: 10, type: 'wB', midi: 52, class: 'E' },
{ index: 32, board: 3, ch: 11, type: 'wF', midi: 53, class: 'F' },
{ index: 33, board: 4, ch: 1, type: 'Bl', midi: 54, class: 'F#' },
{ index: 34, board: 4, ch: 2, type: 'wB', midi: 55, class: 'G' },
{ index: 35, board: 4, ch: 3, type: 'Bl', midi: 56, class: 'G#' },
{ index: 36, board: 4, ch: 4, type: 'wF', midi: 57, class: 'A' },
{ index: 37, board: 4, ch: 5, type: 'Bl', midi: 58, class: 'A#' },
{ index: 38, board: 4, ch: 6, type: 'wB', midi: 59, class: 'B' },
{ index: 39, board: 4, ch: 7, type: 'wF', midi: 60, class: 'C' },
{ index: 40, board: 4, ch: 8, type: 'Bl', midi: 61, class: 'C#' },
{ index: 41, board: 4, ch: 9, type: 'wB', midi: 62, class: 'D' },
{ index: 42, board: 4, ch: 10, type: 'Bl', midi: 63, class: 'D#' },
{ index: 43, board: 4, ch: 11, type: 'wF', midi: 64, class: 'E' },
{ index: 44, board: 5, ch: 1, type: 'wB', midi: 65, class: 'F' },
{ index: 45, board: 5, ch: 2, type: 'Bl', midi: 66, class: 'F#' },
{ index: 46, board: 5, ch: 3, type: 'wF', midi: 67, class: 'G' },
{ index: 47, board: 5, ch: 4, type: 'Bl', midi: 68, class: 'G#' },
{ index: 48, board: 5, ch: 5, type: 'wB', midi: 69, class: 'A' },
{ index: 49, board: 5, ch: 6, type: 'Bl', midi: 70, class: 'A#' },
{ index: 50, board: 5, ch: 7, type: 'wF', midi: 71, class: 'B' },
{ index: 51, board: 5, ch: 8, type: 'wB', midi: 72, class: 'C' },
{ index: 52, board: 5, ch: 9, type: 'Bl', midi: 73, class: 'C#' },
{ index: 53, board: 5, ch: 10, type: 'wF', midi: 74, class: 'D' },
{ index: 54, board: 5, ch: 11, type: 'Bl', midi: 75, class: 'D#' },
{ index: 55, board: 6, ch: 1, type: 'wB', midi: 76, class: 'E' },
{ index: 56, board: 6, ch: 2, type: 'wF', midi: 77, class: 'F' },
{ index: 57, board: 6, ch: 3, type: 'Bl', midi: 78, class: 'F#' },
{ index: 58, board: 6, ch: 4, type: 'wB', midi: 79, class: 'G' },
{ index: 59, board: 6, ch: 5, type: 'Bl', midi: 80, class: 'G#' },
{ index: 60, board: 6, ch: 6, type: 'wF', midi: 81, class: 'A' },
{ index: 61, board: 6, ch: 7, type: 'Bl', midi: 82, class: 'A#' },
{ index: 62, board: 6, ch: 8, type: 'wB', midi: 83, class: 'B' },
{ index: 63, board: 6, ch: 9, type: 'wF', midi: 84, class: 'C' },
{ index: 64, board: 6, ch: 10, type: 'Bl', midi: 85, class: 'C#' },
{ index: 65, board: 6, ch: 11, type: 'wB', midi: 86, class: 'D' },
{ index: 66, board: 6, ch: 1, type: 'Bl', midi: 87, class: 'D#' },
{ index: 67, board: 6, ch: 2, type: 'wF', midi: 88, class: 'E' },
{ index: 68, board: 6, ch: 3, type: 'wB', midi: 89, class: 'F' },
{ index: 69, board: 6, ch: 4, type: 'Bl', midi: 90, class: 'F#' },
{ index: 70, board: 6, ch: 5, type: 'wF', midi: 91, class: 'G' },
{ index: 71, board: 6, ch: 6, type: 'Bl', midi: 92, class: 'G#' },
{ index: 72, board: 6, ch: 7, type: 'wB', midi: 93, class: 'A' },
{ index: 73, board: 6, ch: 8, type: 'Bl', midi: 94, class: 'A#' },
{ index: 74, board: 6, ch: 9, type: 'wF', midi: 95, class: 'B' },
{ index: 75, board: 6, ch: 10, type: 'wB', midi: 96, class: 'C' },
{ index: 76, board: 6, ch: 11, type: 'Bl', midi: 97, class: 'C#' },
{ index: 77, board: 7, ch: 1, type: 'wF', midi: 98, class: 'D' },
{ index: 78, board: 7, ch: 2, type: 'Bl', midi: 99, class: 'D#' },
{ index: 79, board: 7, ch: 3, type: 'wB', midi: 100, class: 'E' },
{ index: 80, board: 7, ch: 4, type: 'wF', midi: 101, class: 'F' },
{ index: 81, board: 7, ch: 5, type: 'Bl', midi: 102, class: 'F#' },
{ index: 82, board: 7, ch: 6, type: 'wB', midi: 103, class: 'G' },
{ index: 83, board: 7, ch: 7, type: 'Bl', midi: 104, class: 'G#' },
{ index: 84, board: 7, ch: 8, type: 'wF', midi: 105, class: 'A' },
{ index: 85, board: 7, ch: 9, type: 'Bl', midi: 106, class: 'A#' },
{ index: 86, board: 7, ch: 10, type: 'wB', midi: 107, class: 'B' },
{ index: 87, board: 7, ch: 11, type: 'wF', midi: 108, class: 'C' },
]
}
}

View File

@ -0,0 +1,169 @@
import os,sys,argparse,yaml,types,select,serial,logging,time
from picadae_api import Picadae
class PicadaeShell:
def __init__( self, cfg ):
self.p = None
self.parseD = {
'q':{ "func":None, "varN":0, "help":"quit"},
'?':{ "func":"help", "varN":0, "help":"Print usage text."},
'w':{ "func":"write", "varN":-1, "help":"write <i2c_addr> <reg_addr> <data0> ... <dataN>"},
'r':{ "func":"read", "varN":3, "help":"read <i2c_addr> <reg_addr> <byteN>"},
'v':{ "func":"note_on_vel", "varN":2, "help":"note-on <pitch> <vel>"},
'u':{ "func":"note_on_us", "varN":2, "help":"note-on <pitch> <usec>"},
'o':{ "func":"note_off", "varN":1, "help":"note-off <pitch>"},
'T':{ "func":"set_vel_map", "varN":3, "help":"table <pitch> <vel> <usec>"},
't':{ "func":"get_vel_map", "varN":2, "help":"table <pitch> <vel>"},
'D':{ "func":"set_pwm_duty", "varN":2, "help":"duty <pitch> <percent>"},
'd':{ "func":"get_pwm_duty", "varN":1, "help":"duty <pitch>"},
'F':{ "func":"set_pwm_freq", "varN":2, "help":"freq <pitch> <hz>"},
'f':{ "func":"get_pwm_freq", "varN":1, "help":"freq <pitch>"},
'B':{ "func":"set_flags", "varN":2, "help":"flags <pitch> <flags>"},
'N':{ "func":"make_note", "varN":3, "help":"note <pitch> atkUs durMs"},
}
def _do_help( self, _ ):
for k,d in self.parseD.items():
s = "{} = {}".format( k, d['help'] )
print(s)
def _do_write( self, argL ):
return self.p.write(argL[0], argL[1], argL[2:])
def _do_read( self, argL ):
return self.p.read(*argL)
def _do_note_on_vel( self, argL ):
return self.p.note_on_vel(*argL)
def _do_note_on_us( self, argL ):
return self.p.note_on_us(*argL)
def _do_note_off( self, argL ):
return self.p.note_off(*argL)
def _do_set_vel_map( self, argL ):
return self.p.set_velocity_map(*argL)
def _do_get_vel_map( self, argL ):
return self.p.get_velocity_map(*argL)
def _do_set_pwm_duty( self, argL ):
return self.p.set_pwm_duty(*argL)
def _do_get_pwm_duty( self, argL ):
return self.p.get_pwm_duty(*argL)
def _do_set_pwm_freq( self, argL ):
return self.p.set_pwm_freq(*argL)
def _do_get_pwm_freq( self, argL ):
return self.p.get_pwm_freq(*argL)
def _do_set_flags( self, argL ):
return self.p.set_flags(*argL)
def _do_make_note( self, argL ):
return self.p.make_note(*argL)
def _syntaxError( self, msg ):
print("Syntax Error: " + msg )
return None
def _exec_cmd( self, tokL ):
if len(tokL) <= 0:
return None
opcode = tokL[0]
if opcode not in self.parseD:
return self._syntaxError("Unknown opcode: '{}'.".format(opcode))
d = self.parseD[ opcode ]
func_name = "_do_" + d['func']
if hasattr(self, func_name ):
func = getattr(self, func_name )
try:
argL = [ int(tokL[i]) for i in range(1,len(tokL)) ]
except:
return self._syntaxError("Unable to create integer arguments.")
if d['varN'] != -1 and len(argL) != d['varN']:
return self._syntaxError("Argument mismatch {} != {}.".format(len(argL),d['varN']))
result = func(argL)
return None
def run( self ):
# create the API object
self.p = Picadae( cfg.key_mapL, cfg.i2c_base_addr, cfg.serial_dev, cfg.serial_baud )
print("'q'=quit '?'=help")
time_out_secs = 1
while True:
# wait for keyboard activity
i, o, e = select.select( [sys.stdin], [], [], time_out_secs )
if (i):
# read the command
s = sys.stdin.readline().strip()
# tokenize the command
tokL = s.split(' ')
# if this is the 'quit' command
if tokL[0] == 'q':
break
# execute the command
self._exec_cmd( tokL )
self.p.close()
def parse_args():
"""Parse the command line arguments."""
descStr = """Picadae auto-calibrate."""
logL = ['debug','info','warning','error','critical']
ap = argparse.ArgumentParser(description=descStr)
ap.add_argument("-s","--setup", default="picadae_cmd.yml", help="YAML configuration file.")
ap.add_argument("-c","--cmd", nargs="*", help="Give a command as multiple tokens")
ap.add_argument("-r","--run", help="Run a named command list from the setup file.")
ap.add_argument("-l","--log_level",choices=logL, default="warning", help="Set logging level: debug,info,warning,error,critical. Default:warning")
return ap.parse_args()
def parse_yaml_cfg( fn ):
"""Parse the YAML configuration file."""
cfg = None
with open(fn,"r") as f:
cfgD = yaml.load(f, Loader=yaml.FullLoader)
cfg = types.SimpleNamespace(**cfgD['picadae_cmd'])
return cfg
if __name__ == "__main__":
args = parse_args()
cfg = parse_yaml_cfg( args.setup )
app = PicadaeShell(cfg)
app.run()

View File

@ -78,12 +78,12 @@ void i2c_read_from( uint8_t i2c_addr, uint8_t dev_reg_addr, uint8_t read_byte_cn
// Request to read from the client. Note that 'sendStop'==0.
// Use this call to tell the client what data should be sent
// during the subsequent twi_readFrom().
twi_writeTo(I2C_REMOTE_ADDR, &dev_reg_addr, 1, kWaitFl, kNoSendStopFl);
twi_writeTo(i2c_addr, &dev_reg_addr, 1, kWaitFl, kNoSendStopFl);
// Blocking waiting and wait to read the client's response.
for( uint8_t i=0; i<read_byte_cnt; ++i)
if( twi_readFrom(I2C_REMOTE_ADDR, &recv_char, 1, i==read_byte_cnt-1) )
if( twi_readFrom(i2c_addr, &recv_char, 1, i==read_byte_cnt-1) )
uart_putchar(recv_char);
PORTB ^= _BV(PORTB5); // toggle LED
@ -217,6 +217,7 @@ int main (void)
}
else
{
// TODO: handle case where there are no data bytes (only e.g. note-off)
state = kWait_for_value;
data_buf[0] = dev_reg_addr; // make 'dev_reg_addr' the first data value to write
data_buf_idx = 1; //
@ -237,8 +238,13 @@ int main (void)
if(data_buf_idx == op_byte_cnt )
{
/*
uint8_t ii;
for(ii=0; ii<op_byte_cnt; ++ii)
uart_putchar( data_buf[ii] );
*/
i2c_xmit( I2C_REMOTE_ADDR, data_buf, op_byte_cnt, kSendStopFl);
i2c_xmit( i2c_addr, data_buf, op_byte_cnt, kSendStopFl);
state = kWait_for_cmd;
}
}

View File

@ -1,3 +1,7 @@
#
# Usage: make I2C_ADDR=8
#
ifndef TTY
TTY=/dev/ttyACM0
endif
@ -9,7 +13,7 @@ endif
MCU=attiny85
AVRDUDEMCU=t85
CC=/usr/bin/avr-gcc
CFLAGS=-g -Os -Wall -mcall-prologues -mmcu=$(MCU)
CFLAGS=-g -Os -Wall -mcall-prologues -mmcu=$(MCU) -DI2C_SLAVE_ADDRESS=$(I2C_ADDR)
OBJ2HEX=/usr/bin/avr-objcopy
AVRDUDE=avrdude

View File

@ -1,10 +1,14 @@
// w 60 0 1 10 : w i2c_addr SetPWM enable duty_val
// w 60 5 12 8 32 : w i2c_addr write addrFl|src coarse_val
// w 60 4 0 5 : w i2c_addr read src read_addr (set the read address to register 5)
// r 60 4 3 : r i2c_addr <dum> cnt (read the first 3 reg's beginning w/ 5)
/*
AT TINY 85
+--\/--+
RESET _| 1 8 |_ +5V
~OC1B HOLD DDB3 _| 2 7 |_ SCL
~OC1B HOLD DDB3 _| 2 7 |_ SCL yellow
OC1B ONSET DDB4 _| 3 6 |_ DDB1 LED
GND _| 4 5 |_ SDA
GND _| 4 5 |_ SDA orange
+------+
* = Serial and/or programming pins on Arduino as ISP
*/
@ -12,6 +16,7 @@
// This program acts as the device (slave) for the control program i2c/a2a/c_ctl
#define F_CPU 8000000L
#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
@ -19,50 +24,98 @@
#include "usiTwiSlave.h"
#define HOLD_DIR DDB3
#define ATTK_DIR DDB4
#define LED_DIR DDB1
#define I2C_SLAVE_ADDRESS 0x8 // the 7-bit address (remember to change this when adapting this example)
#define HOLD_PIN PINB3
#define ATTK_PIN PINB4
#define LED_PIN PINB1
// Opcodes
enum
{
kSetPwm_Op = 0, // Set PWM registers 0 {<enable> {<duty> {<freq>}}}
kNoteOnVel_Op = 1, // Turn on note 1 {<vel>}
kNoteOnUsec_Op = 2, // Turn on note 2 {<coarse> {<fine> {<prescale>}}}
kNoteOff_Op = 3, // Turn off note 3
kRead_Op = 4, // Read a value 4 {<src>} {<addr>} } src: 0=reg 1=table 2=eeprom
kWrite_Op = 5, // Set write 5 {<addrfl|src> {addr} {<value0> ... {<valueN>}}
kInvalid_Op = 6 // addrFl:0x80 src: 4=reg 5=table 6=eeprom
};
enum
{
kTmr0_Prescale_idx = 0, // Timer 0 clock divider: 1=1,2=8,3=64,4=256,5=1024
kTmr0_Coarse_idx = 1, //
kTmr0_Fine_idx = 2, //
kPWM0_Duty_idx = 3, //
kPWM0_Freq_idx = 4, // 1-4 = clock divider=1=1,2=8,3=64,4=256,5=1024
kCS13_10_idx = 5, // Timer 1 Prescalar (CS13,CS12,CS11,CS10) from Table 12-5 pg 89 (0-15) prescaler = pow(2,val-1), 0=stop,1=1,2=2,3=4,4=8,....14=8192,15=16384 pre_scaled_hz = clock_hz/value
kTmr1_Coarse_idx = 6, // count of times timer0 count to 255 before OCR1C is set to Tmr0_Fine
kTmr1_Fine_idx = 7, // OCR1C timer match value
kPWM1_Duty_idx = 8, //
kPWM1_Freq_idx = 9, //
kTable_Addr_idx = 10, // Next table address to read/write
kTable_Coarse_idx = 11, // Next table coarse value to read/write
kTable_Fine_idx = 12, // Next table fine value to read/write
kReg_Rd_Addr_idx = 0, // Next Reg Address to read
kTable_Rd_Addr_idx = 1, // Next Table Address to read
kEE_Rd_Addr_idx = 2, // Next EEPROM address to read
kRead_Src_idx = 3, // kReg_Rd_Addr_idx=reg, kTable_Rd_Addr_idx=table, kEE_Rd_Addr_idx=eeprom
kReg_Wr_Addr_idx = 4, // Next Reg Address to write
kTable_Wr_Addr_idx = 5, // Next Table Address to write
kEE_Wr_Addr_idx = 6, // Next EEPROM address to write
kWrite_Dst_idx = 7, // kReg_Wr_Addr_idx=reg, kTable_Wr_Addr_idx=table, kEE_Wr_Addr_idx=eeprom
kTmr_Coarse_idx = 8, //
kTmr_Fine_idx = 9, //
kTmr_Prescale_idx = 10, // Timer 0 clock divider: 1=1,2=8,3=64,4=256,5=1024 Default: 8 (32us)
kPwm_Enable_idx = 11, //
kPwm_Duty_idx = 12, //
kPwm_Freq_idx = 13, //
kMode_idx = 14, // 1=repeat 2=pwm
kState_idx = 15, // 1=attk 2=hold
kError_Code_idx = 16, // Error Code
kMax_idx
};
enum
{
kTmr_Repeat_Fl= 1,
kTmr_Pwm_Fl = 2,
kAttk_Fl = 1,
kHold_Fl = 2
};
// Flags:
// 1=Repeat: 1=Timer and PWM are free running. This allows testing with LED's. 0=Timer triggers does not reset on time out.
// 2=PWM: On timer timeout 1=PWM HOLD 0=Set HOLD
volatile uint8_t ctl_regs[] =
{
4, // 0 (1-5) 4=32us per tick
123, // 1 (0-255) Timer 0 Coarse Value
8, // 2 (0-255) Timer 0 Fine Value
127, // 3 (0-255) Duty cycle
4, // 4 (1-4) PWM Frequency (clock pre-scaler)
9, // 5 9=32 us period w/ 8Mhz clock (timer tick rate)
123, // 6 (0-255) Tmr1_Coarse count of times timer count to 255 before loading Tmr0_Minor for final count.
8, // 7 (0-254) Tmr1_Fine OCR1C value on final phase before triggering timer
127, // 8 (0-255) PWM1 Duty cycle
254, // 9 (0-255) PWM1 Frequency (123 hz)
0, // 10 (0-127) Next table addr to read/write
0, // 11 (0-255) Next table coarse value to write
0, // 12 (0-255) Next table fine value to write
0, // 0 (0-(kMax_idx-1)) Reg Read Addr
0, // 1 (0-255) Table Read Addr
0, // 2 (0-255) EE Read Addr
kReg_Rd_Addr_idx, // 3 (0-2) Read source
0, // 4 (0-(kMax_idx-1)) Reg Write Addr
0, // 5 (0-255) Table Write Addr
0, // 6 (0-255) EE Write Addr
kReg_Wr_Addr_idx, // 7 (0-2) Write source
123, // 8 (0-255) Timer 0 Coarse Value
8, // 9 (0-255) Timer 0 Fine Value
4, // 10 (1-5) 4=32us per tick
1, // 11 (0-1) Pwm Enable Flag
127, // 12 (0-255) Pwm Duty cycle
254, // 13 (0-255) Pwm Frequency (123 hz)
0, // 14 mode flags 1=Repeat 2=PWM
0, // 15 state flags 1=attk 2=hold
0, // 16 (0-255) Error bit field
};
#define tableN 256
uint8_t table[ tableN ];
uint8_t table[ tableN ]; // [ coarse_0,fine_0, coarse_1, fine_1, .... coarse_127,fine_127]
enum
{
kInvalid_Read_Src_ErrFl = 0x01,
kInvalid_Write_Dst_ErrFl = 0x02
};
#define set_error( flag ) ctl_regs[ kError_Code_idx ] |= (flag)
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
@ -113,7 +166,7 @@ uint8_t EEPROM_read(uint8_t ucAddress)
// r 8 kTable_Coarse_idx -> 127
// r 8 kTable_Fine_idx -> 64
/*
#define eeprom_addr( addr ) (kMax_idx + (addr))
void table_write_cur_value( void )
@ -138,19 +191,7 @@ void table_load( void )
table[tbl_addr+1] = EEPROM_read( eeprom_addr(tbl_addr+1) );
}
}
void restore_memory_from_eeprom( void )
{
/*
uint8_t i;
for(i=0; i<kMax_idx; ++i)
{
ctl_regs[i] = EEPROM_read( eeprom_addr( i ) );
}
*/
table_load();
}
*/
//------------------------------------------------------------------------------
@ -163,11 +204,15 @@ void restore_memory_from_eeprom( void )
volatile uint8_t tmr0_state = 0; // 0=disabled 1=coarse mode, 2=fine mode
volatile uint8_t tmr0_coarse_cur = 0;
#define set_attack() do { ctl_regs[kState_idx] |= kAttk_Fl; PORTB |= _BV(ATTK_PIN); } while(0)
#define clear_attack() do { PORTB &= ~_BV(ATTK_PIN); ctl_regs[kState_idx] &= ~kAttk_Fl; } while(0)
// Use the current tmr0 ctl_reg[] values to set the timer to the starting state.
void tmr0_reset()
{
// if a coarse count exists then go into coarse mode
if( ctl_regs[kTmr0_Coarse_idx] > 0 )
if( ctl_regs[kTmr_Coarse_idx] > 0 )
{
tmr0_state = 1;
OCR0A = 0xff;
@ -175,10 +220,14 @@ void tmr0_reset()
else // otherwise go into fine mode
{
tmr0_state = 2;
OCR0A = ctl_regs[kTmr0_Fine_idx];
OCR0A = ctl_regs[kTmr_Fine_idx];
}
tmr0_coarse_cur = 0;
ctl_regs[kState_idx] |= kAttk_Fl; // set the attack state
PORTB |= _BV(ATTK_PIN); // set the attack pin
TIMSK |= _BV(OCIE0A); // enable the timer interrupt
}
ISR(TIMER0_COMPA_vect)
@ -191,18 +240,50 @@ ISR(TIMER0_COMPA_vect)
case 1:
// coarse mode
if( ++tmr0_coarse_cur >= ctl_regs[kTmr0_Coarse_idx] )
if( ++tmr0_coarse_cur >= ctl_regs[kTmr_Coarse_idx] )
{
tmr0_state = 2;
OCR0A = ctl_regs[kTmr0_Fine_idx];
OCR0A = ctl_regs[kTmr_Fine_idx];
}
break;
case 2:
// fine mode
PINB = _BV(PINB4); // writes to PINB toggle the pins
// If in repeat mode
if(ctl_regs[kMode_idx] & kTmr_Repeat_Fl)
{
uint8_t fl = ctl_regs[kState_idx] & kAttk_Fl;
tmr0_reset(); // restart the timer
// ATTK_PIN is always set after tmr0_reset() but we need to toggle in 'repeat' mode
if( fl )
{
clear_attack();
}
// In repeat mode we run the PWM output continuously
TIMSK |= _BV(OCIE1B) + _BV(TOIE1); // Enable PWM interrupts
}
else // not in repeat mode
{
clear_attack();
if( ctl_regs[kMode_idx] & kTmr_Pwm_Fl)
{
TIMSK |= _BV(OCIE1B) + _BV(TOIE1); // PWM interupt Enable interrupts
}
else
{
PORTB |= _BV(HOLD_PIN); // set the HOLD pin
}
TIMSK &= ~_BV(OCIE0A); // clear timer interrupt
}
break;
}
}
@ -212,13 +293,12 @@ void timer0_init()
{
TIMSK &= ~_BV(OCIE0A); // Disable interrupt TIMER1_OVF
TCCR0A |= 0x02; // CTC mode
TCCR0B |= ctl_regs[kTmr0_Prescale_idx]; // set the prescaler
TCCR0B |= ctl_regs[kTmr_Prescale_idx]; // set the prescaler
GTCCR |= _BV(PSR0); // Set the pre-scaler to the selected value
tmr0_reset(); // set the timers starting state
//tmr0_reset(); // set the timers starting state
TIMSK |= _BV(OCIE0A); // Enable interrupt TIMER1_OVF
}
@ -227,129 +307,28 @@ void timer0_init()
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// PWM (Timer0)
// Pwm
//
void pwm0_update()
{
OCR0B = ctl_regs[kPWM0_Duty_idx]; // 50% duty cycle
TCCR0B |= ctl_regs[kPWM0_Freq_idx]; // PWM frequency pre-scaler
}
void pwm0_init()
{
// WGM[1:0] = 3 (TOP=255)
// OCR0B = duty cycle (0-100%)
// COM0A[1:0] = 2 non-inverted
//
TCCR0A |= 0x20 + 3; // 0x20=non-inverting 3=WGM bits Fast-PWM mode (0=Bot 255=Top)
TCCR0B |= 0x00 + 4; // 3=256 pre-scaler 122Hz=1Mghz/(v*256) where v=64
GTCCR |= _BV(PSR0); // Set the pre-scaler to the selected value
pwm0_update();
DDRB |= _BV(DDB1); // set direction on
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// Timer1
//
volatile uint8_t tmr1_state = 0;
volatile uint8_t tmr1_coarse_cur = 0;
static uint8_t tmr1_init_fl = 0;
void tmr1_reset()
{
if( ctl_regs[kTmr1_Coarse_idx] > 0 )
{
tmr1_state = 1;
OCR1C = 254;
}
else
{
tmr1_state = 2;
OCR1C = ctl_regs[kTmr1_Fine_idx];
}
tmr1_coarse_cur = 0;
}
ISR(TIMER1_OVF_vect)
{
if( !tmr1_init_fl )
{
PORTB |= _BV(PINB3); // set PWM pin
}
else
{
switch( tmr1_state )
{
case 0:
// disabled
break;
case 1:
// coarse mode
if( ++tmr1_coarse_cur >= ctl_regs[kTmr1_Coarse_idx] )
{
tmr1_state = 2;
OCR1C = ctl_regs[kTmr1_Fine_idx];
}
break;
case 2:
// fine mode
PINB = _BV(PINB4); // writes to PINB toggle the pins
tmr1_reset();
break;
}
}
}
void timer1_init()
{
TIMSK &= ~_BV(TOIE1); // Disable interrupt TIMER1_OVF
OCR1A = 255; // Set to anything greater than OCR1C (the counter never gets here.)
TCCR1 |= _BV(CTC1); // Reset TCNT1 to 0 when TCNT1==OCR1C
TCCR1 |= _BV(PWM1A); // Enable PWM A (to generate overflow interrupts)
TCCR1 |= ctl_regs[kCS13_10_idx] & 0x0f; //
GTCCR |= _BV(PSR1); // Set the pre-scaler to the selected value
tmr1_reset();
tmr1_init_fl = 1;
TIMSK |= _BV(TOIE1); // Enable interrupt TIMER1_OVF
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// PWM1
//
// PWM is optimized to use pins OC1A ,~OC1A, OC1B, ~OC1B but this code
// PWM is optimized to use pins OC1A ,~OC1A, OC1B, ~OC1B
// but since these pins are not available this code uses
// ISR's to redirect the output to PIN3
void pwm1_update()
{
OCR1B = ctl_regs[kPWM1_Duty_idx]; // control duty cycle
OCR1C = ctl_regs[kPWM1_Freq_idx]; // PWM frequency pre-scaler
OCR1B = ctl_regs[kPwm_Duty_idx]; // control duty cycle
OCR1C = ctl_regs[kPwm_Freq_idx]; // PWM frequency pre-scaler
}
ISR(TIMER1_OVF_vect)
{
PORTB |= _BV(HOLD_PIN); // set PWM pin
}
ISR(TIMER1_COMPB_vect)
{
PORTB &= ~(_BV(PINB3)); // clear PWM pin
PORTB &= ~(_BV(HOLD_PIN)); // clear PWM pin
}
@ -357,7 +336,7 @@ void pwm1_init()
{
TIMSK &= ~(_BV(OCIE1B) + _BV(TOIE1)); // Disable interrupts
DDRB |= _BV(DDB3); // setup PB3 as output
DDRB |= _BV(HOLD_DIR); // setup PB3 as output
// set on TCNT1 == 0 // happens when TCNT1 matches OCR1C
// clr on OCR1B == TCNT // happens when TCNT1 matches OCR1B
@ -367,11 +346,6 @@ void pwm1_init()
GTCCR |= _BV(PSR1); // Set the pre-scaler to the selected value
pwm1_update();
TIMSK |= _BV(OCIE1B) + _BV(TOIE1); // Enable interrupts
}
//------------------------------------------------------------------------------
@ -389,38 +363,84 @@ const uint8_t reg_size = sizeof(ctl_regs);
// than one byte of data (with TinyWireS.send) to the send-buffer when
// using this callback
//
void on_request()
{
uint8_t val = 0;
switch( reg_position )
switch( ctl_regs[ kRead_Src_idx ] )
{
case kTable_Coarse_idx:
val = table[ ctl_regs[kTable_Addr_idx]*2 + 0 ];
case kReg_Rd_Addr_idx:
val = ctl_regs[ ctl_regs[kReg_Rd_Addr_idx] ];
break;
case kTable_Fine_idx:
val = table[ ctl_regs[kTable_Addr_idx]*2 + 1 ];
case kTable_Rd_Addr_idx:
val = table[ ctl_regs[kTable_Rd_Addr_idx] ];
break;
case kEE_Rd_Addr_idx:
val = EEPROM_read(ctl_regs[kEE_Rd_Addr_idx]);
break;
default:
// read and transmit the requestd position
val = ctl_regs[reg_position];
set_error( kInvalid_Read_Src_ErrFl );
return;
}
usiTwiTransmitByte(val);
// Increment the reg position on each read, and loop back to zero
reg_position++;
if (reg_position >= reg_size)
{
reg_position = 0;
}
ctl_regs[ ctl_regs[ kRead_Src_idx ] ] += 1;
}
void _write_op( uint8_t* stack, uint8_t stackN )
{
uint8_t stack_idx = 0;
if( stackN > 0 )
{
uint8_t src = stack[0] & 0x07;
uint8_t addr_fl = stack[0] & 0x08;
// verify the source value
if( src < kReg_Wr_Addr_idx || src > kEE_Wr_Addr_idx )
{
set_error( kInvalid_Write_Dst_ErrFl );
return;
}
// set the write source
stack_idx = 1;
ctl_regs[ kWrite_Dst_idx ] = src;
// if an address value was passed also ....
if( addr_fl && stackN > 1 )
{
stack_idx = 2;
ctl_regs[ src ] = stack[1];
}
}
//
for(; stack_idx<stackN; ++stack_idx)
{
uint8_t addr_idx = ctl_regs[ ctl_regs[kWrite_Dst_idx] ]++;
uint8_t val = stack[ stack_idx ];
switch( ctl_regs[ kWrite_Dst_idx ] )
{
case kReg_Wr_Addr_idx: ctl_regs[ addr_idx ] = val; break;
case kTable_Wr_Addr_idx: table[ addr_idx ] = val; break;
case kEE_Wr_Addr_idx: EEPROM_write( table[ addr_idx ], val); break;
default:
set_error( kInvalid_Write_Dst_ErrFl );
break;
}
}
}
//
// The I2C data received -handler
//
@ -432,87 +452,80 @@ void on_request()
void on_receive( uint8_t byteN )
{
if (byteN < 1)
PINB = _BV(LED_PIN); // writes to PINB toggle the pins
const uint8_t stackN = 16;
uint8_t stack_idx = 0;
uint8_t stack[ stackN ];
uint8_t i;
if (byteN < 1 || byteN > TWI_RX_BUFFER_SIZE)
{
// Sanity-check
return;
}
if (byteN > TWI_RX_BUFFER_SIZE)
{
// Also insane number
return;
}
// get the register index to read/write
reg_position = usiTwiReceiveByte();
uint8_t op_id = usiTwiReceiveByte();
byteN--;
// If only one byte was received then this was a read request
// and the buffer pointer (reg_position) is now set to return the byte
// at this location on the subsequent call to on_request() ...
if (!byteN)
if(byteN)
{
return;
while( byteN-- )
{
stack[stack_idx] = usiTwiReceiveByte();
++stack_idx;
}
}
// ... otherwise this was a write request and the buffer
// pointer is now pointing to the first byte to write to
while(byteN--)
switch( op_id )
{
// write the value
ctl_regs[reg_position] = usiTwiReceiveByte();
case kSetPwm_Op:
for(i=0; i<stack_idx; ++i)
ctl_regs[ kPwm_Enable_idx + i ] = stack[i];
pwm1_update();
break;
// Set timer 1
if( kTmr0_Prescale_idx <= reg_position && reg_position <= kTmr0_Fine_idx )
{ timer0_init(); }
else
case kNoteOnUsec_Op:
for(i=0; i<stack_idx; ++i)
ctl_regs[ kTmr_Coarse_idx + i ] = stack[i];
tmr0_reset();
break;
case kNoteOff_Op:
TIMSK &= ~(_BV(OCIE1B) + _BV(TOIE1)); // PWM interupt disable interrupts
PORTB &= ~_BV(HOLD_PIN); // clear the HOLD pin
break;
// Set PWM 0
if( kPWM0_Duty_idx <= reg_position && reg_position <= kPWM0_Freq_idx )
{ pwm0_update(); }
else
// Set timer 1
if( kCS13_10_idx <= reg_position && reg_position <= kTmr1_Fine_idx )
{ timer1_init(); }
else
// Set PWM 1
if( kPWM1_Duty_idx <= reg_position && reg_position <= kPWM1_Freq_idx )
{ pwm1_update(); }
else
// Write table
if( reg_position == kTable_Fine_idx )
{ table_write_cur_value(); }
reg_position++;
if (reg_position >= reg_size)
case kRead_Op:
if( stack_idx > 0 )
{
reg_position = 0;
ctl_regs[ kRead_Src_idx ] = stack[0];
if( stack_idx > 1 )
ctl_regs[ ctl_regs[ kRead_Src_idx ] ] = stack[1];
}
break;
case kWrite_Op:
_write_op( stack, stack_idx );
break;
}
}
int main(void)
{
cli(); // mask all interupts
restore_memory_from_eeprom();
DDRB |= _BV(DDB4) + _BV(DDB3) + _BV(DDB1); // setup PB4,PB3,PB1 as output
PORTB &= ~(_BV(PINB4) + _BV(PINB3) + _BV(PINB1)); // clear output pins
DDRB |= _BV(ATTK_DIR) + _BV(HOLD_DIR) + _BV(LED_DIR); // setup PB4,PB3,PB1 as output
PORTB &= ~(_BV(ATTK_PIN) + _BV(HOLD_PIN) + _BV(LED_PIN)); // clear output pins
timer0_init();
@ -525,9 +538,9 @@ int main(void)
sei();
PINB = _BV(PINB4); // writes to PINB toggle the pins
PINB = _BV(LED_PIN); // writes to PINB toggle the pins
_delay_ms(1000);
PINB = _BV(PINB4); // writes to PINB toggle the pins
PINB = _BV(LED_PIN); // writes to PINB toggle the pins
while(1)