1
0
mirror of https://github.com/rfivet/stm32bringup.git synced 2025-10-18 08:54:01 -04:00
Files
stm32bringup/docs/26_uptime.html
2025-10-03 14:23:32 +08:00

350 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2.6 uptime</title>
<link type="text/css" rel="stylesheet" href="style.css">
</head>
<body>
<h1>2.6 uptime</h1>
It's time to throw away the prototype and write a production version of
<code>uptime</code>. There is several things I want to straighten up in
<b>uptime.1.c</b>:
<ul>
<li> <code>main()</code> is using assembly code to wait for interrupt.
Definitively not high level C.
<li> Also not high level C the lines <code>kputu( last) ; puts( ” sec”) ;</code>.
I should have <code>printf( “%u sec\n”, last) ;</code> instead.
<li> <code>kputc()</code> function prototype and the external variable
declaration for <code>uptime</code> should be included as a C header file.
</ul>
Similar to what I did when I split the functionalities between files
according to the three stages of execution (boot, init, main), I will
reorganize the code according to three categories (system, library,
application).
<h2>System</h2>
First I make clear what the system interface is by writing the header
<b>system.h</b>. Here belong the global variables external declarations and the
function prototypes.
<pre>
/* system.h -- system services */
extern volatile unsigned uptime ; /* seconds elapsed since boot */
int init( void) ; /* System initialization, called once at startup */
void kputc( unsigned char c) ; /* character output */
int kputs( const char s[]) ; /* string output */
void yield( void) ; /* give way */
</pre>
Next, I make a revision of <b>uplow.1.c</b> by making a copy into
<b>uplow.2.c</b>.
<p>
I include <b>system.h</b> which is the interface that <b>uplow.2.c</b>
implements. I will have several implementations of the same interface,
so <b>system.h</b> is not just the interface published by <b>uplow.2.c</b>,
it's <b>uplow.2.c</b> which is an implementation of <b>system.h</b>.
<pre>
#include "system.h" /* implements system.h */
</pre>
I extract the code for <b>puts()</b> as it is a library function that doesn't
really belong to the system.
<p>
I add the implementation of <b>kputs()</b> and <b>yield()</b>.
<pre>
int kputs( const char s[]) { /* string output */
int cnt = 0 ;
int c ;
while( (c = *s++) != 0) {
kputc( c) ;
cnt += 1 ;
}
return cnt ;
}
void yield( void) { /* give way */
__asm( "WFI") ; /* Wait for System Tick Interrupt */
}
</pre>
<h2>Library</h2>
I create the implementation of <code>printf()</code> in <b>printf.c</b>.
<ul>
<li> It uses the system interface <b>system.h</b>.
<li> I have eliminated the recursivity from my previous <code>kputu()</code>
version by adding characters at the beginning of a string.
<li> <code>kputu()</code> takes one additional divider parameter, so it can be
used to print unsigned integer in various format like octal, decimal and
hexadecimal. Current implementation will work for base 8 to 16, it won't
work for binary or base 36.
<li> <code>kputi()</code> outputs signed integer.
<li> <code>printf()</code> implements a subset of the format interpreter: %%,
%c, %d, %i, %o, %s, %u, %x, %X.
</ul>
<pre>
/* printf.c -- format and print data */
#include &lt;stdarg.h>
#include &lt;stdio.h>
#include "system.h" /* kputc(), kputs() */
static int kputu( unsigned u, unsigned d) {
char s[ 12] ; /* room for 11 octal digit + EOS */
char *p = &s[ sizeof s - 1] ; /* point to last byte */
*p = 0 ; /* null terminated string */
do {
unsigned r = u % d ;
u /= d ;
*--p = "0123456789ABCDEF"[ r] ;
} while( u) ;
return kputs( p) ;
}
static int kputi( int i) {
int flag = i < 0 ;
if( flag) {
i = -i ;
kputc( '-') ;
}
return flag + kputu( i, 10) ;
}
int printf( const char *fmt, ...) {
va_list ap ;
int cnt = 0 ;
int c ; /* current char in format string */
va_start( ap, fmt) ;
while( ( c = *fmt++) != 0)
if( c != '%') {
cnt += 1 ; kputc( c) ;
} else if( ( c = *fmt++) == 0) {
cnt += 1 ; kputc( '%') ;
break ;
} else
switch( c) {
case 'c':
cnt += 1 ; kputc( va_arg( ap, int /* char */)) ;
break ;
case 'o':
cnt += kputu( va_arg( ap, unsigned), 8) ;
break ;
case 'u':
cnt += kputu( va_arg( ap, unsigned), 10) ;
break ;
case 'x':
case 'X':
cnt += kputu( va_arg( ap, unsigned), 16) ;
break ;
case 'i':
case 'd':
cnt += kputi( va_arg( ap, int)) ;
break ;
case 's':
cnt += kputs( va_arg( ap, char *)) ;
break ;
default:
cnt += 1 ; kputc( '%') ;
/* fallthrough */
case '%':
cnt += 1 ; kputc( c) ;
}
va_end( ap) ;
return cnt ;
}
</pre>
<h2>Application</h2>
I write my final version of uptime in <b>uptime.c</b>.
<ul>
<li> It uses the system interface and standard library.
<li> Instead of a count of seconds elapsed it displays a breakdown in week,
days, hours, minutes and seconds.
</ul>
<pre>
/* uptime.c -- tells how long the system has been running */
#include &lt;stdio.h>
#include "system.h" /* uptime, yield() */
static void display( unsigned u, const char *s) {
if( u)
printf( " %d %s%s", u, s, &"s"[ u <= 1]) ;
}
int main( void) {
unsigned last = 0 ;
for( ;;)
if( last != uptime) {
unsigned w, d, h, m ,s ;
last = uptime ;
d = h = m = 0 ;
s = last % 60 ;
w = last / 60 ;
if( w) {
m = w % 60 ;
w /= 60 ;
if( w) {
h = w % 24 ;
w /= 24 ;
if( w) {
d = w % 7 ;
w /= 7 ;
}
}
}
printf( "up") ;
display( w, "week") ;
display( d, "day") ;
display( h, "hour") ;
display( m, "minute") ;
display( s, "second") ;
printf( "\n") ;
} else
yield() ; /* Wait for System Tick Interrupt */
}
</pre>
<h2>Build</h2>
To build I add the composition in <b>Makefile</b>.
<pre>SRCS = startup.c uplow.2.c uptime.c printf.c</pre>
Unfortunately, the build fails at the link phase.
<pre>
$ make
f030f4.elf
D:\Program Files (x86)\GNU Arm Embedded Toolchain\arm-gnu-toolchain-14.2.rel1-mi
ngw-w64-i686-arm-none-eabi\bin\arm-none-eabi-ld.exe: uptime.o: in function `main
':
D:\home\Projects\stm32bringup/uptime.c:41:(.text.startup+0xa4): undefined refere
nce to `putchar'
make: *** [Makefile:53: f030f4.elf] Error 1
</pre>
The linker found a reference to <code>putchar()</code> at line 41 of
<b>uptime.c</b>.
<pre>
printf( "\n") ;
</pre>
I haven't used <code>putchar()</code> in my code and line 41 is a
<code>printf( "\n")</code> that can be optimized to a
<code>putchar( '\n')</code>. This must be some high level C optimization of gcc.
<p>
I add the code for <code>putchar()</code> in <b>putchar.c</b> as it is a
standard library function.
<pre>
/* putchar.c -- write a character to stdout */
#include &lt;stdio.h>
#include "system.h" /* kputc() */
int putchar( int c) {
kputc( c) ;
return c ;
}
</pre>
Updating <b>Makefile</b> by adding <code>putchar.c</code> to the composition.
<pre>SRCS = startup.c uplow.2.c uptime.c printf.c putchar.c</pre>
The build now complete successfully.
<pre>
$ make
f030f4.elf
text data bss dec hex filename
1317 0 8 1325 52d f030f4.elf
f030f4.hex
f030f4.bin
</pre>
By checking the map file provided by the linker, I can see that the
number of low level modules referred by the code generated by the
compiler has increased. Both integer and unsigned division but also some
code to handle <code>switch()</code> statement are now referenced.
<pre>
Archive member included to satisfy reference by file (symbol)
D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-14.2.rel1-mi
ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/14.2.1/thumb/v6-m/nofp\libgcc.a
(_thumb1_case_sqi.o)
printf.o (__gnu_thumb1_case_sqi)
D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-14.2.rel1-mi
ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/14.2.1/thumb/v6-m/nofp\libgcc.a
(_udivsi3.o)
uptime.o (__aeabi_uidiv)
D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-14.2.rel1-mi
ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/14.2.1/thumb/v6-m/nofp\libgcc.a
(_divsi3.o)
uptime.o (__aeabi_idiv)
D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-14.2.rel1-mi
ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/14.2.1/thumb/v6-m/nofp\libgcc.a
(_dvmd_tls.o)
D:/Program Files (x86)/GNU Arm Embedded Toolchain/
arm-gnu-toolchain-14.2.rel1-mingw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/1
4.2.1/thumb/v6-m/nofp\libgcc.a(_udivsi3.o) (__aeabi_idiv0)
</pre>
<h2>Test</h2>
I flash the board and start execution, the output works as expected.
<p>
<img alt="uptime" src="img/26_uptime.png">
<p>
It will take a while to see the days and weeks counts appear, so I will
need to power the board independently from its serial interface. For
test purpose I fast forward the execution by using a bigger value for
the increment of <code>uptime</code> in <code>SysTick_handler()</code>.
<h2>Checkpoint</h2>
Rereading the code while writing this web page, I found a typo in the
week calculation. After that I retested with a bigger time increment to
make sure days and weeks values are correct. It's also clear that the
test coverage for the printf format interpreter is not sufficient as I have
coded more than is necessary to implement <b>uptime</b>.
<p>
I didn't expect gcc to optimize calls to high level C functions,
replacing some <code>printf()</code> by <code>putchar()</code>, thus forcing me
to write additional code. So far I am not concerned by execution speed, so this
type of optimization is a bit counter productive.
<p>
<a href="27_library.html">Next</a>, I will make sure that what belongs to the
library category fits in an actual library file.
<hr>© 2020-2025 Renaud Fivet
</body>
</html>