From 5ffd77da17900eb1a894fbe708e97baab99e99f6 Mon Sep 17 00:00:00 2001 From: "kevin.larke" Date: Mon, 19 Aug 2019 21:27:25 -0400 Subject: [PATCH] Added table writing (not yet tested) and removed 'repeat-mode'. --- control/app/picadae_api.py | 50 ++++----- control/app/picadae_shell.py | 109 ++++++++----------- control/tiny/i2c_timer_pwm.c | 204 +++++++++++++++-------------------- 3 files changed, 154 insertions(+), 209 deletions(-) diff --git a/control/app/picadae_api.py b/control/app/picadae_api.py index e52d194..1ee2c89 100644 --- a/control/app/picadae_api.py +++ b/control/app/picadae_api.py @@ -12,7 +12,7 @@ class TinyOp(Enum): noteOffOp = 3 setReadAddr = 4 writeOp = 5 - setModeOp = 6 + writeTableOp = 6 invalidOp = 7 @@ -30,7 +30,7 @@ class TinyRegAddr(Enum): kTmrPrescaleAddr = 10 kPwmDutyAddr = 11 kPwmFreqAddr = 12 - kModeAddr = 13 + kPwmDivAddr = 13 kStateAddr = 14 kErrorCodeAddr = 15 @@ -155,9 +155,10 @@ class Picadae: self.keyMapD = { d['midi']:d for d in key_mapL } self.i2c_base_addr = i2c_base_addr self.prescaler_usec = prescaler_usec + self.log_level = 0 self.serialProc.start() - + def close( self ): self.serialProc.quit() @@ -179,7 +180,6 @@ class Picadae: def call_op( self, midi_pitch, op_code, argL ): return self.write( self._pitch_to_i2c_addr( midi_pitch ), op_code, argL ) - def set_read_addr( self, i2c_addr, mem_id, addr ): return self. write(i2c_addr, TinyOp.setReadAddr.value,[ mem_id, addr ]) @@ -211,7 +211,7 @@ class Picadae: - def block_on_picadae_read( self, midi_pitch, mem_id, reg_addr, byteOutN, time_out_ms ): + def block_on_picadae_read( self, midi_pitch, mem_id, reg_addr, byteOutN, time_out_ms=250 ): i2c_addr = self._pitch_to_i2c_addr( midi_pitch ) @@ -251,34 +251,33 @@ class Picadae: def get_velocity_map( self, midi_pitch, midi_vel, time_out_ms=250 ): byteOutN = 2 - return self.block_on_picadae_read( midi_pitch, TinyConst.kRdTableSrcId.value, midi_vel*2, byteOutN, time_out_ms ) - - def set_pwm_duty( self, midi_pitch, duty_cycle_pct ): + return self.block_on_picadae_read( midi_pitch, TinyConst.kRdTableSrcId.value, midi_vel*2, byteOutN, time_out_ms ) + + def set_pwm( self, midi_pitch, duty_cycle_pct ): return self.call_op( midi_pitch, TinyOp.setPwmOp.value, [ int( duty_cycle_pct * 255.0 /100.0 )]) - def get_pwm_duty( self, midi_pitch, time_out_ms=250 ): + def get_pwm( self, midi_pitch, time_out_ms=250 ): return self.block_on_picadae_read_reg( midi_pitch, TinyRegAddr.kPwmDutyAddr.value, time_out_ms=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.block_on_picadae_read_reg( midi_pitch, TinyRegAddr.kPwmFreqAddr.value, time_out_ms=time_out_ms ) - def set_mode( self, midi_pitch, mode ): - # TODO validate mode value - return self.call_op( midi_pitch, TinyOp.setModeOp.value, [ mode ] ) - - def get_mode( self, midi_pitch, time_out_ms=250 ): - return self.block_on_picadae_read_reg( midi_pitch, TinyRegAddr.kModeAddr.value, time_out_ms=time_out_ms ) + def get_pwm_div( self, midi_pitch, time_out_ms=250 ): + return self.block_on_picadae_read_reg( midi_pitch, TinyRegAddr.kPwmDivAddr.value, time_out_ms=time_out_ms ) + + def write_table( self, midi_pitch, time_out_ms=250 ): + # TODO: sending a dummy byte because we can't handle sending a command with no data bytes. + return self.call_op( midi_pitch, TinyOp.writeTableOp.value,[0]) 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 set_log_level( self, log_level ): + self.log_level = log_level + return Result() def _pitch_to_i2c_addr( self, pitch ): return self.keyMapD[ pitch ]['index'] + self.i2c_base_addr @@ -305,11 +304,12 @@ class Picadae: 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) + if self.log_level: + s = "{} {} {}".format( opcode, i2c_addr, reg_addr ) + + for x in byteL: + s += " {}".format(x) - print(s) + print(s) diff --git a/control/app/picadae_shell.py b/control/app/picadae_shell.py index 98a1e30..ae7c655 100644 --- a/control/app/picadae_shell.py +++ b/control/app/picadae_shell.py @@ -7,77 +7,44 @@ 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 "}, - 'M':{ "func":"set_mode", "varN":2, "help":"set_mode (1=repeat 2=pwm)" }, - 'm':{ "func":"get_mode", "varN":1, "help":"get_mode "}, - 'N':{ "func":"make_note", "varN":3, "help":"note atkUs durMs"}, + 'q':{ "func":None, "minN":0, "maxN":0, "help":"quit"}, + '?':{ "func":"_help", "minN":0, "maxN":0, "help":"Print usage text."}, + 'w':{ "func":"_write", "minN":-1, "maxN":-1,"help":"write ... "}, + 'r':{ "func":"_read", "minN":4, "maxN":4, "help":"read "}, + 'v':{ "func":"note_on_vel", "minN":2, "maxN":2, "help":"note-on "}, + 'u':{ "func":"note_on_us", "minN":2, "maxN":3, "help":"note-on (1=1, 2=8, 3=64,(4)=256 16us, 5=1024)"}, + 'o':{ "func":"note_off", "minN":1, "maxN":1, "help":"note-off "}, + 'T':{ "func":"set_vel_map", "minN":3, "maxN":3, "help":"table "}, + 't':{ "func":"get_vel_map", "minN":2, "maxN":2, "help":"table "}, + 'D':{ "func":"set_pwm", "minN":2, "maxN":4, "help":"duty { {
}} div:2=2,3=4,4=8,5=16,6=32,7=64,8=128,9=256,(10)=512 32us, 11=1024,12=2048,13=4096,14=8192,15=16384" }, + 'd':{ "func":"get_pwm_duty", "minN":1, "maxN":1, "help":"duty "}, + 'f':{ "func":"get_pwm_freq", "minN":1, "maxN":1, "help":"freq "}, + 'i':{ "func":"get_pwm_div", "minN":1, "maxN":1, "help":"div "}, + 'W':{ "func":"write_table", "minN":1, "maxN":1, "help":"write_table "}, + 'N':{ "func":"make_note", "minN":3, "maxN":3, "help":"note atkUs durMs"}, + 'L':{ "func":"set_log_level","minN":1, "maxN":1, "help":"log (0-1)."} } - def _do_help( self, _ ): + def _help( self, _=None ): for k,d in self.parseD.items(): s = "{} = {}".format( k, d['help'] ) print(s) return Result() - def _do_write( self, argL ): + def _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_mode( self, argL ): - return self.p.set_mode(*argL) - - def _do_get_mode( self, argL ): - return self.p.get_mode(*argL) - - def _do_make_note( self, argL ): - return self.p.make_note(*argL) + def _read( self, argL ): + return self.p.block_on_picadae_read(argL[0], argL[1], argL[2], argL[3]) def _syntaxError( self, msg ): print("Syntax Error: " + msg ) return Result() def _exec_cmd( self, tokL ): + + result = Result() + if len(tokL) <= 0: return None @@ -88,21 +55,29 @@ class PicadaeShell: d = self.parseD[ opcode ] - func_name = "_do_" + d['func'] + func_name = d['func'] + func = None + # find the function associated with this command if hasattr(self, func_name ): - func = getattr(self, func_name ) + func = getattr(self, func_name ) + elif hasattr(self.p, func_name ): + func = getattr(self.p, func_name ) + else: + return self._syntaxError("Exec function not found: '{}'.".format(func_name)) - try: - argL = [ int(tokL[i]) for i in range(1,len(tokL)) ] - except: - return self._syntaxError("Unable to create integer arguments.") + try: + # convert the parameter list into integers + 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) - + # validate the count of command args + if d['minN'] != -1 and (d['minN'] > len(argL) or len(argL) > d['maxN']): + return self._syntaxError("Argument count mismatch. {} is out of range:{} to {}".format(len(argL),d['minN'],d['maxN'])) + + # call the command function + result = func(*argL) return result diff --git a/control/tiny/i2c_timer_pwm.c b/control/tiny/i2c_timer_pwm.c index c883e86..29734c9 100644 --- a/control/tiny/i2c_timer_pwm.c +++ b/control/tiny/i2c_timer_pwm.c @@ -35,13 +35,13 @@ // 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 - 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 + kSetPwm_Op = 0, // Set PWM duty/hz/div 0 { { {
}}} div:2=2,3=4,4=8,5=16,6=32,7=64,8=128,9=256,10=512,11=1024,12=2048,13=4096,14=8192,15=16384 + kNoteOnVel_Op = 1, // Turn on note 3 {} + kNoteOnUsec_Op = 2, // Turn on note 4 { { {}}} + kNoteOff_Op = 3, // Turn off note 5 + kSetReadAddr_Op = 4, // Set a read addr. 6 {} {} } src: 0=reg 1=table 2=eeprom + kWrite_Op = 5, // Set write 7 { {addr} { ... {}} addrFl:0x80 src: 4=reg 5=table 6=eeprom + kWriteTable_Op = 6, // Write table to EEprom 9 kInvalid_Op = 7 // }; @@ -60,33 +60,25 @@ 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 (16us) + kTmr_Prescale_idx = 10, // Timer 0 clock divider: 1=1,2=8,3=64,4=256,5=1024 Default: 4 (16us) kPwm_Duty_idx = 11, // kPwm_Freq_idx = 12, // + kPwm_Div_idx = 13, // - kMode_idx = 13, // 1=repeat 2=pwm kState_idx = 14, // 1=attk 2=hold kError_Code_idx = 15, // Error Code + kMax_Coarse_Tmr_idx = 16, // Max. allowable coarse timer value kMax_idx }; enum { - kMode_Repeat_Fl = 1, - kMode_Pwm_Fl = 2, - kAttk_Fl = 1, - kHold_Fl = 2 + kState_Attk_Fl = 1, + kState_Hold_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 - volatile uint8_t ctl_regs[] = { 0, // 0 (0-(kMax_idx-1)) Reg Read Addr @@ -99,26 +91,39 @@ volatile uint8_t ctl_regs[] = 0, // 6 (0-255) EE Write Addr kReg_Wr_Addr_idx, // 7 (0-2) Write source - 245, // 8 (0-255) Timer 0 Coarse Value - 25, // 9 (0-255) Timer 0 Fine Value + 5, // 8 (0-255) Timer 0 Coarse Value (20400 us) + 0, // 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) + 10, // 13 (0-15) Pwm clock div - kMode_Repeat_Fl, // 13 mode flags 1=Repeat 2=PWM - 0, // 14 state flags 1=attk 2=hold + 0, // 14 state flags 1=attk 2=hold (read/only) 0, // 15 (0-255) Error bit field + 14, // 16 (0-255) Max allowable coarse timer count }; +// These registers are saved to Eeprom +uint8_t eeprom_addr[] = +{ + kTmr_Prescale_idx, + kPwm_Duty_idx, + kPwm_Freq_idx, + kPwm_Div_idx +}; + + + #define tableN 256 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 + kInvalid_Read_Src_ErrFl = 0x01, + kInvalid_Write_Dst_ErrFl = 0x02, + kInvalid_Coarse_Tmr_ErrFl = 0x04 }; #define set_error( flag ) ctl_regs[ kError_Code_idx ] |= (flag) @@ -143,7 +148,6 @@ void EEPROM_write(uint8_t ucAddress, uint8_t ucData) EECR |= (1< 127 -// r 8 kTable_Fine_idx -> 64 - -/* -#define eeprom_addr( addr ) (kMax_idx + (addr)) - -void table_write_cur_value( void ) +void write_table() { - uint8_t tbl_addr = ctl_regs[ kTable_Addr_idx ] * 2; - - table[ tbl_addr+0 ] = ctl_regs[ kTable_Coarse_idx ]; - table[ tbl_addr+1 ] = ctl_regs[ kTable_Fine_idx ]; + uint8_t i; + uint8_t regN = sizeof(eeprom_addr); - EEPROM_write( eeprom_addr( tbl_addr+0 ), ctl_regs[ kTable_Coarse_idx ] ); - EEPROM_write( eeprom_addr( tbl_addr+1 ), ctl_regs[ kTable_Fine_idx ]); + // write the persistent registers + for(i=0; i 0 ) { @@ -230,11 +221,7 @@ void tmr0_reset() 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 + TIMSK |= _BV(OCIE0A); // enable the timer interrupt } ISR(TIMER0_COMPA_vect) @@ -242,7 +229,7 @@ ISR(TIMER0_COMPA_vect) switch( tmr0_state ) { case 0: - // disabled + // timer is disabled break; case 1: @@ -257,39 +244,12 @@ ISR(TIMER0_COMPA_vect) case 2: // fine mode - // If in repeat mode - if(ctl_regs[kMode_idx] & kMode_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(); - } + // This marks the end of a timer period - // In repeat mode we run the PWM output continuously - TIMSK |= _BV(OCIE1B) + _BV(TOIE1); // Enable PWM interrupts - - } - else // not in repeat mode - { - clear_attack(); + clear_attack(); - if( ctl_regs[kMode_idx] & kMode_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 - - } + TIMSK |= _BV(OCIE1B) + _BV(TOIE1); // PWM interupt Enable interrupts + TIMSK &= ~_BV(OCIE0A); // clear timer interrupt break; } @@ -343,7 +303,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 |= 10; // 32us period (512 divider) prescaler + TCCR1 |= ctl_regs[ kPwm_Div_idx]; // 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 @@ -487,20 +447,38 @@ void on_receive( uint8_t byteN ) switch( op_id ) { case kSetPwm_Op: - for(i=0; i ctl_regs[ kMax_Coarse_Tmr_idx ]) + { + ctl_regs[ kTmr_Coarse_idx ] = ctl_regs[ kMax_Coarse_Tmr_idx ]; + set_error( kInvalid_Coarse_Tmr_ErrFl ); + } + // if a prescaler was included then the timer needs to be re-initialized + if( i == 3 ) + tmr0_init(); + tmr0_reset(); break; case kNoteOff_Op: + TIMSK &= ~_BV(OCIE0A); // clear timer interrupt (shouldn't be necessary) TIMSK &= ~(_BV(OCIE1B) + _BV(TOIE1)); // PWM interupt disable interrupts - PORTB &= ~_BV(HOLD_PIN); // clear the HOLD pin + PORTB &= ~_BV(HOLD_PIN); // clear the HOLD pin break; case kSetReadAddr_Op: @@ -517,13 +495,8 @@ void on_receive( uint8_t byteN ) _write_op( stack, stack_idx ); break; - case kSetMode_Op: - if( stack_idx > 0) - { - ctl_regs[ kMode_idx ] = stack[0]; - tmr0_reset(); - } - + case kWriteTable_Op: + write_table(); break; } } @@ -550,9 +523,6 @@ 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) {