Thread: What is the correct way to link multiple .c and .h files? (I'm doing it wrong)

  1. #1
    Registered User
    Join Date
    Nov 2018
    Posts
    5

    Question What is the correct way to link multiple .c and .h files? (I'm doing it wrong)

    I'm a bit new to C/C++. I'm working on my first real project, and it's the first time I've used multiple files. I'm currently stuck with it saying I have multiple definitions of a big long list of stuff (just about everything).

    The way I have the code set up is each file has its own .c and .h file, declarations and constant definitions in the header files, then function definitions in the c file. The files form a long chain so that all files get included, and none are included more than once. In ascending order, each file includes the one before it.

    1. Bus_Timing.h (uses "extern" to reference a variable from file 3)
    2. Bus_Timing.c
    3. Communication_Format.h
    4. Communication_Format.c (uses functions & variables from file 1&2, naturally called/used because those files are upstream in the include chain)
    5. Cashless.h
    6. Cashless.c (uses functions & variables from files 1-4, they're included upstream)
    7. Main.c


    In general, the first few errors say I defined a function in file 2 (or "defined" a variable, by assigning it a value), then when I try to use that function or variable in file 6, it sees it as a 2nd definition rather than a function call or me assigning a value to the variable.



    If what I'm doing wrong hasn't been revealed yet, here's the files and error codes. I've left the "cashless" files out because they're 1000+ lines each. Note, i'm using arduino so there is a bit of c++ but I'm trying to stick to C because I want this to run on linux eventually. You can get to the error list by scrolling to the very bottom.

    Thanks if anyone helps! It's an open source project (currently just me working on it) I made to provide super easy to use, 100% capable&configurable code to interact with vending machines in an abstract way (making it substantially easier for a tech startup to make some new device for a vending machine). The industry standard this code is a transcription of, has existed nearly half a century, and still there are no software libraries for it, so that's what this code is aimed to fix. There's more files, but they're irrelevant to the question.

    Bus_Timing.h
    Code:
    #include <esp32-hal.h>
     
    struct transmission {                              //Structure for status/timing flags related to the MDB transmission.
        unsigned char isUnresponsive : 1; //Flag set when transmission completes and non-response time is reached before a response.
        unsigned char isDone : 1;         //Flag set when recieved transmission completes and inter-byte time-out is reached before the next byte, or know end of conversation (poll required before peripheral transmits again).
        unsigned char isResetting : 1;    //Flag set when a 'BUS RESET' is taking place.
        unsigned char isActive : 1;       //Flag set when VMC's TX line goes active, and unset the instant the line goes inactive (used to detect 'BUS RESET').
    } transmission;
    
    unsigned long microseconds;//microseconds = Used by the timing method with 'micros()' in order to measure how many microseconds have gone by since the method was last called, rather than how many have gone by since the system started up.
    void chronoLogic(unsigned char command, unsigned char address);    //Stopwatch that updates all timing flags.  Update later to have separate timers for each device.
    
    extern unsigned int result;
    extern unsigned long micros();
    Bus_Timing.c
    Code:
    #include "Bus_Timing.h"
    
    void chronoLogic(unsigned char command, unsigned char address) {     //Command 0 to reset.  Command 1 to update timing variables.  Think of it like a stopwatch, but it updates flags rather than reporting a time.
      if (!command) {
          microseconds = micros();              //Save time of this command's execution.
          transmission.isDone = 0;              //Reset the inter-byte timing flag.
          transmission.isUnresponsive = 0;      //Reset the non-response timing flag.
          transmission.isResetting = 0;         //Reset the 'BUS RESET' timing flag.
          return;
      }
      result = micros() - microseconds;
      if (result > 2144) transmission.isDone = 0x01;
      if (result > 6144) transmission.isUnresponsive = 0x01;
      if (result > 101144) transmission.isResetting = 0x01;
      if (result > 201144) transmission.isResetting = 0x00;
      return;
    }
    Code:
    #include "Bus_Timing.cpp"
    
    unsigned int counter, result, pointer;
    unsigned char checksum, ack, ret, nak;
    unsigned short txBypass; /*Fixes tx() using nineBit datatype's read-only as a write method.*/
     /*counter = A tracker for loops to define an endpoint with.*/
     /*result = A non-volatile temporary storage variable for results of operations to be stored.  When calling a method and using the returned value from it, this makes checking the value more than once possible.*/
     /*pointer = Points to the last byte in a data block to indicate when to stop transmitting or when the last recieved byte occurs within the block.*/
     /*checksum = Calculated checksum for comparison with recieved checksum.*/
     /*ack, ret, nak = Values of described command/value.*/
    void serialEvent1();    //9 bit UART data recieved.
    int rX(unsigned char address);               //Recieves a byte, judges if it needs to recieve the rest of the data, and ends at appropriate time.  Returns status codes.
    void clearBlock();      //Clear all communications related variables.
    int tX(unsigned int pointer, unsigned char address);               //Send the data in the block, calculate and insert check byte into location of pointer, uness pointer is zero.  ALways sets mode bit on last byte.
    
    union nineBit {                   //Union allows writing mode and data bits simultaneously as a short, therefore not overwriting one another.  Bit format: dddddddd_______m
      struct {                        //Structure allows accessing 8 data bits and 1 mode bit separately, as well as setting the parent union's size.
        unsigned char data : 8;       //8 data bits dddddddd most significant byte when written to as a short..
        unsigned char mode : 1;       //1 mode bit  _______m least significant byte when written to as a short.
        } part;                       //Named for readability & organization of code.
        unsigned short whole;         //Allows writing the 'data' and 'mode' all at once, otherwise the second write would erase the first.
    } block[35];                      //Array of 9 bit data, 36 is the maximum amount of 'bytes' MDB allows per transmission.
    unsigned char command; //Would be part of the 'block' union but must be a separate variable.
    
    static struct peripheral {
      unsigned char isActive : 1;
      unsigned char address;
    } peripheral;
    Communication_Format.c
    Code:
    //static unsigned char
    #include "Communication_Format.h"
    #include <HardwareSerial.h>
    
    int rX(unsigned char address) {                                          //1=ACK, 2=NAK, 3=RET, 4=NO-RESPONSE, 5=Ignored, 6=OK!
        clearBlock();
        while (!Serial1.available()) {                  //Wait for first byte.
            chronoLogic(1, address);                             //Update timing flags.
            if (transmission.isUnresponsive)            //Check if time is up.
                return 4;                               //Return status that time is up if so.
        }
        block[counter].whole = Serial1.read();          //Get first byte.
        command = block[0].part.data;
      if (block[0].part.data == ack)                //If Acknowledged...
        return 1;                             //Return with one, meaning Acknowledge response recieved.
      if (block[0].part.data == ret)                //If VMC requests a retransmit...
        return 3;                               //Return to API in order to reload the block w/ data.
      if (block[0].part.data == nak)                //If Negatively Acknowledged...
        return 2;
      if (!block[0].part.mode) {
          if (!peripheral.isActive)
              return 5;                               //Return if it's not an address byte and the device isn't active.
      }
      else if ((block[0].part.data & 0xf8) != peripheral.address)
             return 5;                                //Return if it's not addressed to you.
        while (counter != 36)  {                      //While the counter does not exceed the maximum block size...
            while (!Serial1.available()) {               ////Wait for data to be recieved.
                chronoLogic(1, address);                         //Update timing flags.
                if (transmission.isDone)                //Check for inter-byte timeout.
                    return 6;                           //Transmission ended.
            }
            chronoLogic(0, address);
            checksum += block[counter].part.data;       //Add last byte to checksum.
            counter++;
            block[counter].whole = Serial1.read();      //Recieve data byte.
        }
    }
    
    void clearBlock() {                            //Clears data out of the block.
        counter = 0;
        while (counter != 36) {
            block[counter].whole = (0x0000);
            counter++;
        }
        counter = 0;
        command = 0x0;
        checksum = 0x00;
    }
    
    int tX(unsigned int pointer, unsigned char address) {
        counter = 0;
        checksum = 0x00;
        while (pointer != counter) {
        Serial1.write(block[counter].whole);
        checksum += block[counter].part.data;     //Add on to the checksum byte.
        counter++;
        Serial1.flush();                        //Wait for information to finish sending, placed here so previous commands execute prior to any waiting time.
        }
        block[counter].whole = (block[counter].whole | checksum | 0x100);
        Serial1.write(block[counter].whole);
        chronoLogic(0, address);                         //Set non-response timer.
        result = rX(address);
        if ((result == 1) || (result == 2) || (result == 4))
            clearBlock();
        return result;
    }  //The pointer describes how many data bytes are sent.  If just sending ACK or NAK, there are zero data bytes.  Commands are considered data bytes.

    Error
    Code:
    Arduino: 1.8.7 (Mac OS X), Board: "ESP32 Dev Module, Disabled, Default, 240MHz (WiFi/BT), QIO, 80MHz, 4MB (32Mb), 921600, None"
    
    sketch/Cashless.cpp.o:(.bss.transmission+0x0): multiple definition of `transmission'
    sketch/Bus_Timing.cpp.o:(.bss.transmission+0x0): first defined here
    sketch/Cashless.cpp.o: In function `chronoLogic(unsigned char, unsigned char)':
    Cashless.cpp:(.text._Z11chronoLogichh+0x0): multiple definition of `chronoLogic(unsigned char, unsigned char)'
    sketch/Bus_Timing.cpp.o:Bus_Timing.cpp:(.text._Z11chronoLogichh+0x0): first defined here
    sketch/Cashless.cpp.o:(.bss.microseconds+0x0): multiple definition of `microseconds'
    sketch/Bus_Timing.cpp.o:(.bss.microseconds+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.transmission+0x0): multiple definition of `transmission'
    sketch/Bus_Timing.cpp.o:(.bss.transmission+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.result+0x0): multiple definition of `result'
    sketch/Cashless.cpp.o:(.bss.result+0x0): first defined here
    sketch/Communication_Format.cpp.o: In function `chronoLogic(unsigned char, unsigned char)':
    Communication_Format.cpp:(.text._Z11chronoLogichh+0x0): multiple definition of `chronoLogic(unsigned char, unsigned char)'
    sketch/Bus_Timing.cpp.o:Bus_Timing.cpp:(.text._Z11chronoLogichh+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.counter+0x0): multiple definition of `counter'
    sketch/Cashless.cpp.o:(.bss.counter+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.block+0x0): multiple definition of `block'
    sketch/Cashless.cpp.o:(.bss.block+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.command+0x0): multiple definition of `command'
    sketch/Cashless.cpp.o:(.bss.command+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.checksum+0x0): multiple definition of `checksum'
    sketch/Cashless.cpp.o:(.bss.checksum+0x0): first defined here
    sketch/Communication_Format.cpp.o: In function `clearBlock()':
    Communication_Format.cpp:(.text._Z10clearBlockv+0x0): multiple definition of `clearBlock()'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z10clearBlockv+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.ack+0x0): multiple definition of `ack'
    sketch/Cashless.cpp.o:(.bss.ack+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.ret+0x0): multiple definition of `ret'
    sketch/Cashless.cpp.o:(.bss.ret+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.nak+0x0): multiple definition of `nak'
    sketch/Cashless.cpp.o:(.bss.nak+0x0): first defined here
    sketch/Communication_Format.cpp.o: In function `rX(unsigned char)':
    Communication_Format.cpp:(.text._Z2rXh+0x0): multiple definition of `rX(unsigned char)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z2rXh+0x0): first defined here
    sketch/Communication_Format.cpp.o: In function `tX(unsigned int, unsigned char)':
    Communication_Format.cpp:(.text._Z2tXjh+0x0): multiple definition of `tX(unsigned int, unsigned char)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z2tXjh+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.txBypass+0x0): multiple definition of `txBypass'
    sketch/Cashless.cpp.o:(.bss.txBypass+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.pointer+0x0): multiple definition of `pointer'
    sketch/Cashless.cpp.o:(.bss.pointer+0x0): first defined here
    sketch/Communication_Format.cpp.o:(.bss.microseconds+0x0): multiple definition of `microseconds'
    sketch/Bus_Timing.cpp.o:(.bss.microseconds+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.cashless2+0x0): multiple definition of `cashless2'
    sketch/Cashless.cpp.o:(.bss.cashless2+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.cashless+0x0): multiple definition of `cashless'
    sketch/Cashless.cpp.o:(.bss.cashless+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.transmission+0x0): multiple definition of `transmission'
    sketch/Bus_Timing.cpp.o:(.bss.transmission+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.result+0x0): multiple definition of `result'
    sketch/Cashless.cpp.o:(.bss.result+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `chronoLogic(unsigned char, unsigned char)':
    MDB.ino.cpp:(.text._Z11chronoLogichh+0x0): multiple definition of `chronoLogic(unsigned char, unsigned char)'
    sketch/Bus_Timing.cpp.o:Bus_Timing.cpp:(.text._Z11chronoLogichh+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.counter+0x0): multiple definition of `counter'
    sketch/Cashless.cpp.o:(.bss.counter+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.block+0x0): multiple definition of `block'
    sketch/Cashless.cpp.o:(.bss.block+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.command+0x0): multiple definition of `command'
    sketch/Cashless.cpp.o:(.bss.command+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.checksum+0x0): multiple definition of `checksum'
    sketch/Cashless.cpp.o:(.bss.checksum+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `clearBlock()':
    MDB.ino.cpp:(.text._Z10clearBlockv+0x0): multiple definition of `clearBlock()'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z10clearBlockv+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.ack+0x0): multiple definition of `ack'
    sketch/Cashless.cpp.o:(.bss.ack+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.ret+0x0): multiple definition of `ret'
    sketch/Cashless.cpp.o:(.bss.ret+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.nak+0x0): multiple definition of `nak'
    sketch/Cashless.cpp.o:(.bss.nak+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `rX(unsigned char)':
    MDB.ino.cpp:(.text._Z2rXh+0x0): multiple definition of `rX(unsigned char)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z2rXh+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `tX(unsigned int, unsigned char)':
    MDB.ino.cpp:(.text._Z2tXjh+0x0): multiple definition of `tX(unsigned int, unsigned char)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z2tXjh+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `cashlessSetup()':
    MDB.ino.cpp:(.text._Z13cashlessSetupv+0x0): multiple definition of `cashlessSetup()'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z13cashlessSetupv+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.cashless1+0x0): multiple definition of `cashless1'
    sketch/Cashless.cpp.o:(.bss.cashless1+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `resetPeripheral(int)':
    MDB.ino.cpp:(.text._Z15resetPeripherali+0x0): multiple definition of `resetPeripheral(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z15resetPeripherali+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `beginSession(int, String)':
    MDB.ino.cpp:(.text._Z12beginSessioni6String+0x0): multiple definition of `beginSession(int, String)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z12beginSessioni6String+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `startCashless(int)':
    MDB.ino.cpp:(.text._Z13startCashlessi+0x0): multiple definition of `startCashless(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z13startCashlessi+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `sessionCancelRequest(int, String)':
    MDB.ino.cpp:(.text._Z20sessionCancelRequesti6String+0x0): multiple definition of `sessionCancelRequest(int, String)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z20sessionCancelRequesti6String+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `vendApproved(int, String)':
    MDB.ino.cpp:(.text._Z12vendApprovedi6String+0x0): multiple definition of `vendApproved(int, String)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z12vendApprovedi6String+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `vendDenied(int, String)':
    MDB.ino.cpp:(.text._Z10vendDeniedi6String+0x0): multiple definition of `vendDenied(int, String)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z10vendDeniedi6String+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `displayRequest(String)':
    MDB.ino.cpp:(.text._Z14displayRequest6String+0x0): multiple definition of `displayRequest(String)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z14displayRequest6String+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `displayCancel(String)':
    MDB.ino.cpp:(.text._Z13displayCancel6String+0x0): multiple definition of `displayCancel(String)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z13displayCancel6String+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.pointer+0x0): multiple definition of `pointer'
    sketch/Cashless.cpp.o:(.bss.pointer+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `rESET(int)':
    MDB.ino.cpp:(.text._Z5rESETi+0x0): multiple definition of `rESET(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z5rESETi+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `pOLL(int)':
    MDB.ino.cpp:(.text._Z4pOLLi+0x0): multiple definition of `pOLL(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z4pOLLi+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `sETUP(int)':
    MDB.ino.cpp:(.text._Z5sETUPi+0x0): multiple definition of `sETUP(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z5sETUPi+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `vEND(int)':
    MDB.ino.cpp:(.text._Z4vENDi+0x0): multiple definition of `vEND(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z4vENDi+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `rEADER(int)':
    MDB.ino.cpp:(.text._Z6rEADERi+0x0): multiple definition of `rEADER(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z6rEADERi+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `rEVALUE(int)':
    MDB.ino.cpp:(.text._Z7rEVALUEi+0x0): multiple definition of `rEVALUE(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z7rEVALUEi+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `eXPANSION(int)':
    MDB.ino.cpp:(.text._Z9eXPANSIONi+0x0): multiple definition of `eXPANSION(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z9eXPANSIONi+0x0): first defined here
    sketch/MDB.ino.cpp.o: In function `cashlessMain(int)':
    MDB.ino.cpp:(.text._Z12cashlessMaini+0x0): multiple definition of `cashlessMain(int)'
    sketch/Cashless.cpp.o:Cashless.cpp:(.text._Z12cashlessMaini+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.txBypass+0x0): multiple definition of `txBypass'
    sketch/Cashless.cpp.o:(.bss.txBypass+0x0): first defined here
    sketch/MDB.ino.cpp.o:(.bss.microseconds+0x0): multiple definition of `microseconds'
    sketch/Bus_Timing.cpp.o:(.bss.microseconds+0x0): first defined here
    /var/folders/yp/9ssqgmw5487096x4gxt_syn80000gp/T/arduino_cache_16607/core/core_6a7bd73d325d0e5f3ec363d0061f08d8.a(HardwareSerial.cpp.o):(.bss.Serial+0x0): multiple definition of `Serial'
    sketch/MDB.ino.cpp.o:(.bss.Serial+0x0): first defined here
    /var/folders/yp/9ssqgmw5487096x4gxt_syn80000gp/T/arduino_cache_16607/core/core_6a7bd73d325d0e5f3ec363d0061f08d8.a(main.cpp.o):(.literal._Z8loopTaskPv+0x4): undefined reference to `loop()'
    /var/folders/yp/9ssqgmw5487096x4gxt_syn80000gp/T/arduino_cache_16607/core/core_6a7bd73d325d0e5f3ec363d0061f08d8.a(main.cpp.o): In function `loopTask(void*)':
    /Users/Nathan/Documents/Arduino/hardware/espressif/esp32/cores/esp32/main.cpp:23: undefined reference to `loop()'
    collect2: error: ld returned 1 exit status
    exit status 1
    Error compiling for board ESP32 Dev Module.
    
    This report would have more information with
    "Show verbose output during compilation"
    option enabled in File -> Preferences.

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by mr_darker
    I'm a bit new to C/C++.
    There is no such language called C/C++. There is a programming language called C, and another programming language called C++ which has a language standard that makes references to the language standard for C, and it is possible to write a C program that can be compiled as C++ (and vice versa), but they are ultimately two different programming languages.

    Hence, I am concerned that you posted in this forum for C programming, and your code appears to contain at least one file with a ".c" extension, but it also contains at least one file with a ".cpp" extension. Are you aware that C and C++ are different languages, and so by default compilers may treat files with these conventional filenames differently, e.g., by compiling ".c" files as if they were written in C and ".cpp" files as if they were written in C++?

    A few other comments on your code:
    • Even though you say that "none are included more than once", use header inclusion guards. From what I see of the error messages, you are mistaken in your claim, so indeed the header inclusion guards should help.
    • You should never include ".c" or ".cpp" files in your code. Granted, these "file extensions" are primarily naming conventions, but because of how they might be treated by default, it is a recipe for confusion to include files named with conventions for source files. If you intend a file to be included, name it with a convention for headers (e.g., with a ".h" extension).
    • In Bus_Timing.h, you define the non-static file scope variable named microseconds. Do not do this. If it is meant to be a global variable, declare it extern and define it in a source file.
    • Actually, I would question why you need two global variables (you have another one named result) in the first place. Avoid global variables, and if you really do need them, make them especially descriptive (which is also true of global constants). "microseconds" and "result" may be good names for variables in a local context, but in a global context they are poorly named: what on earth is "microseconds" about, and for what purpose do you need this "result"? The names should reflect these.
    • Instead of (or perhaps in addition to) commenting on magic numbers, use named constants. This is regarding your function named rX.


    EDIT
    Reading your post again:
    Quote Originally Posted by mr_darker
    The way I have the code set up is each file has its own .c and .h file, declarations and constant definitions in the header files, then function definitions in the c file. The files form a long chain so that all files get included, and none are included more than once. In ascending order, each file includes the one before it.

    1. Bus_Timing.h (uses "extern" to reference a variable from file 3)
    2. Bus_Timing.c
    3. Communication_Format.h
    4. Communication_Format.c (uses functions & variables from file 1&2, naturally called/used because those files are upstream in the include chain)
    5. Cashless.h
    6. Cashless.c (uses functions & variables from files 1-4, they're included upstream)
    7. Main.c
    You have a wrong approach. The files should not "form a long chain so that all files get included", unless you're taking the approach of having only one source file and everything else is a header file, but you're not doing that because you say that "declarations and constant definitions in the header files, then function definitions in the c file", and yet you are including ".c" files in other files, so they are actually "header files".

    Again, do not include ".c" files in other files. It also looks like the #include "Bus_Timing.cpp" line is a typo error: you probably meant to write #include "Bus_Timing.c", which is wrong for the reasons I outlined. As I mentioned earlier, this also explains why you have these multiple definition errors: you're probably compiling all the ".c" files, which means that your nice separation of "declarations and constant definitions in the header files, then function definitions in the c file" becomes completely useless because you're compiling the function definitions and such over and over again.

    No, do not include ".c" files in other files. Only include headers, i.e., ".h" files. Compile your ".c" files and link the resulting object files. Use header inclusion guards.
    Last edited by laserlight; 01-31-2019 at 12:41 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Nov 2018
    Posts
    5

    Thumbs up update

    There is no such language called C/C++.
    I know, I'm just using Arduino IDE for now, and the libraries used (for hardware specific code) is written in C++. Other than Arduino-Hardware specific code, I want the code to remain purely C, so that when I switch hardware from a development board to something like a linux module, it's an easy transition where I don't have to re-write C++ code in its C equivalent. That'd be why I post in a C forum, I'm looking for answers that are in C. It's my understanding C++ is partially if not mostly backwards compatible with C++, which is why I kinda referred to them interchangably. All ".c" files are only ".cpp" files when used with arduino.

    use header inclusion guards
    Done. I'm now running into a problem with multiple definitions though. (New/edited code below)

    never include ".c" or ".cpp" files in your code
    The files should not "form a long chain so that all files get included", unless you're taking the approach of having only one source file and everything else is a header file
    Fixed & Fixed (It's nolonger a single "chain")

    If it is meant to be a global variable, declare it extern
    Fixed.

    Avoid global variables
    Fixed, although replaced with a global function. Why are global variables bad though?

    In Bus_Timing.h, you define the non-static file scope variable named microseconds
    I'd actually like this to be static, with the belief that this makes its value non-volatile between function calls. Is this true? And if so, I also read something that had a list of rules that "only experts" should break if they have good reason. Defining a static variable in a header file was a rule it said not to do. I looked up what happens if you do and the answer seemed to be that a copy of it is made for each file that includes it. This would be ideal as I'd like multiple devices to use this timer at the same time, and have their timing values kept separate. Would this be a good reason to use static in the header file?

    use named constants
    Fixed, I made header file with a big long list of preprocessor definitions. The idea is that using preprocessors, the definitions aren't kept in the program's memory. In Cashless.h there are 300+ lines of structure definitions used to store constants in a uniform & predictable naming system that makes it easy to use any of the 100+ constants without having to look up the name for it. There's up to 2000 lines of code using these constants, so before I go switching all of that over to preprocessor definitions (more for perfection rather than saving maybe 0.5kB of space), I'd like to confirm this is a good idea.

    Reading your post again
    Thanks! I appreciate your time!


    Side question. While reading the Linux Kernel coding style, it says that using typedef on a structure or a pointer, "is a mistake". Do you know why this is? Specifically the structure as I've been trying to do that.

    This is what I'm currently stuck on, multiple definitions. It may be easiest to focus on how "time_elapsed" which is a variable that's first defined in "Bus_Timing.cpp", gets an erronous 2nd definition in "Communication_Format.cpp". There is no reference to this variable in any files other than the Bus_Timing files, so it's involved with the way the files are included/linked. A fix to me appears to be moving all non-extern definitions out of the header files and into their source files, but that seems like it'd be wrong or bad practice, as I've always thought you're supposed to keep definitions in the header files. It seems to work though so is it bad to move the declaration and definition of a local variable into the source file, and not have it in the header file? Here's what I got now for code. The errors seem about the same, except it's now talking about object files, and I'm using arduino so don't know much about that. I'm aware of what makefiles and all that do, but have never used the concepts personally, and don't know how it works or how to use make/object/linker files. So keep that in mind, I'm just using arduino for now, makefile stuff, if not 100% necessary yet, is a lesson a bit further down the road for me.

    Bus_Timing.cpp
    Code:
    #ifndef BUS_TIMING
    #define BUS_TIMING
    
    #include "Bus_Timing.h"
    
    unsigned long time_elapsed;
    
    uint8_t chronoLogic(uint8_t command) {     /*Think of it like a stopwatch, but it returns a timing status rather than a time*/
      if (command == TIMER_RESET) {
          microseconds = micros();              //Save time of this command's execution.
          return OK;
      }
      else if (command == TIMER_UPDATE) {
        time_elapsed = micros() - microseconds;
        if (time_elapsed > 200000) return T_SETUP;  /*Bus Reset is over*/
        if (time_elapsed > 100000) return T_BREAK;  /*Bus Reset Timer*/
        if (time_elapsed > 5000) return T_RESPONSE; /*Adjusts these  values according to your hardware's inaccuracies.  Verify with a Logic  Analyzer*/
        if (time_elapsed > 1000) return T_INTER_BYTE;
      }
    }
    
    #endif  /*BUS_TIMING*/


    Bus_Timing.h
    Code:
    #ifndef BUS_TIMING_HEADER
    #define BUS_TIMING_HEADER
    
    #include <Arduino.h>
    #include "PreProcessors.h"
    
    static unsigned long microseconds;
    extern uint8_t chronoLogic(uint8_t command);    /*Stopwatch that updates all timing flags*/
    extern unsigned long micros();
    
    #endif  /*BUS_TIMING_HEADER*/


    Communication_Format.cpp
    Code:
    #ifndef COMMUNICATION_FORMAT
    #define COMMUNICATION_FORMAT
    
    #include "Arduino.h"
    #include "Communication_Format.h"
    
    /*  rX() when called directly after indication that data has been received,
     *    retrieves the whole block of data, even if only one byte has been received so far.
     *    It returns a value that indicates whether or not the data is for the device that
     *    called the function.  If the parity byte doesn't match, or the mode bits are in the
     *    wrong place, it returns a value to indicate this error instead.  The parity is only
     *    checked at the beginning and end of the block, though it is very unlikely one ends
     *    up in the middle of a block without triggering a DISORDER return value.
     *  tX() when called directly after loading the block and setting the pointer, sends
     *    the block and automatically adjusts the mode bits according to what device has called
     *    the function.  Upon sending the last byte of the block, tX() calls rX() and listens
     *    for a response.  Return values from rX() are passed through tX().
     */
    
    
    
    uint8_t rX(uint8_t address) {                                         //1=ACK, 2=NAK, 3=RET, 4=NO-RESPONSE, 5=Ignored, 6=OK!, 7=Disordered,  8=Distorted
    /*Receive the first Byte*/
      clearBlock();
      while (!Serial1.available()) {
        if (chronoLogic(TIMER_UPDATE) == T_RESPONSE)
        return NO_RESPONSE;
      }
      block[counter].whole = Serial1.read();
    /*Check if it is one of the three response codes*/
      if (block[0].part.data == ACK)
        return ACK;
      if (block[0].part.data == RET)
        return RET;
      if (block[0].part.data == NAK)           
        return NAK;
    /*Check the mode bit*/
      if (block[0].part.mode == 0x01) {
        if (address != VMC_ADDRESS) {
          if ((block[0].part.data & 0xf8) != address) /*Check if it is addressed to the calling peripheral*/
            return IGNORED;                           /*Data is  intentionally not automatically cleared, as some calling devices such as  a sniffer, may want to read anyways*/
        }
        else if (address == VMC_ADDRESS)
          return DISORDERED;                          /*The VMC should never get a block where the first byte has the mode bit set*/
      }
      else if (block[0].part.mode == 0x00) {
        if (address != VMC_ADDRESS) {
          return DISORDERED;                          /*A Peripheral should  never get a block where the first byte isn't an address byte*/
        }
      }
    /*Retrieve the rest of the block*/
      while (counter != 36)  {
        while (!Serial1.available()) {
          if (chronoLogic(TIMER_UPDATE) == T_INTER_BYTE) {  /*1mS of silence marks the end of a conversation*/
            if (checksum != block[counter].part.data)
              return DISTORTED;
            if (address == VMC_ADDRESS) {
              if (block[counter].part.mode != 0x01)         /*The last byte sent to the VMC always has the mode bit set*/
                return DISORDERED;
              return RECEIVED;
            }
            if (address != VMC_ADDRESS) {
              if (block[counter].part.mode != 0x00)         /*The mode bit is only set on the first byte sent to a peripheral*/
                return DISORDERED;
              return RECEIVED;
            }
          }
        }
        chronoLogic(TIMER_RESET);
        checksum += block[counter].part.data;
        counter++;
        block[counter].whole = Serial1.read();
      }
    }
    
    void clearBlock() {
        counter = 0;
        while (counter != 36) {
            block[counter].whole = (0x0000);
            counter++;
        }
        counter = 0;
        checksum = 0x00;
    }
    
    uint8_t tX(unsigned int pointer, uint8_t address) {
    /*Send All Data*/
      counter = 0;
      checksum = 0x00;
      if (address == VMC_ADDRESS) {
        if (pointer != 0)
          block[counter].whole = (block[counter].whole | 0x100);  /*VMC  always sets the mode bit on the first byte, unless it's ACK, NAK, or  RET*/
      }
      while (pointer != counter) {
        Serial1.write(block[counter].whole);
        checksum += block[counter].part.data;
        counter++;
        Serial1.flush();
      }
    /*Send the last byte*/
      if (address != VMC_ADDRESS)
        block[counter].whole = (block[counter].whole | 0x100 | checksum);   /*Peripheral always marks the end of a block with a mode bit*/
      if (address == VMC_ADDRESS) {
        if (counter != 0)                                                   /*ACK, NAK, and RET don't use a check byte if from the VMC*/
          block[counter].whole = (block[counter].whole | checksum);
      }
      Serial1.write(block[counter].whole);
    /*Prepare for a response*/
      chronoLogic(TIMER_RESET);
      return rX(address);
    }
    
    #endif


    Communication_Format.h
    Code:
    #ifndef COMMUNICATION_FORMAT_HEADER
    #define COMMUNICATION_FORMAT_HEADER
    
    #include "PreProcessors.h"
    #include "Bus_Timing.h"
    
    extern uint8_t chronoLogic(uint8_t command);
    
    unsigned int counter;
    uint8_t checksum, pointer;
    uint8_t rX(uint8_t address);                        //Recieves a byte,  judges if it needs to recieve the rest of the data, and ends at  appropriate time.  Returns status codes.
    void clearBlock();                                  //Clear all communications related variables.
    uint8_t tX(unsigned int pointer, uint8_t address);  //Send the data in  the block, calculate and insert check byte into location of pointer,  uness pointer is zero.  ALways sets mode bit on last byte.
    
    extern union nineBit {             //Union allows writing mode and data  bits simultaneously as a short, therefore not overwriting one another.   Bit format: dddddddd_______m
      struct {                        //Structure allows accessing 8 data  bits and 1 mode bit separately, as well as setting the parent union's  size.
        uint8_t data : 8;             //8 data bits dddddddd most significant byte when written to as a short..
        uint8_t mode : 1;             //1 mode bit  _______m least significant byte when written to as a short.
        } part;                       //Named for readability & organization of code.
        unsigned short whole;
    } block[35];                      /*Array of 9 bit data, MDB has a maximum block size of 36 "bytes"*/
    
    #endif

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 5
    Last Post: 10-07-2017, 10:37 PM
  2. [Help]Correct or Wrong?Loops
    By exjames1991 in forum C Programming
    Replies: 11
    Last Post: 10-19-2009, 02:11 AM
  3. Replies: 1
    Last Post: 05-01-2003, 02:52 PM
  4. What is wrong here, just won't print the correct numbers
    By Unregistered in forum C++ Programming
    Replies: 16
    Last Post: 05-10-2002, 03:31 PM
  5. What is wrong with my project(multiple files)
    By Ruflano in forum C++ Programming
    Replies: 2
    Last Post: 03-30-2002, 09:47 PM

Tags for this Thread