Skip to main content

Unix Timestamp Translator For Keil C51

··1106 words·3 mins· loading · loading · ·
Study Internship C C++
Aphcity
Author
Aphcity
I’m just a average student
Table of Contents

To implement with the timing function for the IoT module on the 8051 series microcontroller, we need to use Unix Time to record and transmit time in a standardized way. However, due to the particularity of the 8-bit microcontroller, we need to program the timestamp translator program with saving space and meeting the requirements of the C51 code. That also means that we cannot use the times.h from C99 and do more accurate calculations on choosing the data types to prevent data overflow.

Resources
#

Repo: https://github.com/Aphcity/timestamp

Unix Time Online Translator: https://tool.lu/timestamp/

Algorithms under Windows OS
#

Code works under Windows: Branch 6022208

Use TS_time_ to represent real time.

Unix Timestamp to RTC time
#

time_struct stamptotime(uint32_t stamp)
{
    const uint8_t month_buf[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    time_struct t_out;
    uint32_t min_add, hour_add, day_add, mon_add, year_add, day_temp, year_temp;
    t_out.h_year = 19;
    t_out.l_year = 70;
    t_out.mon = 1;
    t_out.day = 1;
    t_out.hour = 0;
    t_out.min = 0;
    t_out.sec = 0;
    min_add = (t_out.sec + stamp) / 60;
    t_out.sec = (t_out.sec + stamp) % 60;
    hour_add = (t_out.min + min_add) / 60;
    t_out.min = (t_out.min + min_add) % 60;
    day_add = (t_out.hour + hour_add) / 24;
    t_out.hour = (t_out.hour + hour_add) % 24;
    mon_add = 0;
    day_temp = t_out.day;
    for (int i = t_out.mon - 1;; i++)
    {
        size_t a = i % 12;
        year_temp = t_out.h_year * 100 + t_out.l_year + (i / 12);
        size_t temp = (a == 1 && leapyear_check(year_temp)) ? (month_buf[a] + 1) : month_buf[a];
        /* If we update the month, initial the day to the '1' */
        if (mon_add)
            day_temp = 1;
        if (day_add + day_temp > temp)
        {
            mon_add++;
            day_add -= (temp - day_temp + 1);
        }
        else
        {
            t_out.day += day_add;
            break;
        }
    }
    year_add = (t_out.mon + mon_add) / 12;
    t_out.mon = (t_out.mon + mon_add) % 12;
    // Reset the base to Jan.01.2000 when year_add is greater than 30
    if (year_add > 30)
    {
        year_add -= 30;
        t_out.h_year = 20 + year_add / 100;
        year_add = year_add % 100;
        t_out.l_year = 0;
    }
    else
        t_out.h_year = 19;
    t_out.l_year += year_add;
    return t_out;
}

month_buf is to store the number of days per month in a normal year, and leapyear_check is to ensure that the extra day in a leap year can be correctly calculated.

Logic and debugging
#

Calculate the seconds, minutes, and hours that do not require complex calculations, and simply take the remainder first.

As for the date part, first calculate the number of days added, and then start to traverse month_buf to get the year and month that have passed, and get the year increment and month increment.

  1. When processing increments greater than 30 years, it was found that the original code (as shown below) would cause overflow in the output of the low-byte year.
  • This is because the year increment is rounded up every 100 years in the calculation while the starting year is 1970. The low-byte year 70 will increase to 100+ when processing data of 30+.
  • There are two solutions here. After the addition, check the low-byte year again, and if it is greater than 100, choose to carry it to the high-byte; judge the year increment in advance. When the increment is greater than or equal to 30 years, directly add one to the high-byte, and reduce the increment by 30, which is equivalent to resetting the initial benchmark of the timestamp to 2000. This is a easier way to understand, so the second one is chosen.
  1. When processing the actual time month in December, since 12n % 12 == 0, 12 will be directly carried to year_add, causing the year to be incremented by one, and the month to be 0. Therefore, a conditional judgment is added to ensure that December is not carried.

RTC time to Unix Timestamp
#

This function is relatively simpler than converting to RTC, and the logic is in a similar way

uint32_t timetostamp(time_struct t_in)
{
    const uint8_t month_buf[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    uint32_t stamp = 0;
    uint32_t year = t_in.h_year * 100 + t_in.l_year;
    // 计算已过年的秒数
    for (uint32_t y = 1970; y < year; y++)
        stamp += leapyear_check(y) ? 366 * 86400 : 365 * 86400;
    // 计算已过月的秒数
    for (uint8_t m = 0; m < t_in.mon - 1; m++)
        if (m == 1 && leapyear_check(year)) // February in a leap year
            stamp += 29 * 86400;
        else
            stamp += month_buf[m] * 86400;
    // 计算已过天的秒数
    stamp += (t_in.day - 1) * 86400;
    // 计算已过时分秒的秒数
    stamp += t_in.hour * 3600;
    stamp += t_in.min * 60;
    stamp += t_in.sec;
    // 返回 int
    return stamp;
}

Logic
#

First, calculate the increment in seconds corresponding to the year increment. This can be calculated directly, but note that leap years will add an extra year. Then calculate the full month starting from January and the total number of days it contains. Then integrate and add the increment in seconds corresponding to the number of days, hours, minutes, and seconds themselves to get the result.

The logic is simple. Compared with the previous function, it is less prone to omissions in calculation or thinking. No errors were found during the debugging process.

Keil Debugging
#

In C51, the type sizes of short and int are the same, both are 16 bytes. This also caused data overflow when transferring the code to Keil. unsigned long was used as the storage method for 32-bit unsigned integers. Here is a reference to the basic common data types in C51.

Data TypesBitsBytesMinium ValueMaxium Value
bit101
signed char81-128127
unsigned char810255
enum8 / 161 / 2-128 / -32768+127 / +32767
signed short int162-32768+32767
unsigned short int162065535
signed int162-32768+32767
unsigned int162065535
signed long int324-2147483648+2147483647
unsigned long int32404294967295
float324±1.175494E-38±3.402823E+38
double324±1.175494E-38±3.402823E+38
sbit101
sfr810255
sfr16162065535