doragasu 0 Posted February 12, 2013 Share Posted February 12, 2013 I'm trying to get printf() to work in my newlib-based setup. I followed the instrutions here. I'm using the same code for syscalls, and almost the same linker script. Just added a ALIGN(8) directive before the heap label, and changed the way the stack is defined to a (at least for me) cleaner one. I have checked with both the debugger, and browsing the .map file that linker script works as expected, and if I build the test program in the tutorial, it behaves exactly the same, giving me exactly the same memory addresses and usage. But when I try to integrate it in a project I'm developing, calling printf() somehow works: the text is written to the UART. But as a side effect, memory gets corrupted . I have tested _sbrk(), and it works perfect. This function is called twice, and each time it grows the heap the requested amount and returns the right pointer to allocated memory. I have even tried replacing the _write() stub with an empty one (just a "return len;" sentence), and it behaves the same: when I call printf(), memory gets corrupted. If I remove the printf() calls from my code, no memory corruption occurs. I have also tried increasing the stack up to 16 KiB without luck. I have been the last 3 days trying to debug this. I just can't make it work. Any suggestions? Could it be a bug in newlib? Quote Link to post Share on other sites
Bingo600 0 Posted February 12, 2013 Share Posted February 12, 2013 Mayge you could get some hints here http://www.embedded.com/story/OEG20011220S0058http://www.embedded.com/story/OEG20020103S0073 http://www.embecosm.com/appnotes/ean9/ean9-howto-newlib-1.0.html /Bingo Quote Link to post Share on other sites
doragasu 0 Posted February 13, 2013 Author Share Posted February 13, 2013 Thanks for the tips. I have checked them, but unfortunately still found no solution to my problem Quote Link to post Share on other sites
doragasu 0 Posted February 13, 2013 Author Share Posted February 13, 2013 I assumed snprintf() would have the same memory corruption problem, but I have just tested it and it's working! Why would printf() corrupt the memory and snprintf() work? EDIT: debugged a bit more, and I have noticed sprintf() does not call _sbrk(), but printf() does. So there must be a problem with _sbrk()... Quote Link to post Share on other sites
doragasu 0 Posted February 18, 2013 Author Share Posted February 18, 2013 I thought I solved the problem when I changed the toolchain to gcc-arm-embedded. The problem was minimized but it's not completely gone. Anyway, I have switched to sprintf + custom function for sending text through the UART... Quote Link to post Share on other sites
spirilis 1,265 Posted January 26, 2014 Share Posted January 26, 2014 Taking a look at this stuff myself right now. Looks like the default linker script doesn't define the heap or stack, and the stack in startup_gcc.c is just a 64-word array... I had managed to get newlib printf working on the Renesas RX 32-bit platform once before (using the Okaya LCD onboard the RDKRX62N as the output target) so I have the newlib stubs already working from there, just need to add proper malloc and stack support I guess. I'm using the launchpad.net ARM none-eabi-gcc binaries for my install. Is it necessary to define my own _sbrk()? I didn't have to do that for the RX. Quote Link to post Share on other sites
spirilis 1,265 Posted January 28, 2014 Share Posted January 28, 2014 woot! Was successful in getting it working! Code coming soon Quote Link to post Share on other sites
spirilis 1,265 Posted January 28, 2014 Share Posted January 28, 2014 alright, I got this. First, attaching my code- tiva1202_v0.1.zip Github too- https://github.com/spirilis/tiva1202 This guide was a huge help: http://eehusky.wordpress.com/2012/12/17/using-gcc-with-the-ti-stellaris-launchpad-newlib/ The linker script that comes with TivaWare (at least one of the examples; didn't check to see if there were any more-elaborate ones) needs some work, namely, to support the concept of a heap and another symbol that Newlib needs for malloc() use in fprintf et al. Here's my script: MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000 } _stack_size = 4K; SECTIONS { .text : { _text = .; KEEP(*(.isr_vector)) *(.text*) *(.rodata*) _etext = .; } > FLASH .data : { _data = .; *(vtable) *(.data*) _edata = .; } > SRAM AT > FLASH .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } > FLASH __exidx_start = .; .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } > FLASH __exidx_end = .; .bss : { _bss = .; *(.bss*) *(COMMON) _ebss = .; } > SRAM _heap_bottom = .; _heap_top = ORIGIN(SRAM) + LENGTH(SRAM) - _stack_size; _stack_bottom = _heap_top; _stack_top = ORIGIN(SRAM) + LENGTH(SRAM); } Defining stack_size = 4K helps me move from the default paradigm of having the "stack" being a 64-word global array to it being up at the top of RAM by default, and that variable is used to determine the maximum size of the heap too. The .data definition was a bit weird; default TivaWare linker script had .data defined with : AT <some weird shit> instead of it being .data : { blahblah } > SRAM AT > FLASH. Without that, the .ARM.exidx stuff fails. Finally, with all this in place, startup_gcc.c needs modification to use _stack_top as the initial stack pointer entry for the interrupt vector table. Default startup_gcc.c has this going on: //***************************************************************************** // // Reserve space for the system stack. // //***************************************************************************** static uint32_t pui32Stack[64]; //***************************************************************************** // // The vector table. Note that the proper constructs must be placed on this to // ensure that it ends up at physical address 0x0000.0000. // //***************************************************************************** __attribute__ ((section(".isr_vector"))) void (* const g_pfnVectors[])(void) = { (void (*)(void))((uint32_t)pui32Stack + sizeof(pui32Stack)), // The initial stack pointer ResetISR, // The reset handler NmiSR, // The NMI handler FaultISR, // The hard fault handler IntDefaultHandler, // The MPU fault handler IntDefaultHandler, // The bus fault handler IntDefaultHandler, // The usage fault handler ... where the stack is literally a 64-word entry in the global variable section. Changing to using _stack_top: //***************************************************************************** // // Reserve space for the system stack. // Disabled - we're defining stack_bottom and stack_top in the linker script for Newlib support. // //***************************************************************************** //static uint32_t pui32Stack[64]; //***************************************************************************** // // The vector table. Note that the proper constructs must be placed on this to // ensure that it ends up at physical address 0x0000.0000. // //***************************************************************************** extern uint32_t _stack_top; // Defined in the linker script __attribute__ ((section(".isr_vector"))) void (* const g_pfnVectors[])(void) = { (void (*)(void))((uint32_t) &_stack_top), // The initial stack pointer ResetISR, // The reset handler NmiSR, // The NMI handler FaultISR, // The hard fault handler IntDefaultHandler, // The MPU fault handler IntDefaultHandler, // The bus fault handler IntDefaultHandler, // The usage fault handler 0, // Reserved 0, // Reserved ... Have to comment-out the pui32Stack entry or else the compiler generates a pedantic-warning I believe. Now, for the newlib stubs. I call my newlib stub driver "devoptab" ... devoptab.c and devoptab.h. In my implementation, the user defines an array of "driver" entries that can be searched through to match the "filename" being passed to open() (by fopen(), for example). But first, here's what the "driver" entry struct looks like, along with the rest of "devoptab.h": #ifndef DEVOPTAB_H #define DEVOPTAB_H #include <reent.h> #include <errno.h> #include <stdint.h> #include <sys/types.h> typedef struct { const char *name; int (*open )( const char *path, int flags, int mode ); int (*close )( int fd ); int (*write )( int fd, const char *ptr, int len ); int (*read )( int fd, char *ptr, int len ); } devoptab_t; // Maximum # of file descriptors allowed #define MAX_FILEDES 16 #ifndef NEWLIB_STUBS_DISABLE // Global device driver list (file descriptors) extern const devoptab_t *devoptab_list[]; // Utility function for drivers to discover next-available file descriptor int devoptab_next_filedes(int prefer_fd); #endif #endif /* DEVOPTAB_H */ My devoptab.c driver includes the primitives, and #include's "nokia1202_devoptab.h" so it has access to the extern const devoptab_t's for the Nokia 1202 driver. #include <stdint.h> #include <stdbool.h> #include <reent.h> #include "devoptab.h" #include "nokia1202_devoptab.h" #include <string.h> #include <unistd.h> /* List of registered drivers implementing open, close, read, write primitives. * Last entry should be NULL. */ const devoptab_t *devoptab_list[] = { &devoptab_nokia1202, &devoptab_nokia1202_backlit, NULL }; // File descriptor map (maps to devoptab_list[] index to determine which driver handles it) static int devoptab_opens[MAX_FILEDES]; static uint32_t devoptab_init; /* Utility function usable by drivers to determine the next available file descriptor * This intentionally ignores STDIN, STDOUT, STDERR as functions which provide one of the those * are expected to automatically assume those file descriptors. */ int devoptab_next_filedes(int prefer_fd) { int fd; // Allow caller to use prefer_fd if it's available. if (prefer_fd >= 0 && prefer_fd < MAX_FILEDES && devoptab_opens[prefer_fd] < 0) { return prefer_fd; } for (fd = 3; fd < MAX_FILEDES; fd++) { if (devoptab_opens[fd] < 0) return fd; } return -1; } /* Newlib primitives for open, close, read, write */ int _write (int fd, const void *buf, size_t cnt) { if (fd >= MAX_FILEDES || devoptab_opens[fd] < 0) { errno = ENODEV; return -1; } return devoptab_list[devoptab_opens[fd]]->write(fd, buf, cnt); } int _read (int fd, void *buf, size_t cnt) { if (fd >= MAX_FILEDES || devoptab_opens[fd] < 0) { errno = ENODEV; return -1; } return devoptab_list[devoptab_opens[fd]]->read(fd, buf, cnt); } int _open (const char *file, int flags, int mode) { int which_devoptab = 0; int fd, idstrlen; char *idstr = (char *)file, idstr_tmp[128]; // Init catch if (devoptab_init != 0xDEADBEEF) { for (int i=0; i < MAX_FILEDES; i++) { devoptab_opens[i] = -1; } devoptab_init = 0xDEADBEEF; } /* Devices with /dev only match the "/dev/XXXXX" portion with the drivers, so a driver can implement * an open-ended filesystem underneath its /dev/XXXXX node. * The full path is still passed as the parameter to open(). */ if (!strncmp(file, "/dev/", 5) || !strncmp(file, "/sys/", 5)) { strncpy(idstr_tmp, file, 5); idstr = (char *)file+5; while (*idstr != '/' && (idstr-file) < 127) strncat(idstr_tmp, idstr++, 1); idstr = idstr_tmp; } idstrlen = strlen(idstr); do { if (strncmp(devoptab_list[which_devoptab]->name, idstr, idstrlen) == 0) { fd = devoptab_list[which_devoptab]->open(file, flags, mode); if (fd >= 0) devoptab_opens[fd] = which_devoptab; return fd; } } while (devoptab_list[which_devoptab++]); errno = ENODEV; return -1; } int _close (int fd) { if (fd >= MAX_FILEDES || devoptab_opens[fd] < 0) { errno = ENODEV; return -1; } int _origopen = devoptab_opens[fd]; devoptab_opens[fd] = -1; return devoptab_list[_origopen]->close(fd); } int _fstat(int fd, struct stat *buf) { return 0; } int _isatty(int fd) { if (fd <= STDERR_FILENO) return 1; errno = ENOTTY; return 0; } off_t _lseek(int fd, off_t offset, int whence) { errno = ENOSYS; return -1; } /* _sbrk() needed since default newlib install doesn't have it. * Needed for malloc() et all to work. * Looks like newlib DOES have malloc, free, realloc, etc... but it needs sbrk * to define its interface with the actual SRAM heap. */ static char *heap_end = 0; extern uint32_t _heap_bottom; extern uint32_t _heap_top; caddr_t _sbrk(unsigned int incr) { char *prev_heap_end; if (heap_end == 0) { heap_end = (caddr_t) &_heap_bottom; } prev_heap_end = heap_end; if (heap_end + incr > (caddr_t)&_heap_top) { return (caddr_t)0; } heap_end += incr; return (caddr_t) prev_heap_end; } I used the _sbrk() implementation from the link above just about unchanged. The rest of it is pretty self-explanatory; a list of file descriptors is maintained as devoptab_opens[], whose entries entail an index inside the devoptab_list[] array denoting which driver is handling that file descriptor. A static global is checked every time open() is called to enforce initialization of the table without the user having to run anything specific from their main() routine. Then the drivers' various open, close, read, write, etc. function calls are called. To be clear, the order of driver entries in devoptab_list[] has no bearing on the file descriptors that get assigned to those drivers. I suppose that'd be a simpler way to do it, but I wanted my driver to be more open-ended than that. The driver's open() function returns the file descriptor that actually gets used. And it's supposed to follow convention (with devoptab_next_filedes() as the gatekeeper so to speak; returning the next unused filedes or if prefer_fd >= 0, that filedes only if it's currently unclaimed). What I added that is interesting IMO is the way filenames are compared in open(). For anything other than a /dev/ type of filename, it just compares the whole string with what the driver registers under its ->name member. My Nokia 1202 driver registers "LCD" and "LCDBACKLIT" as possible filenames that match its driver. But for things under /dev/, it only tries to match "/dev/..../", so in theory device drivers can be written to respond to a hierarchical-filesystem type of design. That's as far as I've taken it for now though. I also added "/sys/" support sort've like the SYSFS in Linux. Haven't done anything with those 2 though. So with all that, the main program (nokia1202.c) looks like this: /* nokia1202.c */ // needed for driverlib rom stuff #define TARGET_IS_BLIZZARD_RA1 1 #include <stdio.h> #include <stdint.h> #include <stdbool.h> #include "inc/tm4c123gh6pm.h" #include "inc/hw_types.h" #include "inc/hw_memmap.h" #include "driverlib/sysctl.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/pin_map.h" #include "driverlib/gpio.h" #include "driverlib/ssi.h" #include "nokia1202_drv.h" #include "ste2007.h" #include "font_5x7.h" int main() { MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ); // 80MHz CPU; speed is calculated as 200MHz/SYSDIV // SPI, GPIO config for Nokia 1202 LCD MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); MAP_GPIOPinConfigure(GPIO_PB4_SSI2CLK); MAP_GPIOPinConfigure(GPIO_PB6_SSI2RX); MAP_GPIOPinConfigure(GPIO_PB7_SSI2TX); MAP_GPIOPinTypeSSI(GPIO_PORTB_BASE, (GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_7)); MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2); MAP_SSIConfigSetExpClk(SSI2_BASE, MAP_SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 4000000, 8); MAP_SSIEnable(SSI2_BASE); // Need at least 250ms wait for Nokia LCD to be ready for commands after power-on. ROM_SysTickPeriodSet(80000*125); ROM_SysTickIntDisable(); HWREG(NVIC_ST_CURRENT_R) = 1; ROM_SysTickEnable(); while (ROM_SysTickValueGet() < 10000) ; while (ROM_SysTickValueGet() > 10000) ; while (ROM_SysTickValueGet() < 10000) ; while (ROM_SysTickValueGet() > 10000) ; ROM_SysTickDisable(); // Display ready for init fopen("LCDBACKLIT", "w"); char mylcd_buf[128]; setvbuf(stdout, mylcd_buf, _IOLBF, 128); ste2007_contrast(12); printf("Totally cool! %g\n", 1.055); while(1) ; } The crap at the end between fopen() and ste2007_contrast() change the buffering; default is no-buffer on stdout I think, this adds a 128-byte char buffer and defines _IOLBF (Line-Buffered) mode. So when I do the printf(), since there is a "\n" in there, it comes up on the LCD display right away. But if I omitted the "\n", nothing would show up (cursor would remain at the upper left) unless I did an "fflush(stdout);" shortly after. Since my Nokia 1202 driver supports efficient writes to the display when it's passed a string > 1 byte long (makes all changes in framebuffer memory before committing them in one big burst of SPI data), using buffered behavior like this is preferred. Anyway, this was fun. Not sure what I'm going to do with it beyond that, but, when I come up with a hardcore Tiva application that I prefer to write in standard C... I'm probably going to leverage this newlib-stub stuff much harder. A good example of a second driver to add to this would be the LaunchPad's Backchannel UART registering itself as STDERR_FILENO. pabigot and bluehash 2 Quote Link to post Share on other sites
pabigot 355 Posted March 27, 2014 Share Posted March 27, 2014 (edited) BTW: I'm also working in this area and will be posting a related discussion once I've got everything fully understood, but a lot of what you've written is very true and complements the approach I'm taking. FWIW, default for stdin/stdout/stderr is line-buffered; you can use setvbuf() to disable it with newlib, but if you're using newlib-nano it doesn't work. You can follow that battle here. Update 20140411: The issues with newlib nano turn out to be an idiocy in the linker scripts provided by CMSIS for GCC. Significant space savings can be obtained by using -specs=nano.specs when linking, and setvbuf() works just fine as long as nobody's quietly linking in standard libc too. Edited April 11, 2014 by pabigot spirilis 1 Quote Link to post Share on other sites
doragasu 0 Posted April 11, 2014 Author Share Posted April 11, 2014 Great work! Too printf is this difficult to get to properly work :-P Quote Link to post Share on other sites
bluehash 1,581 Posted April 13, 2014 Share Posted April 13, 2014 Great work! Too printf is this difficult to get to properly work :-P ...but totally worth it in the end Quote Link to post Share on other sites
pabigot 355 Posted April 14, 2014 Share Posted April 14, 2014 BTW: I'm also working in this area and will be posting a related discussion once I've got everything fully understood, but a lot of what you've written is very true and complements the approach I'm taking. Took a few days longer than I expected, but after heroic keyboard banging I've got the basic documentation done for BSPACM's approach to linker scripts and newlib interfacing. Even if BSPACM isn't attractive people trying to use newlib could find useful information at: http://pabigot.github.io/bspacm/newlib.html As I may have mentioned, BSPACM uses a single linker script source file rather than requiring each application to have its own copy that has to be edited to add and remove exception handlers. This is done through the magic of weak symbols, as described here. The linker script and startup files are the ones ARM provides for Cortex-M development, so no TI hackery that will break gcc or newlib. There's also a highly reconfigurable system interface for newlib, including policies for dynamic memory management and file descriptor operations. For the latter the documentation for <bspacm/newlib/fdops.h> may be more relevant, as I didn't want to repeat myself. I'd like to have some diagrams showing how file handle data structures relate to descriptor tables and all that, but I can't get Enterprise Architect to parse function pointers out of the header to produce class diagrams so it ain't gonna happen. There's also only one device example now (using UART), but sometime in the next couple weeks there should be one for console display on Sharp Memory LCDs too. I'm pretty pleased with this framework, and already have seven ARM Cortex-M boards from two vendors working consistently with it. The key takeaway: this program: #include <stdio.h> void main () { printf("Hello, world!"); } with this Makefile: SRC=main.c AUX_CPPFLAGS+=-DBSPACM_CONFIG_ENABLE_UART=1 WITH_FDOPS=1 include $(BSPACM_ROOT)/make/Makefile.common runs as expected on all seven boards. (It's almost like it's platform independent....) Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.