本系列文已改編成書「Arduino 自造趣:結合 JavaScript x Vue x Phaser 輕鬆打造個人遊戲機」,本書改用 Vue3 與 TypeScript 全面重構且加上更詳細的說明,
在此感謝 iT 邦幫忙、博碩文化與編輯小 p 的協助,歡迎大家前往購書,鱈魚在此感謝大家 (。・∀・)。
若想 DIY 卻不知道零件去哪裡買的讀者,可以參考此連結 。( •̀ ω •́ )✧
再來就是實際建立透過 select 選擇的腳位,並建立相關 Firmata 功能。
規劃一下預期 UI 內容。
電子助教:「規劃個毛,這明明就是類比輸入控制組件換個字而已」
鱈魚:「被發現惹 (´,,•ω•,,)」
建立 window-pwm-output-item.vue
組件,用來控制 PWM 輸出。
具體實現功能:
旋鈕
使用 Quasar Knob。
拉條
使用 Quasar Slider。
刪除按鈕(腳位編號)
使用 Quasar Button。
程式的部份為:
window-digital-io-item.vue
pin
之 resolution
計算 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'
)
建立腳位時間!
接下來就是實作類比輸入功能了!
在控制腳位數值之前,一樣先設定腳位模式。
在 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 數值的命令與 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,
});
},
},
};
怎麼好像有點延遲呢?因為 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);
},
// ...
};
看看效果如何。
感覺很棒!(≧∀≦)
大家可以加上更多個 LED 看看效果
成功完成所有基礎功能,接下來要進入實際應用的部分嘍!─=≡Σ((( つ•̀ω•́)つ
以上程式碼已同步至 GitLab,大家可以前往下載: