Skip to content

Instantly share code, notes, and snippets.

@SharpCoder
Last active December 17, 2022 01:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SharpCoder/2b1b0280e9b4c4ecacf14b06b6462522 to your computer and use it in GitHub Desktop.
Save SharpCoder/2b1b0280e9b4c4ecacf14b06b6462522 to your computer and use it in GitHub Desktop.
Bit-Smooshing over Serial

Bit Smoosher

This algorithm will take a 10/12/14 bit word and distribute it among 2 8-bit words. But in a lossless, efficient way. Why might someone want this? Well, it lets you stream uniquely sized words over 8-bit serial. That's why I need it anyway. Here's an example:

Suppose you have an 8-bit serial port and you want to stream 12-bit audio at 8kHz. Well, serial is typically maxing out around 115200 bits/s. You can't just use 2 bytes per datapoint, because you'll generate too much content. In order to solve that problem, you can use the algorithm outlined here to smartly condense (and later, rehydrate) the binary data across multiple 8-bit words.

//! This module will take some amount of 8-bit words and rehydrate them into the original set of i16 bytes.
fn convert(bytes: Vec<u8>, bits: u8) -> Vec<i16> {
let mut result = Vec::new();
let mut overflow_bits = 0;
let uber_mask = 0b111111111111111;
let step = bits - 8;
for i in 0..bytes.len() - 1 {
let byte = *bytes.get(i).unwrap();
let peek = *bytes.get(i + 1).unwrap();
if overflow_bits > 8 - step {
overflow_bits = 0;
continue;
} else {
let mask = uber_mask >> (15 - overflow_bits - step);
let lsb = byte as i16 >> overflow_bits;
let msb = ((peek as i16) & mask) << (8 - overflow_bits);
result.push(lsb | msb);
overflow_bits += step;
}
}
return result;
}
//! This module is used to generate the 8-bit words to send over serial.
pub struct Stream {
pub overflow: u8,
pub byte: u8,
}
impl Stream {
pub fn new() -> Self {
return Stream {
overflow: 0,
byte: 0,
};
}
pub fn append(&mut self, byte: u32, bits: u8) -> (Option<u8>, Option<u8>) {
let mut extra: Option<u8> = None;
let mut next_byte = self.byte;
let step = bits - 8;
for mask in 0..8 {
let val = (byte & (0x1 << mask)) as u8;
next_byte |= val << self.overflow;
}
self.byte = (byte >> (bits - (self.overflow + step))) as u8;
self.overflow += step;
if self.overflow == 8 {
extra = Some(self.byte);
self.byte = 0;
self.overflow = 0;
}
return (Some(next_byte), extra);
}
}
#[cfg(test)]
mod test_stream {
use super::*;
#[test]
fn test_bit_logic() {
let mut stream = Stream::new();
// 12 bit encoding
assert_eq!(stream.append(0b101010101010, 12).0.unwrap(), 0b10101010);
assert_eq!(stream.append(0b111100001111, 12).0.unwrap(), 0b11111010);
stream.clear();
// 10 bit encoding
assert_eq!(stream.append(0b1110101010, 10).0.unwrap(), 0b10101010);
assert_eq!(stream.append(0b0011111111, 10).0.unwrap(), 0b11111111);
assert_eq!(stream.append(0b0011001101, 10).0.unwrap(), 0b11010011);
assert_eq!(stream.append(0b1111001111, 10).0.unwrap(), 0b11001100);
assert_eq!(stream.append(0b1010101101, 10).0.unwrap(), 0b10101101);
assert_eq!(stream.append(0b1111001011, 10).0.unwrap(), 0b101110);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment