0

I've inherited some code that uses the elm-chaN fatfs. It creates Directories and files with either missing or wrong date-time stamps. I've traced the problem to what appears to be an incorrect cast.

I changed the cast and things seem to work. But I would like confirmation from more experienced programmers that the original cast was incorrectly constructed (or a compiler issue)

Below is the original code: the curTime stuct assigns uint16_t to "Year". The other members are uint8_t types. They need to be packed into 32b word (DWORD) following the DOS date/time bitmap format.

{
    DWORD tmr;

    /* Pack date and time into a DWORD variable */
    Calendar curTime = sdInterface->sdGetRTCTime();

    tmr = ((DWORD)(curTime.Year-1980)<<25)
            | ((DWORD)(curTime.Month) << 21)
            | ((DWORD)(curTime.DayOfMonth) << 16)
            | (curTime.Hours << 11)
            | (curTime.Minutes << 5)
            | (curTime.Seconds >> 1); //modified to truncate into two second intervals - jn

    return tmr;
}

Below is the modified code: I explicitly cast the Hours, Minutes and seconds to DWORD type. It is puzzling as to why the original author would cast Month, DayOfMonth but not the other uint8_t types.

DWORD get_fattime(void)
{
    DWORD tmr;

    /* Pack date and time into a DWORD variable */
    Calendar curTime = sdInterface->sdGetRTCTime();

    tmr = ((DWORD)(curTime.Year-1980)<<25)
            | ((DWORD)(curTime.Month) << 21)
            | ((DWORD)(curTime.DayOfMonth) << 16)
            | ((DWORD)(curTime.Hours) << 11)
            | ((DWORD)(curTime.Minutes) << 5)
            | ((DWORD)(curTime.Seconds) >> 1); //modified to truncate into two second intervals - jn

    return tmr;
}

the code seems to work. Would like a sanity check from more experienced programmers.

I am updating this post to provide the requested information: first is the struct Calendar curTime.

//
//! \brief Used in the RTC_C_initCalendar() function as the CalendarTime
//! parameter.
//
//*****************************************************************************
typedef struct Calendar {
    //! Seconds of minute between 0-59
    uint8_t Seconds;
    //! Minutes of hour between 0-59
    uint8_t Minutes;
    //! Hour of day between 0-23
    uint8_t Hours;
    //! Day of week between 0-6
    uint8_t DayOfWeek;
    //! Day of month between 1-31
    uint8_t DayOfMonth;
    //! Month between 1-12
    uint8_t Month;
    //! Year between 0-4095
    uint16_t Year;
} Calendar;

The image below shows the directory of the SD Card running the original code (without the DWORD cast on the Hour, Minute and Time uint8_t objects). Notice the missing date time stamp on two of the files. The directories are also missing their date time stamps.

enter image description here

And the last two images below shows the results of the code with DWORD applied to the uint9_t objects. Both directories and files now have date time stamps.

Directories now have correct timestamps

And the files also have correct timestamps

From the comments I received so far, I am leaning towards this being a compiler error. The code was developed on earlier version of the compiler. This is a new compiler CCS v9.

kial
  • 47
  • 4
  • Are you sure `Hours`, `Minutes`, and `Seconds` are of type `uint8_t`? If so, I would expect the original code to have worked. I would expect the additional casts you have added to make a difference only if `Hours`, `Minutes`, and `Seconds` have signed types. – Steve Summit Aug 23 '19 at 19:21
  • To be clear, the question is what *types* are the `Hours`, `Minutes`, and `Seconds` fields of `Calendar` declared as? (Not what type of value is assigned to them.) – Steve Summit Aug 23 '19 at 19:23
  • yes, I am sure they are uint8_t types. I also would have expected this code to work. so maybe this is a compiler problem rather than a coding problem? But its strange that the original author cast some but not all the uint8_t. – kial Aug 23 '19 at 19:29
  • here is part of the declaration of those types: //***************************************************************************** //***************************************************************************** typedef struct Calendar { .. uint8_t Minutes; //! Hour of day between 0-23 uint8_t Hours; //! Day of week between 0-6 uint8_t DayOfWeek; //! Day of month between 1-31 uint8_t DayOfMonth; //! Month between 1-12 uint8_t Month; //! Year between 0-4095 uint16_t Year; } Calendar; – kial Aug 23 '19 at 19:32
  • 2
    That belongs [in your question](https://stackoverflow.com/posts/57631810/edit), properly formatted for human eye consumption. – WhozCraig Aug 23 '19 at 19:35
  • yeah, your right. but i thought it would be enough to prove that all but year are of type uint8_t. next time. – kial Aug 23 '19 at 19:36
  • The other thing that would help a lot would be to post some examples of input (and expected output) that the program does and doesn't work on. – Steve Summit Aug 23 '19 at 19:55
  • I updated my post to include the before and after results, and to respect WhozCraig request. – kial Aug 23 '19 at 23:34
  • Thanks for the examples, but I'm afraid they're not very useful. I take it that SYSLOG_2.TXT and SYSLOG_4.TXT are two of the files whose times aren't displaying correctly. I was hoping that those two files would be among those you also showed when running under the corrected code, so that I could know their actual times, so that I could investigate the code's behavior on those times. But no. – Steve Summit Aug 24 '19 at 01:33
  • Instead of all those screenshots, why not just say something like: "For the input date January 20, 1989, 10:51:48, the `get_fattime` function generates 0x12345678, which works. But for July 13, 2107, 31:22:28, it generates 0xdeadbeef, which displays as blank." Then we might get somewhere. – Steve Summit Aug 24 '19 at 12:06

3 Answers3

1

[This is not really an answer, but it's too elaborate for a comment.]

Please try this program:

#include <stdio.h>

int main()
{
    uint8_t b = 9;
    DWORD w1 = b << 6;
    DWORD w2 = (DWORD)b << 6;
    printf("%d %d\n", (int)w1, (int)w2);
}

The expected output of this program is

576 576

Shifting right by 6 bits is equivalent to multiplying by 64, and 9 × 64 = 576, so this makes sense.

If, on the other hand, you get the output

64 576

I believe this indicates a bug in your compiler. 64 is what you get if you take 9 and shift it left by 6 bits within an 8-bit field, meaning that you lose a bit off the left.

That (almost) makes sense, and it's what the answer posted by @jaz_n is getting at. However, your compiler is not supposed to shift anything left within an 8-bit field. When you write

x << 6

where x is type uint8_t, the first thing the compiler is supposed to do is promote x to a full-width integer type, then shift it left by 6 bits, in a field that's as wide as type int on your machine (which will obviously be either 16 or 32 bits, typically).

This explains why the three "extra" casts to DWORD should not be necessary. The fact that you had to add them suggests that your compiler may have a problem. If your compiler generates code that prints 64 576 for my test, this is additional evidence that your compiler is wrong.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
0

C compilers have some option to compile in different mode(16-bit, 32-bit, 64-bit). From the code you provided above, it looks DWORD stands for 32-bit.

The original code may work fine if it is compiled in 16-bit mode, because in calculation 16-bit lengths is used by default without specific cast.

Please also keep in mind while you are migrating your code from one platform to another, or change the compile option from 16-bit to 32-bit or 64-bit some of your code may not work properly.

S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
0

Minutes and Hours are 8 bit entities (uint8_t). In the first code snippet, the compiler did exactly what it was suppose to: shift Minutes and Hours to the left some number of bits. Those bits were lost and the residual bits were OR'd with the 32b DWORD. The bitmask failed because the most significant bits of Hours and Minutes were thrown away. In the second code snippet, Minutes and Hours are first cast into a 32b entity, now the shifted bits are preserved and the bitmask succeeded.

Seconds is shifted to the right with the intent to increase the granularity of the field (i.e. 2 Seconds per count) so technically you did not need to cast Seconds into a 32b entity.

jaz_n
  • 41
  • 6
  • But if you left-shift a `uint8_t`, I'm pretty sure it's *supposed* to get promoted to an (unsigned) int first before anything else happens. See [this question](https://stackoverflow.com/questions/57609263/why-does-bitwise-left-shift-promotes-an-uint8-t-to-a-wider-type?r=SearchResults). So the lack of the `DWORD` cast on `Hours`, `Minutes`, and `Seconds` shouldn't matter. It's possible that the compiler is *wrongly* emitting a shift instruction that's limited to 8 bits, in which case your answer would apply, but again, this indicates that the compiler is doing something wrong. – Steve Summit Aug 24 '19 at 14:06
  • (For `Year`, `Month`, and `DayOfMonth`, on the other hand, the cast to `DWORD` is necessary no matter what, because on a 16-bit machine, the cast to integer width would still not prevent data loss with the shifts of 25, 21, and 16 bits on those quantities.) – Steve Summit Aug 24 '19 at 14:09
  • Yes you are correct. The standard does specify that uint8_t are promoted on certain operators (<< included). However CCS v9 is a TI microcontroller compiler which defaults to non compliant due to the limited memory space of the target. So not necessarily a compiler bug but a compiler option. – jaz_n Aug 25 '19 at 15:38