這篇文章針對 Nodemcu V2 開發的 NSR-150 可程式 CDI 說明
Nodemcu 使用接腳(V0.4版):
D1:脈波 PWM 輸入
D4:RC 訊號輸出
D6:點火 輸出
可程式的 CDI 如果只是拿來點火讓引擎運轉,最重要的是 D1 接腳的脈波 PWM 輸入
(此圖是追風的轉子)
當轉子在旋轉時,轉子外圍的凸起的鐵塊,就會被脈波線圈接收器接收(俗稱黑豆乾或黑豆仔)
黑豆乾負責偵測凸台,每次凸台經過時線圈就會產生一個彈跳
脈波的彈跳
同學在驗證波型時,要考慮是先負再正,還是先正才負,延遲的時間會不一樣
所以首先先抓出原廠CDI每個轉速從偵測到凸台,到點火的這段時間是延遲多少
那不論是正、是負,照原本的時間作好Delay後點火,就能作到原廠CDI的騎乘感受^^
手冊中的波型
實際量測到的結果(X軸是轉速,Y軸是 65°-(點火時間-凸台時間)/360
又不知不覺得寫的有點遠,感覺很重要~
接下來作程式說明(V0.4版):
程式碼位置:https://github.com/shadowjohn/NSR-150_CDI
/*
* NSR-150 可程式 CDI
* Version: V0.4
* V0.04 版功能是將二期 RC 改成一期RC的騎乘體驗感
* 接腳如下:
* D1 凸台
* D4 RC 訊號輸出
* D6 點火
*
* Release Date: 2021-09-26
* Author: 羽山秋人 (https://sunnyriver.3wa.tw)
* Author: @FB 田峻墉
*/
#include <Arduino.h>
/*
轉速 60 轉 = 每分鐘 60 轉,每秒 1 轉,1轉 = 1 秒 = 1000.000 ms = 1000000us
轉速 100 轉 = 每分鐘 100 轉,每秒 1.67 轉,1轉 = 0.598802 秒 = 598.802 ms = 598802us
轉速 200 轉 = 每分鐘 200 轉,每秒 3.3 轉,1轉 = 0.300003 秒 = 300.003 ms = 300003us
轉速 600 轉 = 每分鐘 600 轉,每秒 10 轉,1轉 = 0.1 秒 = 100.000 ms = 100000us
轉速 1500 轉 = 每分鐘 1500 轉,每秒 25 轉,1轉 = 0.04 秒 = 40.000 ms = 40000us
轉速 6000 轉 = 每分鐘 6000 轉,每秒 60 轉,1轉 = 0.01666... 秒 = 16.667 ms = 16667us
轉速 10000 轉 = 每分鐘 10000 轉,每秒 166.6 轉,1轉 = 0.00599... 秒 = 5.999 ms = 5999us
轉速 14000 轉 = 每分鐘 14000 轉,每秒 233.3 轉,1轉 = 0.0042863. 秒 = 4.286 ms = 4286us
轉速 14060 轉 = 每分鐘 14060 轉,每秒 240 轉,1轉 = 0.0041667. 秒 = 4.167 ms = 4167us
轉速 16000 轉 = 每分鐘 16000 轉,每秒 266.6 轉,1轉 = 0.0037500. 秒 = 3.750 ms = 3750us
*/
// 宣告接腳
// 宣告成 const 代表變數值不會變動
const int ToPin = D1; //凸台
const int RCPin = D4; //RC訊號
const int FirePin = D6; //點火
// 宣告成 volatile 代表該變數變動會很頻繁
// now_degree 是指目前的角度,初始值先給個 12,引擎一轉就會一直變動
volatile float now_degree = 12;
// NSR維修手冊裡的圖
// volatile const float degree[16] = {12, 12, 12, 12, 17, 29, 29, 25, 23, 20, 17, 13, 10, 8, 8, 8};
// 這個是羽山原本CDI量到的
volatile const float degree[16] = {10, 12, 12, 25, 26, 23, 20, 16, 13, 9, 9, 9, 8, 8, 8, 8};
// CDI_DELAY = (1000000.0 / (float(rpm) / 60.0)) * ((fullAdv - r)/ 360.0);
// 凸台->點火這段等待時間(CDI_DELAY) = 每個轉速時間 * ((65-該轉速的角度)/360)
// 這樣算起來的數值就等於羽山量到的delay時間
volatile const float fullAdv = 65;
// 是否點火,有經過凸台後,isFiring 才會變 true,點完後改回 false
volatile bool isFiring = false;
// Nodemcu由於不適合作多工開發,所以一般都使用外部中斷 (ISR) 來處理其他接腳觸發的事件
// 同時在 ISR 的過程,也不適合在裡面寫 delay、delayMicroseconds,會影響 ISR 取值的正確性
// 所以 ISR 的過程,都會拿來更新 Global 總體變數
// 最後在 loop() 裡,才會處理延遲、點火等工作
volatile unsigned long C = micros(); //紀錄碰到凸台的時間
volatile unsigned long C_old = 0; //紀錄上次碰到凸台的時間
volatile unsigned long rpm = 0; //紀錄當前 RPM
volatile unsigned long RPM_DELAY = 0; //凸台跟上一次凸台相距時間,用來推算當前RPM多少
volatile unsigned int isShowCount = 0; //在 loop 中每轉 100次才回報一次到 Serial 觀看
bool isFirstRC_Flag = true; //當 kick start 時,RC 全開一次的旗標
// 電開機時,isFirstRC_Flag 預設 true,作完就 false,只作一次
// (此變數其實有點多寫了,因為放在 setup 裡,本身也只會執行一次...)
volatile double CDI_DELAY = -1; //計算每一圈碰到凸台後,要延遲多久時間才點火 us
// 這裡就是 ISR 的事件,當凸台發生一個彈跳,且為 RISING,就會觸發這個function (countup)
// 新版的Nodemcu程式,在宣告 ISR 時,要多寫「ICACHE_RAM_ATTR」
// 不然編譯會無法過關
void ICACHE_RAM_ATTR countup() { //For newest version
//收到CDI點火,扣掉偵測到凸台RISING時間
//只要是Rising就是Fire
C = micros();
// 不可能有超過 17000rpm 的狀況
// (1/(17000/60) *1000 * 1000 = 3529
// 當 17000轉,每一圈轉速只有 3529us,相當快,比這個數值更小,轉速就更高
RPM_DELAY = C - C_old;
if(RPM_DELAY < 3500) {
//超過 17000rpm 了
return;
}
//其實還是要限轉,NSR也不可能拉轉超過17000rpm,超過就當作凸台偵測異常
//保守的同學,其實超過 13000rpm 就可以跳過了,甚至 12000rpm,自己再換算一下
if(RPM_DELAY > 598802) {
//低於 100rpm
C_old = C;
rpm = 0;
return;
}
// 過慢的轉速,也跳過,不能永無止盡的等下去,低於 100rpm 跳過當作熄火
rpm = 60000000UL / RPM_DELAY; //計算當前轉速,如1300就 1300rpm
C_old = C; //上一次的凸台時間,改成現在的時間
if(rpm>17000)
{
//不可能的
//rpm = 0;
return;
}
//再保護一次,如果超過 17000rpm,就跳過不點火
if(rpm<80)
{
//太低了,熄火吧
//rpm = 0;
return;
}
// 再保護一次,如果低於 80rpm,就跳過不點火
if (rpm != 0) {
//以下備註用 2500rpm 當說明
int index = (int)rpm / 1000; //將轉速除1000,轉整數,就可以得到現在是對應哪個角度,如 2500rpm,index : 2
int index_n = index + 1; //抓下一個角度,如 2500rpm,index=2+1 = 3
index = (index >= 16) ? 15 : index; //我只有定 16組,0~15
if (index_n >= 16) index_n = 15; //最後一組也不能超過 16,0~15
long s = index * 1000; // 2000
long e = index_n * 1000; // 3000
long ss = degree[index] * 1000; // 12*1000 = 12000
long ee = degree[index_n] * 1000; // 25*1000 = 25000
double r = map(int(rpm), s, e, ss, ee) / 1000.0; // map(2500,2000,3000,12000,25000) / 1000.0 = 18.5
now_degree = r; //所以在 2500rpm ,得 18.5°
//新的 CDI 計算方法
//delay = adv/360*轉速一圈時間
//rpm 一圈時間 * r * 360)
//計算每一圈碰到凸台後,要延遲多久時間才點火 us
CDI_DELAY = (1000000.0 / (float(rpm) / 60.0)) * ((fullAdv - r)/ 360.0);
isFiring = true;
//算出了點火的延遲時間 CDI_DELAY,把 isFiring 設成允許點火
//改成在 loop 裡執行
//delayMicroseconds(long(CDI_DELAY));
//digitalWrite(FirePin, HIGH);
//點火持續時間
//固定點 200us
//delayMicroseconds(200);
//digitalWrite(FirePin, LOW);
}
}
void setup() {
Serial.begin(250000); //胞率設大一些,用電腦看好像比較流暢
Serial.println("Counting...");
// 宣告凸台角(D1),為 INPUT_PULLUP
pinMode(ToPin, INPUT_PULLUP);
// 偽造給 RC 輸出腳的訊號,為 OUTPUT
pinMode(RCPin, OUTPUT);
// 點火腳 D6,也是 OUTPUT,要去觸發 2SC1815 再觸發 SCR
pinMode(FirePin, OUTPUT);
// 註冊凸台中斷
// 使用 digitalPinToInterrupt 才能自動幫你把接角換成 attachInterrupt 的腳位
// 如果使用 Arduino UNO,要注意可用的ISR腳很少…
attachInterrupt(digitalPinToInterrupt(ToPin), countup, RISING); //RISING
// 剛開機,不準點火
digitalWrite(FirePin, LOW);
// 開機後,觸發讓 RC 全開一次
if(isFirstRC_Flag == true)
{
isFirstRC_Flag=false;
// 開機後,RC全開,再關上一次
for(int i=0;i<250;i++)
{
// 50 % duty , 10000rpm , 250 次,就可以全開
// 250次約耗時 1.5 秒
// 要注意不能讓程式在 setup() 階段作太久作太多事,不然 nodemcu 會以為當機就停了)
// 1個 pwm 用了 6ms (約等於 10000rpm)
digitalWrite(RCPin, HIGH);
delayMicroseconds(3000); //3ms
digitalWrite(RCPin, LOW);
delayMicroseconds(3000); //3ms
}
}
digitalWrite(RCPin, LOW);
// 只要不繼續給 pwm 訊號,RC 電腦就會自己關閥門了
}
void loop() {
// put your main code here, to run repeatedly:
// 這個 isShowCount 是用來給電腦看了,插 USB 在 Serial 每 loop 100 次,就會回饋一筆電腦上看
// 每次加1,超過100歸零
if (isShowCount > 100)
{
//display_rpm();
isShowCount = 0;
// 觀察 Global 總體變數, rpm、now_degree、CDI_DELAY
// 這樣作好的 CDI,插上訊號產生器,每個轉速看一看,是不是跟在車上量到的 delay 時間
// 時間一致就是正確,就不用一直拿到車上拉轉量延遲時間...
Serial.print(rpm);
Serial.print(" , ");
Serial.print(now_degree);
Serial.print(" , ");
Serial.print(CDI_DELAY);
Serial.println("");
}
isShowCount++;
//如果點火旗標變 true,就代表凸台抓到了
if(isFiring == true)
{
isFiring = false;
// 由於要讓2期的RC有1期 RC 的驗乘體驗
// 低於5000完全不需要送出 RC 訊號
// 5000以上,收多少訊號,就給多少到RC
/*
轉速 6000 轉 = 每分鐘 6000 轉,每秒 60 轉,1轉 = 0.01666... 秒 = 16.667 ms = 16667us
轉速 10000 轉 = 每分鐘 10000 轉,每秒 166.6 轉,1轉 = 0.00599... 秒 = 5.999 ms = 5999us
*/
if(rpm>=5000)
{
// 超過5000轉,RC 訊號給 HIGH
// 如果你希望還是跟二期 RC 一樣,那把 rpm>=5000 改成 rpm>=0 即可
// 但如果你本來就是一期的 RC 電腦,那就只有一期電腦的特性,只有二期才能模擬一期的功能
digitalWrite(RCPin, HIGH);
}
// 開始延遲,凸台->點火的延遲時間
delayMicroseconds(long(CDI_DELAY));
digitalWrite(FirePin, HIGH); //點火
// 點火持續時間
// 固定點 200us
delayMicroseconds(200); //點200us
digitalWrite(FirePin, LOW); //停止點火
digitalWrite(RCPin, LOW); //RC 訊號給 Low
}
}
寫完了,扣掉囉哩八嗦的註解後,程式不長,也很好理解
回頭再看程式,感覺還有很多小細節可以改進呢^^"
特別是在換算角度的部分,寫的稍醜了點
不過用訊號產生器抽樣挑了幾個轉速,換算後 CDI_DELAY 時間,跟實際量到的數值都一致
羽山也順順騎上下班一個禮拜,體驗相當好~
冷車超級好發,踩一下馬上咚咚咚就轉起來,完全不熄火^~^
未來也許可以改改角度,看看會不會更有樂趣