C function like printf but not sending to stdout? (XC8) - Page 1
Forum section: electronics / general

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 01:21:50 am
content: Being new to C programming, I just can't seem to figure this one out.I am sending a string my LCD write function. The function works fine with variable length text as I use a buffer.Code:void lcd_out(const char *buffer);Now, how do I send it a formatted string like so?Code:lcd_out("Version %d.%d", verMajor, verMinor);I need to somehow replicate the behaviour of printf in my function, but how?

author: Psi
author_rank: Super Contributor
timestamp: March 12, 2013, 01:25:51 am
content: This should get you startedhttp://en.wikipedia.org/wiki/Variadic_function

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 01:25:57 am
content: sprintfwill format a string into a buffer. Be very careful of buffer overflow -snprintfprevents this, but is not present in all C libraries.

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 01:54:40 am
content: Hmmm... seems I really don't understand how strings and pointers work in C. I usually program in Delphi and other languages - anything other than C !How do I:Declare a string (for the LCD buffer)Assign text to that stringPass that to my LCD function

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 01:57:54 am
content: That's because C doesn't have strings. It just has character arrays, and a string literal is just shorthand for a predefined character array. You need to declare a character array "char my_array;", you can set its value withstrcpy(watch the length!), and you can just pass it by name.

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 02:15:52 am
content: Gotcha. OK, I now have this working:Code:lcd_set_cursor(1,0);strcpy(lcd_buffer,"My Project");lcd_out(lcd_buffer);lcd_set_cursor(2,0);sprintf(lcd_buffer,"Version %d.%d",version_major,version_minor);lcd_out(lcd_buffer);Thanks for your help guys. Learning a new language can be frustrating at times, but I think I learned a reasonable amount from that.

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 02:25:41 am
content: If you're just printing a constant string, you don't needCode:strcpy(lcd_buffer,"My Project");lcd_out(lcd_buffer);You can just uselcd_out("My Project");. Usestrcpyif you need to start filling a buffer (then you can usestrcatto append).

author: Harvs
author_rank: Super Contributor
timestamp: March 12, 2013, 02:36:17 am
content: Keep in mind that the use of some of these type of standard library functions (like printf, sprintf etc.) can be far too heavy for a memory limited uC, so you need to watch your compiled code size. Possibly the one included with XC8 has been optimized for use in a uC, but often you're better off rolling your own char array manipulation routines for the specific application. E.g. if you're always just converting an unsigned int to decimal and inserting it at a particular location you can do that with very little memory or processor time.

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 02:53:34 am
content: Quote from: c4757p on March 12, 2013, 02:25:41 amI tried that but get an error:Code:illegal conversion between pointer types

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 02:55:18 am
content: It's been a while, so this is a shot in the dark, but change yourlcd_outdefinition from taking achar *to achar const *.

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 02:59:01 am
content: Perfect! Thanks very much for that.

author: TerminalJack505
author_rank: Super Contributor
timestamp: March 12, 2013, 03:00:59 am
content: Like Harvs says, the sprintf() library code is going to be pretty chunky due to the fact that it has to support a ton of different formatting options. In the case of your example it would be more efficient to use theitoa()function to get the integer variables into string format. If your compiler supports itoa(), that is. It isn't part of the standard but most compilers support it.Code:...lcd_set_cursor(2,0);lcd_out("Version ");lcd_out(itoa(version_major, version_buffer, 10)); // 10 == numerical base.lcd_out(".");lcd_out(itoa(version_minor, version_buffer, 10));Just something to keep in mind in case you start running out of program space.

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 03:02:22 am
content: When you give a string in quotes, the compiler is free to store it wherever it wants, including in read-only memory, so it will refuse to pass it to a function that can modify it. "char const *" means "pointer to a constant (read-only) character". Some compilers do not enforce this, which is why it didn't occur to me at first.

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 03:03:19 am
content: Quote from: TerminalJack505 on March 12, 2013, 03:00:59 amGood point.

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 03:15:38 am
content: Interesting. The sprintf version adds 569 words of code, while the itoa version only adds 287 words. Still bloated compared to the assembler I'm used to writing.On a related point, with assembler I used to convert a simple 0..9 integer to an ASCII character (for display) by adding 0x30 to it. I assume that sort of thing is also possible in C?

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 03:17:41 am
content: Absolutely. In fact, acharisn't even really a character, it's just an 8-bit integer. You can do math on them directly.

author: Rufus
author_rank: Super Contributor
timestamp: March 12, 2013, 03:17:57 am
content: Quote from: Harvs on March 12, 2013, 02:36:17 amOne of the features of the (ex) Hi-Tech compilers is 'omniscient code generation' which effectively compiles the entire program and used libraries as a single module. That gives the optimiser visibility of the entire program allowing it to remove redundant code. I suspect they jump through additional hoops with the printf series parsing possible format strings to know what is redundant.With the free version you only get messages telling you how much better the paid for one would be. Some claim the free version deliberately generates horrid code, personally I think it is just an indication of how much work the optimiser is expected to do.

author: Psi
author_rank: Super Contributor
timestamp: March 12, 2013, 03:19:04 am
content: Quote from: David_AVD on March 12, 2013, 03:15:38 amyep, that still works fine.As said above, a char is just an 8 bit number. So you can do maths on them however you want.

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 03:22:22 am
content: People keep telling me how C code is so much quicker to write than assembler, but I'm old (actually middle age!) and have been somewhat set in my ways.I'm sure I'll get better at writing C code, but it's painfully slow at present.I've been pondering whether the standard or pro version of XC8 would be the way to go. Code space it probably not so much of an issue, but some projects I do use DMX and some high speed SPI data streaming so speed of execution can be important.

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 03:25:49 am
content: Quote from: David_AVD on March 12, 2013, 03:22:22 amI can bang out PIC assembly pretty fast. I rarely revert to C on an 8-bit micro, though I do use it for the 16- and 32-bit ones.

author: IanB
author_rank: Super Contributor
timestamp: March 12, 2013, 03:26:59 am
content: Quote from: David_AVD on March 12, 2013, 03:15:38 amSure. Something like this:Code:int versionMajor = 1;int versionMinor = 5;char buf[] = "Version x.y";buf = versionMajor + 0x30;buf = versionMinor + 0x30;

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 03:29:22 am
content: This works:Code:char temp;temp = version_major+0x30;lcd_out(&temp);But this doesn't:Code:lcd_out(version_major+0x30);

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 03:30:53 am
content: Quote from: IanB on March 12, 2013, 03:26:59 amOoh... I like that. Using the array as ... well an array!

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 03:31:36 am
content: I vastly prefer using'0'to0x30in that case - more obvious.(As an aside, it's also "portable", as in "back when C was designed not everything was ASCII", but that's kind of like buying a trailer for your pickup truck that is also horse-compatible...)Quote from: David_AVD on March 12, 2013, 03:29:22 amLike I said, acharis just a number. You've got to put it in a string/array. If you're doing it a lot of times and can spare the space for code, it may be more efficient to make a second function that just takes a singlechar.

author: IanB
author_rank: Super Contributor
timestamp: March 12, 2013, 03:32:09 am
content: Quote from: David_AVD on March 12, 2013, 03:29:22 amNo; the lcd_out function is expecting the address of a character array in memory. The expression (version_major+0x30) is not a memory address it is an immediate value. Note that whenever you pass a string like "foo" into a function you are really passing the memory address of the first character 'f'.

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 03:34:35 am
content: Quote from: David_AVD on March 12, 2013, 03:29:22 amOoh, missed this one. Bad, very bad. You're taking the address of thecharto use it like a pointer. The problem is that C strings contain an end-of-string marker one past the last character, which doesn't exist because this is just a singlecharvariable, so it's looking somewhere in God-knows-where in your memory for it. You got lucky.The worst part is that there is absolutely no way for the compiler to detect it. A memory analyzer like Valgrind would scream bloody murder at the sight of it, but you can't exactly use Valgrind on an 8-bit microcontroller.

author: IanB
author_rank: Super Contributor
timestamp: March 12, 2013, 03:36:56 am
content: Quote from: c4757p on March 12, 2013, 03:31:36 amGood point. I was going with 0x30 because that is what was given, but the improved version would look like this:Code:int versionMajor = 1;int versionMinor = 5;char buf[] = "Version x.y";buf = '0' + versionMajor;buf = '0' + versionMinor;(Note the single quotes around the '0', not double quotes. The single quotes turn the character into its ASCII value.)

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 03:38:48 am
content: Doing it this way:Code:strcpy(lcd_buffer,"Version x.y");lcd_buffer = version_major + 0x30;lcd_buffer = version_minor + 0x30;lcd_out(lcd_buffer);Results in no real code bloat at all.

author: IanB
author_rank: Super Contributor
timestamp: March 12, 2013, 03:40:06 am
content: Quote from: c4757p on March 12, 2013, 03:34:35 amYup, good catch.The string "hello" is stored in memory as the byte sequence ('h', 'e', 'l', 'l', 'o', 0x00). The null byte is the end of string marker. String functions like lcd_out() or printf() look for this marker so they know when to stop. If you build your own character arrays you must make sure to put the null byte after the last position (many library routines like strcpy do this for you automatically, but watch out for the exceptions).

author: Psi
author_rank: Super Contributor
timestamp: March 12, 2013, 03:42:37 am
content: If you're writing to a LCD panel it will be a fixed number of characters anyway.So you could use a fixed length array of char.Then you don't have to worry about lengths

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 03:45:24 am
content: Even more efficient:Code:lcd_outstr("Version ");lcd_char(version_major + 0x30);lcd_char('.');lcd_char(version_minor + 0x30);

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 03:47:46 am
content: Quote from: Psi on March 12, 2013, 03:42:37 amI have declared the lcd_buffer as:Code:unsigned charlcd_buffer;As I assumed I needed the extra byte for the null terminator in addition to the actual 16 characters (max) for the display.

author: Psi
author_rank: Super Contributor
timestamp: March 12, 2013, 03:51:50 am
content: yeah, if you want to manipulate it with the C string handling routenes you need the null on the end.But you dont "have" to use those.Since its just an array of numbers (like in delphi.. var lcd_buffer : byte; ), you can do stuff to it directly as ascii numbers.etc. you can copy or set the individual character positions, or the entire array using memcpy/memset and then you dont need the null and you don't get any overhead from the string funtions

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 03:53:38 am
content: If I were using a character array to represent a "physical character array", I'd probably treat it like a true array rather than a string and skip the null. Can't really see why I would need to use string functions on something like that anyway.

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 03:57:26 am
content: My lcd output routine was adapted from something I found on the 'net:Code:void lcd_outstr(const char *buffer){while(*buffer) // Write data to LCD up to null{lcd_char(*buffer);// Write character to LCDbuffer++; // Increment buffer}}Does that make sense with the method of calling it?

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 04:00:05 am
content: Whenever I start learning a new programming language I usually write butt-ugly code to start with and improve (optimise) it with practice.I'm not new to the concept of pointers, bytes, etc but was failing to recognise them in the C context.

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 04:00:29 am
content: Looks fine to me. Andthank you thank you thank youfor not using the oldlcd_char(*buffer++)trick. (Hopefully I didn't give anyone any ideas...)This might be a bit more conventional, though:Code:for ( ; *buffer; ++buffer) {lcd_char (*buffer);}(Yes, you can skip a section of thefor-loop definitions. Just omit it and leave the semicolon.)

author: Psi
author_rank: Super Contributor
timestamp: March 12, 2013, 04:04:51 am
content: Personally, i would forget pointers until you become familiar with C coding in general.They tend to complicate things and its real easy to make mistakes.You can make any program you want without having to use them.They just make some things faster and quicker to code, since you can pass the memory address instead of the actual data

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 04:07:05 am
content: I think I'm on the right track now thanks to you guys.One more question; If there is only one statement to execute in the for loop, are the braces around that statement still required?

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 04:08:15 am
content: Yeah, they do complicate things, but you can't really do much at all without them in C...Quote from: David_AVD on March 12, 2013, 04:07:05 amNope. I tend to use them because they save rearranging stuff if I need them later. Also, if your editor doesn't auto-indent, omitting them can be a recipe for disaster, but the ones I use all do.The technical explanation is that the contents of the loop is justone statementand no braces, and the braces and their own contents count collectively as one statement. You can also use them on their own, but that's not very useful.

author: Psi
author_rank: Super Contributor
timestamp: March 12, 2013, 04:09:18 am
content: Quote from: David_AVD on March 12, 2013, 04:07:05 amnope, you only need the braces for multiple statements.for (i=0;i<=10;i++) myfunction();

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 04:10:20 am
content: Cool. I thought the same "block rules" applied to C as I use in Delphi, etc (and it seemed to work) but thought I'd ask to be sure.

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 04:14:10 am
content: Delphi is Pascal, right? I haven't touched Pascal in ages, but I remember finding it strikingly similar to C.

author: Psi
author_rank: Super Contributor
timestamp: March 12, 2013, 04:16:36 am
content: On the subject of braces(blocks)Sometimes you may need to use them inside a case statement.You can't declare a new variable inside a case statement unless its inside a block.Code:Switch (foo){case 1:char mychar; <- doesnt work (you can't declare a new variable)mychar=5;break;case 2:{char mychar2; <- works finemychar2 = 10;break;}}However, since your used to Delphi (like me) its unlikely you'll ever want to declare variables in the middle of code.It can be useful though, your case statement might be based on the mode your device is in. Some modes might need lots of math done and require many variables be declared which are not needed in other modes.

author: IanB
author_rank: Super Contributor
timestamp: March 12, 2013, 04:16:42 am
content: Quote from: c4757p on March 12, 2013, 04:00:29 amDanger, Will Robinson! This code explicitly assumes that the array of characters in buffer is null terminated. Given earlier comments about the possible omission of the terminator when using 16 byte fixed length arrays, this code snippet could lead to disaster...

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 04:23:46 am
content: Quote from: IanB on March 12, 2013, 04:16:42 amBeen thinking of including a max length check into the loop, but still pondering how.

author: IanB
author_rank: Super Contributor
timestamp: March 12, 2013, 04:47:36 am
content: Quote from: David_AVD on March 12, 2013, 04:23:46 amThat's easy. Something like this:Code:#define LCD_BUFFER_SIZE 16void lcd_outstr(const char *buffer){const char *end = buffer + LCD_BUFFER_SIZE;for ( ; buffer < end && *buffer; ++buffer) {lcd_char (*buffer);}

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 04:51:23 am
content: Ah, you beat me to it. I almost had it working but was looking up the AND operator.

author: c4757p
author_rank: Super Contributor
timestamp: March 12, 2013, 05:14:56 am
content: Quote from: IanB on March 12, 2013, 04:16:42 amTrue, I was just working with what he already had. If I were treating it like an array I would use a normal length-bounded array iteration. Didn't notice the inconsistency.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 12, 2013, 06:19:23 am
content: Quote from: David_AVD on March 12, 2013, 01:21:50 amCode:#include <stdarg.h>#include <stdio.h>// returns number of characters printedint lcd_printf(const char *format, ...) {va_list args;int count;char buffer; // pick a reasonable sizeva_start(args, format);count = vsnprintf(buffer, sizeof(buffer), format, args);va_end(args);const char *p = buffer;while (*p) lcd_char(*p++);return count;}PS. Not guaranteed to work on rinky-dink development systems. ;-)PPS. Does your lcd_char() handle CR and LF correctly?

author: andersm
author_rank: Super Contributor
timestamp: March 12, 2013, 07:45:56 am
content: Note that if you're not using printf for anything you can override the putch() function to redirect the output somewhere else.

author: David_AVD
author_rank: Super Contributor
timestamp: March 12, 2013, 08:49:39 am
content: I will be using serial comms in due course, but that's good to know anyway.

author: dfnr2
author_rank: Regular Contributor
timestamp: March 12, 2013, 06:53:21 pm
content: To answer your original question, there is no stdout. the printf() function you get with xc8 calls putch(), which you can define. So if you rename your lcd_char() function to putch(), then calling printf will print to the lcd.Having said that, printf() is usually not used with these tiny chips. You can put it in if you have lots of space, but as people have indicated, writing your own number->string functions is much more efficient. For what you're doing, copying strings, etc. is unwarranted, and way too busy. Better to have little routines tailored to your output. For example, for an 8-bit unsigned, you could use a dedicated routine such as print_dec_8(uint8), like so:uint8 val;val = some_stuff();lcd_print("The number is: ");lcd_print_dec_8(val);...Code:/* PROCEDURE:lcd_print_dec_u8* INPUTS: val (uint8): 8-bit unsigned value to be printed* OUTPUTS: none** DESCRIPTION: Prints argument, in decimal, to the serial port, using* lcd_char().*/void lcd_print_dec_u8(uint8 val){uint8 hundreds = 0;/* If > 100, only two choices for most sig digit. */if (val >= 200) {hundreds = 2;val = val - 200;lcd_char('2');} else if (val >= 100) {hundreds = 1;val = val - 100;lcd_char('1');}/* calculate the tens digit, if present, and print.*/if ((0 != hundreds) || (val >= 10)) {uint8 tens = 0;while (val >= 10) {val -= 10;tens++;}lcd_char(tens + '0');}lcd_char(val + '0');}

author: andersm
author_rank: Super Contributor
timestamp: March 12, 2013, 08:37:57 pm
content: The XC8 compiler will actually generate an optimized printf based on the format strings used, so the results should be more compact than a typical library implementation. Doing the work manually may still be more efficient, but it depends on usage.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 12, 2013, 10:58:33 pm
content: Quote from: dfnr2 on March 12, 2013, 06:53:21 pmPardon me, but: Yuck.Re-inventing the wheel and all that.

author: grumpydoc
author_rank: Super Contributor
timestamp: March 12, 2013, 11:02:37 pm
content: QuoteAgree, and once you've called (v)(s)(n)printf a few times the overhead will start to look cheap given the convenience.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 12, 2013, 11:31:33 pm
content: Quote from: grumpydoc on March 12, 2013, 11:02:37 pmTouché.

author: melonstorm
author_rank: Newbie
timestamp: March 13, 2013, 09:06:27 pm
content: There are also size-reduced implementations of (s)printf available, likethis LGPL-licensed one. If you need sprintf in your project and don't want to use the standard library's comparatively large implementation, you could choose to use something like this.

author: David_AVD
author_rank: Super Contributor
timestamp: April 15, 2013, 05:21:49 am
content: My next issue is not getting the leading zeros like I though this would:Code:sprintf(lcd_buffer,"%3d",sw_result);I thought this should give me a string "000" ... "255", but it seems to still be "0" ... "255" (no leading zeros).

author: IanB
author_rank: Super Contributor
timestamp: April 15, 2013, 05:32:31 am
content: Quote from: David_AVD on April 15, 2013, 05:21:49 amHave you tried "%3.3d" as the format string?

author: ecat
author_rank: Frequent Contributor
timestamp: April 15, 2013, 05:38:22 am
content: Some printf implementations provide only limited options, still the format specifier for 001, 002 etc is "%03d"printf("%03d", 5);Should result in 005

author: David_AVD
author_rank: Super Contributor
timestamp: April 15, 2013, 05:39:55 am
content: Quote from: IanB on April 15, 2013, 05:32:31 amAh, that's got it. I read the XC8 (and other) docs several times and didn't understand that was how it worked. Thanks.

author: David_AVD
author_rank: Super Contributor
timestamp: April 15, 2013, 05:40:35 am
content: Quote from: ecat on April 15, 2013, 05:38:22 amShould, but doesn't for some reason. The "%3.3d" does work however.

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 12:09:18 am
content: Quote from: David_AVD on March 12, 2013, 03:22:22 amDavid, yeah it is worth the trouble. C is a great language to develop program with. Do watch out, with C, it doesn't protect you against yourself.To quote someone: "If it is absolutely dummy proof, it would be in a form only absolute dummy will use."With C, it is not dummy proof, you have to watch you are doing, but it let you do what you want to do and assumes you really know what you are doing.Rick

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 12:34:23 am
content: Quote from: David_AVD on March 12, 2013, 03:57:26 amDavid_AVD,Using this as an opportunity to show you why C is powerful but you have to watch out.First, incase you don’t know the two cases of using ++.int counter=0;int another;another = ++counter; // here counter is incremented before assigning to variableanother = counter++; // here counter is incremented after assigning to variableThe while() statement you have does not check buffer size. A better implementation is:void lcd_outstr(const char *buffer){int ii=0;int maxSize=100; // hard coded max, but can be parameter drivenwhile(*buffer && ++ii<maxSize) // Write upto terminator or maxSize bytes of data to LCD{lcd_char(*buffer); // Write character to LCDbuffer++; // Increment buffer}}I added a counter so if a proper string terminator is NOT found, it will not output more than 100 characters. Lacking the counter and if the string is not properly terminated, it will just keep printing until the CPU hangs or when other bad things happens.Also, I never use single character variables. If you have “int i;”, generic string search is useless. You will find tons of ‘i’ unless you use full word/variable-name type of search. If you use ‘ii’ or ‘jj’ or ‘zz’, they are much easier to find.Rick

author: David_AVD
author_rank: Super Contributor
timestamp: May 11, 2013, 12:52:07 am
content: Good stuff Rick. Thanks.

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 12:57:30 am
content: Quote from: David_AVD on March 12, 2013, 04:07:05 amDavid_AVD,c4757p already pointed out that you don't really need the { } except for multiple statements. An additional useful technique is comma which continues the calculation statement. When comma is used, the last statement has the final value and that is used.ii = 1, 2, 3, 4 ; // ii is assigned 4, the last evaluation doneoriginal:while (*buffer) {lcd_char(*buffer);buffer++;}another version with comma to make that one statement:while (*buffer) lcd_char(*buffer), buffer++;another version with for ( ; ; )for ( ;*buffer; lcd_char(*buffer), ++buffer ) ;Explainationfor ( ;// this first semicolon with stuff between ( ; means no initialization neededfor ( ; *buffer;// between the first and second semicolon is the condition to// remain looping, so here is checking to see if current buffer char is// not null before continue loopfor ( ;*buffer; lcd_char(*buffer), ++buffer )// after the last semicolon, there is the end of a loop closing statement// before end of the loop, do the lcd_char. Since it is a comma, it continue// the calculation and do the buffer pointer increment to point at the next charfor ( ;*buffer; lcd_char(*buffer), ++buffer ) ;// the semicolon after the ) saids I have an empty statement// there is nothing to left to doNow one with size limit:int ii,newSize;for (ii=0, newSize=100 ; *buffer & ii<newSize; ii++, lcd_char(*buffer), ++buffer);I leave it for you to figure out the intialization, the continue-loop checking, and the loop end check.Good luck with your C learning. It will be a fun ride.By the way, note this joke, it highlights some C programming facts:When a C programmer brought a dozen eggs, how many eggs did he just buy?The answer is 11.0 , 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11Only 11. C programmer always start counting with zero.Rick

author: Rufus
author_rank: Super Contributor
timestamp: May 11, 2013, 02:02:18 am
content: Quote from: Rick Law on May 11, 2013, 12:57:30 amI have been writing C for nearly 30 years and don't remember the last time I used a comma operator. I might have kludged an extra assignment into a for statement or macro using it but I can't think of an example. It is hardly useful.I would recommend keeping braces around single statements because a consistent format is easier to read and forgetting to add braces if you add another statement screws up so badly without looking obvious.

author: c4757p
author_rank: Super Contributor
timestamp: May 11, 2013, 02:10:38 am
content: The only use that I can see for a comma operator is combining expressions in a for loop. (I wouldn't really call it a kludge. I've seen some very clear and elegant uses of that to emulate more sophisticated looping systems like in higher-level languages, and they didn't seem kludgey at all.) The only other use I can think of for the comma operator is intentional obfuscation.Quote from: Rufus on May 11, 2013, 02:02:18 amI disagree. If it doesn't look obvious when you forget to add braces you need to use a better editor. There's nothing wrong with doing it if you find that it makes future extension easier, but I often find myself making enough single-statement blocks that it saves me more time to omit them than otherwise. Obviously, the time saved either way is minuscule.

author: megajocke
author_rank: Regular Contributor
timestamp: May 11, 2013, 02:19:06 am
content: Quote from: Rick Law on May 11, 2013, 12:57:30 amActually, that's not the result. Assignment has higher precedence than the comma operator, so this is the same as doing:(ii = 1), 2, 3, 4 ;which has the result that ii is assigned the value of 1, whereafter the expressions 2, 3 and 4 are evaluated with the results being thrown away, essentially becoming no-ops. The compiler might give you a warning that you have expressions without side effects if compiling this code.However, if you writeii = (1, 2, 3, 4) ;you get the result claimed before.

author: Rufus
author_rank: Super Contributor
timestamp: May 11, 2013, 02:37:41 am
content: Quote from: c4757p on May 11, 2013, 02:10:38 amMy editor of choice adds braces as I type so I would have to delete them for single statements. Time is insignificant, screen space less so but I value consistent appearance over space.As for for loops they are just shorthand for a commonly used form of while statement, kludging less common forms of while into them with comma operators is more confusing than just using while.

author: glatocha
author_rank: Regular Contributor
timestamp: May 11, 2013, 03:01:39 am
content: I am always scared to use library functions.They might cover much more than I need and cause a high memory usage.I prepare most of my routines on myself. For example for LCD Integer display:Code:void LCD_Int(int16_t num){int16_t div;uint16_t i;if (num <0){LCD_SendCHAR_4b('-');num = num * -1;}for (i=10000;i>=10;i=i/10){if (num>=i){div = num/i;num = num%i;LCD_SendCHAR_4b(div + '0');}}LCD_SendCHAR_4b(num + '0');}Same I have for doubles.I am also planning to rewrite most of it in asm and maybe get rid of dividing.I started programming with atmel 2051 with 2kb Flash and 128 RAM bytes. So still have in mind always optimize.But maybe nowadays much cheaper is to just update the chip with higher memory version of 0,10$.

author: c4757p
author_rank: Super Contributor
timestamp: May 11, 2013, 03:08:23 am
content: Quote from: Rufus on May 11, 2013, 02:37:41 amI've always seen a "for" loop as being iterative over a sequence, and a "while" loop as being repetitive conditional on an external state - for example,whilethe input given is invalid, continue giving an error message and requesting it again. I'd rather use a "for" loop to loop over some sort of container, even if the top of the loop is a little bit ugly, as it makes the intention clear.

author: IanB
author_rank: Super Contributor
timestamp: May 11, 2013, 03:12:04 am
content: Quote from: Rick Law on May 11, 2013, 12:34:23 amA good practice when writing code is to replay the code in your mind and see if it does what you think it ought to do.In the above example, suppose the buffer has size 1 (a thought experiment). What will happen?First pass through the loop:bufferpoints to the one and only character so the while() test is successful and the character is printed.Second pass through the loop:bufferwas incremented and now points beyond the end of the buffer. It will print some unknown character to the LCD.Note how the++ii<maxSizetest will not save it, since the&&operator short circuits and if the first condition is true the second condition will never be evaluated. If this logic fails with a buffer of size 1, it will fail with a buffer of size 100.Another goal of good code is to be clear and readable, so that human readers who encounter the code later on are readily able to understand what it is intended to do. In light of that, I suggest this variant:Code:void lcd_outstr(const char *buffer, int buflen){const char *endbuf = buffer + buflen;while(buffer < endbuf && *buffer){lcd_char(*buffer);++buffer;}}

author: c4757p
author_rank: Super Contributor
timestamp: May 11, 2013, 03:23:21 am
content: And I would suggest this variant:Code:void lcd_outstr(const char *buffer, size_t buflen){size_t i;for (i = 0; i < buflen && buffer; ++i) {lcd_char (buffer);}}Or if you're tight on RAM in a microcontroller application, slightly less readable but saving a variable (that the compiler ought to optimize out anyway, but some compilers suck):Code:void lcd_outstr(const char *buffer, size_t buflen){for (i = 0; buflen && *buffer; ++buffer, --buflen) {lcd_char (*buffer);}}I wouldn't really recommend the second one, but it's notawful, I suppose.

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 03:34:54 am
content: Quote from: megajocke on May 11, 2013, 02:19:06 amYup, you are right... Forgot about the () there.

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 03:37:14 am
content: Quote from: IanB on May 11, 2013, 03:12:04 amGood pick. Goes to show, a quick line of code merely to show a point should still be carefully reviewed.Edit:Edited the quote to just keep the part my "Good pick" was referring to. The other point I did not agreed (which is in another comment that I just made).

author: Rufus
author_rank: Super Contributor
timestamp: May 11, 2013, 03:43:44 am
content: Quote from: IanB on May 11, 2013, 03:12:04 amLike most string functions it is intended to output zero terminated strings. The buffer has no size. The string size is from the start of the buffer to where ever the zero terminator is. The maxSize check is to prevent overflow of the LCD (which may or may not be required).

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 03:51:40 am
content: Quote from: c4757p on May 11, 2013, 03:08:23 amI useforoverwhileout of habit. Prior to optimization compilers, I read thatfor( ; ; )is far more efficient overwhile(1). After confirming it by looking at the op codes actually generated, I confirmed that for my compiler do indeed generate much better code withfor( ; ; )overwhile(1).Where I am doing a single task within the loop, I found the comma-inside-for much easier to read. Single (or very simple) looped task such as scanning for a particular case.for (cp=startOfSomething,kp=endOfSomething;TestForEnd(cp);callthis(),prepareToReLoop) ;Another case where I would use comma is to seperate substeps:thisOne=(thatOne++,firstFunction(&thatOne));A matter of style and habit, I suppose.

author: kfitch42
author_rank: Frequent Contributor
timestamp: May 11, 2013, 03:53:24 am
content: Quote from: c4757p on March 12, 2013, 01:25:57 amsnprintf helps, but it does not absolve you from buffer related bugs. The biggest problem with snprintf is that it DOES NOT guarantee null-termination. If it ends up truncating (to avoid buffer overflow) the buffer will not be null-terminated. it is the responsibility of the caller of snprintf to check the return value and take appropriate action in case of truncation. In reality, most coders forget this.P.S. I can't count the number of times I have seen off-by-one-errors when using snprintf and friends...

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 03:59:29 am
content: Quote from: IanB on May 11, 2013, 03:12:04 amA matter of style and system. Pointer arithmetic makes the code difficult to port. I started coding C when pointers could be 12 bits (crazy segment:offset of the 8088/8086), 16 bits, or 32 bits. Once you do pointer arithmetic, you are relying on certain method of type-conversion.When I do an int as a counter, int is defined as system default word size. It was 8 bit signed with my first C compiler, 16 later, then 32. When I am counting something up to just 100, using an int will cause less portability issues in my experience.

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 04:06:46 am
content: Quote from: c4757p on May 11, 2013, 03:23:21 amI generally prefer to use *buffer with buffer++ over bufferwith i++. Another style and taste thing.I think we are either going to scare David_AVD from coding C pretty soon showing him a million way to skin a cat, or he is going to get very excited to try coming up with his million&1 way to skin the cat.

author: Rufus
author_rank: Super Contributor
timestamp: May 11, 2013, 04:11:20 am
content: Quote from: Rick Law on May 11, 2013, 03:59:29 amThis code is more stylish and will run much faster (apparently)...Code:void lcd_outstr(const char *buffer, int buflen) {const char *endbuf;for(endbuf=buffer+buflen;buffer<endbuf&&*buffer;lcd_char(*buffer),++buffer);}

author: glatocha
author_rank: Regular Contributor
timestamp: May 11, 2013, 04:14:19 am
content: My God. In which way it is more stylish?That's why I don't like C. The fight for putting as less code as possible.Somehow it always takes my much more time to think what I want to write then typing itself. So I don't really care how small the source code is. I would rather prefer more easy reading.

author: Rufus
author_rank: Super Contributor
timestamp: May 11, 2013, 04:19:52 am
content: Quote from: glatocha on May 11, 2013, 04:14:19 amIn the way that it takes the piss out of some of the other posts in this thread

author: c4757p
author_rank: Super Contributor
timestamp: May 11, 2013, 04:22:43 am
content: If you want to make a real piss-taking abomination you have to use the unholy combination *buffer++.

author: Rufus
author_rank: Super Contributor
timestamp: May 11, 2013, 04:25:03 am
content: Quote from: c4757p on May 11, 2013, 04:22:43 amBut then it wouldn't need a comma operator

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 04:28:29 am
content: Quote from: Rufus on May 11, 2013, 04:11:20 amI can see it could run faster. But I have a habit of avoiding pointer arithmetic unless absolutely necessary. I hate codes that fail when compiled with different flags. If memory serves, I think Microsoft C had small, large, and huge memory model. The char * changes to char FAR * or char HUGE *. Use the wrong slash parameter and the code goes south because what you assume would fit into an int doesn't fit anymore. So, I do pointer arithmetic only when there is a meaningful advantage.

author: c4757p
author_rank: Super Contributor
timestamp: May 11, 2013, 04:30:58 am
content: I've actually seen it used in a very unreadable way in code that was called "good" with a straight face. Of course, I'm still trying to figure out whether the circus of coding behind the GNU project is actually a very elaborate Monty Python skit that we programmers are being unknowingly compelled to participate in...

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 04:37:34 am
content: Quote from: glatocha on May 11, 2013, 04:14:19 amActually, it is not what is more stylish in the sense of what look better. It is a personal style of work - like if you want your pencils to your left or to your right.For me personally, I want to fit more code on the screen. When more code is visible, it helps me grasp what is going on better. Where I have a loop that does something simple, rather than having it being 4 or 5 lines of empty space with a few words in it. I rather see it condensed into a single complex line. My mind can move on to: ok, in this loop, there is this task to scan for tab after comma, and then next is blah blah blah.With and empty { on one line, then buffer++ on another... I can press down arrow 16 times and I am still reading the same conceptual idea whereas with a single complex line I can see many more conceptual task being done on a single screen.

author: David_AVD
author_rank: Super Contributor
timestamp: May 11, 2013, 05:04:19 am
content: Assembler is looking more attractive with every post.

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 06:12:43 am
content: Quote from: David_AVD on May 11, 2013, 05:04:19 amAhem, don't let us scare you off.Once you get a handle of it, you will/can develop your own style and ignore all those comments made. The wide spread of opinion shows there are so many ways in C to accomplish the tasks and you will find the way best suits you. We all settle into a mode that fits us best. To say "this way works for me and should do it this way too" is like saying "can't see? wear my glasses, it works for me well and you too should do just fine with mine."I am a little rusty with my C. Arduino is not something I yet played with, so the code segments I show may not fit Arduino well.If you are familiar with assembler already, C is easy. I like assembler, but I rather debug C over debugging assembler any day.Play with it. Worst case is, you walk off a more knowledgeable man.Rick

author: Bored@Work
author_rank: Super Contributor
timestamp: May 11, 2013, 07:17:45 am
content: Quote from: Rick Law on May 11, 2013, 06:12:43 amA good way to make sure you aren't usable in a larger team. A diva with an own style is one of the worst things you can have in a larger team. Either people pull all in the same direction or you quickly end up with a heap of junk of a code base.My experience with programmers venting their own coding spleen is they often use this, and defend this vigorously, to hide their incompetence. Especially in C there is a huge amount of an existing body of knowledge and conventions. Good, experienced, professional C programmers often don't even need a formal coding convention document. They can agree on one within minutes, or if there is some existing code they can pick up the project's existing conventions by just reading a few pieces of code.By the way, your statements about pointer arithmetic are simply wrong.

author: amyk
author_rank: Super Contributor
timestamp: May 11, 2013, 12:45:42 pm
content: Quote from: Rufus on May 11, 2013, 04:11:20 amEven with compiler optimisation? That's not unheard of, and is the reason whyAsm still has its place.As an experienced C programmer I'd write this:Code:void lcd_outstr(const char *buffer, int buflen) {while(buflen-- && *buffer)lcd_char(*buffer++);}

author: TerminalJack505
author_rank: Super Contributor
timestamp: May 11, 2013, 01:02:10 pm
content: Quote from: glatocha on May 11, 2013, 03:01:39 amWhen you are doing both a division and a modulus of a particular number you should keep the C library function div() in mind. Especially on lower-end MCUs. The div() function will calculate both the quotient and remainder so itcouldsave you some clock cycles.Of course you'll always want to check the compiler's output in both cases since compilers can be pretty clever.

author: c4757p
author_rank: Super Contributor
timestamp: May 11, 2013, 01:16:49 pm
content: Quote from: Bored@Work on May 11, 2013, 07:17:45 amWhen you have an existing codebase, the style should always be conformity, and when you are starting one in a team, a style should always be chosen and agreed upon. For hobby stuff, though, it's definitely better to develop your own style and stick with it than to waste hours reading arguments and nerd rage about how every style out there is supposedly objectively wrong.

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 06:12:04 pm
content: Quote from: c4757p on May 11, 2013, 01:16:49 pmCan't disagree with that. But David_AVD clearly is not developing profession code in a team. So he needs to develop style best fit him.

author: Rick Law
author_rank: Super Contributor
timestamp: May 11, 2013, 06:29:50 pm
content: Quote from: Bored@Work on May 11, 2013, 07:17:45 amBoard@Work, David_AVD is NOT working in a team. He is just exploring for his own.I don't think you are relating to someone who merely is doing it for recreation...Re: I am incorrect about pointer arithmeticYou are right for the worldtodaybut I was not referring to the world today. What I said was Ideveloped that habit when it wasdifficult to port pointer arithmetic.I was working with compilers that ran on a 50k floppy. I was porting to a different compilers that ran on 360k floppy. That was a different world then. Back then, floppies doesn't even interchange. You have "soft sector floppies" and "hard sector floppies". Hell, the compiler even has to load to specific memory addresses. It was a different world then.

author: David_AVD
author_rank: Super Contributor
timestamp: September 30, 2013, 06:27:55 am
content: Sorry for reviving my old thread, but thought it better than create a new one as this is related.I can't seem to work out the code to format a string given more than one variable.I have minutes and seconds in two uint8_t variables that I want to present as a string in the "mm:ss" format, with zeroes instead of spaces for padding. (eg 00:05 for 5 seconds)After looking at many C functions, I can't see how to give any of them more than one argument. A nudge in the right direction would be appreciated. Thanks.

author: IanB
author_rank: Super Contributor
timestamp: September 30, 2013, 06:34:31 am
content: You would wantprintf(print directly to stdout) orsprintf(write formatted text to a string in memory).For example, to print your time string you might use something like:Code:printf("%2.2u:%2.2u", (unsigned int)minutes, (insigned int)seconds);Here % introduces a format specifier, 2.2 specifies a field width of 2 with a precision of 2 (i.e. 2 digits printed), and u specifies an unsigned decimal integer.

author: David_AVD
author_rank: Super Contributor
timestamp: September 30, 2013, 06:52:44 am
content: The warning I get with that is: "incompatible implicit declaration of built-in function 'sprintf' [enabled by default]".I should mention this is with AVR GCC, not XC8. In hindsight I should have started a new thread.The code is:Code:sprintf(Buttons.Caption,"%u2.2:%u2.2",(unsigned int)mins,(unsigned int)secs);

author: amyk
author_rank: Super Contributor
timestamp: September 30, 2013, 07:29:00 am
content: What is the type of Buttons.Caption ? sprintf is declared as taking something like (char*, char *, ...)It's a variadic function - that means it can take a variable number of parameters, and in this case the 2nd one - the format string - gives meaning on how to interpret the parameters after it.Also your format string should be %2.2u and not %u2.2 - the letter ends the conversion specifier so you're going to get e.g. "122.2:52.2" instead of "12: 2".

author: IanB
author_rank: Super Contributor
timestamp: September 30, 2013, 07:35:07 am
content: Quote from: amyk on September 30, 2013, 07:29:00 amJust FYI, a format specifier of %2.2u should print 2 as "02" with a leading zero. With that format you get a field width of 2 with 2 digits printed.

author: Galaxyrise
author_rank: Frequent Contributor
timestamp: September 30, 2013, 08:20:14 am
content: %02u will also give you at least two digits, 0-prefixed if needed. I like doing it that way since that's also how you 0-prefix floats.printf-style formatting does take some getting used to, but it's quite powerful and as such has been implemented in quite a few languages now.Quote from: David_AVD on September 30, 2013, 06:52:44 amThe compiler encountered a call to sprintf without being told what sprintf would look like. That's normally ok because implicit declarations are enabled, but the compiler has knowledge of sprintf built in and that built-in prototype isn't compatible with the arguments you're passing it. So the compiler hopes you know what you're doing. I would guess that warning means the built-in behavior won't run, and instead a call to a real function "sprintf" will be generated.I'm not sure what the implicit declaration of sprintf looks like, but it doesn't have to be implicit. To get the explicit declaration of sprintf, #include <string.h> Once the compiler has an explicit declaration, the warning will likely turn into an error (something like "could not convert parameter 2 from char const * to char *")

author: dannyf
author_rank: Super Contributor
timestamp: September 30, 2013, 10:27:48 am
content: QuoteInclude the header file where sprintf() is prototyped.

author: David_AVD
author_rank: Super Contributor
timestamp: October 01, 2013, 12:50:32 am
content: Thanks again guys. Those forgotten include files will be the death of me.The following is what I ended up with and it works well.Code:unsigned int mins, secs;mins = ExtendedTime / 60;secs = ExtendedTime % 60;sprintf(Buttons.Caption,"%2.2d:%2.2d",mins,secs);

author: ve7xen
author_rank: Super Contributor
timestamp: October 02, 2013, 05:30:43 am
content: This particular string will always be 5 characters long, so you could just generate it with assignment:Code:Buttons.Caption = '0' + (ExtendedTime / 60) / 10;Buttons.Caption = '0' + (ExtendedTime / 60) % 10;Buttons.Caption = ':';Buttons.Caption = '0' + (ExtendedTime % 60) / 10;Buttons.Caption = '0' + (ExtendedTime % 60) % 10;Buttons.Caption = '\0'; // if necessary

author: David_AVD
author_rank: Super Contributor
timestamp: October 02, 2013, 06:47:11 am
content: Quote from: ve7xen on October 02, 2013, 05:30:43 amThat's pretty much exactly how I did it before asking. I did want to learn how to do it the sprintf way for other uses though.

author: dannyf
author_rank: Super Contributor
timestamp: October 02, 2013, 10:11:48 am
content: Generally, printf is costly in speed and execution and should be avoided, unless you are already using it.My preference is to use itoa() or ltoa() or their variants - they are implemented on most compilers and even if they aren't, they are simple to write your own.Be careful however that some implementations of itoa() / ltoa() are done through sprintf().