新西达电调初始化代码,使用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 }

 

posted @ 2019-11-14 11:36  鸡毛巾  阅读(620)  评论(0编辑  收藏  举报