erebus-labs / blocks-o-code

Open Hardware & Software Hardware Manipulative for K12 Students
http://www.erebuslabs.com/elaunch
GNU General Public License v2.0
4 stars 1 forks source link

Correctly mapping ADC values #93

Closed gstro closed 9 years ago

gstro commented 9 years ago

@jmickiewicz and I spent too much time trying to figure out a way to do this elegantly but we never got very far. We'll probably manually map the pot values to tokens internally in the code. But here's the challenge we're facing if anyone wants to give it a go.

Programming Challenge!

Create an algorithm that can map the 4-bit adc_value to an arbitrary list of 16 or less tokens (i.e. our function wheels). Our potentiometer readings are heavy on the first and last values so we want to restrict them to only one value, and then increase the angular spacing of the rest by accepting multiple values for each token toward the center. For the Statements category (#88), we would want to distribute the 6 tokens roughly as such:

This is my submission, which works for 6 tokens :+1:

def parse(adc_val, bitfield, wheel):
    # edge cases
    adc_val = adc_val & 0b1111
    length = len(wheel)
    if length == 0:
        return 'No Value'
    if length == 1:
        return wheel[0]

    # min and max values
    if adc_val == 0:
        return wheel[0]
    if adc_val == 0b1111:
        return wheel[length - 1]

    # choose bewteen 2 tokens
    if length == 2:
        if adc_val & 0b1000:
            return wheel[0]
        else:
            return wheel[1]

    # reduce search space
    if adc_val & bitfield:
        return parse(adc_val, bitfield >> 1, wheel[length/2:])
    else:
        return parse(adc_val, bitfield >> 1, wheel[:-(length/2)])

# testing values
test_wheel = ['OUT1', 'OUT2', 'OUT3', 'OUT4', 'OUT5', 'Print']
print test_wheel
for x in range(16):
    print x, parse(x, 0b1000, test_wheel)

Output

['OUT1', 'OUT2', 'OUT3', 'OUT4', 'OUT5', 'Print']
0 OUT1
1 OUT2
2 OUT2
3 OUT2
4 OUT3
5 OUT3
6 OUT3
7 OUT3
8 OUT4
9 OUT4
10 OUT4
11 OUT4
12 OUT5
13 OUT5
14 OUT5
15 Print

But this falls apart for different categories :-1:

['while', 'endwhile', 'if', 'else', 'endif', '(', ')']
0 while
1 endwhile
2 endwhile
3 endwhile
4 else
5 else
6 else
7 else
8 else
9 else
10 else
11 else
12 (
13 (
14 (
15 )
jmickiewicz commented 9 years ago

I came up with a slightly different model. I thought it would be easier to divide 'extra' slots on the wheel evenly (to non-first or last values), and then give the remainder one each to the values after the first one 0 OUT1 1 OUT2 2 OUT2 3 OUT2 4 OUT2 5 OUT3 6 OUT3 7 OUT3 8 OUT3 9 OUT4 10 OUT4 11 OUT4 12 OUT5 13 OUT5 14 OUT5 15 Print

My algorithm for wheels of more with 4 or more was;

tval is adc value wheel is the set of functions on the wheel L=length (wheel) x=14/(L-2) y= 14%(L-2) if L = 0,1,2,3 0=error, 1=ret 0,2=ret tval >> 3,do something (3 is a special case because the angular area is about 1/4 for 0, 1/4 for 1111, so giving all the extra to other functions is only fair for 4 or more) elseif tval == 0 return 0 elseif tval = 15 return L-1

else z=0 for A from 1 to (L-2) z+=(y-(A-1)>0) if tval <= (A*X + z) return A break

I manually followed my algorithm through a couple length(wheel), and it seemed to work, but I had trouble with the code, missing the 'break' so the for loop always finished with L-2. I gave up before figuring that out though.

erebuslabs commented 9 years ago

Are you still looking for a solution on this? Do you need/want me to spend some time on this?

jmickiewicz commented 9 years ago

I don't think we need any help with this.

We are going to use repeated entries to give more angular area, like the list I start my other comment with. So each of our four lists will have 16 not-necessarily-unique values. This is the clearest way to represent what we are doing, though not the most elegant. Documenting this is also easier since we just need a list of the 31 angular sections. (16 valid sections and 15 non-zero-width lines)

To make a new wheel you'd need to mark a wheel with the angular areas prescribed in the (unfinished) documentation, and modify the table in the beagle bone with your new tokens.

gstro commented 9 years ago

@jmickiewicz, I left my BBB in the locker for your wheel-testing purposes. The block that is currently connected to it has compatible firmware for this setup -- it is also the only one with a pot on it as the other one had a wire break.

root@beaglebone:~# sh i2c3enable.sh 
lrwxrwxrwx 1 root root 0 Dec 31  1999 /sys/bus/i2c/devices/i2c-0 -> ../../../devices/ocp.3/44e0b000.i2c/i2c-0
lrwxrwxrwx 1 root root 0 Dec 31  1999 /sys/bus/i2c/devices/i2c-1 -> ../../../devices/ocp.3/4819c000.i2c/i2c-1
lrwxrwxrwx 1 root root 0 Apr 23 13:20 /sys/bus/i2c/devices/i2c-2 -> ../../../devices/ocp.3/4802a000.i2c/i2c-2
root@beaglebone:~# cd py_scripts/
root@beaglebone:~/test_scripts# python t_adc_range.py 
8
8
8
8
8
8
8
8
7
7
7
6
6
5
5
4
...
gstro commented 9 years ago

Since the potentiometer extremes aren't as big as we thought (they aren't, right?), then maybe we just map the adc values like in the arduino code you had. I just pulled that arduino map function and now I currently have this setup in the lexer to protect the category bounds:

# lists of tokens
value_tokens = ['0', '1', '2', ...]   # 16 tokens, no need to adjust lookup
operator_tokens = [...]               # 13 tokens, need to correct the lookup
control_tokens = [...]                # 7 tokens, need to correct the lookup
statement_tokens = [...]              # 6 tokens, need to correct the lookup
abc_tokens = [
    value_tokens,
    operator_tokens,
    control_tokens,
    statement_tokens
]
...

# maps a new value within its given range to another specified range 
# modified from the arduino 'map' function
def adc_map(self, val, out_min, out_max, in_min=0, in_max=15):
    return (val - in_min) * (out_max - out_min) // (in_max - in_min) + out_min

# takes the block's adc value and token category, maps it for even spacing, and performs token lookup 
def token_match(self, category, adc_val):
    subset = abc_tokens[category]
    token = subset[self.adc_map(adc_val, 0, len(subset)-1)]
    return token

This means the value spacing is unique to the number of tokens in the category -- which means 4 wheels for us, but we were going to make 4 wheels anyway. Update here when you get to the point of needing new maps and I can add to the test script based on the token category.

jmickiewicz commented 9 years ago

@gstro a wheel labeled 0-F exists, and the beaglebone sees the same value as a user.

gstro commented 9 years ago

@jmickiewicz Nice! I've pushed a new t_adc_range.py test script, but I haven't tested the test. It can be called with -v, -a, -c, or -s to represent the value, assignment, control, or statement token categories. So the following will start the live updates and map the adc values to the control wheel of 7 possible tokens:

python t_adc_range.py -c

Hindsight, I could have just passed an integer argument for the total number of tokens to map, but this will also work for the final code so it's good to test.

gstro commented 9 years ago

After our talk today, I say just get the wheel to match the 0-15 values, and we can adjust the code to match.

jmickiewicz commented 9 years ago

The pushed image leaves much to be desired in distribution. var has no repeats the others 0-15 currently are: operators: +,+,-,/,*,^,^,%,=,!=,>,>,<,>=,<=,! control: while,while,while,endwhile,endwhile,if,if,else,else,endif,endif,(,(,),),) statements: out1,out1,out1,out2,out2,out3,out3,out3,out4,out4,out5,out5,out5,print,print,print

jmickiewicz commented 9 years ago

draft

this version has a center connector arranged for an indicator from the left and the pot alignment pin to the bottom.

dfrister commented 9 years ago

Closed during team work session prior to presentation.