This thread has been locked.
If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.
As a newcomer to the TI MSP430 / CCS environment, please forgive a naive question!
Short form:
I have written an app that performs correctly when compiled with the TI C compiler. But when compiled with GCC it fails, ending up with what I presume is an exception at 0x8000 (JMP 0x0000). So I have three related questions:
Some details:
It appears to be failing within a delay loop, which is (effectively):
set_led_on(); while (get_counter() < desired) { asm("nop"); } set_led_off();
If the delay is short, I don't get the exception. And once the exception happens, I see the LED flicker periodically and the debugger is no longer in control, which would suggest the processor is resetting continually. I have disabled the WDT and verified that WDTHOLD is set to 1 prior to entering my loop, so that's not the culprit.
I'm using the GNU v9.3.1.11 (Mitto Systems Limited) compiler in CCS 10.3.1.00003. And as I mentioned, I don't see this problem when using TI v20.2.5LTS.
There is nothing in that short bit of code that would cause trouble. So it is probably in some detail which you haven't shown.
I use the GCC compiler and have never used the TI compiler or this CCS thing.
Hi,
I doubted that you used a wrong link file. For CCS, when you use GCC and TI compiler, it will use different link file format. Usually, you need not change the default. Please check the setting and import a example project from resource explorer and compare the compiler settings.
Hawken Li:
I noticed that if you build a project using one compiler, you cannot (successfully) switch to the other compiler via the Project => Property settings.
Instead, it appears to be necessary to stay with the compiler the project was built with.
I will try creating a stripped down version of my app -- starting from an example project -- so we can better understand what's happening.
Thank you for the info. I'll let you know what happens.
@David Schutlz36: Good point. I'm beginning to think this may be a CCS issue rather than a code issue. (See Hawken Li's reply below.)
I'm including a complete test program to demonstrate the issue.
To Replicate:
First verify under the TI compiler:
Observe: the LED turns on and off every 5 seconds.
Now test under the GCC compiler:
Observe: the LED turns on, but before it turns off, the program crashes. Pausing the debugger displays the message "Break at address "0x4" with no debug information available, or outside of program code."
What's interesting is that if you place a breakpoint at the call to set_led(false) at line 81, you'll see that it crashes before it arrives there. But if you place a breakpoint on the preceding NOP (inside the while() loop), it executes the NOP an indeterminate number of times before crashing.
The test code:
(As an aside, attempting to insert this as code resulted in a server error: You don't have permission ..., so I'm inserting it inline)
/**
* @file timer_test.c
*
* Verify a simple program for the MSP430FR5994, compiled under the TI compiler
* and the GCC compiler.
*
* rdp May 2021
*
* This file is to be included as a part of two Code Composer Studio projects:
* one compiled using the TI compiler and the other using the GCC compiler.
* Compile and test the code on a MSP-EXP430FR5994 development board. You
* should see the LED turn on and off every 5 seconds.
*
* However, it appears to work under the TI compiler, but crashes when compiled
* under the GCC compiler.
*/
// ============================================================================
// Includes
#include <msp430.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
// ============================================================================
// Definitions and types
#define RTC_FREQUENCY 32768L
#define MS_PER_SECOND 1000L
#define MS_TO_DURATION(ms) ((uint32_t)((ms * RTC_FREQUENCY) / MS_PER_SECOND))
// ============================================================================
// Forward declarations
static void set_led(bool on);
static void init_gpios(void);
static void init_buttons(void);
static void on_button1_press(void);
static void on_button2_press(void);
static void init_timer(void);
uint32_t get_time(void); // get current time as a 32-bit value
static uint16_t get_timer_l(void);
static void on_timer_overflow(void);
// ============================================================================
// Local storage
/**
* @brief s_timer_h holds the high-order 16 bits of a 32 bit timer/counter.
* It is incremented every time the 16 bit TA0 timer overflows.
*/
static uint16_t s_timer_h;
// ============================================================================
// Main code
/**
* main.c
*/
int main(void) {
WDTCTL = WDTPW | WDTHOLD; // stop watchdog timer
init_gpios();
init_buttons();
init_timer();
__enable_interrupt();
while (true) {
uint32_t until;
set_led(true);
printf("LED should be on for 5 seconds:\n");
until = get_time() + MS_TO_DURATION(5000);
while (get_time() < until) {
asm(" nop");
}
set_led(false);
printf("LED should be off for 5 seconds:\n");
until = get_time() + MS_TO_DURATION(5000);
while (get_time() < until) {
asm(" nop");
}
}
return 0;
}
// ============================================================================
// Local code
// ====================================
// Managing GPIOs
/**
* @brief Initialize the GPIOs
*/
static void init_gpios() {
// Configure all unused GPIOs as outputs to save power.
P1OUT = (0x00);
P1DIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
// P2.0 configured as UART TX
// P2.1 configured as UART RX
P2OUT = (0x00);
P2DIR = (BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
P2SEL0 &= ~(BIT0 | BIT1);
P2SEL1 |= (BIT0 | BIT1);
P3OUT = (0x00);
P3DIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
P4OUT = (BIT5);
P4DIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
// P5.5 configured as SW2, Input pull-up
// P5.6 configured as SW1, Input pull-up
P5OUT = (BIT5 | BIT6);
P5DIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT7);
P5REN |= (BIT5 | BIT6);
P6OUT = (0x00);
P6DIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
P7OUT = (0x00);
P7DIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
P8OUT = (0x00);
P8DIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
PJOUT = (0x00);
PJDIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
// Disable the GPIO power-on default high-impedance mode to activate
// previously configured port settings (not necessary since we re-
// initialize all GPIOs on startup...)
PM5CTL0 &= ~LOCKLPM5;
}
/**
* @brief Set the demo LED on or off.
*/
static void set_led(bool on) {
if (on) {
P1OUT |= BIT0;
} else {
P1OUT &= ~BIT0;
}
}
// ====================================
// Managing the buttons
static void init_buttons(void) {
// Configure SW1 and SW2 for interrupts
P5IES = (BIT5 | BIT6); // Hi/Low edge
P5IFG = 0; // Clear flags
P5IE = (BIT5 | BIT6); // interrupt enabled
}
static void on_button1_press(void) { asm(" nop"); }
static void on_button2_press(void) { asm(" nop"); }
// Port 4 interrupt service routine for buttons
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = PORT5_VECTOR
__interrupt void Port_5(void)
#elif defined(__GNUC__)
void __attribute__((interrupt(PORT5_VECTOR))) Port_5(void)
#else
#error Compiler not supported!
#endif
{
if (P5IFG & BIT6) {
on_button1_press();
P5IFG &= ~BIT6; // Clear IFG
}
if (P5IFG & BIT5) {
on_button2_press();
P5IFG &= ~BIT5; // Clear IFG
}
__bic_SR_register_on_exit(LPM3_bits); // Exit LPM3
}
// ====================================
// Time Management.
/**
* @brief TA0 is configured in continuous count up mode, clocked at 32KHz. As a
* 16-bit counter, it will overflow once every (2^16)/32768 = 2 seconds. The
* overflow interrupt will increment s_timer_h, which we use as the high-order
* 16 bits of a composite 32-bit counter.
*/
static void init_timer() {
TA0CTL = TASSEL__ACLK | // 32768 Hz source
MC__CONTINUOUS | // continuous mode
TAIE | // Interrupt on overflow
0; // TACLR;
s_timer_h = 0;
}
/**
* @brief Return the number of 32KHz ticks since reboot as a 32 bit value.
*/
uint32_t get_time(void) {
uint16_t timer_h;
uint16_t timer_l;
do {
// take snapshot of high order and low order bits of timer
timer_h = s_timer_h;
timer_l = get_timer_l();
// snapshots are valid only if high order word hasn't changed
} while (timer_h != s_timer_h);
// assemble snapshots of high and low 16 bits into a 32 bit value.
return ((uint32_t)timer_h << 16) | (timer_l);
}
/**
* @brief Read the 16 bit timer
*/
static uint16_t get_timer_l(void) { return TA0R; }
/**
* @brief On timer overflow, increment the high-order 16 bits (at interrupt lvl)
*/
static void on_timer_overflow(void) { s_timer_h += 1; }
// Timer overflow interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = TIMER0_A1_VECTOR
__interrupt void Timer(void)
#elif defined(__GNUC__)
void __attribute__((interrupt(TIMER0_A1_VECTOR))) Timer_A0(void)
#else
#error Compiler not supported!
#endif
{
on_timer_overflow();
TA0CTL &= ~TAIFG; // clear interrupt flag
__bic_SR_register_on_exit(LPM3_bits); // Exit LPM3
}
Two updates:
I realized that static uint16_t s_timer_h; should be marked volatile. After making that change, there was no change in behavior -- the GCC version still crashed.
I compared the two projects. As expected (and as appropriate), the GCC project had a linker file named msp430fr5994.ld, and the TI project had a linker file named lnk_msp430fr5994.cmd.
So I believe the linker files are correct for both projects.
I don't see anything that might cause your trouble. Other problems (see the note at 25.2.1 on reading TAxR) but no red flags.
Use the debugger to examine the system state when things fall apart. Perhaps the stack frame will provide a clue. If all else fails, compare the assembly code generated by the two compilers.
@David Schultz36: Thank you for the suggestions. As I'm coming from the ARM world, which registers should I examine when things fall apart (and what would you look for in those registers)?
In the meantime, I'll figure out how to get CCS to emit assembly code...
It isn't so much registers you want to look at as it is the stack frame. This provides a record of where the program has been.
I use objdump (msp430-elf-objdump -S) to look at what is in a .elf file output by gcc. I did that with your code and didn't see any obvious trouble. (I don't have a fr5994 to run it on.) Except for that volatile issue you mentioned. The gratuitous use of static complicates things as this tends to make gcc not use labels. Optimization can also obscure program flow in many ways including inlining functions. When I get really confused, I remove that switch. (My default is "-O2".)
I don't often use GCC on the MSP430, but running this program with CCS v8.3 (GCC v7.3.2.154 [Mitto]) seems to work fine.
Some shots in the dark:
1) Does it change anything if you remove the printf() calls? I vaguely recall that CCS and GCC use different mechanisms.
2) Check your stack size. I think the stack extends from __heap_end__ to __stack (see .map file).
Well, for what its worth, I'm running a newer CCS and a newer GCC. Regarding the shots in the dark:
What's really odd is that it dies inside the while() loop, after the printf(). And by putting a breakpoint on the NOP instruction inside the loop, it appears to run the loop anywhere between 30 and 50 times (random) before failing.
When it fails, it ends up at address 0x4, which is a "jmp ." loop. And the stack is at the top level, so there's nothing interesting to see what it called before getting jumping there.
From the .map file:
Inside the while() loop (and when it fails), SP is 0x2bf4, so there's no evidence of a stack overflow.
Please remove the printf() then try again. I highly doubted the printf calling in gcc may not include the special command'\n'.
Here is a substantially stripped down version of the code. When run, it fails by jumping off to uninitialized memory:
(Aside: When I try to insert </> code, I still get the error message: You don't have permission to access "">e2e.ti.com/.../configure on this server., so I'm inserting inline.)
And setting a breakpoint with a skip count on the NOP, it shows that the NOP was executed a random number of times before hanging: 28 times in my most recent test.
/**
* @file timer_test.c
*
* Verify a simple program for the MSP430FR5994, compiled under the TI compiler
* and the GCC compiler.
*
* rdp May 2021
*
* This file is to be included as a part of two Code Composer Studio projects:
* one compiled using the TI compiler and the other using the GCC compiler.
* Compile and test the code on a MSP-EXP430FR5994 development board. You
* should see the LED turn on and off every 5 seconds.
*
* However, it appears to work under the TI compiler, but crashes when compiled
* under the GCC compiler.
*/
// ============================================================================
// Includes
#include <msp430.h>
#include <stdint.h>
// ============================================================================
// Definitions and types
#define RTC_FREQUENCY 32768L
#define MS_PER_SECOND 1000L
#define MS_TO_DURATION(ms) ((uint32_t)((ms * RTC_FREQUENCY) / MS_PER_SECOND))
// ============================================================================
// Local storage
/**
* @brief Incremented at interrupt level when the 16 bit TA0 timer overflows,
* s_timer_h represents the high-order 16 bits of a 32 bit timer/counter.
*/
volatile uint16_t s_timer_h;
// ====================================
// Time Management.
/**
* @brief TA0 is configured in continuous count up mode, clocked at 32KHz. As a
* 16-bit counter, it will overflow once every (2^16)/32768 = 2 seconds. The
* overflow interrupt will increment s_timer_h, which we use as the high-order
* 16 bits of a composite 32-bit counter.
*/
void init_timer() {
TA0CTL = TASSEL__ACLK | // 32768 Hz source
MC__CONTINUOUS | // continuous mode
TAIE | // Interrupt on overflow
0; // TACLR;
s_timer_h = 0;
}
/**
* @brief Return the number of 32KHz ticks since reboot as a 32 bit value.
*/
uint32_t get_time(void) {
uint16_t timer_h;
uint16_t timer_l;
do {
// take snapshots of high order and low order bits of timer
timer_h = s_timer_h; // s_timer_h incremented at interrupt level
timer_l = TA0R; // get_timer_lo()
// snapshots are valid only if high order word hasn't changed
} while (timer_h != s_timer_h);
// assemble snapshots of high and low words into a 32 bit word.
return ((uint32_t)timer_h << 16) | timer_l;
}
// Timer overflow interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = TIMER0_A1_VECTOR
__interrupt void Timer(void)
#elif defined(__GNUC__)
void __attribute__((interrupt(TIMER0_A1_VECTOR))) Timer_A0(void)
#else
#error Compiler not supported!
#endif
{
// Arrive ere on timer overflow (once every 2 seconds)
s_timer_h += 1; // increment the high-order timer word
TA0CTL &= ~TAIFG; // clear interrupt flag
__bic_SR_register_on_exit(LPM3_bits); // Exit LPM3
}
// ============================================================================
// Main code
/**
* main.c
*/
int main(void) {
uint32_t until;
WDTCTL = WDTPW | WDTHOLD; // stop watchdog timer
// init_gpios();
P1OUT = (0x00); // set P1OUT.0 as output for LED
P1DIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
PM5CTL0 &= ~LOCKLPM5;
init_timer();
__enable_interrupt();
P1OUT |= BIT0; // set_led(true);
// LED should be on for 5 seconds
until = get_time() + MS_TO_DURATION(5000);
while (get_time() < until) {
asm(" nop");
}
// Using GCC compiler, never arrives here.
P1OUT &= ~BIT0; // set_led(false);
return 0;
}
First off 0xA0058 is non-existent memory and not uninitialized since there is only 256KB on this part.
I was wondering if your leaving MCLK at the power on default could be trouble but when reading the docs again I found other trouble since you seem to be assuming a 32KHz crystal controlled clock is available on ACLK. You have to do a little work with the clock system to get that to happen. Here is what I do on a fr5972:
FRCTL0 = FWPW|NWAITS_1; // set 1 FRAM wait state FRCTL0_H = 0; // lock FRAM control CSCTL0 = CSKEY; // unlock clock system registers CSCTL1 = DCORSEL|DCOFSEL_4; // Set DCO to 16MHz CSCTL2 = SELA__LFXTCLK|SELS__DCOCLK|SELM__DCOCLK; // ACLK = XT1, MCLK = DCO CSCTL3 = DIVA__1|DIVM__1|DIVS__16; // set clock dividers // Startup the LFXT PJSEL0 |= BIT4|BIT5; // assign pins to oscillator CSCTL4 &= ~LFXTOFF; // LFXT On // Wait for oscillator to start do { CSCTL5 &= ~LFXTOFFG; // clear fault flag SFRIFG1 &= ~OFIFG; } while(SFRIFG1&OFIFG); CSCTL0_L = 0; // lock clock system registers
@David Schultz36: Improperly initializing (or failing to initialize) the clocks properly certainly could be a source of trouble, but that raises two more questions:
I've pared down the problem code to even smaller, here in its entirety:
/**
* @file timer_test.c
*
* This code works when compiled by the TI compiler, but crashes when compiled
* by the GCC compiler.
*/
#include <msp430.h>
int main(void) {
WDTCTL = WDTPW | WDTHOLD; // stop watchdog timer
// init_gpios();
P1OUT = (0x00); // set P1OUT.0 as output for LED
P1DIR = (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
PM5CTL0 &= ~LOCKLPM5;
// init_timer()
TA0CTL = TASSEL__ACLK | // 32768 Hz source
MC__CONTINUOUS | // continuous mode
TACLR; // TACLR;
// LED should be on for 0x7fff clock ticks
P1OUT |= BIT0; // set_led(true);
while (TA0R < 0x7fff) {
asm(" nop");
}
// Using GCC compiler, never arrives here.
P1OUT &= ~BIT0; // set_led(false);
return 0;
}
The problem almost certainly has to be in the while (TA0R < 0x7fff) statement, since I see the LED go on, and a breakpoint on the NOP with a skip count of 100 only gets called about 30 to 50 times before the PC wanders off into no-no land.
I checked the generated code. Under the TI compiler (the working case), the while() loop looks like this:
39 while (TA0R < 0x7fff) {
01001c: 90B2 7FFF 0350 CMP.W #0x7fff,&TA0_TA0R
010022: 2C05 JHS (0x002e)
40 asm(" nop");
010024: 4303 NOP
39 while (TA0R < 0x7fff) {
010026: 90B2 7FFF 0350 CMP.W #0x7fff,&TA0_TA0R
01002c: 2BFB JLO (0x0024)
43 P1OUT &= ~BIT0; // set_led(false);
01002e:
The corresponding while() loop compiled under GCC (the failing case) looks like this:
39 while (TA0R < 0x7fff) {
00402c: 421C 0350 MOV.W &TA0_TA0R,R12
004030: 403D 7FFE MOV.W #0x7ffe,R13
004034: 9C0D CMP.W R12,R13
004036: 2807 JLO (0x4046)
40 asm(" nop");
004038: 4303 NOP
39 while (TA0R < 0x7fff) {
00403a: 421C 0350 MOV.W &TA0_TA0R,R12
00403e: 403D 7FFE MOV.W #0x7ffe,R13
004042: 9C0D CMP.W R12,R13
004044: 2FF9 JHS (0x4038)
004046:
I didn't see anything in the errata sheets that would suggest a problem for the GCC code.
Update:
I ran the output of the two versions (TI and GCC) through msp430-elf-objdump and it's evident that the TI version does some initialization that the GCC version does not. This could explain a lot. This leads to one more (and hopefully final) question: where can I find the startup code for the TI and for the GCC versions? I'd like to verify that the startup code accounts for the difference.
Update 2:
I'm going to file a new question ("where can I find the startup code for the TI and for the GCC versions") since that seems the likely cause, but it's now deeply buried in this thread. If that does NOT explain the cause of the hang, I'll revisit this thread.
@David Schultz36: I ran the GCC compiled code after running the TI compiled code and -- surprise -- it completed without crashing. This gives weight to your theory about some registers not being correctly initialized. I'm hoping that comparing the TI startup code against the GCC startup code will show why that's the case.
Could you compare it with assembly so that we can analyze the root cause?
Certainly. I have two files: test-gcc.objdump is the code as assembled by the GCC compiler, test-ti.objdump is the code as assembled by the TI compiler. I can see that the TI version calls __mpu_init() while the GCC version does not.
Is there a way to attach files to this thread? If not, I'll just copy and paste the contents...
I tried attaching a file via Insert => Image/video/file, then clicking on [Upload], selecting the file name, but no filename appeared in the dialog, and clicking [Ok] had no effect.
Hi, the linker script could be the cause.
MSP430-GCC-OPENSOURCE: GCC 9.3.0.31 .lowtext rule is missing from linker scripts
MSP430-GCC-OPENSOURCE: GCC 9.2.0.50 msp430fr5994.ld .lowtext bugfix
That could explain things. Unlike the cause described in MSP430-GCC-OPENSOURCE: GCC 9.2.0.50 msp430fr5994.ld .lowtext bugfix, my example isn't using ISRs.
Petr Trnak: My stripped down example doesn't use interrupts at all, yet still fails. My reading of the bug reports that you supplied is that they only apply to interrupt handling. Did I read that incorrectly?
Configuring the timer to use ACLK generates an ACLK request which will result in an oscillator fault because the LFXT pins are configured as I/O. This isn't supposed to generate an interrupt unless OFIE is set. Or so the documents say.
An easy test is to switch the timer to SMCLK. Or provide a user NMI handler.
That is correct, other bug report was ISR related.
I have just tried to compile your minimal example using CCS Cloud https://dev.ti.com/ide/
New CCS project, GNU v9.3.1.11, MSP430FR5994,
uploaded it on the MSP-EXP430FR5994 launchpad.
And the red LED blinks as expected. So I cannot reproduce the bug.
**Attention** This is a public forum