【二十三】数据驱动

在这里插入图片描述

前言

本篇博客主要回顾了vue.js的两大核心之一,数据驱动。

参考博客:https://juejin.cn/post/6989106100582744072;https://juejin.cn/post/6844904067525935118

面试回答

1.数据驱动:vue的核心是数据驱动,vue2中是利用Object.defineproperty进行数据劫持,同时结合发布者订阅者模式来实现数据的双向绑定,首先有一个发布者类,它提供添加订阅者方法以及通知方法,这两个方法中都要求订阅者类需要有更新方法;然后有一个数据劫持类,它主要是将数据转换成getter、setter,并且在getter中触发添加订阅者类方法,在setter中触发通知方法,订阅者类中则提供更新方法。当数据改变时,就会触发数据劫持类中的setter方法,去调用发布者类中的通知方法,从而调用订阅者中的更新方法;当然还有模板编译类中,用来处理元素节点上的指令以及文本节点的差值表达式这些;最后汇总在vue.js中,new一个数据劫持类、模板编译类,并且把响应式的数据绑定到vue.js上,完成数据的双向绑定。

2.vue3和vue2的区别:从原理上来说,vue2 的双向数据绑定是利⽤ES5的Object.definePropert来对数据进⾏劫持再结合发布订阅模式来实现的。而vue3中使⽤了es6的Proxy对数据进行代理,通过reactive函数给每⼀个对象都包⼀层Proxy来监听属性的变化。vue3这种方式能够避免definePropery不能进行全对象监听并且需要对数组进行特异性操作的一个缺陷。从算法上来说,vue3优化了diff算法,主要从三个方向进行优化,第一个优化是节点更新添加标记,对动态数据进行标记,对于静态数据则直接使用缓存,就是不再对所有的dom进行对比。第二个优化是数据循环优化,主要是对vnode节点遍历循环的方法进行优化,比如头部循环优化、尾部循环优化以及最长链循环优化。最后一个是事件优化,对于各类事件用闭包进行缓存,从而不引起重新渲染,来达到加速的目的。从使用上来说,就是一个选项式Api与组合式Api的区别,vue2是选项式Api,将data、methods、watch、computed都分开管理,而vue3是组合式Api,将所有的逻辑放到setup里面。

知识点

vue核心的本质:利用Object.defineProperty(vue2)/Proxy(vue3)进行数据劫持各个属性的getter、setter,同时结合观察者模式,来实现数据双向绑定以及响应式。其中Proxy是JS2015(ES6)的一个新特性。Proxy的代理是针对整个对象的,而不是对象的某个属性,因此不同于Object.defineProperty的必须遍历对象每个属性,Proxy只需要做一层代理就可以监听统计结构下的所有属性变化,当然对于深层结构,递归还是需要进行的。此外,Proxy支持代理数组的变化,不过Proxy兼容性不太好。

1.数据拦截

这个知识点应用于实现MVVM库/框架的数据绑定,即改变数据的同时,可以同步更改页面上对应的数据。

let test = {
	a:1,
	b:'zxp',
	c:{abc:'abc'}
}
1.ES5:defineProperty()

Object.defineProperty(obj, prop, descriptor) :obj,要在其上定义属性的对象。prop,要定义或修改的属性的名称。descriptor,将被定义或修改的属性描述符,也就是方法。

  • ES5出来的方法
  • 三个参数:对象(必填)、属性值(必填)、描述符(可选)
  • defineProterty的描述符属性
数据属性:value,writable,configurable,enumerable
访问器属性:get,set
注:不能同时设置value和writable,这两对属性是互斥的

拦截对象的两种情况

let obj = {name:'',age:'',sex:''  },
    defaultName = ["这是姓名默认值1","这是年龄默认值1","这是性别默认值1"];
  Object.keys(obj).forEach(key => {
    Object.defineProperty(obj, key, {
      get() {
        return defaultName;
      },
      set(value) {
        defaultName = value;
      }
    });
  });

  console.log(obj.name);
  console.log(obj.age);
  console.log(obj.sex);
  obj.name = "这是改变值1";
  console.log(obj.name);
  console.log(obj.age);
  console.log(obj.sex);

  let objOne={},defaultNameOne="这是默认值2";
  Object.defineProperty(obj, 'name', {
      get() {
        return defaultNameOne;
      },
      set(value) {
        defaultNameOne = value;
      }
  });
  console.log(objOne.name);
  objOne.name = "这是改变值2";
  console.log(objOne.name);

拦截数组变化的情况

let a={};
bValue=1;
Object.defineProperty(a,"b",{
    set:function(value){
        bValue=value;
        console.log("setted");
    },
    get:function(){
        return bValue;
    }
});
a.b;//1
a.b=[];//setted
a.b=[1,2,3];//setted
a.b[1]=10;//无输出
a.b.push(4);//无输出
a.b.length=5;//无输出
a.b;//[1,10,3,4,undefined];


//结论:defineProperty无法检测数组索引赋值,改变数组长度的变化; 但是通过数组方法来操作可以检测到

拦截数组变化的情况多级嵌套对象监听

  let info = {};
  function observe(obj) {
    if (!obj || typeof obj !== "object") {
      return;
    }
    for (var i in obj) {
      definePro(obj, i, obj[i]);
    }
  }

  function definePro(obj, key, value) {
    observe(value);
    Object.defineProperty(obj, key, {
      get: function() {
        return value;
      },
      set: function(newval) {
        console.log("检测变化", newval);
        value = newval;
      }
    });
  }
  definePro(info, "friends", { name: "张三" });
  info.friends.name = "李四";

缺点:
1、只能监听属性,而不是监听对象本身,需要对对象的每个属性进行遍历(提取高度嵌套的对象里的属性可以采取递归的方法),对于原本不在对象中的属性难以监听。
2、无法监听数组的变化: 数组的这些方法是无法触发set的:push, pop, shift, unshift,splice, sort, reverse,vue中能监听是因为对这些方法进行了重写。

2.ES6:Proxy()

new Proxy(target,handler)。target,用Proxy包装的目标对象(可以是任何类型的对象)。handler,一个对象,其属性是当执行一个操作时定义代理的行为的函数。

实例:
let handler = {
    get: function(target, key){
        console.log('get:',key)
        return key in target ? target[key] : 'error'
    },
    set: function(target,key,val){
        console.log('set:',val)
        target[key] = val
    }
};

let p = new Proxy(test, handler);

p.a = 5
//set: 5
//5

p.b = 'qwe'
//set: qwe
//'qwe'

console.log(p.a, p.b);   
//get: a
//get: b
//5 'qwe'

p.c.abc = 'chiji'
//get: c
//'chiji'

console.log('d' in p, p.d);    
//get: d
//false 'error'

test 输出为
{
   a:5,
   b:"qwe",
   c:{
       abc:"chiji"
   }
}

涉及到多级对象或者多级数组

  //传递两个参数,一个是object, 一个是proxy的handler
//如果是不是嵌套的object,直接加上proxy返回,如果是嵌套的object,那么进入addSubProxy进行递归。 
function toDeepProxy(object, handler) {
    if (!isPureObject(object)) addSubProxy(object, handler); 
    return new Proxy(object, handler);

//这是一个递归函数,目的是遍历object的所有属性,如果不是pure object,那么就继续遍历object的属性的属性,如果是pure object那么就加上proxy
    function addSubProxy(object, handler) {
        for (let prop in object) {
            if ( typeof object[prop] == 'object') {
                if (!isPureObject(object[prop])) addSubProxy(object[prop], handler);
                object[prop] = new Proxy(object[prop], handler);
            }
        }
        object = new Proxy(object, handler)
    }

//是不是一个pure object,意思就是object里面没有再嵌套object了
    function isPureObject(object) {
        if (typeof object!== 'object') {
            return false;
        } else {
            for (let prop in object) {
                if (typeof object[prop] == 'object') {
                    return false;
                }
            }
        }
        return true;
    }
}
let object = {
    name: {
        first: {
            four: 5,
            second: {
                third: 'ssss'
            }
        }
    },
    class: 5,
    arr: [1, 2, {arr1:10}],
    age: {
        age1: 10
    }
}
//这是一个嵌套了对象和数组的数组
let objectArr = [{name:{first:'ss'}, arr1:[1,2]}, 2, 3, 4, 5, 6]

//这是proxy的handler
let handler = {
    get(target, property) {
        console.log('get:' + property)
        return Reflect.get(target, property);
    },
    set(target, property, value) {
        console.log('set:' + property + '=' + value);
        return Reflect.set(target, property, value);
    }
}
//变成监听对象
object = toDeepProxy(object, handler);
objectArr = toDeepProxy(objectArr, handler);

//进行一系列操作
console.time('pro')
objectArr.length
objectArr[3];
objectArr[2]=10
objectArr[0].name.first = 'ss'
objectArr[0].arr1[0]
object.name.first.second.third = 'yyyyy'
object.class = 6;
object.name.first.four
object.arr[2].arr1
object.age.age1 = 20;
console.timeEnd('pro')

//问题和优点:reflect对象没有构造函数 可以监听数组索引赋值,改变数组长度的变化, 是直接监听对象的变化,不用深层遍历
defineProterty和proxy的对比
  1. defineProterty是es5的标准,proxy是es6的标准
  2. proxy可以监听到数组索引赋值,改变数组长度的变化
  3. proxy是监听对象,不用深层遍历,defineProterty是监听属性
  4. 利用defineProterty实现双向数据绑定(vue2.x采用的核心) ,利用proxy实现双向数据绑定(vue3.x会采用)

2.数据双向绑定

在这里插入图片描述

实现数据双向绑定步骤:

  1. 实现目标类(dep.js),包含添加观察者方法、通知方法。
  2. 实现数据劫持类(observer.js),完成对传入数据的响应式处理,如对数据对象所有属性进行监听,并在get方法里调用目标类的添加观察者方法以及在set中触发通知方法,更新视图。
  3. 实现观察者类(watcher.js),观察者类包含更新视图方法以及初始化时通过vue连接数据劫持类(observer.js)触发get方法、获取新值、将观察者类储存在目标类中,来实现收到每个属性变动的通知及执行相应函数来更新视图。
  4. 实现模板编译类(Compiler.js),通过传入的参数,主要对node节点以及指令进行遍历,若涉及文本变量则创建新的观察者类,如果是元素节点,则遍历处理指令如v-text、v-model,并执行相应操作,指令方法内都需要创建新的观察者类。
  5. 实现Vue.js类,整合上述类,首先获取数据、元素节点,再处理数据,比如将数据与vue实例绑定,以及创建数据劫持类、模板编译类。
1.Dep.js(发布者类)

每个响应式属性都会创建这么一个Dep对象 ,在使用响应式数据的时候,负责收集Watcher对象。当我们对响应式属性在setter中进行更新的时候,会调用Depnotify方法发送更新通知,然后去调用 Watcher中的update实现视图的更新操作,该类主要需做以下三件事:

  1. 提供添加观察者的方法,让其在实例化时后往目标类(Dep.js)里面添加自己。
  2. 观察者类(watcher.js)必须有一个update()方法,因为在数据劫持类(observer.js)会将观察者类添加到subs中。
  3. 待属性变动dep.notify()通知时,能调用自身的update()方法,并触发Compile中绑定的回调。
class Dep {
  constructor() {
    // 存储观察者
    this.subs = []
  }
  // 添加观察者
  addSub(sub) {
    // 判断观察者是否存在 和 是否拥有update方法
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // 通知方法
  notify() {
    // 触发每个观察者的更新方法
    this.subs.forEach((sub) => {
      sub.update()
    })
  }
}
2.Observer.js(数据劫持类)

数据劫持类主要实现以下逻辑:

  1. 使用 observerdata 中的属性转为 响应式 添加到 自身身上
  2. 使用 observer 方法监听 data 的所有属性变化来通过观察者模式,更新视图
  3. 当对象的属性值也是对象时,也要对其值进行劫持-----递归
  4. 当对象赋值与旧值一样,则不需要后续操作------防止重复渲染
  5. 当模板渲染获取对象属性会调用get添加target,对象属性改动通知订阅者更新----数据变化,视图更新

Tip:Vue通过Object.defineProperty来将对象的key转换成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性,一般通过$set(obj,index,value)或者 $delete(obj,index,value)进行处理,常见的有,不支持数组的长度变化(arr.length-1);修改数组中的指定元素,也无法侦测数组的变化(arr[0] =1)

class Observer {
  constructor(data) {
    // 用来遍历 data
    this.walk(data)
  }
  // 遍历 data 转为响应式
  walk(data) {
    // 判断 data是否为空 和 对象
    if (!data || typeof data !== 'object') return
    // 遍历 data
    Object.keys(data).forEach((key) => {
      // 转为响应式
      this.defineReactive(data, key, data[key])
    })
  }
  // 转为响应式
  // 要注意的 和vue.js 写的不同的是
  // vue.js中是将属性给了Vue转为 getter setter
  // 这里是将data中的属性转为getter setter
  defineReactive(obj, key, value) {
    // 如果是对象类型的 也调用walk 变成响应式,不是对象类型的直接在walk会被return
    this.walk(value)
    // 保存一下 this
    const self = this
    // 创建 Dep 对象
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      // 设置可枚举
      enumerable: true,
      // 设置可配置
      configurable: true,

      // 获取值
      get() {
        // 在这里添加观察者对象 Dep.target 表示观察者
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      // 设置值
      set(newValue) {
        // 判断旧值和新值是否相等
        if (newValue === value) return
        // 设置新值
        value = newValue
        // 赋值的话如果是newValue是对象,对象里面的属性也应该设置为响应式的
        self.walk(newValue)
        // 触发通知 更新视图
        dep.notify()
      },
    })
  }
}
3.Watcher.js(订阅者类)

watcher的作用是数据更新后收到通知之后调用update 进行更新,当我们改变响应式属性的时候,就会触发Observer中的set()方法 ,然后调用notify 方法,拿到了所有的观察者watcher 实例去执行 update方法调用了回调函数 cb(newValue) 方法并把新值传递到了cb(),而cb方法的具体逻辑则在Watcher里(可以参考Compiler.js中的new Watcher),去更新视图。

class Watcher {
  constructor(vm, key, cb) {
    // vm 是 Vue 实例
    this.vm = vm
    // key 是 data 中的属性
    this.key = key
    // cb 回调函数 更新视图的具体方法
    this.cb = cb
    // 把观察者的存放在 Dep.target
    Dep.target = this
    // 在执行完下面这一条语句后,会触发observer中 get 方法,watcher通过vue连接到ovserver中
    // observer中的get 把 观察者 通过dep.addSub(Dep.target) 添加到了 dep.subs中
    this.oldValue = vm[key]
    // Dep.target 就不用存在了 因为上面的操作已经存好了
    Dep.target = null
  }
  // 观察者中的必备方法 用来更新视图
  update() {
    // 获取新值
    let newValue = this.vm[this.key]
    // 比较旧值和新值
    if (newValue === this.oldValue) return
    // 调用具体的更新方法
    this.cb(newValue)
  }
}
4.Compiler.js(模板编译类)

使用compiler编译元素节点上面指令和文本节点差值表达式

Comilper.js在这个文件里实现对文本节点和元素节点指令编译,比如v-text、v-model,他做了以下三件事:

  • 将当前根节点所有子节点遍历放到内存中
  • 编译文档碎片,替换模板(元素、文本)节点中属性的数据
  • 将编译的内容回写到真实DOM上

【重点】:

  1. 先把真实的 dom 移入到内存中操作 — 文档碎片
  2. 编译元素节点和文本节点
  3. 给模板中的表达式和属性添加观察者(Watcher.js)
class Compiler {
  // vm 指 Vue 实例
  constructor(vm) {
    // 拿到 vm
    this.vm = vm
    // 拿到 el
    this.el = vm.$el
    // 编译模板
    this.compile(this.el)
  }
  // 编译模板
  compile(el) {
    // 获取子节点 如果使用 forEach遍历就把伪数组转为真的数组
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) => {
      // 根据不同的节点类型进行编译
      // 文本类型的节点
      if (this.isTextNode(node)) {
        // 编译文本节点
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        //元素节点
        this.compileElement(node)
      }
      // 判断是否还存在子节点考虑递归
      if (node.childNodes && node.childNodes.length) {
        // 继续递归编译模板
        this.compile(node)
      }
    })
  }
  // 判断是否是 文本 节点
  isTextNode(node) {
    return node.nodeType === 3
  }
  // 编译文本节点(简单的实现)
  compileText(node) {
    // 核心思想利用把正则表达式把{{}}去掉找到里面的变量
    // 再去Vue找这个变量赋值给node.textContent
    let reg = /\{\{(.+?)\}\}/
    // 获取节点的文本内容
    let val = node.textContent
    // 判断是否有 {{}}
    if (reg.test(val)) {
      // 获取分组一  也就是 {{}} 里面的内容 去除前后空格
      let key = RegExp.$1.trim()
      // 进行替换再赋值给node
      node.textContent = val.replace(reg, this.vm[key])
      // 创建观察者
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }
  // 判断是否是元素节点
  isElementNode(node) {
    return node.nodeType === 1
  }
  // 编译元素节点这里只处理指令
  compileElement(node) {
    // 获取到元素节点上面的所有属性进行遍历
    // 问题:这里为什么要加一个!
    ![...node.attributes].forEach((attr) => {
      // 获取属性名
      let attrName = attr.name
      // 判断是否是 v- 开头的指令
      if (this.isDirective(attrName)) {
        // 除去 v- 方便操作
        attrName = attrName.substr(2)
        // 获取 指令的值就是  v-text = "msg"  中msg
        // msg 作为 key 去Vue 找这个变量
        let key = attr.value
        // 指令操作 执行指令方法
        // vue指令很多为了避免大量个 if判断这里就写个 uapdate 方法
        //问题:这一步是干嘛的
        this.update(node, key, attrName)
      }
    })
  }
  // 判断元素的属性是否是 vue 指令
  isDirective(attr) {
    return attr.startsWith('v-')
  }
  // 添加指令方法 并且执行
  update(node, key, attrName) {
    // 比如添加 textUpdater 就是用来处理 v-text 方法
    // 我们应该就内置一个 textUpdater 方法进行调用
    // 加个后缀加什么无所谓但是要定义相应的方法
    let updateFn = this[attrName + 'Updater']
    // 如果存在这个内置方法 就可以调用了
    updateFn && updateFn.call(this, node, key, this.vm[key])
  }

  // 实例
  // 提前写好 相应的指定方法比如这个 v-text
  // 使用的时候 和 Vue 的一样
  textUpdater(node, key, value) {
    node.textContent = value
    // 创建观察者
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue
    })
  }
  // v-model
  modelUpdater(node, key, value) {
    node.value = value
    // 创建观察者
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
    // 这里实现双向绑定
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }
}
5.Vue.js

vue.js以及observer.js都对数据进行劫持的原因是,obsever.js中是把data的所有属性加到data自身,变为响应式转成gettersetter方式;在vue.js 中 也把data的所有属性加到Vue上,是为了以后方面操作可以用Vue的实例直接访问到或者在 Vue 中使用 this 访问,这样在Vue$data 中都存在了 所有的data属性了并且是响应式的。

class Vue {
  constructor(options) {
    // 获取到传入的对象 没有默认为空对象
    this.$options = options || {};
    this.$el = typeof options.el === 'string' ?
      document.querySelector(options.el) :
      options.el;
    //获取data
    this.$data = options.data || {};
    // 调用 _proxyData 处理 data中的属性,这里的目的是让用户可以直接通过vue.js去拿到值
    this._proxyData(this.$data); //走到这里直接去看_proxyData方法
    // 使用 Obsever 把data中的数据转为响应式,走到这里直接去处理Observer类
    new Observer(this.$data);
    // 编译模板
    new Compiler(this)
  }
  _proxyData(data) {
    Object.keys(data).forEach(key => {
      // 进行数据劫持
      // 把每个data的属性 到添加到 Vue 转化为 getter setter方法
      Object.defineProperty(this, key, {
        // 设置可以枚举
        enumerable: true,
        // 设置可以配置
        configurable: true,
        // 获取数据
        get() {
          return data[key];
        },
        // 设置数据
        set(newValue) {
          // 判断新值和旧值是否相等
          if (newValue === data[key]) return;
          // 设置新值
          data[key] = newValue;
        },
      });
    });
  }
}
6.HTML
<body>
  <div id="app">
    {{msg}} <br />
    {{age}} <br />
    <div v-text="msg"></div>
    <input v-model="msg" type="text" />
  </div>
  <script src="./js/dep.js"></script>
  <script src="./js/watcher.js"></script>
  <script src="./js/compiler.js"></script>
  <script src="./js/observer.js"></script>
  <script src="./js/vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: '123',
        age: 21,
      },
    })
  </script>
</body>

最后

走过路过,不要错过,点赞、收藏、评论三连~

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【适合小白入门】深入掌握Windows操作系统原理,提升程序开发水平,为学习驱动开发,内核安全打下基础。学完本课程可以轻松的理解Windows内核,开阔思路,对没有底层开发基础的人起到有非常好的指导作用。在此基础上可以开发出有趣且功能强大的软件。 课程目录: 第1章windows驱动基础 第一课 认识windows驱动 第二课 在虚拟机里安装windows操作系统 第三课 windows操作系统基本概念 第四课 操作系统的分层结构 第2章windowsw驱动编译环境配置、安装及调试 第五课 安装驱动开发环境 第六课 安装驱动开发环境 第七课 实战:编写驱动程序加载器 第3章驱动程序的基本结构 第八课 复习c语言的指针和数据结构 第九课 windows驱动程序的基本结构 第十课 编程实战-创建设备对象 第4章windows内存管理 第十一课 内存管理操作 第十二课 驱动开发中使用链表 第十三课 驱动开发中使用快查表 第十四课 在驱动中使用c++中内存管理操作-newdelet 第十六课 驱动开发中宏与断言的使用 第5章应用程序与驱动程序通信 第二十六课 irp与派遣函数 第二十七课 缓冲区读写操作 第十五课 在驱动中使用结构化异常处理 第二十八课 缓冲区读写操作 第二十九课 模拟文件 第三十课 直接方式与其它方式读写操作 第三十一课 io设备控制操作 第三十二课 io设备控制操作 第6章windows内核函数 第十七课 内核模式下的字符串操作1 第十八课 内核模式下的字符串操作 第十九课 内核模式下的字符串操作 第二十课 内核模式下的文件操作 第二十一课 内核模式下的文件操作 第二十二课 内核模式下注册表操作 第二十三课 内核模式下注册表操作 第二十四课 内核模式下注册表操作 第二十五课 内核模式下注册表操作 第7章驱动程序的同步处理 第三十三课 内核模式下的同步与异步操作 第三十四课 用户模式下的同步对象1_事件 第三十五课 用户模式下的同步对象2_线程信号量与互斥体 第三十六课 内核模式下的同步对象3_系统线程创建与普 第三十七课 内核模式下的同步对象4_信号量与互斥体 第三十八课 内核模式与用户模式间的同步操作 第三十九课 其它内核同步要素 第8章IRP的同步与异步 第四十课 应用程序的对文件同步与异步操作 第四十一课 irp异步完成 第四十二课 irp的取消与startio函数 第四十三课 自定义startio函数 第9章定时器54分钟3节 第四十四课 io定时器与dpc定时器 第四十五课 内核模式下的等待操作 第四十六课 时间函数与irp超时处理 第10章驱动程序调用驱动程序1小时3节 第四十七课 通过设备句柄调用驱动程序 第四十八课 设备指针调用驱动程序 第四十九课 自定义irp与ObReferenceObject 第11章分层过滤驱动 第五十课 分层驱动:枚举设备栈上的设备对象 第五十一课 编写过滤驱动程序 第五十二课 irp完成函数 第12章驱动程序开发高级技能 第五十三课 驱动程序的兼容性 第五十五课 驱动调试之windbg与vmware 第五十六课 驱动调试vs vmware 第五十四课 驱动签名证书原理及制作 第五十七课 驱动调试神器virtualkd 第五十八课 汇编语言编写驱动之环境搭建 第五十九课 用汇编语言开发32与64位驱动程序
LINUX设备驱动第三版_ 前言 第一章 设备驱动程序简介 设备驱动程序的作用 内核功能划分 设备和模块的分类 安全问题 版本编号 许可证条款 加入内核开发社团 本书概要 第二章 构造和运行模块 设置测试系统 Hello World模块 核心模块与应用程序的对比 编译和装载 内核符号表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 第三章 字符设备驱动程序 scull的设计 主设备号和次设备号 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 第四章 调试技术 内核中的调试支持 通过打印调试 通过查询调试 通过监视调试 调试系统故障 调试器和相关工具 第五章 并发和竞态 scull的缺陷 并发及其管理 信号量和互斥体 completion 自旋锁 锁陷阱 除了锁之外的办法 快速参考 第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet 工作队列 快速参考 第八章 分配内存 kmalloc函数的内幕 后备高速缓存 get_free_page和相关函数 vmalloc及其辅助函数 per-CPU变量 获取大的缓冲区 快速参考 第九章 与硬件通信 I/O端口和I/O内存 使用I/O端口 I/O端口示例 使用I/O内存 快速参考 第十章 中断处理 准备并口 安装中断处理例程 实现中断处理例程 顶半部和底半部 中断共享 中断驱动的I/O 快速参考 第十一章 内核的数据类型 使用标准C语言类型 为数据项分配确定的空间大小 接口特定的类型 其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 SBus NuBus 外部总线 快速参考 第十三章 USB驱动程序 USB设备基础 USB和Sysfs USB urb 编写USB驱动程序 不使用urb的USB传输 快速参考 第十四章 Linux设备模型 kobject、kset和子系统 低层sysfs操作 热插拔事件的产生 总线、设备和驱动程序 类 各环节的整合 热插拔 处理固件 快速索引 第十五章 内存映射和DMA Linux的内存管理 mmap设备操作 执行直接I/O访问 直接内存访问 快速参考 第十六章 块设备驱动程序 注册 块设备操作 请求处理 其他一些细节 快速参考 第十七章 网络驱动程序 snull设计 连接到内核 net_device结构细节 打开和关闭 数据包传输 数据包的接收 中断处理例程 不使用接收中断 链路状态的改变 套接字缓冲区 MAC 地址解析 定制 ioctl 命令 统计信息 组播 其他知识点详解 快速参考 第十八章 TTY驱动程序 小型TTY驱动程序 tty_driver函数指针 TTY线路设置 ioctls proc和sysfs对TTY设备的处理 tty_driver结构详解 tty_operations结构详解 tty_struct结构详解 快速参考 参考书目 9112405-1_o.jpg (85.53 KB, 下载次数: 50)
下面是使用HAL库驱动3个ADS1115芯片进行数据采集、平均值计算并通过串口输出的示例代码: ```c #include "stm32f1xx_hal.h" #include "string.h" #include "stdio.h" #define ADS1115_I2C_ADDRESS_1 0x90 // 第一个ADS1115的地址 #define ADS1115_I2C_ADDRESS_2 0x92 // 第二个ADS1115的地址 #define ADS1115_I2C_ADDRESS_3 0x94 // 第三个ADS1115的地址 #define I2C_TIMEOUT 1000 // I2C传输超时时间 I2C_HandleTypeDef hi2c1; UART_HandleTypeDef huart1; void SystemClock_Config(void); void MX_GPIO_Init(void); void MX_I2C1_Init(void); void MX_USART1_UART_Init(void); void ADS1115_Read(uint8_t i2c_address, uint8_t reg_address, uint16_t *data); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); uint16_t data_1[4], data_2[4], data_3[4]; // 用于存储读取的数据 uint32_t sum_1 = 0, sum_2 = 0, sum_3 = 0; // 用于计算平均值 uint16_t avg_1, avg_2, avg_3; // 用于存储平均值 char buffer[50]; // 用于存储输出字符串 while (1) { // 读取ADS1115芯片1的4个数据 ADS1115_Read(ADS1115_I2C_ADDRESS_1, 0x00, &data_1[0]); ADS1115_Read(ADS1115_I2C_ADDRESS_1, 0x01, &data_1[1]); ADS1115_Read(ADS1115_I2C_ADDRESS_1, 0x02, &data_1[2]); ADS1115_Read(ADS1115_I2C_ADDRESS_1, 0x03, &data_1[3]); // 读取ADS1115芯片2的4个数据 ADS1115_Read(ADS1115_I2C_ADDRESS_2, 0x00, &data_2[0]); ADS1115_Read(ADS1115_I2C_ADDRESS_2, 0x01, &data_2[1]); ADS1115_Read(ADS1115_I2C_ADDRESS_2, 0x02, &data_2[2]); ADS1115_Read(ADS1115_I2C_ADDRESS_2, 0x03, &data_2[3]); // 读取ADS1115芯片3的4个数据 ADS1115_Read(ADS1115_I2C_ADDRESS_3, 0x00, &data_3[0]); ADS1115_Read(ADS1115_I2C_ADDRESS_3, 0x01, &data_3[1]); ADS1115_Read(ADS1115_I2C_ADDRESS_3, 0x02, &data_3[2]); ADS1115_Read(ADS1115_I2C_ADDRESS_3, 0x03, &data_3[3]); // 计算平均值 for(int i = 0; i < 4; i++) { sum_1 += data_1[i]; sum_2 += data_2[i]; sum_3 += data_3[i]; } avg_1 = sum_1 / 40; avg_2 = sum_2 / 40; avg_3 = sum_3 / 40; // 通过串口输出平均值 memset(buffer, 0, sizeof(buffer)); sprintf(buffer, "ADS1115_1: %d, ADS1115_2: %d, ADS1115_3: %d\r\n", avg_1, avg_2, avg_3); HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), I2C_TIMEOUT); // 清空计算平均值的变量 sum_1 = 0; sum_2 = 0; sum_3 = 0; HAL_Delay(1000); // 延时1秒钟 } } /* 读取ADS1115芯片的数据 */ void ADS1115_Read(uint8_t i2c_address, uint8_t reg_address, uint16_t *data) { uint8_t tx_data[1] = {reg_address}; HAL_I2C_Master_Transmit(&hi2c1, i2c_address, tx_data, 1, I2C_TIMEOUT); // 发送读取地址 HAL_I2C_Master_Receive(&hi2c1, i2c_address, (uint8_t*)data, 2, I2C_TIMEOUT); // 读取数据 *data = (*data >> 8) | (*data << 8); // 交换高低字节 } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000); HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); /* SysTick_IRQn interrupt configuration */ HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); } static void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Configure Analogue filter */ if (HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Configure Digital filter */ if (HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } } void _Error_Handler(char *file, int line) { while(1) { // 错误处理 } } ``` 说明: 1. 在此示例代码中,使用的是3个ADS1115芯片,它们的I2C地址分别为0x90、0x92和0x94。 2. 使用`ADS1115_Read`函数读取ADS1115芯片的数据,并将读取到的数据存储在`data`数组中。 3. 在主循环中,分别读取3个ADS1115芯片的4个数据,并计算出平均值。然后将平均值通过串口输出。 4. 由于ADS1115芯片的数据是16位的,因此在读取数据后需要将高低字节进行交换。 5. 本示例代码使用的是USART1串口,波特率为115200。如果需要更改串口配置,请修改`MX_USART1_UART_Init`函数。 6. 如果需要使用其他GPIO口,请修改`MX_GPIO_Init`函数。 7. 如果需要修改I2C配置,请修改`MX_I2C1_Init`函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值