Help me understand compile/link actions

Go To Last Post
15 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi, 

My first custom project with AVR and Studio 7, although I have done various projects with Arduino previously (and prototyped this particular project as proof of concept).

 

My custom board is atmega2560 (although ... I am playing around with Arduino atmega2560 while my boards are being manufactured).

 

I am starting with the creation of an LCD library (in the broad sense of the term ...) based on a HD44780 clone, and there is clearly something I misunderstand in the compile/link process.

My library consists of LiquidCrystal.h file and LiquidCrystal.cpp.  As appears to be typical with LCD code, _delay_us/_delay_ms is used during various commands to allow the driver chip time to work.

 

So:

 

LiquidCrystal.h

#include <avr/io.h>
#include <inttypes.h>
#include <stddef.h>

#if ( !defined LCD_RSDDR || !defined LCD_RSPORT || !defined LCD_RSPIN )
	#warning "Please define LCD rs DDR/PORT/PIN numbers"
	#define LCD_RSDDR		DDRA
	#define LCD_RSPORT		PORTA
	#define LCD_RSPIN		PA1
#endif

 

LiquidCrystal.cpp 

#include <util/delay.h>
... various includes ...
... code ...

void LiquidCrystal::debug() {
	// debug code
	configure_as_output(LCD_RSDDR, LCD_RSPIN) ;
	set_high(LCD_RSPORT, LCD_RSPIN) ;
	for (uint8_t i=0; i<10; i++) {
		toggle(LCD_RSPORT, LCD_RSPIN) ;
		_delay_ms(200) ;
	}
	// end debug code
}

 

 

main.cpp

#define F_CPU 16000000UL

#include <avr/io.h>
#include <util/delay.h>

#define LCD_RSDDR		DDRA
#define LCD_RSPORT		PORTA
#define LCD_RSPIN		PA1
... etc ...
#include "LCDs/LiquidCrystal/LiquidCrystal.h"

 

You will notice the little debug() loop in LiquidCrystal.cpp; its intent to validate timing.  When I ran that method, it became apparent the clock frequency wasnt being set as I expected.  In fact, each pulse measured a touch over 12milliseconds.

 

Once I included #define F_CPU 16000000UL in lcd.cpp - the pulses measured 200msec on the dot.

 

Clearly, my first execution was operating with the delay.h default F_CPU of 1Mhz (200ms / 16 = 12.5ms).

 

Please forgive my newbidity;  

1.  Can someone explain why my F_CPU in main.cpp was not honoured ?  (I am pretty sure I know the answer to this ... which makes my next question relevant).
2.  What is the "correct" way to structure an liquidcrystal.h/.cpp "library" if I wish to use it in multiple projects considering the above ?

 

 

Many thanks in advance....

 

 

Glenn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"#define F_CPU xxxx" in main.cpp is a pre-processor symbol that is local to that single file, not something that propagates to the (separate) compilation of LiquidCrystal.cpp

Normally you'd handle "global" options like this in a separate .h file ("config.h" is common), or set up a command-line symbol definition in the project properties.  (.../Compiler/Symbols)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In C, I send F_CPU to each object module that I compile. Arduino clumps everything into a single source module before compiling (if I recall), so the single instance of F_CPU is visible. However, when I break up the files into main.c and file1.c (plus headers) F_CPU will not be noticeable to the compiler across the modules. To deal with that directive, I have the Makefile feed it  (e.g., "CPPFLAGS = -DF_CPU=$(F_CPU)" ) to the compiler before building each module. I get myself in trouble every time I think I know something about C++, but it may be the same.

 

CPPFLAGS is a variable used by implicit rules. I can't be sure, but it looks like  CXXFLAGS would be used for C++.

 

https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html

 

update: cut-paste to wrong place in text ... fixed

Last Edited: Tue. Sep 14, 2021 - 06:24 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks westfw.

 

Using .../Compiler/Symbols makes sense (at my current comprehension level anyways).

 

This post (which I only just found) discusses the same issue: https://www.avrfreaks.net/forum/...

 

Regarding the config.h file, wouldn't this suffer the same problem as before ?  where is the config.h #included ?

 

(and ... if relevant ... I have a directory in which I plan to store all "libraries" - which is defined in .../Compiler/Directories, and another directory in which I plan to store "projects".)

 

Cheers...  

 

 

Glenn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was going to show you an example of how to do a shared .h (though I would advise -D (aka "Compiler/Symbols") for anything that has to be seen across multiple sources (for example F_CPU) but reading your example got me to thinking.

 

You have:

#define LCD_RSDDR		DDRA
#define LCD_RSPORT		PORTA
#define LCD_RSPIN		PA1
... etc ...

in main.cpp but why on earth does main() need to know of these symbols that are surely "private" to the LCD library??

 

Actually come to that why are you defining these things at build time anyway? Surely you want a c'tor for your LCD class that is something much more like:

// lcd.h
class Lcd {
    Lcd(uint8_t cols=2, uint8_t rows=2,
    volatile uint8_t * port = &PORTA,
    uint8_t pinRS = 1, uint8_t pinE = 2
    uint8_t pinData0 = 4, uint8_t pinData1 = 5, uint8_t pinData2 = 6, uint8_t pinData3 = 7);

    ~Lcd();

    void init();
    void putChar(char c);
    etc..
}
//lcd.cpp
Lcd::Lcd(uint8_t cols, uint8_t rows,
    volatile uint8_t * port,
    uint8_t pinRS, uint8_t pinE,
    uint8_t pinData0, uint8_t pinData1, uint8_t pinData2, uint8_t pinData3)
    :
    mCols(cols),
    mRows(rows),
    mPort(port),
    mPinRS(pinRS),
    mPinE(pinE),
    mPinD0(pnData0),
    mPinD1(pnData1),
    mPinD2(pnData2),
    mPinD3(pnData3)
{
    // stuff
}
//main.cpp

#include "lcd.h"

Lcd myLCD(20, 4, &PORTC);

int main(void) {
    myLCD.init();
    myLCD.putchar('!');
}

This way the invoker can specify the configuration at the point of instantiation which makes for more readable code. If I see:

Lcd anotherLCD(16, 2, &PORTF, 6, 7, 0, 1, 2, 3);

then I know this is putting RS on PF6, E on PF7 and the four data lines on PF0..PF3.

 

BTW you may want to make the assumption that the four data lines are adjacent and just give the invoker control of the base though some good LCD libraries (Fleury for instance) give you the opportunity to split the four over any pins which can help with wiring nightmares.

Last Edited: Tue. Sep 14, 2021 - 08:40 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobandverns wrote:
e
bobandverns wrote:

Regarding the config.h file, wouldn't this suffer the same problem as before ?  where is the config.h #included ?

 

The answer is that you include it everywhere.

 

In the tutorials sections is an example multi-file generic C project which I wrote.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Brian Fairchild wrote:
The answer is that you include it everywhere.
That might be overkill! You would include config.h in anything that needs to see the configuration values it contains.

 

I suppose it's true to say that if you use command line -D's then that is also presenting symbols to things that may not be interested too. But in that case it does not mean that each and every last source file is peppered with #include's they aren't actually using.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

Brian Fairchild wrote:
The answer is that you include it everywhere.
That might be overkill! You would include config.h in anything that needs to see the configuration values it contains.

 

Maybe, but given that it comes at no cost it's something I do as standard.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah but then you can have files like:

 

#include <avr/io.h>
#include "configUart.h"
#include "configAdc.h"
#include "configTimer.h"

void setPORTB(uint8_t n) {
    PORTB = n;
}

The reader might look at the includes and think "somewhere buried deep in this file it's clearly using all of Uart, Adc and Timers" when in reality it is not using any of those things.

 

The aim should always be to just list the headers from which some type or functionality is being taken. Failure to do this might result in:

#include <alloca.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <avr/boot.h>
#include <avr/cpufunc.h>
#include <avr/eeprom.h>
#include <avr/fuse.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/lock.h>
#include <avr/pgmspace.h>
#include <avr/power.h>
#include <avr/sfr_defs.h>
#include <avr/signature.h>
#include <avr/sleep.h>
#include <avr/version.h>
#include <avr/wdt.h>
#include <util/atomic.h>
#include <util/crc16.h>
#include <util/delay.h>
#include <util/delay_basic.h>
#include <util/parity.h>
#include <util/setbaud.h>
#include <util/twi.h>
#include <compat/deprecated.h>

int main(void) {
    DDRB = 0xFF;
    while(1) {
        PORTB ^= 0xFF;
        _delay_ms(100);
    }
}

There seems little point in including all those header when nothing but io.h and delay.h are being used. I think the maintainer would much prefer:

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    DDRB = 0xFF;
    while(1) {
        PORTB ^= 0xFF;
        _delay_ms(100);
    }
}

with the fair assumption that things from both io.h and delay.h are being used. In fact in the past I have been known to comment:

#include <avr/io.h>         // providing DDRB / PORTB
#include <util/delay.h>     // providing _delay_ms()

int main(void) {
    DDRB = 0xFF;
    while(1) {
        PORTB ^= 0xFF;
        _delay_ms(100);
    }
}

 

Last Edited: Tue. Sep 14, 2021 - 09:46 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Huge thanks all, 

 

I do have arguments with myself about use of #define directives vs. c++ declarations/definitions ... frequently.

 

As I said in the beginning of my post, this is my first time working with Studio 7 having done Arduino stuff before, and having the abstraction (dis)advantage (personally not a huge fan of hiding things away behind black boxes ... but I get the rationale).  

 

In the past, I have tended to limit define directives to include guards, conditional code inclusion/exclusions ... and leave variable declaration/definitions to c/c++ (type safe, known scope etc. etc).    

 

My example was a little test to ensure I could #include from a common code repository until I bumped into the F_CPU / delay.h ... ummm ... thing.  

 

 

 

 

 

 

Glenn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobandverns wrote:
not a huge fan of hiding things away behind black boxes

 

I consider multiple definitions of F_CPU to be worse than placing a single instance in the Makefile. The Makefile is not a black box to my eyes; it is gears and chains exposed (e.g., like working on a bicycle.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ron_sutherland wrote:

bobandverns wrote:
not a huge fan of hiding things away behind black boxes

 

I consider multiple definitions of F_CPU to be worse than placing a single instance in the Makefile. The Makefile is not a black box to my eyes; it is gears and chains exposed (e.g., like working on a bicycle.)

 

Wasn't referring to makefile Ron ... was referring to Arduino.  In fact, havent started playing around with Makefile yet.  If my understanding of it is correct, I totally agree with you.

Glenn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Usually the point of a .h file is to be able to include it in multiple files.

kitchensink.h can be a legibility problem.

 

I have put all the #defines representing hardware choices into a common file,

e.g. pins.h .

Mostly because it was easier,

but it also allowed easy eyeball-checking of whether I

used the same pin for an LED and for software SPI.

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Regarding the config.h file, wouldn't this suffer the same problem as before ?  where is the config.h #included ?

My intent was that the config.h file be included in every C or C++ file, and that it contain mostly definitions that apply to the whole "project" in some sense.  If it contains only F_CPU, that wouldn't be awful.

In your case, I'd also put the LCD pins in there (as per skeeve - which pins do what is a project-level thing, and having them all in one place is good.)

Note that the "cost" of having extra include files, or of including them where they're not actually necessary, is quite low on modern systems.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Arduino clumps everything into a single source module before compiling (if I recall)

Arduino "clumps" all of the .ino files together, but if your project has .c, .cpp, or .S files, they will be compiled separately, just the way C/CPP/S files are normally compiled (no "Arduino pre-processing", either, so they need prototypes for forward references, too.)