Twizy-Cfg/TwizyCfg/Tuning.ino

1360 lines
36 KiB
C++

/**
* ==========================================================================
* Twizy/SEVCON configuration shell
* ==========================================================================
*
* Twizy/SEVCON tuning functions
*
* Based on the OVMS Twizy firmware:
* https://github.com/openvehicles/Open-Vehicle-Monitoring-System
*
* Author: Michael Balzer <dexter@dexters-web.de>
*
* License:
* This is free software under GNU Lesser General Public License (LGPL)
* https://www.gnu.org/licenses/lgpl.html
*
*/
#include <EEPROM.h>
#include "utils.h"
#include "CANopen.h"
#include "Tuning.h"
// SEVCON configuration status:
cfg_status twizy_cfg;
// SEVCON macro configuration profile:
cfg_profile twizy_cfg_profile;
unsigned int twizy_max_rpm; // CFG: max speed (RPM: 0..11000)
unsigned long twizy_max_trq; // CFG: max torque (mNm: 0..70125)
unsigned int twizy_max_pwr_lo; // CFG: max power low speed (W: 0..17000)
unsigned int twizy_max_pwr_hi; // CFG: max power high speed (W: 0..17000)
UINT8 twizy_autorecup_checkpoint; // change detection for autorecup function
UINT twizy_autorecup_level; // autorecup: current recup level (per mille)
UINT8 twizy_autodrive_checkpoint; // change detection for autopower function
UINT twizy_autodrive_level; // autopower: current drive level (per mille)
/***********************************************************************
* COMMAND CLASS: SEVCON CONTROLLER CONFIGURATION
*
* MSG: ...todo...
* SMS: CFG [cmd]
*
*/
struct twizy_cfg_params {
UINT8 DefaultKphMax;
UINT DefaultRpmMax;
UINT DefaultRpmRev;
UINT DeltaBrkStart;
UINT DeltaBrkEnd;
UINT DeltaBrkDown;
UINT8 DefaultKphWarn;
UINT DefaultRpmWarn;
UINT DeltaWarnOff;
UINT DefaultTrq;
UINT DefaultTrqRated;
UINT32 DefaultTrqLim;
UINT DeltaMapTrq;
UINT32 DefaultCurrLim;
UINT DefaultCurrStatorMax;
UINT BoostCurr;
UINT DefaultFMAP[4]; // only upper 2 points needed
UINT ExtendedFMAP[4]; // only upper 2 points needed
UINT DefaultPwrLo;
UINT DefaultPwrLoLim;
UINT DefaultPwrHi;
UINT DefaultPwrHiLim;
UINT DefaultMaxMotorPwr;
UINT8 DefaultRecup;
UINT8 DefaultRecupPrc;
UINT DefaultRampStart;
UINT8 DefaultRampStartPrm;
UINT DefaultRampAccel;
UINT8 DefaultRampAccelPrc;
UINT DefaultPMAP[18];
UINT DefaultMapSpd[4];
};
struct twizy_cfg_params twizy_cfg_params[2] =
{
{
//
// CFG[0] = TWIZY80:
//
// 55 Nm (0x4611.0x01) = default max power map (PMAP) torque
// 55 Nm (0x6076.0x00) = default peak torque
// 57 Nm (0x2916.0x01) = rated torque (??? should be equal to 0x6076...)
// 70.125 Nm (0x4610.0x11) = max motor torque according to flux map
//
// 7250 rpm (0x2920.0x05) = default max fwd speed = ~80 kph
// 8050 rpm = default overspeed warning trigger (STOP lamp ON) = ~89 kph
// 8500 rpm = default overspeed brakedown trigger = ~94 kph
// 10000 rpm = max neutral speed (0x3813.2d) = ~110 kph
// 11000 rpm = severe overspeed fault (0x4624.00) = ~121 kph
80, // DefaultKphMax
7250, // DefaultRpmMax
900, // DefaultRpmMaxRev
400, // DeltaBrkStart
800, // DeltaBrkEnd
1250, // DeltaBrkDown
89, // DefaultKphWarn
8050, // DefaultRpmWarn
550, // DeltaWarnOff
55000, // DefaultTrq
57000, // DefaultTrqRated
70125, // DefaultTrqLim
0, // DeltaMapTrq
450000, // DefaultCurrLim
450, // DefaultCurrStatorMax
540, // BoostCurr
{ 964, 9728, 1122, 9984 }, // DefaultFMAP
{ 1122, 10089, 2240, 11901 }, // ExtendedFMAP
12182, // DefaultPwrLo
17000, // DefaultPwrLoLim
13000, // DefaultPwrHi
17000, // DefaultPwrHiLim
4608, // DefaultMaxMotorPwr [1/256 kW]
182, // DefaultRecup
18, // DefaultRecupPrc
400, // DefaultRampStart
40, // DefaultRampStartPrm
2500, // DefaultRampAccel
25, // DefaultRampAccelPrc
// DefaultPMAP:
{ 880,0, 880,2115, 659,2700, 608,3000, 516,3500,
421,4500, 360,5500, 307,6500, 273,7250 },
// DefaultMapSpd:
{ 3000, 3500, 4500, 6000 }
},
{
//
// CFG[1] = TWIZY45:
//
// 32.5 Nm (0x4611.0x01) = default max power map (PMAP) torque
// 33 Nm (0x6076.0x00) = default peak torque
// 33 Nm (0x2916.0x01) = rated torque (??? should be equal to 0x6076...)
// 36 Nm (0x4610.0x11) = max motor torque according to flux map
//
// 5814 rpm (0x2920.0x05) = default max fwd speed = ~45 kph
// 7200 rpm = default overspeed warning trigger (STOP lamp ON) = ~56 kph
// 8500 rpm = default overspeed brakedown trigger = ~66 kph
// 10000 rpm = max neutral speed (0x3813.2d) = ~77 kph
// 11000 rpm = severe overspeed fault (0x4624.00) = ~85 kph
45, // DefaultKphMax
5814, // DefaultRpmMax
1307, // DefaultRpmMaxRev
686, // DeltaBrkStart
1386, // DeltaBrkEnd
2686, // DeltaBrkDown
56, // DefaultKphWarn
7200, // DefaultRpmWarn
900, // DeltaWarnOff
32500, // DefaultTrq
33000, // DefaultTrqRated
36000, // DefaultTrqLim
500, // DeltaMapTrq
270000, // DefaultCurrLim
290, // DefaultCurrStatorMax
330, // BoostCurr
{ 480, 8192, 576, 8960 }, // DefaultFMAP
{ 656, 9600, 1328, 11901 }, // ExtendedFMAP
7050, // DefaultPwrLo
10000, // DefaultPwrLoLim
7650, // DefaultPwrHi
10000, // DefaultPwrHiLim
2688, // DefaultMaxMotorPwr [1/256 kW]
209, // DefaultRecup
21, // DefaultRecupPrc
300, // DefaultRampStart
30, // DefaultRampStartPrm
2083, // DefaultRampAccel
21, // DefaultRampAccelPrc
// DefaultPMAP:
{ 520,0, 520,2050, 437,2500, 363,3000, 314,3500,
279,4000, 247,4500, 226,5000, 195,6000 },
// DefaultMapSpd:
{ 4357, 5083, 6535, 8714 }
}
};
// ROM parameter access macro:
#define CFG twizy_cfg_params[twizy_cfg.type]
// scale utility:
// returns <deflt> value scaled <from> <to> limited by <min> and <max>
UINT32 scale(UINT32 deflt, UINT32 from, UINT32 to, UINT32 min, UINT32 max)
{
UINT32 val;
if (to == from)
return deflt;
val = (deflt * to) / from;
if (val < min)
return min;
else if (val > max)
return max;
else
return val;
}
// vehicle_twizy_cfg_makepowermap():
// Internal utility for cfg_speed() and cfg_power()
// builds torque/speed curve (SDO 0x4611) for current max values...
// - twizy_max_rpm
// - twizy_max_trq
// - twizy_max_pwr_lo
// - twizy_max_pwr_hi
// See "Twizy Powermap Calculator" spreadsheet for details.
UINT8 pmap_fib[7] = { 1, 2, 3, 5, 8, 13, 21 };
UINT vehicle_twizy_cfg_makepowermap(void)
/* twizy_max_rpm, twizy_max_trq, twizy_max_pwr_lo, twizy_max_pwr_hi */
{
UINT err;
UINT8 i;
UINT rpm_0, rpm;
UINT trq;
UINT pwr;
int rpm_d, pwr_d;
if (twizy_max_rpm == CFG.DefaultRpmMax && twizy_max_trq == CFG.DefaultTrq
&& twizy_max_pwr_lo == CFG.DefaultPwrLo && twizy_max_pwr_hi == CFG.DefaultPwrHi) {
// restore default torque map:
for (i=0; i<18; i++) {
if (err = writesdo(0x4611,0x01+i,CFG.DefaultPMAP[i]))
return err;
}
// restore default flux map (only last 2 points):
for (i=0; i<4; i++) {
if (err = writesdo(0x4610,0x0f+i,CFG.DefaultFMAP[i]))
return err;
}
}
else {
// calculate constant torque part:
rpm_0 = (((UINT32)twizy_max_pwr_lo * 9549 + (twizy_max_trq>>1)) / twizy_max_trq);
trq = (twizy_max_trq * 16 + 500) / 1000;
if (err = writesdo(0x4611,0x01,trq))
return err;
if (err = writesdo(0x4611,0x02,0))
return err;
if (err = writesdo(0x4611,0x03,trq))
return err;
if (err = writesdo(0x4611,0x04,rpm_0))
return err;
// adjust flux map (only last 2 points):
if (trq > CFG.DefaultFMAP[2]) {
for (i=0; i<4; i++) {
if (err = writesdo(0x4610,0x0f+i,CFG.ExtendedFMAP[i]))
return err;
}
}
else {
for (i=0; i<4; i++) {
if (err = writesdo(0x4610,0x0f+i,CFG.DefaultFMAP[i]))
return err;
}
}
// calculate constant power part:
if (twizy_max_rpm > rpm_0)
rpm_d = (twizy_max_rpm - rpm_0 + (pmap_fib[6]>>1)) / pmap_fib[6];
else
rpm_d = 0;
pwr_d = ((int)twizy_max_pwr_hi - (int)twizy_max_pwr_lo + (pmap_fib[5]>>1)) / pmap_fib[5];
for (i=0; i<7; i++) {
if (i<6)
rpm = rpm_0 + (pmap_fib[i] * rpm_d);
else
rpm = twizy_max_rpm;
if (i<5)
pwr = twizy_max_pwr_lo + (pmap_fib[i] * pwr_d);
else
pwr = twizy_max_pwr_hi;
trq = ((((UINT32)pwr * 9549 + (rpm>>1)) / rpm) * 16 + 500) / 1000;
if (err = writesdo(0x4611,0x05+(i<<1),trq))
return err;
if (err = writesdo(0x4611,0x06+(i<<1),rpm))
return err;
}
}
// commit map changes:
if (err = writesdo(0x4641,0x01,1))
return err;
delay5(10);
return 0;
}
// vehicle_twizy_cfg_readmaxpwr:
// read twizy_max_pwr_lo & twizy_max_pwr_hi
// from SEVCON registers (only if necessary / undefined)
UINT vehicle_twizy_cfg_readmaxpwr(void)
{
UINT err;
UINT rpm;
// the controller does not store max power, derive it from rpm/trq:
if (twizy_max_pwr_lo == 0) {
if (err = readsdo(0x4611,0x04))
return err;
rpm = twizy_sdo.data;
if (err = readsdo(0x4611,0x03))
return err;
if (twizy_sdo.data == CFG.DefaultPMAP[2] && rpm == CFG.DefaultPMAP[3])
twizy_max_pwr_lo = CFG.DefaultPwrLo;
else
twizy_max_pwr_lo = (UINT)((((twizy_sdo.data*1000)>>4) * rpm + (9549>>1)) / 9549);
}
if (twizy_max_pwr_hi == 0) {
if (err = readsdo(0x4611,0x12))
return err;
rpm = twizy_sdo.data;
if (err = readsdo(0x4611,0x11))
return err;
if (twizy_sdo.data == CFG.DefaultPMAP[16] && rpm == CFG.DefaultPMAP[17])
twizy_max_pwr_hi = CFG.DefaultPwrHi;
else
twizy_max_pwr_hi = (UINT)((((twizy_sdo.data*1000)>>4) * rpm + (9549>>1)) / 9549);
}
return 0;
}
UINT vehicle_twizy_cfg_speed(int max_kph, int warn_kph)
// max_kph: 6..?, -1=reset to default (80)
// warn_kph: 6..?, -1=reset to default (89)
{
UINT err;
UINT rpm;
CHECKPOINT (41)
// parameter validation:
if (max_kph == -1)
max_kph = CFG.DefaultKphMax;
else if (max_kph < 6)
return ERR_Range + 1;
if (warn_kph == -1)
warn_kph = CFG.DefaultKphWarn;
else if (warn_kph < 6)
return ERR_Range + 2;
// get max torque for map scaling:
if (twizy_max_trq == 0) {
if (err = readsdo(0x6076,0x00))
return err;
twizy_max_trq = twizy_sdo.data - CFG.DeltaMapTrq;
}
// get max power for map scaling:
if (err = vehicle_twizy_cfg_readmaxpwr())
return err;
// set overspeed warning range (STOP lamp):
rpm = scale(CFG.DefaultRpmWarn,CFG.DefaultKphWarn,warn_kph,400,65535);
if (err = writesdo(0x3813,0x34,rpm)) // lamp ON
return err;
if (err = writesdo(0x3813,0x3c,rpm-CFG.DeltaWarnOff)) // lamp OFF
return err;
// calc fwd rpm:
rpm = scale(CFG.DefaultRpmMax,CFG.DefaultKphMax,max_kph,400,65535);
// set fwd rpm:
err = writesdo(0x2920,0x05,rpm);
if (err)
return err;
// set rev rpm:
err = writesdo(0x2920,0x06,LIMIT_MAX(rpm,CFG.DefaultRpmRev));
if (err)
return err;
// adjust overspeed braking points (using fixed offsets):
if (err = writesdo(0x3813,0x33,rpm+CFG.DeltaBrkStart)) // neutral braking start
return err;
if (err = writesdo(0x3813,0x35,rpm+CFG.DeltaBrkEnd)) // neutral braking end
return err;
if (err = writesdo(0x3813,0x3b,rpm+CFG.DeltaBrkDown)) // drive brakedown trigger
return err;
// adjust overspeed limits:
if (err = writesdo(0x3813,0x2d,rpm+CFG.DeltaBrkDown+1500)) // neutral max speed
return err;
if (err = writesdo(0x4624,0x00,rpm+CFG.DeltaBrkDown+2500)) // severe overspeed fault
return err;
twizy_max_rpm = rpm;
return 0;
}
UINT vehicle_twizy_cfg_power(int trq_prc, int pwr_lo_prc, int pwr_hi_prc, int curr_prc)
// See "Twizy Powermap Calculator" spreadsheet.
// trq_prc: 10..130, -1=reset to default (100%)
// i.e. TWIZY80:
// 100% = 55.000 Nm
// 128% = 70.125 Nm (130 allowed for easy handling)
// pwr_lo_prc: 10..139, -1=reset to default (100%)
// 100% = 12182 W (mechanical)
// 139% = 16933 W (mechanical)
// pwr_hi_prc: 10..130, -1=reset to default (100%)
// 100% = 13000 W (mechanical)
// 130% = 16900 W (mechanical)
// curr_prc: 10..123, -1=reset to default current limits
// 100% = 450 A (Twizy 45: 270 A)
// 120% = 540 A (Twizy 45: 324 A)
// 123% = 540 A (Twizy 45: 330 A)
// setting this to any value disables high limits of trq & pwr
// (=> flux map extended to enable higher torque)
// safety max limits = SEVCON model specific boost current level
{
UINT err;
BOOL limited = FALSE;
// parameter validation:
if (curr_prc == -1) {
curr_prc = 100;
limited = TRUE;
}
else if (curr_prc < 10 || curr_prc > 123)
return ERR_Range + 4;
if (trq_prc == -1)
trq_prc = 100;
else if (trq_prc < 10 || (limited && trq_prc > 130))
return ERR_Range + 1;
if (pwr_lo_prc == -1)
pwr_lo_prc = 100;
else if (pwr_lo_prc < 10 || (limited && pwr_lo_prc > 139))
return ERR_Range + 2;
if (pwr_hi_prc == -1)
pwr_hi_prc = 100;
else if (pwr_hi_prc < 10 || (limited && pwr_hi_prc > 130))
return ERR_Range + 3;
// get max fwd rpm for map scaling:
if (twizy_max_rpm == 0) {
if (err = readsdo(0x2920,0x05))
return err;
twizy_max_rpm = twizy_sdo.data;
}
// set current limits:
if (err = writesdo(0x4641,0x02,scale(
CFG.DefaultCurrStatorMax,100,curr_prc,0,CFG.BoostCurr)))
return err;
if (err = writesdo(0x6075,0x00,scale(
CFG.DefaultCurrLim,100,curr_prc,0,CFG.BoostCurr*1000L)))
return err;
// calc peak use torque:
twizy_max_trq = scale(CFG.DefaultTrq,100,trq_prc,10000,
(limited) ? CFG.DefaultTrqLim : 200000);
// set peak use torque:
if (err = writesdo(0x6076,0x00,twizy_max_trq + CFG.DeltaMapTrq))
return err;
// set rated torque:
if (err = writesdo(0x2916,0x01,(trq_prc==100)
? CFG.DefaultTrqRated
: (twizy_max_trq + CFG.DeltaMapTrq)))
return err;
// calc peak use power:
twizy_max_pwr_lo = scale(CFG.DefaultPwrLo,100,pwr_lo_prc,500,
(limited) ? CFG.DefaultPwrLoLim : 200000);
twizy_max_pwr_hi = scale(CFG.DefaultPwrHi,100,pwr_hi_prc,500,
(limited) ? CFG.DefaultPwrHiLim : 200000);
// set motor max power:
if (err = writesdo(0x3813,0x23,(pwr_lo_prc==100 && pwr_hi_prc==100)
? CFG.DefaultMaxMotorPwr
: MAX(twizy_max_pwr_lo,twizy_max_pwr_hi)*0.353))
return err;
return 0;
}
UINT vehicle_twizy_cfg_tsmap(
char map,
INT8 t1_prc, INT8 t2_prc, INT8 t3_prc, INT8 t4_prc,
int t1_spd, int t2_spd, int t3_spd, int t4_spd)
// map: 'D'=Drive 'N'=Neutral 'B'=Footbrake
//
// t1_prc: 0..100, -1=reset to default (D=100, N/B=100)
// t2_prc: 0..100, -1=reset to default (D=100, N/B=80)
// t3_prc: 0..100, -1=reset to default (D=100, N/B=50)
// t4_prc: 0..100, -1=reset to default (D=100, N/B=20)
//
// t1_spd: 0..?, -1=reset to default (D/N/B=33)
// t2_spd: 0..?, -1=reset to default (D/N/B=39)
// t3_spd: 0..?, -1=reset to default (D/N/B=50)
// t4_spd: 0..?, -1=reset to default (D/N/B=66)
{
UINT err;
UINT8 base;
UINT val, bndlo, bndhi;
UINT8 todo;
// parameter validation:
if (map != 'D' && map != 'N' && map != 'B')
return ERR_Range + 1;
// torque points:
if ((t1_prc != -1) && (t1_prc < 0 || t1_prc > 100))
return ERR_Range + 2;
if ((t2_prc != -1) && (t2_prc < 0 || t2_prc > 100))
return ERR_Range + 3;
if ((t3_prc != -1) && (t3_prc < 0 || t3_prc > 100))
return ERR_Range + 4;
if ((t4_prc != -1) && (t4_prc < 0 || t4_prc > 100))
return ERR_Range + 5;
// speed points:
if ((t1_spd != -1) && (t1_spd < 0))
return ERR_Range + 6;
if ((t2_spd != -1) && (t2_spd < 0))
return ERR_Range + 7;
if ((t3_spd != -1) && (t3_spd < 0))
return ERR_Range + 8;
if ((t4_spd != -1) && (t4_spd < 0))
return ERR_Range + 9;
// get map base subindex in SDO 0x3813:
if (map=='B')
base = 0x07;
else if (map=='N')
base = 0x1b;
else // 'D'
base = 0x24;
// set:
// we need to adjust point by point to avoid the "Param dyn range" alert,
// ensuring a new speed has no conflict with the previous surrounding points
todo = 0x0f;
while (todo) {
// point 1:
if (todo & 0x01) {
// get speed boundaries:
bndlo = 0;
if (err = readsdo(0x3813,base+3))
return err;
bndhi = twizy_sdo.data;
// calc new speed:
if (t1_spd >= 0)
val = scale(CFG.DefaultMapSpd[0],33,t1_spd,0,65535);
else
val = (map=='D') ? 3000 : CFG.DefaultMapSpd[0];
if (val >= bndlo && val <= bndhi) {
// ok, change:
if (err = writesdo(0x3813,base+1,val))
return err;
if (t1_prc >= 0)
val = scale(32767,100,t1_prc,0,32767);
else
val = 32767;
if (err = writesdo(0x3813,base+0,val))
return err;
todo &= ~0x01;
}
}
// point 2:
if (todo & 0x02) {
// get speed boundaries:
if (err = readsdo(0x3813,base+1))
return err;
bndlo = twizy_sdo.data;
if (err = readsdo(0x3813,base+5))
return err;
bndhi = twizy_sdo.data;
// calc new speed:
if (t2_spd >= 0)
val = scale(CFG.DefaultMapSpd[1],39,t2_spd,0,65535);
else
val = (map=='D') ? 3500 : CFG.DefaultMapSpd[1];
if (val >= bndlo && val <= bndhi) {
// ok, change:
if (err = writesdo(0x3813,base+3,val))
return err;
if (t2_prc >= 0)
val = scale(32767,100,t2_prc,0,32767);
else
val = (map=='D') ? 32767 : 26214;
if (err = writesdo(0x3813,base+2,val))
return err;
todo &= ~0x02;
}
}
// point 3:
if (todo & 0x04) {
// get speed boundaries:
if (err = readsdo(0x3813,base+3))
return err;
bndlo = twizy_sdo.data;
if (err = readsdo(0x3813,base+7))
return err;
bndhi = twizy_sdo.data;
// calc new speed:
if (t3_spd >= 0)
val = scale(CFG.DefaultMapSpd[2],50,t3_spd,0,65535);
else
val = (map=='D') ? 4500 : CFG.DefaultMapSpd[2];
if (val >= bndlo && val <= bndhi) {
// ok, change:
if (err = writesdo(0x3813,base+5,val))
return err;
if (t3_prc >= 0)
val = scale(32767,100,t3_prc,0,32767);
else
val = (map=='D') ? 32767 : 16383;
if (err = writesdo(0x3813,base+4,val))
return err;
todo &= ~0x04;
}
}
// point 4:
if (todo & 0x08) {
// get speed boundaries:
if (err = readsdo(0x3813,base+5))
return err;
bndlo = twizy_sdo.data;
bndhi = 65535;
// calc new speed:
if (t4_spd >= 0)
val = scale(CFG.DefaultMapSpd[3],66,t4_spd,0,65535);
else
val = (map=='D') ? 6000 : CFG.DefaultMapSpd[3];
if (val >= bndlo && val <= bndhi) {
// ok, change:
if (err = writesdo(0x3813,base+7,val))
return err;
if (t4_prc >= 0)
val = scale(32767,100,t4_prc,0,32767);
else
val = (map=='D') ? 32767 : 6553;
if (err = writesdo(0x3813,base+6,val))
return err;
todo &= ~0x08;
}
}
} // while (todo)
return 0;
}
UINT vehicle_twizy_cfg_drive(int max_prc, int autodrive_ref, int autodrive_minprc)
// max_prc: 10..100, -1=reset to default (100)
// autodrive_ref: 0..250, -1=default (off) (not a direct SEVCON control)
// sets power 100% ref point to <autodrive_ref> * 100 W
// autodrive_minprc: 0..100, -1=default (off) (not a direct SEVCON control)
// sets lower limit on auto power adjustment
{
UINT err;
// parameter validation:
if (max_prc == -1)
max_prc = 100;
else if (max_prc < 10 || max_prc > 100)
return ERR_Range + 1;
#ifdef OVMS_TWIZY_BATTMON
if (autodrive_ref == -1)
;
else if (autodrive_ref < 0 || autodrive_ref > 250)
return ERR_Range + 2;
if (autodrive_minprc == -1)
autodrive_minprc = 0;
else if (autodrive_minprc < 0 || autodrive_minprc > 100)
return ERR_Range + 3;
if ((autodrive_ref > 0) && (!sys_can.DisableAutoPower))
{
// calculate max drive level:
twizy_autodrive_level = LIMIT_MAX(((long) twizy_batt[0].max_drive_pwr * 5L * 1000L)
/ autodrive_ref, 1000);
twizy_autodrive_level = LIMIT_MIN(twizy_autodrive_level, autodrive_minprc * 10);
// set autopower checkpoint:
twizy_autodrive_checkpoint = (twizy_batt[0].max_drive_pwr + autodrive_ref + autodrive_minprc);
}
else
{
twizy_autodrive_level = 1000;
}
#else
twizy_autodrive_level = 1000;
#endif //OVMS_TWIZY_BATTMON
// set:
if (err = writesdo(0x2920,0x01,LIMIT_MAX(max_prc*10, twizy_autodrive_level)))
return err;
return 0;
}
UINT vehicle_twizy_cfg_recup(int neutral_prc, int brake_prc, int autorecup_ref, int autorecup_minprc)
// neutral_prc: 0..100, -1=reset to default (18)
// brake_prc: 0..100, -1=reset to default (18)
// autorecup_ref: 0..250, -1=default (off) (not a direct SEVCON control)
// ATT: parameter function changed in V3.6.0!
// now sets power 100% ref point to <autorecup_ref> * 100 W
// autorecup_minprc: 0..100, -1=default (off) (not a direct SEVCON control)
// sets lower limit on auto power adjustment
{
UINT err;
UINT level;
// parameter validation:
if (neutral_prc == -1)
neutral_prc = CFG.DefaultRecupPrc;
else if (neutral_prc < 0 || neutral_prc > 100)
return ERR_Range + 1;
if (brake_prc == -1)
brake_prc = CFG.DefaultRecupPrc;
else if (brake_prc < 0 || brake_prc > 100)
return ERR_Range + 2;
#ifdef OVMS_TWIZY_BATTMON
if (autorecup_ref == -1)
;
else if (autorecup_ref < 0 || autorecup_ref > 250)
return ERR_Range + 3;
if (autorecup_minprc == -1)
autorecup_minprc = 0;
else if (autorecup_minprc < 0 || autorecup_minprc > 100)
return ERR_Range + 4;
if ((autorecup_ref > 0) && (!sys_can.DisableAutoPower))
{
// calculate max recuperation level:
twizy_autorecup_level = LIMIT_MAX(((long) twizy_batt[0].max_recup_pwr * 5L * 1000L)
/ autorecup_ref, 1000);
twizy_autorecup_level = LIMIT_MIN(twizy_autorecup_level, autorecup_minprc * 10);
// set autopower checkpoint:
twizy_autorecup_checkpoint = (twizy_batt[0].max_recup_pwr + autorecup_ref + autorecup_minprc);
}
else
{
twizy_autorecup_level = 1000;
}
#else
twizy_autorecup_level = 1000;
#endif //OVMS_TWIZY_BATTMON
// set neutral recup level:
level = scale(CFG.DefaultRecup,CFG.DefaultRecupPrc,neutral_prc,0,1000);
if (twizy_autorecup_level != 1000)
level = (((long) level) * twizy_autorecup_level) / 1000;
if (err = writesdo(0x2920,0x03,level))
return err;
// set brake recup level:
level = scale(CFG.DefaultRecup,CFG.DefaultRecupPrc,brake_prc,0,1000);
if (twizy_autorecup_level != 1000)
level = (((long) level) * twizy_autorecup_level) / 1000;
if (err = writesdo(0x2920,0x04,level))
return err;
return 0;
}
#ifdef OVMS_TWIZY_BATTMON
// Auto recup & drive power update function:
// check for BMS max pwr change, update SEVCON settings accordingly
// this is called by vehicle_twizy_state_ticker1() = approx. once per second
void vehicle_twizy_cfg_autopower(void)
{
int ref, minprc;
// check for SEVCON write access:
if ((!sys_can.EnableWrite) || (sys_can.DisableAutoPower)
|| ((twizy_status & CAN_STATUS_KEYON) == 0))
return;
// adjust recup levels?
ref = cfgparam(autorecup_ref);
minprc = cfgparam(autorecup_minprc);
if ((ref > 0)
&& ((twizy_batt[0].max_recup_pwr + ref + minprc) != twizy_autorecup_checkpoint))
{
vehicle_twizy_cfg_recup(cfgparam(neutral), cfgparam(brake), ref, minprc);
}
// adjust drive level?
ref = cfgparam(autodrive_ref);
minprc = cfgparam(autodrive_minprc);
if ((ref > 0)
&& ((twizy_batt[0].max_drive_pwr + ref + minprc) != twizy_autodrive_checkpoint)
&& (twizy_kickdown_hold == 0))
{
vehicle_twizy_cfg_drive(cfgparam(drive), ref, minprc);
}
}
#endif //OVMS_TWIZY_BATTMON
UINT vehicle_twizy_cfg_ramps(int start_prm, int accel_prc, int decel_prc, int neutral_prc, int brake_prc)
// start_prm: 1..250, -1=reset to default (40) (Att! changed in V3.4 from prc to prm!)
// accel_prc: 1..100, -1=reset to default (25)
// decel_prc: 0..100, -1=reset to default (20)
// neutral_prc: 0..100, -1=reset to default (40)
// brake_prc: 0..100, -1=reset to default (40)
{
UINT err;
// parameter validation:
if (start_prm == -1)
start_prm = CFG.DefaultRampStartPrm;
else if (start_prm < 1 || start_prm > 250)
return ERR_Range + 1;
if (accel_prc == -1)
accel_prc = CFG.DefaultRampAccelPrc;
else if (accel_prc < 1 || accel_prc > 100)
return ERR_Range + 2;
if (decel_prc == -1)
decel_prc = 20;
else if (decel_prc < 0 || decel_prc > 100)
return ERR_Range + 3;
if (neutral_prc == -1)
neutral_prc = 40;
else if (neutral_prc < 0 || neutral_prc > 100)
return ERR_Range + 4;
if (brake_prc == -1)
brake_prc = 40;
else if (brake_prc < 0 || brake_prc > 100)
return ERR_Range + 5;
// set:
if (err = writesdo(0x291c,0x02,scale(CFG.DefaultRampStart,CFG.DefaultRampStartPrm,start_prm,10,10000)))
return err;
if (err = writesdo(0x2920,0x07,scale(CFG.DefaultRampAccel,CFG.DefaultRampAccelPrc,accel_prc,10,10000)))
return err;
if (err = writesdo(0x2920,0x0b,scale(2000,20,decel_prc,10,10000)))
return err;
if (err = writesdo(0x2920,0x0d,scale(4000,40,neutral_prc,10,10000)))
return err;
if (err = writesdo(0x2920,0x0e,scale(4000,40,brake_prc,10,10000)))
return err;
return 0;
}
UINT vehicle_twizy_cfg_rampl(int accel_prc, int decel_prc)
// accel_prc: 1..100, -1=reset to default (30)
// decel_prc: 0..100, -1=reset to default (30)
{
UINT err;
// parameter validation:
if (accel_prc == -1)
accel_prc = 30;
else if (accel_prc < 1 || accel_prc > 100)
return ERR_Range + 1;
if (decel_prc == -1)
decel_prc = 30;
else if (decel_prc < 0 || decel_prc > 100)
return ERR_Range + 2;
// set:
if (err = writesdo(0x2920,0x0f,scale(6000,30,accel_prc,0,20000)))
return err;
if (err = writesdo(0x2920,0x10,scale(6000,30,decel_prc,0,20000)))
return err;
return 0;
}
UINT vehicle_twizy_cfg_smoothing(int prc)
// prc: 0..100, -1=reset to default (70)
{
UINT err;
// parameter validation:
if (prc == -1)
prc = 70;
else if (prc < 0 || prc > 100)
return ERR_Range + 1;
// set:
if (err = writesdo(0x290a,0x01,1+(prc/10)))
return err;
if (err = writesdo(0x290a,0x03,scale(800,70,prc,0,1000)))
return err;
return 0;
}
#ifdef OVMS_TWIZY_CFG_BRAKELIGHT
UINT vehicle_twizy_cfg_brakelight(int on_lev, int off_lev)
// *** NOT FUNCTIONAL WITHOUT HARDWARE MODIFICATION ***
// *** SEVCON cannot control Twizy brake lights ***
// on_lev: 0..100, -1=reset to default (100=off)
// off_lev: 0..100, -1=reset to default (100=off)
// on_lev must be >= off_lev
// ctrl bit in 0x2910.1 will be set/cleared accordingly
{
UINT err;
// parameter validation:
if (on_lev == -1)
on_lev = 100;
else if (on_lev < 0 || on_lev > 100)
return ERR_Range + 1;
if (off_lev == -1)
off_lev = 100;
else if (off_lev < 0 || off_lev > 100)
return ERR_Range + 2;
if (on_lev < off_lev)
return ERR_Range + 3;
// set range:
if (err = writesdo(0x3813,0x05,scale(1024,100,off_lev,64,1024)))
return err;
if (err = writesdo(0x3813,0x06,scale(1024,100,on_lev,64,1024)))
return err;
// set ctrl bit:
if (err = readsdo(0x2910,0x01))
return err;
if (on_lev != 100 || off_lev != 100)
twizy_sdo.data |= 0x2000;
else
twizy_sdo.data &= ~0x2000;
if (err = writesdo(0x2910,0x01,twizy_sdo.data))
return err;
return 0;
}
#endif // OVMS_TWIZY_CFG_BRAKELIGHT
// vehicle_twizy_cfg_calc_checksum: get checksum for twizy_cfg_profile
//
// Note: for extendability of struct twizy_cfg_profile, 0-Bytes will
// not affect the checksum, so new fields can simply be added at the end
// without losing version compatibility.
// (0-bytes translate to value -1 = default)
BYTE vehicle_twizy_cfg_calc_checksum(BYTE *profile)
{
UINT checksum;
UINT8 i;
checksum = 0x0101; // version tag
for (i=1; i<sizeof(twizy_cfg_profile); i++)
checksum += profile[i];
if ((checksum & 0x0ff) == 0)
checksum >>= 8;
return (checksum & 0x0ff);
}
// vehicle_twizy_cfg_readprofile: read profile from params to twizy_cgf_profile
// with checksum validation
// it invalid checksum initialize to default config & return FALSE
BOOL vehicle_twizy_cfg_readprofile(UINT8 key)
{
if (key >= 1 && key <= 3) {
// read custom cfg from params:
//par_getbin(PARAM_PROFILE_S + ((key-1)<<1), &twizy_cfg_profile, sizeof twizy_cfg_profile);
EEPROM.get(key * 64, twizy_cfg_profile);
// check consistency:
if (twizy_cfg_profile.checksum == vehicle_twizy_cfg_calc_checksum((BYTE *)&twizy_cfg_profile))
return TRUE;
else {
// init to defaults:
memset(&twizy_cfg_profile, 0, sizeof(twizy_cfg_profile));
return FALSE;
}
}
else {
// no custom cfg: load defaults
memset(&twizy_cfg_profile, 0, sizeof(twizy_cfg_profile));
return TRUE;
}
}
// vehicle_twizy_cfg_writeprofile: write from twizy_cgf_profile to params
// with checksum calculation
BOOL vehicle_twizy_cfg_writeprofile(UINT8 key)
{
if (key >= 1 && key <= 3) {
twizy_cfg_profile.checksum = vehicle_twizy_cfg_calc_checksum((BYTE *)&twizy_cfg_profile);
//par_setbin(PARAM_PROFILE_S + ((key-1)<<1), &twizy_cfg_profile, sizeof twizy_cfg_profile);
EEPROM.put(key * 64, twizy_cfg_profile);
return TRUE;
}
else {
return FALSE;
}
}
// vehicle_twizy_cfg_applyprofile: configure current profile
// return value: 0 = no error, else error code
// sets: twizy_cfg.profile_cfgmode, twizy_cfg.profile_user
UINT vehicle_twizy_cfg_applyprofile(UINT8 key)
{
UINT err;
int pval;
// clear success flag:
twizy_cfg.applied = 0;
// login:
if (err = login(1))
return err;
// update op (user) mode params:
if (err = vehicle_twizy_cfg_drive(cfgparam(drive),cfgparam(autodrive_ref),cfgparam(autodrive_minprc)))
return err;
if (err = vehicle_twizy_cfg_recup(cfgparam(neutral),cfgparam(brake),cfgparam(autorecup_ref),cfgparam(autorecup_minprc)))
return err;
if (err = vehicle_twizy_cfg_ramps(cfgparam(ramp_start),cfgparam(ramp_accel),cfgparam(ramp_decel),cfgparam(ramp_neutral),cfgparam(ramp_brake)))
return err;
if (err = vehicle_twizy_cfg_rampl(cfgparam(ramplimit_accel),cfgparam(ramplimit_decel)))
return err;
if (err = vehicle_twizy_cfg_smoothing(cfgparam(smooth)))
return err;
// update user profile status:
twizy_cfg.profile_user = key;
// update pre-op (admin) mode params if configmode possible at the moment:
err = configmode(1);
if (!err) {
err = vehicle_twizy_cfg_speed(cfgparam(speed),cfgparam(warn));
if (!err)
err = vehicle_twizy_cfg_power(cfgparam(torque),cfgparam(power_low),cfgparam(power_high),cfgparam(current));
if (!err)
err = vehicle_twizy_cfg_makepowermap();
if (!err)
err = vehicle_twizy_cfg_tsmap('D',
cfgparam(tsmap[0].prc1),cfgparam(tsmap[0].prc2),cfgparam(tsmap[0].prc3),cfgparam(tsmap[0].prc4),
cfgparam(tsmap[0].spd1),cfgparam(tsmap[0].spd2),cfgparam(tsmap[0].spd3),cfgparam(tsmap[0].spd4));
if (!err)
err = vehicle_twizy_cfg_tsmap('N',
cfgparam(tsmap[1].prc1),cfgparam(tsmap[1].prc2),cfgparam(tsmap[1].prc3),cfgparam(tsmap[1].prc4),
cfgparam(tsmap[1].spd1),cfgparam(tsmap[1].spd2),cfgparam(tsmap[1].spd3),cfgparam(tsmap[1].spd4));
if (!err)
err = vehicle_twizy_cfg_tsmap('B',
cfgparam(tsmap[2].prc1),cfgparam(tsmap[2].prc2),cfgparam(tsmap[2].prc3),cfgparam(tsmap[2].prc4),
cfgparam(tsmap[2].spd1),cfgparam(tsmap[2].spd2),cfgparam(tsmap[2].spd3),cfgparam(tsmap[2].spd4));
#ifdef OVMS_TWIZY_CFG_BRAKELIGHT
if (!err)
err = vehicle_twizy_cfg_brakelight(cfgparam(brakelight_on),cfgparam(brakelight_off));
#endif // OVMS_TWIZY_CFG_BRAKELIGHT
// update cfgmode profile status:
if (err == 0)
twizy_cfg.profile_cfgmode = key;
// switch back to op-mode (5 tries):
key = 5;
while (configmode(0) != 0) {
if (--key == 0)
break;
delay5(20); // 100 ms
}
} else {
// pre-op mode currently not possible;
// just set speed limit:
pval = cfgparam(speed);
if (pval == -1)
pval = CFG.DefaultKphMax;
err = writesdo(0x2920,0x05,scale(CFG.DefaultRpmMax,CFG.DefaultKphMax,pval,0,65535));
if (!err)
err = writesdo(0x2920,0x06,scale(CFG.DefaultRpmMax,CFG.DefaultKphMax,pval,0,CFG.DefaultRpmRev));
}
// set success flag:
twizy_cfg.applied = 1;
return err;
}
// vehicle_twizy_cfg_switchprofile: load and configure a profile
// return value: 0 = no error, else error code
// sets: twizy_cfg.profile_cfgmode, twizy_cfg.profile_user
UINT vehicle_twizy_cfg_switchprofile(UINT8 key)
{
// check key:
if (key > 3)
return ERR_Range + 1;
// load new profile:
vehicle_twizy_cfg_readprofile(key);
twizy_cfg.unsaved = 0;
// apply profile:
return vehicle_twizy_cfg_applyprofile(key);
}
#ifdef OVMS_TWIZY_DEBUG
// utility: output power map info to string:
char *vehicle_twizy_fmt_powermap(char *s)
{
// Pt2 = Constant Torque End (max_pwr_lo / max_trq)
s = stp_i(s, " PwrLo: ", twizy_max_pwr_lo);
readsdo(0x4611,0x03);
s = stp_l2f(s, "W / ", (twizy_sdo.data * 1000) / 16, 3);
readsdo(0x4611,0x04);
s = stp_l2f(s, "Nm @ ", (twizy_sdo.data * CFG.DefaultKphMax * 10) / CFG.DefaultRpmMax, 1);
s = stp_rom(s, "kph");
// Pt8 = max_pwr_hi
s = stp_i(s, " PwrHi: ", twizy_max_pwr_hi);
readsdo(0x4611,0x0f);
s = stp_l2f(s, "W / ", (twizy_sdo.data * 1000) / 16, 3);
readsdo(0x4611,0x10);
s = stp_l2f(s, "Nm @ ", (twizy_sdo.data * CFG.DefaultKphMax * 10) / CFG.DefaultRpmMax, 1);
s = stp_rom(s, "kph");
return s;
}
#endif // OVMS_TWIZY_DEBUG
// utility: output profile switch result info to string
// and set new profile nr in PARAM_PROFILE
char *vehicle_twizy_fmt_switchprofileresult(char *s, INT8 profilenr, UINT err)
{
if (err && (err != ERR_CfgModeFailed)) {
// failure:
s = vehicle_twizy_fmt_err(s, err);
}
else {
if (profilenr == -1) {
s = stp_rom(s, "WS"); // working set
}
else {
s = stp_i(s, "#", profilenr);
//par_set(PARAM_PROFILE, s-1);
EEPROM.put(PARAM_PROFILE, profilenr);
}
if (err == ERR_CfgModeFailed) {
// partial success:
s = stp_rom(s, " PARTIAL: retry at stop!");
}
else {
// full success:
s = stp_i(s, " OK: SPEED ", cfgparam(speed));
s = stp_i(s, " ", cfgparam(warn));
s = stp_i(s, " POWER ", cfgparam(torque));
s = stp_i(s, " ", cfgparam(power_low));
s = stp_i(s, " ", cfgparam(power_high));
s = stp_i(s, " ", cfgparam(current));
}
s = stp_i(s, " DRIVE ", cfgparam(drive));
s = stp_i(s, " ", cfgparam(autodrive_ref));
s = stp_i(s, " ", cfgparam(autodrive_minprc));
s = stp_i(s, " RECUP ", cfgparam(neutral));
s = stp_i(s, " ", cfgparam(brake));
s = stp_i(s, " ", cfgparam(autorecup_ref));
s = stp_i(s, " ", cfgparam(autorecup_minprc));
}
return s;
}
void vehicle_twizy_init() {
INT8 i;
twizy_max_rpm = 0;
twizy_max_trq = 0;
twizy_max_pwr_lo = 0;
twizy_max_pwr_hi = 0;
twizy_cfg.type = 0;
// reload last selected user profile on init:
EEPROM.get(PARAM_PROFILE, i);
twizy_cfg.profile_user = constrain(i, 0, 3);
twizy_cfg.profile_cfgmode = twizy_cfg.profile_user;
vehicle_twizy_cfg_readprofile(twizy_cfg.profile_user);
twizy_cfg.unsaved = 0;
twizy_cfg.keystate = 0;
// Init autopower system:
twizy_autorecup_level = twizy_autodrive_level = 1000;
twizy_autorecup_checkpoint = twizy_autodrive_checkpoint = 0;
}