emailing-files-is-still-better / Ir-Blinky

PIC IR blinker for simple raw data
2 stars 1 forks source link

Saving final code for each number sequence that is flashed #12

Open emailing-files-is-still-better opened 1 year ago

emailing-files-is-still-better commented 1 year ago

So what is the best way to save all of the decoded preambles, codes, and delays? I would think each one would just get it's own main c that is uploaded here but is there a better way to track those?

georgev93 commented 1 year ago

A few ways to skin this cat. Method 1 is simplest. Method 2 will make life easier when you want to start brute forcing codes.

Method 1: Preprocessor defines (What we have now)

A preprocessor define is implemented as #define [VARIABLE NAME] [VALUE]. It assigns a value to a variable (and if you care, the canonical way to name a preprocessor define is all caps and separated by underscores). What's actually happening, is that the compiler looks through your code before it compiles, and replaces any instances of that "variable" with its corresponding value. So a preprocessor define statement isn't truly a variable at all. It's just meant to make the code look clearer to the reader and allow for more convenient changes in multiple spots in your code.

Wait, so what's the difference between that and a const variable?

const variables are actually stored in non-volatile memory in the target machine/microcontroller. That makes them not as efficient because using them actually costs memory space, whereas defines don't really live in the code after it's compiled. However, when you define a const variable, you define it as const [TYPE] [VARIABLE NAME] = [VALUE];, so you can enforce a type which is a good safety practice. Then when you try to have a numeric variable contain the word "potato", the compiler says ERROR! "potato" isn't a number! as opposed to 10 random errors with more confusing phrases like ERROR! Right side of assignment operator results in 0!. So if you have the memory to spare, consts are generally preferred to preprocessor defines.

Cool. I didn't want to know any of that. Thanks a lot. Back to my code

SO. You could have a section in the beginning of your code that has stuff like:

#define CODE_72 01100110
#define CODE_92 00100010

#define CODE CODE_92

Then just change that #define CODE ... line.

If you wanted to be very fancy, you could make a separate file called something like codes.h that contains all of those #define CODE_XX ... lines. Then in your main.c you could just at #include "codes.h", then use that #define CODE ... line. Then as you learn more codes, you just add them to their own separate file. Nice and clean.

Note: You will need to do some "add file to project" sort of action in MPLAB X to tell it to use any extra files you create. Otherwise your file won't be linked and you'll be getting a warning of WARNING: codes.h not found and an error of ERROR: CODE_92 not defined!.

Method 2: A function that takes an integer argument

Picture this: You have a function that looks like this:

void setCode(uint8_t number);   // An example prototype

Then, in your code before you enter the loop you call setCode(92);. Maybe to make it easier, you could instead do setCode(CODE); with a preprocessor define for CODE living right up at the top of your main file (nice and easy to find and change! I like the philosophy of "if I need to tweak something with this code, I don't want to dig down into the code, I want nice easy variables to tweak at the top").

If it comes to brute forcing, you would be in good shape to do something like

for (int i=0; i<100; i++) {
  setCode(i);
  // transmit 10 times
}

Great. Love it. How though? What does this function actually look like on the inside?

Well that's the million dollar question. Let's say life makes sense and it's literally transmitting the binary value as the code. In that case, you just need to convert the byte to an array of booleans. Something like:

void setCode(uint8_t number) {
  for (int i=0; i<8; i++) {
    if((number & (1<<(7-i))) == 0) {
      CODE[i] = false;
    } else {
      CODE[i] = true;
    }
  }
}

Holy shit man. You just did like 5 things we didn't talk about. What the hell are you doing.

Actually only two... calm down! But this is some fancier stuff. One thing at a time:

Bitshifting

>> and << are bitshift operators. They work like this (the following numbers are in binary):

So if you see what's happening here, it's "take the binary number, and rotate the bits x number of times in a direction`. The way I'm using that is starting with 1 (decimal notation), which is equal to 0b00000001 (binary notation as indicated by the "0b" prefix), then I'm rotating it each time to get 0b00000001 first (<< of 0), then 0b00000010 (<< or 1), all the way to 0b10000000 (<< of 7).

Bitwise operators

We briefly talked about this when we talked about registers, but there's a key distinction between logical operators and bitwise operators. && is logical AND, || is logical OR. & is bitwise AND, | is bitwise OR. Bitwise operations work like this:

Basically, it does logical operations bit by bit, then outputs the result.

Back to the function

So my function just says "Look ONLY bit 7 (the left-most bit in an 8-bit byte) and treat all other bits as 0. Is the result all zeros? If so, make that element of the array "false". Otherwise, make it "true". Fancy stuff, right?

That's all well and good, but I think we know the codes don't make that much sense. So maybe that function we described could be just useful for brute forcing, but it might not help us if the codes don't make any sense. We might need a Look Up Table (LUT) type function. Like this:

void setCodeWithLUT(uint8_t number) {
  if(number == 1) {
    CODE = 01000100;
  } else if (number == 2) {
    CODE = 01100110;
  } else if(number == 3) {
    // ...
  }
}

Or, using a slightly different way of writing the same thing:

void setCodeWithLUT(uint8_t number) {
  switch(number) {
    case 1:
      CODE = 01000100;
      break;
    case 2:
      CODE = 01100110;
      break;
    // ...
  }
}

The breaks are needed after each case, otherwise it doesn't break out of the switch and it just executes the next case

georgev93 commented 1 year ago

Actually, what would be even easier than a LUT function would be a a const array. So that way if you want the code for address 92, you'd just do something like CODE = code_LUT[92]; So just somewhere in your code (like it's own header file) you would define an array like

const uint8_t code_LUT[] = {
  01000100, // 0
  01100110, // 1
 // ...
};

This is, of course, using uint8_t as the type for each code. I think we're actually going to use a boolean byte array to store each code (although that's a super memory-wasteful way to do things, but it might be simpler). I'd need to do this not in Github to see if it compiles, but I think you could replace uint8_t in the above blurb with something like bool[8]. Replace 8 with however many bits the code is. That way, you'll be declaring an array of bool arrays. [Inception sound]

georgev93 commented 1 year ago

Dang, can you imagine if I were to type these sorts of things out in Whatsapp? Crazy.

emailing-files-is-still-better commented 1 year ago

I'll have to pray and spray bastardized versions of those examples into mplab and see if anything sticks haha. I think you'd have a market for writing a programming instructional book. It would be highly entertaining.