STM32基于onewire单总线的数据抽象实例简析

2024-03-21  

1.前言


onewire(单总线) 是DALLAS公司推出的外围串行扩展总线技术总线,顾名思义,它是采用一根信号线进行通信,既传输时钟信号又传输数据,而且能够进行双向通信,具有节省I/O口线、资源结构简单、成本低廉、便于总线扩展和维护等诸多优点。


常用到单总线的器件,一般是温度传感器、EEPROM、唯一序列号芯片等,如DS18B20、DS2431。


在使用单总线时,往往很少CPU会提供硬件单总线,几乎都是根据单总线标准的时序图,通过普通IO翻转模拟实现单总线。而在模式实现时序图的过程中,需要根据CPU时钟频率等条件进行时序时间计算,如果更换CPU后,需要重新计算时序时间,如果时序代码和器件外设控制代码集成在一起,则代码改动比较大。


或者同一CPU需要模拟多根单总线时,传统的“复制”方式使得程序显得累赘,还增加ROM占用空间。因此,可以利用“函数指针”的方式,将时序部分抽象出来,达到“复用”代码的效果,减少重复代码编写。


2.onewire 抽象


2.1 onewire 结构体


onewire结构体主要是对与CPU底层相关的操作抽象分离,调用时只需将该结构体地址(指针)作为函数入口参数,通过该指针实现对底层函数的回调。该结构体我们命名为“struct ops_onewire_dev”,其原型如下:


struct ops_onewire_dev

{

    void (*set_sdo)(int8_t state);

    uint8_t (*get_sdo)(void);

    void (*delayus)(uint32_t us);

};

其中: 1)set_sdo:IO输出1bit,包括时钟和数据。 2)get_sdo:IO输入1bit,包括时钟和数据。 3)delayus:时序延时函数,根据CPU频率进行计算。 回调函数相关文章:C语言技巧之回调函数


2.2 onewire 对外接口


extern uint8_t ops_onewire_reset(struct ops_onewire_dev *onewire);

extern int ops_onewire_read(struct ops_onewire_dev *onewire,void *buff,int size);

externintops_onewire_write(structops_onewire_dev*onewire,void*buff,intsize);

1)分别为复位函数、读函数、写函数。


2)入口首参数为“struct ops_onewire_dev”结构体指针,此部分就是硬件层相关,需要后期初始化的.


3)其余入口参数易于理解,读/写缓存及数据大小。


2.3 onewire 抽象接口实现


分别实现上述三者函数接口。


2.3.1 复位函数


复位函数,在单总线初始化外设器件时需要用到,用于判断总线与器件是否通信上,类似“握手”的动作。如图,为DS18B20的复位时序图,以下与单总线相关的时序图,都是以DS18B20为例,因为此芯片为单总线应用的经典。23fff114b6074d78f35f5d9f360579b3_wKgZomU7awiAfN1MAACD9VFIX00365.png

根据时序图,实现复位函数。


/**

  * @brief  单总线复位时序

  * @param  onewire 总线结构体指针

  * @retval 成功返回0

*/

uint8_t ops_onewire_reset(struct ops_onewire_dev *onewire)

{

 uint8_t ret = 0;

 

 onewire->set_sdo(1);

 onewire->delayus(50);

 onewire->set_sdo(0);

 onewire->delayus(500);

 onewire->set_sdo(1);

 onewire->delayus(40);

 ret = onewire->get_sdo();

 onewire->delayus(500);

 onewire->set_sdo(1);

 return ret;

}


2.3.2 读函数


读函数即以该函数,通过单总线从外设上读取数据,至于代码的实现,完全是时序图的实现,无特殊难点。先实现单字节读函数,再通过调用单字节读函数实现多字节读函数。


bdf0246cecda453a11b58849190e5684_pYYBAGGSOe-AeFXUAACD9VFIX00147.png


/**

  * @brief  单总线读取一字节数据

  * @param  onewire 总线结构体指针

  * @retval 返回读取的数据

*/

static char ops_onewire_read_byte(struct ops_onewire_dev *onewire)

{

 char data = 0;

 uint8_t i;

 

 for(i=8;i>0;i--)

 {

  data >>= 1;

  onewire->set_sdo(0);

  onewire->delayus(5);

  onewire->set_sdo(1);

  onewire->delayus(5);

  if(onewire->get_sdo())

   data |= 0x80;

  else

   data &= 0x7f;

  onewire->delayus(65);

  onewire->set_sdo(1);

 }

 return data;

}


/**

  * @brief  读取多字节

  * @param  onewire 总线结构体指针

  * @param  buff 存放数据缓存

  * @param  size 数据大小

  * @retval 返回读取到的数据大小

*/

int ops_onewire_read(struct ops_onewire_dev *onewire,void *buff,int size)

{

 int i;

 char *p = (char*)buff; 

 for(i=0;i


2.3.3 写函数


写函数与读函数同理,即以该函数,通过单总线往外设写入数据,至于代码的实现,完全是时序图的实现,无特殊难点。先实现单字节写函数,再通过调用单字节写函数实现多字节写函数。

b0e1fa49040c89b1566377df4e198832_poYBAGGSOfGAUeEhAADKD-j20k4098.png

/**

  * @brief  单总线写一字节

  * @param  onewire 总线结构体指针

  * @param  data 待写数据

  * @retval 返回读取的数据

*/

static int ops_onewire_write_byte(struct ops_onewire_dev *onewire,char data)

{

 uint8_t i;

 

 for(i=8;i>0;i--)

 {

  onewire->set_sdo(0);

  onewire->delayus(5);

  if(data&0x01)

   onewire->set_sdo(1);

  else

   onewire->set_sdo(0);

  onewire->delayus(65);

  onewire->set_sdo(1);

  onewire->delayus(2);

  data >>= 1;

 }

 return 0;

}



/**

  * @brief  写多字节

  * @param  onewire 总线结构体指针

  * @param  buff 代写数据地址

  * @param  size 数据大小

  * @retval 写入数据大小

*/

int ops_onewire_write(struct ops_onewire_dev *onewire,void *buff,int size)

{

 int i;

 char *p = (char*)buff;

 for(i=0;i

 至此,onewire(单总线)抽象化完成,此部分代码与硬件层分离,亦可单独作为一个模块,移植到不同平台CPU时,也几乎无需改动。剩下部分工作则是实现“struct ops_onewire_dev”中的函数指针原型,即可使用一根单总线。


3.onewire 抽象应用


以STM32F1为例,实现上述抽象接口。


3.1 “struct ops_onewire_dev” 实现


此部分即是与硬件相关部分,不同CPU平台改动该部分即可,如从51单片机移植到STM32上。下面涉及到的IO宏,是对应IO的宏定义,如“ONEWIRE1_PORT”、“ONEWIRE1_PIN”,实际使用的是PC13 IO口。


3.1.1 IO输出


static void gpio_set_sdo(int8_t state)

{

    if (state)

  GPIO_SetBits(ONEWIRE1_PORT,ONEWIRE1_PIN); 

    else

  GPIO_ResetBits(ONEWIRE1_PORT,ONEWIRE1_PIN); 

}


3.1.2 IO输入


static uint8_t gpio_get_sdo(void)

{

    return (GPIO_ReadInputDataBit(ONEWIRE1_PORT,ONEWIRE1_PIN));

}


3.1.3 延时函数


static void gpio_delayus(uint32_t us)

{

#if 1  /* 不用系统延时时,开启 */

    volatile int32_t i;

 

    for (; us > 0; us--)

    {

     i = 30;  //mini 17

        while(i--);

    }

#else

  delayus(us);

#endif

}


3.2onewire 总线初始化


3.2.1 onewire 抽象相关


第一步:定义一个“struct ops_onewire_dev”结构体类型变量(全局)——onewire1_dev。


struct ops_onewire_dev onewire1_dev;


 第二步:实例化“onewire1_dev”中的函数指针。


onewire1_dev.get_sdo=gpio_get_sdo; onewire1_dev.set_sdo=gpio_set_sdo; onewire1_dev.delayus=gpio_delayus; 第三步:使用时,通过传入“onewire1_dev”地址(指针)即可。


3.2.2 onewire 基础相关


初始基础部分,与使用的CPU硬件相关,如时钟、IO方向等。


/**

  * @brief  初始化单总线

  * @param  none

  * @retval none

*/

void stm32f1xx_onewire1_init(void)

{

 GPIO_InitTypeDef GPIO_InitStructure;          

 RCC_APB2PeriphClockCmd(ONEWIRE1_RCC,ENABLE);  



 GPIO_InitStructure.GPIO_Pin = ONEWIRE1_PIN;

   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;

   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      

   GPIO_Init(ONEWIRE1_PORT, &GPIO_InitStructure);             

 ONEWIRE1_PORT->BSRR = ONEWIRE1_PIN;      

 

 /* device init */

 onewire1_dev.get_sdo = gpio_get_sdo;

 onewire1_dev.set_sdo = gpio_set_sdo;

 onewire1_dev.delayus = gpio_delayus;

}


4.onewire 使用


经过前面的步骤后,我们已经通过IO口翻转,模拟实现了一根单总线——“onewire1_dev”,以DS18B20为例,调用第一部分中三者接口,实现对DS18B20的操作。


4.1 DS18B20操作


对于DS18B20,不陌生,即是温度传感器,不多赘述,使用的功能主要是作为温度检测,另外还有其内部的唯一序列号会作为同一总线上挂多个DS18B20时的“地址”识别。 亦可把DS18B20的唯一序列号作为模块、产品、通信总线等的唯一标识使用。因此,代码也是主要实现这两个功能。


#include "onewire_hw.h"

#include "ds18b20.h"


static uint8_t ds18b20_start(void)

{    

 char reg;

    ops_onewire_reset(&onewire1_dev);     

      

 reg = 0xcc; /* 跳过ROM */

 ops_onewire_write(&onewire1_dev,®,1);

 reg = 0x44; /* 温度转换指令 */

 ops_onewire_write(&onewire1_dev,®,1);        

 return 0;

}



/**

  * @brief  读取温度

  * @param  none

  * @retval 温度值,浮点型

*/

float ds18b20_readtemp(void)

{

    uint8_t  tl,th,sign;

 uint16_t reg_temp; 

 char reg;

 float temp;

 

 ds18b20_start();

 ops_onewire_reset(&onewire1_dev); 

 reg = 0xcc;

 ops_onewire_write(&onewire1_dev,®,1); /* 跳过ROM */

 reg = 0xbe;

 ops_onewire_write(&onewire1_dev,®,1); /* 读取RAM */

 ops_onewire_read(&onewire1_dev,&tl,1);  /* 低8位数据 */

 ops_onewire_read(&onewire1_dev,&th,1);   /* 高8位数据 */

 if(th > 7)

 {/* - */

  th = ~th;

  tl = ~tl + 1; 

  sign = 0;             

 }

 else 

 {/* + */

  sign = 1;   

 }  

 reg_temp = (th<<8) | tl;

 temp = reg_temp * 0.0625f; 

 if(sign)

 {

  return temp;       

 }

 else 

 {

  return -temp;   

 }

}

 

/**

  * @brief  读唯一序列号

  * @param  rom 返回序列号缓存

  * @retval none

*/

void ds18b20_readrom(char *rom)

{

 uint8_t i;

 char reg;

 

 ops_onewire_reset(&onewire1_dev);

 reg = 0x33;

 ops_onewire_write(&onewire1_dev,®,1);

 for (i = 0;i < 8;i++)

 {

  ops_onewire_read(&onewire1_dev,&rom[i],1);  

 } 

}


 至此,完成单总线的抽象分层使用。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。