#include "pages.h" #include "globals.h" #include "utilities.h" #include "table3d_axis_io.h" // Maps from virtual page "addresses" to addresses/bytes of real in memory entities // // For TunerStudio: // 1. Each page has a numeric identifier (0 to N-1) // 2. A single page is a contiguous block of data. // So individual bytes are identified by a (page number, offset) // // The TS layout is not what is in memory. E.g. // // TS Page 2 |0123456789ABCD|0123456789ABCDEF| // | | // Arduino In Memory |--- Entity A ---| |--- Entity B -----| // // Further, the in memory entity may also not be contiguous or in the same // order that TS expects // // So there is a 2 stage mapping: // 1. Page # + Offset to entity // 2. Offset to intra-entity byte // Page sizes as defined in the .ini file constexpr const uint16_t PROGMEM ini_page_sizes[] = { 0, 128, 288, 288, 128, 288, 128, 240, 384, 192, 192, 288, 192, 128, 288, 256 }; // ========================= Table size calculations ========================= // Note that these should be computed at compile time, assuming the correct // calling context. template static inline constexpr uint16_t get_table_value_end(void) { return table_t::xaxis_t::length*table_t::yaxis_t::length; } template static inline constexpr uint16_t get_table_axisx_end(void) { return get_table_value_end()+table_t::xaxis_t::length; } template static inline constexpr uint16_t get_table_axisy_end(const table_t *) { return get_table_axisx_end()+table_t::yaxis_t::length; } // ========================= Intra-table offset to byte class ========================= template class offset_to_table { public: // This class encapsulates mapping a linear offset to the various parts of a table // and exposing the linear offset as an mutable byte. // // Tables do not map linearly to the TS page address space, so special // handling is necessary (we do not use the normal array layout for // performance reasons elsewhere) // // We take the offset & map it to a single value, x-axis or y-axis element // // Using a template here is a performance boost - we can call functions that // are specialised per table type, which allows the compiler more optimisation // opportunities. See get_table_value(). offset_to_table(table_t *pTable, uint16_t table_offset) : _pTable(pTable), _table_offset(table_offset) { } // Getter inline byte operator*(void) const { switch (get_table_location()) { case table_location_values: return get_value_value(); case table_location_xaxis: return get_table3d_axis_converter(table_t::xaxis_t::domain).to_byte(get_xaxis_value()); case table_location_yaxis: default: return get_table3d_axis_converter(table_t::yaxis_t::domain).to_byte(get_yaxis_value()); } } // Setter inline offset_to_table &operator=( byte new_value ) { switch (get_table_location()) { case table_location_values: get_value_value() = new_value; break; case table_location_xaxis: get_xaxis_value() = get_table3d_axis_converter(table_t::xaxis_t::domain).from_byte(new_value); break; case table_location_yaxis: default: get_yaxis_value() = get_table3d_axis_converter(table_t::yaxis_t::domain).from_byte(new_value); } invalidate_cache(&_pTable->get_value_cache); return *this; } private: inline byte& get_value_value(void) const { return _pTable->values.value_at((uint8_t)_table_offset); } inline table3d_axis_t& get_xaxis_value(void) const { return *(_pTable->axisX.begin().advance(_table_offset - get_table_value_end())); } inline table3d_axis_t& get_yaxis_value(void) const { return *(_pTable->axisY.begin().advance(_table_offset - get_table_axisx_end())); } enum table_location { table_location_values, table_location_xaxis, table_location_yaxis }; inline table_location get_table_location(void) const { if (_table_offset()) { return table_location_values; } if (_table_offset()) { return table_location_xaxis; } return table_location_yaxis; } table_t *_pTable; uint16_t _table_offset; }; // ========================= Offset to entity byte mapping ========================= inline byte& get_raw_location(page_iterator_t &entity, uint16_t offset) { return *((byte*)entity.pData + (offset-entity.start)); } inline byte get_table_value(page_iterator_t &entity, uint16_t offset) { #define CTA_GET_TABLE_VALUE(size, xDomain, yDomain, pTable, offset) \ return *offset_to_table((TABLE3D_TYPENAME_BASE(size, xDomain, yDomain)*)pTable, offset); #define CTA_GET_TABLE_VALUE_DEFAULT ({ return 0U; }) CONCRETE_TABLE_ACTION(entity.table_key, CTA_GET_TABLE_VALUE, CTA_GET_TABLE_VALUE_DEFAULT, entity.pData, (offset-entity.start)); } inline byte get_value(page_iterator_t &entity, uint16_t offset) { if (Raw==entity.type) { return get_raw_location(entity, offset); } if (Table==entity.type) { return get_table_value(entity, offset); } return 0U; } inline void set_table_value(page_iterator_t &entity, uint16_t offset, byte new_value) { #define CTA_SET_TABLE_VALUE(size, xDomain, yDomain, pTable, offset, new_value) \ offset_to_table((TABLE3D_TYPENAME_BASE(size, xDomain, yDomain)*)pTable, offset) = new_value; break; #define CTA_SET_TABLE_VALUE_DEFAULT ({ }) CONCRETE_TABLE_ACTION(entity.table_key, CTA_SET_TABLE_VALUE, CTA_SET_TABLE_VALUE_DEFAULT, entity.pData, (offset-entity.start), new_value); } inline void set_value(page_iterator_t &entity, byte value, uint16_t offset) { if (Raw==entity.type) { get_raw_location(entity, offset) = value; } else if (Table==entity.type) { set_table_value(entity, offset, value); } } // ========================= Static page size computation & checking =================== // This will fail AND print the page number and required size template static inline void check_size() { static_assert(ini_page_sizes[pageNum] >= min, "Size is off!"); } // Since pages are a logical contiguous block, we can automatically compute the // logical start address of every item: the first one starts at zero, following // items must start at the end of the previous. #define _ENTITY_START(entityNum) entity ## entityNum ## Start #define ENTITY_START_VAR(entityNum) _ENTITY_START(entityNum) // Compute the start address of the next entity. We need this to be a constexpr // so we can static assert on it later. So we cannot increment an exiting var. #define DECLARE_NEXT_ENTITY_START(entityIndex, entitySize) \ constexpr uint16_t ENTITY_START_VAR( PP_INC(entityIndex) ) = ENTITY_START_VAR(entityIndex)+entitySize; // ========================= Logical page end processing =================== // The members of all page_iterator_t instances are compile time constants and // thus all page_iterator_t instances *could* be compile time constants. // // If we declare them inline as part of return statements, gcc recognises they // are constants (even without constexpr). Constants need to be stored somewhere: // gcc places them in the .data section, which is placed in SRAM :-(. // // So we would end up using several hundred bytes of SRAM. // // Instead we use this (and other) intermediate factory function(s) - it provides a barrier that // forces GCC to construct the page_iterator_t instance at runtime. inline const page_iterator_t create_end_iterator(uint8_t pageNum, uint16_t start) { return page_iterator_t { .pData = nullptr, .table_key = table_type_None, .page = pageNum, .start = start, .size = start, .type = End, }; } // Signal the end of a page #define END_OF_PAGE(pageNum, entityNum) \ check_size(); \ return create_end_iterator(pageNum, ENTITY_START_VAR(entityNum)); \ // ========================= Table processing =================== inline const page_iterator_t create_table_iterator(void *pTable, table_type_t key, uint8_t pageNum, uint16_t start, uint16_t size) { return page_iterator_t { .pData = pTable, .table_key = key, .page = pageNum, .start = start, .size = size, .type = Table, }; } // If the offset is in range, create a Table entity_t #define CHECK_TABLE(pageNum, offset, pTable, entityNum) \ if (offset < ENTITY_START_VAR(entityNum)+get_table_axisy_end(pTable)) \ { \ return create_table_iterator(pTable, (pTable)->type_key, \ pageNum, \ ENTITY_START_VAR(entityNum), get_table_axisy_end(pTable)); \ } \ DECLARE_NEXT_ENTITY_START(entityNum, get_table_axisy_end(pTable)) // ========================= Raw memory block processing =================== inline const page_iterator_t create_raw_iterator(void *pBuffer, uint8_t pageNum, uint16_t start, uint16_t size) { return page_iterator_t { .pData = pBuffer, .table_key = table_type_None, .page = pageNum, .start = start, .size = size, .type = Raw, }; } // If the offset is in range, create a Raw entity_t #define CHECK_RAW(pageNum, offset, pDataBlock, blockSize, entityNum) \ if (offset < ENTITY_START_VAR(entityNum)+blockSize) \ { \ return create_raw_iterator(pDataBlock, pageNum, ENTITY_START_VAR(entityNum), blockSize);\ } \ DECLARE_NEXT_ENTITY_START(entityNum, blockSize) // =============================================================================== // Does the heavy lifting of mapping page+offset to an entity // // Alternative implementation would be to encode the mapping into data structures // That uses flash memory, which is scarce. And it was too slow. static inline __attribute__((always_inline)) // <-- this is critical for performance page_iterator_t map_page_offset_to_entity(uint8_t pageNumber, uint16_t offset) { // The start address of the 1st entity in any page. static constexpr uint16_t ENTITY_START_VAR(0) = 0U; switch (pageNumber) { case 0: END_OF_PAGE(0, 0) case veMapPage: { CHECK_TABLE(veMapPage, offset, &fuelTable, 0) END_OF_PAGE(veMapPage, 1) } case ignMapPage: //Ignition settings page (Page 2) { CHECK_TABLE(ignMapPage, offset, &ignitionTable, 0) END_OF_PAGE(ignMapPage, 1) } case afrMapPage: //Air/Fuel ratio target settings page { CHECK_TABLE(afrMapPage, offset, &afrTable, 0) END_OF_PAGE(afrMapPage, 1) } case boostvvtPage: //Boost, VVT and staging maps (all 8x8) { CHECK_TABLE(boostvvtPage, offset, &boostTable, 0) CHECK_TABLE(boostvvtPage, offset, &vvtTable, 1) CHECK_TABLE(boostvvtPage, offset, &stagingTable, 2) END_OF_PAGE(boostvvtPage, 3) } case seqFuelPage: { CHECK_TABLE(seqFuelPage, offset, &trim1Table, 0) CHECK_TABLE(seqFuelPage, offset, &trim2Table, 1) CHECK_TABLE(seqFuelPage, offset, &trim3Table, 2) CHECK_TABLE(seqFuelPage, offset, &trim4Table, 3) CHECK_TABLE(seqFuelPage, offset, &trim5Table, 4) CHECK_TABLE(seqFuelPage, offset, &trim6Table, 5) CHECK_TABLE(seqFuelPage, offset, &trim7Table, 6) CHECK_TABLE(seqFuelPage, offset, &trim8Table, 7) END_OF_PAGE(seqFuelPage, 8) } case fuelMap2Page: { CHECK_TABLE(fuelMap2Page, offset, &fuelTable2, 0) END_OF_PAGE(fuelMap2Page, 1) } case wmiMapPage: { CHECK_TABLE(wmiMapPage, offset, &wmiTable, 0) CHECK_TABLE(wmiMapPage, offset, &vvt2Table, 1) CHECK_TABLE(wmiMapPage, offset, &dwellTable, 2) END_OF_PAGE(wmiMapPage, 3) } case ignMap2Page: { CHECK_TABLE(ignMap2Page, offset, &ignitionTable2, 0) END_OF_PAGE(ignMap2Page, 1) } case veSetPage: { CHECK_RAW(veSetPage, offset, &configPage2, sizeof(configPage2), 0) END_OF_PAGE(veSetPage, 1) } case ignSetPage: { CHECK_RAW(ignSetPage, offset, &configPage4, sizeof(configPage4), 0) END_OF_PAGE(ignSetPage, 1) } case afrSetPage: { CHECK_RAW(afrSetPage, offset, &configPage6, sizeof(configPage6), 0) END_OF_PAGE(afrSetPage, 1) } case canbusPage: { CHECK_RAW(canbusPage, offset, &configPage9, sizeof(configPage9), 0) END_OF_PAGE(canbusPage, 1) } case warmupPage: { CHECK_RAW(warmupPage, offset, &configPage10, sizeof(configPage10), 0) END_OF_PAGE(warmupPage, 1) } case progOutsPage: { CHECK_RAW(progOutsPage, offset, &configPage13, sizeof(configPage13), 0) END_OF_PAGE(progOutsPage, 1) } case boostvvtPage2: //Boost, VVT and staging maps (all 8x8) { CHECK_TABLE(boostvvtPage2, offset, &boostTableLookupDuty, 0) CHECK_RAW(boostvvtPage2, offset, &configPage15, sizeof(configPage15), 1) END_OF_PAGE(boostvvtPage2, 2) } default: abort(); // Unknown page number. Not a lot we can do. break; } } // ====================================== External functions ==================================== uint8_t getPageCount(void) { return _countof(ini_page_sizes); } uint16_t getPageSize(byte pageNum) { return pgm_read_word(&(ini_page_sizes[pageNum])); } void setPageValue(byte pageNum, uint16_t offset, byte value) { page_iterator_t entity = map_page_offset_to_entity(pageNum, offset); set_value(entity, value, offset); } byte getPageValue(byte pageNum, uint16_t offset) { page_iterator_t entity = map_page_offset_to_entity(pageNum, offset); return get_value(entity, offset); } // Support iteration over a pages entities. // Check for entity.type==End page_iterator_t page_begin(byte pageNum) { return map_page_offset_to_entity(pageNum, 0U); } page_iterator_t advance(const page_iterator_t &it) { return map_page_offset_to_entity(it.page, it.start+it.size); } /** * Convert page iterator to table value iterator. */ table_value_iterator rows_begin(const page_iterator_t &it) { return rows_begin(it.pData, it.table_key); } /** * Convert page iterator to table x axis iterator. */ table_axis_iterator x_begin(const page_iterator_t &it) { return x_begin(it.pData, it.table_key); } /** * Convert page iterator to table x axis iterator. */ table_axis_iterator x_rbegin(const page_iterator_t &it) { return x_rbegin(it.pData, it.table_key); } /** * Convert page iterator to table y axis iterator. */ table_axis_iterator y_begin(const page_iterator_t &it) { return y_begin(it.pData, it.table_key); }