PORTB acts like an integer (int). When you write to PORTB, you write to all 8 bits at once. You can only use square brackets (var[i]) with arrays, and fields (var.fieldname) with structures. You can't use a variable in a field name.
To set and clear bits, use the C bitwise operators:
PORTB |= (1 << 4); //Set bit 4
PORTB &= ~(1 << 6); //Clear bit 6
PORTB = 0x03; //Set bits 0 and 1, clear all others
These let you use a variable:
PORTB |= (1 << i); //Set bit i
PORTB &= ~(1 << i); //Clear bit i
There are other ways of doing what you want, but this is the simplest.
Your code seems to turn on one LED at a time, going from B0 to B7 and back again. If you want to make an actual binary counter, you could do something like this:
unsigned long i;
while (1)
{
for (i = 0; i < 256; i++)
{
PORTB = i;
Delay_ms(1000);
}
}
On more recent PICs, it's better style to use LATB instead of PORTB.
UPDATE: The basic rule for bitwise operators is that you OR with 1 to set bits, AND with 0 to clear bits, and XOR with 1 to toggle bits. The bitwise operations I used above work like this:
PORTB |= (1 << 4); //Set bit 4
is equivalent to:
PORTB = PORTB | (1 << 4); //Set bit 4
(1 << 4) is equal to 00010000. In decimal, that's \$1 \cdot 2^4\$, or 16. In hexadecimal, it's 0x10. The OR operator (|) works by ORing each bit in PORTB with the corresponding bit in (1 << 4). For example, if PORTB only has bit 1 set to start with, you get:
00000010 PORTB
| 00010000 (1 << 4)
-----------
00010010 PORTB with bit 4 set
Bit 4 is set without disturbing any other bit.
Clearing a bit is only slightly more complicated. Let's start with a binary example. If PORTB has bits 2 and 4 set, and you want to clear bit 2, you need to do this:
00010100 PORTB
& 11111011 Mask value for clearing bit 2
-----------
00010000 PORTB with bit 2 cleared
Bit 2 is cleared without disturbing any other bit. See how the second operand is almost all ones? We could write that out manually in hexadecimal as 0xFB:
PORTB = PORTB & 0xFB; //Clear bit 2
But then whoever reads the code has to stop and think about which bit(s) 0xFB clears. We can make the code more clear by setting the one bit we care about to 2 and inverting the result:
~(1 << 2);
which gives:
~ 00000100 (1 << 2)
-----------
11111011 ~(1 << 2) = Mask value for clearing bit 2
This is more clear than a hexadecimal value:
PORTB = PORTB & ~(1 << 2); //Clear bit 2
which shortens to:
PORTB &= ~(1 << 2) //Clear bit 2
When you're setting or clearing multiple bits, it's more common to use hexadecimal values instead of writing out a bunch of shifted values. The only way to deal with this is to learn how hexadecimal relates to binary, which isn't very hard.
One important caveat is that C compilers interpret numbers as signed ints. If an int is only 16 bits (which is often true in MCUs), then something like this can fail:
unsigned long a;
a = (1 << 20); //Needs more than 16 bits -- too big for an int!
//a could now be equal to zero
You can fix this by casting one of the numbers to a long. Make it unsigned to avoid warnings when the top bit is set:
unsigned long a;
a = (1UL << 20); //Should now work correctly
It's almost always better to use uint8_t, uint16_t, and uint32_t from instead of the native C types, but using the UL suffix is still common.