Jump to content
 

Orunmila

Member
  • Content Count

    221
  • Joined

  • Last visited

  • Days Won

    28

Blog Comments posted by Orunmila


  1. The way I2C works when you add a second device no changes should be required in your configuration. If the working address stops working it is usually either because of multiple devices with the same address or it is due to an electrical problem. Could be the capacitive loading on the bus or even reflections if you have a star with long lines without termination (could also be incorrect size of or even missing pullup resistors). What I would suggest is to try and reduce the speed after ensuring that you have different addresses. 

    Also look at the lines with an oscilloscope and check to see if the bus is being pulled down (ACK) after the address. If not no device is recognising the address.


  2. On 12/2/2020 at 8:03 AM, PacMan said:

    The initialization is using something called 'Compound Literals'.

    It's perfectly valid according to the GCC documentation, but beware, it can be 'dangerous' if you're using C++ (I'm not).

    Unfortunately, the GCC documentation does not seem to mention how to ensure the compound literals to be in a specific section.

    -But if anyone would know these things (apart from the compiler developers), it would be developers of embedded software like you. 😉


    Perhaps I can explain it better with an example.

    If you wrote code like this, which is using a non-compound literal
     

    int i = 7;

    where do you expect the 7 to be located in the map file?

    Of course the correct answer is that it gets encoded as part of the assignment code, so it would end up in the TEXT segment with the other code, but there is no rule for this, if this is an auto variable it will just be in the TEXT segment, if it was statically allocated though the compiler may, as an optimization, copy variables in bulk into the ram location of i, but I would say even in that case it goes in TEXT.

    The problem is not whether or not the literal is valid C or not, of course it is valid, but I think your expectations of where (or perhaps more accurately IF) memory is allocated for the struct are not quite right. I think if you look at the ASM generated for your initializer it will make more sense. In the Microchip PIC's I spent a lot of time on your initializer would compile to something like this

    MOVLW  2;
    MOVWF  actualCat
    MOVLW  17;
    MOVWF  actualCat+4

     Now if you have code that takes the address of that, it will actually return the address of what? Perhaps the instruction MOVLW? This is dangerous because it will not necessarily behave the way that you expect. If you do a cast like you are doing, depending on the implementation in the compiler it may create a temporary variable on the stack, and then return the address of this temporary variable. This is also dangerous because although your allocation will work the pointer will not be valid later in the application, and even if you test it and the memory is still intact it may be deemed fair game to the compiler to hand out to someone else at the time, in that case it is dangerous as you are pointing to unallocated memory and will get undefined behavior (even in C++).

     

    Quote

     

    Here's a link to an example, which you may find useful, because initializing this way is really beneficial on small devices:

    http://nickdesaulniers.github.io/blog/2013/07/25/designated-initialization-with-pointers-in-c/

     

    Yes of course I am also using designated initializers in the examples in the article above, I agree that they work great (not only on small devices, on all devices), but of course you lose control of the memory allocation if you do it the way you are doing it (specifically casting the anonymous struct and then taking it's address) because if you start casting things you better know what you are doing because casting == telling the compiler you know better than the compiler - never a great idea 😉

     I generally avoid designated initializers though since it was only added in C99 and many compilers from embedded vendors do not yet support them so I always run into portability problems when I do that.

     


  3. You are initializing the structure using an anonymous structure which is not specified to be located in a specific setion. I have not investigated it any further but I think it might actually be dangerous to take the pointer of an anonymous struct like that because I believe it will be allocated on the stack and it will not be obvious when the memory is being freed (although I may be mistaken here, it still looks dangerous to me).

    I think what you should do is something like this

    #define BOB_SECTION	__attribute__((section(".bob")))
    #define FRANK_SECTION	__attribute__((section(".frank")))
    
    struct Cat {
    	uint16_t	heads;
    	uint16_t	tails;
    };
    
    FRANK_SECTION struct Cat actualCat = {
    		.heads = 2,
    		.tails = 17
    	};
    
    BOB_SECTION struct Cat*  gNeelix = &actualCat;

     


  4. On 6/14/2020 at 10:28 PM, ekanlar said:

    Thank you so much for this helpful document.

    In the offset error section; you first say the offset error is 2.5 LSB but then you mention it as 0.5 LSB typical and 2.0 LBS at max. This made me confused a little bit 

    Thanks for pointing out the mistake, I have updated the post to show 2.0 LSB which is the correct Offset error for this device

    • Like 1

  5. On 2/18/2020 at 11:14 AM, JG2018 said:

    This is great, thanks for summing that up!

    So if I wanted to calculate the number of bits that are lost from your example above:

    Source impedance = 100k; LSb Error = 5LSb; log(5)/log(2) = 2.32

    This means that the last 2.32 bits are lost and my ENOB = 9.68?

    Yes that is correct. 


  6. Glad you found it helpful JG2018,

    Let me try and answer your questions.

    On 2/13/2020 at 12:13 PM, JG2018 said:

    1. How did you get the equation to calculate the fractional number of bits [Log(# LSb error)/Log(2)]?

    This part is just math and the magic of logarithms. The important identity here is that logx(y) = log10(x)/log10(y), and we know that log2(x) will give us how many bits we need to represent x, but our calculators do not do log2 instead they do log10 , which means we have to take log10(x) / log10(2) to get the number of bits we need to represent the number.

     

    On 2/13/2020 at 12:13 PM, JG2018 said:

    2. For a high impedance input, does the voltage offset due to leakage current contribute to the LSb error?

    The short answer is yes because the leakage contributes to a measurement error, and we measure the ADC error as a proportion of LSB size as we described above.

    How the leakage contributes is a lot more involved and depends on the construction of the ADC and the methods it uses for sampling. Section 7 above shows the simplified circuit diagram of the ADC input path and you can see where Microchip places the leakage current specified in the datasheet. If you calculate the network currents when this circuit is attached to your circuit under measurement you can determine how much of an effect the leakage will have to your measurements. 

    If the source impedance is small the bulk of the current you feed into the pin will end up going into the sampling capacitor and the leakage will have a small effect, but of course the higher your source impedance is the lower the current into the sampling capacitor and the more of an effect the leakage current will have.

    We can use the PIC18F47K40 example above since the leakage current is indicated in the simplified diagram in section 7 above.

    If we have 10k of source impedance and we have an additional 1k of internal impedance on the ADC charge path we have at 3V approx 272uA charging the capacitor. The pin leakage is specified at max to be 125nA, which means that 125nA/272uA is the proportion of the charge current lost to our measurement. In this example it contributes 0.0459% error. For a 10-bit ADC this would mean roughly 0.45 LSB of error (calculated as 0.0459% of the full range which is 2^10). 

    That explains the comment in the device datasheet that if your source impedance is more than 10k this will result in an additional error of more than 0.5 LSB which means it will start contributing to your measurement error.

    To see how this turns into a larger error we can do the same math with a source impedance of 100k instead. This divides the current by 10 and consequently increases the error to 4.59 LSB of error. This means you can still take the measurements but you will potentially have almost 5 LSB of additional error to contend with. And you cannot calibrate out the leakage as it varies with voltage and temperature and the spec already has it varying from 5nA to 125nA depending on these parameters and also the process variation.


  7. 12 hours ago, IamGroot said:
    
    string1[3] = 'a';

    This gave a segmentation fault on compilation 

     

    Looks like it gave the segfault upon running? And yes that is what I would expect because  on a PC your code is trying to write to memory which should not permit writes. On an embedded system the behavior will depend a lot on the underlying system. Some systems will actually crash in some way, others, like XC8 based PIC microcontrollers, actually copy the read-only section into RAM so the code will actually work. This is why this is so dangerous, the behavior depends on the target and the toolchain and when this is one day tried on another system it could be a real challenge to figure out the mistake because it is so easily masked.


  8. Your example is actually quite complex and it contains a number of really typical and very dangerous mistakes. I think it will take up way too much space here in the comments to answer this properly so I will write another blog just about your question here and post that over the weekend when I get some time to put it together.

    A hint to what is wrong with the code: The buffer you are using in get_person() is allocated on the stack, but the space is made available for use after the function returns. After this point you have a struct which contains a pointer to the buffer on the stack, but the memory no longer belongs to you and when you call any other function it will get overwritten.

    There is no rule that the compiler must allocate variables on the stack consecutively, and actually XC8 uses a "compiled stack" which means that variables are placed permanently where they will not overlap. You can probably get behavior closer to what you expect if you change the stack model to use a software stack.

    The last time this happened to someone in my team they were calling get_person for the first name, and then for the second name, and after calling it twice they tried to print out both names and both the structs had the same string, if you call it many times all the people will have the last name you entered.

    Try this with your code:

     struct person p1 = {"nobody"};
     struct person p2 = {"nobody"};
    
     get_person(&p1);
     get_person(&p2);
    
     printf("p1 name = %s\n", p1.name);
     printf("p2 name = %s\n", p2.name);
    

    You will enter a new name in get_person for each one, and after that the printing should not print nobody but the 2 different names you have entered, and both names should be different.

    Let me know if that behaves as you expected?

    Read my upcoming blog on stack usage and pointers to see why 🙂


  9. 8 hours ago, westfw said:

    Your update didn't include changing the "loop size" for AVR....
    You could also used the OUT instruction, which would cut down on the setup....

     

    Thanks for pointing that out! I looked over the code 3 times before I realized you were referring to the Table 🙂

    And yes I agree there are a lot of ways to optimize what happens before the loop, we decided not to even bother with making that smaller as it did not form part of our focus here. For example I also noticed that the Start/MCC generated code for the AVR was actually quite large, more than 4x what the size was for the PIC code - in fact it took more than 800 instructions to just make a loop that toggles a pin. In ASM I can do all that in almost 100x less, but if this was going to be a reasonably fast read we had to be laser focussed.

    The idea in the longer run is to add a lot of small posts that focus on one specific aspect every time, and we will be sure to cover the advantages of OUT in the future sometime based on your recommendation.

     


  10. 4 hours ago, witz said:

    Wandering to here, just want to ask some questions :      
    1.
    "The type of initialization of a union using the second member of the union was not possible before C99"  why? 

    Because in C89 this would be a syntax error. The syntax did not exist until it was introduced in C99 together with named initializers. In C89 it was not possible to initialize a union by it's second member because it was not possible to name the target member. This is important because many compilers today are still not fully C99 compliant and support only some of it's constructs, which means that if you use named initializers your code may be less portable because some compilers may still choke on that syntax.

    4 hours ago, witz said:

    2.
    typedef signed int t;

          typedef int plain;
          struct tag {
                unsigned t:4;
    "one named t that contains values"  All I see is a unname member.

    This example is verbatim from the C99 standard section 6.7.7 paragraph 6. The answer to your question is right there in the last sentence "The first two bit-field declarations differ in that unsigned is a type specifier (which forces t to be the name of a structure member), while const is a type qualifier (which modifies t which is still visible as a typedef name). "

    So in other words because of the "unsigned" the t is forced to be the name of the member and it is NOT the type of the member as you may expect. This means that when used like that the member is indeed not unnamed, it is named as t of type unsigned and the typedef from above is not applicable at all. I know, that is why even in the standard they refer to this as "obscure"!

    4 hours ago, witz said:

    3. Why on earth can't I use navigation keys and Enter here???

    I have no idea, navigation keys and Enter work just fine for me. I am using Google Chrome, perhaps it is the browser or a setting. Which browser are you using?

     


  11. I have to add here that the single thing which trips most people up on RTCOUNTER is the timer period. The timer MUST be free-running, which mean that you can NOT use MCC to set a timer period by using a reload or PR value, the timer HAS to be set to maximum period and the only thing you can modify is the prescaler for the timer to adjust the period.

    I know this is limiting, but it allows us to have a pure monotonic upwards counting number which we can concatenate with our sw variable to construct the timer. Remember that we want as few interrupts as possible, so running the timer for MAX period is exactly what we want to do here! This library does not have an event when the timer gets an interrupt or overflow (that is what the TIMEOUT driver does if you want that, but there is quite a price for this). 

    When you call the check function it will simply compare the current timer register with the absolute time variable for the next timer to expire, it is a quick compare, so pretty cheap to do this.

    • Like 1
  12. The Story of Mel


    3 minutes ago, N9WXU said:

    That aside, be sure to read the detailed explanations.  They are as fascinating as the original story.

    https://jamesseibel.com/the-story-of-mel/

    https://medium.freecodecamp.org/macho-programmers-drum-memory-and-a-forensic-analysis-of-1960s-machine-code-6c5da6a40244

     

    Thanks for posting these, that first one "The Story of Mel Explained" is a fantastic explanation of it piece by piece!

×
×
  • Create New...