├── .gitignore ├── examples └── bluepill │ ├── memory.x │ ├── README.md │ ├── Cargo.toml │ ├── .cargo │ └── config │ └── examples │ ├── channels │ └── mod.rs │ └── read_all.rs ├── Cargo.toml ├── README.md ├── src └── lib.rs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /examples/*/target 3 | Cargo.lock 4 | *.code-workspace 5 | -------------------------------------------------------------------------------- /examples/bluepill/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | /* TODO Adjust these memory regions to match your device memory layout */ 5 | /* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */ 6 | FLASH : ORIGIN = 0x08000000, LENGTH = 64K 7 | RAM : ORIGIN = 0x20000000, LENGTH = 20K 8 | } 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "analog-multiplexer" 3 | version = "1.0.2" 4 | authors = ["Riskable "] 5 | edition = "2018" 6 | description = "A platform agnostic driver for 4051 and 4067 series analog multiplexers targetting the Rust embedded-hal" 7 | license = "Apache-2.0" 8 | readme = "README.md" 9 | categories = ["embedded", "hardware-support", "no-std"] 10 | keywords = ["embedded-hal-driver", "4051", "4067", "74hc4067", "74hc4051"] 11 | repository = "https://github.com/riskable/analog-multiplexer" 12 | documentation = "https://docs.rs/analog-multiplexer" 13 | exclude = [ 14 | ".gitignore", 15 | "target/*" 16 | ] 17 | 18 | [dependencies] 19 | embedded-hal = "0.2.4" -------------------------------------------------------------------------------- /examples/bluepill/README.md: -------------------------------------------------------------------------------- 1 | # Example: read_all 2 | 3 | This Blue Pill board example reads all 16 (or 8 with minor changes) channels on a 4067 (or 4051) series analog multiplexer every 100ms and pretty-prints the results over an ST-LINK debugger via probe-rs (`rprintln!()`). 4 | 5 | # Usage 6 | 7 | ```shell 8 | $ cd examples/bluepill # Make sure you're in the right place 9 | $ cargo run --example read_all 10 | ``` 11 | 12 | # Demonstration 13 | 14 | Here's an imaginary example using a 74HC4067 with a Blue Pill (stm32f104) board... 15 | 16 | Here's me using it to read a hall effect sensor on channel 15: 17 | 18 | ![Reading a hall effect sensor on channel 15](https://thumbs.gfycat.com/FlippantAptHadrosaurus-size_restricted.gif) -------------------------------------------------------------------------------- /examples/bluepill/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "analog-multiplexer-bluepill" 3 | version = "0.1.1" 4 | authors = ["Riskable "] 5 | edition = "2018" 6 | description = "Library for working with 4015 and 4067 series analog multiplexers" 7 | license = "Apache-2.0" 8 | readme = "README.md" 9 | categories = ["embedded", "hardware-support", "no-std"] 10 | keywords = ["4051", "4067", "74hc4067", "74hc4051"] 11 | exclude = [ 12 | ".gitignore", 13 | "target/*" 14 | ] 15 | 16 | [dependencies] 17 | analog-multiplexer = { path = "../../../analog-multiplexer" } 18 | embedded-hal = "0.2.4" 19 | panic-halt = "0.2" 20 | cortex-m = "0.6.4" 21 | cortex-m-rtic = "0.5.5" 22 | 23 | [dependencies.rtt-target] 24 | version = "0.2.2" 25 | features = ["cortex-m"] 26 | 27 | [dependencies.stm32f1xx-hal] 28 | version = "0.7.0" 29 | features = ["rt", "stm32f103", "medium"] 30 | 31 | [profile.dev] 32 | opt-level = 1 33 | codegen-units = 16 34 | debug = true 35 | lto = false 36 | 37 | [profile.release] 38 | opt-level = "s" # optimize for size 39 | codegen-units = 1 # better optimizations 40 | debug = true # symbols are nice and they don't increase the size on Flash 41 | lto = true # better optimizations 42 | -------------------------------------------------------------------------------- /examples/bluepill/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | # uncomment this to make `cargo run` execute programs on QEMU 3 | # runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" 4 | 5 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 6 | # uncomment ONE of these three option to make `cargo run` start a GDB session 7 | # which option to pick depends on your system 8 | # runner = "arm-none-eabi-gdb -q -x jlink.gdb" 9 | # runner = "arm-none-eabi-gdb -q -x openocd.gdb" 10 | # runner = "gdb-multiarch -q -x openocd.gdb" 11 | # runner = "gdb -q -x openocd.gdb" 12 | runner = "probe-run --chip STM32F103C8" 13 | 14 | rustflags = [ 15 | # LLD (shipped with the Rust toolchain) is used as the default linker 16 | "-C", "link-arg=-Tlink.x", 17 | 18 | # if you run into problems with LLD switch to the GNU linker by commenting out 19 | # this line 20 | # "-C", "linker=arm-none-eabi-ld", 21 | 22 | # if you need to link to pre-compiled C libraries provided by a C toolchain 23 | # use GCC as the linker by commenting out both lines above and then 24 | # uncommenting the three lines below 25 | # "-C", "linker=arm-none-eabi-gcc", 26 | # "-C", "link-arg=-Wl,-Tlink.x", 27 | # "-C", "link-arg=-nostartfiles", 28 | 29 | # uncomment for unchecked wrapping arithmetics also in dev mode 30 | # "-Z", "force-overflow-checks=off", 31 | ] 32 | 33 | [build] 34 | # Pick ONE of these compilation targets 35 | # target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ 36 | target = "thumbv7m-none-eabi" # Cortex-M3 37 | # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) 38 | # target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) 39 | 40 | -------------------------------------------------------------------------------- /examples/bluepill/examples/channels/mod.rs: -------------------------------------------------------------------------------- 1 | //! This little module provides a place to store analog channel data (`ChannelValues`) 2 | //! and also provides a super user-friendly channel data viewing experience 3 | //! via `core::fmt::Display`. Only meant for the examples but if you so desire 4 | //! feel free to copy it into your own code for use with debugging. 5 | 6 | // Struct for storing the state of each channel and pretty-printing it via rprintln 7 | #[derive(Default)] 8 | pub struct ChannelValues { 9 | pub ch0: u16, 10 | pub ch1: u16, 11 | pub ch2: u16, 12 | pub ch3: u16, 13 | pub ch4: u16, 14 | pub ch5: u16, 15 | pub ch6: u16, 16 | pub ch7: u16, 17 | // NOTE: Channels past 7 will remain 0 if using an 8-channel multiplexer 18 | pub ch8: u16, 19 | pub ch9: u16, 20 | pub ch10: u16, 21 | pub ch11: u16, 22 | pub ch12: u16, 23 | pub ch13: u16, 24 | pub ch14: u16, 25 | pub ch15: u16, 26 | } 27 | 28 | impl ChannelValues { 29 | pub fn by_index(&self, i: u8) -> u16 { 30 | match i { 31 | 0 => self.ch0, 32 | 1 => self.ch1, 33 | 2 => self.ch2, 34 | 3 => self.ch3, 35 | 4 => self.ch4, 36 | 5 => self.ch5, 37 | 6 => self.ch6, 38 | 7 => self.ch7, 39 | 8 => self.ch8, 40 | 9 => self.ch9, 41 | 10 => self.ch10, 42 | 11 => self.ch11, 43 | 12 => self.ch12, 44 | 13 => self.ch13, 45 | 14 => self.ch14, 46 | 15 => self.ch15, 47 | _ => panic!("Invalid channel: {}", i), 48 | } 49 | } 50 | 51 | pub fn update_by_index(&mut self, i: u8, val: u16) { 52 | match i { 53 | 0 => self.ch0 = val, 54 | 1 => self.ch1 = val, 55 | 2 => self.ch2 = val, 56 | 3 => self.ch3 = val, 57 | 4 => self.ch4 = val, 58 | 5 => self.ch5 = val, 59 | 6 => self.ch6 = val, 60 | 7 => self.ch7 = val, 61 | 8 => self.ch8 = val, 62 | 9 => self.ch9 = val, 63 | 10 => self.ch10 = val, 64 | 11 => self.ch11 = val, 65 | 12 => self.ch12 = val, 66 | 13 => self.ch13 = val, 67 | 14 => self.ch14 = val, 68 | 15 => self.ch15 = val, 69 | _ => panic!("Invalid channel: {}", i), 70 | } 71 | } 72 | } 73 | 74 | // impl our super user-friendly terminal view into all channels 75 | impl core::fmt::Display for ChannelValues { 76 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 77 | let _ = f.write_str("\x1B[2J\x1B[0H"); // Clear the screen and move cursor to start 78 | let _ = f.write_str("Multiplexer Channel Values:\n"); 79 | let _ = f.write_str("\n\x1B[1mch0\tch1\tch2\tch3\tch4\tch5\tch6\tch7\n\x1B[0m"); 80 | for i in 0..8 { 81 | let _ = f.write_fmt(format_args!("{}\t", self.by_index(i))).unwrap(); 82 | } 83 | let _ = f.write_str("\n"); 84 | let _ = f.write_str("\x1B[1mch8\tch9\tch10\tch11\tch12\tch13\tch14\tch15\n\x1B[0m"); 85 | for i in 8..16 { 86 | let _ = f.write_fmt(format_args!("{}\t", self.by_index(i))).unwrap(); 87 | } 88 | let _ = f.write_str("\n"); 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # analog-multiplexer 2 | A platform agnostic driver for 4051 and 4067 series analog multiplexers targetting the Rust embedded-hal 3 | 4 | This crate provides an interface, `Multiplexer` that makes it trivially easy 5 | to select channels on any given 74HC4051 or 74HC4067 series analog multiplexer. 6 | Internally it keeps track of each multiplexer's state, allowing you to 7 | check what channel is presently active or to enable/disable the multiplexer 8 | at will. 9 | 10 | # Supported Hardware (Analog Multiplexers) 11 | 12 | * 4067 series: [74HC4067](https://assets.nexperia.com/documents/data-sheet/74HC_HCT4067.pdf) 13 | * 4051 series: [74HC4051](https://www.ti.com/lit/ds/symlink/cd74hc4051-ep.pdf) 14 | * ...and any other similar IC that uses three or four channel select pins 15 | 16 | # Usage 17 | 18 | Here's an imaginary example using a 74HC4067 with a Blue Pill (stm32f104) board... 19 | 20 | ```rust 21 | // NOTE: This is pseudocode. It's just meant to get the concept across :) 22 | use analog_multiplexer::Multiplexer; // Important part 23 | 24 | use stm32f1xx_hal::gpio::State; 25 | // The pins we're using: 26 | use stm32f1xx_hal::gpio::gpiob::{PB0, PB5, PB12, PB13, PB14, PB15}; 27 | use stm32f1xx_hal::{adc}; // So we can read an analog pin (PB0) 28 | 29 | fn main() { 30 | // stm32f1xx_hal boilerplate... 31 | let device = pac::Peripherals::take().unwrap(); 32 | let mut flash = device.FLASH.constrain(); 33 | let mut rcc = device.RCC.constrain(); 34 | let mut _afio = device.AFIO.constrain(&mut rcc.apb2); 35 | let _clocks = rcc 36 | .cfgr 37 | .use_hse(8.mhz()) 38 | .sysclk(72.mhz()) 39 | .pclk1(36.mhz()) 40 | .freeze(&mut flash.acr); 41 | // Setup ADC (we're using ADC1 for this example since we're reading PB0) 42 | let adc1 = adc::Adc::adc1(device.ADC1, &mut rcc.apb2, _clocks); 43 | // Setup GPIOB (so we can access the ADC via PB0) 44 | let mut gpiob = device.GPIOB.split(&mut rcc.apb2); 45 | // Configure PB0 as an analog input (all channels lead to this analog input pin!) 46 | let analog_pin = gpiob.pb0.into_analog(&mut gpiob.crl); 47 | // Setup PB12-PB15 for accessing S0-S3 on the 74HC4067 multiplexer 48 | let s0 = gpiob 49 | .pb12 50 | .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 51 | let s1 = gpiob 52 | .pb13 53 | .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 54 | let s2 = gpiob 55 | .pb14 56 | .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 57 | let s3 = gpiob 58 | .pb15 59 | .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 60 | // NOTE: On some multiplexers the S0-S3 pins are labeled A, B, C, D 61 | // Enable pin... If you want to be able to enable/disable the multiplexer on-the-fly 62 | let en = gpiob 63 | .pb5 64 | .into_push_pull_output_with_state(&mut gpiob.crl, State::Low); 65 | // TIP: Just run a wire from EN to GND to keep it enabled all the time 66 | // Multiplexer pins are given as a tuple in the order S0-S3 then enable pin (EN): 67 | let pins = (s0,s1,s2,s3,en); // For 16-channel 68 | // let pins = (s0,s1,s2,en); // For 8-channel 69 | let mut multiplexer = Multiplexer::new(pins); // The important part! 70 | multiplexer.enable(); // Make sure it's enabled (if using EN pin) 71 | loop { 72 | for chan in 0..multiplexer.num_channels { 73 | multiplexer.set_channel(chan); // Change the channel 74 | let data: u16 = adc1.read(&mut *analog_pin).unwrap(); 75 | // Do something with the data here 76 | } 77 | } 78 | } 79 | 80 | ``` 81 | 82 | # Working Example 83 | 84 | There's a *proper* working example in the `examples` directory (`read_all`) that uses [RTIC](https://rtic.rs) and probe-rs to great effect. It requires an ST-LINK programmer, a Blue Pill board, and [probe-run](https://crates.io/crates/probe-run). 85 | 86 | Here's me using it to read a hall effect sensor on channel 15: 87 | 88 | ![Reading a hall effect sensor on channel 15](https://thumbs.gfycat.com/FlippantAptHadrosaurus-size_restricted.gif) 89 | 90 | # License 91 | 92 | Licensed under: 93 | 94 | * Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 95 | -------------------------------------------------------------------------------- /examples/bluepill/examples/read_all.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | //! **NOTE:** For this example to work you need a Blue Pill board connected 6 | //! to an ST-LINK debugger and [probe-run](1). A `.cargo/config` file is 7 | //! included and configured with `runner = "probe-run --chip STM32F103C8"`. 8 | //! 9 | //! This example reads the state of all channels on a 16 or 8-channel 10 | //! multiplexer every 100ms and pretty-prints their values using 11 | //! probe-rs (friends don't let friends use semihosting!). By default it's 12 | //! configured for a 16-channel multiplexer (74HC4067) so if you want to use 13 | //! an 8-channel one you'll need to comment-swap a few lines (search for 14 | //! "4051" to find them). 15 | //! 16 | //! You'll also need to change the pins to whatever pins you're using with 17 | //! your own Blue Pill board. The default configuration uses: 18 | //! 19 | //! * `PB0`: Connected to `Z` (analog output on the multilpexer) 20 | //! * `PB5`: Connected to `EN` (enable pin--if you don't just have it run to GND) 21 | //! * `PB12, PB13, PB14, PB15`: Connected to `S0, S1, S2, S3` (channel select pins) 22 | //! 23 | //! **NOTE::** You can just run `EN` to ground to keep it always enabled. 24 | //! 25 | //! [1]: https://github.com/knurling-rs/probe-run 26 | //! 27 | 28 | // The part that matters: 29 | extern crate analog_multiplexer; 30 | use analog_multiplexer::{DummyPin, Multiplexer}; 31 | // This is just a convenient container for keeping track of channel data and pretty-printing it: 32 | mod channels; // Look at channels/mod.rs if you're curious how it works 33 | 34 | extern crate panic_halt; 35 | use cortex_m; 36 | use embedded_hal::digital::v2::OutputPin; 37 | use rtic::app; 38 | use rtic::cyccnt::U32Ext as _; 39 | use rtt_target::{rprintln, rtt_init_print}; 40 | use stm32f1xx_hal::gpio::{gpioc::PC13, Analog, Output, PushPull, State}; 41 | // NOTE: Change these to the pins you plan to use for S0, S1, etc: 42 | use stm32f1xx_hal::gpio::gpiob::{PB0, PB12, PB13, PB14, PB15, PB5}; 43 | // ...you'll also need to change them in the `type` declarations below... 44 | use stm32f1xx_hal::adc; 45 | use stm32f1xx_hal::pac::ADC1; 46 | use stm32f1xx_hal::prelude::*; 47 | 48 | // Define which pins go to where on your analog multiplexer (for RTIC's `Resources`) 49 | type S0 = PB12>; // These just make things easier to read/reason about 50 | type S1 = PB13>; // aka "very expressive" 51 | type S2 = PB14>; 52 | type S3 = PB15>; // You can comment this out if using 8-channel (74HC4051) 53 | type EN = DummyPin; // If EN is connected to GND to keep it always enabled 54 | // type EN = PB5>; // If you want to enable/disable the multiplexer on-the-fly 55 | // You can swap which line is commented below to use an 8-channel instead of 16: 56 | type Multiplex = Multiplexer<(S0, S1, S2, S3, EN)>; // If using 16-channel (74HC4067) 57 | // type Multiplex = Multiplexer<(S0, S1, S2, EN)>; // If using 8-channel (74HC4051) 58 | // NOTE: If you swapped above you'll also need to swap the `let pins...` line below 59 | 60 | const PERIOD: u32 = 10_000_000; // Update state (and blink LED 1/2) every 100ms 61 | 62 | // We need to pass monotonic = rtic::cyccnt::CYCCNT to use schedule feature fo RTIC 63 | #[app(device = stm32f1xx_hal::pac, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] 64 | const APP: () = { 65 | // Global resources (global variables) are defined here and initialized with the 66 | // `LateResources` struct in init (RTIC stuff) 67 | struct Resources { 68 | led: PC13>, 69 | multiplexer: Multiplex, // If we didn't use the type aliases above this would be very messy 70 | ch_states: channels::ChannelValues, 71 | adc1: adc::Adc, 72 | analog_pin: PB0, 73 | } 74 | 75 | // This is needed for probe-rs to work with RTIC at present (20200919). 76 | // Won't be needed forever (they're working on it =) 77 | #[idle] 78 | fn idle(_cx: idle::Context) -> ! { 79 | loop { 80 | cortex_m::asm::nop(); 81 | } // Don't do anything at all 82 | } 83 | 84 | // NOTE: Most of this is rtic and cortex-m boilerplate. 85 | // The multiplexer-specific stuff is noted via comments below... 86 | #[init(schedule = [readall])] 87 | fn init(cx: init::Context) -> init::LateResources { 88 | // Enable rtt-target/probe-rs debugging stuff 89 | rtt_init_print!(); 90 | rprintln!("init multiplexer example()"); 91 | 92 | // Enable cycle counter (so we can schedule things) 93 | let mut core = cx.core; 94 | core.DWT.enable_cycle_counter(); 95 | 96 | let device: stm32f1xx_hal::stm32::Peripherals = cx.device; 97 | 98 | // Setup clocks (stm32f1xx_hal boilerplate) 99 | let mut flash = device.FLASH.constrain(); 100 | let mut rcc = device.RCC.constrain(); 101 | let mut _afio = device.AFIO.constrain(&mut rcc.apb2); 102 | let _clocks = rcc 103 | .cfgr 104 | .use_hse(8.mhz()) 105 | .sysclk(72.mhz()) 106 | .pclk1(36.mhz()) 107 | .freeze(&mut flash.acr); 108 | 109 | // Setup ADC (we're using ADC1 for this example since we're reding PB0) 110 | let adc1 = adc::Adc::adc1(device.ADC1, &mut rcc.apb2, _clocks); 111 | 112 | // Setup GPIOB (so we can access the ADC via PB0) 113 | let mut gpiob = device.GPIOB.split(&mut rcc.apb2); 114 | 115 | // Configure PB0 as an analog input (all channels lead to this analog input pin!) 116 | let analog_pin = gpiob.pb0.into_analog(&mut gpiob.crl); 117 | 118 | // Setup the Blue Pill's built-in LED for blinkage (blink-rage?) 119 | let mut gpioc = device.GPIOC.split(&mut rcc.apb2); 120 | let led = gpioc 121 | .pc13 122 | .into_push_pull_output_with_state(&mut gpioc.crh, State::Low); 123 | 124 | // Setup PB12-PB15 for accessing S0-S3 on the 74HC4067 multiplexer 125 | let s0 = gpiob 126 | .pb12 127 | .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 128 | let s1 = gpiob 129 | .pb13 130 | .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 131 | let s2 = gpiob 132 | .pb14 133 | .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 134 | // You can comment this one out if using an 8-channel (74HC4051): 135 | let s3 = gpiob 136 | .pb15 137 | .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 138 | 139 | let en = DummyPin; // Use the DummyPin if you have it always enabled (e.g. connected to GND) 140 | // If you want to be able to enable/disable the multiplexer on-the-fly: 141 | // let en = gpiob 142 | // .pb5 143 | // .into_push_pull_output_with_state(&mut gpiob.crl, State::Low); 144 | 145 | // ** ANALOG MULTIPLEXER STUFF ** 146 | // Setup the Multiplexer with our configured pins (swap comments below for 8 channel) 147 | let pins = (s0, s1, s2, s3, en); // For 16-channel (74HC4067) 148 | // let pins = (s0,s1,s2,en); // For 8-channel (74HC4051) 149 | let mut multiplexer = Multiplexer::new(pins); 150 | multiplexer.enable(); // Just an example (it gets enabled when you instantiate it) 151 | 152 | // Keep track of channel states/values (for pretty printing) 153 | let ch_states: channels::ChannelValues = Default::default(); 154 | 155 | // Schedule the reading/blinking task 156 | cx.schedule.readall(cx.start + PERIOD.cycles()).unwrap(); 157 | 158 | init::LateResources { 159 | led: led, 160 | multiplexer: multiplexer, 161 | ch_states: ch_states, 162 | analog_pin: analog_pin, // NOTE: Wish we didn't need BOTH 163 | adc1: adc1, // the pin and the ADC to read it 164 | } 165 | } 166 | 167 | #[task(resources = [led, multiplexer, ch_states, analog_pin, adc1], schedule = [readall])] 168 | fn readall(cx: readall::Context) { 169 | // Use the safe local `static mut` of RTIC 170 | static mut LED_STATE: bool = false; // RTIC's blink scheduler boilerplate 171 | // These are just here to keep the code nice and concise: 172 | let multiplexer = cx.resources.multiplexer; 173 | let ch_states = cx.resources.ch_states; 174 | let adc1 = cx.resources.adc1; 175 | let led = cx.resources.led; 176 | 177 | // ** ANALOG MULTIPLEXER STUFF ** 178 | // Cycle through all channels and record the value of each in ChannelValues (ch_states) 179 | for chan in 0..multiplexer.num_channels { 180 | multiplexer.set_channel(chan); // NOTE: Changing channels takes at most 7ns 181 | let data: u16 = adc1.read(&mut *cx.resources.analog_pin).unwrap(); 182 | ch_states.update_by_index(chan as u8, data); 183 | } 184 | rprintln!("{}", ch_states); // probe-rs goodness 185 | 186 | if *LED_STATE { 187 | led.set_high().unwrap(); 188 | *LED_STATE = false; 189 | } else { 190 | led.set_low().unwrap(); 191 | *LED_STATE = true; 192 | } 193 | // See you in the next PERIOD! 194 | cx.schedule.readall(cx.scheduled + PERIOD.cycles()).unwrap(); 195 | } 196 | 197 | extern "C" { 198 | fn EXTI0(); 199 | } 200 | }; 201 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | //! This crate provides an interface, `Multiplexer` that makes it trivially easy 3 | //! to select channels on any given 74HC4051 or 74HC4067 series analog multiplexer. 4 | //! Internally it keeps track of each multiplexer's state, allowing you to 5 | //! check what channel is presently active or to enable/disable the multiplexer 6 | //! at will. 7 | //! 8 | //! # Example using a 74HC4067 with a Blue Pill (stm32f104) board 9 | //! 10 | //! ``` 11 | //! // NOTE: This is pseudocode. It's just meant to get the concept across :) 12 | //! use analog_multiplexer::Multiplexer; // Important part 13 | //! 14 | //! use stm32f1xx_hal::gpio::State; 15 | //! // The pins we're using: 16 | //! use stm32f1xx_hal::gpio::gpiob::{PB0, PB5, PB12, PB13, PB14, PB15}; 17 | //! use stm32f1xx_hal::{adc}; // So we can read an analog pin (PB0) 18 | //! 19 | //! fn main() { 20 | //! // stm32f1xx_hal boilerplate... 21 | //! let device = pac::Peripherals::take().unwrap(); 22 | //! let mut flash = device.FLASH.constrain(); 23 | //! let mut rcc = device.RCC.constrain(); 24 | //! let mut _afio = device.AFIO.constrain(&mut rcc.apb2); 25 | //! let _clocks = rcc 26 | //! .cfgr 27 | //! .use_hse(8.mhz()) 28 | //! .sysclk(72.mhz()) 29 | //! .pclk1(36.mhz()) 30 | //! .freeze(&mut flash.acr); 31 | //! // Setup ADC (we're using ADC1 for this example since we're reading PB0) 32 | //! let adc1 = adc::Adc::adc1(device.ADC1, &mut rcc.apb2, _clocks); 33 | //! // Setup GPIOB (so we can access the ADC via PB0) 34 | //! let mut gpiob = device.GPIOB.split(&mut rcc.apb2); 35 | //! // Configure PB0 as an analog input (all channels lead to this analog input pin!) 36 | //! let analog_pin = gpiob.pb0.into_analog(&mut gpiob.crl); 37 | //! // Setup PB12-PB15 for accessing S0-S3 on the 74HC4067 multiplexer 38 | //! let s0 = gpiob 39 | //! .pb12 40 | //! .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 41 | //! let s1 = gpiob 42 | //! .pb13 43 | //! .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 44 | //! let s2 = gpiob 45 | //! .pb14 46 | //! .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 47 | //! let s3 = gpiob 48 | //! .pb15 49 | //! .into_push_pull_output_with_state(&mut gpiob.crh, State::Low); 50 | //! // NOTE: On some multiplexers the S0-S3 pins are labeled A, B, C, D 51 | //! // Enable pin... If you want to be able to enable/disable the multiplexer on-the-fly 52 | //! let en = gpiob 53 | //! .pb5 54 | //! .into_push_pull_output_with_state(&mut gpiob.crl, State::Low); 55 | //! // TIP: Just run a wire from EN to GND to keep it enabled all the time 56 | //! // Multiplexer pins are given as a tuple in the order S0-S3 then enable pin (EN): 57 | //! let pins = (s0,s1,s2,s3,en); // For 16-channel 58 | //! // let pins = (s0,s1,s2,en); // For 8-channel 59 | //! let mut multiplexer = Multiplexer::new(pins); // The important part! 60 | //! multiplexer.enable(); // Make sure it's enabled (if using EN pin) 61 | //! loop { 62 | //! for chan in 0..multiplexer.num_channels { 63 | //! multiplexer.set_channel(chan); // Change the channel 64 | //! let data: u16 = adc1.read(&mut *analog_pin).unwrap(); 65 | //! // Do something with the data here 66 | //! } 67 | //! } 68 | //! } 69 | //! 70 | //! ``` 71 | //! 72 | //! **NOTE:** There's a working Blue Pill/RTIC example in the `examples` directory. 73 | //! 74 | 75 | extern crate embedded_hal as hal; 76 | 77 | use core::convert::Infallible; 78 | use hal::digital::v2::OutputPin; 79 | 80 | /// Provides an interface for setting the active channel 81 | /// and enabling/disabling an 8-channel (74HC4051) or 82 | /// 16-channel (74HC4067) analog multiplexer. It also 83 | /// keeps track of which channel is currently active 84 | /// (`active_channel`) and provides a convenient 85 | /// `num_channels` field that can be used to iterate 86 | /// over all the multiplexer's channels. 87 | pub struct Multiplexer { 88 | pub pins: Pins, 89 | pub num_channels: u8, 90 | pub active_channel: u8, 91 | pub enabled: bool, 92 | } 93 | 94 | /// A trait so we can support both 8-channel and 16-channel 95 | /// multiplexers simultaneously by merely instantiating them 96 | /// with a 5 (16-channel) or 4 (8-channel) member tuple of 97 | /// `OutputPin`s. 98 | pub trait Output { 99 | fn set_channel(&mut self, channel: u8); 100 | fn enable(&mut self); 101 | fn disable(&mut self); 102 | fn num_channels(&self) -> u8; 103 | } 104 | 105 | /// A 5-pin implementation to support 16-channel multiplexers (e.g. 74HC4067) 106 | impl< 107 | E, 108 | S0: OutputPin, // aka "A" 109 | S1: OutputPin, // aka "B" 110 | S2: OutputPin, // aka "C" 111 | S3: OutputPin, // aka "D" 112 | EN: OutputPin, // aka "Inhibit" 113 | > Output for (S0, S1, S2, S3, EN) 114 | { 115 | /// Sets the current active channel on the multiplexer (0-15) 116 | fn set_channel(&mut self, channel: u8) { 117 | // NOTE: Figuring out the binary math on this was not fun. Not fun at all! 118 | // Thanks to @grantm11235:matrix.org for showing me the way =) 119 | if channel & (1 << 0) == 0 { 120 | self.0.set_low().ok(); 121 | } else { 122 | self.0.set_high().ok(); 123 | } 124 | 125 | if channel & (1 << 1) == 0 { 126 | self.1.set_low().ok(); 127 | } else { 128 | self.1.set_high().ok(); 129 | } 130 | 131 | if channel & (1 << 2) == 0 { 132 | self.2.set_low().ok(); 133 | } else { 134 | self.2.set_high().ok(); 135 | } 136 | 137 | if channel & (1 << 3) == 0 { 138 | self.3.set_low().ok(); 139 | } else { 140 | self.3.set_high().ok(); 141 | } 142 | } 143 | 144 | /// Brings the `EN` pin low to enable the multiplexer 145 | fn enable(&mut self) { 146 | self.4.set_low().ok(); 147 | } 148 | 149 | /// Brings the `EN` pin high to disable the multiplexer 150 | fn disable(&mut self) { 151 | self.4.set_high().ok(); 152 | } 153 | 154 | /// Returns the number of channels supported by this multiplexer 155 | /// (so you can easily iterate over them). 156 | fn num_channels(&self) -> u8 { 157 | 16 158 | } 159 | } 160 | 161 | /// A 4-pin implementation to support 8-channel multiplexers (e.g. 74HC4051) 162 | impl< 163 | E, 164 | S0: OutputPin, 165 | S1: OutputPin, 166 | S2: OutputPin, 167 | EN: OutputPin, 168 | > Output for (S0, S1, S2, EN) 169 | { 170 | /// Sets the current active channel on the multiplexer (0-7) 171 | fn set_channel(&mut self, channel: u8) { 172 | if channel & (1 << 0) == 0 { 173 | self.0.set_low().ok(); 174 | } else { 175 | self.0.set_high().ok(); 176 | } 177 | 178 | if channel & (1 << 1) == 0 { 179 | self.1.set_low().ok(); 180 | } else { 181 | self.1.set_high().ok(); 182 | } 183 | 184 | if channel & (1 << 2) == 0 { 185 | self.2.set_low().ok(); 186 | } else { 187 | self.2.set_high().ok(); 188 | } 189 | } 190 | 191 | /// Brings the `EN` pin low to enable the multiplexer 192 | fn enable(&mut self) { 193 | self.3.set_low().ok(); 194 | } 195 | 196 | /// Brings the `EN` pin high to disable the multiplexer 197 | fn disable(&mut self) { 198 | self.3.set_high().ok(); 199 | } 200 | 201 | /// Returns the number of channels supported by this multiplexer 202 | /// (so you can easily iterate over them). 203 | fn num_channels(&self) -> u8 { 204 | 8 205 | } 206 | } 207 | 208 | impl Multiplexer { 209 | /// Given a 5 or 4-member tuple, `(s0, s1, s2, s3, en)` or 210 | /// `(s0, s1, s2, en)` where every member is an `OutputPin`, 211 | /// returns a new instance of `Multiplexer` for a 212 | /// 16-channel or 8-channel analog multiplexer, respectively. 213 | /// 214 | /// **NOTE:** Some multiplexers label S0-S3 as A-D. They're 215 | /// the same thing. 216 | pub fn new(mut pins: Pins) -> Self { 217 | // Default to enabled on channel 0 218 | let active_channel = 0; 219 | let enabled = true; 220 | pins.enable(); 221 | pins.set_channel(0); 222 | // For quick reference later: 223 | let num_channels = pins.num_channels(); 224 | 225 | Self { 226 | pins, 227 | num_channels, 228 | active_channel, 229 | enabled, 230 | } 231 | } 232 | 233 | /// Sets the current active channel on the multiplexer 234 | /// (0 up to `num_channels`) and records that state in 235 | /// `self.active_channel` 236 | pub fn set_channel(&mut self, channel: u8) { 237 | self.pins.set_channel(channel); 238 | self.active_channel = channel; 239 | } 240 | 241 | /// Enables the multiplexer and sets `self.enabled = true` 242 | pub fn enable(&mut self) { 243 | self.pins.enable(); 244 | self.enabled = true; 245 | } 246 | 247 | /// Disables the multiplexer and sets `self.enabled = false` 248 | pub fn disable(&mut self) { 249 | self.pins.enable(); 250 | self.enabled = false; 251 | } 252 | } 253 | 254 | /// A DummyPin for when you've got your EN (enable) pin run to GND 255 | /// (the analog multiplexer is always enabled) 256 | pub struct DummyPin; 257 | impl OutputPin for DummyPin { 258 | type Error = Infallible; 259 | fn set_low(&mut self) -> Result<(), Self::Error> { 260 | Ok(()) 261 | } 262 | fn set_high(&mut self) -> Result<(), Self::Error> { 263 | Ok(()) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------