[应用相关] mbed 单总线通讯

[复制链接]
1471|5
 楼主| 捉虫天师 发表于 2015-12-30 20:11 | 显示全部楼层 |阅读模式

不管是UART,I2C还是SPI,它们通讯时都需要使用2条或2条以上的数据线,但在某些情况下,数据线或者MCU的管脚是非常宝贵的,在某些对通讯速度要求不高的情况下能否只利用一条数据线实现通讯呢?答案是肯定的,而且在必要的情况下,这条数据线同时也是电源线,从而实现只利用GND和DATA两条连接线就实现电源供给和数据传输,即单总线(OneWire)通讯。

单总线系统一般由一个单总线主机和多个从器件构成,微处理器为主机,其它单总线设备为从机,可以实现一对多的通讯,也就是说每个外设都有唯一的地址,标准速度为15.4kbps。单总线在使用时需要1个上拉电阻保证总线空闲时为高电平,其具体的连接样例如下:

单总线的具体通信过程请参考相关标准文档,其本质就是在严格时序限定下的数据读取,具体描述如下:

单总线协议首先定义了复位脉冲、应答脉冲、写0、读0和读1时序等几种基本信号类型,所有的单总线命令序列(初始化,ROM命令,功能命令)都是由这些基本的信号类型组成的。在这些信号中,除了应答脉冲外,其它均由主机发出同步信号,并且发送的所有命令和数据都是字节的低位在前。各个基本信号的具体描述如下:

l  复位脉冲:主机通过拉低单总线至少480μs产生Tx复位脉冲,然后由主机释放总线,并进入Rx接收模式;

l  应答脉冲:主机释放总线时,会产生一由低电平跳变为高电平的上升沿,单总线器件检测到该上升沿后,延时15~60μs,接着单总线器件通过拉低总线60~240μsμ来产生应答脉冲;

l  写信号:所有的读、写时序至少需要60μs,且每两个独立的时序之间至少需要1μs的恢复时间。写时序均始于主机拉低总线,若主机在拉低总线15μs之内释放总线,表示单总线器件写1;若主机拉低总线后能保持至少60μs的低电平,则表示向单总线器件写0;

l  读信号:读时序也始于主机拉低总线,在主机发出读时序之后,单总线器件开始在总线上发送0或1。若单总线器件发送1,则总线保持高电平,若发送0,则拉低总线。由

单总线器件发送数据后可保持15μs有效时间,因此,主机在读时序期间必须释放总线,且须在15μs的采样总线状态,以便接收从机发送的数据。

下面是各个基本信号的时序图:

一般单总线设备的访问过程如下:

基于以上基本信号,单总线设备的访问过程如下:

1.  初始化:单总线上的所有传输操作均以初始化序列开始。初始化序列由总线主机发送的复位脉冲和随后从器件发送的一个在线应答脉冲组成。从器件发出的在线应答脉冲的作用是让主机知道从设备已经准备就绪。

2.  写入功能命令:一旦主机检测到从器件的应答脉冲,它就可以发出符合从设备格式的功能命令,通用命令有:READ,SEARCH,SKIP等,具体需要参考器件的datasheet。

3.  读取从设备发过来的数据:从设备在正常情况下不能主动发送数据,在主机的要求下才发送,这时主机就可以根据格式读取从设备发过来的数据了。

   
 楼主| 捉虫天师 发表于 2015-12-30 20:12 | 显示全部楼层
单总线通讯初步应用

mbed并没有提供官方的单总线对象库给我们使用,但我们可以寻找第三方的来使用,而且通过对以上单总线协议的理解,自己写一个也并不复杂。Xbed LPC1768板载的ds2411就是一个包含48bit唯一编码的单总线协议设备,我们可以用它来测试我们写的代码的正确性,下面是一个单总线库的简单实现:

  1. OneWire::OneWire(PinName _owpin) : owpin(_owpin) {

  2.     owpin.mode(PullUp); //把管脚设成上拉模式,相当于我们在外部接了上拉电阻

  3. }

  4. OneWire::~OneWire() {

  5. };

  6. //读取唯一地址,对于ds2411来说也就是它的值

  7. void OneWire::getRomCode(char *rc) {

  8.     this->busInit();//从复位开始

  9.     this->writeByte(0x33);//预定于命令

  10.     for (int i=0; i<=7; i++) {

  11.         rc[i] = this->readByte();

  12.     }

  13. }

  14. void OneWire::cmdDevice(char *rc, char cmd) {

  15.     this->busInit();

  16.     this->writeByte(0x55);

  17.     for (int i=0; i<=7; i++) {

  18.         this->writeByte(rc[i]);

  19.     }

  20.     this->writeByte(cmd);

  21. }

  22. void OneWire::getData(char *data, int bytes) {

  23.     for (int i=0;i<=bytes-1;i++) {

  24.         data[i] = this->readByte();

  25.     }

  26. }

  27. //写数据0,1

  28. void OneWire::writeBit(int bit) {

  29.     this->owpin.output();

  30.     if (bit==1)

  31.     {

  32.         this->owpin = 0;

  33.         wait_us(10);

  34.         this->owpin = 1;

  35.         wait_us(55);

  36.     }

  37.     else

  38.     {

  39.         this->owpin = 0;

  40.         wait_us(65);

  41.         this->owpin = 1;

  42.         wait_us(5);

  43.     }

  44. }

  45. //读数据0,1

  46. int OneWire::readBit() {

  47.     int pin;

  48.     this->owpin.output();

  49.     this->owpin = 0;

  50.     wait_us(3);

  51.     this->owpin = 1;

  52.     wait_us(10);

  53.     this->owpin.input();

  54.     pin = this->owpin;

  55.     this->owpin.output();

  56.     wait_us(53);

  57.     return pin;

  58. }

  59. void OneWire::writeByte(unsignedchar by) {

  60.     int b;

  61.     int i;

  62.     for (i=0; i<=7; i++) {

  63.         b = by & 0x01;

  64.         this->writeBit(b);

  65.         by = by >> 1;

  66.     }

  67. }

  68. unsignedchar OneWire::readByte(void) {

  69.     unsignedchar i;

  70.     unsignedchar wert = 0;

  71.     for (i=0; i<8; i++) {

  72.         if (this->readBit()) wert |=0x01 << i;

  73.     }

  74.     return(wert);

  75. }

  76. //初始化

  77. int OneWire::busInit() {

  78.     int r;

  79.     this->owpin.output();

  80.     this->owpin = 0;

  81.     wait_us(540);

  82.     this->owpin = 1;

  83.     this->owpin.input();

  84.     wait_us(65);

  85.     r = this->owpin;

  86.     this->owpin.output();

  87.     wait_us(540);

  88.     return r;

  89. }

基于以上代码我们就可以使用下面的代码来打印板载的ds2411数值。

  1. #include"OneWire.h"

  2. Serial pc(USBTX,USBRX);

  3. DigitalOut led(LED1);

  4. OneWire ds2411(P1_29);

  5. int main() {

  6.     char romcode[8];             // Array for ROM-Code

  7.     ds2411.busInit();

  8.     ds2411.getRomCode(romcode);      // Get ROM-Code

  9.     printf("Ds2411 rom is %X.%X.%X.%X.%X.%X.%X.%X \n",romcode[0],romcode[1],romcode[2],romcode[3],romcode[4],romcode[5],romcode[6],romcode[7]);



  10. }


 楼主| 捉虫天师 发表于 2015-12-30 20:13 | 显示全部楼层
单总线协议读取DS18B20

DS18B20是非常常用的单总线温度传感器,我们可以采用下面的方式把它与xbed LPC1768相连,由于LPC1768内置上拉电阻,所以图中的上拉电阻并不是必须的:

与之相对应的代码如下:

  1. #include"OneWire.h"

  2. Serial pc(USBTX,USBRX);

  3. OneWire ds(p20);

  4. int main() {

  5.     char type_s;

  6.     char data[12];

  7.     char addr[8];

  8.     float celsius, fahrenheit;

  9.     ds.getRomCode(addr);

  10.     printf("Ds18b20 address is %X.%X.%X.%X.%X.%X.%X.%X \n",addr[0],addr[1],addr[2],addr[3],addr[4],addr[5],addr[6],addr[7]);

  11.     switch (addr[0]) {

  12.     case 0x10:

  13.         pc.printf("Chip = DS18S20 \n");

  14.         type_s = 1;

  15.         break;

  16.     case 0x28:

  17.         pc.printf("Chip = DS18B20 \n");

  18.         type_s = 0;

  19.         break;

  20.     case 0x22:

  21.         pc.printf("Chip = DS1822 \n");

  22.         type_s = 0;

  23.         break;

  24.     default:

  25.         pc.printf("Device is not a DS18x20 family device.");

  26.         return 0;

  27.     }

  28.     ds.cmdDevice(addr,0x44);

  29.     wait_ms(1000);

  30.     ds.cmdDevice(addr,0xBE);

  31.     ds.getData(data,9);

  32.     unsignedint raw = (data[1] << 8) | data[0];

  33.     if (type_s) {

  34.         raw = raw << 3; // 9 bit resolution default

  35.         if (data[7] == 0x10) {

  36.             // count remain gives full 12 bit resolution

  37.             raw = (raw & 0xFFF0) + 12 - data[6];

  38.         }

  39.     } else {

  40.         char cfg = (data[4] & 0x60);

  41.         if (cfg == 0x00) raw = raw << 3;  // 9 bit resolution, 93.75 ms

  42.         elseif (cfg == 0x20) raw = raw << 2; // 10 bit res, 187.5 ms

  43.         elseif (cfg == 0x40) raw = raw << 1; // 11 bit res, 375 ms

  44.         // default is 12 bit resolution, 750 ms conversion time

  45.     }

  46.     celsius = (float)raw / 16.0;

  47.     fahrenheit = celsius * 1.8 + 32.0;

  48.     pc.printf("Temperature = %f Celsius, %f Fahrenheit. \n",celsius,fahrenheit);



  49. }

该代码中具体发送命令的含义请参考数据手册,不过下面几个命令是单总线中常用的,我们有必要了解一下:

l  Read ROM(读ROM)[33h]

此命令允许总线主机读单总线设备的8位产品系列编码,唯一的48位序列号,以及8位的CRC。此命令只能在总线上仅有一个单总线的情况下可以使用。如果总线上存在多于一个的从属器件,那么当所有从片企图同时发送时将发生数据冲突的现象(漏极开路会产生线与的结果)。

l  Match ROM( 符合ROM)[55h]

此命令后继以64位的ROM数据序列,允许总线主机对多点总线上特定的单总线设备寻址。只有与64位ROM序列严格相符的单总线设备才能对后继的存贮器操作命令作出响应。所有与64位ROM序列不符的从片将等待复位脉冲。此命令在总线上有单个或多个器件的情况下均可使用。

l  Write Scratchpad(写暂存存储器)[4Eh]

这个命令向单总线的暂存器中写入数据,开始位置在地址2。接下来写入的两个字节将被存到暂存器中的地址位置2和3。可以在任何时刻发出复位命令来中止写入。

l  Read Scratchpad(读暂存存储器)[BEh]

       这个命令读取暂存器的内容。读取将从字节0开始,一直进行下去,直到第9(字节8,CRC)字节读完。如果不想读完所有字节,控制器可以在任何时间发出复位命令来中止读取。


dongnanxibei 发表于 2015-12-30 22:01 | 显示全部楼层
读信号:读时序也始于主机拉低总线,在主机发出读时序之后,单总线器件开始在总线上发送0或1。若单总线器件发送1,则总线保持高电平,若发送0,则拉低总线。由单总线器件发送数据后可保持15μs有效时间,因此,主机在读时序期间必须释放总线,且须在15μs的采样总线状态,以便接收从机发送的数据。
 楼主| 捉虫天师 发表于 2016-1-11 19:04 | 显示全部楼层
Read ROM(读ROM)[33h]
此命令允许总线主机读单总线设备的8位产品系列编码,唯一的48位序列号,以及8位的CRC。此命令只能在总线上仅有一个单总线的情况下可以使用。如果总线上存在多于一个的从属器件,那么当所有从片企图同时发送时将发生数据冲突的现象(漏极开路会产生线与的结果)。
尤彼卡 发表于 2016-1-11 22:15 | 显示全部楼层
主机在读时序期间必须释放总线,且须在15μs的采样总线状态
您需要登录后才可以回帖 登录 | 注册

本版积分规则

213

主题

3276

帖子

7

粉丝
快速回复 在线客服 返回列表 返回顶部