|
|
View previous topic :: View next topic |
Author |
Message |
haxan7
Joined: 27 Jul 2013 Posts: 79
|
Problems with large struct. |
Posted: Tue Oct 13, 2015 9:34 am |
|
|
I think it is a compiler bug, I need help to go around it.
When I try to initialize the struct, the value assigned to a variable (any possibly others as well) is garbled up.
uController: PIC18F46K22
Compiler: 5.047 and 5.049
The is code is too large to post, here is a excerpt that causes problems:
Code: |
#define DBGLU(x) fprintf(DEBUG, "%s=%Lu\r\n",#x,x)
typedef void (*callbackFunction)(void);
int _debounceTicks = 50; // number of ticks for debounce times.
typedef struct{
uint16_t _pin;
// These variables will hold functions acting as event source.
callbackFunction _clickFunc;
callbackFunction _doubleClickFunc;
callbackFunction _pressFunc;
callbackFunction _longPressStartFunc;
callbackFunction _longPressStopFunc;
callbackFunction _duringLongPressFunc;
uint16_t _clickTicks; // number of ticks that have to pass by before a click is detected
uint16_t _pressTicks; // number of ticks that have to pass by before a long button press is detected
bool _buttonReleased; // Logic level when button is NOT pressed
bool _buttonPressed; // Logic level when button is pressed
bool _isLongPressed;
// These variables that hold information across the upcoming tick calls.
// They are initialized once on program start and are updated every time the tick function is called.
uint8_t _state;
uint32_t _startTime; // will be set in state 1
}Button;
void Button_init(Button* b, uint16_t pin, bool activeLow=1){
b->_pin = pin;
b->_clickTicks = 2000; // number of millisec that have to pass by before a click is detected.
b->_pressTicks = 1000; // number of millisec that have to pass by before a long button press is detected.
DBGLU(b->_clickTicks);
b->_state = 0; // starting with state 0: waiting for button to be pressed
b->_isLongPressed = false; // Keep track of long press state
if (activeLow) {
// button connects ground to the pin when pressed.
b->_buttonReleased = 1; // notPressed
b->_buttonPressed = 0;
} else {
// button connects VCC to the pin when pressed.
b->_buttonReleased = 0;
b->_buttonPressed = 1;
} // if
b->_doubleClickFunc = NULL;
b->_pressFunc = NULL;
b->_longPressStartFunc = NULL;
b->_longPressStopFunc = NULL;
b->_duringLongPressFunc = NULL;
DBGLU(b->_clickTicks);
}
|
output to the serial console is
Code: |
b->_clickTicks=2000
b->_clickTicks=0
|
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19504
|
|
Posted: Tue Oct 13, 2015 10:38 am |
|
|
Try storing an int16, instead of a function address, and explicitly cast this to a function address when required.
Look at this thread:
<http://www.ccsinfo.com/forum/viewtopic.php?t=49499>
CCS persistently sometimes has problems with function pointers.
Then separately, simplify the alignment for the compiler. Declare your single bit values last, and use the C standard bitfield, rather than bool. Again CCS sometimes does nasty things with bool values. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Oct 13, 2015 11:04 am |
|
|
Oops, I was typing in my huge post but Ttelmah already answered it.
Oh well.
The problem appears to be this:
1. The compiler allocates 2 bytes of RAM in the structure for each
callback function declaration. That's correct.
In the 18F46K22, there are 64 kbytes of flash memory. The address
range is 0 to 0xFFFF, which requires 16-bits to hold the address of a
function.
So the offset of each function address in the structure is 2 bytes higher
than the previous entry. That's all reasonable.
2. When you initialize all the callback function addresses to NULL (or it
could be to any address), the compiler writes to 4 sequential bytes.
That's not correct. By writing to 4 bytes instead of 2, it's stepping on
the next callback function address in the structure. That's why you
get 0000 as the 2nd result.
For example, suppose we edit your code to set the last entry below
to 0x1234 instead of NULL:
Code: | b->_doubleClickFunc = NULL;
b->_pressFunc = NULL;
b->_longPressStartFunc = NULL;
b->_longPressStopFunc = NULL;
b->_duringLongPressFunc = 0x1234; |
Now look at the .LST file code for the last 3 lines above:
First you can see the structure offset of each entry at the start of each
block. It goes 8, 0A, 0C, which is 2 bytes apart, because a function
address only takes 16 bits in this PIC.
Code: |
.................... b->_longPressStartFunc = NULL;
00194: MOVLW 08
00196: ADDWF b,W
00198: MOVWF FSR0L
0019A: MOVLW 00
0019C: ADDWFC b+1,W
0019E: MOVWF FSR0H
001A0: CLRF INDF0
001A2: CLRF PREINC0
001A4: CLRF PREINC0
001A6: CLRF PREINC0
.................... b->_longPressStopFunc = NULL;
001A8: MOVLW 0A
001AA: ADDWF b,W
001AC: MOVWF FSR0L
001AE: MOVLW 00
001B0: ADDWFC b+1,W
001B2: MOVWF FSR0H
001B4: CLRF INDF0
001B6: CLRF PREINC0
001B8: CLRF PREINC0
001BA: CLRF PREINC0
....................
|
Now let's look at the final section. It loads 34 12 (in Intel lo-hi format)
into the structure element, but then it continues on and clears two
additional bytes. Why ? This is some kind of bug. Note that all callback
address entries are getting this treatment. So they're all stepping on
each other. Note that this problem only occurs with function addresses.
Code: |
.................... b->_duringLongPressFunc = 0x1234;
001BC: MOVLW 0C
001BE: ADDWF b,W
001C0: MOVWF FSR0L
001C2: MOVLW 00
001C4: ADDWFC b+1,W
001C6: MOVWF FSR0H
001C8: MOVLW 34
001CA: MOVWF INDF0
001CC: MOVLW 12
001CE: MOVWF PREINC0
001D0: CLRF PREINC0 // *** Steps on the next 2 bytes *** BUG !
001D2: CLRF PREINC0
.................... |
A simple brute force workaround is to add a dummy int16 variable after
each callback function address declaration. Then the code will just be
stepping on a dummy variable, and who cares what happens to that ?
This work-around should at least allow you to continue development
until somebody can come up with an explanation, or a better workaround.
CCS should be asked about why it's doing this.
I tested this with CCS vs. 5.050 in MPLAB vs. 8.92 simulator.
Quote: |
typedef struct{
uint16_t _pin;
// These variables will hold functions acting as event source.
callbackFunction _clickFunc;
int16 dummy0;
callbackFunction _doubleClickFunc;
int16 dummy1;
callbackFunction _pressFunc;
int16 dummy2;
callbackFunction _longPressStartFunc;
int16 dummy3;
callbackFunction _longPressStopFunc;
int16 dummy4;
callbackFunction _duringLongPressFunc;
int16 dummy5;
|
Here's the test program I made to work on this. You actually should
have made this for me.
Code: | #include <18F46K22.h>
#fuses INTRC_IO, NOWDT
#use delay(clock=4M)
#use rs232(baud=9600, UART1, ERRORS, stream=DEBUG)
#include <stdint.h>
#define bool int1
#define NULL (int16)0
#define DBGLU(x) fprintf(DEBUG, "%s=%Lx\r\n",#x,x) // *** Was %lu ***
typedef void (*callbackFunction)(void);
int _debounceTicks = 50; // number of ticks for debounce times.
typedef struct{
uint16_t _pin;
// These variables will hold functions acting as event source.
callbackFunction _clickFunc;
int16 dummy0;
callbackFunction _doubleClickFunc;
int16 dummy1;
callbackFunction _pressFunc;
int16 dummy2;
callbackFunction _longPressStartFunc;
int16 dummy3;
callbackFunction _longPressStopFunc;
int16 dummy4;
callbackFunction _duringLongPressFunc;
int16 dummy5;
uint16_t _clickTicks; // number of ticks that have to pass by before a click is detected
uint16_t _pressTicks; // number of ticks that have to pass by before a long button press is detected
bool _buttonReleased; // Logic level when button is NOT pressed
bool _buttonPressed; // Logic level when button is pressed
bool _isLongPressed;
// These variables that hold information across the upcoming tick calls.
// They are initialized once on program start and are updated every time the tick function is called.
uint8_t _state;
uint32_t _startTime; // will be set in state 1
}Button;
void Button_init(Button* b, uint16_t pin, bool activeLow=1){
b->_pin = pin;
b->_clickTicks = 2000; // number of millisec that have to pass by before a click is detected.
b->_pressTicks = 1000; // number of millisec that have to pass by before a long button press is detected.
DBGLU(b->_clickTicks);
b->_state = 0; // starting with state 0: waiting for button to be pressed
b->_isLongPressed = false; // Keep track of long press state
if (activeLow) {
// button connects ground to the pin when pressed.
b->_buttonReleased = 1; // notPressed
b->_buttonPressed = 0;
} else {
// button connects VCC to the pin when pressed.
b->_buttonReleased = 0;
b->_buttonPressed = 1;
} // if
b->_doubleClickFunc = NULL;
b->_pressFunc = NULL;
b->_longPressStartFunc = NULL;
b->_longPressStopFunc = NULL;
b->_duringLongPressFunc = 0x1234;
DBGLU(b->_clickTicks);
}
//=====================================
void main()
{
Button b;
uint16_t pin;
bool activeLow;
activelow=1;
pin = PIN_B0;
Button_init(&b, pin, activeLow);
while(TRUE);
} |
|
|
|
haxan7
Joined: 27 Jul 2013 Posts: 79
|
|
Posted: Tue Oct 13, 2015 11:43 am |
|
|
Spot on. Thanks a lot. My program definitely works now.
Apologies for any inconvenience caused by me by not posting a compilable program. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19504
|
|
Posted: Tue Oct 13, 2015 2:07 pm |
|
|
It is interesting, since it relates to that old post (long time ago, so the problem has been around a while). PCM_programmer has nailed it down further. It appears that when a function pointer is inside a structure, CCS is not correctly remembering the size, and treating them as if they were the largest size (that applies to PCD compilers and some of the larger PIC18's), where they have to be > 16bit. Yet elsewhere it correctly adjusts the size for the chip involved. Hence my solution of using numeric values at the size needed for the processor works, or leaving the extra space as PCM_programmer does also works.
There is a (relatively) elegant work-round that also works and doesn't waste the space. I redeclared the typedef as a union between a function pointer, and an int16 (called ptr, and val), and then just write the NULL to
b->_doubleClickFunc.val = NULL;
Then when I want to use the function pointer, use
b->_doubleClickFunc.ptr
This seems to be working OK. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Mon Oct 19, 2015 8:53 am |
|
|
I emailed a bug report to CCS about this thread. Here is their reply:
Quote: |
Thanks for the problem report. This will be fixed in 5.051 due out early this week. |
|
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|