int main(void)
{
DDRC = DDRC | (1<<2);
DDRC = DDRC | (1<<3);
while (1)
{
//openSolenoidValves(100,60);
//startStepperMotor();
}
void openSolenoidValves(double air, double oxygen) {
PORTC = PORTC | (1<<2); //open oxygen(normally closed valve)
PORTC = PORTC & (~(1<<3)); //open air (normally open valve)
_delay_ms(oxygen);
PORTC = PORTC & (~(1<<2));//close oxygen
_delay_ms(air-oxygen);
PORTC = PORTC | (1<<3);//close air
_delay_ms(air);
}
void startStepperMotor(){
//this function also has delays
}
我想同时启动打开电磁阀功能和启动步进电机功能。但这两种功能都有延迟。有什么方法可以做到这一点吗?(MicroController-Atmega32)
单核处理器不能同时执行并行处理意义上的两个执行线程,但实际上并不需要这样来解决问题。这是一个调度处理器时间而不是同时执行的问题。在你的情况下,由于两个函数的大部分时间都在延迟中无所事事——;什么都不做";时间是可以共享的-不同时做任何事情都很容易。
一种解决方案是使用RTOS。简单的RTOS内核(如FreeRTOS)在AVR上运行,但由于每个线程都必须有自己的堆栈,因此对于内存很小的部分来说,占用空间可能会过高。
如果你已经为你的目标提供了一个RTOS端口,并且熟悉这些东西(我想自从你问了这个问题之后你就不熟悉了),这可能是最简单的解决方案;您的代码可能看起来像(伪代码,而不是任何特定的真实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
...
}
然而,如果你还没有设置RTOS,那么在你的目标上运行RTOS对于简单的项目来说可能是一个不可接受的开销。在这种情况下,您需要通过轮询来避免延迟,并且只有在该这样做的时候才采取行动;当前时间";或";勾号";函数,它不是标准AVR库的一部分(例如clock()
没有实现)。我会讲到这一点,但假设我们有一个免费运行的1ms计数器,接口为millis()
(请注意,Arduino库确实有这样的功能)。然后你可以创建一个";定时器";API类:
#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 ;
}
然后,您可以将openSolenoidValves()
函数重写为状态机,例如:
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 ;
}
}
在每个状态中,如果当前计时器未过期,则调用函数,并且不执行任何操作,并立即返回。当计时器到期时,它执行必要的电磁阀操作并切换状态-并立即返回。
你以类似的方式实现步进器功能,那么你的执行循环可以是:
for(;;)
{
openSolenoidValves( 100, 60 ) ;
startStepperMotor() ;
}
正如您所看到的,即使是非常简单的调度,与RTOS线程相比,它也会变得相当复杂。但是,如果在编写代码之前使用状态机图进行设计,那么在基本且可重复使用的状态机框架内,实现可能是非常机械的。还要注意,您不需要只等待时间,例如,您可以轮询输入以触发事件来推进状态机。
另外请注意,如果您使用Arduino Sketch框架,则不会有无休止的for(;;)
或while(1)
循环,状态机函数只会在loop()
函数中调用(反过来,在作为框架一部分提供的无休止循环中调用)。
现在,如果您没有使用Arduino,并且没有millis()
或类似的系统tick功能,那么在AVR上,您可以使用定时器外设在重新加载中断中增加计数器来创建一个。例如:
#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 ;
}
使用类似main()
的
int main( void )
{
sysTickInit() ;
DDRC = DDRC | (1<<2) ;
DDRC = DDRC | (1<<3) ;
for(;;)
{
openSolenoidValves( 100, 60 ) ;
startStepperMotor() ;
}
}
另请参阅按下按钮3秒钟以及如何使用Atmega8 1MHz测量其时间?对于使用系统勾选轮询的其他非阻塞调度。
不幸的是,ATmega32只有一个核心。有一种东西叫做";实时操作系统";它可以让你同时运行多个这样的任务,我相信你可以选择一个在ATmega32上工作的任务,并将你的代码移植到它,但这可能比它值得的麻烦得多。通过将代码编写为以非阻塞方式(即消除所有阻塞延迟)和/或在适当的情况下使用中断,可以更容易地实现代码的并发执行。
是的,这是完全可能的。这叫做协作多任务处理。从研究"无延迟闪烁"开始,我的答案是:如何进行高分辨率、基于时间戳、无阻塞、单线程协作的多任务
示例:
int main(void)
{
doSetupStuff();
configureHardwareTimer();
while (1)
{
doTask1();
doTask2();
doTask3();
}
}
关于如何以非阻塞方式编写这3个任务的示例,请参阅我上面的回答。
如果操作正确,这项技术可以在任何平台上工作,从AVR到Arduino到STM32再到Windows、Linux、Raspberry Pi等,并且可以用任意编程语言完成。
而且,它被设计为单线程,因此不需要操作系统、多核或完整的调度器。实际上,这个是一种轻量级的协作(与抢先相反)调度器。
它重量轻,我相信即使是最小的AVR微控制器也可以实现,这些微控制器只有64字节的RAM,一直到最大的具有数百GBRAM的PC。
这个millis()函数帮助我解决了问题。
就像Arduino中的millis()函数一样,该函数返回自程序启动以来的时间(以毫秒为单位)。
作为开发人员的详细信息,该功能仅在atmega328p上进行了测试,但也可能在许多其他AVR上运行。
实现这个功能很容易——看看例子:
- 首先必须使用init_millis()启动时钟
- 调用init_millis()后,调用sei()以启用全局中断
- 只要您想获得自程序已启动
开发人员的回购=>https://github.com/monoclecat/avr-millis-function#:~:text=avr%2Millis%2Dfunction-,只是%20喜欢%20millis()%20函数%20在%20Arduino%2C%20这个%20函数,还有许多%20其他%20AVRs%20作为%20。