RTOS for AVR

Go To Last Post
557 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

Which is the best and simple RTOS for AVR, I don't have experince in RTOS for AVR. Just start to programing with RTOS from MCS51 (Rtx51 tiny) that available only in round-robin task switching.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm familiar with two of them, although there are probably several others. The first is AvrX - fairly small and fast, but suffers from poor documentation. Tbe other is FreeRTOS, which is a bit bigger, far better documented and runs on several platforms besides AVR. You can find more on them in the Tools section, or just Google on the name for links to the home pages.

Dave

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Best and simple might be conflicting requirements. What might be best and simple for you might be unusable for me. There's a number of RTOS available for the AVR both free and for $. If you give us some specific requirements, then someone might be able to guide you better. It's a bit like asking 'what car should I buy'

Unless you're doing a project the really requires a RTOS, I wouldn't use one.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The Simple for me mean not take a long time to learning it, from my experience some project if we use RTOS it helpful to easy design. I try to find RTOS that available for Preemptive Priority-Based Scheduling, for GCC complier.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The use of RTOS for 8 bit uC is not really common, since they tend to be short in resources (specifically RAM). Thus the big uC like ATmega64-128-256 are on the limit and the ones that most commonly use RTOS. Anyway, I wrote a 110 KBytes of code program that runs without any RTOS, and it wouldn't be needed. I needed 4KBytes of RAM just for the application, thus no way to use any RTOS (that would need more than 128 bytes) in this application for ATmega128.

Bigger processos tend to use RTOS, like ARM's, and there is an intense debate about use them instead of the big megaAVR's in this forum. ARM's also use to have more RAM and other tools that are oriented to RTOS (see the PIT in AT91SAM7 family).

Guillem.

Guillem.
"Common sense is the least common of the senses" Anonymous.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've used AvrX on a mega168

--

"If it wasn't for bad luck I'd have no luck at all"

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Can we get a few 'rules of thumb' on when the extra effort pays off? The 'main loop with interrupts' works fine if most of the interrupts are small and fast, and all the subroutines in the main loop are short enough to allow the program to run 'real time'... you hit the switch and something happens before you have time to think 'oh crap.... its hung up'. This scheme starts to fall apart if there are a couple of things that need to happen at the same time... these lamps need to flash on the control panel while the user is keying in an access code at the same time and refreshing the display at the same time, and there is some comm task going on at the same time. ATM machine probably has a dozen tasks running. When two of these tasks take longer than a 20ms loop time, you need to put it in a task.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Recently, I've been doing a datalogger that writes data to a SD card. Due to the filesystem, the time it takes to write a block of data can vary a great deal. Since I usually use a co-operative tasker this causes a bit of a problem. I could re-write the filesystem code to work in a more co-operative fashion but in this instance a pre-emptive task switcher might be more appropriate. I looked at FreeRtos. Once you go to a preemtive method, get get extra complexity - every task has to be treated like an interrupt service routine in regards to sharing variables. So most RTOSes add methods to do this and so the learning curve increases.

Most people write code that runs in a linear fashion - from start to end. With a co-operative method you need to think in terms of states - get data, process,get out. Those who have played around with BasicStamps a lot should know these techniques as you have no choice.

As for the 'real timeness' of various tasks - serial comms needs to be real time - you need to send/receive those chars otherwise you'll miss receives. User interface - does the user know if you take 50mS or 100mS to respond? Probably not.

Most of my code comprises of serial comms that do Modbus, scan some pushbuttons for a user interface, update a LCD to show current values etc and a few other tasks to flash lights, honk horns, process alarms, do logic control for relays, read a/d and process the values. This is all done co-operatively with a timer tick to schedule the tasks and serial comms done via interrupts. Apart from the serial comms, most of the work is done in tasks and since each task runs to completion, each task is mutually exclusive so I don't need to worry about sharing variables. Each task is allotted 10mS which is more than enough in most cases.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Cooperative task scheduling, for small micros, has the huge advantage that all tasks share one stack and this saves a lot of RAM.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am using csRTOS-
http://www.circuitcellar.com/avr...

Simple enough for me to understand. Works good, too. Not a lot of overhead, so works on mega88/168 just fine.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

freeware cooperative scheduler with lots of calendar and I/O functions.
https://www.avrfreaks.net/index.p...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

stevech wrote:
freeware cooperative scheduler with lots of calendar and I/O functions.
https://www.avrfreaks.net/index.p...
Looks good. Will have to try it.

I wander if a cooperative scheduler can be thought of as having hardware and 'software' interrupts, 'software' interrupts being the tasks. Hardware interrupts can interrupt 'software' interrupts, but 'software' interrupts run to completion, or until they give up control (hence cooperative, I guess). So, you just have a bunch of 'interrupts', each triggered by time or event.

Just thinking. Or proving otherwise.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Cooperative task scheduling is merely round-robin CPU sharing. The on-stack local variables are valid only for the time that the task has the CPU. Static variables have to be in global or in a data structure obtained from a memory pool, as in malloc(). This is the compromise to avoid a stack per task which is a heavy penalty for a micro without much RAM, esp. for dormant tasks.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For interest I ported csRTOS to the ATmega32 and posted it to the projects section.
https://www.avrfreaks.net/index.php?module=Freaks%20Academy&func=viewItem&item_id=987&item_type=project
Glen's project on the CircuitCellar site is very well documented and is a very good example of what is needed for a simple scheduler.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I had to change a couple of things with the newest winavr installed-

PGM_P romAdr; // used to access bytes stored in flash
changed to
const prog_uint8_t * romAdr; // correction

there was another similar change needed, but can't find it now.

PGM_P is a const prog_char * , which gave an error/warning or something because it is actually used to point to a uint8_t, not a char. The newest gcc must now be checking little things like that now.

The nice thing (for me), is that csrtos is in 1 file, and is easy enough for me to use. The actual 'os' code in that file is actually very little.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks curtvm,

I used a 2006 version of WinAVR, I will need to update it!

I re-wrote the debounce for the clock setting switches, as a micro-switch I was using must have had too much bounce for the original code. I would like to have another go at debouncing (using the provided os calls), ie use a counter to get 5 valid lows, before proceeding.

There is one line of code that I don't understand and several C gurus at work couldn't really explain it to me, could you?

((U8 (*)(void)) romAdr)(); // call the task to initialise it

If you leave the (void) out, as one guy suggested one gets a compiler error to the effect, "called object is not a function". So, from that I assume romAdr is being passed into some sort of function, but what??

Quote:
The nice thing (for me), is that csrtos is in 1 file, and is easy enough for me to use. The actual 'os' code in that file is actually very little.

I have spend the last two evenings trying to separate the csRTOS core code from the user code. There's lots of room in the ATmega32 and I thought it might make it easier for first time users. Still working on this one!

Cheers

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

davef wrote:
There is one line of code that I don't understand and several C gurus at work couldn't really explain it to me, could you?

((U8 (*)(void)) romAdr)(); // call the task to initialise it

If you leave the (void) out, as one guy suggested one gets a compiler error to the effect, "called object is not a function". So, from that I assume romAdr is being passed into some sort of function, but what??

I am not a C guru, but here's a stab at it-

romAdr is the address of a task (the previous 2 lines set it up), which is called (in the loop, to initialize each task), and which osTASK_INIT then does its thing for each task.

I can understand the void part, but the U8 (*) , I'm not sure. (romAdr is a U8 pointer to a void function?). I will need to look in books for that.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I read that as a function that returns a type U8 (unsigned char?) and takes no arguments from the caller.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

crtvm,
romADR is a pointer to a string in PGM (program memory), according to pgmspace.h

stevech
Yes, U8 is defined as an unsigned char in this project
What is the function called? (void)?

Also where does the (*) figure in this?

Just interested . . .

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've spent an hour or two trying to beat it into submission using iccavr... hope someone else beats me to it....

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

. . . beat what? Trying to port it to the ATmega32 using iccvar?

Have you read the notes re porting using different compilers? Evidently, you need to look at what registers are used in saveTask() and restoreTask().

Good luck!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, I've spent also an hour or two to port it to M128 with IAR. And I'm a novice with IAR, since I'm an ImageCraft Compiler user.

It tooks me one day to rewrite it to use multiple stack and port it to M1281, mixing with FreeRTOS. Once one knows a little about how it works, it is really not too difficult.

Guillem.

Guillem.
"Common sense is the least common of the senses" Anonymous.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

my lss listing, with some of my comments-

//PGM_P romAdr; is the same as- const prog_char * romAdr;
//so romAdr stores a pointer to a char that is in pgm memory
//startAdr[] stores in pgm memory the address of each task

	for (thisTask = 0; thisTask < nTasks; thisTask++) {
	// loop through all tasks
    1822:	10 92 e6 01 	sts	0x01E6, r1
    1826:	80 91 e6 01 	lds	r24, 0x01E6
    182a:	87 30       	cpi	r24, 0x07	; 7
    182c:	d0 f4       	brcc	.+52     	; 0x1862 
	// get address of task address array in flash
		romAdr = (char*)&(startAdr[thisTask]);
    182e:	80 91 e6 01 	lds	r24, 0x01E6
    1832:	99 27       	eor	r25, r25
    1834:	88 0f       	add	r24, r24
    1836:	99 1f       	adc	r25, r25
    1838:	80 59       	subi	r24, 0x90	; 144
    183a:	9f 4f       	sbci	r25, 0xFF	; 255
    183c:	90 93 69 01 	sts	0x0169, r25
    1840:	80 93 68 01 	sts	0x0168, r24
	//romAdr now has the address which stores the address of the task
	// get starting address of thisTask from flash
		romAdr = (void*)pgm_read_word(romAdr);
    1844:	fc 01       	movw	r30, r24
    1846:	25 91       	lpm	r18, Z+
    1848:	34 91       	lpm	r19, Z
    184a:	30 93 69 01 	sts	0x0169, r19
    184e:	20 93 68 01 	sts	0x0168, r18
	//romAdr now has the pgm memory address of the task
	// call the task to initialize it
		((U8 (*)(void)) romAdr)();
    1852:	f9 01       	movw	r30, r18
    1854:	09 95       	icall
	//that's it
    1856:	80 91 e6 01 	lds	r24, 0x01E6
    185a:	8f 5f       	subi	r24, 0xFF	; 255
    185c:	80 93 e6 01 	sts	0x01E6, r24
    1860:	e2 cf       	rjmp	.-60     	; 0x1826 
	}

((U8 (*)(void)) romAdr)(); reduces to a simple icall, but not sure how that comes about. Still trying to figure out.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

davef wrote:
((U8 (*)(void)) romAdr)(); // call the task to initialise it

That's merely applying a function pointer cast to whatever romAdr is. Say you had:

typedef unsigned char U8;
typedef U8 (*fn_ptr_type)(void);

Then anything you declare of type fn_ptr_type is going to be a pointer to a function that takes void parameters and returns a U8 (unsigned char). So you might have:

fn_ptr_type my_ptr;

to declare such a pointer. But say you want to initialise this to absolute address 0x1234 if you tried:

my_ptr = 0x1234;

the compiler would complain ("../eric.c:30: warning: assignment makes pointer from integer without a cast" in fact) as you cannot assign an int (which is what the literal constant 0x1234 is) to a pointer to a function. So you'd need to cast it to clear the warning.

Now a simple cast (say you wanted 0x1234 to be treated by the compiler as an unsigned long) would be:

my_ptr = (unsigned long)0x1234;

but casts to function pointer types always look a bit more exotic:

my_ptr = (U8 (*)(void))0x1234;

though I'd do that typedef myself and end up with:

my_ptr = (fn_ptr_type)0x1234;

which I think is more readable (though it's true that the casual reader now has to go hunting back through the source to find out what "fn_ptr_type" really is unless they use a decent source browser/editor such as Source Insight)

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

((U8 (*)(void)) romAdr)();

so, the typecast (U8 (*)(void)) tells the compiler that what comes next is a pointer to a function that takes no parameters and returns a U8, which is romAdr (which holds the function address), the () tells the compiler to call that function?

The U8 has to be there if a return value is not used?

Thanks for helping!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

((void (*)(void)) romAdr)();

I just tried that, ends up with the same code and no errors.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Cliff,

Appreciate your explanation. I think I am getting the idea . . . I'll read it a few more times!

So, "a function pointer cast to a string in program space". Or, in this case, "a function pointer cast to the task you want to initialise".

I have used void(* const pf[])(void), so I was expecting something to follow the (*), ie ((u08(*xyz)(void))romAdr)();

Quote:
but casts to function pointer types always look a bit more exotic:

Could one also say "casts to a pointer to a function type"?

curtvm's response has just come up and his comment

Quote:
the typecast (U8 (*)(void))

Got it now, the typecast is MUCH bigger than I thought.

Thanks guys . . . now how to apply this new knowledge?

Cheers

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah but when you used:

void(* const pf[])(void)

that wasn't a type definition or a cast. That's actually defining a constant array called pf[] that holds pointers to functions that have void parms and void returns. (though the fact that there's no dimension in the [] means that this actually defines a pointer to such things and not an array itself in fact)

Think about casts in simple terms - when you want 0x1234 to be an unsigned long you'd use:

(unsigned long)0x1234

and not:

(unsigned long var_name)0x1234

Similarly to cast to a function pointer you use:

(ret_type (*)(parm_type))0x1234

and not:

(ret_type (*var_name)(parm_type))0x1234

HTH

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
the () tells the compiler to call that function?

Indeed it does. Any:

var_name();

results in a call to var_name. In fact if the var_name (which is equally a fn_name) was a function that took parameters then:

var_name(1234, "hello");

would call through the function pointer 'var_name' and pass into the function the 1234 and a pointer to "hello".

curtvm wrote:
((void (*)(void)) romAdr)();

I just tried that, ends up with the same code and no errors.


yeah but it's not right. Say you were going to use that U8 that the function pointed to by 'romAdr' returns you can't say:

U8_retval = ((void (*)(void)) romAdr)();

but you can say:

U8_retval = ((U8 (*)(void)) romAdr)();

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
curtvm wrote:
((void (*)(void)) romAdr)();

I just tried that, ends up with the same code and no errors.


yeah but it's not right. Say you were going to use that U8 that the function pointed to by 'romAdr' returns you can't say:

U8_retval = ((void (*)(void)) romAdr)();

but you can say:

U8_retval = ((U8 (*)(void)) romAdr)();

Cliff

The U8 part had me confused, because in this case (csrtos), the function pointer points to functions (tasks) which do not take any parameters or return any values. So if no return values are needed, it can be void. I'm out on a limb, but I would then say it is correct in this specific case.

Thanks again!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the further clarification, Cliff.

I wrote, what I hope is a better debounce routine (loops 5 clockticks only incrementing on valid lows). Also, managed to separate the code. Hopefully, the "points of change" for other applications has been reduced slightly!

I'll update the project. Now all I need is a good project to try and use something like this!

Appreciate the help guys.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I agree, in this context I'm not sure why they are defining the function pointers to have a U8 return and then never making use of it - so it'd be safe to simplify things a bit here I think.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have been using csrtos on my little project-
http://www.mtcnet.net/~henryvm/gps/
and has been working good. (notice the strange mix of smt and 'regular' components- I didn't want to order more parts, so decided to just use whatever I had on hand)

I have 7 tasks currently running, along with a few interrupts. I even have the display multiplexing inside a task (should be in an interrupt, but since none of the tasks take alot of time, it works).

Things to remember with csrtos, you have to make some kind of os call in the tasks (usually osSUSPEND at the end of it),local variables need to be static if an os call is made anywhere but at the end of the task. You can adjust the clocktick interrupt to whatever you need. I have mine set to 2ms, and a wait time of 10ms.

Thanks again to Cliff for the (always) good explanations.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just posted the code and then I had this horrible thought that I had broken the setClockTask(). However, it seems to work properly . . . which I don't understand!

I thought that if the switch low was less than 5*16ms then the interrupts on INT0 and INT1 would never get re-enabled. I can make switch closures of less than 10ms and the function seems to keep working properly.

I can comment out the interrupt enables at the end and the function then falls over. And if I comment out the interrupt disables at the beginning I get multiple clock increments with one switch closure. So, I conclude that even though I don't get my 5 loops the INT0 and INT1 still get enabled.

Any ideas??

void setClockTask(void) 
{                       
   static u16 loop_counter = 0;
  
   osTASK_INIT;			

   for(;;)
   {
      GICR &= ~(1<<INT0); // disable INT0 to ensure no interrupts are generated
      GICR &= ~(1<<INT1); // disable INT1 to ensure no interrupts are generated

      while (loop_counter < 5) // 5 * 16ms clockTick = 90ms
      {
         osWAIT(1);

      // if PORTD2(INT0) or PORTD3(INT1) is low  
         if (((PIND & 0x04) == 0) || ((PIND & 0x08) == 0))
         {
            loop_counter++;
         }    
      }

      if (loop_counter == 5)
      {
         loop_counter = 0; // reset

         if (clockSet & 1)
         {
            secs = 0;
            mins += 1;

            if(mins > 59)
            {
               mins = 0;
            }    
		   }

         if (clockSet & 2)
         {
		 	   hrs += 1;

            if(hrs > 23)
            {
               hrs = 0;
            }   
		   }

         GIFR = 1<<INTF0; // clear any pending interrupts
         GICR |= (1<<INT0); // enable INT0
         GIFR = 1<<INTF1; // clear any pending interrupts
         GICR |= (1<<INT1); // enable INT1
         osSUSPEND;
      }
      else
         {;} // keep looping   
   }
}
/* ---------- end of setClockTask() ---------- */
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

An additional note about these typecasts of pointers to functions, and why they look so exotic:

Let's declare some function 'foo':

void foo(void);

A function's "signature" is defined as:
- The number of parameters, and
- The type of each parameter, and
- The type of the return value.

The signature of foo, above, is no parameters (void) and no return value (void).

The name of a function is the address of that function. Let's declare a variable, 'var', that will hold a pointer (address) of the function foo.

First, we start with:
var

Now we want to make it a pointer to something:
* var

Let's put that in the same location as the function name in the declaration of 'foo'. In other words, take the function declaration, and replace the name of the function (which is equal to the address of the function) with our variable name and the pointer operator:
void * var(void)

However take a close look above. With operator precedence, the above would be described as: var is a function accepting no parameters and returning a pointer to void. This is NOT what we are trying to do. So we have to use parentheses to tell the compiler exactly what we are trying to do:
void (* var)(void)

This is described as: var is a pointer to some function that accepts no parameters (void) and returns nothing (void). If we take the variable name out of the declaration:
void (*)(void)

then we now have a TYPE description, and no longer a variable.

If we have a constant 16-bit number:
0x1000
and we want to say that that numer is the address of some function that we want to call then we can TYPECAST that number as a pointer to a function:
(void (*)(void))0x1000

Now the compiler will treat that correctly as a pointer to a function. When a pointer to a function is DEREFERENCED, there is only one logical thing that can be done, and that is to CALL THE FUNCTION. Normally, we show the dereference of the pointer, with extra parentheses:
*((void (*)(void)0x1000)

But it is still not a true function call until we put the parameter list parentheses (and obviously the semicolon) at the end:
*((void (*)(void)0x1000)();

In C, there is a shortcut available to the programmer: You do NOT have to explicitly dereference a pointer to a function. As there is only one that can be done with a pointer to a function, and that is to call it. So you can do this as so:
((void (*)(void)0x1000)();

Calling a function through a pointer, is called an "indirect function call". This is also implemented in assembly with the ICALL opcode.

Now, if we assign the constant number to a variable:
uint16_t foo = 0x1000;

Then we can replace the number with the variable as so:
((void (*)(void)foo)();

So now we have the strange looking construct: A variable foo, is typecast as a pointer to a function, taking no arguments, and returning no value, and then calling that function.

If the function returns a value, such as a single byte, and we want to call that function, and assign that return value to another variable, then we have:
bar = ((uint8_t (*)(void)foo)();

Hope that makes things clear. :)
Eric Weddington

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

* * * * * rating for this message!

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

EW wrote:
Hope that makes things clear. :)
Eric Weddington
It makes sense when I read it from start to finish, but it probably won't stick in my brain (its like teaching someone how to do some task on a computer, each step makes sense and is logical, but 5 minutes later the question will be, 'how do you do that again?'). I need to make a copy of this thread.
davef wrote:
Any ideas??

No, and I don't even remember running the demo code except for some blinking lights on stk500.

I assume you want to disable irq (unlike the demo code) to keep irq from firing.

how about a variation of original demo code-(have not tried it,could be bogus code by me)

for(;;) {
	//I think these could be moved to the irqs instead
	GICR &= ~(1<<INT0); // disable INT0
	GICR &= ~(1<<INT1); // disable INT1
	if (clockSet & 1) {
		secs = 0;
		mins += 1;
		if(mins > 59) mins = 0;
	}
	if (clockSet & 2) {
		hrs += 1;
		if(hrs > 23) hrs = 0;
	}
	//wait however long needed to debounce
	osWAIT(5);
	GIFR = 1<<INTF0; // clear any pending interrupts
	GICR |= (1<<INT0); // enable INT0
	GIFR = 1<<INTF1; // clear any pending interrupts
	GICR |= (1<<INT1); // enable INT1
	osSUSPEND;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm,

The intention of the posted code is to count up 5 valid switch on conditions before saying you have a valid switch closure. Your code still only tests for one switch on conditions (I think), which could be noise, etc.

Think I have figured out the debounce issue in my posted code.

What happens when the switch is held down for only, let's say 1 or 2 clockTicks is:

- loop_counter equals 1 or 2
- switch goes up
- this task just keeps looping until
- you hold the switch down long enough to get loop_counter to 5

This is potentially undesired behaviour because you wouldn't be debouncing this 2nd switch closure properly.

Better code will be published in v3!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Eric,

Yes, that certainly clarifies things a lot. There was much more to this then I ever imagined.

Quote:
So now we have the strange looking construct: A variable foo, is typecast as a pointer to a function, taking no arguments, and returning no value, and then calling that function.

The main issue I got hung-up on was that I didn't see romAdr as a function, but as an "address of a string in program space". But, because of the previous 2 lines in the csRTOS code, romAdr is now the address of a function.

Now, the issue is to be able to recognise other situations where this would be useful AND apply it!

Cheers,

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

davef wrote:
The intention of the posted code is to count up 5 valid switch on conditions before saying you have a valid switch closure. Your code still only tests for one switch on conditions (I think), which could be noise, etc.
My first variation would have problems on the release of the switch.

how about a variation of a variation of the original code-

for(;;) {
	//I think this could be done in irqs instead
	//they could disable themselves
	GICR &= ~((1<<INT0)||(1<<INT1)); // disable INT0/1
	//wait a certain period of time
	osWAIT(5);
	//check if int0 pin was cause of irq
	if (clockSet & 1){
		//and is still down
		if ((PIND & 0x04) == 0){
			secs = 0;
			mins += 1;
			if(mins > 59) mins = 0;
		}
	}
	//else check if int1 pin was cause of irq
	else if (clockSet & 2){
		//and is still down
		if ((PIND & 0x08) == 0){
			hrs += 1;
			if(hrs > 23) hrs = 0;
		}
	}
	GIFR = ((1<<INTF0)||(1<<INTF1)); // clear pending interrupts
	GICR |= ((1<<INT0)||((1<<INT1)); // enable INT0/1
	osSUSPEND;
}

If you got to the task, one of the switches made contact, now wait a certain period of time and check if the switch that fired the irq is still down, if so, inc the clock (see a switch close, close my eyes and count to 5, look again).

This would debounce both the press and release of the switch. I'm not sure checking the 'middle' of the total debounce time is needed, as it just makes you debounce more times but with less debounce time (did that make any sense?).

I'm sure you will come up with something (more simple = more better).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
My first variation would have problems on the release of the switch.

As I recall, I solved that problem using the code at the end. If you get an unwanted falling edge interrupt when the switch is released (and bounces) that algorithm would eventually say the switch is high and subsequent code can sort that condition out.

Interrupts get re-enabled when you leave the ISR. I was advised by our embedded guru to disable specific interrupts, I guess to have one less pending interrupt to worry about!

Figuring out which INTx caused the interrupt is an added complication. Separate functions for setting the minutes and the hours would simplify things.

I use a routine (in another project) which counts the number of valid switch states and when it reaches 5 conclude that I have the correct state. Also, by doing a old_switch_state/new_switch_state test you don't have to worry about whether or not you are looking for a high or a low. The algorithm will sort things out.

// Even if the initial read of old_switch_state is not a valid switch
// change, this routine will eventually determine the correct switch
// position.
// You would need 5 "incorrect" HIGH or LOW sequences, each spaced by
// osWAIT() to end up with an incorrect switch state

while(counter < 5)
   {
      old_switch_state = (PINB & BIT_3);
            
      osWAIT(1);

      new_switch_state = (PINB & BIT_3);
   
      if(new_switch_state == old_switch_state)
      {
         counter++;
      }
   }

   counter = 0; // reset

   then test for wanted switch position

To summarise, I am fairly happy with the way the debounce currently performs. If I was going to use it in a serious project I would re-visit the debounce strategy.

On another snippet from csRTOS . . . can you explain how:

crtBufN = 1; // index of next char to send
UDR = crtBuf[0]; // send the first char

is able to send out more than one char? I would expect something to index through the crtBuf array.

Thanks,
davef

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm,

BTW, your combined interrupt disable, clear flag and re-able interrupts statements need a bit-wise AND between them.

Here's my setClockTask(), which good enough for now.

void setClockTask(void) 
{                       
   osTASK_INIT;			

   for(;;)
   {
      GICR &= ~((1<<INT0) | (1<<INT1)); // disable INT0/1

      osWAIT(5); // 16ms * 5 = 90ms, a simple debounce period
   
   // after 90ms check to see the required port is still low
   // if PORTD2(INT0) or PORTD3(INT1)   
      if (((PIND & 0x04) == 0) || ((PIND & 0x08) == 0))
      {
         if (clockSet & 1)
         {
            secs = 0;
            mins += 1;

            if(mins > 59)
            {
               mins = 0;
            }    
		   }
		      
         if (clockSet & 2)
         {
		 	   hrs += 1;
		
            if(hrs > 23)
            {
               hrs = 0;
            }   
		   }
      }   

      GIFR = ((1<<INTF0) | (1<<INTF1)); // clear pending interrupts
      GICR |= ((1<<INT0) | (1<<INT1)); // enable INT0/1
   
      osSUSPEND;
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

davef wrote:
To summarise, I am fairly happy with the way the debounce currently performs. If I was going to use it in a serious project I would re-visit the debounce strategy.
You can debounce any way you like, no debounce laws to follow as far as I'm aware, and as long as it works, any user of the device won't care how you did it, either.

davef wrote:
On another snippet from csRTOS . . . can you explain how:

crtBufN = 1; // index of next char to send
UDR = crtBuf[0]; // send the first char

is able to send out more than one char? I would expect something to index through the crtBuf array.

In the demo, they are using TX interrupt to transmit. The reason crtBufN gets set to 1 (instead of 0), is because the first character is put in UDR0 in the task, and when that character is done transmitting, the irq will take care of anything in crtBuf that is not 0, starting at position 1 (which is crtBuf[crtBufN]).

I think that's correct, anyway.

davef wrote:
BTW, your combined interrupt disable, clear flag and re-able interrupts statements need a bit-wise AND between them.
I incorrectly logical OR'd them instead of bitwise OR, if you bitwise ANDed them, you would always get 0. (you probably meant OR, but typed AND).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the explanation, yes

if ((toSend = crtBuf[crtbufN++])) // if there is another non-zero character
                                  // in the buffer, send it.

says it all.

Oops, I did mean bit-wise OR.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am using scmRTOS. It is here: http://scmrtos.sourceforge.net
I have been using it with ATmega16.
I have chosen this OS, because it is free tiny preemptive Real-Time Operating System.
There's russian and english doc on the site.
The RTOS written on C++. I work in linux suse and I use gcc port and avr-gcc to build my projects.

__________
Sorry for my english.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Firstly, why are interrupts used for the pushbuttons? Why not have a task that runs at a regular interval - say 10mS and polls the various switches, detects if they have been active for X ticks then increments the required vars? This routine could also be used to increment the time. You've wiped off two interrupt sources and simplified the code. Also, how do you ensure the atomicity of your shared variables? I see no explicit method in the code, does the RTOS provide critical sections or mutexes? What happens if your interrupt code kicks in when another task has read the variable,changed it but has yet to write it back? Every task in a pre-emptive system is effectively an interrupt task, so you need to be careful when sharing variables between tasks. Most OS's provide mechanisms for this. Or, just use a co-operative method and avoid most of the problems and have simpler and smaller code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman,

Not sure who you are talking to, haker_fox or davef. The reference to pre-emptive leads me to think you are talking about scmRTOS. Correct?

By coincidence, the csRTOS example uses two switches on interrupts, so maybe you are taking about that scheduler.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Typically a RTOS was not needed really.

In most cases it was only needed to do some delays without stalling all other tasks.
So a simple scheduler can do the job also:

http://www.mikrocontroller.net/a...

And in such a case, if a big task cost to many time, simple divide it into short subtasks, which are done consecutive (every subtask put the next into the scheduler).

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

csrtos is a cooperative single-stack priority based 'RTOS'(scheduler?) with semaphores, not pre-emptive.

Kartman wrote:
Firstly, why are interrupts used for the pushbuttons?
Its demo code. I guess the author just wanted a simple way to demonstrate tasks, semaphores and interrupts.
Kartman wrote:
What happens if your interrupt code kicks in when another task has read the variable,changed it but has yet to write it back?
You disable interrupts in your task where needed, make your change, then re-enable interrupts. No different than the way you would normally do it.

I have a serial rx irq routine grabbing various data packets, and a task that reads them. In that task, I disable irq's right before reading the data (just a few bytes), read it, then turn irq's back on and do my work on that data. I also have the rx irq mark the first byte in a packet a certain way (0x01), and clear it when the packet is done (0x00), so my task will only read the packet when it sees that the packet is not currently in the process of being received/re-written.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
Also, how do you ensure the atomicity of your shared variables? I see no explicit method in the code, does the RTOS provide critical sections or mutexes? What happens if your interrupt code kicks in when another task has read the variable,changed it but has yet to write it back?

Wouldn't you just protect the variable access with a sempahore? (which most of these "mini RTOS" seem to provide)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Wouldn't you just protect the variable access with a sempahore? (which most of these "mini RTOS" seem to provide)
You are probably right about that, in my case I just resorted to cli/sei since it was only for access to a few bytes and wouldn't matter to the irq's time-wise. Eventually I'll figure out the correct way to do things.

The demo code in csrtos shows how to use semaphores for uart tx access (irq driven), which makes sense (at least if you have more than 1 task wanting to transmit).

Pages