A single core processor cannot execute two threads of execution simultaneously in the sense of parallel processing, but you don't actually need that to solve your problem. It is an issue of scheduling processor time rather then simultaneous execution. In your case since both functions spend most of their time in delays doing nothing - that "do nothing" time can be shared - doing nothing concurrently is easy.
One solution would be to use an RTOS. Simple RTOS kernels such as FreeRTOS run on AVR, but the footprint may be prohibitive for parts with tiny memories as each thread must have its own stack.
If you already have an RTOS port for your target and are familiar with such things (which I guess your are not since you asked the question), it is probably the simplest solution; your code might look something like (pseudo-code and not any particular real RTOS API):
int main(void)
{
DDRC = DDRC | (1<<2);
DDRC = DDRC | (1<<3);
osCreateThread( solenoidTask, SOLONOID_PRIORITY ) ;
osCreateThread( stepperTask, STEPPER_PRIORITY ) ;
osStartKernel() ; // does not return
}
void solenoidTask()
{
for(;;)
{
openSolenoidValves( 100, 60 ) ;
}
}
void stepperTask()
{
for(;;)
{
startStepperMotor( 100, 60 ) ;
}
}
void openSolenoidValves(double air, double oxygen)
{
PORTC = PORTC | (1<<2); //open oxygen(normally closed valve)
PORTC = PORTC & (~(1<<3)); //open air (normally open valve)
osDelay( oxygen ) ;
PORTC = PORTC & (~(1<<2));//close oxygen
osDelay( air - oxygen ) ;
PORTC = PORTC | (1<<3);//close air
osDelay( air ) ;
}
void startStepperMotor()
{
// use osDelay() - not busy wait delay.
// this function also has delays
...
}
However getting an RTOS running on your target may be an unacceptable overhead for simple projects if you do not have it already set-up. In that case you need to avoid delays by polling for time and taking actions only when it is time to do so. For that you need a "current time" or "tick" function, which is not part of the standard AVR library (clock()
for example is not implemented). I'll get to that, but say we have a free-running 1ms counter with interface millis()
(note that the Arduino library does have such a function). Then you might create a "timer" API like:
#include <stdint.h>
#include <stdbool.h>
typedef struct
{
unsigned start ;
unsigned expiry ;
} sTimer ;
void startTimer( sTimer* timer, uint32_t expiry )
{
timer.expiry = expiry ;
timer.start = millis() ;
}
bool isTimerExpired( sTimer* timer )
{
return millis() - start >= expiry ;
}
Then you would re-write your openSolenoidValves()
function as as a state machine as follows for example:
void openSolenoidValves(double air, double oxygen)
{
static enum
{
START,
ALL_OPEN,
OXY_CLOSE,
AIR_CLOSE
} state = ALL_OPEN ;
sTimer timer ;
switch( state )
{
case START :
{
startTImer( &timer, oxygen ) ;
PORTC = PORTC | (1<<2); //open oxygen(normally closed valve)
PORTC = PORTC & (~(1<<3)); //open air (normally open valve)
state = ALL_OPEN ;
}
break ;
case ALL_OPEN :
{
if( isTimerExpired( &timer ) )
{
PORTC = PORTC & (~(1<<2)) ; // close oxygen
startTimer( &timer, air - oxygen ) ;
state = OXY_CLOSED ;
}
}
break ;
case OXY_CLOSED :
{
if( isTimerExpired( &timer ) )
{
PORTC = PORTC | (1<<3); // close air
startTimer( &timer, air ) ;
state = AIR_CLOSED ;
}
}
break ;
case AIR_CLOSED :
{
if( isTimerExpired( &timer ) )
{
state = START ;
}
}
break ;
}
}
In each state, the function is called and does nothing if the current timer has not expired and returns immediately. When the timer expire it performs the necessary solenoid operation and switches state - and returns immediately.
You implement the stepper function in a similar manner, then your executive loop can be:
for(;;)
{
openSolenoidValves( 100, 60 ) ;
startStepperMotor() ;
}
As you can see even for quite simple scheduling it can get rather complicated compared to RTOS threads. However if you design it with state machine diagrams before attempting to write the code, the implementation can be quite mechanistic within a basic and re-usable state-machine framework. Note also that you need not wait only on time, you could for example poll inputs to trigger events to advance the state-machines.
Note also if you were using the Arduino Sketch framework, you would not have the endless for(;;)
or while(1)
loop, the state-machine functions would simply be called in the loop()
function (which is in turn called withing an endless loop provided as part of the framework).
Now if you are not using Arduino for example and have no millis()
or similat system tick function, on AVR you can create one using a timer peripheral incrementing a counter in the reload interrupt. For example:
#include <stdint.h> ;
#include <avr/io.h> ;
#include <avr/interrupt.h> ;
// Timer reload value for 1ms
#define SYSTICK_RELOAD (CORE_CLK_FREQ / 1000UL)
// Millisecond counter
volatile uint32_t tick_millisec = 0 ;
ISR (TIMER1_COMPA_vect)
{
tick_millisec++;
}
void sysTickInit()
{
// CTC mode, Clock/1
TCCR1B |= (1 << WGM12) | (1 << CS10);
// Load the output compare
OCR1AH = (SYSTICK_RELOAD >> 8);
OCR1AL = SYSTICK_RELOAD ;
// Enable the compare match interrupt
TIMSK1 |= (1 << OCIE1A);
// Enable interrupts
sei();
}
uint32_t millis()
{
uint32_t now = 0 ;
// Read tick count and re-read if it is not consistent
// (due interrupt pre-emption and update during non-atomic access)
do
{
now = tick_millisec ;
} while( now != tick_millisec ) ;
return now ;
}
With a main()
like:
int main( void )
{
sysTickInit() ;
DDRC = DDRC | (1<<2) ;
DDRC = DDRC | (1<<3) ;
for(;;)
{
openSolenoidValves( 100, 60 ) ;
startStepperMotor() ;
}
}
See also Pressing button for 3 seconds and how to measure its time with Atmega8 1MHz? for other non-blocking scheduling using system tick polling.