Skip to content

Commit

Permalink
add pca9635 I2C LED master controller
Browse files Browse the repository at this point in the history
  • Loading branch information
vk2seb committed Sep 2, 2023
1 parent d2b8ed2 commit 80d7614
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 0 deletions.
14 changes: 14 additions & 0 deletions example-colorlight-i5.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand Down Expand Up @@ -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")),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -276,6 +288,8 @@ def main():

add_encoder(soc)

add_pca9635_master(soc)

# Useful to double-check connectivity ...
"""
clkdiv_test = Signal(8)
Expand Down
40 changes: 40 additions & 0 deletions firmware/litex-fw/src/gw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
20 changes: 20 additions & 0 deletions firmware/litex-fw/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -206,9 +210,21 @@ fn main() -> ! {

let mut cycle_cnt = riscv::register::cycle::read() as u32;
let mut td_us: Option<u32> = 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) => {
Expand Down Expand Up @@ -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:",
Expand Down
109 changes: 109 additions & 0 deletions rtl/pca9635_master.py
Original file line number Diff line number Diff line change
@@ -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),
]
Loading

0 comments on commit 80d7614

Please sign in to comment.