Sonsivri
 
*
Welcome, Guest. Please login or register.
Did you miss your activation email?
April 18, 2024, 08:23:02 08:23


Login with username, password and session length


Pages: [1]
Print
Author Topic: AVR C language question  (Read 5114 times)
0 Members and 1 Guest are viewing this topic.
LabVIEWguru
Senior Member
****
Offline Offline

Posts: 300

Thank You
-Given: 270
-Receive: 593



« on: August 13, 2014, 03:55:06 03:55 »

I was never a "strong" C programmer, but I'm trying to learn again with an ATMEGA 16 development board and the Atmel Studio 6 development environment. I have a puzzle - it works, but I'm not sure why, and that concerns me. I would appreciate some advice.

We have:

USCRB |= (1<<RXEN) | (1<<TXEN);

So:

           USCRB                                  |=                     (1<<RXEN)                             |      (1<<TXEN)
(USART Function Register)   [Assignment by bitwise OR]   (RXEN PREVIOUSLY DECLARED) (OR)   (TXEN PREVIOUSLY DECLARED)

Is this then to be read "OR RXEN OR TXEN IN USCRB WITH "1" ?

I thought "<<" was a Logical Shift Left and ">>" was a logical shift right?  I am missing some basic principle or is it necessary to "shift" a one into the bit position of this register?

Why is this better than:

     LDA  #%00001100
     ORA  USCRB

Is there a more efficient way to do this in "C" ?

Thanks in advance - I tend to think in assembler, and it's difficult to make the transition.

LabVIEWguru
Logged
micropcb
Active Member
***
 Muted
Offline Offline

Posts: 113

Thank You
-Given: 53
-Receive: 390


« Reply #1 on: August 13, 2014, 04:31:31 04:31 »

Hi,
 (1<<RXEN)   will shift the number 1 i.e. 00000001 RXEN places to the left. So if for eg. RXEN is the fifth bit in USCRB, then this will generate 00010000. Similarly if TXEN is the 3rd bit, you will get 00000100. ORing these will give you 00010100.This is ORed with the current value in USCRB to change only the required flags.

This certainly makes the code more readable than
USCRB |= 0b00010100

Hope this helps.
« Last Edit: August 13, 2014, 04:46:53 04:46 by micropcb » Logged
sam_des
Senior Member
****
Offline Offline

Posts: 253

Thank You
-Given: 124
-Receive: 146


« Reply #2 on: August 13, 2014, 05:41:37 05:41 »

@LabVIEWGuru,
I assume you are using gcc.
If you read the "iom16.h" you will get following definitions,
Code:
#define UCSRB   _SFR_IO8(0x0A)
#define TXB8    0
#define RXB8    1
#define UCSZ2   2
#define TXEN    3
#define RXEN    4
#define UDRIE   5
#define TXCIE   6
#define RXCIE   7

_SFR_IO8() is gcc macro which flags that this is 8-BIT SF register within IO memory space so that compiler can generate proper instructions.
You are right in saying that << is actually shift left operation. so,
Code:
(1<<RXEN) ---> (1<<4) ---> (0b_0000_0001 << 4) ---> 0b_0001_0000
(1<<TXEN) ---> (1<<3) ---> (0b_0000_0001 << 3) ---> 0b_0000_1000
& ORing them -> 0b_0001_1000

Finally ORing them with UCSRB leaves bits which are already set within it unchanged...
Code:
Suppose we have already set the RX interrupt, UCSRB = 0b_1000_0000

Now,
UCSRB |= (1<<RXEN) | (1<<TXEN) will make, UCSRB = 0b_1001_1000

Note that there is equivalent code for clearing the bits,
Code:
UCSRB &= ~(1<<RXCIE) ==> UCSRB &= 0b_0111_1111

will clear only only RX interrupt while other bits are left unchanged. You can extend code to clear multiple bits,
UCSRB &= ~(1<<RXCIE) & ~(1<<TXCIE)

The (1<<x) just simplifies, is more readable & less error prone as you are less likely miss wrong register names than wrong binary number...As you as well as micropcb both made mistake in specifying the bit positions...  Huh Sad

There is no advantage in terms of code or speed with either method.

Another probably more important thing is if you enable strict ANSI-C checking, binary constants are flagged as error. So you can't use directly "UCSRB |= 0b00011000". Though gcc/Imagecraft/Codevision allows it, IAR doesn't  Sad Embarrassed So your code wouldn't be portable between compilers as easily...

There is another special macro for gcc in sfr_defs.h, included by io.h, (you can use it with any c-compiler) which make it even more readable,
Code:
#define _BV(x)           (1<<x)

You can use it,

UCSRB |= _BV(RXEN) | _BV(TXEN)

BTW, You can use same method of (1<<x) is avr assembler too.

Hope that helps,
sam_des
« Last Edit: August 13, 2014, 06:12:22 06:12 by sam_des » Logged

Never be afraid to do something new. Remember Amateurs built the Ark, Professionals built the Titanic !
hate
Hero Member
*****
 Warned
Offline Offline

Posts: 555

Thank You
-Given: 156
-Receive: 355


« Reply #3 on: August 13, 2014, 09:34:46 09:34 »

One more point to mention is the operator precedence in C, that is when 2 or more operators (like |=, |, &, [], *, -> etc...) are encountered in the same statement by the compiler, the compiler evaluates those operators in the order of their priority. So shift operators (<<) in the parenthesis' come first then the OR operator (|) and then the assignment (|=) in your example.
http://en.cppreference.com/w/c/language/operator_precedence
Logged

Regards...
sarah90
Active Member
***
Offline Offline

Posts: 111

Thank You
-Given: 7
-Receive: 11



« Reply #4 on: August 13, 2014, 05:30:15 17:30 »

One last thing to add is that modern compilers will evaluate (1<<RXEN) | (1<<TXEN) directly to 0b00001100 before generating code and will not generate code to calculate the value on the AVR as could happen in the old days. It may not look as an efficient operation but it is.
Logged
havok1919
Newbie
*
Offline Offline

Posts: 8

Thank You
-Given: 17
-Receive: 6


« Reply #5 on: August 15, 2014, 07:41:50 07:41 »

@LabVIEWGuru,

Just for a little insight as to 'why'-- the idea is to make the code more easily portable (and arguably more readable).  In theory, the same chunk of serial code could run on multiple targets (even different CPU cores that might just share a peripheral IP block-- like Atmel's Mega/XMega/AVR32/ARM) just by a header file changing around the defined values of RXEN and TXEN.  You really saw the style show up a lot when the highly integrated ARM SOCs started coming out maybe ten years back with 6000+ page manuals and massive IP blocks that were just bolted on to cores with all the BSP stuff and docs automatically generated.  The stype has since trickled down to smaller MCU's.

One thing about that operator precedence link... C leaves a lot of evaluations 'undefined' for precedence since different architectures may have more or less efficient ways to perform something.  Even something trivial:

Code:
a[i]=i++;

Isn't actually defined in K&R C to evaluate in a particular order (compiler's discretion), so if you want to ensure things happen the way you want them to it's often easier to use C more like 'portable assembler' and break complex statements up in to multiple smaller ones and/or use intermediate variables to hold results as you evaluate and before you combine them.
Logged
hate
Hero Member
*****
 Warned
Offline Offline

Posts: 555

Thank You
-Given: 156
-Receive: 355


« Reply #6 on: August 15, 2014, 02:11:06 14:11 »

One thing about that operator precedence link... C leaves a lot of evaluations 'undefined' for precedence since different architectures may have more or less efficient ways to perform something.
Yeah I forgot to mention, for the operator precedence to work as expected, the compiler must be ANSI-C compliant.
Logged

Regards...
bigtoy
Active Member
***
Offline Offline

Posts: 238

Thank You
-Given: 322
-Receive: 297


« Reply #7 on: August 24, 2014, 12:19:56 00:19 »

Operator precedence in C has certainly bitten me before. These days I look carefully at my code, and if there's a line which assumes a specific operator precedence, I break it into 2 (or more) lines so that it's no longer dependent upon it.  (Depending upon the code, sometimes extra brackets can help too.)
Logged
FTL
Junior Member
**
Offline Offline

Posts: 83

Thank You
-Given: 170
-Receive: 33


« Reply #8 on: August 24, 2014, 05:47:06 05:47 »

The syntax you are using is somewhat forced by the way the data fields are defined in the header file:
Code:
#define UCSRB   _SFR_IO8(0x0A)
#define TXB8    0
#define RXB8    1
#define UCSZ2   2
#define TXEN    3
#define RXEN    4
#define UDRIE   5
#define TXCIE   6
#define RXCIE   7
In this case each bit is defined by its position in the byte, so you need to create a byte with the bit in the proper position before doing anything. Hence the "(1<<RXEN)" that creates a byte (actually just a constant, not a byte) with a 1-bit in the proper place.

This makes a header file that documents the bit positions in a very clear fashion, but forces you to code the (1<<RXEN), which is resolved at compile time.

There are several other ways that the header file could have been coded. All are valid, but force the use of different syntax when using the bit fields.

For instance, it could have been coded something like this:
Code:
#define UCSRB   _SFR_IO8(0x0A)
#define TXB8    0x01
#define RXB8    0x02
#define UCSZ2   0x04
#define TXEN    0x08
#define RXEN    0x10
#define UDRIE   0x20
#define TXCIE   0x40
#define RXCIE   0x80
Or
Code:
#define UCSRB   _SFR_IO8(0x0A)
#define TXB8    1
#define RXB8    2
#define UCSZ2   4
#define TXEN    8
#define RXEN    16
#define UDRIE   32
#define TXCIE   64
#define RXCIE   128
In this case you could simply OR the fields together because each bit field is already defined with a 1 bit in the proper position. I think this format of bit definitions is the most common on various platforms with the code using +, ANDs and ORs to set, clear and test the bits. You can test for two bits by coding (TXB8 + RXB8), which results in a 3, which can be or'ed with the data field to test either bit.

This is less clear in the definition, but quick to code and bit a bit unclear in the code in that you are doing OR and ANDs to test, set, and clear bits. That is the assembler way though, so most programmers find it clear.

Another technique is to define C structures for each data byte that contains flags. For Instance:
Code:
struct {
  int TXB8  : 1;
  int RXB8  : 1;
  int UCSZ2 : 1;
  int TXEN  : 1;
  int RXEN  : 1;
  int UDRIE : 1;
  int TXCIE : 1;
  int RXCIE : 1;
} S_UCSRB;
The ":1" defines a bit field of length 1.

A variable must then be defined at the appropriate memory location (probably called UCSRB in this case). Positioning at a specific location in memory generally uses a C language extension or PRAGMA statement supported by your compiler. It can also be referenced using a pointer. Once the structure and variable are defined, bits can be referred to by their actual name.

For instance:
Code:
UCSRB.RXCIE=1;
if (UCSRB.RXCIE) { /* some code */ );
pUCSRB->S_UCSRB.RXCIE = 1;

I don't think this is as commonly used, but I think it both makes the header file clear and makes the code very clear. Maybe it is less common because it moves fully into a high-level language way of doing things, instead of an assembly language way, which coders seem to like.

I have seen all three techniques used and I think they are all fine. the key is that you need to look at the definition in the header file to see how the bits are defined and how to use them for your platform. You really need to do that for every bit field you use, because the header files may not be consistent.

Edit: Fix the examples I gave in the second technique.
« Last Edit: August 24, 2014, 04:36:08 16:36 by FTL » Logged
metal
Global Moderator
Hero Member
*****
Offline Offline

Posts: 2420

Thank You
-Given: 862
-Receive: 678


Top Topic Starter


« Reply #9 on: August 24, 2014, 06:24:43 06:24 »

the last method you described is widely used in ARM header files. vendors build most of the header files using structs to make the code smaller.

Look at "Accessing Peripherals in C" page 61 - Joseph Yiu, The definitive Guide to Cortex-M0 Book.
« Last Edit: August 24, 2014, 06:27:33 06:27 by metal » Logged
FTL
Junior Member
**
Offline Offline

Posts: 83

Thank You
-Given: 170
-Receive: 33


« Reply #10 on: August 24, 2014, 04:33:14 16:33 »

One thing to be clear about that I did not mention is the ordering of the bits within the byte. Especially if you are making your own header files. Lots of debugging time can be wasted by actually referencing a different bit than you intended.

In the first technique, it is strictly a standard within the header whether the high-order or low-order bit is bit #1. In most cases, the bit numbering would probably match the ordering used in the chip datasheet, but that is no guarantee. Either the high-order or low-order bit could be defined as bit # 0.

In the second technique, there is little room for error, as x01 clearly refers to the low-order bit, and x80 the high-order bit. (In typing this I realized that I used 80 my original post, not 0x80 or 128 as I should have. I will edit the original post to fix that.)

In the third technique the C language defines the order in that the first bit defined in the structure is the low-order bit, so it is unambiguous. I find that not 100% intuitive, since as you go down the list of bits in the structure, you are moving up towards higher order bits in the byte. That is opposite to what a structure of chars would do. There, the first byte defined is the first in memory and as you define more bytes you move up in memory.
Logged
sam_des
Senior Member
****
Offline Offline

Posts: 253

Thank You
-Given: 124
-Receive: 146


« Reply #11 on: August 24, 2014, 06:17:44 18:17 »

Quote
Another technique is to define C structures for each data byte that contains flags. For Instance:
Code:

struct {
  int TXB8  : 1;
  int RXB8  : 1;
  int UCSZ2 : 1;
  int TXEN  : 1;
  int RXEN  : 1;
  int UDRIE : 1;
  int TXCIE : 1;
  int RXCIE : 1;
} S_UCSRB;

The ":1" defines a bit field of length 1.

A variable must then be defined at the appropriate memory location (probably called UCSRB in this case). Positioning at a specific location in memory generally uses a C language extension or PRAGMA statement supported by your compiler. It can also be referenced using a pointer. Once the structure and variable are defined, bits can be referred to by their actual name.

For instance:
Code:

UCSRB.RXCIE=1;
if (UCSRB.RXCIE) { /* some code */ );
pUCSRB->S_UCSRB.RXCIE = 1;


Yes, this is much more intuitive than using _BV() macros. But with AVR, you've to be careful while specifying the absolute address of new bit-field variable.
e.g. UCSRB has address of 0x0A, but it is in I/O register space,
Code:
#define UCSRB_sfr_bits (*((volatile struct UCSRB_bits_t*)0x0a))

UCSRB_sfr_bits.bTXCIE = 1;                             // Wrong, won't access UCSRB

Compiler won't complain anything & simply spit-out,

6c: 80 91 0a 00 lds r24, 0x000A
70: 80 64            ori r24, 0x40 ; 64
72: 80 93 0a 00 sts 0x000A, r24

Certainly not what we are looking for...

An offset of 0x20 must be added to 0x0a,
Code:
#define UCSRB_sfr_bits (*((volatile struct UCSRB_bits_t*)0x2a))             // Now correctly access UCSRB

Now compiler generates,

6c: 56 9a        sbi 0x0a, 6 ; 10


This is what actually gcc's _SFR_IO_ADDR() macro does behind the scene. See sfr_defs.h.

Also some compilers like Rowley, doesn't support the 8-BIT bit-fields.

I feel that unless compiler does natively supports SFR bit-fields, its better to stick with _BV() macros & AND, OR etc operations.

sam_des

Logged

Never be afraid to do something new. Remember Amateurs built the Ark, Professionals built the Titanic !
hate
Hero Member
*****
 Warned
Offline Offline

Posts: 555

Thank You
-Given: 156
-Receive: 355


« Reply #12 on: August 24, 2014, 09:52:44 21:52 »

Another technique is to define C structures for each data byte that contains flags. For Instance:
Code:
struct {
  int TXB8  : 1;
  int RXB8  : 1;
  int UCSZ2 : 1;
  int TXEN  : 1;
  int RXEN  : 1;
  int UDRIE : 1;
  int TXCIE : 1;
  int RXCIE : 1;
} S_UCSRB;
The ":1" defines a bit field of length 1.

A variable must then be defined at the appropriate memory location (probably called UCSRB in this case). Positioning at a specific location in memory generally uses a C language extension or PRAGMA statement supported by your compiler. It can also be referenced using a pointer. Once the structure and variable are defined, bits can be referred to by their actual name.

For instance:
Code:
UCSRB.RXCIE=1;
if (UCSRB.RXCIE) { /* some code */ );
pUCSRB->S_UCSRB.RXCIE = 1;
Not all compilers allow the programmer to use this technique. Avr-gcc for example doesn't allow the programmer to assign 'structs' to memory addresses while SDCC implements an '__at' operator to do that. Unfortunately this method isn't a standard but an extension of the compiler if implemented, hence the usage is not portable between architectures or even compilers.

@havok1919:
I missed your point in my first reply but now reading it again, I realized you meant something else.
Code:
a[i]=i++;
This example doesn't have much to do with operator precedence but with the order of evaluation of operands. The C standard defines no precedence for the order of evaluation of operands of all but 2 binary operators being '&&' and '||'. In the code example above the left side
Code:
a[i]
or the right side
Code:
i++
expression might be evaluated first depending on how the compiler implements the order and statements like this are considered bad programming practice as they depend on the order of evaluation of operands. Another example might be:
Code:
i =4;
printf("%d, %d", i, ++i);
which might print '4, 5' or '5, 5' depending on how the compiler stacks function arguments (starting from left or starting from right). And again this isn't defined anywhere in the standard.
« Last Edit: August 24, 2014, 09:54:47 21:54 by hate » Logged

Regards...
Pages: [1]
Print
Jump to:  


DISCLAIMER
WE DONT HOST ANY ILLEGAL FILES ON THE SERVER
USE CONTACT US TO REPORT ILLEGAL FILES
ADMINISTRATORS CANNOT BE HELD RESPONSIBLE FOR USERS POSTS AND LINKS

... Copyright © 2003-2999 Sonsivri.to ...
Powered by SMF 1.1.18 | SMF © 2006-2009, Simple Machines LLC | HarzeM Dilber MC