Jump to content
drumanart

Two Servo Motors Controlled Using Data Received From Usart

Recommended Posts

Hello.

I have 2 servo motors (standard 50Hz, 1000us to 2000us) attached to PORTB, RB0 to RB1 and

I use the USART of the PIC to receive data from a PC using a IC FT232 level shifter.

 

My problem is the interrupt handling.

I create the duty cycle and the value for the servos with TIMER0 and all works nice as long as I only send one byte from the PC to the PIC and then wait until the servo got to the desired angle. As soon as I try to handle the servos with a continuous data stream (from a slider) I get conflicts with the RX module and the PWM generated on PORTB.

 

Do I have to use novo RTOS to have the ability to create TASKs or is there another way?

 

I use MPLAB and a licensed SouceBoost V6.95.

 

Thanks for help Martin

Share this post


Link to post
Share on other sites

Hi

 

Like you said, its probably a problem with interrupt handling.

You can uso NOVO but I don't think that it is a must.

 

But, if you want some serious help, how about teling us a few important things?

Wich PIC?

Wich peripherals are you using and how?

 

It would be nice if you post your ISR code and, if needed, a couple of explanations on what it is suposed to do.

With that information at hand, we might eventually be able to help you in finding your bug.

 

 

Best regards

Jorge

Share this post


Link to post
Share on other sites

Thanks for the fast reply.

 

The PIC is a 18F4420 and the servos are FUTABA 3003. I use the PORTB to toggle RB0 and RB1 to generate the duty cycle and the corresponding value to be applied to the servo motor.

 

To adjust the servos I send a simple 3 byte protocol from the PC to the PIC's USART:

1st: 0xFF (header)

2nd: channel (to juice the servo)

2rd :data

 

So the main function tests the rcif flag and when a 255 is received the RX_DATA function gets the other two bytes, the address and data byte. For test reasons I send the data back to the PC: serial_printf(header) and serial_printf(pw_data).

 

Then the program calls the PWM function:

 

// MAIN

void main(void)
{
 osccon = 01100000b;					   // OSCCON int Osc, 4 Mhz.
 while (!iofs);									  // DEBUG without iofs OSCCON stable bit.

 init();												 // Port settings etc.
.
 char header = 0x00, aux;				  // 1st.header 0xFF, 2nd channel from ROBOREALM
 short pw_data = 128;						// Servo middle position.

while(1){

  _asm clrwdt;									// the watchdog.

if (rcif) {										  // 1st reads header.
header = rcreg;
serial_printf(header);								
}
if (header == 0xFF){							
  pw_data = RX_DATA();					  // get the second and third byte from rxreg.
  serial_printf(pw_data);  
  }
  if (!cren){										   // avoids conflicts
  PWM(pw_data);								// call the PWM function
  }
  header = 0x00;								  // clear register.

  }
}
//
//


// PWM function
#define PW_0 latb.0					 // PW 0 ON/OFF (violet).
#define PW_1 latb.1					 // PW 1 ON/OFF (yellow).

char flag = 0x00, aux = 0x00;

void PWM(short data_ch){

  char data = data_ch;

  char ch = data_ch>>8;	 // evaluates the channel number.

  if test_bit (!flag,0){			// toggle between duty cyycle and data cycle.

	switch (ch){
	case 0:
	PW_0 = 1;					   // out Hi (SERVO horizontal).
	break;
	case 1:
	PW_1 = 1;
	}
	tmr0l = data/2;				   // range 0 to 127. 2ms = 0x7F(min), 1ms = 0x00 (max); 0x40 middle.
	t0con = 11000010b;		  // precaler 1/8, 8bit enable.
	tmr0if = 0x00;					// clear INTCON overflow bit.
	while (!tmr0if);
	set_bit (flag,0);				  // switch between duty and PWM time.
	PW_0 = 0;						 // out Lo (SERVO horizontal).
	PW_1 = 0;
	tmr0l = 0x70;					 // duty cycle.
	t0con = 11000110b;		  // precaler 1/128, 8bit enable.
	tmr0if = 0x00;					// clear INTCON overflow bit.
	}

  if (tmr0if){
	clear_bit (flag,0);			   // switch between duty and PWM time.
	tmr0if = 0x00;					// clear TMR0 overflow bit.
	}
  }
//
//

So as you see there is still not a ISR routine involved.

Do I have to call the ISR routine inside the PWM function, so if a header byte (0xFF) is received that the program picks up the other two bytes?

Thanks Martin

Edited by Pavel
inserted code tags around the C code

Share this post


Link to post
Share on other sites

Hi Martin

 

I did mention the ISR because you said that your problem was the interrupt handling.

Normally we don't call ISR from code, it gets called by hardware when an interrupt is issued, provided that you have interrupts enabled.

ISR stands for Interrupt Service Routine, and its the function that handles the interrupts genereted by hardware.

In Boost C it is a special function declared with the "interrupt" or "interrupt_low" modifiers.

 

By what I can see in your code you are not using interrupts, you are pooling the interrupt flags, witch is a totally different thing.

 

 

After examinig your main() function I think that the processing of a continuos data stream from the PC is using up too much processing time.

This is making the calls to the PWM function too far apart thus originating breaks in what should be a continuos PWM signal.

When you send one single command, then your main() function will have nothing else to do except care for the PWM generation keeping it continuos enought to have the servo stable.

 

 

Trying not to go too far apart from your logic I would suggest that you do something like this:

 

Set up the PWM generator as a Numerically Controled Oscilator and process it in an ISR.

Keep the main() function doing the communications processing.

In this way the generation of the PWM signal will occur with a higger priority than the communications, because the interrupt routine will keep processing it even during the communication.

 

The code in te main function will be similar to what you have now, except that the received data will be processed there and the results will be stored as the control values for the PWM signal (TimeON and TimeOFF).

 

The ISR will be fired from the TMR0 overflow interrupt, and all it has to do is to change the output level and reload timero with the value from TimeOn or TimeOFF depending on the output state.

 

By the way, if you are echoing the comunications data back to the PC just for test/debug, get rid of it as soon as possible, it is costing you a lot of processing.

 

 

 

Hope it helps

 

Best regards

Jorge

 

PS:Use the "<>" button on the editor toolbar, to have your source code text formated with synatx highlight. It will be much more readable.

Edited by JorgeF

Share this post


Link to post
Share on other sites

Hi Martin

 

 

Yesterday night I forgot one other sugestion I have for you.

 

You mentioned that you are feeding a continuos data stream from the PC, in order to hve the servos follow some variable input (a slider)..

 

Maybe you can reduce the communications workload by only sending data from the PC when there is an efective change in the inputs. There is no point in sending the same vaue again and again.

When the inputs are steady so the last data sent will be. As the TimeON and TimeOFF values are stored in the PIC, the PWM signal will keep the servo in a steady position without the need for adicional data from the PC.

 

 

Best regards

Jorge

Share this post


Link to post
Share on other sites

Thanks for the helpfull tips.

I' ll do what you recommend this weekend and I let you know of the (hopefully) succseful results.

 

Only one thing: you refer to numerically controlled Osc., that I use the TMR0 timer to toggle between the TimeON/TimeOff cycles as I do in my PWM function?

And I understood that it is better to move my PWM function to the main function, right?

In case if there is a timer overflow (hardware interrupt) and if I have a function called void interrupt(void) the program jumps automatically to that (ISR) routine without even declaring the function?

 

Kind regards Martin

Share this post


Link to post
Share on other sites

Hi

 

By the way you refer to the interrupts processing it looks like you are not used to how they work and how to use them.

Probably a good review of section 9.0 of the PIC18F4420 datasheet (http://ww1.microchip...eDoc/39631E.pdf) can be usefull.

 

In a nutshell, Numerically Controled Oscillators are oscillators that have their TimeON and TimeOFF controled by counters (not much different from your PWM function above).

They set the output Low and start a TimeOFF counting. When the counter reaches the end, switch output to High and start TimeON counting. When the counter reaches the end go back to beginning, on an endless loop.

This is the basic, then we can add things like a start/stop flag, a third counting for an idle interval, or other extra features we need for the job at hand.

 

 

About the program suggestions.

What I mean is that after receiveing the data from the PC, you do the maths to obtain the TimeOn and TimeOFF values for the N. C. Osc. and store them in variables.

 

The ISR will read those variables to control a software implementation of the NCO as described above.

You can use TIMER0 or any other, the PIC you are using has 4 timers/counters. You will need 2, one for each servo.

 

 

Yes, to set up an ISR function is BoostC you use the special names "interrupt" and "interrupt_low" (I messed up in a previous post and wrongly called this names modifiers).

 

The PIC18 family has 2 interrupt levels, one with high priority and another with low priority.

The BoostC functions for the ISRs are "void interrupt(void)" and "void interrupt_low(void)". the compiler identifies this names and codes the functions in a special way.

These functions are declared and coded like any other 'C' function.

The only thing diferent is that they are called by hardware events (interrupts) and not directly in code.

 

 

 

Best regards

Jorge

 

PS:

If you feel you can profit from some tutorials, I would suggest http://www.gooligum.com.au/ it has a series of tutorials that can help clarify most of the concepts I've been talking about, and also some important details on PIC hardware. They don't use such a powerful and complex PIC as you are using, but the concepts and principals are all there, Also they don't use BoostC, but with some minor compiler related differences 'C' is 'C'.

Don't need to do the lessons based on their hardware, carefull reading should be enought.

Edited by JorgeF

Share this post


Link to post
Share on other sites

Thanks a lot.

Yesterday night a changed my code and I use now the ISR void intererupt(void) to reset the tmr0io timer overflow flag.

The code works now much better then my first version. But still, as I use only the TMR0 to handle both servos my code is a little dirty. (I wrote it before I red your reply).

I understood the ISR related hardware interrupt. Now if you mention to write the PWM with a counter (TimerOn/TimerOff) where does the ISR get the interrupt flag from.

Thanks Martin

My code is now:

#pragma CLOCK_FREQ 4000000						    // CLK 4Mhz.
#define PW_0 latb.0								   // PW 0 ON/OFF (violet).
#define PW_1 latb.1								   // PW 0 ON/OFF (yellow).

void interrupt(void){
 tmr0if = 0x00;								   // clear INTCON overflow bit
 tonoff = ~tonoff;							    // TimerOn/TimerOff.
}
//
//
void main(void)
{
 osccon = 01100000b;						    // OSCCON int Osc, 4 Mhz.
 while (!iofs);									  // DEBUG without iofs OSCCON stable bit.

 init();											 // Port settings etc.
 char header = 0x00;								 // 1st.header 0xFF, 2nd channel fro ROBOREALM.
 char data, ch = 0;
 short pw_data = 128;							    // Servo middle position.

  while(1){
   _asm clrwdt;							    // the watchdog.

  if (rcif) {
  header = rcreg;								 // 1st reads header.
  }
    if (header == 0xFF){
	  pw_data = RX_DATA();
	  header = 0x00;							  // clear register.
	  }
	  data = pw_data;
	  ch = pw_data>>8;

	  if (tonoff && help){

	    t0con = 11000010b;					    // precaler 1/8, 8bit enable.				  
	    tmr0l = data/2;						   // range 0 to 127. 2ms = 0x7F(min), 1ms = 0x00 (max); 0x40 middle.
	    help = false;
	    switch (ch){
	    case 0:
	    PW_0 = 1;								 // out Hi (SERVO horizontal).
	    break;
	    case 1:
	    PW_1 = 1;								 // out Hi (SERVO horizontal).
	    }
	   }

	    else {
		  if ((!tonoff) && (!help)){
		  t0con = 11000110b;					   // precaler 1/128, 8bit enable.
		  tmr0l = 0x70;						    // (DEBUG before 70) data for servo turn.
		  PW_0 = 0;							    // out Lo (SERVO horizontal).
		  PW_1 = 0;
		  help = true;
		  }
	   }
    }
}
//
//

Share this post


Link to post
Share on other sites

Hi Martin

 

You mentioned that you are using 50Hz servos.

If so you only need to repeat the PWM positioning pulses every 20mS for each servo.

 

I think you should set up a global 20mS time frame with the help of one of the PIC timers, and use another timer for generating the pulses.

At the start of each 20mS period you generate a pulse for Servo1, then for Servo2, and then wait till the end of the 20mS period and start it all over again.

 

This will control the 2 servos that you have now, and allow you to had more servos, up to 9 , latter without the need for a full new program.

Of course you have to set up two sets of timing variables TimeOn/TimeOff, one for each servo.

 

 

My ususal aproach for the implementation of the NCO is slightly diferent from yours.

In the main() function I would have calculated the TimeOn/TimeOff values for a given servo (channel) and stored them in global variables.

 

In the ISR, besides changing the output level, I would have also do the reloading of the timer with the counting for the new state.

In this way the 2 processes, the NCO and the communications and control will be tottaly independent. The communication bettewn the 2 processes will be done by the values TimeOn and TimeOff.stored in variables.

 

 

A note of experience.

My usual approach to MCU based projects is to see them as an electronic project.

I start by designing what would be a modular description of a discreet electronics project.

Then a implement each module in software and use global variables to be the control signals that interconnect the different modules.

Thats way I describe programs as functional modules, like counters, oscilators, and so.

 

 

I have implemented a number of NCOs in software with all kinds of bell and whistles in the past.

During the weekend I'll try to find one that you can use as an example to fine tune your own implementation.

 

 

Best regards

Jorge

Edited by JorgeF

Share this post


Link to post
Share on other sites

Hi Martin

 

I did manage to find a simple project that you can use as an ilustration of my previous suggestions.

I'm attaching it to this post.

 

A little history

I'm new to the PIC18 family of MCUs and also to BoostC. For the last 5 years, I've been programming low end PIC12/16 MCUs in assembly.

I was studiyng BoostC and PIC18 during the Easter holydays, and my 14 year old kid, fed up with the playstation, decide to get curious about what I was doing.

The result of his curiosity is this small program that flashes a row of LEDs with a duty cycle derived from a potentiometer.

The flashing runs from LED1 to LED8 and then cycles back in an endless loop.

 

The test hardware is a PICDEM PIC18 Explorer Board from Microchip (DM183032) with a PIC18F8722 mcu (you can find the schematics in the user guide available for download at the Microchip site).

 

Uses Timer0 as a time reference.

Uses an interrupt driven NCO built around the variables LedTimeOn, LedTimeOff and LetTimer.

Reads the potentiometer via an ADC chanel and uses it to adjust the Led On time in a range from 1/10 sec to 9/10 sec.

After each flash cycle of 1 second it shifts to the next LED.

The mani() function handles the ADC and sets the parameters for the NCO.

Timer0 interrupts are used for all the timing and for managing the NCO to generate the pulses to light the LEDs.

 

Its not exactly "state of the art" but maybe you can find something useful for your project.

At least it ilustrates all the concepts we've been discussing.

 

Pay attention to one thing, the DEBUG project options are set to the Microchip ICD3 programmer/debugger..

 

 

Best regards

Jorge

FLASH_LEDS.zip

Edited by JorgeF

Share this post


Link to post
Share on other sites

Hello Jorge.

I just made an project in MPLAB with your code and I can't find the functions below. Is there code missing?

 

InitLeds(); // Set Leds control initial status

InitBugFlags(BugFlags); // Init flags that signal abnormal rpogram behaviour

Timer0_Config(); // Configure Timer 0

ADC_Config(); // Configure ADC

ADC_On(); // Turn ADC On to start sampling

Load_ADC_Delay(); // Start a delay before first ADC convertion

Enable_Tmr0_Int(); // Enable Timer0 interrupts

Enable_Interrupts(); // Enable global interrupts to start timing

Timer0_On(); // Start Timer0

 

Yesterday I made an other variant of my program using TMR0 as a 50Hz time-frame and TMR1 to trigger the pulses for each SERVO.

 

Following your advice I did the following:

Start the timer0 (50hz) and than send a pulse (using timer1) for Servo 1 and right afterwards I repeat the same for Servo 2 and after 20ms, because of the interrupt of timer0 the procedure starts again.

It works, but I have max. 4ms where I can't get data from the USART (or from an analog input).

In your code description the assignment of the potentiometer changes after 1 sec. My idea is more to have something like a joystick where I send data in real time to the Servos, like a remote controlled airplane).

 

Also, you are talking about TimeON and TimeOFF variables. This I don't understand. Isn't the off-time fixed already by the duty cycle (20ms, timer0)? So, if I send, for example, a pulse of 1ms, setting the appropriated Port Pin to Hi and after this periode again to Low, why I do need the TimeOFF variable?

 

Something I still have quite wrong I guess.

Sorry for my ignorance

Martin

Share this post


Link to post
Share on other sites

Hi Martin

 

Hello Jorge.

I just made an project in MPLAB with your code and I can't find the functions below. Is there code missing?

 

InitLeds(); // Set Leds control initial status

InitBugFlags(BugFlags); // Init flags that signal abnormal rpogram behaviour

Timer0_Config(); // Configure Timer 0

ADC_Config(); // Configure ADC

ADC_On(); // Turn ADC On to start sampling

Load_ADC_Delay(); // Start a delay before first ADC convertion

Enable_Tmr0_Int(); // Enable Timer0 interrupts

Enable_Interrupts(); // Enable global interrupts to start timing

Timer0_On(); // Start Timer0

 

These are not functions. These are macros defined in the "SeqLedFlash_Config.h" include file.

It seems you forgot an "#include" statement in one or more 'c' files that use these macros.

 

Yesterday I made an other variant of my program using TMR0 as a 50Hz time-frame and TMR1 to trigger the pulses for each SERVO.

 

Following your advice I did the following:

Start the timer0 (50hz) and than send a pulse (using timer1) for Servo 1 and right afterwards I repeat the same for Servo 2 and after 20ms, because of the interrupt of timer0 the procedure starts again.

It works, but I have max. 4ms where I can't get data from the USART (or from an analog input).

 

The only possible reason I can imagine for those 4mS seconds is that you are using active delays in your timing instead of interrupts.

Are you keeping the processor in a closed loop waiting for the end of the Timers, like in your first program?

 

If you check the sample project I sent you, you can see that it is not activelly waiting for the peripherals (Timer0 and ADC).

By active waiting I mean this " while(!test_bit(intcon,T0IF)); " or " while(test_bit(adcon0,GO_DONE)); ". The processor is prevented from doing other things while the counters are running.

 

 

In your code description the assignment of the potentiometer changes after 1 sec. My idea is more to have something like a joystick where I send data in real time to the Servos, like a remote controlled airplane).

 

You got it wrong.

The potentiometer ir read in a free run fashion. I just inserted a small "6*1.64 mS" delay bettwen ADC convertions just because there is no need to be reading it so fast. In this particular program there is no meaning on it. But in a bigger application I would be handling the ADC with interrupts and I don't want to chocke the processor with interrupts every few uS (the time it takes to finish an ADC covertion).

 

Also, you are talking about TimeON and TimeOFF variables. This I don't understand. Isn't the off-time fixed already by the duty cycle (20ms, timer0)? So, if I send, for example, a pulse of 1ms, setting the appropriated Port Pin to Hi and after this periode again to Low, why I do need the TimeOFF variable?

 

Something I still have quite wrong I guess.

Sorry for my ignorance

Martin

 

Ok. let's get back to basics.

A generic PWM signal as 2 caracteristics. The period and the duty cycle.

The period (or frequency) is fixed and defines the repeat rate of the pulses. The duty cycle is variable and is defined as the ratio betwen the active and inactive parts of the period.

Giving TimeOn+TimeOff = Period and this value beeing constant.

 

For example, flashing a LED at 2HZ with 50% duty cyle:

Period = 1/2 Second

TimeOn = TimeOff = 1/4 Second

For a 75% duty cycle, we will have TimeOn = 3/8 Second and TimeOff = 1/8 Second.

 

In the sample project I sent you you can see that I had set up a NC Oscilator with a period os 1 Second and the duty cycle variying from 10% to 90% acording to the value from the ADC reading.

 

 

Now lets see how to apply this to your specific situation.

The PWM pulses for the servos have a special caracteristic the TimeOn [0.5-1.5 mS] is so much smaller than the period [20 mS], that instead of seeing at as continuos oscilation, it can be looked like a train of isolated pulses.

This aproach is even better, as the counter values for TimeOn [0.5-1.5 mS] and TimeOff [19.5 - 18.5 mS] are too far apart to be handled by the same counter in an eficient way.

 

So your aproach is correct.

Set up one counter (Tmer0) to generat the time base, the 20mS heartbeat.

And then use another one (Timer1) for the controling pulse. In this special case you don't need a TimeOff counting, the gap from the end of one pulse to the start of the next is controled by the 20mS time base.

 

Also you can use the interrupt at the end of one pulse, to start the pulse for the next servo.

This way you would be able to control up to 19 servos with the same counter (leave on slot for the processing overhead).

 

Your interrupt handler will be operating in the folowing sequence.

Int_Tmr0 -> Servo1 = high, start Timer1 with pulse width for servi 1

Int_Tmr1 -> Servo 1= low, Servo 2 = high. start Timer 1 with pulsse width for servo 2

Int_Tmr 1 -> Servo 2 = low. Stop timer 1 (no more servos to handle)

....................

Int_Tmr0 -> << start all over again >>

 

 

Just another thing.

You can use the code I posted whenever way it suits you, but I would suggest studying rather tan copying.

It has plenty of comments but also a high density of information. Take a good look on interrupt usage.

 

 

Best regards

Jorge

Edited by JorgeF

Share this post


Link to post
Share on other sites

Uff, there we are.

I had the ipen RCON,7 bit not set. The bit enables together with the gie INTCON,7 the high-priority interrupts (as you know of course).

Until know a Timer1 overflow never jumped to the ISR routine, this is why I tried all this funny tricks with time-loops etc. what, made the CPU running for only one task.

 

The (hopefully) very last question: In your LED code I learned that you configure several registers with marcos. Until know I used #define Registor 00000000b to configure the right value. Is the use of marcos better?

 

Thanks a lot for wasting so mach time

Martin

Share this post


Link to post
Share on other sites

Hi Martin

 

Do you really need to use the full-fledged PIC18 interrupts system, with priorities and all?

Unless required by the project at hand, I would suggest that you use the PIC16 compatible mode, Its simpler without two priority levels and is the default at power-up.

 

If you decide to use interrupts with priorities, make sure you have a "interrupt_low()" function in place, to catch interrupts configured for low priority. I allways set up interrupt handlers even if simple dummies. In my sample program you can find a trap for unexpected interrupts.

 

 

About the defines.

When I start a new project, after I decided what PIC and what peripherals and operating modes I'm going to use.

I make a tour of the datasheet to review the configurations I'm going to need.

During this tour I write down the base configurations for the the relevant SFRs, check default power up values and also the action bits within them.

Is during this process that I write down my "config" header (.h) files with all the constant values, macros to manipulate the control bits and do the initializations. I also cover the external circuitry details like I/O pin functions and what are active high or low, the signal timings and all that stuff.

I give all that constants and macros relevant names for to the job at hand.

 

For example:

"ADC_Start()", "ADC_Done(); Enable_Timer0_Int().

 

That makes it easier for me to write the application code without worrying about the details like "What SFR? What bit? Active high or low? What pin is conected to the LED? What pin is the motor control?.....

Doing datasheet and schematic research while writing the code makes me jump bettwen application logic and hardware details, thus breaking my line of thinking. This breaks tend to waste a awfull lot of time and originate a lot of bugs.

My experience says that this aproach reduces number of bugs thus reducing development time.

 

Another example

In the program I sent you as an example you can see that the "SetLedTime(VAL)" macro takes care of all the maths to adjust the light TimeOff in order to maintain the PWM period constant for the varying light TimeOn. Also takes care of the minimum On time.

 

 

I don't know if its something extraordinary, but I got used to write programs with several hundred lines of code (ASM, C), that run without bugs at first atempt. Maybe I got the practice from when an ICD was a very expensive luxury, or before that, from having to make school programs in Pascal, C, PL/M51,..., without having a computer at home, and having only 2 or 3 hours a week of access to one at the university.

It was like that in the begining of the eighties, before the PC or even the ZX-Spectrum saw the light of the day. :)

 

 

Best regards

Jorge

 

PS: I'm not wasting time. From time to time I default to my days as a teatcher in high school and university.

PS2: About R/C modeling. I still have about 8 R/C gliders, lots of servos and a Multiplex 3030 Master Edition transmiter lying around, from my aeromodeling days when I took part in some competitions in FAI oficcial classes F3F and F3B.

Edited by JorgeF

Share this post


Link to post
Share on other sites

Hi Jorge.

 

I was for two ill, but yesterday I went ahead with the program.

As I can't make the TMR1 jumping into the ISR routine without setting the RCON ispen flag Hi, I did the program flow the following way.

I start (enable) the TMR1 in the ISR routine and handle the rest in the main routine, and then, after the second Servo pulse is executed, I disable the TMR1. This works but with durty jumps when I fiddle with the sliders (not smooth enough).

If I set the RCON ispen bit, the receiver flag rcif is also handled in the isr ROUTINE.

Do I have to handle the receiving data and the PWM in the ISR routine?

 

Thanks Martin

Attached I send you the code.

 

void interrupt(void){
 //reload 20ms periode time.
 if (tmr0if){
 tmr0l = 100;								   // Value for TIMER0: 100d.= 20ms - 50Hz
 tmr0if = false;							    // Clear INTCON overflow bit.(TMR0)
 count = 1;
 PW_0 = true;								   // Strats SERVO 1 pulse.
 tmr1on = true ;							    // Enable TIMER1.
 tmr1if = false;							    // PIR1 (clear timer1 overflow flag)
 tmr1h = TIME1_HI;							  // Load fixed value into TIMER1 register;
 tmr1l = TIME_ON1;							  // Variable value for Servor 1.

  }
}
//
//

 

 

void main(void)
{
 osccon = 01110000b;						   // OSCCON int Osc, 8 Mhz.
 while (!iofs);									 // DEBUG without iofs OSCCON stable bit.

 init();										    // Port settings etc.
 short pw_data = 120;							   // Servo middle position.
 char header = 0x00, ch;						    // 1st.header 0xFF, 2nd channel fro ROBOREALM.
  while(1){

  _asm clrwdt;							  // The watchdog.
    if (rcif) {
    tmr0on = false;
    header = rcreg;							   // 1st reads header.
    }
	 if (header == 0xFF){
	  pw_data = RX_DATA();
	  header = 0x00;							  // Clear register.
	  tmr0on = true;
	  ch = data_assigne(pw_data);
	  }

		 if ((tmr1if) && (count == 1)){
		  PW_0 = false;						    // Stops SERVO 1 pulse.
		  PW_1 = true;							 // Starts SERVO 2 pulse.
		  tmr1if = false;						  // PIR1 (clear timer1 overflow flag).
		  tmr1h = TIME1_HI;					    // Fixed value;
		  tmr1l = TIME_ON2;					    // Value to be loaded for Servor 2.
		  count_aux = count++;
		  }
		 if ((tmr1if) && (count == 2)){
		  PW_1 = false;						    // Stops SERVO 2 pulse.
		  tmr1on = false;						  // disable TIMER1.
		  count = 0;
	    }
  }
}
//
//

 

 

#ifndef _C_18F4420_SERVO_V1_H_
#define _C_18F4420_SERVO_V1_H_
#ifndef false
#define false 0
#endif
#ifndef true
#define true !false
#endif
#include <system.h>
#include <icd2.h>
#define TIMER_ON_OFF 1
#define HELP 2
#define PW_LED latc.4		    //Power (yellow) LED.
volatile bit tonoff@TIMER_ON_OFF.0;
volatile bit help@HELP.0;
volatile bit Go_Done@ADCON0.1;   // Note the 16fxx chip puts this in bit 2.
volatile unsigned char ad_h@ADRESH;
volatile unsigned char ad_l@ADRESL;
volatile bit iofs@OSCCON.2;	 // OSCCON stable bit.
volatile bit trmt@TXSTA.1;    // transmit shift registor.
volatile bit tmr1if@PIR1.0;    // TIMER1 overflow bit.
volatile bit tmr1on@T1CON.0;   // TIMER1 enable bit
volatile bit tmr1ie@PIE1.0;	 // TIMER1 enable overfolw bit.
volatile bit rcif@PIR1.5;	  // transmit shift registor.
volatile bit cren@RCSTA.4;    // 1 enable receiver.
volatile bit tmr0ie@INTCON.5;   // TIMER0 enable overfolw bit.
volatile bit tmr0if@INTCON.2;   // TIMER0 overflow bit.
volatile bit int0if@INTCON.1;   // TIMER0 external overflow bit.
volatile bit tmr0on@T0CON.7;   // TIMER0 enable bit.
volatile bit gie@INTCON.7;	 // General interrupt flag.
volatile bit peie@INTCON.6;	 // General interrupt flag.
volatile bit ipen@RCON.7;	   // Interrupt Prioity enable.
volatile bit t0@RCON.3;		 // Watchdog overflow bit.Lo = WDT overflow.
#define INTCON_CONF  10100000b   // GIE enabled, TMR0IE enables.
#define INTCON3_CONF 10001000b   // INT1IE, enabled.
#define RCON_CONF    00011100b   // Interrupt priority disabled.
#define PIE1_CONF    00000001b
#define TIME0_CONF   11000111b   // Assigned to T0CON presc. 1/256, 8 bit, enabled.
#define TIMEFR_VAL   01100100b   // value of 100d.= 20ms - 50Hz.
#define TIME1_CONF   00111000b   // T1CON, 1/8 presc. Timer1 disabled.
#define TIME1_LOW    10000100b   // (middle pos.) 0xFF = 1ms; 0x00 = 2ms.
#define TIME1_HI	 11111110b   // to get the above times in ms.
short RX_DATA();				 // get data from rcreg.
void init();					 // init PORT, timers, interrupts etc.
char data_assigne(short);	    // assignes data to corresponding Servor Motor.
#endif // _C_18F4420_SERVO_V1_H_

Share this post


Link to post
Share on other sites

Hi Martin

 

After a quick browse odf your code it seems that you still have a logic problem.

You have servo pulse generation mixed with communications.

 

For instance, you stop timer1 when you start receiving data, and then restart it again.

If data arrives in the middle of one servo control pulse, the pulse will be extended for the duration of the communication.

 

Probably you won't notice a thing if this happens to only one pulse and then the communication stops and things get back to normal.

But when you start receiveing a steady flow of data packets, there will be plenty of distorted control pulses and your servos will go crazy.

 

 

I would suggest you move the full processing of Timer1 to the ISR and leave only the communication process to the main() function.

 

 

Best regards

Jorge

Share this post


Link to post
Share on other sites

Hello very patiently Jorge.

Yes I understand your advise, but my problem is, to make that a TMR1 overflow jumps to the ISR I have to set the RCON ipen bit (I could not yet find an other solution). So if I set the ipen bit, data received indicated by the rcif bit jumps also to the ISR. I would have to put mostly of my code to the ISR, right?

Kind regards Martin

Share this post


Link to post
Share on other sites

Hi Martin

 

My sugestion is to keep the data receiving code in the main function.

Move only the Timer0 and Timr1 to the ISR.

 

I don't exactly see how you are configuring interrupts, so here are some guidelines how i would do it:

 

Starting from power on defaults

 

1 - Leave "rcon.IPEN = 0" (this will set the interrupt mode to single priority, so the iPR registers are out of duty and you only have one interrupt vector.

2 - Set "intcon.PEIE = 1" (this allow interrupts from peripherals - Timers 1, 2 ....., ADC,... EUSART .......) This is needed because Timer1 is in the peripherals group.

 

When needed:

 

- Enable/Disable timer0 overflow with "intcon.TMR0IE"

- Test/Clear Timer0 overflow with "intcon.TMR0IF"

- Enable/Disable Timer1 overflow interrupt with "pie1.TMR1IE"

- Test/Clear Timer1 overflow with "pir1.TMR1IF"

- Test for data reception from PC the same way you are doing now, by pooling "pir1.RCIF"

- Globaly Enable/Disable all interrupts with "intcon.GIE".

 

Comunications with the PC will only generate interrupts if "pie1.RCIE" (receive) or "pie1.TXIE" (transmit) are enabled (= 1).

 

 

Another thing, you don't need to disable inetrrupts when receiving data from the PC. The all idea behind interruots is to be able to keep certain things running in a timely fashion while doing something else.

 

The value for the servos position, should be stored in a couple of global variables (one for each servo) when they arrive from the PC.

In the ISR just read the values from those variables when needed for loading Timer1.

 

This way you can even disconnect the serial cable from the PC and the servos will stay put at the last commanded position, because it is stored in the variables on the PIC.

 

 

Best regards

Jorge

Edited by JorgeF

Share this post


Link to post
Share on other sites

Yeep Jorge,

So I did and I get a continuous puls for both SERVOS. Only when I set the INTCON GIE and PEIE bit high also the pie1.RCIE jumps to the ISR. Now, what I did is, I handle the RCIE data also in the ISR. My main code only assigns the incoming data with global variables to its corresponding SERVOS.

Uff, it works and connecting a oscilloscope, the PWM locks stable and continuous and incomming data, as far as I can see it, want affect it's flow, but still, if I turn the slider my SERVO jumps.

I suppose if I get it work to seperate the RCIE from the ISR the result will by finally how it has to be.

If you are so kind to have a quick glance at my ISR weather my logic is right, I would appreciate it a lot.

Cheers Martin

 

void interrupt(void){
 char count_aux, dummy;
 //reload 20ms periode time.
 if (tmr0if){
 tmr0l = 100;								   // Value for TIMER0: 100d.= 20ms - 50Hz
 tmr0if = false;							    // Clear INTCON overflow bit.(TMR0)
 count = 1;
 PW_0 = true;								   // Strats SERVO 1 pulse.
 tmr1on = true ;							    // Enable TIMER1.
 tmr1if = false;							    // PIR1 (clear timer1 overflow flag)
 tmr1h = TIME1_HI;							  // Load fixed value into TIMER1 register;
 tmr1l = TIME_ON1;							  // Variable value for Servor 1.
 ID_LED = false;
   }[/font][/color]
[color=#282828][font=helvetica, arial, sans-serif]    if ((tmr1if) && (count == 1)){
  PW_0 = false;								 // Stops SERVO 1 pulse.
  PW_1 = true;								  // Starts SERVO 2 pulse.
  tmr1if = false;							   // PIR1 (clear timer1 overflow flag).
  tmr1h = TIME1_HI;							 // Fixed value;
  tmr1l = TIME_ON2;							 // Value to be loaded for Servor 2.
  count_aux = count++;
  return;
  }
   if ((tmr1if) && (count == 2)){
  PW_1 = false;								 // Stops SERVO 2 pulse.
  tmr1on = false;							   // disable TIMER1.
  count = 0;
  return;
  }[/font][/color]
[color=#282828][font=helvetica, arial, sans-serif]	 if (rcif) {
  header = rcreg;							   // 1st byte is header.
  while ((!rcif) || (!t0));					 // t0 is a fuse to avoid endless looping.
  ID_LED = true;
  ch	 = rcreg;							   // 2nd byte is channel.
  while ((!rcif) || (!t0));					 // t0 is a fuse to avoid endless looping.
  pw_data = rcreg;							  // 3rd byte is data.
  }
}

 

void main(void)

{

 

osccon = 01110000b; // OSCCON int Osc, 8 Mhz.

while (!iofs); // DEBUG without iofs OSCCON stable bit.

 

init(); // Port settings etc.

 

//short pw_data = 120; // Servo middle position.

char count_aux;

 

 

while(1){

 

_asm clrwdt; // The watchdog.

 

if (header == 0xFF){

header = 0x00; // set header back.

switch (ch){

case 0:

TIME_ON1 = pw_data;

break;

case 1:

TIME_ON2 = pw_data;

}

}

}

}

//

//

Share this post


Link to post
Share on other sites

Sorry the code was wrong pasted, here again:

 

 

void main(void)
{
 osccon = 01110000b;						   // OSCCON int Osc, 8 Mhz.
 while (!iofs);									 // DEBUG without iofs OSCCON stable bit.

 init();										    // Port settings etc.
 //short pw_data = 120;							   // Servo middle position.
 char count_aux;

  while(1){

  _asm clrwdt;							  // The watchdog.

	 if (header == 0xFF){
	    header = 0x00;						    // set header back.
	    switch (ch){
	    case 0:
	    TIME_ON1 = pw_data;
	    break;
	    case 1:
	    TIME_ON2 = pw_data;
	  } 
	 }
 }
}
//
//

 

void interrupt(void){
 char count_aux, dummy;
 //reload 20ms periode time.
 if (tmr0if){
 tmr0l = 100;								   // Value for TIMER0: 100d.= 20ms - 50Hz
 tmr0if = false;							    // Clear INTCON overflow bit.(TMR0)
 count = 1;
 PW_0 = true;								   // Strats SERVO 1 pulse.
 tmr1on = true ;							    // Enable TIMER1.
 tmr1if = false;							    // PIR1 (clear timer1 overflow flag)
 tmr1h = TIME1_HI;							  // Load fixed value into TIMER1 register;
 tmr1l = TIME_ON1;							  // Variable value for Servor 1.
 ID_LED = false;
   }
   if ((tmr1if) && (count == 1)){
  PW_0 = false;								 // Stops SERVO 1 pulse.
  PW_1 = true;								  // Starts SERVO 2 pulse.
  tmr1if = false;							   // PIR1 (clear timer1 overflow flag).
  tmr1h = TIME1_HI;							 // Fixed value;
  tmr1l = TIME_ON2;							 // Value to be loaded for Servor 2.
  count_aux = count++;
  return;
  }
   if ((tmr1if) && (count == 2)){
  PW_1 = false;								 // Stops SERVO 2 pulse.
  tmr1on = false;							   // disable TIMER1.
  count = 0;
  return;
  }
 if (rcif) {
  header = rcreg;							   // 1st byte is header.
  while ((!rcif) || (!t0));					 // t0 is a fuse to avoid endless looping.
  ID_LED = true;
  ch	 = rcreg;							   // 2nd byte is channel.
  while ((!rcif) || (!t0));					 // t0 is a fuse to avoid endless looping.
  pw_data = rcreg;							  // 3rd byte is data.
  }
}
//
//

Share this post


Link to post
Share on other sites

Hi Martin

 

If the EUSART interrupts are calling the ISR its because you have them enabled as interrupts (pie1.RCIE=1).

You only set this flag if you want to process data receiving in the ISR.

If you want to handle the serial port reception in "main" by pooling "pir1.RCIF", like in your initial code, you must keep "pie1.RCIE=0" (interrupt disabled)..

 

 

The interrupt flags (IF) are independent of their respective interrupt enable (IE) masks.

When data is received by the EUSART, "pir1.RCIF" is allways set whatever the value of "pie1.RCIE" and "intcon.GIE".

The interrupt enable (IE) masks only control if an interrupt is generated (call to ISR) or no.

 

Take a good look at figure 9-1, section 9, page 92 of the PIC18F4420 datasheet.

You will notice that the IE signals are "anded" with the IF signals in the interrupt control circuitry, but the IFs are not dependent on IEs.

 

 

Best regards

Jorge

 

PS: You could have edited the post, instead of repeating it.

Edited by JorgeF

Share this post


Link to post
Share on other sites

Hi Martin

 

If your servos are jumpy, that might be a all new ball game.

Thats a common problem in automation and control, due to mechanical inertia and slow response when compared to electronics.

This is sorted out with the so called PID (Proportional Integral Diferencial) system response

.

In a nutshell, your input data, from the sliders, might be varying too fast for the servos and even for the comunications process.

A quick solution would be to limit the variation rate. This can be done either at the destination (PIC) or at the source (PC).

 

The simplest way is to have 2 values for each servo. The "current value" and the "target value".

And then use a counting system to increment/decrement the current value until it reaches the target value. This incrementing/decrementing should be slow enought for the servos to handle. This dumping process, after put in place can be fine-tunned by testing.

 

The target value is what come from the input devices (slider, joystick, ....). And the current value will be the one to feed to the servos.

If properlly tuned, this dumping is invisible. The slowliness of the variation is in "electronics time" what is allways very fast in terms of "mechanical time" or "humam time".

 

 

Best regards

Jorge

 

PS: RC transmiters use PID damping. When you move a stick very fast or have a feature controled by a On/Off switch (open cockpit, retract landig gear, deploy landing flaps,...) you can notice a delay in the servo response due to slew rate limitation.

Edited by JorgeF

Share this post


Link to post
Share on other sites

Hello Jorge.

Yes, as I use a wireless module (Easy Radio ER400TRS) at a baudrate of 38400 a have to investigate first whether the data arrive correctly or if I have already bytes missing during data transfer.

May I'm in the right in thinking to create an array for the incoming data (as a buffer) and adjust the SERVOS as fast as possible to the target value afterwards?

 

PS. Last night when I was already in bed I had the flash that I don't have to handle the RCIF flag in the ISR. So I got up again and I moved it to the main function. Uff, this was a long one .....

Thanks very much for your help

Martin

Share this post


Link to post
Share on other sites

Hi Martin

 

 

All this talk about servo control has been echoing at the background of my head.

 

I have several pieces of code, also lots of hardware, lying around as parts to build a smart toy veichle (line tracking, proximity detection. etc, etc..) for my kids.

Now that hollydays are aproaching, this free time project surfaced again.

 

Its here were you play a part.

This all discussion around servo control convinced me that a few servos can be usefull for the above mentioned toy. At least a rotating head or some kind of robotic arm.

 

 

A couple of days ago, I missed a flight and found myself stuck in a train with about 3 hours to burn.

Having nothing better to do, I started another piece of scrap code, this time on the subject of servo control.

I used most of what we have been discussing in this topic and even borrowed some numbers and pieces from your code.

 

Besides some details that are dependent of external hardware and PIC connections, The code skeleton as it all including communications buffering and servo movement speed limitation.

From my years of R/C aeromodeling I think this might even be usefull for other modelers wanting to build a, allways usefull, servo tester or some other utility.

 

So I'm sharing this scrap code with you all.

Caution: This is not working code, its just a basic skeleton of a few ideas, with lots of blanks.

 

 

Best regards

Jorge

Servos_Control.zip

Edited by JorgeF

Share this post


Link to post
Share on other sites

This is great,

I just printed the code and I go into it this days.

 

My two SERVOS work now quite nice, but still, my main problem is the data transmission. With my Easy Radio(ER400TRS) I have some problem to send packages of three bytes: "header" 0xFF; "channel" 0x00/0x01) and "data" 0x00 to 0xFF.

So, the past weekend I made a new protocol sending only two bytes. 10 bits for the SERVO data and 6 bites for addressing.

This works best until know.

Probably I forget the EASY RADIO and do the wireless transfer with the NORDIC 24L01. This units allow sending packages until 32 bytes. Two month ago I made a small PCB with an accelerometer and a gyro sending 6 different data wireless to the computer. It works very stable and fast.

Thanks a lot for your help

Martin

Share this post


Link to post
Share on other sites

Hi

 

The way I handle communications, you can send one byte a time.

The data will be received and properly handled even if there is a long gap betwen each byte.

The finite state machine that I use has no timeout, so it handles data as it arrives, and after each byte it will wait indefinitely for the next one.

 

Note that I also didn't implement any kind of flushing neither resyncronization feature.

If you want to use that approach, you better test for some special marker (maybe your 0xFF) and reset the state machine to a proper status if that byte arrives out of sequence.

As it is, if the data link gets broken, and one data byte or both are missing, the next header will be treated as data (chanel or position).

 

 

 

Best regards

Jorge

Edited by JorgeF

Share this post


Link to post
Share on other sites

Hi Jorge.

I have seen that you handle the RX data also in the ISR. Unfortunately your code want run in Sourceboost neither in MPLAB so (for me) it is not so easy to follow entirely your ideas.

Somehow I understand that you resend data to the servos every 20ms, but I can't find where you handle the "Elapsed20ms" variable.

Thanks Martin

Share this post


Link to post
Share on other sites

Hi

 

Of course it won't compile. I metined that its a skeleton.

you can see that there a lot of gaps "................." in it.

Understanding it is by reading/studying, the code and the comments.

 

I use the interrupt on receive to get the RX data and stuff it in the buffer, before another byte arrives.

Processing of this data is done in main.

 

The "Elapsed20mS" its a flag to mark the ocurrence of the Timer0 interrupt (20mS tick).

Its tested (and also reseted) in main, its the first step of the "while(1)" loop.

When the test comes out true, once each 20mS, the position info for each servo is recalculated, in order to reach the target position (from RX data) but not exceeding some speed limit that is defined in one of the header files.

Its the simplest form of damping that I mentioned earlier. Limiting the speed is not very interesting, but softening spikes is quite useful.

 

 

Best regards

Jorge

Edited by JorgeF

Share this post


Link to post
Share on other sites

Your content will need to be approved by a moderator

Guest
You are commenting as a guest. If you have an account, please sign in.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

×