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频率进行计算。
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);
extern int ops_onewire_write(struct ops_onewire_dev *onewire,void *buff,int size);
1)分别为复位函数、读函数、写函数。2)入口首参数为“struct ops_onewire_dev”结构体指针,此部分就是硬件层相关,需要后期初始化的. 3)其余入口参数易于理解,读/写缓存及数据大小。
2.3 onewire 抽象接口实现
分别实现上述三者函数接口。
2.3.1 复位函数
复位函数,在单总线初始化外设器件时需要用到,用于判断总线与器件是否通信上,类似“握手”的动作。如图,为DS18B20的复位时序图,以下与单总线相关的时序图,都是以DS18B20为例,因为此芯片为单总线应用的经典。
根据时序图,实现复位函数。
/**
* @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 读函数
读函数即以该函数,通过单总线从外设上读取数据,至于代码的实现,完全是时序图的实现,无特殊难点。先实现单字节读函数,再通过调用单字节读函数实现多字节读函数。
/**
* @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;ireturn i;
}
2.3.3 写函数
写函数与读函数同理,即以该函数,通过单总线往外设写入数据,至于代码的实现,完全是时序图的实现,无特殊难点。先实现单字节写函数,再通过调用单字节写函数实现多字节写函数。
/**
* @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;iif(ops_onewire_write_byte(onewire,p[i]) != 0)
break;
}
return 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.1 onewire 总线初始化
3.1.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.1.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 = (thif(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
至此,完成单总线的抽象分层使用。
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !