From de586ca2e36ceb79f5667a035e41b37f1df3aafa Mon Sep 17 00:00:00 2001 From: "kevin.larke" Date: Sat, 3 Aug 2019 19:42:59 -0400 Subject: [PATCH] Updates for initial timer and pwm test on piccadae. --- control/app/picadae_api.py | 270 +++++++++++++++++ control/app/picadae_cmd.yml | 102 +++++++ control/app/picadae_shell.py | 169 +++++++++++ control/ctrl/main.c | 12 +- control/tiny/Makefile | 6 +- control/tiny/i2c_timer_pwm.c | 541 ++++++++++++++++++----------------- 6 files changed, 832 insertions(+), 268 deletions(-) create mode 100644 control/app/picadae_api.py create mode 100644 control/app/picadae_shell.py diff --git a/control/app/picadae_api.py b/control/app/picadae_api.py new file mode 100644 index 0000000..e0cfc63 --- /dev/null +++ b/control/app/picadae_api.py @@ -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) diff --git a/control/app/picadae_cmd.yml b/control/app/picadae_cmd.yml index 7234f12..8e3bbdf 100644 --- a/control/app/picadae_cmd.yml +++ b/control/app/picadae_cmd.yml @@ -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' }, + + ] } } diff --git a/control/app/picadae_shell.py b/control/app/picadae_shell.py new file mode 100644 index 0000000..0fc9bc5 --- /dev/null +++ b/control/app/picadae_shell.py @@ -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 ... "}, + 'r':{ "func":"read", "varN":3, "help":"read "}, + 'v':{ "func":"note_on_vel", "varN":2, "help":"note-on "}, + 'u':{ "func":"note_on_us", "varN":2, "help":"note-on "}, + 'o':{ "func":"note_off", "varN":1, "help":"note-off "}, + 'T':{ "func":"set_vel_map", "varN":3, "help":"table "}, + 't':{ "func":"get_vel_map", "varN":2, "help":"table "}, + 'D':{ "func":"set_pwm_duty", "varN":2, "help":"duty "}, + 'd':{ "func":"get_pwm_duty", "varN":1, "help":"duty "}, + 'F':{ "func":"set_pwm_freq", "varN":2, "help":"freq "}, + 'f':{ "func":"get_pwm_freq", "varN":1, "help":"freq "}, + 'B':{ "func":"set_flags", "varN":2, "help":"flags "}, + 'N':{ "func":"make_note", "varN":3, "help":"note 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() diff --git a/control/ctrl/main.c b/control/ctrl/main.c index 5519bf9..07b46e2 100644 --- a/control/ctrl/main.c +++ b/control/ctrl/main.c @@ -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 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 #include #include @@ -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 { { {}}} + kNoteOnVel_Op = 1, // Turn on note 1 {} + kNoteOnUsec_Op = 2, // Turn on note 2 { { {}}} + kNoteOff_Op = 3, // Turn off note 3 + kRead_Op = 4, // Read a value 4 {} {} } src: 0=reg 1=table 2=eeprom + kWrite_Op = 5, // Set write 5 { {addr} { ... {}} + 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 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; + 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 - tmr0_reset(); // restart the timer + // 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 - - TIMSK |= _BV(OCIE0A); // Enable interrupt TIMER1_OVF + //tmr0_reset(); // set the timers starting state + } @@ -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 TWI_RX_BUFFER_SIZE) - { - // Also insane number - return; - } - - // get the register index to read/write - reg_position = 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) - { - return; - } - - // ... otherwise this was a write request and the buffer - // pointer is now pointing to the first byte to write to - while(byteN--) - { - // write the value - ctl_regs[reg_position] = usiTwiReceiveByte(); - - // Set timer 1 - if( kTmr0_Prescale_idx <= reg_position && reg_position <= kTmr0_Fine_idx ) - { timer0_init(); } - else - - - // 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) - { - reg_position = 0; - } - - - } + 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; + } + // get the register index to read/write + 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) + { + while( byteN-- ) + { + stack[stack_idx] = usiTwiReceiveByte(); + ++stack_idx; + } + } + + switch( op_id ) + { + case kSetPwm_Op: + for(i=0; i 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) @@ -509,10 +524,8 @@ 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)