5
\$\begingroup\$

From the sandbox!

I2c or TWI, is a 2 way stream that uses 2 signal lines: SDA and SCK. This is used in sensors to communicate between a master and a slave. So, how does this work? Well, here's the boilerplate:

Input:

1 I2c command represented by 2 strings where - is a high signal, and _ is low. SDA is always the first string.

Output:

The debug info for that command, including weather
it's a READ or WRITE,
which address it's to (in Hex), and
what memory the signal's addressing(in Hex).
if the transmission is invalid, print "Invalid"

How does I2c work, then?

enter image description here Image from Sparkfun!
An i2c request has 2 9-bit parts. the address and the payload. The address comes first.

A bit is only READ when the clock signal goes from LOW to HIGH. Going from HIGH to LOW is not a valid reading event.

The address is 7 bits, which come after both SDA and SCL are LOW. This is sent by the master.

After the 7th bit, the next is sent by the master, which is the READ Bit. If this bit is HIGH, then it's a READ operation, if the bit is LOW, then it's a WRITE operation.

After the 8 bits, the slave responds with an ACK bit, if the ACK bit is HIGH, then the entire transmission is invalid.

Then, the second transmission is sent.

The first 8 bits represent the data address that the master is READing or WRITEing.

and the final bit is the 2nd ACK bit. If this bit is HIGH, then the entire transmission is invalid.

Examples:

Raw IN: ["--____--__----__----__----__--__--__","___-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-"]
SDA: --____--__----__--__--__----__--__--__ 
SCK: ___-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
Result: 0 0 1 0 1 1 0 1 0 1 0 1 1 0 1 0 1 0
Parse:  addr[22] op:[R] data:[181] success[yes]  
out: address:0x16 op:Read data:0xB5

Raw IN: ["--____--__----__------__----__--__--__","___-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-"]
SDA: --____--__----__------__----__--__--__ 
SCK: ___-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
Result: 0 0 1 0 1 1 0 1 1 1 0 1 1 0 1 0 1 0
Parse:  addr[22] op:[R] data:[181] success[No]  
out: invalid
Note: (the ACK bit flipped high);

Raw IN: ["--_-_--__--_-__--_-_-__-_-_-_--__--__-","___-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-"]
SDA: --_-_--__--_-__--___-__-_-_-_--__--__- 
SCK: ___-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
Result: 0 0 1 0 1 1 0 1 0 1 0 1 1 0 1 0 1 0
Parse:  addr[22] op:[R] data:[181] success[yes]
out: address:0x16 op:Read data:0xB5 
Note: (the bit before the change is read.);

Raw IN: ["--_____--___-----___--___--___-----__---____--____","___--_-_--_--_-_--_-_--_-_--_-_--_-_--_-___-_--__-"]
SDA: --_____--___-----___--___--___-----__---____--____
SCK: ___--_-_--_--_-_--_-_--_-_--_-_--_-_--_-___-_--__-
Result: 0  0 1  0  1 1  0 1  0 1  0 1  1 0  1   0 1   0
Parse:  addr[22] op:[R] data:[181] success[yes] 
out: address:0x16 op:Read data:0xB5
Note: (Clock signals Don't have to be perfect.);    

Raw IN: ["--_____--___-----___--___--___-----__---____--____","___---_-_--_-___-_--__-"]
SDA: --_____--___-----___--___--___-----__---____--____
SCK: ___---_-_--_-___-_--__-
Result: 0   0 1  0   1 0   1
Parse:  Invalid
out: invalid
Note: (They do however, have to match the length of the data dignal.);

Extra rules:

The 0x prefix must be appended to all hex values in the output, as a formality. The input is strictly - for HIGH and _ for LOW, and will always be 2 separate strings with a separator character. If both SDA and SCL change at the same time, then the previous bit is read.

This is a code golf, so whoever creates a running program in the least number of bytes wins! Good luck

\$\endgroup\$
14
  • \$\begingroup\$ Is the third example incorrect..? It seems that the first result bit should be set. \$\endgroup\$ Commented Jun 15, 2017 at 19:59
  • \$\begingroup\$ @TylerMacDonell i looked it over, and it is correct. For this, imagine that sda is pushed forward (right) half a character \$\endgroup\$
    – tuskiomi
    Commented Jun 15, 2017 at 20:42
  • \$\begingroup\$ Oops, misunderstood this part: If both SDA and SCL change at the same time, then the previous bit is read. \$\endgroup\$ Commented Jun 15, 2017 at 20:47
  • 1
    \$\begingroup\$ The SDA and SCK in the first example's raw input does not match the signals on the following two lines \$\endgroup\$
    – PunPun1000
    Commented Jun 19, 2017 at 18:51
  • 1
    \$\begingroup\$ I did this once in college, that was enough lol. \$\endgroup\$ Commented Jul 24, 2017 at 19:22

1 Answer 1

2
\$\begingroup\$

GNU sed, 427 378 364 + 1 = 428 379 365 bytes

+1 byte for -r flag. Takes colon-separated input. Output format is e.g. a0x16oRd0xB5, where a precedes the address, o precedes the operation (R or W), and d precedes the data.

Why do I do this to myself?

s/^/:/
:Q
s/:.+:$|::./E/
s/:(.).(.*):_-/\1:x\2:x/
s/:./:/g
tQ
s/(.{7})(.)(.)(.*)(.):/#\3\1%\2#\5\4:/
/#-|E/{z;iinvalid
q}
s/#./#/g
:R
s/[:%]/!u0;%0123456789ABCDEF,/
:A
s/((-)|_)!/!\2/
s/!-(u+)(.+);/!\1\2;\1/
:
/u%/!bZ
s/F;/;0/
t
s/(.);(.*%.*\1)(.)/\3\2\3/
s/u;/u1/
s/u(u*)%/;\1%/
b
:Z
s/!(u+)/!\1\1/
/#!/!bA
s/#!u+(.+);.+,/\1/
/:/bR
s/(..)(.)/a0x\1o\2d0x/
y/-_/RW/

Try it online!

Explanation

The code works in two phases. In phase 1 we turn the two signals into a single binary string. This is the easy part.

# Prepend a `:`
s/^/:/
:Q
  # Check for length mismatch (one string exhausted before the other)
  s/:.+:$|::./E/

  # If the first two characters of SCK are `_-`, record first character of SDA
  # and discard second; replace each pair with `x` so we can unconditionally...
  s/:(.).(.*):_-/\1:x\2:x/

  # ...consume a character from each signal
  s/:./:/g

  # If we did a substitution, branch to :Q
  tQ

If this was our input:

--_____--___-----___--___--___-----__---____--____:___--_-_--_--_-_--_-_--_-_--_-_--_-_--_-___-_--__-

...we now have this:

__-_--_-_-_--_-_-_:

Before phase 2, we reformat this to make it a little easier to work with:

# Reformat `.......ma........b:` to `#a.......%m#b........:`
s/(.{7})(.)(.)(.*)(.):/#\3\1%\2#\5\4:/

# Check if ACK high or length mismatch; if either, print error and quit
/#-|E/{z;iinvalid
q}

# Delete ACK bits
s/#./#/g

Now we have:

#__-_--_%-#-_--_-_-:

In phase 2, we convert each run of _ and - (0 and 1) to a hexadecimal number. Because sed doesn't do math, this is almost half of the code. It actually takes each binary digit, converts it to unary, and then increments the hexadecimal number once for each unary digit.

:R
# Initialize: u = magnitude (unary 1); 0 = running sum (decimal); lookup table
s/[:%]/!u0;%0123456789ABCDEF,/

:A
  # Take the least-significant bit; if it's 0 drop it
  s/((-)|_)!/!\2/

  # If 1 (-), remove it and copy current magnitude (`u+`) after `;`
  s/!-(u+)(.+);/!\1\2;\1/

  # Increment number for each `u` after `;`
  # e.g. 12;uu -> 13uu -> 13;u -> 14u -> 14;
  :
    # No more `u`s, branch to :Z
    /u%/!bZ

    # Carry: 1F;u -> 1;0u -> 20u -> 20;
    s/F;/;0/
    t

    s/(.);(.*%.*\1)(.)/\3\2\3/
    s/u;/u1/

    # Drop a `u`; branch to `:`
    s/u(u*)%/;\1%/
    b

  :Z
    # Double magnitude: !uu3 -> !uuuu3
    s/!(u+)/!\1\1/
    # If there are binary digits left, branch to :A
    /#!/!bA

# Clean up
s/#!u+(.+);.+,/\1/

# Branch to :R for the second number
/:/bR

# Format output
s/(..)(.)/a0x\1o\2d0x/
y/-_/RW/

Before formatting, we have:

16-B5

All that remains is to add a prefix to each hexadecimal number change the operation (- or _) to read (R) or write (W), giving us:

a0x16oRd0xB5
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.