D18 -「脉冲×宽度×调变」:建立控制组件

再来就是实际建立透过 select 选择的脚位,并建立相关 Firmata 功能。

建立 PWM 控制组件

规划一下预期 UI 内容。

D18 - window-pwm-output 线框.png

电子助教:「规划个毛,这明明就是类比输入控制组件换个字而已」

鳕鱼:「被发现惹 (´,,•ω•,,)」

建立 window-pwm-output-item.vue 组件,用来控制 PWM 输出。

具体实现功能:

程序的部份为:

  • 透过拉条控制 PWM 输出
  • 删除功能同 window-digital-io-item.vue
  • 根据 pinresolution 计算 PWM 输出最大数值

src\components\window-pwm-output-item.vue <template lang="pug">

.c-row.q-0px.items-center.w-full
  .pin-number
    .text-20px
      | {{ pin.number }}
    q-btn.bg-white(
      @click='handleDelete',
      icon='r_delete',
      dense,
      flat,
      rounded,
      color='grey-5'
    )
  q-slider.mx-20px(
    v-model='pinValue',
    color='light-green-4',
    :min='0',
    :max='valueMax'
  )
  q-knob(
    v-model='pinValue',
    size='60px',
    show-value,
    readonly,
    color='light-green-4',
    track-color='grey-3',
    font-size='12px',
    :min='0',
    :max='valueMax'
  )

src\components\window-pwm-output-item.vue <style scoped lang="sass">

@import '@/styles/quasar.variables.sass'

.pin-number
  width: 36px
  padding: 10px 0px
  margin-right: 10px
  font-family: 'Orbitron'
  color: $grey
  text-align: center
  position: relative
  &:hover
    .q-btn
      pointer-events: auto
      opacity: 1
  .q-btn
    position: absolute
    top: 50%
    left: 50%
    transform: translate(-50%, -50%)
    pointer-events: none
    transition-duration: 0.4s
    opacity: 0

src\components\window-pwm-output-item.vue <script>

/**
 * @typedef {import('@/script/modules/port-transceiver').default} PortTransceiver
 *
 * @typedef {import('@/types/type').PinInfo} PinInfo
 * @typedef {import('@/types/type').PinCapability} PinCapability
 */

import { mapState } from 'vuex';

import { PinMode } from '@/script/utils/firmata.utils';
const { PWM } = PinMode;

export default {
  name: 'WindowPwmOutputItem',
  components: {},
  props: {
    /** @type {PinInfo} */
    pin: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      /** 脚位数值 */
      pinValue: 0,
    };
  },
  computed: {
    ...mapState({
      /** @type {PortTransceiver} */
      portTransceiver: (state) => state.core.transceiver,
    }),

    /** 数值最大值 */
    valueMax() {
      /** @type {PinInfo} */
      const pin = this.pin;

      const target = pin.capabilities.find(
        (capability) => capability.mode === PWM
      );

      return 2 ** target.resolution - 1;
    },
  },
  watch: {},
  created() {
  },
  mounted() {},
  beforeDestroy() {},
  methods: {
    handleDelete() {
      this.$emit('delete', this.pin);
    },
  },
};

接着在 window-pwm-output.vue 引入 window-pwm-output-item.vue

src\components\window-pwm-output.vue <script>

// ...

import BaseWindow from '@/components/base-window.vue';
import BaseSelectPin from '@/components/base-select-pin.vue';
import WindowPwmOutputItem from '@/components/window-pwm-output-item.vue';

// ...

export default {
  name: 'WindowPwmOutput',
  components: {
    'base-window': BaseWindow,
    'base-select-pin': BaseSelectPin,
    'window-pwm-output-item': WindowPwmOutputItem,
  },
  // ...
};

src\components\window-pwm-output.vue <template lang="pug">

base-window.window-pwm-output(
  :pos='pos',
  header-icon-color='light-green-4',
  body-class='c-col p-20px pt-20px',
  title='PWM 输出功能'
)
  base-select-pin(
    :pins='supportPins',
    color='light-green-4',
    @selected='addPin',
    @err='handleErr'
  )

  q-scroll-area.pt-10px.h-300px.flex
    transition-group(name='list-complete', tag='div')
      window-pwm-output-item.py-10px(
        v-for='pin in existPins',
        :pin='pin',
        :key='pin.number',
        @delete='deletePin'
      )

建立脚位时间!

D18 - 建立 PWM 输出控制项.gif

接下来就是实作类比输入功能了!

PWM 输出

在控制脚位数值之前,一样先设定脚位模式。

设定脚位模式

window-pwm-output-item.vue 新增以下程序:

  • methods 新增 init(),初始化脚位相关功能。
  • created() 呼叫 init()

src\components\window-pwm-output-item.vue <script>

/**
 * @typedef {import('@/script/modules/port-transceiver').default} PortTransceiver
 *
 * @typedef {import('@/types/type').PinInfo} PinInfo
 * @typedef {import('@/types/type').PinCapability} PinCapability
 */

import { mapState } from 'vuex';

import { PinMode } from '@/script/utils/firmata.utils';
const { PWM } = PinMode;

export default {
  name: 'WindowPwmOutputItem',
  // ...
	created() {
    this.init();
  },
  // ...
  methods: {
    // ...

		init() {
      /** @type {PinInfo} */
      const pin = this.pin;

      /** @type {PortTransceiver} */
      const portTransceiver= this.portTransceiver;

      portTransceiver.addCmd('setMode', {
        pin: pin.number,
        mode: PWM,
      });
    },
  },
};

设定 PWM 输出数值

到设定 PWM 数值的命令与 Analog 14-bit data format 格式相同:

0  analog pin, 0xE0-0xEF, (MIDI Pitch Wheel)
1  analog least significant 7 bits
2  analog most significant 7 bits

Uno 之 PWM 最大数值为 255,根据协定需将 255 以 7 bit 为一组拆分成 2 byte 传输。

忘记为甚麽的朋友们可以回去复习「D04 - 从零开始的 Firmata 通讯」)

utils.js 建立一个专门拆分数值的功能 numberToSignificantBytes()

src\script\utils\utils.js

// ...

/** 将数值转为 Bytes
 * @param {number} number 数值
 * @param {number} [length] bytes 数量
 * @param {number} [bitsNum] 每 byte 有效位元数
 */
export function numberToSignificantBytes(number, length = 2, bitsNum = 7) {
  const bytes = [];
  const mesh = 2 ** bitsNum - 1;

  let remainingValue = number;

  for (let i = 0; i < length; i++) {
    const byte = remainingValue & mesh;
    bytes.push(byte);

    remainingValue = remainingValue >> bitsNum;
  }

  return bytes;
}

cmd-define.js 新增设定 PWM 数值命令。

src\script\firmata\cmd-define.js

// ...

export default [
  // ...

  // setPwmPinValue: 设定 PWM 数值
  {
    key: 'setPwmValue',
    getValue({ pin, value }) {
      const cmd = 0xE0 + pin;
      const [byte01, byte02] = numberToSignificantBytes(value);
      return [cmd, byte01, byte02];
    },
  },
]

最後实作发送命令部分:

  • 侦测 pinValue 变化并发送数值。
  • methods 新增 writeValue() 发送数值。

src\components\window-pwm-output-item.vue <script>

// ...

export default {
  name: 'WindowPwmOutputItem',
  // ...
  watch: {
    pinValue(value) {
      this.writeValue(value);
    },
  },
  // ...
  methods: {
    // ...

    /** 发送数值 */
    writeValue(value) {
      /** @type {PinInfo} */
      const pin = this.pin;

      /** @type {PortTransceiver} */
      const portTransceiver = this.portTransceiver;

      portTransceiver.addCmd('setPwmValue', {
        pin: pin.number,
        value,
      });
    },
  },
};

D18 - PWM 控制组件输出.gif

怎麽好像有点延迟呢?因为 watch 事件触发太频繁,导致命令发送有点塞车。

聪明的读者们一定想到怎麽解决了吧。没错,就是老朋友 throttle

watch 的部份改成呼叫 throttle

src\components\window-pwm-output-item.vue <script>

// ...
import { throttle } from 'lodash-es';

// ...

export default {
  name: 'WindowPwmOutputItem',
  // ...
  data() {
    return {
      /** 脚位数值 */
      pinValue: 0,

      throttle: {
        writeValue: null,
      },
    };
  },
  // ...
  watch: {
    pinValue(value) {
      this.throttle.writeValue(value);
    },
  },
  created() {
    this.init();

    this.throttle.writeValue = throttle(this.writeValue, 60);
  },
  // ...
};

看看效果如何。

D18 - PWM 控制组件输出加入 throttle.gif

感觉很棒!(≧∀≦)

大家可以加上更多个 LED 看看效果

成功完成所有基础功能,接下来要进入实际应用的部分喽!─=≡Σ((( つ•̀ω•́)つ

总结

  • 了解 Firmata PWM 功能
  • 完成设定 PWM 输出命令
  • 完成「PWM 输出视窗」

以上程序码已同步至 GitLab,大家可以前往下载:

GitLab - D18


<<:  Day 17 : Add Two Numbers

>>:  【从实作学习ASP.NET Core】Day20 | 前台 | 建立前台页面

进击的软件工程师之路-软件战斗营 第十三周

学习进度 资料结构 泛型 通配字元 Android Studio RecyclerView Recy...

Day29 vue.js网页 团队介绍 管理员功能

延续昨日 今天我们来实现 管理者帐号跟团队介绍 其实这两点应该可以同时执行 因为只有管理者可以新增跟...

学习Python纪录Day13 - Web API、JSON

Web API Open data是一种Web API,使用HTTP请求来执行其他系统提供功能来存取...

DAY25 轻量级模型-MobileNets

今天我们不教程序,而是想带大家读一篇关於模型的论文,那为什麽会有这一Part呢?以前小编也不喜欢读P...

成为我们的夥伴,一起航向伟大的航道吧

启航罗~~~ 此篇开始,会介绍AWS上使用完全托管的Kubernetes服务之EKS系列。一般自己托...