Stendall 0 Posted March 26, 2014 Share Posted March 26, 2014 Hi. I'm trying to port a display library of mine for HT1632 display from Arduino to Energia: http://code.google.com/p/ht1632c-driver/ The library for Arduino compiles flawlessly in Energia without a change. It relies only in pinMode, digitalWrite and one single malloc call. The library compiles but don't even come to run the first line inside setup(). I've traced down the problem and found that the Stellaris hang if you try to use pinMode or digitalWrite outside the setup or loop scope. The same library in Arduino works without a problem. A short example of the problem: #include "HT1632C.h" #define DATA_PIN PC_5 #define WR_PIN PC_4 #define CS_PIN1 PC_6 #define CS_PIN2 PC_7 // Here don't work. Never come out. In arduino works. HT1632 matrix = HT1632(DATA_PIN, WR_PIN, CS_PIN1, CS_PIN2); void setup() { //Here works. HT1632 matrix = HT1632(DATA_PIN, WR_PIN, CS_PIN1, CS_PIN2); } void loop() { } Inside ot the constructor of the HT1632 class: HT1632::HT1632(byte data, byte wclock, byte chip0, byte chip1, byte chip2, byte chip3, byte rclock) { //Set the I/O Directions pinMode(data, OUTPUT); pinMode(wclock, OUTPUT); if (rclock) { pinMode(rclock, OUTPUT); _RCLOCK = rclock; } pinMode(chip0, OUTPUT); digitalWrite(chip0, HIGH); _CHIP0 = chip0; _NMODULES = 1; if (chip1) { pinMode(chip1, OUTPUT); digitalWrite(chip1, HIGH); _CHIP1 = chip1; _NMODULES = 2; } if (chip2) { pinMode(chip2, OUTPUT); digitalWrite(chip2, HIGH); _CHIP2 = chip2; _NMODULES = 3; } if (chip3) { pinMode(chip3, OUTPUT); digitalWrite(chip3, HIGH); _CHIP2 = chip3; _NMODULES = 4; } _DATA = data; _WCLOCK = wclock; _BUFFER_MALLOC = false; } The full library it's in the first link and attached to this post. I supose that I will have to defer the pin setup and digital writes to the init() instead of the constructor. No problem there, and I think that calling pinMode or digitalWrite outside of the scope of setup and loop it's anyway a bad coding practice in this kind of environments like Energia and Arduino. I wonder anyway if this out of scope problem it's intended in Energia. Regards. HT1632C.tar.bz2 Quote Link to post Share on other sites
madias 0 Posted March 26, 2014 Share Posted March 26, 2014 (edited) Have you tried it out with: #include "Energia.h" to your library? instead you can always use #include "Arduino.h" for crossover libraries (it´s only a reference to Energia.h in Energia and works with both: Arduino and E.) edit: I see, that #include "Arduino.h" is both in *.cpp and *.h, but maybe this line doesn't work in Energia: #if(ARDUINO >= 100) #include <Arduino.h> #else #include <WProgram.h> #endif My idea: just set it to #include <Arduino.h> and drop the rest (in *.h AND *.cpp) I also see #include <digitalWriteFast.h> in the header file, maybe also a problem? Edited March 26, 2014 by madias Quote Link to post Share on other sites
Stendall 0 Posted March 27, 2014 Author Share Posted March 27, 2014 Thank you madias. Will try the Energia.h and remove all Arduinos.h reference. Actualy I'm using a identical versión of the library, but instead using the digitalWriteFast and pinModeFast I've replaced all ocurrences with the proper digitalWrite and pinMode. Just I dont have realized that before because I was working with a copy of the library. Also I've managed to get running CCSv6 with the last Energia release, compile and debug. So tomorrow will debug it. Thanks again. Quote Link to post Share on other sites
spirilis 1,265 Posted March 27, 2014 Share Posted March 27, 2014 Was just having a conversation about this on IRC today... Basically you want to declare your instance as a global using "HT1632 matrix(DATA_PIN, WR_PIN, CS_PIN1, CS_PIN2);" ... Looks a bit different but it works. Here's the 2nd critical thing. Your constructor should NOT do anything important beyond setting variables or somesuch. This is because C++ constructors on embedded platforms typically run before any proper initialization has been performed and you can't guarantee the system is ready to carry out any work. Offload that to a begin() public method you run from setup(), e.g. just like Serial, SPI, etc does. It'll be matrix.begin() optionally with more params. Quote Link to post Share on other sites
Stendall 0 Posted March 27, 2014 Author Share Posted March 27, 2014 It's working after correct the includes (Energia instead Arduino) and defer the pinMode and digitalWrite calls to the init(). The only thing that don't work it's the text functions. I will check that later. I have to power the HT1632 module from 3.3v instead vbus, because if I do the later the stellaris crash and don't even appears in the usb bus. Maybe the stellaris need a few miliamps more than the arduino. Anyway it's ok. Thank you all. Updated library: https://drive.google.com/file/d/0Bzni4Zrd7hg_Qk80Y1d5bkFrSTA/edit?usp=sharing Quote Link to post Share on other sites
energia 485 Posted March 27, 2014 Share Posted March 27, 2014 This issue has come up before. It has to do with when the constructor being called before main(). I never dug into this deep enough to figure out what exactly is going on. I'll try to spend some time on it this week. Not sure if it can be fixed though. Maybe @@pabigot can shed some light on this as well? Quote Link to post Share on other sites
Stendall 0 Posted March 27, 2014 Author Share Posted March 27, 2014 Thank you Robert. Anyway I don't know if this is something that needs to be fixed. Maybe it's desirable that libraries do not call port functions before the setup. Quote Link to post Share on other sites
spirilis 1,265 Posted March 27, 2014 Share Posted March 27, 2014 This issue has come up before. It has to do with when the constructor being called before main(). I never dug into this deep enough to figure out what exactly is going on. I'll try to spend some time on it this week. Not sure if it can be fixed though. Maybe @@pabigot can shed some light on this as well? It's delegated to the C init runtime stuff, in this case, see hardware/lm4f/cores/lm4f/startup_gcc.c, ResetISR, bottom portion: // // call any global c++ ctors // cnt = __preinit_array_end - __preinit_array_start; for (i = 0; i < cnt; i++) __preinit_array_start[i](); cnt = __init_array_end - __init_array_start; for (i = 0; i < cnt; i++) __init_array_start[i](); // // call 'C' entry point, Energia never returns from main // main(); } Quote Link to post Share on other sites
pabigot 355 Posted March 27, 2014 Share Posted March 27, 2014 Hmn? OK, knowing nothing about Energia especially on Stellaris, a thought: the TI linker scripts from TivaWare strip out a lot of stuff, including all the sections and init code that's normally what invokes static constructors. It's part of the reason I ditched them and went with the startup infrastructure provided by ARM and gcc-arm, which seems more likely to work for C++ or even C modules that put functions in the .ctors and .dtors sections. That's my best guess without actually looking into it; hope it has some relevance. (Re @@spirilis comment that you can't guarantee the system is ready to carry out any work until main: you could if the init scripts were done correctly, since that's why the standard startup defines a function named SystemInit that's supposed to configure the system clock and maybe other resources. Understand, though, I'm still galloping along on my hobby horse here: I'm really peeved at TI's approach to Cortex-M devices.) Quote Link to post Share on other sites
spirilis 1,265 Posted March 27, 2014 Share Posted March 27, 2014 Hmn? OK, knowing nothing about Energia especially on Stellaris, a thought: the TI linker scripts from TivaWare strip out a lot of stuff, including all the sections and init code that's normally what invokes static constructors. It's part of the reason I ditched them and went with the startup infrastructure provided by ARM and gcc-arm, which seems more likely to work for C++ or even C modules that put functions in the .ctors and .dtors sections. That's my best guess without actually looking into it; hope it has some relevance. (Re @@spirilis comment that you can't guarantee the system is ready to carry out any work until main: you could if the init scripts were done correctly, since that's why the standard startup defines a function named SystemInit that's supposed to configure the system clock and maybe other resources. Understand, though, I'm still galloping along on my hobby horse here: I'm really peeved at TI's approach to Cortex-M devices.) Energia (Arduino does this too, I think; been a while since I poked around the AVR Arduino's core) does it the way you see in TivaWare; ResetISR() does basic .data copy, BSS zeroing, then runs C++ constructors, then runs main(). The Energia core then defines main() and takes over from there. The user's "sketch" has an init function called setup(), but it is run from Energia's main() some time after Energia has done its own system init stuff. So in theory, especially since Energia ships with a copy of startup_gcc.c and doesn't use GCC's own crt stuff, this could be reworked so C++ constructors are run right before setup() and after Energia has initialized all its stuff. Might make the behavior inconsistent with the MSP430 port though. For what it's worth, this is Energia's main() for the lm4f/tm4c port: int main(void) { ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_EEPROM0); if(ROM_EEPROMInit() == EEPROM_INIT_ERROR) { if(ROM_EEPROMInit() != EEPROM_INIT_ERROR) EEPROMMassErase(); } timerInit(); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOG); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOH); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOJ); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOK); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOM); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOP); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOQ); #ifdef TARGET_IS_SNOWFLAKE_RA0 ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOR); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOS); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOT); #endif //Unlock and commit NMI pins PD7 and PF0 HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = 0x4C4F434B; HWREG(GPIO_PORTF_BASE + GPIO_O_CR) |= 0x1; HWREG(GPIO_PORTD_BASE + GPIO_O_LOCK) = 0x4C4F434B; HWREG(GPIO_PORTD_BASE + GPIO_O_CR) |= 0x80; setup(); for (; { loop(); if (serialEventRun) serialEventRun(); } } Quote Link to post Share on other sites
pabigot 355 Posted March 27, 2014 Share Posted March 27, 2014 Energia (Arduino does this too, I think; been a while since I poked around the AVR Arduino's core) does it the way you see in TivaWare; ResetISR() does basic .data copy, BSS zeroing, then runs C++ constructors, then runs main(). The Energia core then defines main() and takes over from there. The user's "sketch" has an init function called setup(), but it is run from Energia's main() some time after Energia has done its own system init stuff. So in theory, especially since Energia ships with a copy of startup_gcc.c and doesn't use GCC's own crt stuff, this could be reworked so C++ constructors are run right before setup() and after Energia has initialized all its stuff. Might make the behavior inconsistent with the MSP430 port though. I saw a hint of that in your post which snuck in while I was writing. I'd still look at the linker script to make sure that the material that the compiler is putting into specific sections (so it can be found) actually make it into the final image. Also be aware that the standard gcc-arm infrastructure (if you're using the GNU for ARM Embedded on launchpad.net) is coupled with newlib: the startup routine just zeros bss, and it's _mainCRTStartup in newlib that copies over data, runs constructors, etc., before handing off control to main(). May be that Energia's missing something that's normally done there. Quote Link to post Share on other sites
spirilis 1,265 Posted March 27, 2014 Share Posted March 27, 2014 I saw a hint of that in your post which snuck in while I was writing. I'd still look at the linker script to make sure that the material that the compiler is putting into specific sections (so it can be found) actually make it into the final image. Also be aware that the standard gcc-arm infrastructure (if you're using the GNU for ARM Embedded on launchpad.net) is coupled with newlib: the startup routine just zeros bss, and it's _mainCRTStartup in newlib that copies over data, runs constructors, etc., before handing off control to main(). May be that Energia's missing something that's normally done there. So after examining some source from newlib, and looking back at Energia as well as the TivaWare examples/project/startup_gcc.c, it looks like what normally happens is there's __preinit_array function execution, followed by _init();, followed by the __init_array function execution. Neither TivaWare's example nor Energia's startup_gcc.c run the "_init();" between those two, as you see. There is no _init() type of function. So it's definitely a deviant in that regard. Linker script-wise, though, it includes the __preinit_array, __init_array and even __fini_array which isn't really used in both the "lm4fcpp_blizzard.ld" and "lm4fcpp_snowflake.ld" scripts. I'm guessing the original poster's original form of the library didn't work because the SysCtlPeripheralEnable()'s hadn't run yet, but I'm going to look back at the datasheet to see if that's the typical behavior (system freezes when a peripheral's accessed but not enabled?) edit: Yeah, most likely. SysCtlPeripheralEnable() basically enables the Clock for that peripheral, and accessing a peripheral which doesn't have a clock generates a Bus Fault. Page 339 of the TM4C123GH6PM datasheet: The RCGCGPIO register provides software the capability to enable and disable GPIO modules in Run mode. When enabled, a module is provided a clock and accesses to module registers are allowed. When disabled, the clock is disabled to save power and accesses to module registers generate a bus fault. This register provides the same capability as the legacy Run Mode Clock Gating Control Register n RCGCn registers specifically for the watchdog modules and has the same bit polarity as the corresponding RCGCn bits. Bus Fault handler in Energia's startup_gcc.c is the IntDefaultHandler: IntDefaultHandler, // The bus fault handler and that infinite-loops: static void IntDefaultHandler(void) { // // Go into an infinite loop. // while (1) { ; // trap any handler not defined } } Quote Link to post Share on other sites
spirilis 1,265 Posted March 27, 2014 Share Posted March 27, 2014 Hmm. @@energia Thinking about this some more, I wonder if there is legitimate value in moving the system init stuff for Energia's TM4C port from main() to an _init() function, then modify startup_gcc.c so it runs "_init();" between the preinit_array and init_array stuff. This may enable greater compatibility with Arduino libraries, such as the original HT1632 lib the original poster was trying to use, that do things "dirty" by issuing pinMode/digitalWrite calls inside their constructor. Basically main.cpp would look like this: #include <Energia.h> #if defined(PART_TM4C129XNCZAD) #include "inc/tm4c129xnczad.h" #elif defined(PART_TM4C1294NCPDT) #include "inc/tm4c1294ncpdt.h" #elif defined(PART_TM4C1233H6PM) || defined(PART_LM4F120H5QR) #include "inc/tm4c123gh6pm.h" #else #error "**** No PART defined or unsupported PART ****" #endif #include "inc/hw_gpio.h" #include "driverlib/rom.h" #include "driverlib/sysctl.h" #include "driverlib/eeprom.h" void _init() { ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_EEPROM0); if(ROM_EEPROMInit() == EEPROM_INIT_ERROR) { if(ROM_EEPROMInit() != EEPROM_INIT_ERROR) EEPROMMassErase(); } timerInit(); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOG); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOH); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOJ); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOK); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOM); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOP); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOQ); #ifdef TARGET_IS_SNOWFLAKE_RA0 ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOR); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOS); ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOT); #endif //Unlock and commit NMI pins PD7 and PF0 HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = 0x4C4F434B; HWREG(GPIO_PORTF_BASE + GPIO_O_CR) |= 0x1; HWREG(GPIO_PORTD_BASE + GPIO_O_LOCK) = 0x4C4F434B; HWREG(GPIO_PORTD_BASE + GPIO_O_CR) |= 0x80; } int main() { setup(); for (; { loop(); if (serialEventRun) serialEventRun(); } } and startup_gcc.c more like: extern void _init(void); // this is global, defined just before ResetISR() ..... (end of ResetISR)- // // call any global c++ ctors // cnt = __preinit_array_end - __preinit_array_start; for (i = 0; i < cnt; i++) __preinit_array_start[i](); _init(); cnt = __init_array_end - __init_array_start; for (i = 0; i < cnt; i++) __init_array_start[i](); // // call 'C' entry point, Energia never returns from main // main(); 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.