新西达电调初始化代码,使用nodejs ffi技术调用wiringpi,代码使用typescript编写
这是我设计的F450四轴飞行器飞控代码的一部分
运行在orangepi-zero上,操作系统是armbian,思路是使用node-ffi调用wiringpi的so库与GPIO通信,然后控制端逻辑代码使用typescript编写
需要注意的是node-ffi目前不支持node11版本以及以上,我使用的node版本是10
ffi对so库发起调用的效率并不高,但是在这里依然是可以满足通信的需求了
这里使用typescript封装了一个电机类,注释比较多,可以方便参考
下面是电机类
1 import { GPIO } from '../gpio'; 2 import WPIFFI from '../wiringpi-ffi'; 3 4 /** 5 * 电机类 6 * 封装了PWM,电调的初始化方法以及电机档位的控制方法 7 */ 8 export class Motor { 9 /** 10 * 控制电机的GPIO口 11 */ 12 private gpio: GPIO; 13 /** 14 * 是否已经初始化PWM 15 */ 16 private pwmInitialized: boolean; 17 /** 18 * 是否已经初始化电调 19 */ 20 private controllerInitialized: boolean; 21 /** 22 * 当前电机转速档位 23 */ 24 private gear: number; 25 26 /** 27 * 获取控制电机的GPIO口 28 */ 29 public get GPIO(): GPIO { 30 return this.gpio; 31 } 32 /** 33 * 获取PWM的初始化状态 34 */ 35 public get PWMInitialized() { 36 return this.pwmInitialized; 37 } 38 /** 39 * 获取电调的初始化状态 40 */ 41 public get ControllerInitialized() { 42 return this.controllerInitialized; 43 } 44 /** 45 * 获取电机的初始化状态(PWM且电调) 46 */ 47 public get Initialized() { 48 return this.PWMInitialized && this.ControllerInitialized; 49 } 50 /** 51 * 获取电机的当前档位 52 */ 53 public get Gear(): number { 54 return this.gear; 55 } 56 57 /** 58 * 调用FFI初始化PWM 59 * @param value 初始化的脉冲宽度值 60 * @param range 脉冲可调范围 61 */ 62 private pwmInit(value: number = 0, range: number = 200) { 63 console.log(`[${this.gpio}] init: ${value} ${range}`); 64 if (WPIFFI.softPwmCreate(this.gpio, value, range)) { 65 throw(new Error(`[${this.gpio}] pwm initialization failed`)); 66 } 67 } 68 /** 69 * 调用FFI设置PWM脉冲 70 * @param value 脉冲值,默认配置下,此值的范围为[0 ~ 200] 71 */ 72 private pulseSet(value: number) { 73 if (!this.PWMInitialized) { 74 console.log(`[${this.gpio}] pwm not initialized`); 75 return; 76 } 77 // 设置脉冲 78 console.log(`[${this.gpio}] set: ${value}`); 79 WPIFFI.softPwmWrite(this.gpio, value); 80 } 81 82 /** 83 * 初始化PWM,即使能GPIO口的PWM时钟 84 * 这里设置时钟为200个单位,即:1s / (200 * 100us) = 50hz 85 */ 86 public PWMInit(): void { 87 if (!this.PWMInitialized) { 88 this.pwmInitialized = true; 89 this.pwmInit(0, 200); 90 } else { 91 console.log(`[${this.gpio}] pwm already initialized`); 92 } 93 } 94 /** 95 * @async 96 * 初始化控制电机的电调 97 * 因为电调初始化协议有时序性,所以这里使用setTimeout异步延时发送初始化信号 98 * 整个初始化过程会异步等待约10秒钟 99 * 如不能确定电机状态,请谨慎调用,二次初始化会导致油门开到最大 100 * 新西达电调的初始化协议,这里简单描述一下 101 * 1.初始化PWM时钟,使其能在GPIO口产生PWM信号(本程序的PWM频率为:1s / (200 * 100us) = 50hz,11个档位) 102 * 2.输出2ms的PWM脉冲,为设定的油门最大值 103 * 3.听到短促的滴滴声音后,输出1ms的PWM脉冲,设定的油门最小值 104 * 4.等待几秒钟之后,发送1ms~2ms之间的PWM脉冲,即可启动电机 105 */ 106 public ControllerInit(): Promise<void> { 107 return new Promise<void>((resolve, reject) => { 108 // 如果PWM没有初始化则报错 109 if (!this.PWMInitialized) { 110 reject(`[${this.gpio}] pwm not initialized`); 111 } 112 // 如果电调并未初始化 113 if (!this.ControllerInitialized) { 114 // 这里先设置了标志,防止异步重入的错误 115 this.controllerInitialized = true; 116 // 发送高脉冲 117 this.pulseSet(20); 118 // 延时发送低脉冲 119 setTimeout(() => { 120 this.pulseSet(10); 121 // 等待初始化完成返回 122 setTimeout(() => { 123 resolve(); 124 }, 7000); 125 }, 3000); 126 } else { 127 console.log(`[${this.gpio}] controller already initialized`); 128 resolve(); 129 } 130 }); 131 } 132 /** 133 * @async 134 * 初始化电机 135 * 首先会初始化控制电机的GPIO口以使能PWM信号 136 * 其次会初始化控制电机的电调并异步等待完成 137 */ 138 public async Init(): Promise<void> { 139 this.PWMInit(); 140 await this.ControllerInit(); 141 } 142 /** 143 * 设置电机档位 144 * @param gear 电机档位,可调范围为[0 ~ 10] 145 */ 146 public GearSet(gear: number): void { 147 if (!this.PWMInitialized) { 148 console.log(`[${this.gpio}] pwm not initialized`); 149 return; 150 } 151 if (!this.ControllerInitialized) { 152 console.log(`[${this.gpio}] controller not initialized`); 153 return; 154 } 155 if (gear < 0 || gear > 10) { 156 console.log(`[${this.gpio}] the range of gear must be [0 ~ 10]`); 157 return; 158 } 159 const floorGear = Math.floor(gear); 160 // 实际脉冲范围为[10 ~ 20] 161 const value = floorGear + 10; 162 // 设置脉冲信号 163 this.pulseSet(value); 164 // 写入当前档位 165 this.gear = floorGear; 166 } 167 /** 168 * 设置电机档位并持续一段时间后退回之前的档位 169 * @param gear 电机档位,可调范围为[0 ~ 10] 170 * @param s 档位保持的时间,单位秒,超出此时间之后档位将会退回到之前的状态 171 * @param keep 是否回退 172 */ 173 public GearSetTimeout( 174 gear: number, 175 s: number, 176 keep: boolean = false, 177 ): Promise<void> { 178 return new Promise<void>((resolve) => { 179 const ms = Math.floor(s * 1000); 180 const bakGear = this.gear; 181 this.GearSet(gear); 182 setTimeout(() => { 183 if (!keep) { 184 this.GearSet(bakGear); 185 } 186 resolve(); 187 }, ms); 188 }); 189 } 190 /** 191 * 在控制台输出设备的详情信息 192 */ 193 public Detail(): void { 194 console.log(`GPIO: ${this.GPIO}`); 195 console.log(`PWMInitialized[true/false]: ${this.PWMInitialized}`); 196 console.log(`ControllerInitialized[true/false]: ${this.ControllerInitialized}`); 197 console.log(`Gear[0 ~ 10]: ${this.Gear}`); 198 } 199 /** 200 * @constructor 构造函数,创建一个可用的电机对象 201 * @param gpio 控制电机的GPIO口,具体请查看实际硬件连接与OrangePi Zero的GPIO定义 202 */ 203 public constructor(gpio: GPIO) { 204 this.gpio = gpio; 205 this.pwmInitialized = false; 206 this.controllerInitialized = false; 207 this.gear = 0; 208 } 209 }