打印
[其它]

Arduino多线程ProtoThread测试

[复制链接]
5038|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 hbzjt2011 于 2016-12-2 10:59 编辑

Arduino Nano是Arduino USB接口的微型版本,最大的不同是没有电源插座以及USB接口是Mini-B型插座。Arduino Nano是尺寸非常小的而且可以直接插在面包板上使用。其处理器核心ATmega328(Nano3.0),同时具有14路数字输入/输出口(其中6路可作为PWM输出),8路模拟输入,一个16MHz晶体振荡器,一个mini-B USB口,一个ICSP header和一个复位按钮。
正面照:                                            反面照:
              
Arduino Nano供电方式
  • mini-B USB接口供电
  • pin27 +5V接外部直流5V电源
存储器
ATmega328包括了片上32KB Flash,其中2KB用于Bootloader。同时还有2KB SRAM和1KB EEPROM。
输入输出
  • 14路数字输入输出口:工作电压为5V,每一路能输出和接入最大电流为40mA。每一路配置了20-50K欧姆内部上拉电阻(默认不连接)。除此之外,有些引脚有特定的功能
    • 串口信号RX(0号)、TX(1号): 提供TTL电压水平的串口接收信号,与FT232Rl的相应引脚相连。
    • 外部中断(2号和3号):触发中断引脚,可设成上升沿、下降沿或同时触发。
    • 脉冲宽度调制PWM(3、5、6、9、10 、11):提供6路8位PWM输出。
    • SPI(10(SS),11(MOSI),12(MISO),13(SCK)):SPI通信接口。
    • LED(13号):Arduino专门用于测试LED的保留接口,输出为高时点亮LED,反之输出为低时LED熄灭。
  • 6路模拟输入A0到A5:每一路具有10位的分辨率(即输入有1024个不同值),默认输入信号范围为0到5V,可以通过AREF调整输入上限。除此之外,有些引脚有特定功能
    • TWI接口(SDA A4和SCL A5):支持通信接口(兼容I2C总线)。
  • AREF:模拟输入信号的参考电压。
  • Reset:信号为低时复位单片机芯片。
通信接口
  • 串口:ATmega328内置的UART可以通过数字口0(RX)和1(TX)与外部实现串口通信;ATmega16U2可以访问数字口实现USB上的虚拟串口。
  • TWI(兼容I2C)接口:
  • SPI 接口:
下载程序
  • Arduino Nano上的MCU已经预置了bootloader程序,因此可以通过Arduino软件直接下载程序。
  • 可以直接通过Nano上ICSP header直接下载程序到MCU。
电路图:
ArduinoNano30Schematic.pdf (60.54 KB)

介绍完Arduino片上资源后,下面介绍主题:ProtoThread的应用,先上代码:
/**
* This is a very small example that shows how to use
* protothreads. The program consists of two protothreads that wait
* for each other to toggle a variable.
*/

/* We must always include pt.h in our protothreads code. */
#include "pt.h"

/* Two flags that the two protothread functions use. */
static int protothread1_flag, protothread2_flag;

/* protothread state variables, one for each ptorothread. */
static struct pt pt1, pt2;

// constants won't change. Used here to set a pin number :
const int ledPin =  13;      // the number of the LED pin

/**
* The first protothread function. A protothread function must always
* return an integer, but must never explicitly return - returning is
* performed inside the protothread statements.
*
* The protothread function is driven by the main loop further down in
* the code.
*/
static int protothread1(struct pt *pt)
{
  /* A protothread function must begin with PT_BEGIN() which takes a
     pointer to a struct pt. */
  PT_BEGIN(pt);

  /* We loop forever here. */
  while(1) {
    /* Wait until the other protothread has set its flag. */
    PT_WAIT_UNTIL(pt, protothread2_flag != 0);
    Serial.println("Protothread 1 running");
   
    digitalWrite(ledPin,LOW);
   
    /* We then reset the other protothread's flag, and set our own
       flag so that the other protothread can run. */
    protothread2_flag = 0;
    protothread1_flag = 1;

    /* And we loop. */
  }

  /* All protothread functions must end with PT_END() which takes a
     pointer to a struct pt. */
  PT_END(pt);
}

/**
* The second protothread function.
* This one is almost the same as the first one.
*/
static int protothread2(struct pt *pt)
{
  PT_BEGIN(pt);

  while(1) {
    /* Let the other protothread run. */
    protothread2_flag = 1;

    /* Wait until the other protothread has set its flag. */
    PT_WAIT_UNTIL(pt, protothread1_flag != 0);
    Serial.println("Protothread 2 running");
   
    digitalWrite(ledPin,HIGH);
   
    /* We then reset the other protothread's flag. */
    protothread1_flag = 0;

    /* And we loop. */
  }
  PT_END(pt);
}

/**
* setup() is where the protothreads are initialized.
* The state variables pt1 and pt2 holdthe state of the two protothreads.
*/
void setup()
{
  /* Initialize the protothread state variables with PT_INIT(). */
  PT_INIT(&pt1);
  PT_INIT(&pt2);

  Serial.begin(9600);
  
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);
}

/**
* Finally, we have the main loop.
* This is where the protothreads are scheduled.
*/
void loop()
{
  /*
   * We schedule the two protothreads by repeatedly calling their
   * protothread functions and passing a pointer to the protothread
   * state variables as arguments.
   */
  protothread1(&pt1);
  protothread2(&pt2);
}

运行效果:


     ProtoThread是专为资源有限的系统设计的一种耗费资源特别少并且不使用堆栈的线程模型,相比于嵌入式操作系统,其有如下优点:
      1. 以纯C语言实现,无硬件依靠性; 因此不存在移植的困难。
      2. 极少的资源需求,每个Protothread仅需要2个额外的字节;
      3. 支持阻塞操纵且没有栈的切换。

  使用Protothread实现多任务的最主要的好处在于它的轻量级。每个Protothread不需要拥有自已的堆栈,所有的Protothread共享同一个堆栈空间,这一点对于RAM资源有限的系统尤为有利。相对于操作系统下的多任务而言,每个任务都有自已的堆栈空间,这将消耗大量的RAM资源,而每个Protothread仅使用一个整型值保存当前状态。


    所有的“线程”都是(也允许不是)一个死循环,不多占用堆栈,不能被抢占,所以中途必须“退出”,不然怎么会有其他“线程”被调度的机会?退出有两种结果:下一次被调度后从头运行或从退出的位置继续向后运行。Protothreads使用C语言中的“switch”结构或goto语句来实现。既然能够从上一次退出的位置继续向后运行,那么就需要一个变量来记录这个“位置”信息,这个变量就是线程唯一占用的ram变量。作者提供的数据类型是短整型或字符串类型。我认为这就是Protothreads的精髓,也就是说Protothreads线程不是被其他程序“中断”的,而是自己主动退出,但退出之前记录当前位置,以便下次从此位置继续运行。
    由于线程是反复被调用的,因此,写程序的时候不能像写一般的函数一样使用局部变量,因为每次重新调用都会把变量初始化了,如果要保持变量,可以把它定义为static的
  在pt.h中定义了很多功能:
PT_INIT(pt)   初始化任务变量,只在初始化函数中执行一次就行
PT_BEGIN(pt)   启动任务处理,放在函数开始处
PT_END(pt)   结束任务,放在函数的最后
PT_WAIT_UNTIL(pt, condition) 等待某个条件(条件可以为时钟或其它变量,IO等)成立,否则直接退出本函数,下一次进入本函数就直接跳到这个地方判断
PT_WAIT_WHILE(pt, condition)  和上面一个一样,只是条件取反了
PT_WAIT_THREAD(pt, thread) 等待一个子任务执行完成
PT_SPAWN(pt, child, thread) 新建一个子任务,并等待其执行完退出
PT_RESTART(pt)   重新启动某个任务执行
PT_EXIT(pt)   任务后面的部分不执行,直接退出重新执行
PT_YIELD(pt)   锁死任务
PT_YIELD_UNTIL(pt, cond) 锁死任务并在等待条件成立,恢复执行
在pt中一共定义四种线程状态,在任务函数退出到上一级函数时返回其状态
PT_WAITING  等待
PT_EXITED  退出
PT_ENDED  结束
PT_YIELDED  锁死

下面文件是对ProtoThread源码的分析:
轻量级线程库protothreads介绍.pdf (434.9 KB)

我们需要做的就是保证每个线程的完整性,与事件处理程序的编写,接下来可以结合其他硬件进行更为复杂系统的开发。

PT1.4源码:
pt-1.4.zip (256.5 KB)




相关帖子

沙发
mcu5i51| | 2016-12-2 14:35 | 只看该作者
挺好,简单好用

使用特权

评论回复
板凳
as4781| | 2016-12-19 15:31 | 只看该作者
挺经典的一个例子了,不错

使用特权

评论回复
地板
colin2135| | 2016-12-21 11:13 | 只看该作者
不错,学习了。

使用特权

评论回复
5
6881087| | 2017-3-22 08:10 | 只看该作者
不错,挺好的,学习了。

使用特权

评论回复
6
zxy2266| | 2017-5-24 21:42 | 只看该作者
OS确实很方便,但俺是软件小白,看不懂啊

使用特权

评论回复
7
wdliming| | 2022-6-16 09:41 | 只看该作者
谢谢分享

使用特权

评论回复
8
gaochy1126| | 2022-6-29 16:46 | 只看该作者
Arduino还可以开多线程?这个确实可以啊。            

使用特权

评论回复
9
gaochy1126| | 2022-6-29 16:47 | 只看该作者
Arduino Nano运行操作系统感觉就剩不下多少内存了吧。      

使用特权

评论回复
10
gaochy1126| | 2022-6-29 16:50 | 只看该作者
ATmega328的频率也比较低,不如esp32的速度快了。         

使用特权

评论回复
11
xu@xupt| | 2022-12-13 21:25 | 只看该作者
学习啦

使用特权

评论回复
12
gaochy1126| | 2022-12-29 20:10 | 只看该作者
使用ESP32,这个确实是多进程的操作,性能很给力的。                           

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:欢迎参与LabVIEW版块的讨论学习! 点我一键即达

157

主题

2338

帖子

41

粉丝