From 6dc45c01274bccc6cf8219ddc48f6ad20f8a8048 Mon Sep 17 00:00:00 2001 From: "kevin.larke" Date: Tue, 6 Aug 2019 21:36:22 -0400 Subject: [PATCH] Updates to support 16Mhz and reading from register memory from picadae_shell.py. --- control/app/picadae_api.py | 185 +++++++++++++++++++++-------------- control/app/picadae_cmd.py | 5 + control/app/picadae_cmd.yml | 3 +- control/app/picadae_shell.py | 54 ++++++---- control/tiny/Makefile | 5 +- control/tiny/i2c_timer_pwm.c | 94 ++++++++++-------- 6 files changed, 207 insertions(+), 139 deletions(-) diff --git a/control/app/picadae_api.py b/control/app/picadae_api.py index e0cfc63..597f5c3 100644 --- a/control/app/picadae_api.py +++ b/control/app/picadae_api.py @@ -5,49 +5,62 @@ 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 TinyOp(Enum): + setPwmOp = 0 + noteOnVelOp = 1 + noteOnUsecOp = 2 + noteOffOp = 3 + setReadAddr = 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 + kRdRegAddrAddr = 0 + kRdTableAddrAddr = 1 + kRdEEAddrAddr = 2 + kRdSrcAddr = 3 + kWrRegAddrAddr = 4 + kWrTableAddrAddr = 5 + kWrEEAddrAddr = 6 + kWrDstAddr = 7 + kTmrCoarseAddr = 8 + kTmrFineAddr = 9 + kTmrPrescaleAddr = 10 + kPwmDutyAddr = 11 + kPwmFreqAddr = 12 + kModeAddr = 13 + kStateAddr = 14 + kErrorCodeAddr = 15 class TinyConst(Enum): - kRdRegSrcId = TinyRegAddr.kRdRegAddrAddr, # 0 - kRdTableSrcId = TinyRegAddr.kRdTableAddrAddr, # 1 - kRdEESrcId = TinyRegAddr.kRdEEAddrAddr # 2 + 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 + 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 +class Result(object): + def __init__( self, value=None, msg=None ): + self.value = value + self.msg = msg + def set_error( self, msg ): + if self.msg is None: + self.msg = "" + + self.msg += " " + msg + + def __bool__( self ): + return self.msg is None + + def _serial_process_func( serial_dev, baud, pipe ): reset_N = 0 @@ -100,7 +113,8 @@ class SerialProcess(Process): 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)) @@ -108,6 +122,7 @@ class SerialProcess(Process): def send(self,msg_id,value): # send a msg to the child process self.parent_end.send((msg_id,value)) + return Result() def recv(self): # @@ -127,7 +142,7 @@ class SerialProcess(Process): class Picadae: - def __init__( self, key_mapL, i2c_base_addr=1, serial_dev='/dev/ttyACM0', serial_baud=38400 ): + def __init__( self, key_mapL, i2c_base_addr=21, serial_dev='/dev/ttyACM0', serial_baud=38400, prescaler_usec=16 ): """ key_mapL = [{ index, board, ch, type, midi, class }] serial_dev = /dev/ttyACM0 @@ -137,65 +152,86 @@ class Picadae: 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.prescaler_usec = prescaler_usec self.serialProc.start() def close( self ): self.serialProc.quit() + def wait_for_serial_sync(self, timeoutMs=10000): + + # wait for the letter 'a' to come back from the serial port + result = self.block_on_serial_read(1,timeoutMs) + + if result and len(result.value)>0 and result.value[0] == ord('a'): + pass + else: + result.set_error("Serial sync failed.") + + return result + 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_read_addr( self, i2c_addr, mem_id, addr ): + return self. write(i2c_addr, TinyOp.setReadAddr.value,[ mem_id, addr ]) + + def read_request( self, i2c_addr, reg_addr, byteOutN ): + return self._send( 'r', i2c_addr, reg_addr,[ byteOutN ] ) - 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 ): + def block_on_serial_read( self, byteOutN, time_out_ms=250 ): - self.read_request( self, i2c_addr, reg_addr, argL, byteOutN ) - - ts = datetime.datetime.now() + datetime.timeDelta(milliseconds=time_out_ms) - + ts = datetime.datetime.now() + datetime.timedelta(milliseconds=time_out_ms) retL = [] + while datetime.datetime.now() < ts and len(retL) < byteOutN: - + + # If a value is available at the serial port return is otherwise return None. x = self.serialProc.recv() - if x is not None: - retL.append(x) + + if x is not None and x[0] == SerialMsgId.DATA_MSG: + for b in x[1]: + retL.append(int(b)) time.sleep(0.01) - - return retL + + result = Result(value=retL) + + if len(retL) < byteOutN: + result.set_error("Serial port time out on read.") + + return result + + + + def block_on_picadae_read( self, i2c_addr, mem_id, reg_addr, argL, byteOutN, time_out_ms ): + + result = self.set_read_addr( i2c_addr, mem_id, reg_addr ) + + if result: + result = self.read_request( i2c_addr, TinyOp.setReadAddr.value, byteOutN ) + + if result: + result = self.block_on_serial_read( byteOutN, time_out_ms ) + + return result def note_on_vel( self, midi_pitch, midi_vel ): return self.write( self._pitch_to_i2c_addr( midi_pitch ), - TinyOpD['noteOnVelOp'], + TinyOp.noteOnVelOp.value, [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'], + TinyOp.noteOnUsecOp.value, 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. + TinyOp.noteOffOp.value, + [0] ) # TODO: sending a dummy byte because we can't handle sending a command with no data bytes. def set_velocity_map( self, midi_pitch, midi_vel, pulse_usec ): pass @@ -203,14 +239,15 @@ class Picadae: 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 ): + def set_pwm_duty( self, midi_pitch, duty_cycle_pct ): return self.write( self._pitch_to_i2c_addr( midi_pitch ), - TinyOpD['setPwmOp'], - [enableFl, int( duty_cycle_pct * 255.0 /100.0 )]) + TinyOp.setPwmOp.value, + [ 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, + return self.block_on_picadae_read( self._pitch_to_i2c_addr( midi_pitch ), + TinyRegAddr.kRdRegAddrAddr.value, + TinyRegAddr.kPwmDutyAddr.value, [], 1, time_out_ms ) def set_pwm_freq( self, midi_pitch, freq_div_id ): @@ -219,13 +256,14 @@ class Picadae: 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, + return self.block_on_picadae_read( self._pitch_to_i2c_addr( midi_pitch ), + TinyRegAddr.kRdRegAddrAddr.value, + TinyRegAddr.kPwmFreqAddr.value, [], 1, time_out_ms ) def set_flags( self, midi_pitch, flags ): return self.write( self._pitch_to_i2c_addr( midi_pitch ), - TinyOpD['writeOp'], + TinyOp.writeOp.value, [ 12, 14, flags ]) def make_note( self, midi_pitch, atk_us, dur_ms ): @@ -245,7 +283,6 @@ class Picadae: 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) diff --git a/control/app/picadae_cmd.py b/control/app/picadae_cmd.py index 60a44ea..21f6341 100644 --- a/control/app/picadae_cmd.py +++ b/control/app/picadae_cmd.py @@ -275,6 +275,11 @@ class App: # form the command into a byte array cmd_bV = bytearray( [ ord(op_code), i2c_addr, reg_addr, op_byteN ] + dataL ) + + # s = "" + # for i in range(len(cmd_bV)): + # s += "%i " % (cmd_bV[i]) + # print(s) return (cmd_bV,None) diff --git a/control/app/picadae_cmd.yml b/control/app/picadae_cmd.yml index 8e3bbdf..17fda6f 100644 --- a/control/app/picadae_cmd.yml +++ b/control/app/picadae_cmd.yml @@ -4,7 +4,8 @@ serial_dev: "/dev/ttyACM0", serial_baud: 38400, i2c_base_addr: 21, - prescaler_usec: 32, + prescaler_usec: 16, + serial_sync_timeout_ms: 10000, key_mapL: [ diff --git a/control/app/picadae_shell.py b/control/app/picadae_shell.py index 0fc9bc5..dbf1a24 100644 --- a/control/app/picadae_shell.py +++ b/control/app/picadae_shell.py @@ -1,6 +1,7 @@ import os,sys,argparse,yaml,types,select,serial,logging,time from picadae_api import Picadae +from picadae_api import Result class PicadaeShell: def __init__( self, cfg ): @@ -27,6 +28,7 @@ class PicadaeShell: for k,d in self.parseD.items(): s = "{} = {}".format( k, d['help'] ) print(s) + return Result() def _do_write( self, argL ): return self.p.write(argL[0], argL[1], argL[2:]) @@ -69,7 +71,7 @@ class PicadaeShell: def _syntaxError( self, msg ): print("Syntax Error: " + msg ) - return None + return Result() def _exec_cmd( self, tokL ): if len(tokL) <= 0: @@ -95,36 +97,48 @@ class PicadaeShell: if d['varN'] != -1 and len(argL) != d['varN']: return self._syntaxError("Argument mismatch {} != {}.".format(len(argL),d['varN'])) - result = func(argL) + result = func(argL) + - return None + return result def run( self ): # create the API object - self.p = Picadae( cfg.key_mapL, cfg.i2c_base_addr, cfg.serial_dev, cfg.serial_baud ) + self.p = Picadae( cfg.key_mapL, cfg.i2c_base_addr, cfg.serial_dev, cfg.serial_baud, cfg.prescaler_usec ) - print("'q'=quit '?'=help") - time_out_secs = 1 - - while True: + # wait for the letter 'a' to come back from the serial port + result = self.p.wait_for_serial_sync(timeoutMs=cfg.serial_sync_timeout_ms) - # wait for keyboard activity - i, o, e = select.select( [sys.stdin], [], [], time_out_secs ) + if not result: + print("Serial port sync failed.") + else: + print(result.value) + + print("'q'=quit '?'=help") + time_out_secs = 1 - if (i): - # read the command - s = sys.stdin.readline().strip() + while True: - # tokenize the command - tokL = s.split(' ') + # wait for keyboard activity + i, o, e = select.select( [sys.stdin], [], [], time_out_secs ) - # if this is the 'quit' command - if tokL[0] == 'q': - break + if (i): + # read the command + s = sys.stdin.readline().strip() - # execute the command - self._exec_cmd( tokL ) + # tokenize the command + tokL = s.split(' ') + + # if this is the 'quit' command + if tokL[0] == 'q': + break + + # execute the command + result = self._exec_cmd( tokL ) + + if result.value: + print(result.value) self.p.close() diff --git a/control/tiny/Makefile b/control/tiny/Makefile index 5ba6a74..b40c53f 100644 --- a/control/tiny/Makefile +++ b/control/tiny/Makefile @@ -21,12 +21,15 @@ AVRDUDE=avrdude # /usr/bin/avrdude -C/etc/avrdude/avrdude.conf -v -pattiny85 -cstk500v1 -P/dev/ttyACM0 -b19200 -Uflash:w:/tmp/arduino_build_108059/i2c.ino.hex:i # +# lfuse=0xe2 = 8 Mghz +# lfuse=0xe1 = 16 Mghz + all: $(CC) $(CFLAGS) $(TARGET).c usiTwiSlave.c -o$(TARGET) $(OBJ2HEX) -R .eeprom -O ihex $(TARGET) $(TARGET).hex burn: - $(AVRDUDE) -p $(MCU) -P $(TTY) -C/etc/avrdude/avrdude.conf -v -c avrisp -b 19200 -U flash:w:$(TARGET).hex -U lfuse:w:0xe2:m -U hfuse:w:0xdd:m -U efuse:w:0xff:m + $(AVRDUDE) -p $(MCU) -P $(TTY) -C/etc/avrdude/avrdude.conf -v -c avrisp -b 19200 -U flash:w:$(TARGET).hex -U lfuse:w:0xe1:m -U hfuse:w:0xdd:m -U efuse:w:0xff:m clean: rm -f *.hex *.obj *.o diff --git a/control/tiny/i2c_timer_pwm.c b/control/tiny/i2c_timer_pwm.c index 0e17429..2ebc637 100644 --- a/control/tiny/i2c_timer_pwm.c +++ b/control/tiny/i2c_timer_pwm.c @@ -15,7 +15,7 @@ // This program acts as the device (slave) for the control program i2c/a2a/c_ctl -#define F_CPU 8000000L +#define F_CPU 16000000L #include #include @@ -35,13 +35,15 @@ // Opcodes enum { - kSetPwm_Op = 0, // Set PWM registers 0 { { {}}} + 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 + kSetReadAddr_Op = 4, // Set a read addr. 4 {} {} } src: 0=reg 1=table 2=eeprom + kWrite_Op = 5, // Set write 5 { {addr} { ... {}} addrFl:0x80 src: 4=reg 5=table 6=eeprom + kSetMode_Op = 6, // Set the mode flags 6 {} 1=repeat 2=pwm + + kInvalid_Op = 7 // }; @@ -59,26 +61,29 @@ enum 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) + kTmr_Prescale_idx = 10, // Timer 0 clock divider: 1=1,2=8,3=64,4=256,5=1024 Default: 8 (16us) - kPwm_Enable_idx = 11, // - kPwm_Duty_idx = 12, // - kPwm_Freq_idx = 13, // + kPwm_Duty_idx = 11, // + kPwm_Freq_idx = 12, // - kMode_idx = 14, // 1=repeat 2=pwm - kState_idx = 15, // 1=attk 2=hold - kError_Code_idx = 16, // Error Code + kMode_idx = 13, // 1=repeat 2=pwm + kState_idx = 14, // 1=attk 2=hold + kError_Code_idx = 15, // Error Code kMax_idx }; enum { - kTmr_Repeat_Fl= 1, - kTmr_Pwm_Fl = 2, - kAttk_Fl = 1, - kHold_Fl = 2 + kMode_Repeat_Fl = 1, + kMode_Pwm_Fl = 2, + kAttk_Fl = 1, + kHold_Fl = 2 }; + +#define isInRepeatMode() ctl_regs[ kMode_idx ] & kMode_Repeat_Fl +#define isInPwmMode() ctl_regs[ kMode_idx ] & kMode_Pwm_Fl + // 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 @@ -93,15 +98,14 @@ volatile uint8_t ctl_regs[] = 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 + 245, // 8 (0-255) Timer 0 Coarse Value + 25, // 9 (0-255) Timer 0 Fine Value + 4, // 10 (1-5) 4=16us per tick + 127, // 11 (0-255) Pwm Duty cycle + 254, // 12 (0-255) Pwm Frequency (123 hz) + kMode_Repeat_Fl, // 13 mode flags 1=Repeat 2=PWM + 0, // 14 state flags 1=attk 2=hold + 0, // 15 (0-255) Error bit field }; #define tableN 256 @@ -251,7 +255,7 @@ ISR(TIMER0_COMPA_vect) // fine mode // If in repeat mode - if(ctl_regs[kMode_idx] & kTmr_Repeat_Fl) + if(ctl_regs[kMode_idx] & kMode_Repeat_Fl) { uint8_t fl = ctl_regs[kState_idx] & kAttk_Fl; @@ -271,7 +275,7 @@ ISR(TIMER0_COMPA_vect) { clear_attack(); - if( ctl_regs[kMode_idx] & kTmr_Pwm_Fl) + if( ctl_regs[kMode_idx] & kMode_Pwm_Fl) { TIMSK |= _BV(OCIE1B) + _BV(TOIE1); // PWM interupt Enable interrupts } @@ -289,17 +293,13 @@ ISR(TIMER0_COMPA_vect) } -void timer0_init() +void tmr0_init() { TIMSK &= ~_BV(OCIE0A); // Disable interrupt TIMER1_OVF TCCR0A |= 0x02; // CTC mode 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 - - + GTCCR |= _BV(PSR0); // Set the pre-scaler to the selected value } @@ -341,7 +341,7 @@ void pwm1_init() // set on TCNT1 == 0 // happens when TCNT1 matches OCR1C // clr on OCR1B == TCNT // happens when TCNT1 matches OCR1B // // COM1B1=1 COM1B0=0 (enable output on ~OC1B) - TCCR1 |= 9; // 32us period (256 divider) prescaler + TCCR1 |= 10; // 32us period (512 divider) prescaler GTCCR |= _BV(PWM1B); // Enable PWM B and disconnect output pins GTCCR |= _BV(PSR1); // Set the pre-scaler to the selected value @@ -485,13 +485,13 @@ void on_receive( uint8_t byteN ) switch( op_id ) { case kSetPwm_Op: - for(i=0; i 0 ) { ctl_regs[ kRead_Src_idx ] = stack[0]; @@ -515,6 +514,15 @@ void on_receive( uint8_t byteN ) case kWrite_Op: _write_op( stack, stack_idx ); break; + + case kSetMode_Op: + if( stack_idx > 0) + { + ctl_regs[ kMode_idx ] = stack[0]; + tmr0_reset(); + } + + break; } } @@ -523,12 +531,10 @@ int main(void) { cli(); // mask all interupts - 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(); + tmr0_init(); pwm1_init(); // setup i2c library @@ -542,10 +548,12 @@ int main(void) _delay_ms(1000); PINB = _BV(LED_PIN); // writes to PINB toggle the pins + // if in repeat mode + if( ctl_regs[ kMode_idx ] & kMode_Repeat_Fl) + tmr0_reset(); while(1) { - //_delay_ms(1000); if (!usi_onReceiverPtr) {