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.
- 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.
- 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 Types | Bits | Bytes | Minium Value | Maxium Value |
---|---|---|---|---|
bit | 1 | 0 | 1 | |
signed char | 8 | 1 | -128 | 127 |
unsigned char | 8 | 1 | 0 | 255 |
enum | 8 / 16 | 1 / 2 | -128 / -32768 | +127 / +32767 |
signed short int | 16 | 2 | -32768 | +32767 |
unsigned short int | 16 | 2 | 0 | 65535 |
signed int | 16 | 2 | -32768 | +32767 |
unsigned int | 16 | 2 | 0 | 65535 |
signed long int | 32 | 4 | -2147483648 | +2147483647 |
unsigned long int | 32 | 4 | 0 | 4294967295 |
float | 32 | 4 | ±1.175494E-38 | ±3.402823E+38 |
double | 32 | 4 | ±1.175494E-38 | ±3.402823E+38 |
sbit | 1 | 0 | 1 | |
sfr | 8 | 1 | 0 | 255 |
sfr16 | 16 | 2 | 0 | 65535 |