LoginSignup
6
9

More than 5 years have passed since last update.

Arduinoでクルマのエンジンが動く? オープンソースECU Speeduino (7)ソースコードの構造

Last updated at Posted at 2019-02-03

Speeduinoのソースコードを読み解く

前回記事 (6)燃料噴射量・点火時期制御

Speeduinoの制御を理解するため、ソースコードを解読していきます。
ソースコードを理解することで、いろいろとカスタマイズすることもできます。:smiling_imp:

GitHubのソースコード

  • Speeduinoコミュニティの中で日々更新されており、移植性やコード品質の向上が行われているようです。
  • そのため、流動的であり、あるコードが別のソースファイルの関数へ移動することもあります。:cry:
  • ここでは、2018.10リリースのソースコードについて記載しています。

ソースコードファイル一覧

以下のようなソースコードファイルから構成されています。
srcフォルダ内に複数のライブラリがありますが、一部しか利用していないようです。

ソース ヘッダー 概要
auxiliaries.ino auxiliaries.h ファン制御、ブースト制御など
cancomms.ino cancomms.h CAN通信用マイコンとのシリアル通信処理
comms.ino comms.h TunerStudioとのシリアル通信コマンド処理
corrections.ino corrections.h 様々な制御処理の集合
crankMaths.ino crankMaths.h クランク角算出処理
decoders.ino decoders.h クランク角パルス信号処理
display.ino display.h QLEDディスプレイ(SSD1306)表示(オプション)
errors.ino errors.h 故障診断エラーコード処理(故障診断は未実装)
globals.h グローバル変数定義
idle.ino idle.h アイドル回転数制御
maths.ino maths.h 固定小数点演算ライブラリ
scheduledIO.ino scheduledIO.h 点火処理スケジュール
scheduler.ino scheduler.h タイマー割込み定義、クランク角センサ割込み定義
sensors.ino sensors.h センサーA/D値読込み処理
speeduino.ino speeduino.h 初期化処理と様々なバックグラウンド処理
storage.ino storage.h EEPROMデータ処理
table.ino table.h ルックアップテーブル関数
timers.ino timers.h タイマー割込み処理
updates.ino updates.h EEPROMアップデート処理(初期化時)
utils.ino utils.h マイコン依存処理定義(I/Oピン、割込み)、クランク角パルス処理選択
src/PID_v1/PID_v1.cpp src/PID_v1/PID_v1.h PID制御ライブラリ

コンパイルスイッチ

  • 複数のターゲットマイコンでコードを共有化するため、#if definedマクロ等が多くあります。:hushed:
  • 対象のマイコン以外ではコンパイルされないため、ソースコードを読むときに注意が必要です。:confounded:

リリース 2018.10では、Arduino Mega以外は、まだ実装途中のようです。:open_mouth:

#defineマクロ マイコンボード
__AVR_ATmega1280__ Arduino Mega1280
__AVR_ATmega2560__ Arduino Mega2560
CORE_TEENSY Teensy
CORE_STM32 STM32
ARDUINO_ARCH_STM32 STM32 GENERIC core

コンパイルスイッチの例

utils.ino
uint16_t freeRam ()
{
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  extern int __heap_start, *__brkval;
  uint16_t v;

  return (uint16_t) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);

#elif defined(CORE_TEENSY)
  uint32_t stackTop;
  uint32_t heapTop;

  // current position of the stack.
  stackTop = (uint32_t) &stackTop;

  // current position of heap.
  void* hTop = malloc(1);
  heapTop = (uint32_t) hTop;
  free(hTop);

  // The difference is the free, available ram.
  return (uint16_t)stackTop - heapTop;
#elif defined(CORE_STM32)
  char top = 't';
  return &top - reinterpret_cast<char*>(sbrk(0));

#endif
}

GitHubソースコード

初期化関数 setup()

マイコン起動時の初期化処理を記述しています。

主要なコール関数

関数 処理
initialiseTimers() タイマー割込み(1ms)の設定
loadConfig() EEPROMのパラメータ読込み
doUpdates() EEPROMのアップデート
speeduino.ino
void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  //Setup the dummy fuel and ignition tables
  //dummyFuelTable(&fuelTable);
  //dummyIgnitionTable(&ignitionTable);
  table3D_setSize(&fuelTable, 16);
  table3D_setSize(&ignitionTable, 16);
  table3D_setSize(&afrTable, 16);
  table3D_setSize(&stagingTable, 8);
  table3D_setSize(&boostTable, 8);
  table3D_setSize(&vvtTable, 8);
  table3D_setSize(&trim1Table, 6);
  table3D_setSize(&trim2Table, 6);
  table3D_setSize(&trim3Table, 6);
  table3D_setSize(&trim4Table, 6);
  initialiseTimers();

  loadConfig();
  doUpdates(); //Check if any data items need updating (Occurs ith firmware updates)

ループ処理関数(バックグランド処理) loop()

  • タイマー割込み(timers.ino)でセットされるTIMER_maskを参照し、周期毎の処理を行っています。
  • その他、ループ回数毎に行う処理も存在します。

主要なコール関数

周期 関数 処理
31loop command() シリアル通信コマンド処理
31loop canCommand() CAN通信用シリアルコマンド処理
66ms readTPS() TPS読込み
33ms boostControl() ブースト制御
250ms readO2() O2センサ読込み
250ms readBat() バッテリ電圧読込み
250ms nitrousControl() NOx制御
250ms vvtControl() VVT制御
250ms idleControl() アイドル回転数制御
1000ms readBaro() 大気圧センサ読込み
1loop correctionsFuel() 燃料噴射量算出
1loop getVE() VE算出
1loop getAdvance() 点火時期算出
1loop PW() インジェクタパルス幅算出
speeduino.ino
void loop()
{
      mainLoopCount++;
      LOOP_TIMER = TIMER_mask;
      //Check for any requets from serial. Serial operations are checked under 2 scenarios:
      // 1) Every 64 loops (64 Is more than fast enough for TunerStudio). This function is equivalent to ((loopCount % 64) == 1) but is considerably faster due to not using the mod or division operations
      // 2) If the amount of data in the serial buffer is greater than a set threhold (See globals.h). This is to avoid serial buffer overflow when large amounts of data is being sent
      //if ( (BIT_CHECK(TIMER_mask, BIT_TIMER_15HZ)) || (Serial.available() > SERIAL_BUFFER_THRESHOLD) )
      if ( ((mainLoopCount & 31) == 1) or (Serial.available() > SERIAL_BUFFER_THRESHOLD) )
      {
        if (Serial.available() > 0) { command(); }
        else if(cmdPending == true)
        {
          //This is a special case just for the tooth and composite loggers
          if (currentCommand == 'T') { command(); }
        }

      }

クランク角センサ割込み

  • クランク角センサパルスをトリガーに起動されます。
  • 割込み処理により。パルスの周期を計測し、回転数、クランク角を算出しています。
  • 様々な車種のクランク角センサ信号処理関数があらかじめ用意されています。
関数 処理
triggerSetup_xxx() クランク角センサパラメータ定義
triggerPri_xxxx() 1stクラック角センサ信号処理
triggerSec_xxxx() 2ndクラック角センサ信号処理
getRPM_xxxx() 回転数算出
getCrankAngle_xxxx() クランク角算出

decoders.ino

decoders.ino
/*
Speeduino - Simple engine management for the Arduino Mega 2560 platform
Copyright (C) Josh Stewart
A full copy of the license may be found in the projects root directory
*/

/*
This file contains the various crank and cam wheel decoder functions.

Each decoder must have the following 4 functions (Where xxxx is the decoder name):

* triggerSetup_xxx - Called once from within setup() and configures any required variables
* triggerPri_xxxx - Called each time the primary (No. 1) crank/cam signal is triggered (Called as an interrupt, so variables must be declared volatile)
* triggerSec_xxxx - Called each time the secondary (No. 2) crank/cam signal is triggered (Called as an interrupt, so variables must be declared volatile)
* getRPM_xxxx - Returns the current RPM, as calculated by the decoder
* getCrankAngle_xxxx - Returns the current crank angle, as calculated b the decoder

And each decoder must utlise at least the following variables:
toothLastToothTime - The time (In uS) that the last primary tooth was 'seen'
*

*/

1msタイマー割込み OneMSInterval()

・1msのタイマー割込み処理で、一定周期ごと(33ms,66ms,100ms,250ms等)、TIMER_maskフラグをセットしています。
・周期的な処理の多くは、このフラグを基にバックグラウンド処理(loop関数)の中で行われています。

timers.ino
//Timer2 Overflow Interrupt Vector, called when the timer overflows.
//Executes every ~1ms.
#if defined(CORE_AVR) //AVR chips use the ISR for this
ISR(TIMER2_OVF_vect, ISR_NOBLOCK) //This MUST be no block. Turning NO_BLOCK off messes with timing accuracy
#elif defined (CORE_TEENSY) || defined(CORE_STM32)
void oneMSInterval() //Most ARM chips can simply call a function
#endif
{
  ms_counter++;

  //Increment Loop Counters
  loop33ms++;
  loop66ms++;
  loop100ms++;
  loop250ms++;
  loopSec++;

  unsigned long targetOverdwellTime;

  //Overdwell check
  targetOverdwellTime = micros() - dwellLimit_uS; //Set a target time in the past that all coil charging must have begun after. If the coil charge began before this time, it's been running too long
  bool isCrankLocked = configPage4.ignCranklock && (currentStatus.RPM < currentStatus.crankRPM); //Dwell limiter is disabled during cranking on setups using the locked cranking timing. WE HAVE to do the RPM check here as relying on the engine cranking bit can be potentially too slow in updating
  //Check first whether each spark output is currently on. Only check it's dwell time if it is

  if(ignitionSchedule1.Status == RUNNING) { if( (ignitionSchedule1.startTime < targetOverdwellTime) && (configPage4.useDwellLim) && (isCrankLocked != true) ) { endCoil1Charge(); ignitionSchedule1.Status = OFF; } }
  if(ignitionSchedule2.Status == RUNNING) { if( (ignitionSchedule2.startTime < targetOverdwellTime) && (configPage4.useDwellLim) && (isCrankLocked != true) ) { endCoil2Charge(); ignitionSchedule2.Status = OFF; } }
  if(ignitionSchedule3.Status == RUNNING) { if( (ignitionSchedule3.startTime < targetOverdwellTime) && (configPage4.useDwellLim) && (isCrankLocked != true) ) { endCoil3Charge(); ignitionSchedule3.Status = OFF; } }
  if(ignitionSchedule4.Status == RUNNING) { if( (ignitionSchedule4.startTime < targetOverdwellTime) && (configPage4.useDwellLim) && (isCrankLocked != true) ) { endCoil4Charge(); ignitionSchedule4.Status = OFF; } }
  if(ignitionSchedule5.Status == RUNNING) { if( (ignitionSchedule5.startTime < targetOverdwellTime) && (configPage4.useDwellLim) && (isCrankLocked != true) ) { endCoil5Charge(); ignitionSchedule5.Status = OFF; } }



  //30Hz loop
  if (loop33ms == 33)
  {
    loop33ms = 0;
    BIT_SET(TIMER_mask, BIT_TIMER_30HZ);
  }

  //15Hz loop
  if (loop66ms == 66)
  {
    loop66ms = 0;
    BIT_SET(TIMER_mask, BIT_TIMER_15HZ);
  }

  //Loop executed every 100ms loop
  //Anything inside this if statement will run every 100ms.
  if (loop100ms == 100)
  {
    loop100ms = 0; //Reset counter
    BIT_SET(TIMER_mask, BIT_TIMER_10HZ);

    currentStatus.rpmDOT = (currentStatus.RPM - lastRPM_100ms) * 10; //This is the RPM per second that the engine has accelerated/decelleratedin the last loop
    lastRPM_100ms = currentStatus.RPM; //Record the current RPM for next calc
  }

  //Loop executed every 250ms loop (1ms x 250 = 250ms)
  //Anything inside this if statement will run every 250ms.
  if (loop250ms == 250)
  {
    loop250ms = 0; //Reset Counter
    BIT_SET(TIMER_mask, BIT_TIMER_4HZ);
    #if defined(CORE_STM32) //debug purpose, only visal for running code
      digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    #endif

    #if defined(CORE_AVR)
      //Reset watchdog timer (Not active currently)
      //wdt_reset();
      //DIY watchdog
      //This is a sign of a crash:
      //if( (initialisationComplete == true) && (last250msLoopCount == mainLoopCount) ) { setup(); }
      //else { last250msLoopCount = mainLoopCount; }
    #endif
  }

参考

Speeduino Wiki コードの概要

最後に

次回は、ソースコードで使われているデータを読み解く予定です。

6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9