Arduino controller to control the backlighting of a gear selector
I see some things that may help you improve your code.

The Arduino language isn't C

The language used for the Arduino isn't quite C and isn't quite C++, so if your goal is "to learn C," you might want to be careful about the differences.  Specifically, the Arduino's use of setup and loop is unique to it.  Also, all of the "built-in" things such as digitalWrite and Serial are non-standard.  You can still learn useful things by learning to program the Arduino, but it's important to remain aware of the differences.

Know the differences between C++ and C

As mentioned above, the Arduino language isn't quite C or C++, but in most ways, it's closer to C++ than C.  One such difference that affects this code is the use of the struct interval.  In C++ the name of a struct automatically defines a type.  In C, it does not.  So the state in plain C would need to be written like this:

struct state
{
    char name;
    int led;
    struct interval gauss1;
    struct interval gauss2;
};


Alternatively, one could use a typedef in the declaration of the interval structure like this:

typedef struct interval_s
{
    int lower;
    int upper;
} interval;


Eliminate global variables where practical

Having routines dependent on global variables makes it that much more difficult to understand the logic and introduces many opportunities for error.  Eliminating global variables where practical is always a good idea, whether programming for desktop machines or for embedded systems.  For global variables such as prev_gauss1 and prev_gauss2, consider wrapping them in objects to make it easier to see that they're related.  Here again, this is a difference in the Arduino language where it is common to have global variables and plain C where it is rare.

Understand the fundamental types

The analogRead() call returns an int (which is 2 bytes on the Arduino), but the converted value of gauss1 and gauss2 is a long which is 4 bytes long on the Arduino.  However, all of the numbers in the table are small enough to fit into an int which suggests that you probably don't really need a long there.  

Minimize runtime conversions

It's not critical here, because normal drivers don't expect microsecond shifting, but in general, it's good to minimize the work that an embedded system must do at runtime.  This tends to make the program more efficient and more responsive, as well as usually resulting in smaller code.  In this case, the raw readings are taken from the sensors, converted using a simple linear equation and then compared to fixed values in the table.  Instead, the table could store the corresponding raw values and the comparison could be made without conversion.

Make use of defined constants

The numLED constant is defined, correctly, but their connection to the array of states is not explicit.  I'd suggest changing the declaration from this: 

state shift_condition[] = {


To this:

state shift_condition[numLED] = {


Reconsider naming

The names gauss1 and gauss2 are not terrible, but it might be worth considering what the values really mean.  Because they're actually sensor data, it might be better to name them for what they represent rather than the units.

Set states explicitly

The only time the U_led is written, it's set HIGH if no states are matched.  If it ever gets to that point, there's no recovery even if it gets in range of one of the gears.  Further, it's often good in embedded systems to set states explicitly.  So when you set one LED, it's often good to explicitly unset the LEDs that are not supposed to be lit.  The reason for this is that glitches can happen in the real world.  For example, an energetic gamma particle can flip a bit in RAM and it's useful to have a recovery for such events.