6

I have implemented fputc and fgetc in retarget.c to successfully use printf via UART0 on a Cortex-M3.

However, I want a second uart channel for additional debug information. How can I integrate this as nicely as I can UART0 using printf?

For example, using fprintf to a custom target and checking in fputc which target to send the character to.. E.g. for normal output fprintf(UART0,".."); and for debug output fprintf(UART1,"..");

But I cannot see if fopen is called for stdout so I am struggling to see how to manually implement this. (If I just call fprintf(RANDOM_VALUE,..), I don't know how this will behave.

I guess that once I have it directed to a different 'FILE', then it is simply a matter of checking which is being pointed to within fputc but it is the initial setting of the FILE pointer that I am struggling with.

Perhaps some way to differentiate between stdout and stderr, although then I still have the same problem for getting input from the two separate channels.

Also is fprintf in the microlib? If not, is there a better way to implement this?

Thanks!

5
  • 1
    Which libc library are you using? It will depend on the implementation as to how easy it will be to drop in your own stream handling. Commented Jan 23, 2013 at 12:04
  • How can I find this out? I am using Keil uVision, if that helps. I can't see any option for types of library. Commented Jan 23, 2013 at 13:47
  • If you want to implement a custom special case stream, you could either hard code it's number as a constant which your program can use, or you could make your fopen() recognize a special string, for example "/dev/UART1" which would make the functionality fairly clear to any experienced developer who ends up reading your code. Commented Jan 23, 2013 at 16:41
  • I think that using fopen and fclose would be inconvenient, since I want this to be as accessible as stdout, spread over the code. Is there any danger in just picking a random FILE* value and having a switch statement in fputc to check for this value (if it matches, print to UART1; if not, print to UART0)? Does anything else use this pointer? My concern is that if the pointer matches anything else in the memory map, could using fprintf interfere with it? Commented Jan 23, 2013 at 17:02
  • I suppose I could declare a variable and use that pointer to ensure it is not pointing anywhere harmful? Commented Jan 23, 2013 at 17:07

1 Answer 1

10

fputc() takes a stream pointer argument, there are two standard output streams stdin, stdout and stderr. At the lower level of the retargeting these are associated with the file descriptors 0, 1, and 2 respectively, you can use this information to associate stderr with the alternate UART at the device driver level.

You can then output debug data using stderr thus:

fprintf (stderr, "Error reading file" ) ; 

for example.

A minimal retargeting (specific to Keil ARM-MDK/RealView) might look like this:

struct __FILE { int handle; }; enum { STDIN_HANDLE, STDOUT_HANDLE, STDERR_HANDLE } ; FILE __stdin = {STDIN_HANDLE} ; FILE __stdout = {STDOUT_HANDLE} ; FILE __stderr = {STDERR_HANDLE} ; int fputc(int ch, FILE *f) { int ret = EOF ; switch( f->handle ) { case STDOUT_HANDLE : // Write character to UART0 ... ret = ch ; break ; case STDERR_HANDLE : // Write character to UART1 ... ret = ch ; break ; default : break ; return ret ; } 

Obviously this is also where you might hook in a filesystem if you needed, in which case your __FILE struct would no doubt have additional members.

If you don't want to use stderr for this purpose, you will have to retarget fopen() to translate a device name ("dbg:" for example) into a file descriptor for the desired port and then use stdio to output to the associated stream.

Also is fprintf in the microlib? If not, is there a better way to implement this?

The documentation will tell you, but yes. Microlib stdio support is controlled by the #pragma import(__use_full_stdio) directive, the documentation is not clear about what is excluded if this is not used. Try it without and use it if anything is missing. That said I would imagine that printf() is implemented as an fprintf() to the stdout stream, so if you have printf() you have fprintf().

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for the sample.. so I assume that STDIN_HANDLE = 0, STDOUT_HANDLE = 1, etc..? If I need input on both channels as well, how would you split stdin?
STDIN_HANDLE etc. are enumerated automatically in the order listed in the enum starting from zero - that is required ISO C behaviour; you can explicitly assign values to the enums if you wish to be absolutely clear, but it is not strictly necessary. Input is dealt with by fgetc() in a similar manner. I don't believe that there is anything that would stop you accepting input on either stdout or stderr. In the case where the console comprises of a separate keyboard and display it may be necessary to make the distinction but not with a UART.
Thanks, I didn't realise you could also use stdout and stderr for input. I am not able to implement this today, but there should be all the hints I need here now! You have really helpful!
@Mark: You can't generally; or at least it is probably "undefined" behaviour. However what defines the behaviour is the low-level device driver and in this case you have full control over that in the retargetting.
Note you can always add additional "implicitly open" global streams by defining additional FILE objects, e.g. FILE dbgio_stream = {DBGIO_HANDLE};, then create a global pointer FILE* dbgio = &dbgio_stream ; and handle that in a similar manner to the standard handles. You would then simply use fprintf( dbgio, ...) for example. It does assume that the you have initialised device beforehand. You can even have separate dbgin and dbgout streams if you wish mapping to different or the same device.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.