diff --git a/example-colorlight-i5.py b/example-colorlight-i5.py index 8ad7910..d806a1a 100755 --- a/example-colorlight-i5.py +++ b/example-colorlight-i5.py @@ -23,6 +23,8 @@ from eurorack_pmod_migen.core import * from eurorack_pmod_migen.blocks import * +from rtl.pca9635_master import * + _io_eurolut_proto1 = [ ("eurorack_pmod_p3b", 0, Subsignal("mclk", Pins("A3")), @@ -77,6 +79,12 @@ Subsignal("sw_n", Pins("E19"), Misc("PULLMODE=NONE")), IOStandard("LVCMOS33") ), + ("pca9635", 0, + Subsignal("i2c_sda", Pins("F1")), + Subsignal("i2c_scl", Pins("F2")), + Subsignal("oan", Pins("G3"), Misc("PULLMODE=DOWN")), + IOStandard("LVCMOS33") + ), ("pmod_aux1", 0, Subsignal("p5", Pins("N17")), Subsignal("p6", Pins("N18")), @@ -153,6 +161,10 @@ def add_oled(soc): spi_master.add_clk_divider() soc.submodules.oled_ctl = GPIOOut(soc.platform.request("oled_ctl")) +def add_pca9635_master(soc): + pca9635_master = PCA9635Master(soc.platform, soc.platform.request("pca9635")) + soc.add_module("pca9635", pca9635_master) + class RotaryEncoder(Module, AutoCSR): def __init__(self, in_i, in_q): # two incoming bits for in-phase and quadrature (A and B) inputs @@ -276,6 +288,8 @@ def main(): add_encoder(soc) + add_pca9635_master(soc) + # Useful to double-check connectivity ... """ clkdiv_test = Signal(8) diff --git a/firmware/litex-fw/src/gw.rs b/firmware/litex-fw/src/gw.rs index b16371a..420e3eb 100644 --- a/firmware/litex-fw/src/gw.rs +++ b/firmware/litex-fw/src/gw.rs @@ -14,6 +14,11 @@ pub trait KarlsenLpf { fn set_resonance(&self, value: i16); } +pub trait PwmLed { + fn reset_line(&self, set_high: bool); + fn led(&self, ix: usize, value: u8); +} + macro_rules! eurorack_pmod { ($($t:ty),+ $(,)?) => { $(impl EurorackPmod for $t { @@ -72,6 +77,41 @@ macro_rules! karlsen_lpf { }; } +macro_rules! pwm_led { + ($($t:ty),+ $(,)?) => { + $(impl PwmLed for $t { + fn reset_line(&self, set_high: bool) { + self.csr_reset.write(|w| unsafe { w.bits(set_high as u32) }); + } + fn led(&self, ix: usize, value: u8) { + unsafe { + let v = value as u32; + match ix { + 0 => self.led0.write(|w| w.bits(v)), + 1 => self.led1.write(|w| w.bits(v)), + 2 => self.led2.write(|w| w.bits(v)), + 3 => self.led3.write(|w| w.bits(v)), + 4 => self.led4.write(|w| w.bits(v)), + 5 => self.led5.write(|w| w.bits(v)), + 6 => self.led6.write(|w| w.bits(v)), + 7 => self.led7.write(|w| w.bits(v)), + 8 => self.led8.write(|w| w.bits(v)), + 9 => self.led9.write(|w| w.bits(v)), + 10 => self.led10.write(|w| w.bits(v)), + 11 => self.led11.write(|w| w.bits(v)), + 12 => self.led12.write(|w| w.bits(v)), + 13 => self.led13.write(|w| w.bits(v)), + 14 => self.led14.write(|w| w.bits(v)), + 15 => self.led15.write(|w| w.bits(v)), + _ => panic!("bad index") + } + } + } + })+ + }; +} + pub(crate) use eurorack_pmod; pub(crate) use wavetable_oscillator; pub(crate) use karlsen_lpf; +pub(crate) use pwm_led; diff --git a/firmware/litex-fw/src/main.rs b/firmware/litex-fw/src/main.rs index 9c01489..5818917 100644 --- a/firmware/litex-fw/src/main.rs +++ b/firmware/litex-fw/src/main.rs @@ -41,6 +41,7 @@ karlsen_lpf!(pac::KARLSEN_LPF0); karlsen_lpf!(pac::KARLSEN_LPF1); karlsen_lpf!(pac::KARLSEN_LPF2); karlsen_lpf!(pac::KARLSEN_LPF3); +pwm_led!(pac::PCA9635); const SYSTEM_CLOCK_FREQUENCY: u32 = 60_000_000; @@ -147,14 +148,17 @@ fn main() -> ! { &peripherals.KARLSEN_LPF3, ]; + let pca9635 = peripherals.PCA9635; let uart_midi = UartMidi::new(peripherals.UART_MIDI); let mut timer = Timer::new(peripherals.TIMER0, SYSTEM_CLOCK_FREQUENCY); + pca9635.reset_line(true); pmod0.reset_line(true); pmod1.reset_line(true); timer.delay_ms(10u32); + pca9635.reset_line(false); pmod0.reset_line(false); pmod1.reset_line(false); @@ -206,9 +210,21 @@ fn main() -> ! { let mut cycle_cnt = riscv::register::cycle::read() as u32; let mut td_us: Option = None; + let mut v = 0u8; loop { + v += 3; + + for i in 0..=15 { + let this_v = v+i*16; + if this_v < 128 { + pca9635.led(i.into(), this_v); + } else { + pca9635.led(i.into(), 128-this_v); + } + } + while let Ok(event) = midi_in.read() { match event { MidiMessage::NoteOn(_, note, velocity) => { @@ -282,6 +298,10 @@ fn main() -> ! { peripherals.ENCODER_BUTTON.in_.read().bits(), ]).ok(); + if peripherals.ENCODER_BUTTON.in_.read().bits() != 0 { + peripherals.CTRL.reset.write(|w| unsafe { w.soc_rst().bit(true) }); + } + draw_titlebox(&mut disp, 16, "PMOD1", &[ "ser:", "jck:", diff --git a/rtl/pca9635_master.py b/rtl/pca9635_master.py new file mode 100644 index 0000000..4d1cf87 --- /dev/null +++ b/rtl/pca9635_master.py @@ -0,0 +1,109 @@ +import os + +from migen import * + +from litex.soc.interconnect.csr import * + +ROOT = os.path.dirname(os.path.realpath(__file__)) +PMOD_ROOT = os.path.join(ROOT, "../deps/eurorack-pmod/gateware") + +class PCA9635Master(Module, AutoCSR): + def __init__(self, platform, pads): + + # Configuration .hex + + self.led_cfg_file = os.path.join(PMOD_ROOT, "drivers/pca9635-cfg.hex") + + # Exposed signals + + self.clk_256fs = ClockSignal("clk_256fs") + self.rst = Signal() + + self.csr_reset = CSRStorage(1) + + self.led0 = CSRStorage(8) + self.led1 = CSRStorage(8) + self.led2 = CSRStorage(8) + self.led3 = CSRStorage(8) + self.led4 = CSRStorage(8) + self.led5 = CSRStorage(8) + self.led6 = CSRStorage(8) + self.led7 = CSRStorage(8) + self.led8 = CSRStorage(8) + self.led9 = CSRStorage(8) + self.led10 = CSRStorage(8) + self.led11 = CSRStorage(8) + self.led12 = CSRStorage(8) + self.led13 = CSRStorage(8) + self.led14 = CSRStorage(8) + self.led15 = CSRStorage(8) + + # Internal signals + + self.i2c_scl_oe = Signal() + self.i2c_scl_i = Signal() + self.i2c_sda_oe = Signal() + self.i2c_sda_i = Signal() + + # Verilog sources + + platform.add_verilog_include_path(ROOT) + platform.add_verilog_include_path(PMOD_ROOT) + platform.add_sources(ROOT, "pca9635_master.sv") + platform.add_sources(PMOD_ROOT, "external/no2misc/rtl/i2c_master.v") + + self.specials += Instance("pca9635_master", + # Parameters + p_LED_CFG = self.led_cfg_file, + + # Ports (clk + reset) + i_clk = self.clk_256fs, + i_rst = self.rst, + + # Pads (tristate, require different logic to hook these + # up to pads depending on the target platform). + o_scl_oe = self.i2c_scl_oe, + i_scl_i = self.i2c_scl_i, + o_sda_oe = self.i2c_sda_oe, + i_sda_i = self.i2c_sda_i, + + i_led0 = self.led0.storage, + i_led1 = self.led1.storage, + i_led2 = self.led2.storage, + i_led3 = self.led3.storage, + i_led4 = self.led4.storage, + i_led5 = self.led5.storage, + i_led6 = self.led6.storage, + i_led7 = self.led7.storage, + i_led8 = self.led8.storage, + i_led9 = self.led9.storage, + i_led10 = self.led10.storage, + i_led11 = self.led11.storage, + i_led12 = self.led12.storage, + i_led13 = self.led13.storage, + i_led14 = self.led14.storage, + i_led15 = self.led15.storage, + ) + + + # FIXME: For now these tristate implementations are ECP5 specific. + + self.specials += Instance("TRELLIS_IO", + p_DIR = "BIDIR", + i_B = pads.i2c_scl, + i_I = 0, + o_O = self.i2c_scl_i, + i_T = ~self.i2c_scl_oe + ) + + self.specials += Instance("TRELLIS_IO", + p_DIR = "BIDIR", + i_B = pads.i2c_sda, + i_I = 0, + o_O = self.i2c_sda_i, + i_T = ~self.i2c_sda_oe + ) + + self.comb += [ + self.rst.eq(self.csr_reset.storage), + ] diff --git a/rtl/pca9635_master.sv b/rtl/pca9635_master.sv new file mode 100644 index 0000000..b689cd8 --- /dev/null +++ b/rtl/pca9635_master.sv @@ -0,0 +1,141 @@ +`default_nettype none + +module pca9635_master #( + parameter LED_CFG = "", + parameter LED_CFG_BYTES = 16'd26 +)( + input clk, + input rst, + + // I2C signals to be routed to IO. + output scl_oe, + input scl_i, + output sda_oe, + input sda_i, + + input [7:0] led0, + input [7:0] led1, + input [7:0] led2, + input [7:0] led3, + input [7:0] led4, + input [7:0] led5, + input [7:0] led6, + input [7:0] led7, + input [7:0] led8, + input [7:0] led9, + input [7:0] led10, + input [7:0] led11, + input [7:0] led12, + input [7:0] led13, + input [7:0] led14, + input [7:0] led15, +); + +// Overall state machine of this core. +localparam I2C_LED1 = 0, + I2C_LED2 = 1; + + +logic [3:0] i2c_state = I2C_LED1; + +// Index into i2c config memories +logic [15:0] i2c_config_pos = 0; + +// Logic for startup configuration of LEDs over I2C. +logic [7:0] led_config [0:LED_CFG_BYTES-1]; +initial $readmemh(LED_CFG, led_config); + +// Index at which PWM values start in the led config. +localparam PCA9635_PWM0 = 4; + +// Valid commands for `i2c_master` core. +localparam [1:0] I2CMASTER_START = 2'b00, + I2CMASTER_STOP = 2'b01, + I2CMASTER_WRITE = 2'b10, + I2CMASTER_READ = 2'b11; + +// Outbound signals to `i2c_master` core. +logic [7:0] data_in; +logic ack_in; +logic [1:0] cmd; +logic stb = 1'b0; + +// Inbound signals from `i2c_master` core. +logic [7:0] data_out; +logic ack_out; +logic err_out; +logic ready; + +always_ff @(posedge clk) begin + if (rst) begin + i2c_state <= I2C_LED1; + end else begin + if (ready && ~stb) begin + case (i2c_state) + I2C_LED1: begin + cmd <= I2CMASTER_START; + stb <= 1'b1; + i2c_state <= I2C_LED2; + i2c_config_pos <= 0; + end + I2C_LED2: begin + case (i2c_config_pos) + LED_CFG_BYTES: begin + cmd <= I2CMASTER_STOP; + i2c_state <= I2C_LED1; + end + default: begin + data_in <= led_config[5'(i2c_config_pos)]; + cmd <= I2CMASTER_WRITE; + end + // Override PWM values from led configuration. + PCA9635_PWM0 + 0: data_in <= led0; + PCA9635_PWM0 + 1: data_in <= led1; + PCA9635_PWM0 + 2: data_in <= led2; + PCA9635_PWM0 + 3: data_in <= led3; + PCA9635_PWM0 + 4: data_in <= led4; + PCA9635_PWM0 + 5: data_in <= led5; + PCA9635_PWM0 + 6: data_in <= led6; + PCA9635_PWM0 + 7: data_in <= led7; + PCA9635_PWM0 + 8: data_in <= led8; + PCA9635_PWM0 + 9: data_in <= led9; + PCA9635_PWM0 + 10: data_in <= led10; + PCA9635_PWM0 + 11: data_in <= led11; + PCA9635_PWM0 + 12: data_in <= led12; + PCA9635_PWM0 + 13: data_in <= led13; + PCA9635_PWM0 + 14: data_in <= led14; + PCA9635_PWM0 + 15: data_in <= led15; + endcase + i2c_config_pos <= i2c_config_pos + 1; + ack_in <= 1'b1; + stb <= 1'b1; + end + endcase + end else begin + stb <= 1'b0; + end + end +end + +i2c_master #(.DW(4)) i2c_master_inst( + .scl_oe(scl_oe), + .scl_i(scl_i), + .sda_oe(sda_oe), + .sda_i(sda_i), + + .data_in(data_in), + .ack_in(ack_in), + .cmd(cmd), + .stb(stb), + + .data_out(data_out), + .ack_out(ack_out), + .err_out(err_out), + + .ready(ready), + + .clk(clk), + .rst(rst) +); + +endmodule