Jump to content
 

Orunmila

Member
  • Content Count

    165
  • Joined

  • Last visited

  • Days Won

    16

Orunmila last won the day on April 24

Orunmila had the most liked content!

Community Reputation

36 Excellent

About Orunmila

  • Rank
    Teacher

Recent Profile Visitors

250 profile views
  1. I decided to write this up as bootloaders have pretty much become ubiquitous for 32-bit projects, yet I was unable to find any good information on the web about how to use linker scripts with XC32 and MPLAB-X. When you need to control where the linker will place what part of your code, you need to create a linker script which will instruct the linker where to place each section of the program. Before we get started you should download the MPLAB XC32 C/C++ Linker and Utilities Users Guide. There is also some useful information in the MPLAB XC32 C/C++ Compiler User’s Guide for PIC32M MCUs, the appropriate version for your compiler should be in the XC32 installation folder under "docs". This business of linker scripts is quite different from processor to processor. I have recently been working quite a bit with the PIC32MZ2048EFM100, so I will target this to this device using the latest XC32 V2.15. This post will focus on what you need to to do to get the tools to use your linker script. Since XC32 is basically a variant of the GNU C compiler you can find a lot of information on the web about how to write linker scripts, here is a couple. http://www.scoberlin.de/content/media/http/informatik/gcc_docs/ld_3.html https://sourceware.org/binutils/docs-2.17/ld/Scripts.html#Scripts Adding a linker script The default linker script for the PIC32MZ2048EFM100 can be found in the compiler folder at /xc32/v2.15/pic32mx/lib/proc/32MZ2048EFM100/p32MZ2048EFM100.ld. If you need a starting point that would be a good place. For MPLAB-X and XC32 the extention of the linker script does not have any meaning. The linker script itself is not compiled, it is passed into the linker at the final step of building your program. The command line should look something like this for a simple program: "/Applications/microchip/xc32/v2.15/bin/xc32-gcc" -mprocessor=32MZ2048EFM100 -o dist/default/production/mine.X.production.elf build/default/production/main.o -DXPRJ_default=default -legacy-libc -Wl,--defsym=__MPLAB_BUILD=1,--script="myscript.ld",--no-code-in-dinit,--no-dinit-in-serial-mem,-Map="dist/default/production/mine.X.production.map",--memorysummary,dist/default/production/memoryfile.xml" The linker script should be listed on the command line as "--script="name" When you create a new project MPLAB will create a couple of "Logical Fodlers" for you. These folders are not actual folders on your file system, but files in these are sometimes treated differently, and Linker Files is a particular case of this. My best advice is not to ever rename of in any other way mess with these folders created for you by MPLAB. If you did edit configurations.xml or renamed any of these I suggest you just create new project file as there are so many ways this could go wrong fixing it will probably take you longer than just re-creating it. I have seen cases where it all looks 100% but the IDE simply does not use the linker script, just ignoring it. The normal way to add files to a MPLAB-X project is to right-click on the Logical folder you wanted the file to appear in and select which kind of file under the "New" menu. In this menu files that you use often are shown as a shortcut, to see the entire list of possible files you need to select "Other..." at the bottom of the list. Unfortunatley Microchip has not placed "Linker Script" in this list, so there is no way to discover using the IDE how to add a linker script. When it all goes according to plan (the happy path) you can simply right-click on "Linker Files" and add your script. This is also what the manual says to do of course. When you have added the file it should look like this (pay careful attention to the icon of the linker script file, it should NOT have a source code icon. It should just be a white block like this, and if this is the case the program should compile just fine using the linker script, you can confirm that the script is being passed in by inspecting the linker command line. Adding a linker script - Problems - when it all goes wrong! I noticed in the IDE that the icon for the script was actually that of a .C source file. When this happens something has gone very wrong, and the compiler will attempt to compiler your linker script as a C source file. You will end up getting an error similar to this, stating that there is "No rule to make target": CLEAN SUCCESSFUL (total time: 51ms) make -f nbproject/Makefile-default.mk SUBPROJECTS= .build-conf make[2]: *** No rule to make target 'build/default/production/newfile.o', needed by 'dist/default/production/aaa.X.production.hex'. Stop. make[1]: Entering directory '/Users/cobusve/MPLABXProjects/aaa.X' make[2]: *** Waiting for unfinished jobs.... make -f nbproject/Makefile-default.mk dist/default/production/aaa.X.production.hex make[2]: Entering directory '/Users/cobusve/MPLABXProjects/aaa.X' make[1]: *** [.build-conf] Error 2 "/Applications/microchip/xc32/v2.15/bin/xc32-gcc" -g -x c -c -mprocessor=32MZ2048EFM100 -MMD -MF build/default/production/main.o.d -o build/default/production/main.o main.c -DXPRJ_default=default -legacy-libc make: *** [.build-impl] Error 2 make[2]: Leaving directory '/Users/cobusve/MPLABXProjects/aaa.X' nbproject/Makefile-default.mk:90: recipe for target '.build-conf' failed make[1]: Leaving directory '/Users/cobusve/MPLABXProjects/aaa.X' nbproject/Makefile-impl.mk:39: recipe for target '.build-impl' failed BUILD FAILED (exit value 2, total time: 314ms) I tried jumping through every hoop here, even did the hokey pokey but nothing would work to get the IDE to accept my linker script! I even posted a question on the forum here and got no help. At first I thought I would be clever and remove the script I just added, and just re-add it to the project, but no luck there. So now I was following the instructions exactly, my project was building without the script, I right-clicked on "Linker Files" selected "Add Existing Item" and then selected my script and once again it showed up as a source file and caused the project build to fail by trying to compile this as C code :( Next attempt was to remove the file, then close the IDE. Open the IDE, build the project and then after this add the existing file. Nope, still does not work :( I know MPLAB-X from time to time will cache information and you can get rid of this by deleting everything from the project except for your source files, Makefile and configurations.xml and project.xml. I went ahead and deleted all these files, restarted the IDE, added the file again - nope - still does not work. So much for RTFM! Eventually our of desperation I tried to rename the file before adding it back in. Even this did not work until I got lucky - I changed the extention of the file to gld (a commonly used extension for gnu linker files), and tried to re-add the file, and this eventually worked ! If you are having a hard time getting MPLAB-X to add your linker script, do not dispair. You are probably not doing anywthing wrong! The right way to add a linker script to your project is indeed to just add it to "Linker Files" as they say, sometimes you just get unlucky due to some exotic bugs in the IDE. Just remove the file from your project, change the extention to something else (it seems like you can choose anything as long as it is different) and add the file back in and it should work. If not come back here and let me know and we can figure it out together :)
  2. Ok great news I figured out what was going wrong! I was working with an old project file. The project was not using a linker script before. It turns out that MPLAB is doing all kinds of strange things in the background to figure out that it has to treat files in the Logical Folder called by "name=LinkerScript" and "displayname=Linker Files" as linker scripts instead of C files, and once it has gotten itself confused about this there is no going back without recreating the entire project file. Now since ours contained hundreds of source files we tried to avoid this but alas, turns out there is not really another way :( There is an example here https://www.microchip.com/forums/m651658.aspx on how to add the item back in. This seems to only work if you add it in AND rename the item BEFORE opening the project in MPLAB-X, if you open the project first you will be out of luck. For now you will have to do a lot of trial and error, or just re-create the project if you need to add a linker script, and even then good luck, the IDE can muck it up quite easily! I think I see a blog post coming on how to get a linker script into your MPLAB-X project. It seems to be harder than it should be! Edit: I have written up my experience in a blog entry here:
  3. There is one problem we keep seeing bending people's minds over and over again and that is race conditions under concurrency. Race conditions are hard to find because small changes in the code which subtly modifies the timing can make the bug disappear entirely. Adding any debugging code such as a printf can cause the bug to mysteriously disappear. We spoke about this kind of "Heisenbug" before in the lore blog. In order for there to be a concurrency bug you have to either have multiple threads of execution (as you would have when using an RTOS) or, like we see more often in embedded systems, you are using interrupts. In this context an interrupt behaves the same as a parallel execution thread and we we have to take special care whenever both contexts of execution will be accesing the same variable. Usually this cannot be avoided, we e.g. receive data in the ISR and want to process it in the main loop. Whenever we have this kind of interaction where we need to access shared data which will be accessed by multiple contexts we need to serialize access to the data to ensure that the two contexts will safely navigate this. You will see that a good design will minimise the amount of data that will be shared between the two contexts and carefully manage this interaction. Simple race condition example The idea here is simply that we receive bytes in the ISR and process them in the main loop. When we receive a byte we increase a counter and when we process one we reduce the counter. When the counter is at 0 this means that we have no bytes left to process. uint8_t bytesInBuffer = 0; void interrupt serialPortInterrupt(void) { enqueueByte(RCREG); // Add received byte to buffer bytesInBuffer++; } void main(void) { while(1) { if ( bytesInBuffer > 0 ) { uint8_t newByte = dequeueByte(); bytesInBuffer--; processByte(newByte); } } } The problem is that both the interrupt and the mainline code access the same memory here and these interactions last for multiple instruction cycles. When an operation completes in a single cycle we call it "atomic" which means that it is not interruptable. There are a numbe of ways that instructions which seem to be atomic in C can take multiple machine cycles to complete. Some examples: Mathematical operations - these often use an accumulator or work register Assignment. If I have e.g. a 32bit variable on an 8-bit processor it can take 8 cycles or more to do x = y. Most pointer operations (indirect access). [This one can be particularly nasty btw.] Arrays [yes if you do i[12] that actually involves a multiply!] In fact this happens at two places here, the counter as well as the queue containing they bytes, but we will focus only on the first case for now, the counter "bytesInBuffer". Consider what would happen if the code "bytesInBuffer++" compiled to something like this: MOVFW bytesInBuffer ADDLW 1 MOVWF bytesInBuffer Similarly the code to reduce the variable could look like this: MOVFW bytesInBuffer SUBLW 1 MOVWF bytesInBuffer The race happens once the main execution thread is trying the decrement the variable. Once the value has been copied to the work register the race is on. If the mainline code can complete the calculation before an interrupt happens everything will work fine, but this will take 2 instructions. If an interrupt happens after the first instruction or after the 2nd instruction the data will be corrupted. Lets look at the case where there is 1 byte in the buffer and the main code starts processing this byte, but before the processing is complete another byte arrives. Logically we would expect the counter to be incremented by one in the interrupt and decremented by 1 in the mainline code, so the end result should be bytesInBuffer = 1, and the newly received byte should be ready for processing. The execution will proceed something like this - we will simplify to ignore clearing and checking of interrupt flags etc. (W is the work register, I_W is the interrupt context W register): // State before : bytesInBuffer = 1. Mainline is reducing, next byte arrives during operation Mainline Interrupt // state ... [bytesInBuffer = 1] MOVFW bytesInBuffer [bytesInBuffer = 1, W=1] SUBLW 1 [bytesInBuffer = 1, W=0] MOVFW bytesInBuffer [bytesInBuffer = 1, W=0,I_W=1] ADDLW 1 [bytesInBuffer = 1, W=0,I_W=2] MOVWF bytesInBuffer [bytesInBuffer = 2, W=0,I_W=2] MOVWF bytesInBuffer [bytesInBuffer = 0, W=0,I_W=2] ... [bytesInBuffer = 0] As you can see instead of ending up with bytesInBuffer = 0 instead of 1 and the newly received byte is never processed. This typically leads to a bug report saying that the serial port is either losing bytes randomly or double-receiving bytes e.g. UART (or code) having different behaviors for different baudrates - PIC32MX - https://www.microchip.com/forums/m1097686.aspx#1097742 Deadlock Empire When I get a new junior programmer in my team I always make sure they understand this concept really well by laying down a challenge. They have to defeat "The Deadlock Empire" and bring me proof that they have won the BossFight at the end. Deadlock Empire is a fun website which uses a series of examples to show just how hard proper serialization can be, and how even when you try to serialze access using mutexes and/or semaphores you can still end up with problems. You can try it out for youerself - the link is https://deadlockempire.github.io Even if you are a master of concurrency I bet you will still learn something new in the process! Serialization When we have to share data between 2 or more contexts it is critical that we properly serialize access to it. By this we mean that the one context should get to complete it's operation on the data before the 2nd context can access it. We want to ensure that access to the variables happen in series and not in parallel to avoid all of these problems. The simplest way to do this (and also the least desireably) is to simply disable interrupts while accessing the variable in the mainline code. That means that the mainline code will never be interrupted in the middle of an operation on the shared date and so we will be safe. Something like this: // State before : bytesInBuffer = 1. Mainline is reducing, next byte arrives during operation Mainline Interrupt // state ... [bytesInBuffer = 1] BCF GIE /* disable interrupts */ [bytesInBuffer = 1] MOVFW bytesInBuffer [bytesInBuffer = 1, W=1] SUBLW 1 [bytesInBuffer = 1, W=0] MOVWF bytesInBuffer [bytesInBuffer = 0, W=0] BSF GIE /* enable interrupts */ [bytesInBuffer = 0, W=0] MOVFW bytesInBuffer [bytesInBuffer = 0, W=0, I_W=0] ADDLW 1 [bytesInBuffer = 0, W=0, I_W=1] MOVWF bytesInBuffer [bytesInBuffer = 1, W=0, I_W=1] ... [bytesInBuffer = 1] And the newly received byte is ready to process and our counter is correct. I am not going to go into fancy serialization mechanisms such as mutexes and semaphores here. If you are using a RTOS or are working on a fancy advanced processor then please do read up on the abilities of the processor and the mecahnisms provided by the OS to make operations atomic, critical sections, semaphores, mutexes and concurrent access in general. Concurrency Checklist We always want to explicitly go over a checklist when we are programming in any concurrent environment to make sure that we are guarding all concurrent access of shared data. My personal flow goes something like this: Do you ever have more than one context of execution? Interrupts Threads Peripherals changing data (We problems with reading/writing 16-bit timers on 8-bit PIC's ALL OF THE TIME!) List out all data that is accessed in more than one context. Look deep, sometimes it is subtle e.g. accessing an array index may be calling multiply internally. Don't miss registers, especially 16-bit values like Timers or ADC results Is it possible to reduce the amount of data that is shared? Take a look at the ASM generated by the compiler to make 100% sure you understand what is happening with your data. Operations that look atomic in C are often not in machine instructions. Explicitly design the mechanism for serializing between the two contexts and make sure it is safe under all conditions. Serialization almost always causes one thread to be blocked, this will slow down either processing speed by blocking the other thread or increase latency and jitter in the case of interrupts We only looked into the counter variable above. Of course the queue holding the data is also shared, and in my experience I see much more issues with queues being corrupted due to concurrent access than I see with simple counters, so do be careful with all shared data. One last example I mentioned 16-bit timers a couple of times, I have to show my favourite example of this, which happens when people write to the timer register without stopping the timer. // Innocent enough looking code to update Timer1 register void updateTimer1(uint16_t value) { TMR1 = value; } // This is more common, same problem void updateTimer1_v2(uint16_t value) { TMR1L = value >> 8; TMR1H = value & 0xFF; } With the code above the compiler is generally smart enough not to do actual right shifts or masking, realizing that you are working with the lower and upper byte and this code compiles in both cases to something looking like this: MOVFW value+1 // High byte MOVWF TMR1H MOVFW value // Low byte MOVWF TMR1L And people always forget that when the timer is still running this can have disastrous results as follows when the timer increments in the middle of this all like this: // We called updateTimer1(0xFFF0) - expecting a period of 16 cycles to the next overflow // We show the register value to the right for just one typical error case // The timer ticks on each instruction ... [TMR1 = 0x00FD] MOVFW value+1 [TMR1 = 0x00FE] MOVWF TMR1H [TMR1 = 0xFFFF] MOVFW value [TMR1 = 0x0000] MOVWF TMR1L [TMR1 = 0x00F0] ... [TMR1 = 0x00F1] ... [TMR1 = 0x00F2] This case is always hard to debug because you can only catch it failing when the update is overlapping with the low byte overflowing into the high byte, which happens only 1 in 256 timer cycles, and since the update takes 2 cycles we have only a 0.7% chance of catching it in the act. As you can see, depending on whether you clear the interrupt flag after this operation or not you can end up with either getting an immediate interrupt due to the overflow in the middle of the operation, or a period which vastly exceeds what you are expecting (by 65280 cycles!). Of course if the low byte did not overflow in the middle of this the high byte is unaffected and we get exactly the expected behavior. When this happens we always see issues reported on the forums that sound something like "My timer is not working correctly. Every once in a while I get a really short or really long period" When I see these issues posted in future I will try to remember to come update this page with links just for fun 🙂
  4. Arghhh, why! I will see if I can beg it to listen then...
  5. I am trying to use a linker script with MPLAB-X for my PIC32 project but for some reason the script is not being passed to the linker at all. I expected that all I had to do was add the .ld file to my project, typically by placing it in the "Linker Files" virtual folder in MPLAB-X in my project. I did this and the linker script is being ignored by the linker. This is one of those $100 questions (if you know the story of the mechanic asking $100 for knowing where to hit ...). So my question is how do I get MPLAB-X to use my linker script which I have added to the PIC32 project?
  6. Oh by the way I figured this one out, I am surprized nobody jumped on this easy question! The syntax for hexmate in this case was simply hexmate <file1> <file2> -o <outputname> All I had to do was build the project from command line, pass to hexmate the resulting hex file as well as the bootloader hex file and it spits out the combination of the 2. Hexmate is such a simple little program I felt like just writing a script to do what it is doing, but with the continuation addresses in .hex files it turns out using this versatile tool is by far the best solution!
  7. Saw this question today on the MCHP forum so I thought I would post a short example. My first advice to anybody who has this kind of problem is to generate the code with MCC and study what it is doing in terms of order of initialization and also what it is setting in the configuration bits for the device. Even if you are planning on using ASM, do that first with your settings so you can have a working example to start with! Here is a minimal example on the PIC16F18344 for TMR1 #pragma config WDTE = OFF // Watchdog Timer Enable bits (WDT disabled; SWDTEN is ignored) #include <xc.h> void main(void) { INTCONbits.PEIE = 1; // Enable peripheral interrupts TMR1IE = 1; // Enable TMR1 peripheral interrupt T1CON = 0x01; // Set the timer to prescaler 1:1 (fastest), clock source instruction clock // and pick timer2 clock in as source (not Secondary Oscillator) // we also select synchronization, no effect as we run of FOSC here. TMR1H = 0x00; TMR1L = 0x00; INTCONbits.GIE = 1; // Enable GIE last, or interrupts can happen while we are setting up! while(1); // Wait forever for interrupts return; } void __interrupt() myISR(void) { NOP(); TMR1IF = 0; // Remember to clear the IF here or we will just end up in the ISR all of the time! }
  8. Awesome! What do I have to do to get that to do printf("Hello, world") ?
  9. A colleague of mine recommended this little book to me sometime last year. I have been referring to it so often now that I think we should add this to our reading list for embedded software engineers. The book is called "Don't make me think", by Steve Krug.The one I linked below is the "Revisited" version, which is the updated version. This book explains the essense of good user interface design, but why would I recommend this to embedded software engineers? After all embedded devices seldom have rich graphical GUI's and this book seems to be about building websites? It turns out that all the principles that makes a website easy to read, that makes for an awesome website in other words, apply almost verbatim to writing readable/maintainable code! You see code is written for humans to read and maintain, not for machines (machines prefer to read assembly or machine code in binary after all!). The principles explained in this book, when applied to your software will make it a pleasure to read, and effortless to maintain, because it will clearly communicate it's message without the unnecessary clutter and noise that we usually find in source code. You will learn that people who are maintaining and extending your code will not be reasoning as much as they will be satisficing (yes that is a real word !). This forms the basis of what Bob Martin calls "Viscosity" in your code. (read about it in his excellent paper entitled Design Principles and Design Patterns. The idea of Viscosity is that developers will satisfice when maintaining or extending the code, which results in the easiest way to do things being followed most often, so if the easiest thing is the correct thing the code will not rot over time, on the other hand if doing the "right" thing is hard people will bypass the design with ugly hacks and the code will become a tangled mess fairly quickly. But I digress, this book will help you understand the underlying reasons for this and a host of other problems. This also made me think of some excellent videos I keep on sending to people, this excellent talk by Chandler Carruth which explains that, just like Krug explains in this little book, programmers do not actually read code, they scan it, which is why consistency of form is so important (coding standards). Also this great talk by Kevlin Henney which explains concepts like signal to noise ratio and other details about style in your code (including how to write code with formatting which is refactoring immune - hint you should not be using tabs - because of course only a moron would use tabs) Remember, your code is the user interface to your program for maintainers of the code who it was written for in the first place. Let's make sure they understand what the hell it is you were doing before they break your code! For the lazy - here is an Amazon share link to the book, click it, buy it right now! https://amzn.to/2ZEoO4O
  10. I have a MPLAB-X project which uses a loadable (it is combining my program with a bootloader which is in another project). I need to compile this project from the command line for CI automation. For the entire build process every command executed is nicely printed in the build window, but for the loadable it claims to be using Hexmate, but the command line to execute it is not shown at all. Can anyone help me with the syntax using Hexmate to get the same behavior as adding the Loadable from MPLAB-X?
  11. Beginners always have a hard time understanding when to use a header file and what goes in the .c and what goes in the .h. I think the root cause of the confusion is really a lack of information about how the compilation process of a C program actually works. C education tends to focus on the semantics of loops and pointers way too quickly and completely skips over linkage and how C code is compiled, so today I want to shine a light on this a bit. C code is compiled as a number of "translation units" (I like to think of them as modules) which are in the end linked together to form a complete program. In casual usage a "translation unit" is often referred to as a "Compilation Unit". The process of compilation is quite nicely described in the XC8 Users Guide in section 4.3 so we will look at some of the diagrams from that section shortly. Compilation Steps Before we get into a bit more detail I want to step back slightly and quite the C99 standard on the basic idea here. Section 5.1.1.1 of the C99 standard refers to this process as follows: A C (or C++ for that matter) compiler will process your code as shown to the right (pictures from XC8 User's Guide). The C files are compiled completely independenty of each other into a set of object files. After this step is completed the object files are linked together, like a set of Lego blocks, into a final program during the second stage. It is possible for 2 entirely different programs to share some of the same obj files this way. Re-usable object files that perform common functions are often bundled together into an archive of object files referred to as a "Library" by the standard, and is often "zipped" together into a single file called .lib or .a This sequence is pretty much standard for all C compilers as depicted on the right. Looking at XC8 in particular there is a little more details that only applies to this compiler. The PIC16 also poses some challenges for compilers as the architecture has banked memory. This means that moving code around from one location to another may not only change the addreses of objects (which is quite standard) but it may also require some extra instructions (bank switch instructions) to be added depending on where the code is ultimately placed in memory. We will not get any deeper into the details here, but I want to point out the most important aspects. Some useful tips: Most compilers will have an option to emit and keep the output from the pre-processor so that you can look at it. When debugging your #define's and MACROs are getting you under this is an excellent debugging tool. With the latest version of XC8 the option to keep these files is "-save-temps" which can be passed to the linker as an additional argument. (They will end up in a folder called "build" and the .pre files in the diagram may have an extention ".i" depending on the processor you are on). During the linking step all the objects (translation units) which will be combined to create the final program will be linked together to produce an executable. This process will decide where each variable and function will be placed, and all symbolic references are replaced with actual addresses. This process is sometimes referred to as allocation, re-allocation or fix-up. At this step it is possible to supply most linkers with a linker file or "linker script" which will guide the linker about which memory locations you want it to use. Although the C standard does not specify the format of the object files, some common formats do exist. Most compilers used to use the COFF format (Common Object File Format) which typically produces files with the extension .o or .obj. Another popular format favored by many compilers today is the ELF (Executable and Linkable Format). The most important thing to take away from all that is that your C files the H files they includ will be combined into a single translation unit which will be processed by the compiler. The compiler literally just pastes you include file into the translation unit at the place you include it. There is no magic here. So why do I need an H file at all then? As noted in the standard different translation units communicate with each other through either calling functions with external linkage, or manipulating objects with external linkage. When 2 translation units communicate in this way it is very important that they both have the exact same definition for the objects they are exchanging. If we just had the definitions in C files without any headers then the definitions of everything that is shared would have to be very carefully re-typed in each file, and all of these copies would later have to be maintained. It really is that simple, the only reason we have header files is to save us from maintaining shared code in multiple places. As you should have noticed by now the descriptions of translation units sound very similar to "libraries" or "modules" of your program, and this is precisely what they are. Each translation unit is an independent module which may or may not be dependent on one or more other modules, and you can use these concepts to split your programs into more managable and re-usable modules. This is the divide and conquer strategy. In this scheme the sole purpose of header files is to be used by more than one translation unit. They represent a definition of the interface that 2 modules in your program can use to communicate with each other and saves you from typing it all multiple times. Let's look at a simple example of a how a header file named module.h may be processed when it is included into your C code. // This is module.h - the header file // This file defines the interface specification for this module. // It contains all definitions of functions and/or variables with external linkage // The purpose of this file is to provide other translation units with the names of the objects that this translation unit // provides, so that they can be used to communicate with this translation unit. // This declaration promises the compiler that somewhere there will be a function called "myFunction" which the linker will be able to resolve void myFunction(void); // This declaration promises the compiler that somewhere there will be a variable called "i" which the linker will be able to resolve extern int i; And it's corresponding C source file module.c // This is module.c - the C file for my module // This file contains the implemenation of my module // It is typical for a module to include it's own interface, this makes it easier to implement by ensuring the interface and implemenation are identical #include "module.h" // Declaring a variable like this will allocate storage for it. // In C Variables with global scope has external linkage by default (This is NOT true for C++ where this would have internal linkage) int i = 42; // Functions have external linkage by default, it is not necessary to say extern void myFunction as the "extern" is implied void myFunction(void) { ... // some code here } As described above the pre-processor will convert this into a file for the compiler to process which looks like this : // This is module.c - the C file for my module // This file contains the implemenation of my module // It is typical for a module to include it's of interface, this makes it easier to implement in many ways // This is module.h - the header file // This file defines the interface specification for this module. // It contains all definitions of functions and/or variables with external linkage // The purpose of this file is to provide other translation units with the names of the objects that this translation unit // provides, so that they can be used to communicate with this translation unit. // This declaration promises the compiler that somewhere there will be a function called "myFunction" which the linker will be able to resolve void myFunction(void); // This declaration promises the compiler that somewhere there will be a variable called "i" which the linker will be able to resolve extern int i; // Defining a variable like this will allocate storage for it. // In C Variables with file scope has external linkage by default (This is NOT true for C++ where this would have internal linkage) int i = 42; // Functions have external linkage by default, it is not necessary to say extern void myFunction as the "extern" is implied void myFunction(void) { // some code here } void main(void) { myFunction(void); } Now you will notice that with the inclusion of the header like this some things like the "int i" end up occuring in the file twice. This can be very confusing when we try and establish exactly which of these statements are just declarations of the names of variables and which ones actually allocate memory. If a symbol like "int i" is declared more than once in the file how do we ensure that memory is not allocated more than once, especially if "int i" occurs in the global file scope of more than one tranlsation unit! In order to make more sense of this we can go over how the compiler will process the combined "tranlation unit" from top to bottom for our simple example. When the compiler processes this file it first finds a declaration of a function without an implementation/definition. This tells the compiler to only declare this name in what is commonly referred to as it's "dictionary". Once the name is established it is possible for the implementation to safely refer to this name. Such a declaration of a function without an implementation is called a function prototype. The next code line contains a declaration of an integer called "i" with external linkage (we will get to linkage in the next section). This is a declaration as opposed to a definition, as it does not have any initializer (an assignment with an initial value). This declaration places "i" in the dictionary, but does not allocate storage for the variable. It also marks the object as having external linkage. When 2 compilation units declare the same object with external linkage the compiler will know that they are linked (refer to the same thing), and it will only allocate space for it once so that both translation units end up manipulating the same variable! Later on the compiler finds "int i = 42", this is a definition of the same symbol "i", this time it also supplies an initializer, which tells the compiler to set this variable to 42 before main is run. As this is a definition this is the statement that will cause memory to be allocated for the variable. If you try and have 2 definitions for the same object (even in 2 separate translation units) the compile will report an error which will alert you that the object was defined more than once. It will either say "duplicate symbol" or "multiple definition for object" or something along these lines (error messages are not specified by the standard so these messages are different on each compiler). Next we encounter the implementation/definition of the function myFunction. Lastly we encounter the implementation/definition of main, which is traditionally the entry point of the application. I encourage you to cut and paste that snippet above into an empty project and compile it to assure yourself that this works fine. After that I want you to paste these examples so we can better understand the mechanics here, and prove that I am not smoking something here! // Some test code to show why include files actually end up working int j; int j; int j = 1; void main(void) { // Empty main } You can compile this and note that there will be no error. (a project with this code is attached for your convenience). This is because int j; is what is called a "tentative definition". What this means is that we are stating that there is a definition for this variable in this translation unit. If the end of the tranlation unit is reached and no definition has been provided (there was no definition with an initializer) then the compiler must behave as if there was a definition with an initializer of "0" at the end of the translation unit. You can have as many tentative definitions as you want for the same object, even if they are in the same compilation unit, as long as their types are all the same. The third line is the only definition of "j" which also triggers the allocation of storage for the variable. If this line is removed storage will be allocated at link time as if there was a definition with initializer of 0 at the end of the file if no definitions can be found in any of the translation units being linked. Now change the code to look as follows: // Some test code to show why include files actually end up working int j; int j = 1; int j = 1; void main(void) { // Empty main } This will result in the following error message, since more than one initializer is provided we have multiple definitions for the object in the same translation unit, which is not allowable. This is because a declaration with an initializer is a definition for a variable and a definition will allocate storage for the variable. We can only allocate storage for a variable once. On CLang the error looks only slightly different: Now let's try something else. Change it to look like this. This time the variable is an auto variable, so it has internal linkage (this is also called a local variable). In this case we are not allowed to declare the same variable more than once because there is no good reason (like with header files) to do this and if this happens it would most likely be a mistake so the compiler will not allow it. // Some test code to show why include files actually end up working void main(void) { int j; int j; int j = 1; } The error produced looks as follows on XC8, and I will get this even if I have only 2 j's with no initializer: An important note here, auto variables (local variables) will NOT be automatically initialized to 0 like variables at file scope with external linkage will be. This means that if you do not supply any initializer the variable can and will likely have any random value. Linkage We spoke about linkage quite a bit, so lets also make this clear. The C Standard states in section 6.2.2: For any identifier with file scope linkage is automatically external. External linkage means that all variables with this identical name in all translation units will be linked together to point to the same object. Variables with file scope which has a "static" storage-class specifier have internal linkage. This means that all objects WITHIN THIS TRANSLATION UNIT with the same name will be linked to refer to the same object, but objects in other translation units with the same symbol name will NOT be linked to this one. Local variables (variables with block or function scope) automatically has no linkage, this means they will never be linked, which means having the same symbol twice will cause an error (as they cannot be linked). An example of this was shown in the last sample block of the previous section. Note that adding "extern" in front of a local variable will give it external linkage, which means that it will be linked to any global variables elsewhere in the program. I have made a little example project to play with which demonstrates this behavior (perhaps to your surprize!) If two tranlation units both contain definitions for the same symbol with external linkage the compiler will only define the object once and both definitions will be linked to the same definition. Since the definitions provide initial values this only works if both definitions are identical. There is a nice example, as always, in the C99 standard. int i1 = 1; // definition, external linkage static int i2 = 2; // definition, internal linkage extern int i3 = 3; // definition, external linkage int i4; // tentative definition, external linkage static int i5; // tentative definition, internal linkage int i1; // valid tentative definition, refers to previous int i2; // 6.2.2 renders undefined, linkage disagreement int i3; // valid tentative definition, refers to previous int i4; // valid tentative definition, refers to previous int i5; // 6.2.2 renders undefined, linkage disagreement extern int i1; // refers to previous, whose linkage is external extern int i2; // refers to previous, whose linkage is internal extern int i3; // refers to previous, whose linkage is external extern int i4; // refers to previous, whose linkage is external extern int i5; // refers to previous, whose linkage is internal // I had to add the missing one extern int i6; // Valid declaration only, whose linkage is external. No storage is allocated. Note that if we have a declaration only such as i6 above in a compilation unit the unit will compile without allocating any storage to the object. At link-time the linker will attempt to locate the definition of the object that allocates it's storate, if none is found in any other compilation unit for the program you will get an error, something like "reference to undefined symbol i6" Looking over those examples you will note that the storage-class specifier "extern" should not be confused with the linkage of the variable. It is very possible that a variable with external storage class can have internal linkage as indicated by the examples from the standard for "i2" and also for "i5". To see if you understand "extern" take a look at this example. What happens when you have one file (e.g. main.c) which defines a local variable as extern like this? [ First try to predict and then test it and see for yourself] #include <stdio.h> void testFunction(void); int i = 1; void main(void) { extern int i; testFunction(); printf("%d\r\n", i); } And in a different file (e.g. module.c) place the following: int i; void testFunction(void) { i = 5; } You should be able to tell if this will compile or not, and if not what error it would give, or will it compile just fine and print 5 ? Also try the following: What happens when you remove the "extern" storage-class specifier? What happens when instead you just emove the entire line "extern int i;" from function main ? (no externs in either file). Is that what you expected? What happens when you move the initializer from file-scope (just leaving the int i), to the function scope definition inside of main (when you have "extern int i = 1;" inside of the main function)? What happens when you add "extern" to the file scope declaration (replace "int i = 1;" with "extern int i = 1;") In Closing When you are breaking your C code into independent "Translation Units" or "Compilation Units", keep in mind that the entire header file is being pasted into your C file whenever you use #include. Keeping this in mind can help you resolve all kinds of mysterious bugs. Make sure you understand when variables have external storage and when they have external linkage. Remember that if 2 modules declare file scope variables with external linkage and the same name, they will end up being the same variable, so 2 libraries using "temp" is a bad idea, as these will end up overwriting each other and causing hard to locate bugs. Answers Cheat Sheet: The code listed compiles fine and prints "5". Additional exercises: When you remove the extern from the first line in function main it prints some random value (on my machine 287088694). This is because the local variable does not have linkage to the other variable called i. When you instead remove the entire first line from the main function it compiles and prints "5" like before. Having "extern int i = 1" inside of function main does not compile at all, complaining that "an extern variable cannot have an initializer" Having "extern int i = 1" in file scope is allowed though! This compiles just fine although CLang will give a warning for this one as long as only one definition exists. If you now also add an initializer to the file scope int i in module.c it will not compile any more.
  12. Ok I had a look, boy is that a lot of complex code to do such a simple thing! I have some questions for you. You say that with the modification you received all of the bytes correctly, but without the call to clear the overrun the reception stops after getting only a few bytes? This seems very strange because the overrun error should only have an effect if an actual overrun happened on the uart, and if that happened you would have lost at least one byte of data??? Like I said there is a lot of code and if you are running at a high baud rate you have to ensure that you have enough time in the worst case scenario to receive data at full speed without dropping any characters. This means your entire state machine must be able to go through a full cycle at least once per character, but likely twice as it may pass the check for the next byte just before it arrives, so you need to make sure you can cycle through everything in time. The easiest way to test this is toggle a pin only in your app state machine, you know it goes through 3 states every cycle deterministically, so just time with a scope how long this takes, take this time and multiply by 2 and that will be your maximum byte period. Now remember if you have the clock on 96MHz and you are trying to receive at 24Mbps that would mean 2.4 Mbyte/s (as uart has 1 start and 1 stop abd 8 data bits = 10total). That would give you only 96/24 = 40 instructions to cycle through the entire loop for each byte received. Looking at the code this does not seem to be possible, so you should run behind and loose data, getting overruns, which if you do not clear the error will disable the UART, as you are seeing. I would have said I am certain that this is what is happening, but you say you could receive 5kb of data without any error which seems almost unbelievable to me if you were running at such a high baud rate (of course you did not specify what speed you were running the test at?) You can usually buy some time and process data much faster by reading more than 1 byte at a time. If you make your buffer 100 bytes and every cycle read as many bytes as you can it should be possible to speed up the processing by more than 10x as the code now only has to do a tight loop to copy the data out (perhaps 5/6 instruction cycles) instead of an entire loop through the main state machine (hundreds of cycles) for every byte. Can you please take the measurements I suggested and tell me how are you checking if you received all 5kb correctly, because I still find that hard to believe.
  13. I can try and take a look later today, can you please attach your latest project as exported from MPLAB as a package zip?
  14. I think cdecl is the best place to learn c
  15. The weird thing is that printing out the value in the file like I did it includes the quotes though. When I played around with the command line I eventually managed to get it to work by passing in something like: -D'CONFIG_FILE=\\\"demo_config.h\\\"' The disappointing thing for me was that passing in the exact same thing that I am passing to GCC does not seem to work with the Microchip version.
×
×
  • Create New...