|
|
View previous topic :: View next topic |
Author |
Message |
bernd2700
Joined: 04 Jan 2012 Posts: 4
|
RS485 / RS232 #use with enable pin bug? |
Posted: Wed Jan 04, 2012 4:18 pm |
|
|
Hi!
If I use the directive "ENABLE=PIN_C7" with my chip "PIC16F690", I have 3 problems:
1) In my main program, a LED toggles e.g. every 10ms. As soon as specify the 'ENABLE=PIN_C7' directive in the '#use rs232()' statement and the time to put out the characters over the serial needs more than 10ms, it kills the toggling completely = it kills the timing = it stays inside the interrupt until everything is sent out. This is NOT the case as WITHOUT this enable function, in this case, it behaves correctly: It just writes the characters to the registers, the hardware does the rest and it immediately returns to the main program.
2) In addition, a short spike to gnd occurs between every character sent which I do not want at all!
3) So, I started for searching an alternative: I put another pin to high before I enable the #int_tbe and I put this pin to low again after I disable the #int_tbe. Unfortunately, here the problem is, that this pin goes low, BEFORE all characters have been sent out! So is there an interrupt called like 'transmission completed' available? I did not find any. The only thing I have now in mind, is that as soon as the pin goes low, I build a delay which is just enough for some characters of 9600baud and put then the pin to low.
Please tell me a better solution to this and why I have this problem 1 & 2 ? Do others see that, too? I searched the forum but did not find anything.
Please help! Many thanks in advance,
bernd2700 |
|
|
asmboy
Joined: 20 Nov 2007 Posts: 2128 Location: albany ny
|
|
Posted: Wed Jan 04, 2012 4:44 pm |
|
|
mandatory:
1- POST your COMPILER version number
2- Then POST your code so we can SEE it
using the code buttons on this page .
( all the code including fuses etc etc )
then you have the chance to get help |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19496
|
|
Posted: Thu Jan 05, 2012 3:26 am |
|
|
The easiest/best solution, is to use interrupt driven transmit (as in EX_STISR.c), and where the lines are to enable the interrupt, turn your enable 'on' (which you have done), then where the code is in the ISR to disable the interrupt, start a timer that runs for one byte time, and when this triggers, turn the enable off.
Problem is that the CCS enable code, works by polling the transmit buffer empty bit, since the hardware has no direct indication that the byte has been sent, so the code drops the enable, sends the byte, and then waits for the byte to be sent, and raises the enable. This is why is destroys your code timing.
It is a hardware weakness, which CCS bodge round. Their bodge works fine if you are not using interrupt driven TX, but as you have found doesn't really work for interrupt based code.
Best Wishes |
|
|
bernd2700
Joined: 04 Jan 2012 Posts: 4
|
|
Posted: Thu Jan 05, 2012 3:57 am |
|
|
Dear asmboy!
Thanks for the hint. I was thinking I read somewhere in the rules, that I must not post ccs-code, but anyhow it's simple. Here is an abbreviated version, since I think, if I post all the code including the debouncing of the switches etc. it becomes unreadable for you. So I spent time and abrreviated it as I think that the important stuff is best readable for you.
What it does: The subprogram "Statemachine_RS232" is called from the main every 10ms. If a switch is pressed, it sends a string out to the serial, waits some time whereas it listens to the receiver for an answer during this wait time, then if the answer from the receiver is correct, it switches on a led.
About the version of the ccs, how can I find out, I mean where this is written?
Thanks very much in advance for further help,
bernd 2700
Code: |
/****************** "MAIN.C" ******************************************************/
#include <16F690.h>
#include <string.h>
#include <System_conf.h>
#INT_TIMER1 // Interrupt Service Routine for Timer1
void isr_timer1()
{
set_timer1(0xD8EF); // Same value as from initialization
FlagTimeBasePeriod10ms = 1;
clear_interrupt(INT_TIMER1); // Absicherung ISF löschen
}
void Init(void)
{
setup_timer_1 ( T1_INTERNAL | T1_DIV_BY_1 ); // Generates 2MHz out of fosc=8MHz
set_timer1(0xD8EF); // 2MHz/10000=5ms (=Period 10ms); 65535-10000=D8EF
ENABLE_INTERRUPTS(INT_TIMER1);
ENABLE_INTERRUPTS(GLOBAL);
}
void main(void)
{
Init();
while (1)
{
if ( FlagTimeBasePeriod10ms )
{
Statemachine_RS232();
FlagTimeBasePeriod10ms = 0;
}
}
}
/****************** "SYSTEM_CONF.H" ******************************************************/
#use delay(internal=8M) // Set up for internal oscillator, 8MHz
#use rs232(STREAM=Data_RS232, BAUD=9600, XMIT=PIN_B7, RCV=PIN_B5, ERRORS, PARITY=N, BITS=8, STOP=1)
/****************** "RS232.C" ******************************************************/
#include <16F690.h>
#include <string.h>
#include <stdlib.h>
#include <System_conf.h>
#define RS232_WaitTime 2540/10 // [10ms] Specify time here, the transmitter
// has to wait for an answer of the receiver.
#define RS232_BufLenRcv 10 // Max. length of receive RS232 Buffer as array of characters starting from 0, so 10 means 10 characters ([0] to [9]).
#define RS232_SndOffsAddChar 2 // Send OFFSet in number of characters: If string is put back, also "E:" is sent (Echo) => 2 Characters
static char RS232_CurBufIndex = 0;
static char RS232_SndCurBufIndex = 0;
static char RS232_RcvStr[RS232_BufLenRcv];
static char RS232_SndStr[RS232_BufLenRcv+RS232_SndOffsAddChar]; // To also display "E:" for "Echo:" e.g.
static unsigned int WaitTimeCount = 0; // Counter variable which counts to RS232_WaitTime
static unsigned int WaitOngoing = 0;
#INT_RDA // Interrupt Service Routine for RS232
void isr_rda()
{
RS232_RcvStr[RS232_CurBufIndex] = fgetc(Data_RS232); // Get the value from the serial recieve reg. Place...
// ... here at this place, that if garbage is received, the UART does not shut down
if ( RS232_CurBufIndex < (RS232_BufLenRcv-1) )
{
RS232_CurBufIndex++;
}
clear_interrupt(INT_RDA); // Absicherung ISF löschen
}
#INT_TBE
void isr_tbe()
{
if (
( RS232_SndStr[RS232_SndCurBufIndex] != 0x0d ) && // If the input was enter
( RS232_SndCurBufIndex < (RS232_BufLenRcv + RS232_SndOffsAddChar - 1) ) // For safety, if no enter was detected, normally not needed
)
{
putc ( RS232_SndStr[RS232_SndCurBufIndex] );
RS232_SndCurBufIndex++;
}
else
{
putc ( 0x0d ); // Last character which is sent is always a CR.
DISABLE_INTERRUPTS(INT_TBE);
RS232_SndCurBufIndex = 0;
}
clear_interrupt(INT_TBE); // Absicherung ISF löschen
}
void Statemachine_RS232(void)
{
unsigned int i = 0; // Counting variable for loops
if ( WaitOngoing == 0 )
{
if ( "A SWITCH IS PRESSED" )
{
State_TransmitChCmds();
}
}
else
{
State_Wait();
}
}
void State_TransmitChCmds(void)
{
RS232_SndStr[0] = "R";
RS232_SndStr[1] = "0";
RS232_SndStr[2] = "1";
RS232_SndStr[3] = 0x0d;
ENABLE_INTERRUPTS(INT_TBE); // Ready to send the string => Go!
RS232_CurBufIndex = 0;
ENABLE_INTERRUPTS(INT_RDA); // Cmd was sent, now listen to the receiver
}
void State_Wait()
{
WaitTimeCount++;
if ( WaitTimeCount >= RS232_WaitTime )
{
WaitOngoing = 0;
WaitTimeCount = 0;
State_ReceiveChStates();
}
}
void State_ReceiveChStates()
{
DISABLE_INTERRUPTS(INT_RDA); // Also the receiver must have sent an answer already during the wait time (State_Wait).
"If RS232_RcvStr = something then switch on a led."
}
|
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19496
|
|
Posted: Thu Jan 05, 2012 6:14 am |
|
|
OK.
First thing is that you are handling the end of transmit data wrong.
You need to let INT_TBE occur once more _after_ sending your '0d'.
When TBE triggers with the buffer empty, and you have _nothing_ to send, then disable the interrupt, and turn off the enable.
If you look at ST_ISR.c, it disables the interrupt only when it has nothing to send. This is the point where you can disable the select line.
You could patch your code fairly easily by adding a static flag, and setting this where you currently disable the interrupt, then when you arrive next time with this flag set, disable the interrupt.
So:
Code: |
#INT_TBE
void isr_tbe() {
static int1 finish_tx_flag=FALSE;
if (finish_tx_flag) {
OUTPUT_HIGH(ENABLE_LINE);
finish_tx_flag=FALSE;
disable_interrupts(INT_TBE);
return;
}
if (
( RS232_SndStr[RS232_SndCurBufIndex] != 0x0d ) && // If the input was enter
( RS232_SndCurBufIndex < (RS232_BufLenRcv + RS232_SndOffsAddChar - 1) ) // For safety, if no enter was detected, normally not needed
)
{
putc ( RS232_SndStr[RS232_SndCurBufIndex] );
RS232_SndCurBufIndex++;
}
else
{
putc ( 0x0d ); // Last character which is sent is always a CR.
finish_tx_flag=TRUE;
RS232_SndCurBufIndex = 0;
}
//clear_interrupt(INT_TBE); // Absicherung ISF löschen
//Not needed CCS automatically clears the interrupt when you leave
//the ISR, unless 'NO_CLEAR' is specified
}
|
Then just pull the enable line low in your transmission code when data is first sent.
Best Wishes |
|
|
bernd2700
Joined: 04 Jan 2012 Posts: 4
|
|
Posted: Thu Jan 05, 2012 9:11 am |
|
|
Hi Ttelmah!
Thanks for the hint, I changed the code accordingly, but the same behaviour. This means, although I have changed my code that it represents your example, even then, when I put this OUTPUT_HIGH(ENABLE_LINE) (which is in my case OUTPUT_LOW(PIN_C1) ), there is still one character sent out AFTER the pin c1 changed to low state according to the oscilloscope.
So, if I follow your hint to set up a timer, I did it, but with partly success only: The pin c2 in timer0 toggles with a period of either 560us and - some seconds later but randomly repeatedly - 600us. I think it should toggle with 8/4/2 = 1MHz / 255 = with a period of 510us! And, additionally it jitters: The high phase lasts between approx. 283us and 307us (measured with cursors from the osci). My "main" timer, timer1 is very stable with a period of my wanted 10ms (everytime stable with exact 10.02ms according to osci.)
Why this jitter all the time and additionally this sporadically switching between 560us and 600us? Obviously, I did not set up the timer0 correctly. Where is the error? Please help me.
Many, many thanks,
bernd2700
Code: |
#INT_TIMER1 // Interrupt Service Routine for Timer1
void isr_timer1()
{
set_timer1(0xD8EF); // Same value as from initialization
output_toggle(PIN_C1);
FlagTimeBasePeriod10ms = 1;
clear_interrupt(INT_TIMER1); // Absicherung ISF löschen
}
#INT_timer0 // Interrupt Service Routine for timer0
void isr_timer0()
{
output_toggle(PIN_C2);
set_timer0(0x00); // Same value as from initialization
clear_interrupt(INT_TIMER0); // Absicherung ISF löschen
}
void Init(void)
{
setup_timer_1 ( T1_INTERNAL | T1_DIV_BY_1 ); // Generates 2MHz out of fosc=8MHz
set_timer1(0xD8EF); // 2MHz/10000=5ms (=Period 10ms); 65535-10000=D8EF
setup_timer_0 ( T0_INTERNAL | RTCC_DIV_2 ); // Generates MHz out of fosc=8MHz
set_timer0(0x00);
ENABLE_INTERRUPTS(INT_TIMER1);
ENABLE_INTERRUPTS(INT_timer0);
// ENABLE_INTERRUPTS(INT_RDA);
ENABLE_INTERRUPTS(GLOBAL);
|
|
|
|
RF_Developer
Joined: 07 Feb 2011 Posts: 839
|
|
Posted: Thu Jan 05, 2012 11:02 am |
|
|
Timers 0 and 1 are optimised for one-shot use. They generate an interrupt when they time out, i.e. when their counters reach 0. They then wrap round to their maximum count and continue counting down.
All that happen in hardware. One source of timing jitter and uncertainty (I hesitate to call it error as actually there's nothing "wrong" with this behaviour, what's "wrong", if anything, is the user's [lack of] understanding of it) is interrupt latency. You are resetting the count in your ISR, but you cannot precisely predict when your ISR will run in relation to the hardware interrupt signal.
There is also considerable overhead in CCS's interrupt handler and its steering arrangements. We are talking some tens of us in most cases. It will, of course as with all things, vary with processor and clock speed.
That means that your timer interval will be extended by the interrupt latency and the firmware overhead in the isr entry. Isr exit overhead is almost certainly not important: its anything that happens BEFORE you set the timer that's important.
The hardware latency will be fixed, give or take. It will vary depending on what instruction is being executed at the time, and by when the interrrupt occurs in the instruction. Thanks to that arguably quirky Harvard archtecture and to pipelining most, though not all instructions execute in four clocks, or two on 24 and 32 series PICS). Certainly a us or two here and there is not unlikely.
With two or more interrupts, and with critical sections, there will be additional, unpredictable latency due to interrupt hold off. I.e. where for one reason or another, such as already executing an interrupt routine, the PIC cannot enter your isr immediately.
All these make precise repetitive intervals with timers 0 and 1 difficult. Unless, that is, you are NOT preseting the count and simply allowing it to roll round and round.
To get round these issues Microchip gives us the Timer2 style of counter which has a count pre-load that set by hardware on timeout. Timer2 can be set to give a much wider range of times and is capable of much greater repetitive precision. There are still latency issues of course, but the base repeat timing is much better as its done in hardware rather than in firmware.
I cannot say for sure which of your observed timing variations come from which of these sources. I suspect the 560 vs 600us thing is due to interrupt hold-off while the "other" isr is executing. The "jitter" is more likely to be other latency variations.
Put another way, you not using the best timer for your application....
RF Developer. |
|
|
bernd2700
Joined: 04 Jan 2012 Posts: 4
|
|
Posted: Thu Jan 05, 2012 4:16 pm |
|
|
Dear RF_Developer,
Thanks for your post. I did not chose timer2, cause there is this "period", which is for me not well enough described what this means. Therefore I chose timer0. However, I think at least, timer1 counts up and when it reaches FFFF it generates an interrupt. However, I "solved" this issue now in - as Ttelmah suggested - setting up timer0 and tried different set_timer numbers as long as finally the time delay fitted the required delay for one character.
Thank you guys,
bernd2700 |
|
|
|
|
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
|