iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0
Modern Web

你渴望連結嗎?將 Web 與硬體連上線吧!系列 第 19

D18 -「脈衝×寬度×調變」:建立控制組件

  • 分享至 

  • xImage
  •  

本系列文已改編成書「Arduino 自造趣:結合 JavaScript x Vue x Phaser 輕鬆打造個人遊戲機」,本書改用 Vue3 與 TypeScript 全面重構且加上更詳細的說明,

在此感謝 iT 邦幫忙、博碩文化與編輯小 p 的協助,歡迎大家前往購書,鱈魚在此感謝大家 (。・∀・)。

若想 DIY 卻不知道零件去哪裡買的讀者,可以參考此連結 。( •̀ ω •́ )✧


再來就是實際建立透過 select 選擇的腳位,並建立相關 Firmata 功能。

建立 PWM 控制組件

規劃一下預期 UI 內容。

https://ithelp.ithome.com.tw/upload/images/20240125/20140213e5mkDcshtn.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


上一篇
D17 - 「脈衝×寬度×調變」:Arduino PWM 功能
下一篇
D19 - 「吶,你想要成為什麼顏色?」:打造一個調色盤吧!
系列文
你渴望連結嗎?將 Web 與硬體連上線吧!33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言