Archive for 数字电路

ProtoThread 协程复习笔记

近一年没用 ProtoThread 了,忘得差不多了。这次搞硬件密码器重新学习了下,同时记录下来笔记,方便下次使用。

ProtoThread 是一个资源占用极少的多线程协程库,可以在51、avr等资源极其有限的硬件上面运行。

ProtoThread 有几种实现方式,这里只考虑存c switch 实现。这个实现其实就是用1byte存放当前执行到的位置,下次恢复这个线程的时候通过 switch 跳转到上次执行的位置继续执行。每个线程就只占用2byte存放当前位置(当前代码行号)即可。伪代码:

 
//线程结构体
struct pt {
  unsigned short lc;   // 当前线程执行到的位置(行号)
};
PT_THREAD(f1(struct pt *pt))
// 展开宏后的代码
// char f1(struct pt *pt)
{
    // 这里可以写每次任务切换进来都执行的代码    
    //  注意,只有静态变量才能正确的保存,非静态变量在切换协程时会丢失
    // 同样,静态变量的限制使得一个函数不能多协程同时执行,局部变量会冲突。
    static int a;

   PT_BEGIN(pt);  // 准备协程环境
    // 展开宏后的代码:
    // char PT_YIELD_FLAG = 1;
   //  switch(pt.lc) { case 0;
   // 等待至条件达成
    // 可以看到,是通过记录当前执行位置后直接退出函数来实现的让出cpu。
    // 恢复执行时通过 switch 的 case 跳转回上次执行的位置来实现的恢复执行。
    PT_WAIT_UNTIL(pt, 条件);
    // 展开后的代码:
    /*
  do {
    (pt)->lc =  __LINE__;
     case __LINE__:;
    if(!(condition)) {
      return PT_WAITING;  
    }
  } while(0)
   */
   PT_END(pt); // 结束协程环境
    // 展开后的代码:
    // };
    // PT_YIELD_FLAG = 0;
    // pt.lc=0;
    // return PT_ENDED;
}
void main()
{
    struct pt p1;    // 线程1
    struct pt p2;   // 线程2
   //初始化线程
    PT_INIT(&p1); // 其实就是 p1.lc = 0
    PT_INIT(&p2); 
   while(1)
    {
      /// 启动两个线程
      f1(&p1); 
      f1(&p2);        
    }
}

这种实现的几个特点:
• 纯c实现,无硬件依赖,方便移植
• 不能在函数内再次使用switch
• 资源占用极少,每线程仅占用2字节
• 支持阻塞操作,没有栈的切换
• 运行在单一c函数中,无法跨函数是用。弥补措施是被调用函数也写成单独的Protothread
• 线程切换时不保存局部变量,弥补措施是使用静态局部变量,造成单个函数不能多线程同时执行。另一个弥补措施是实现自己的 pt 结构,将需要保存的局部变量保存到 pt 结构里面。

附几个函数:
PT_INIT(pt) 初始化任务变量,只在初始化函数中执行一次就行
PT_BEGIN(pt) 启动任务处理,放在函数开始处
PT_END(pt) 结束任务,放在函数的最后
PT_WAIT_UNTIL(pt, condition) 等待某个条件(条件可以为时钟或其它变量,IO等)成立,否则直接退出本函数,下一次进入本 函数就直接跳到这个地方判断
PT_WAIT_WHILE(pt, cond) 和上面一个一样,只是条件取反了
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 锁死

(下面的是定时器,该宏是 http://www.cnblogs.com/xiaowuyi/p/4319720.html 写的,用之前请在#include “pt.h” 的前面,前面啊!加上一句#define PT_USE_TIMER)

先说明一下,下面的定时不一定完全准确的,可能会有点点的误差,可能偏后。如果遇上了很烦的任务,有可能会使延时延后。但是正常情况下,直接用就好了。

如果要很精确的延时,请用delay语句或者计时器,但是,绝大多数情况下,绝大多数情况!绝大多数情况!请用下面的语句代替delay延时!这样才能把CPU让给别的任务使用。

PT_TIMER_DELAY(pt,延时毫秒数);
字面上的意思,不用多说了吧?最大值约为49.7天,估计没人会延时辣么久吧……

PT_TIMER_MICRODELAY(pt,延时微秒数);
字面上的意思,不用多说了吧?注意,最小精度与arduino的版本有关,与micros()有精度一致。

PT_TIMER_WAIT_TIMEOUT(pt,条件,毫秒数);
如果条件成立了,或者超时了,就继续运行,否则切换任务。

要用的话,请在#include “pt.h”前面加上一句 #define PT_USE_SEM

首先要创建一个信号量,这个一定是全局变量:
static struct pt_sem 信号量名;

接着请在setup()函数里面给它初始化:
PT_SEM_INIT(&信号量名,数量);
信号量名前面有个&,别忘了。数量就相当于停车场的总车位数。

然后要用啦。任务要停一辆车进去:
PT_SEM_WAIT(pt,&信号量名);
信号量名前面有个&,别忘了。一个语句只能停一辆车,土豪好多车就用多次。

任务要开一辆车出来:
PT_SEM_SIGNAL(pt,&信号量名);
信号量名前面有个&,别忘了。用一次出一辆。

参考:
• http://dunkels.com/adam/pt/
• Protothread机制文档 http://blog.csdn.net/tietao/article/details/8507455
• 一个“蝇量级” C 语言协程库 http://coolshell.cn/articles/10975.html
• ProtoThreads http://blog.csdn.net/utopiaprince/article/details/6041385

No comment »

NRF24L01 无线模块笔记

NRF24L01 收发地址

NRF24L01 模式

引脚      名称      引脚功能     描述
1         CE        数字输入     RX 或 TX 模式选择
2         CSN       数字输入     SPI 片选信号
3         SCK       数字输入     SPI 时钟
4         MOSI      数字输入     从 SPI 数据输入脚
5         MISO      数字输出     从 SPI 数据输出脚
6         IRQ       数字输出     可屏蔽中断脚
7         VDD       电源         电源(+3V)
8         VSS       电源         接地(0V)

nRF24L01 可以设置为以下几种主要的模式,
模式            PWR_UP         PRIM_RX              CE            FIFO 寄存器状态 
接收模式         1                1                  1             -
发送模式         1                0                  1             数据在 TX FIFO 寄存器中
发送模式         1                0                 1→0            停留在发送模式,直至数据发送完
待机模式 II      1                0                  1             TX FIFO 为空
待机模式 I       1                -                  0             无数据传输
掉电模式         0                -                  -             -

关于 nRF24L01 I/O 脚更详细的描述请参见下面的表。
nRF24L01 在不同模式下的引脚功能
引脚名称         方向        发送模式       接收模式     待机模式    掉电模式 
CE              输入         高电平>10us     高电平       低电平     -  
CSN             输入         SPI 片选使能,低电平使能
SCK             输入         SPI 时钟
MOSI            输入         SPI 串行输入
MISO            三态输出     SPI 串行输出
IRQ             输出        中断,低电平使能

增强型的ShockBurstTM 模式 比 ShockBurstTM 模式多了个自动应答、自动重发功能。

nRF24L01 在接收模式下可以接收6 路不同通道的数据,每一个数据通道使用不同的地址,但
是共用相同的频道。也就是说6 个不同的nRF24L01 设置为发送模式后可以与同一个设置为接收模式的
nRF24L01 进行通讯,而设置为接收模式的nRF24L01 可以对这6 个发射端进行识别。数据通道0 是唯一
的一个可以配置为40 位自身地址的数据通道。1~5 数据通道都为8 位自身地址和32 位公用地址。所有的
数据通道都可以设置为增强型ShockBurst 模式。
nRF24L01 在确认收到数据后记录地址,并以此地址为目标地址发送应答信号。在发送端,数据通道0
被用做接收应答信号,因此,数据通道0 的接收地址要与发送端地址相等以确保接收到正确的应答信号。
见图5 选择地址举例。

增强型ShockBurstTM 发送模式:
1、 配置寄存器位PRIM_RX 为低
2、 当MCU 有数据要发送时,接收节点地址(TX_ADDR)和有效数据(TX_PLD)通过SPI 接口写入
nRF24L01。发送数据的长度以字节计数从MCU 写入TX FIFO。当CSN 为低时数据被不断的写入。
发送端发送完数据后,将通道0 设置为接收模式来接收应答信号,其接收地址(RX_ADDR_P0)与接
收端地址(TX_ADDR)相同。例:在图5 中数据通道5 的发送端(TX5)及接收端(RX)地址设置如下:
TX5:TX_ADDR=0xB3B4B5B605
TX5:RX_ADDR_P0=0xB3B4B5B605
RX:RX_ADDR_P5=0xB3B4B5B605
3、 设置CE 为高,启动发射。CE 高电平持续时间最小为10 us。
4、 nRF24L01 ShockBurstTM 模式:

限制:
单无线模块只能接受4个设备发送的信息(实际是6个,但是1个为自动应答使用,一个我打算作为默认广播地址使用,就只剩下4个了。其中除了自动应答使用的1个设备地址之外的5个设备的地址非8位部分必须相同。8位地址可以容纳255个设备。),单个设备只能有一个设备地址来接收其他设备发送的信息。
一般200个地址就足够用了,也就是把所有设备除了后8位全部设置成为一样的。

路由设计:

之前被nrf24文档里面的图给坑了,认为为了维持两个设备之间的无线通信必须双方都有一个接收地址设置成为一样的,造成单个nrf24只能和6个设备通信。实际上被那个图给误解了,发数据到另一个设备时只需要把发送地址设置为对方的接收地址(6个中的任意一个)就行,并不需要把自己的接收地址也设置为对方的接收地址(开启自动重发功能时需要将接收地址1设置为发送地址来接受ACK回应),这样设备通信数量的限制就基本上没有了。

文档相关资料:
2、 当MCU 有数据要发送时,接收节点地址(TX_ADDR)和有效数据(TX_PLD)通过SPI 接口写入
nRF24L01。发送数据的长度以字节计数从MCU 写入TX FIFO。当CSN 为低时数据被不断的写入。
发送端发送完数据后,将通道0 设置为接收模式来接收应答信号,其接收地址(RX_ADDR_P0)与接
收端地址(TX_ADDR)相同。
4、 接收到有效的数据包后(地址匹配、CRC 检验正确),数据存储在RX_FIFO 中,同时RX_DR 位
置高,并产生中断。状态寄存器中RX_P_NO 位显示数据是由哪个通道接收到的。

中继设备只需要保存

目前不考虑超过200各设备的情况,真要超过的话以后可以分组的方式来解决。
添加新设备方式:
主控直接和间接(通过已并网的设备中继)的向新设备默认地址广播探测新设备。广播信息如果经过中继的话会记录着中继信息。新设备收到后,会已新设备默认地址按原路径回复信息(包含设备唯一编号)。主控会根据收到的回应来确认找到几个新设备(通过设备唯一编号),各个设备有几条线路可以连接到主控。然后主控为新设备分配设备地址(以后设备地址就固定为这个了)并生成主路由和备用路由信息发给新设备,然后新设备启用收到的新设备地址及路由信息。

个别设备断网时重新入网办法:
< del datetime="2014-01-31T05:43:02+00:00">子设备在主路由信息无法连接主控时会将自身地址设置为本网的新设备默认地址,并循环使用几个主备路由信息来尝试连接到主控。连接到主控则更新路由信息。< /del>

主控在连接不到子设备时会以本网的新设备默认地址为接收地址直接和间接(通过已并网设备中继)的发送探测子设备信息,子设备要是收到则回复,主控等待一段时间根据收到的回复建立新的路由信息并发给子设备,子设备重新并网。

当然,如果主控实在是收不到回应则要么是子设备丢失了本网的地址段(网段非默认网段的情况),要么是之间连接中继或信道环境严重恶化,无法连接,或者子设备掉电或损坏。这些情况只能人工检查和修复了。

No comment »

python 与 arduino 串口通信

 
>>> import serial
>>> ser = serial.Serial(8)
>>> ser.timeout=1
>>> ser.close()
>>> ser = serial.Serial(8)
>>> ser.timeout=1
>>> ser.readline()
''
>>> ser.write("11111\n")   # 每个命令以 \n 结尾
6L
>>> ser.readline()
'received: 11111\r\n'
>>> ser.write("11111\n")
6L
>>> ser.write("22222\n33333333\n") # 可以一次发送多个命令,同样以 \n 结尾。
15L
>>> ser.readline()
'received: 11111\r\n'
>>> ser.readline()
'received: 22222\r\n'
>>> ser.readline()
'received: 33333333\r\n'
>>> ser.readline()
''
>>> 
 
char serial_line[100] ="";
int serial_line_length=0;

void setup()
{
  Serial.begin(9600);
  Serial.setTimeout(1000); //串口超时 1000 毫秒
}

void loop()
{
  if (Serial.available() > 0)
  {
    // 读取,读到\n或100字符或超时
    serial_line_length = Serial.readBytesUntil('\n', serial_line, 100);
    serial_line[serial_line_length ]='\0';    // 截断字符串
    Serial.print("received: ");
    Serial.println(serial_line);
  }
}

参考:
http://www.geek-workshop.com/thread-5733-1-1.html
http://wiki.geek-workshop.com/doku.php?id=arduino:arduino_language_reference:serial
http://pythonhosted.org//pyserial/pyserial_api.html

No comment »

ARDUINO 最小系统

atmeg328 内置复位电路,不需要外接复位电路。虽然也有内置晶振,但是 ARDUINO 只支持 16M 或 8M 的频率,所以最小系统还得用 16M 晶振接到9、10口(XTAL1、XTAL2),然后再分别接2个22p的电容到地就可以了。

全新的 atmeg328 没有 ARDUINO 的bootloader(引导程序) ,得用 IPS 接口写进去。之后就可以用串口写程序了。
熔丝位之类的设置不用管,ARDUINO IDE 写bootloader时会自动设置。对了,ARDUINO 的“使用编程器下载”功能会清除 ARDUINO 的bootloader(引导程序),除非资源太紧张,不建议使用。

如果需要复位按钮,可以用一个10K上拉电阻接到1(RESET)脚,然后弄个按钮,一端接到地,一段也接到1(RESET)脚。

还有一个问题,atmeg328 有两个 VCC 口(一个是 A/D 转换器的电源,这个必须接,不然芯片跑不起来,各个脚都是0.5V电压),建议都接到电源上面,如果只接7脚的 VCC ,会悲剧的跑不起来… 今天一上午都弄这个问题了。

附参考:
ARDUINO_V2

atmega328w

Arduino_Uno_Rev3-schematic

No comment »

Arduino 下 Nokia 5110 LCD + DS18B20 多点测温

温度库:http://milesburton.com/Dallas_Temperature_Control_Library
5110库:https://github.com/adafruit/Adafruit-PCD8544-Nokia-5110-LCD-library

资料上显示 DS18B20 可以使用 3-5v 电源供电,可以做到9-12位精度。不过12位精度比9位精度测温慢多了。而1820只能5V供电,9位精度。

 
/*********************************************************************
This is an example sketch for our Monochrome Nokia 5110 LCD Displays

  Pick one up today in the adafruit shop!
  ------> http://www.adafruit.com/products/338

These displays use SPI to communicate, 4 or 5 pins are required to
interface

Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!

Written by Limor Fried/Ladyada  for Adafruit Industries.
BSD license, check license.txt for more information
All text above, and the splash screen must be included in any redistribution
*********************************************************************/

#include 
#include 
#include "floatToString.h"

#include 
#include 

// pin 7 - Serial clock out (SCLK)
// pin 6 - Serial data out (DIN)
// pin 5 - Data/Command select (D/C)
// pin 4 - LCD chip select (CS)
// pin 3 - LCD reset (RST)
// pin2 - DS18B20  需要4.7k欧上拉电阻
Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3);

#define NUMFLAKES 10
#define XPOS 0
#define YPOS 1
#define DELTAY 2


#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16

static unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };





// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS 2
#define TEMPERATURE_PRECISION 12

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

// arrays to hold device addresses
DeviceAddress insideThermometer, outsideThermometer;




void setup()   {
  Serial.begin(9600);

  display.begin();
  // init done

  // you can change the contrast around to adapt the display
  // for the best viewing!
  display.setContrast(50);

  display.display(); // show splashscreen
  
  
  // Start up the library
  sensors.begin();

  // locate devices on the bus
  Serial.print("Locating devices...");
  Serial.print("Found ");
  Serial.print(sensors.getDeviceCount(), DEC);
  Serial.println(" devices.");
    // set the resolution to 12 bit
  sensors.setResolution( TEMPERATURE_PRECISION);

  

  // draw a single pixel
  display.drawPixel(10, 10, BLACK);
  display.display();
  delay(2000);
  
  
}

//char buffer[25];// floatToString
//char s[25];// floatToString


void loop() {
  
    Serial.print("Requesting temperatures...");
  sensors.requestTemperatures();
  Serial.println("DONE");
  

  display.clearDisplay();   // clears the screen and buffer

//  float tempC = sensors.getTempCByIndex(0);
//  floatToString(buffer, tempC, 2);
//  Serial.println("Temp C: ");
//  Serial.print(tempC);
//sprintf(s,"c:%s",buffer);

  // text display tests
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.setCursor(0,0);
  display.print("c0:");
  display.println(sensors.getTempCByIndex(0));
  display.print("c1:");
  display.println(sensors.getTempCByIndex(1));
  display.print("c2:");
  display.println(sensors.getTempCByIndex(2));
  display.display();
  delay(2000);



}

注意,需要使用arduino-1.52以上版本,arduino-1.05的会出现以下错误:

\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp: In constructor 'RobotControl::RobotControl()':
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:8: error: 'LCD_CS' was not declared in this scope
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:8: error: 'DC_LCD' was not declared in this scope
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:8: error: 'RST_LCD' was not declared in this scope
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp: In member function 'void RobotControl::begin()':
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:18: error: 'MUXA' was not declared in this scope
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:18: error: 'MUXB' was not declared in this scope
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:18: error: 'MUXC' was not declared in this scope
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:18: error: 'MUXD' was not declared in this scope
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:19: error: 'MUX_IN' was not declared in this scope
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:22: error: 'BUZZ' was not declared in this scope
\arduino-1.0.5-windows\arduino-1.0.5\libraries\Robot_Control\ArduinoRobot.cpp:25: error: 'Serial1' was not declared in this scope

No comment »