Operators and conversions: where bugs hide
Integer division, implicit conversions and operator precedence. The causes of code that compiles but gives the wrong value.
Before it calculates, C brings the operands to a common type. If you do not know these rules, you write code that is syntactically fine and numerically wrong.
Integer division
3 / 4 is 0. Both are integers, so the fraction is dropped. Writing adc / 4095 * 3.3 to compute a voltage gives 0 almost every time. Multiply first, or convert to floating point.
float v = adc * 3.3f / 4095.0f; // works as intended
Integer promotion
Even when you add two uint8_t values, the arithmetic is done in int. So with uint8_t a = 200, b = 100;, a + b is 300, and it becomes 44 the moment you store it back in a uint8_t. The same rules explain why comparing a signed value with an unsigned one turns a negative number into a very large positive one.
Precedence
In if (reg & MASK == 0), == is evaluated first, so it means reg & (MASK == 0). Do not be sparing with parentheses in conditions that mix in bit operations.