|
|
View previous topic :: View next topic |
Author |
Message |
chaphill
Joined: 07 May 2019 Posts: 21 Location: Chappell Hill, Tx
|
async communication dspic33ev256gm004 |
Posted: Mon Nov 08, 2021 11:45 am |
|
|
I have been fighting a problem with asynchronous communication with a dspic33 board. This is for interfacing a legacy tool to a third party communication system, so I don't have a lot of control over the inputs and outputs. The bulk of the program is working, but periodically an adjustment to a tool parameter is needed, which requires a downlink from a remote location. The baud rate is 115200 and is typically a 4, 5 or 6 byte command that is entered in hex and sent to the tool. I have gotten a DMA data transmission of up to 522 bytes at a time to work to the telemetry, and have gotten commands to work with the legacy tool but I can't get this simple small communication to work. I have stripped the code down to the attached:
Code: |
#include <33EV256GM004.h>
#device ICSP=1
#use delay(clock=80000000,crystal=7372800)
#FUSES NOWDT //No Watch Dog Timer
#FUSES NOCKSNOFSM // CHECK TO SEE IF THIS FIXES TIMIMG ISSUES
#pin_select U1TX=PIN_C6 // TX_W
#pin_select U1RX=PIN_B9 // RX_W
#use rs232(uart1, baud=115200, stream=telem, ERRORS, BITS=8) // for communication with W
unsigned int16 millisec_count = 0; //is the number of milliseconds, reset in interrupt routine
unsigned int16 Ten_Msec = 0; // rollover counter that keeps track of 10 msec tics
unsigned int16 Fifty_Msec = 0; // counter for the fifty millisecond data transmission rate (20 frames a second)
unsigned int16 Telem_Msec_Cntr = 0;
unsigned int16 sync_cnt = 39936; // gives a pretty precise 1 msec. interrupt at 40 MFlops
unsigned int1 dwnlnk_test_recvd = false;
unsigned int8 k = 0;
unsigned int8 dwnlnk_buff[10];
#INT_TIMER4
void timer4_isr(void)
{
Fifty_Msec++; // used to genererate reports every 50 milliseconds and reset
Telem_Msec_Cntr++; // keeps track of number of 1 msec tics for telemetry
millisec_count++; // used to generate the Ten_Msec count
if (millisec_count >= 10) // check this, may be using millisec_count
{
Ten_Msec++; //this is the 10 millisec tic (rollover counts)
millisec_count = 0;
}
if (Fifty_Msec >= 50) // want to collect a frame of data from each slave every 50 milliseconds
{
Fifty_Msec = 0;
output_toggle(PIN_B10); // 50 msec period, shows processor working
}
}
#INT_RDA
void Uart1_isr(void) // will be receiving commands sent down will just be 8 bit communication incoming,
// this seems to be working in MCA interface and should work here but need to put in timeout to be safe
// don't need to put checksum in here, mca interface does it when converting to 9 bits
{
unsigned int8 TempByte;
TempByte = fgetc(telem);
output_toggle(PIN_C7); // for debug 10/30/31 is Ain0
dwnlnk_buff[k] = TempByte;
k++;
if (k>=5)
{
dwnlnk_test_recvd = true;
}
} // this is a temporary test for what is being sent into the RX by the code written for the Arduino Mega
#INT_UART1E
void Uart1Error()
{
output_toggle(PIN_C7); // toggle Ain0 for indicator that there was an error
}
void main()
{
delay_ms(500); // wait a half second so slaves can get ready
set_tris_b(0b1111001011101111); //B4 (ENA485), B8 (cyt1), B10 (PWM1H) B11 (pwm1l)
set_tris_c(0b1100111101); // C1 (XMITRS485), C6 (TXMCA), C7 (A0)
set_tris_a(0b111111111); //
setup_timer4(TMR_INTERNAL,sync_cnt);
enable_interrupts(INT_TIMER4);
enable_interrupts(INT_RDA); // enable serial communications (downlink commands) going to try to check in main loop
enable_interrupts(INT_UART1E);
while(TRUE)
{
if (dwnlnk_test_recvd) // special print of downlinked command
{
dwnlnk_test_recvd = false;
k = 0;
{
for (k= 0; K<5; k++)
fputc(dwnlnk_buff[k], telem);
}
k = 0;
}
delay_us(200);
// output_toggle(PIN_B10);
} // end of main loop
} // end of main
|
compiler is 5.097
What happens is that with the command fed into the RX input from several different sources (e.g. FTDI USB to RS232 cable and even an Arduino Mega programmed to send out commands every 2 seconds) I can see that the commands are correct at the input pin (using a Salae logic analyzer) but the commands seem to not be received properly. I put the print statement in the main loop above to send the received data back out. I get scrambled data, interestingly only the upper nibble of the byte is corrupt, the lower nibble seems to go through correectly. I have used an ICD-U64 to look at the registers for the uart, and see that everything looks normal there. U1MODE = 8008, U1STA = 510 and the U1BRG is 56. I can't see the array values with the U64 though, I have to guess about that.
I have a feeling I am overlooking something but I have spent weeks on this, and am way passed the point where I can charge for these hours. HELP!!!
Looking back at this, sorry some of my comments are pretty wordy, I tried to strip them out but looks like I missed a lot. - Ray |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19495
|
|
Posted: Mon Nov 08, 2021 12:19 pm |
|
|
Re-structure your int_rda:
Code: |
#INT_RDA
void Uart1_isr(void) // will be receiving commands sent down will just be 8 bit communication incoming,
// this seems to be working in MCA interface and should work here but need to put in timeout to be safe
// don't need to put checksum in here, mca interface does it when converting to 9 bits
{
unsigned int8 TempByte;
do
{
TempByte = fgetc(telem);
output_toggle(PIN_C7); // for debug 10/30/31 is Ain0
dwnlnk_buff[k] = TempByte;
k++;
if (k>=5)
{
dwnlnk_test_recvd = true;
}
} while (kbhit(telem));
} // this is a temporary test for what is being sent into the RX by the code written for the Arduino Mega
|
This is necessary with the DsPIC's. You have up to 4 characters that
could be waiting when you get an interrupt. You should read them all.
It's unlikely given your clock rate, but worth handling.
Do you get any error trigger?. You need to read a byte to clear this
or it'll stay set inside the UART. |
|
|
chaphill
Joined: 07 May 2019 Posts: 21 Location: Chappell Hill, Tx
|
|
Posted: Mon Nov 08, 2021 1:10 pm |
|
|
Thanks Ttelmah, I have incorporated a read into the U1error interrupt routine. I did not know that it had to be read, but it makes sense. But I have had a scope lead on the line I toggle in the U1error interrupt routine and have never seen it change through the test. I have collected at least 50 samples using the logic analyzer and the Arduino to generate signals with apparently no errors in the communication that generated an error interrupt.
I also tried the modified routine you suggested. It did not make a difference. I seem to get the correct number of bytes back and the three bytes of the five I send that only have zeros in the upper nibble are in the same place and have the correct value each time. Only the first and third bytes are different from what I send down. Out of the 50 or so samples I collected there was not a single read of the first byte that was correct, and only one out of the approximately 50 of the third byte that was correct.
It is almost like there is some attempt to auto baud rate detect going on with the upper nibble but I did test to see if the Auto baud bit was set in the SFR and looked at the list code for a bit set at 220.3. |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1345
|
|
Posted: Mon Nov 08, 2021 1:31 pm |
|
|
Also note that you increment your "k" variable in the interrupt but you don't guard against it overflowing your array. Sure you set it back to 0 in the main, but if you get data too quickly or enough noise, you could start writing to random memory. You should probably put some sort of IF check on k before writing to the buffer again once it is full. |
|
|
chaphill
Joined: 07 May 2019 Posts: 21 Location: Chappell Hill, Tx
|
|
Posted: Mon Nov 08, 2021 2:11 pm |
|
|
Jeremiah,
Yes you are right, pretty sloppy code there. The problem the real routine had was that it tested to see if the first byte (the address of the board the command is for) was the one for this board. What the original problem was is that since the routine never read the first byte right it assumed the information that followed was for another board and just dumped the rest. So it wasn't overflowing, it never got anything written to it at all. I am forcing it to write something out so I could try to figure out what is wrong, even if there was something overwriting it. The real code (I have not looked at the array in this stripped routine) was kind of unusual, I don't know why but the compiler stuck this array way out by itself. It starts at 16B4, and there is nothing else below it or above it for quite a while. Maybe CCS is protecting me from myself! Three of the five bytes I receive are the ones I sent, and in the order I sent, it just does not read the first or the third byte correctly. Unfortunately the first byte is the address and the third byte is the command in the protocol of the legacy tool I am working with. |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1345
|
|
Posted: Mon Nov 08, 2021 3:43 pm |
|
|
One other thing to consider. A lot of dspics I've used can't start the PLL immediately and need to do so later at runtime.
I've had quite a few that required me to
1. set the fuses to FRC and HS (to start as the internal but prepare for the crystal), Also make sure other fuses allow for clock switching.
2. set the #use delay() to only specify "clock" (and not "crystal") as my ultimately desired value. This prevents it from changing your FUSES while still allowing you to set the clock needed for UART and delays correctly
3. call setup_oscillator() at the beginning of main to turn on the PLL and get the PIC running at the clock speed specified in #2
4. One or two PICs made me check a status bit to make sure the PLL and clock were stable. This was less frequently required
I don't know if yours requires this, but it would explain why the UART isn't receiving correctly if your instruction and peripheral clock speed isn't actually correct.
Something to at least look into.
Note that on the chips I had that required this for some the PLL didn't work at all and some the PLL was unstable. Also sometimes the ERRATA can bring to light PLL issues. |
|
|
chaphill
Joined: 07 May 2019 Posts: 21 Location: Chappell Hill, Tx
|
|
Posted: Mon Nov 08, 2021 4:42 pm |
|
|
Jeremiah,
You may be on to something there. I have an output_toggle in my timer interrupt that I use as a standard check to see if the processor is running. It is always either a 50 Hz or a 10 Hz oscillator to way better than a percent. I noticed on this one that what was supposed to be the 10 Hz. signal was actually more like 55 milliseconds on each half of the signal. I tried a second board and it was even worse. When I got the third one it is dead on and there are no issues with receiving the command or sending it at 115200.
I am going to have to dig into what you are saying, that is a strong possibility. Thanks! |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1345
|
|
Posted: Mon Nov 08, 2021 6:53 pm |
|
|
chaphill wrote: | Jeremiah,
You may be on to something there. I have an output_toggle in my timer interrupt that I use as a standard check to see if the processor is running. It is always either a 50 Hz or a 10 Hz oscillator to way better than a percent. I noticed on this one that what was supposed to be the 10 Hz. signal was actually more like 55 milliseconds on each half of the signal. I tried a second board and it was even worse. When I got the third one it is dead on and there are no issues with receiving the command or sending it at 115200.
I am going to have to dig into what you are saying, that is a strong possibility. Thanks! |
No problem. If that's the case I would also recommend starting with reading the ERRATA document for your chip. It might save you a lot of time if there is already documentation on a known chip issue. Microchip's website has them for download for each chip. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19495
|
|
Posted: Tue Nov 09, 2021 1:15 am |
|
|
If the clock is being slow getting started, it would explain data corruption
at the start. Definitely worth investigating....
So I'd follow Jeremiah's suggestions here.
The point about the PIC24/33 UART, is that it only sets the interrupt flag
when new data arrives. The flag can also be cleared when there is still data in
the internal buffer. So imagine that a second character arrives while you
are in the ISR. The ISR exits having only read one character. Then a while
later a third character arrives. The interrupt triggers again and one more
character is read. Again though at some point another character arrives
while one is in the ISR. The result is that the buffer can slowly grow, with
unread characters....
This is why the handling has to be this little bit different from the traditional
PIC routine....
I'd get rid of the error ISR, and just handle the errors in the data ISR.
So:
Code: |
//With OERR and fERR created as #bits to the register for the UART:
#INT_RDA //serial receive
void slave(void)
{
uint16_t rbyte; //now 16bit
while (kbhit(SERIAL))
{
//handle errors
if (OERR==1 || FERR==1)
{
rbyte=fgetc(SERIAL); //receive and throw
OERR=0; //clear the errors
FERR=0;
continue; //loop till flags clear
}
rbyte=fgetc(SERIAL); //9bit character
|
This is from a UART handler dealing with 9bit data, but the basic approach
stays the same for 8bit. Obviously with the test, one has to more the
kbhit test to the start of the sourine, so it'll exit correctly if the only data
is corrupted.
I already glanced at the errata for this chip, since I have met several
DsPIC's that have errors in the UART. This one doesn't seem to have any. |
|
|
chaphill
Joined: 07 May 2019 Posts: 21 Location: Chappell Hill, Tx
|
|
Posted: Wed Nov 10, 2021 11:34 am |
|
|
I am still fighting the problem but I have made some progress I think.
Jeremiah, yes I have the errata for this chip almost memorized now, one of my first experiences with this chip could have been avoided if I had read it so I check it a lot.
Ttelmah, thanks for the input. I took out a lot of the "real" program to get down to what I had zeroed in on. I maintained the ERRORS interrupt with a flag to show if that was happening but I had pretty much defined I was not getting the first byte in the communication. The program was testing the first byte for the address, and I had a flag showing that the interrupt routine was entered for every byte sent but another flag showed it was never recognizing the address. I was pretty confident that the problem was there so I wrote the test program to just look at every byte that came in so I could see what the issue is.
I am attempting to set up the two speed start up sequence as defined in the family reference manual (section 15.0) for the oscillator. So far I have a very solid reliable "starts every time the same" setup, I am just off by a factor or 1.5. I want 80 MHz tosc and have 120 MHz. Since it took me over 3 hours to come up with a calculation for the N1, N2 and M I MIGHT have made a mistake there!
Just in case someone thinks I am not happy with this chip, nothing could be farther from the truth. When it is working this guy is gathering and sending some amazing quantities of data via DMA with no glitches, it is just the once every three or four hours when I want to adjust something that it has issues. |
|
|
chaphill
Joined: 07 May 2019 Posts: 21 Location: Chappell Hill, Tx
|
|
Posted: Wed Nov 10, 2021 5:31 pm |
|
|
I think I have finally gotten the startup process working on this dsPIC. I used the two step startup process described in the family reference manual. The dsPIC starts up on the internal FRC and waits until the PLL stabilizes for the external clock input before switching automatically to it. As anyone knows that has read this stuff it always seems to take a lot of reading between the lines and guestimating to get something to work from these documents. At least for me it does. The attached code has started up a whole bunch of times with the same clock frequency, and shows the fuses and the register stuffing I did. Hope it helps someone!
Code: |
//
// This is a simple almost do nothing program that shows how I set the fuses and initialized the clock on a dsPIC33EV256GM004
// The program receives a five byte block of data in RX and then sends that block out via TX
// The fuses setup can be verified by checking the ccs lst file, down at the end. It is an "enjoyable experience"
// figuring out which words are what...
// The clock frequency was calculated from my input frequency from an external oscillator using the pll calculations
// in the data sheet. -Ray
//
#include <33EV256GM004.h>
#device ICSP=1
#use delay(clock=80179200) // note: no mention of "crystal" here, thanks Jeremiah!
// (clock calculated from external osc. frequency and pll calculations
#FUSES NOWDT //No Watch Dog Timer (for debug)
#fUSES IESO // STARTS WITH frc OSCILLATOR, THEN SWITCHES TO DEFINED CLOCK WHEN IT IS STABLE
#FUSES EC // have external oscillator
#FUSES PR_PLL // PRIMARY WITH PLL, EC DEFINED ABOVE AS PRIMARY
#pin_select U1TX=PIN_C6 // TX_W
#pin_select U1RX=PIN_B9 // RX_W
#use rs232(uart1, baud=115200, stream=telem, ERRORS, BITS=8) // for communication with W
// now for the PLL for the 7372800 oscillator:
// with N1 - 2
// N2 = 4
// M = 87
// calculate an oscillator of 80179200 from my input of 7372800 hz from an external oscillator
// PLLDIV = 85
// PLLPRE = 0
// PLLPOST = 1
unsigned int16 millisec_count = 0; //is the number of milliseconds, reset in interrupt routine
unsigned int16 Ten_Msec = 0; // rollover counter that keeps track of 10 msec tics
unsigned int16 Fifty_Msec = 0; // counter for the fifty millisecond data transmission rate (20 frames a second)
unsigned int16 Telem_Msec_Cntr = 0;
unsigned int16 sync_cnt = 40090; // gives a pretty precise 1 msec. interrupt at 40.090 MFlops
unsigned int1 dwnlnk_test_recvd = false;
unsigned int8 k = 0;
unsigned int8 dwnlnk_buff[10];
// the following is in the "geterdone mode", proper programming later
//#word Osc_config = getenv("SFR:OSCCON")
//can't write w/o unlock so assume is set by fuses???
#word CLKDIV = getenv("SFR:CLKDIV")
#word PLLFBD = getenv("SFR:PLLFBD")
#INT_TIMER4
void timer4_isr(void)
{
Fifty_Msec++; //
Telem_Msec_Cntr++;
millisec_count++;
if (millisec_count >= 10)
{
Ten_Msec++; //this is a 10 millisec tic (rollover counts)
millisec_count = 0;
output_toggle(PIN_B10); // 20 msec period, shows processor working, (freq 49.997 hz)
}
if (Fifty_Msec >= 50) // want to collect a frame of data from each slave every 50 milliseconds
{
Fifty_Msec = 0;
}
}
#INT_RDA
void Uart1_isr(void) // will be receiving commands sent down
{
unsigned int8 TempByte;
do
{
TempByte = fgetc(telem);
output_toggle(PIN_C7); // for debug (useful for observing that signal made it here
dwnlnk_buff[k] = TempByte;
k++;
if (k>=5)
{
dwnlnk_test_recvd = true;
k = 0;
}
} while (kbhit(telem));
}
#INT_UART1E
void Uart1Error() // just clears the error flag if an error reached
{
output_toggle(PIN_B8); // toggle cntr1 for indicator that there was an error
unsigned int8 TempByte;
TempByte = fgetc(telem); // if got an error read a byte, thanks Ttelmah!
}
void main()
{
PLLFBD = 85; // sets M = 87 (pllfreq = 85)
CLKDIV = 0x40; // sets divisor for FRC - 0, PLLPOST =1 and PLLPRE = 0
// the above two valvues are what was calculated for the inputs into the pll for the clock, not an elegant way to write them
// but "goterdone"
delay_ms(500); // wait a half second so slaves can get ready
set_tris_b(0b1111001011101111); //B4 (ENA485), B8 (cyt1), B10 (PWM1H) B11 (pwm1l)
set_tris_c(0b1100111101); // C1 (XMITRS485), C6 (TXMCA), C7 (A0)
set_tris_a(0b111111111); //
setup_timer4(TMR_INTERNAL,sync_cnt);
enable_interrupts(INT_TIMER4);
enable_interrupts(INT_RDA);
enable_interrupts(INT_UART1E);
while(TRUE)
{
if (dwnlnk_test_recvd) // special print of downlinked command
{
dwnlnk_test_recvd = false;
k = 0;
{
for (k= 0; K<5; k++)
fputc(dwnlnk_buff[k], telem);
}
k = 0;
}
delay_us(200); // is this a reasonable delay? 20000 times a second through the main loop? changed to 200 usec delay 11/3/21
// output_toggle(PIN_B10);
} // end of main loop
} // end of main
|
Thanks to everyone that has helped, be it on this thread or on the many that I have read in the last few days! |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19495
|
|
Posted: Thu Nov 11, 2021 12:45 am |
|
|
Big question. Has it fixed the comms?.... |
|
|
chaphill
Joined: 07 May 2019 Posts: 21 Location: Chappell Hill, Tx
|
|
Posted: Thu Nov 11, 2021 9:12 am |
|
|
Yes, the communication problems have been fixed, BUT... The initial board would still not communicate. I used my variable baud rate feature of the
logic analyzer and found it would communicate just fine at 110 kbaud instead of the programmed 115200. I still have not hooked the tool up to the real system after changing the board but hopefully that will be soon. On the bench it is working.
As a side benefit from this exercise the system clock is output on the osc2 pin, so I was able to get on the board in question and found out its clock was 38 MHz instead of the 40MHz that the other boards were running. I was planning on disabling the output on this pin after the tests but I think it is a pretty useful test now.
One of the annoying features of this particular dsPIC is that it seems to partially fail. I have had single I/O pins fail when the rest of the pic functioned just fine, and now this. I think I prefer the whole thing going up in smoke... |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9221 Location: Greensville,Ontario
|
|
Posted: Thu Nov 11, 2021 9:30 am |
|
|
grr... 'partial failures'.......grrrrrrrrr
Any chance you have a heatsink on it ? If not, perhaps a local 'hot spot' inside the die is causing the nasty failures.
Curious, like my cats...
Jay |
|
|
chaphill
Joined: 07 May 2019 Posts: 21 Location: Chappell Hill, Tx
|
|
Posted: Thu Nov 11, 2021 11:14 am |
|
|
Unfortunately heat sinks don't do a lot of good where this thing is going. The board in question had not been heat tested yet but I haven't seen any failures there. The reason I am using this particular pic is because of its temperature ratings.
It is hard to say what may have caused the issue because this is a prototype. It has seen lots of potential causes, but I would guess voltage stress before temperature. The fool that works on this tool is prone to getting into the high voltage. |
|
|
|
|
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
|