02e3f2643f
Build Firmware / Build stm32-teslacharger (pull_request) Successful in 1m1s
src/main.cpp:161:16: warning: cannot declare '::main' with a linkage specification [-Wpedantic] Long-standing upstream OpenInverter code (predates SKUDAK). C++ forbids linkage specifications on main; with -pedantic in CPPFLAGS this fires every build. Confirmed safe to drop: - libopencm3 vector.c:35 forward-declares int main(void) with default linkage and invokes it at boot. - Linker resolves main by symbol name, not by linkage attribute. - stm32-sine has the same upstream issue; same fix applies if we ever push upstream. Bump VERSTR -S3 -> -S4 so the OI web UI version field shows the new build. Verified in the built binary as "4=1.20.R-S4". Build: clean, no compiler warnings (only the long-standing linker RWX notice remains, unrelated). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
243 lines
7.8 KiB
C++
243 lines
7.8 KiB
C++
/*
|
||
* This file is part of the stm32-template project.
|
||
*
|
||
* Copyright (C) 2020 Johannes Huebner <dev@johanneshuebner.com>
|
||
*
|
||
* This program is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation, either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
#include <stdint.h>
|
||
#include <libopencm3/stm32/usart.h>
|
||
#ifdef TEST_COMMON_H
|
||
#include "../test/timer_mock.h"
|
||
#else
|
||
#include <libopencm3/stm32/timer.h>
|
||
#include <libopencm3/stm32/rtc.h>
|
||
#endif
|
||
#include <libopencm3/stm32/can.h>
|
||
#include <libopencm3/stm32/iwdg.h>
|
||
#include <libopencm3/stm32/crc.h>
|
||
#include "stm32_can.h"
|
||
#include "cansdo.h"
|
||
#include "terminal.h"
|
||
#include "params.h"
|
||
#include "hwdefs.h"
|
||
#include "digio.h"
|
||
#include "hwinit.h"
|
||
#include "anain.h"
|
||
#include "param_save.h"
|
||
#include "my_math.h"
|
||
#include "errormessage.h"
|
||
#include "printf.h"
|
||
#include "stm32scheduler.h"
|
||
#include "picontroller.h"
|
||
#include "chargercan.h"
|
||
#include "charger.h"
|
||
#include "terminalcommands.h"
|
||
|
||
#define PRINT_JSON 0
|
||
|
||
static Stm32Scheduler* scheduler;
|
||
static CanHardware* can;
|
||
static CanMap* canMap;
|
||
PiController dcCurController;
|
||
uint32_t startTime;
|
||
|
||
//sample 100ms task
|
||
static void Ms100Task(void)
|
||
{
|
||
DigIo::led_out.Toggle();
|
||
//The boot loader enables the watchdog, we have to reset it
|
||
//at least every 2s or otherwise the controller is hard reset.
|
||
iwdg_reset();
|
||
//Calculate CPU load. Don't be surprised if it is zero.
|
||
float cpuLoad = scheduler->GetCpuLoad() / 10.0f;
|
||
//This sets a fixed point value WITHOUT calling the parm_Change() function
|
||
Param::SetFloat(Param::cpuload, cpuLoad);
|
||
//Set timestamp of error message
|
||
ErrorMessage::SetTime(rtc_get_counter_val());
|
||
Param::SetInt(Param::uptime, rtc_get_counter_val());
|
||
Param::SetFloat(Param::uaux, AnaIn::uaux.Get() / 223.418f);
|
||
|
||
ResetValuesInOffMode();
|
||
CalcTotals();
|
||
CalcEnable();
|
||
CalcAcCurrentLimit();
|
||
ChargerStateMachine();
|
||
|
||
EvseRead();
|
||
|
||
canMap->SendAll();
|
||
}
|
||
|
||
static void MapChargerMessages()
|
||
{
|
||
uint32_t dummyId;
|
||
uint8_t dummyOfs;
|
||
int8_t dummyAdd, dummyLen;
|
||
float dummyGain;
|
||
bool dummyrx;
|
||
|
||
//check sample value, if it is mapped assume valid CAN map
|
||
if (canMap->FindMap(Param::hwaclim, dummyId, dummyOfs, dummyLen, dummyGain, dummyAdd, dummyrx)) return;
|
||
|
||
//canMap->Clear();
|
||
|
||
ChargerCAN::MapMessages(canMap);
|
||
|
||
canMap->Save();
|
||
}
|
||
|
||
/** This function is called when the user changes a parameter */
|
||
void Param::Change(Param::PARAM_NUM paramNum)
|
||
{
|
||
s32fp spnt;
|
||
|
||
switch (paramNum)
|
||
{
|
||
case Param::idckp:
|
||
case Param::idcki:
|
||
dcCurController.SetGains(Param::GetInt(Param::idckp), Param::GetInt(Param::idcki));
|
||
break;
|
||
case Param::idclim:
|
||
case Param::idcspnt:
|
||
spnt = MIN(Param::Get(Param::idcspnt), Param::Get(Param::idclim));
|
||
dcCurController.SetRef(spnt);
|
||
break;
|
||
case Param::vcuchglim:
|
||
{
|
||
// SKUDAK-516: VCU sends user-set ChargeLimit_pct on CAN 0x212. Translate
|
||
// percentage to udcspnt (DC voltage setpoint) using a linear cell-voltage
|
||
// ramp. 96-cell pack (Tesla LDU / VW MEB compatible):
|
||
// pct=20 → 3.30 V/cell × 96 = 316.8 V (storage / deep-cycle floor)
|
||
// pct=80 → 3.94 V/cell × 96 = 378.0 V (recommended daily ceiling)
|
||
// pct=100 → 4.15 V/cell × 96 = 398.4 V (matches existing udclim default)
|
||
// Endpoints chosen to be safe across Tesla/VW/Volt2 chemistries — adjust
|
||
// here if a particular pack needs a tighter range. The VCU validates 20-100
|
||
// before transmit so out-of-range values never arrive.
|
||
int pct = Param::GetInt(Param::vcuchglim);
|
||
if (pct < 20) pct = 20;
|
||
if (pct > 100) pct = 100;
|
||
const float CELL_LOW_V = 3.30f;
|
||
const float CELL_HIGH_V = 4.15f;
|
||
const float CELL_COUNT = 96.0f;
|
||
float cell_target = CELL_LOW_V + (CELL_HIGH_V - CELL_LOW_V) * (pct - 20) / 80.0f;
|
||
Param::SetFloat(Param::udcspnt, cell_target * CELL_COUNT);
|
||
break;
|
||
}
|
||
default:
|
||
//Handle general parameter changes here. Add paramNum labels for handling specific parameters
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void HandleClear()
|
||
{
|
||
MapChargerMessages();
|
||
}
|
||
|
||
static bool CanCallback(uint32_t, uint32_t*, uint8_t)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//Whichever timer(s) you use for the scheduler, you have to
|
||
//implement their ISRs here and call into the respective scheduler
|
||
extern "C" void tim2_isr(void)
|
||
{
|
||
scheduler->Run();
|
||
}
|
||
|
||
// C++ forbids a linkage specification on `main` (-Wpedantic). The reset_handler
|
||
// in libopencm3's vector.c forward-declares `int main(void)` with default
|
||
// linkage and resolves it at link time by symbol name, so dropping `extern "C"`
|
||
// here doesn't affect how main is invoked at boot.
|
||
int main(void)
|
||
{
|
||
extern const TERM_CMD termCmds[];
|
||
|
||
clock_setup(); //Must always come first
|
||
rtc_setup();
|
||
ANA_IN_CONFIGURE(ANA_IN_LIST);
|
||
DIG_IO_CONFIGURE(DIG_IO_LIST);
|
||
AnaIn::Start(); //Starts background ADC conversion via DMA
|
||
write_bootloader_pininit(); //Instructs boot loader to initialize certain pins
|
||
gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, AFIO_MAPR_CAN1_REMAP_PORTB);
|
||
tim_setup(); //Use timer3 for sampling pilot PWM
|
||
nvic_setup(); //Set up some interrupts
|
||
parm_load(); //Load stored parameters
|
||
Param::Change(Param::idckp); //Call callback once for parameter propagation
|
||
Param::Change(Param::idclim); //Call callback once for parameter propagation
|
||
|
||
Stm32Scheduler s(TIM2); //We never exit main so it's ok to put it on stack
|
||
scheduler = &s;
|
||
//Initialize CAN1, including interrupts. Clock must be enabled in clock_setup()
|
||
Stm32Can c(CAN1, CanHardware::Baud500, true);
|
||
FunctionPointerCallback cb(CanCallback, HandleClear);
|
||
c.AddCallback(&cb);
|
||
can = &c;
|
||
|
||
//store a pointer for easier access
|
||
CanMap cm(&c);
|
||
canMap = &cm;
|
||
CanSdo sdo(&c, &cm);
|
||
sdo.SetNodeId(2);
|
||
|
||
TerminalCommands::SetCanMap(&cm);
|
||
|
||
Terminal t3(USART3, termCmds);
|
||
Terminal t1(USART1, termCmds);
|
||
|
||
MapChargerMessages();
|
||
dcCurController.SetCallingFrequency(10);
|
||
|
||
//Up to four tasks can be added to each timer scheduler
|
||
//AddTask takes a function pointer and a calling interval in milliseconds.
|
||
//The longest interval is 655ms due to hardware restrictions
|
||
//You have to enable the interrupt (int this case for TIM2) in nvic_setup()
|
||
//There you can also configure the priority of the scheduler over other interrupts
|
||
s.AddTask(Ms100Task, 100);
|
||
|
||
//backward compatibility, version 4 was the first to support the "stream" command
|
||
Param::SetInt(Param::version, 4);
|
||
Param::SetFlag(Param::test_time, Param::FLAG_HIDDEN);
|
||
Param::SetFlag(Param::test_timer_flag, Param::FLAG_HIDDEN);
|
||
Param::SetFlag(Param::test_timer_icvalue, Param::FLAG_HIDDEN);
|
||
|
||
//In version 1.11 this changed from mV to V
|
||
if (Param::GetInt(Param::udcspnt) > 420)
|
||
{
|
||
Param::SetFloat(Param::udcspnt, Param::GetFloat(Param::udcspnt) / 1000);
|
||
}
|
||
|
||
//Now all our main() does is running the terminal
|
||
//All other processing takes place in the scheduler or other interrupt service routines
|
||
//The terminal has lowest priority, so even loading it down heavily will not disturb
|
||
//our more important processing routines.
|
||
while(1)
|
||
{
|
||
t1.Run();
|
||
t3.Run();
|
||
|
||
if (sdo.GetPrintRequest() == PRINT_JSON)
|
||
{
|
||
char c = 0;
|
||
TerminalCommands::PrintParamsJson(&sdo, &c);
|
||
}
|
||
}
|
||
|
||
|
||
return 0;
|
||
}
|
||
|