【代码解析】嵌入式单总线分层架构设计

李肖遥 2025-04-07 22:12
    关注、星标公众号,直达精彩内容
来源:网络素材

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”,其原型如下:

structops_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 对外接口

externuint8_tops_onewire_reset(struct ops_onewire_dev *onewire);
externintops_onewire_read(struct ops_onewire_dev *onewire,void *buff,int size);
externintops_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_tops_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 返回读取的数据
*/

staticcharops_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 返回读取到的数据大小
*/

intops_onewire_read(struct ops_onewire_dev *onewire,void *buff,int size)
{
int i;
char *p = (char*)buff; 
for(i=0;i
  p[i++]=ops_onewire_read_byte(onewire);
return i;
}

2.3.3 写函数

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

图片
/**
  * @brief  单总线写一字节
  * @param  onewire 总线结构体指针
  * @param  data 待写数据
  * @retval 返回读取的数据
*/

staticintops_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;
 }
return0;
}

/**
  * @brief  写多字节
  * @param  onewire 总线结构体指针
  * @param  buff 代写数据地址
  * @param  size 数据大小
  * @retval 写入数据大小
*/

intops_onewire_write(struct ops_onewire_dev *onewire,void *buff,int size)
{
int i;
char *p = (char*)buff;
for(i=0;i
 {
if(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输出

staticvoidgpio_set_sdo(int8_t state)
{
    if (state)
  GPIO_SetBits(ONEWIRE1_PORT,ONEWIRE1_PIN); 
    else
  GPIO_ResetBits(ONEWIRE1_PORT,ONEWIRE1_PIN); 
}

3.1.2 IO输入

staticuint8_tgpio_get_sdo(void)
{
    return (GPIO_ReadInputDataBit(ONEWIRE1_PORT,ONEWIRE1_PIN));
}

3.1.3 延时函数

staticvoidgpio_delayus(uint32_t us)
{
#if 1  /* 不用系统延时时,开启 */
    volatileint32_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。

structops_onewire_devonewire1_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
*/

voidstm32f1xx_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"

staticuint8_tds18b20_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);        
return0;
}

/**
  * @brief  读取温度
  * @param  none
  * @retval 温度值,浮点型
*/

floatds18b20_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
*/

voidds18b20_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);  
 } 
}

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

5.源码

https://github.com/Prry/drivers-for-mcu


版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。


‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

关注我的微信公众号,回复“星球”加入知识星球,有问必答。



点击“阅读原文”查看知识星球详情,欢迎点分享、收藏、点赞、在看。

李肖遥 公众号“技术让梦想更伟大”,作者:李肖遥,专注嵌入式,只推荐适合你的博文,干货,技术心得,与君共勉。
评论 (0)
  •   天空卫星健康状况监测维护管理系统:全方位解析  在航天技术迅猛发展的当下,卫星在轨运行的安全与可靠至关重要。整合多种技术,实现对卫星的实时监测、故障诊断、健康评估以及维护决策,有力保障卫星长期稳定运转。  应用案例       系统软件供应可以来这里,这个首肌开始是幺伍扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照数字顺序组合就可以找到。  一、系统架构与功能模块  数据采集层  数据处理层  智能分析层  决策支持层  二、关键技术  故障诊断技术  
    华盛恒辉l58ll334744 2025-04-10 15:46 113浏览
  • 文/Leon编辑/侯煜‍关税大战一触即发,当地时间4月9日起,美国开始对中国进口商品征收总计104%的关税。对此,中国外交部回应道:中方绝不接受美方极限施压霸道霸凌,将继续采取坚决有力措施,维护自身正当权益。同时,中国对原产于美国的进口商品加征关税税率,由34%提高至84%。随后,美国总统特朗普在社交媒体宣布,对中国关税立刻提高至125%,并暂缓其他75个国家对等关税90天,在此期间适用于10%的税率。特朗普政府挑起关税大战的目的,实际上是寻求制造业回流至美国。据悉,特朗普政府此次宣布对全球18
    华尔街科技眼 2025-04-10 16:39 144浏览
  • 什么是车用高效能运算(Automotive HPC)?高温条件为何是潜在威胁?作为电动车内的关键核心组件,由于Automotive HPC(CPU)具备高频高效能运算电子组件、高速传输接口以及复杂运算处理、资源分配等诸多特性,再加上各种车辆的复杂应用情境等等条件,不难发见Automotive HPC对整个平台讯号传输实时处理、系统稳定度、耐久度、兼容性与安全性将造成多大的考验。而在各种汽车使用者情境之中,「高温条件」就是你我在日常生活中必然会面临到的一种潜在威胁。不论是长时间将车辆停放在室外的高
    百佳泰测试实验室 2025-04-10 15:09 108浏览
  • 行业痛点:电动车智能化催生语音交互刚需随着全球短途出行市场爆发式增长,中国电动自行车保有量已突破3.5亿辆。新国标实施推动行业向智能化、安全化转型,传统蜂鸣器报警方式因音效单一、缺乏场景适配性等问题,难以满足用户对智能交互体验的需求。WT2003HX系列语音芯片,以高性能处理器架构与灵活开发平台,为两轮电动车提供从基础报警到智能交互的全栈语音解决方案。WT2003HX芯片技术优势深度解读1. 高品质硬件性能,重塑语音交互标准搭载32位RISC处理器,主频高达120MHz,确保复杂算法流畅运行支持
    广州唯创电子 2025-04-10 09:12 201浏览
  • 行业变局:从机械仪表到智能交互终端的跃迁全球两轮电动车市场正经历从“功能机”向“智能机”的转型浪潮。数据显示,2024年智能电动车仪表盘渗透率已突破42%,而传统LED仪表因交互单一、扩展性差等问题,难以满足以下核心需求:适老化需求:35%中老年用户反映仪表信息辨识困难智能化缺口:78%用户期待仪表盘支持手机互联与语音交互成本敏感度:厂商需在15元以内BOM成本实现功能升级在此背景下,集成语音播报与蓝牙互联的WT2605C-32N芯片方案,以“极简设计+智能交互”重构仪表盘技术生态链。技术破局:
    广州唯创电子 2025-04-11 08:59 168浏览
  • 背景近年来,随着国家对资源、能源有效利用率的要求越来越高,对环境保护和水处理的要求也越来越严格,因此有大量的固液分离问题需要解决。真空过滤器是是由负压形成真空过滤的固液分离机械。用过滤介质把容器分为上、下两层,利用负压,悬浮液加入上腔,在压力作用下通过过滤介质进入下腔成为滤液,悬浮液中的固体颗粒吸附在过滤介质表面形成滤饼,滤液穿过过滤介质经中心轴内部排出,达到固液分离的目的。目前市面上的过滤器多分为间歇操作和连续操作两种。间歇操作的真空过滤机可过滤各种浓度的悬浮液,连续操作的真空过滤机适于过滤含
    宏集科技 2025-04-10 13:45 116浏览
  • 政策驱动,AVAS成新能源车安全刚需随着全球碳中和目标的推进,新能源汽车产业迎来爆发式增长。据统计,2023年中国新能源汽车渗透率已突破35%,而欧盟法规明确要求2024年后新能效车型必须配备低速提示音系统(AVAS)。在此背景下,低速报警器作为车辆主动安全的核心组件,其技术性能直接关乎行人安全与法规合规性。基于WT2003H芯片开发的AVAS解决方案,以高可靠性、强定制化能力及智能场景适配特性,正成为行业技术升级的新标杆。WT2003H方案技术亮点解析全场景音效精准触发方案通过多传感器融合技术
    广州唯创电子 2025-04-10 08:53 227浏览
  •   海上电磁干扰训练系统:全方位解析      海上电磁干扰训练系统,作为模拟复杂海上电磁环境、锻炼人员应对电磁干扰能力的关键技术装备,在军事、科研以及民用等诸多领域广泛应用。接下来从系统构成、功能特点、技术原理及应用场景等方面展开详细解析。   应用案例   系统软件供应可以来这里,这个首肌开始是幺伍扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照数字顺序组合就可以找到。   一、系统构成   核心组件   电磁信号模拟设备:负责生成各类复杂的电磁信号,模拟海上多样
    华盛恒辉l58ll334744 2025-04-10 16:45 183浏览
  • 由西门子(Siemens)生产的SIMATIC S7 PLC在SCADA 领域发挥着至关重要的作用。在众多行业中,SCADA 应用都需要与这些 PLC 进行通信。那么,有哪些高效可行的解决方案呢?宏集为您提供多种选择。传统方案:通过OPC服务器与西门子 PLC 间接通信SIMATIC S7系列的PLC是工业可编程控制器,能够实现对生产流程的实时SCADA监控,提供关于设备和流程状态的准确、最新数据。S7Comm(全称S7 Communication),也被称为工业以太网或Profinet,是西门
    宏集科技 2025-04-10 13:44 129浏览
  • 技术原理:非扫描式全局像的革新Flash激光雷达是一种纯固态激光雷达技术,其核心原理是通过面阵激光瞬时覆盖探测区域,配合高灵敏度传感器实现全局三维成像。其工作流程可分解为以下关键环节:1. 激光发射:采用二维点阵光源(如VCSEL垂直腔面发射激光器),通过光扩散器在单次脉冲中发射覆盖整个视场的面阵激光,视场角通常可达120°×75°,部分激光雷达产品可以做到120°×90°的超大视场角。不同于传统机械扫描或MEMS微振镜方案,Flash方案无需任何移动部件,直接通过电信号控制激光发射模式。2.
    robolab 2025-04-10 15:30 136浏览
  •     前几天同事问我,电压到多少伏就不安全了?考虑到这位同事的非电专业背景,我做了最极端的答复——多少伏都不安全,非专业人员别摸带电的东西。    那么,是不是这么绝对呢?我查了一下标准,奇怪的知识增加了。    标准的名字值得玩味——《电流对人和家畜的效应》,GB/T 13870.5 (IEC 60749-5)。里面对人、牛、尸体分类讨论(搞硬件的牛马一时恍惚,不知道自己算哪种)。    触电是电流造成的生理效应
    电子知识打边炉 2025-04-09 22:35 235浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦