diff --git a/SmartEVSE-3/include/Melopero_RV3028.h b/SmartEVSE-3/include/Melopero_RV3028.h new file mode 100644 index 00000000..3297207a --- /dev/null +++ b/SmartEVSE-3/include/Melopero_RV3028.h @@ -0,0 +1,152 @@ +#if SMARTEVSE_VERSION == 4 +#ifndef Melopero_RV3028_H_INCLUDED +#define Melopero_RV3028_H_INCLUDED + +#include "Arduino.h" +#include "Wire.h" + +#define RV3028_ADDRESS (uint8_t) 0b1010010 + +#define SECONDS_REGISTER_ADDRESS 0x00 +#define MINUTES_REGISTER_ADDRESS 0x01 +#define HOURS_REGISTER_ADDRESS 0x02 +#define WEEKDAY_REGISTER_ADDRESS 0x03 +#define DATE_REGISTER_ADDRESS 0x04 +#define MONTH_REGISTER_ADDRESS 0x05 +#define YEAR_REGISTER_ADDRESS 0x06 + +#define SECONDS_TS_REGISTER_ADDRESS 0x15 +#define MINUTES_TS_REGISTER_ADDRESS 0x16 +#define HOURS_TS_REGISTER_ADDRESS 0x17 +#define DATE_TS_REGISTER_ADDRESS 0x18 +#define MONTH_TS_REGISTER_ADDRESS 0x19 +#define YEAR_TS_REGISTER_ADDRESS 0x1A + +#define UNIX_TIME_ADDRESS 0x1B + +#define AMPM_HOUR_FLAG 0b00000010 +#define PM_FLAG 0b00100000 +#define HOURS_ONLY_FILTER_FOR_ALARM 0b00111111 + +#define MINUTES_ALARM_REGISTER_ADDRESS 0x07 +#define HOURS_ALARM_REGISTER_ADDRESS 0x08 +#define WEEKDAY_DATE_ALARM_REGISTER_ADDRESS 0x09 +#define DATE_ALARM_MODE_FLAG 0b00100000 +#define ENABLE_ALARM_FLAG 0b01111111 +#define ALARM_FLAG 0b00000100 +#define ALARM_INTERRUPT_FLAG 0b00001000 + +#define TIMER_ENABLE_FLAG 0b00000100 +#define TIMER_INTERRUPT_ENABLE_FLAG 0b00010000 +#define TIMER_EVENT_FLAG 0b00001000 +#define TIMER_REPEAT_FLAG 0b10000000 +#define TIMER_VALUE_0_ADDRESS 0x0A +#define TIMER_VALUE_1_ADDRESS 0x0B + +#define STATUS_REGISTER_ADDRESS 0x0E + +#define CONTROL1_REGISTER_ADDRESS 0x0F +#define CONTROL2_REGISTER_ADDRESS 0x10 + +#define USER_RAM1_ADDRESS 0x1F +#define USER_RAM2_ADDRESS 0x20 + +#define EEPROM_ADDRESS_ADDRESS 0x25 +#define EEPROM_DATA_ADDRESS 0x26 +#define EEPROM_COMMAND_ADDRESS 0x27 + + +enum TimerClockFrequency : uint8_t { + Hz4096 = 0x00, + Hz64 = 0x01, + Hz1 = 0x02, + Hz1_60 = 0x03 +}; + +class Melopero_RV3028 { + + // instance attributes/members + public: + TwoWire *i2c; + + //constructor and device initializer + public: + Melopero_RV3028(); + void initI2C(TwoWire &bus = Wire); + + //methods + public: + uint8_t getSecond(); + uint8_t getMinute(); + uint8_t getHour(); + + uint8_t getWeekday(); + uint8_t getDate(); + uint8_t getMonth(); + uint16_t getYear(); + + uint32_t getUnixTime(); + + uint8_t getTSSecond(); + uint8_t getTSMinute(); + uint8_t getTSHour(); + + uint8_t getTSDate(); + uint8_t getTSMonth(); + uint16_t getTSYear(); + + void set24HourMode(); + void set12HourMode(); + bool is12HourMode(); + bool isPM(); + + //time is always set as 24 hours time! + void setTime(uint16_t year, uint8_t month, uint8_t weekday, uint8_t date, uint8_t hour, uint8_t minute, uint8_t second); + //automatically sets the time + //void setTime(); TODO: implement + + /* + The alarm can be set for minutes, hours and date or weekday. Any combination of those can be + selected. + */ + bool isDateModeForAlarm(); + void setDateModeForAlarm(bool flag); + void enableAlarm(uint8_t weekdayOrDate, uint8_t hour, uint8_t minute, + bool dateAlarm = true, bool hourAlarm = true, bool minuteAlarm = true, bool generateInterrupt = true); + void disableAlarm(); + + void enablePeriodicTimer(uint16_t ticks, TimerClockFrequency freq, bool repeat = true, bool generateInterrupt = true); + void disablePeriodicTimer(); + + //everySecond: if True the periodic time update triggers every second. If False it triggers every minute. + void enablePeriodicTimeUpdate(bool everySecond = true, bool generateInterrupt = true); + void disablePeriodicTimeUpdate(); + + void clearInterruptFlags(bool clearTimerFlag=true, bool clearAlarmFlag=true, bool clearPeriodicTimeUpdateFlag=true); + + //TODO: + //void reset(); + // refactor code to use more andOrRegister instead of a read and a write + + + public: + uint8_t readFromRegister(uint8_t registerAddress); + void writeToRegister(uint8_t registerAddress, uint8_t value); + void writeToRegisters(uint8_t startAddress, uint8_t *values, uint8_t length); + void andOrRegister(uint8_t registerAddress, uint8_t andValue, uint8_t orValue); + + // Sets up the device to read/write from/to the eeprom memory. The automatic refresh function has to be disabled. + void useEEPROM(bool disableRefresh = true); + bool waitforEEPROM(); + uint8_t readEEPROMRegister(uint8_t registerAddress); + void writeEEPROMRegister(uint8_t registerAddress, uint8_t value); + + uint8_t BCDtoDEC(uint8_t bcd); + uint8_t DECtoBCD(uint8_t dec); + + uint8_t to12HourFormat(uint8_t bcdHours); + uint8_t to24HourFormat(uint8_t bcdHours); +}; + +#endif // Melopero_RV3028_H_INCLUDED +#endif diff --git a/SmartEVSE-3/include/qca.h b/SmartEVSE-3/include/qca.h new file mode 100644 index 00000000..216ae70b --- /dev/null +++ b/SmartEVSE-3/include/qca.h @@ -0,0 +1,8 @@ +#if SMARTEVSE_VERSION == 4 +extern SPIClass QCA_SPI1; + +uint16_t qcaspi_read_register16(uint16_t reg); +void qcaspi_write_register(uint16_t reg, uint16_t value); +void qcaspi_write_burst(uint8_t *src, uint32_t len); +uint32_t qcaspi_read_burst(uint8_t *dst); +#endif diff --git a/SmartEVSE-3/include/wchisp.h b/SmartEVSE-3/include/wchisp.h new file mode 100644 index 00000000..cac89070 --- /dev/null +++ b/SmartEVSE-3/include/wchisp.h @@ -0,0 +1,27 @@ +#if SMARTEVSE_VERSION == 4 +/*====================================================================* + * WCH Serial Bootloader commands + *--------------------------------------------------------------------*/ + +#define WCH_START 0xA1 +#define WCH_STOP 0xA2 +#define WCH_SET_KEY 0xA3 +#define WCH_ERASE_FLASH 0xA4 +#define WCH_PROGRAM_FLASH 0xA5 +#define WCH_VERIFY_FLASH 0xA6 +#define WCH_READ_OPTION 0xA7 +#define WCH_WRITE_OPTION 0xA8 + +#define WCH_SET_BAUDRATE 0xC5 +#define WCH_EXIT 0x00 + +#define WCH_TX_HEADER 0x57 +#define WCH_RX_HEADER 0x5A + +#define WCHDEBUG // Display serial comm + + +void WchEnterBootloader(void); +void WchReset(void); +uint8_t WchFirmwareUpdate(void); +#endif diff --git a/SmartEVSE-3/src/Melopero_RV3028.cpp b/SmartEVSE-3/src/Melopero_RV3028.cpp new file mode 100644 index 00000000..5bb4c205 --- /dev/null +++ b/SmartEVSE-3/src/Melopero_RV3028.cpp @@ -0,0 +1,342 @@ +#if SMARTEVSE_VERSION == 4 +#include "Melopero_RV3028.h" + +Melopero_RV3028::Melopero_RV3028(){ +} + +void Melopero_RV3028::initI2C(TwoWire &bus){ + i2c = &bus; +} + +uint8_t Melopero_RV3028::readFromRegister(uint8_t registerAddress){ + i2c->beginTransmission(RV3028_ADDRESS); + + //set register pointer + i2c->write(registerAddress); + i2c->endTransmission(); + + i2c->requestFrom(RV3028_ADDRESS, (uint8_t)1); + //TODO: check if the byte is sent with i2c->available() + uint8_t result = i2c->read(); + return result; +} + +void Melopero_RV3028::writeToRegister(uint8_t registerAddress, uint8_t value){ + i2c->beginTransmission(RV3028_ADDRESS); + //set register pointer + i2c->write(registerAddress); + i2c->write(value); + i2c->endTransmission(); +} + +void Melopero_RV3028::writeToRegisters(uint8_t startAddress, uint8_t *values, uint8_t length){ + i2c->beginTransmission(RV3028_ADDRESS); + //set start register address + i2c->write(startAddress); + + i2c->write(values, length); + + i2c->endTransmission(); +} + +void Melopero_RV3028::andOrRegister(uint8_t registerAddress, uint8_t andValue, uint8_t orValue){ + uint8_t regValue = readFromRegister(registerAddress); + regValue &= andValue; + regValue |= orValue; + writeToRegister(registerAddress, regValue); +} + +uint8_t Melopero_RV3028::BCDtoDEC(uint8_t bcd){ + return (bcd / 0x10 * 10) + (bcd % 0x10); +} + +uint8_t Melopero_RV3028::DECtoBCD(uint8_t dec){ + return ((dec / 10) << 4) ^ (dec % 10); +} + +uint8_t Melopero_RV3028::to12HourFormat(uint8_t bcdHours){ + //PM HOURS bit 5 is 1 + if (BCDtoDEC(bcdHours) > 12){ + bcdHours = DECtoBCD(BCDtoDEC(bcdHours) - 12); + bcdHours |= PM_FLAG; + } + return bcdHours; +} + +uint8_t Melopero_RV3028::to24HourFormat(uint8_t bcdHours){ + if((bcdHours & PM_FLAG) > 0) + bcdHours = DECtoBCD(BCDtoDEC(bcdHours & ~PM_FLAG) + 12); + + return bcdHours; +} + +bool Melopero_RV3028::is12HourMode(){ + return (readFromRegister(CONTROL2_REGISTER_ADDRESS) & AMPM_HOUR_FLAG) > 0; +} + +void Melopero_RV3028::set12HourMode(){ + //avoid converting alarm hours if they are already in 12 hour format + if (!is12HourMode()){ + uint8_t alarmHours = readFromRegister(HOURS_ALARM_REGISTER_ADDRESS) & HOURS_ONLY_FILTER_FOR_ALARM; + writeToRegister(HOURS_ALARM_REGISTER_ADDRESS, to12HourFormat(alarmHours)); + } + + writeToRegister(CONTROL2_REGISTER_ADDRESS, (readFromRegister(CONTROL2_REGISTER_ADDRESS) | AMPM_HOUR_FLAG)); +} + +void Melopero_RV3028::set24HourMode(){ + //avoid converting alarm hours if they are already in 24 hour format + if (is12HourMode()){ + uint8_t alarmHours = readFromRegister(HOURS_ALARM_REGISTER_ADDRESS) & HOURS_ONLY_FILTER_FOR_ALARM; + writeToRegister(HOURS_ALARM_REGISTER_ADDRESS, to24HourFormat(alarmHours)); + } + + writeToRegister(CONTROL2_REGISTER_ADDRESS, (readFromRegister(CONTROL2_REGISTER_ADDRESS) & (~AMPM_HOUR_FLAG))); +} + +void Melopero_RV3028::setTime(uint16_t year, uint8_t month, uint8_t weekday, uint8_t date, uint8_t hour, uint8_t minute, uint8_t second){ + uint8_t time_array[] = {DECtoBCD(second), DECtoBCD(minute), DECtoBCD(hour), DECtoBCD(weekday), DECtoBCD(date), DECtoBCD(month), DECtoBCD((uint8_t) (year - 2000))}; + + if (is12HourMode()){ + set24HourMode(); + writeToRegisters(SECONDS_REGISTER_ADDRESS, time_array, 7); + set12HourMode(); + } + else{ + writeToRegisters(SECONDS_REGISTER_ADDRESS, time_array, 7); + } + +} + + +uint8_t Melopero_RV3028::getTSSecond(){ + return BCDtoDEC(readFromRegister(SECONDS_TS_REGISTER_ADDRESS)); +} + +uint8_t Melopero_RV3028::getTSMinute(){ + return BCDtoDEC(readFromRegister(MINUTES_TS_REGISTER_ADDRESS)); +} + +uint8_t Melopero_RV3028::getTSHour(){ + if (is12HourMode()){ + uint8_t bcdHours = readFromRegister(HOURS_TS_REGISTER_ADDRESS); + return BCDtoDEC(bcdHours & ~PM_FLAG); + } + else { + return BCDtoDEC(readFromRegister(HOURS_TS_REGISTER_ADDRESS)); + } +} + +uint8_t Melopero_RV3028::getTSDate(){ + return BCDtoDEC(readFromRegister(DATE_TS_REGISTER_ADDRESS)); +} + +uint8_t Melopero_RV3028::getTSMonth(){ + return BCDtoDEC(readFromRegister(MONTH_TS_REGISTER_ADDRESS)); +} + +uint16_t Melopero_RV3028::getTSYear(){ + return BCDtoDEC(readFromRegister(YEAR_TS_REGISTER_ADDRESS)) + 2000; +} + + + +uint8_t Melopero_RV3028::getSecond(){ + return BCDtoDEC(readFromRegister(SECONDS_REGISTER_ADDRESS)); +} + +uint8_t Melopero_RV3028::getMinute(){ + return BCDtoDEC(readFromRegister(MINUTES_REGISTER_ADDRESS)); +} + +uint8_t Melopero_RV3028::getHour(){ + if (is12HourMode()){ + uint8_t bcdHours = readFromRegister(HOURS_REGISTER_ADDRESS); + return BCDtoDEC(bcdHours & ~PM_FLAG); + } + else { + return BCDtoDEC(readFromRegister(HOURS_REGISTER_ADDRESS)); + } +} + + +bool Melopero_RV3028::isPM(){ + return (readFromRegister(HOURS_REGISTER_ADDRESS) & PM_FLAG) > 0; +} + +uint8_t Melopero_RV3028::getWeekday(){ + return BCDtoDEC(readFromRegister(WEEKDAY_REGISTER_ADDRESS)); +} + +uint8_t Melopero_RV3028::getDate(){ + return BCDtoDEC(readFromRegister(DATE_REGISTER_ADDRESS)); +} + +uint8_t Melopero_RV3028::getMonth(){ + return BCDtoDEC(readFromRegister(MONTH_REGISTER_ADDRESS)); +} + +uint16_t Melopero_RV3028::getYear(){ + return BCDtoDEC(readFromRegister(YEAR_REGISTER_ADDRESS)) + 2000; +} + +uint32_t Melopero_RV3028::getUnixTime(){ + uint32_t unixTime = 0; + unixTime |= ((uint32_t) readFromRegister(UNIX_TIME_ADDRESS)); + unixTime |= ((uint32_t) readFromRegister(UNIX_TIME_ADDRESS + 1)) << 8; + unixTime |= ((uint32_t) readFromRegister(UNIX_TIME_ADDRESS + 2)) << 16; + unixTime |= ((uint32_t) readFromRegister(UNIX_TIME_ADDRESS + 3)) << 24; + return unixTime; +} + +bool Melopero_RV3028::isDateModeForAlarm(){ + return (readFromRegister(CONTROL1_REGISTER_ADDRESS) & DATE_ALARM_MODE_FLAG) > 0; +} + +void Melopero_RV3028::setDateModeForAlarm(bool flag){ + if (flag){ + writeToRegister(CONTROL1_REGISTER_ADDRESS, readFromRegister(CONTROL1_REGISTER_ADDRESS) | DATE_ALARM_MODE_FLAG); + } + else { + writeToRegister(CONTROL1_REGISTER_ADDRESS, readFromRegister(CONTROL1_REGISTER_ADDRESS) & ~DATE_ALARM_MODE_FLAG); + } +} + +void Melopero_RV3028::enableAlarm(uint8_t weekdayOrDate, uint8_t hour, uint8_t minute,bool dateAlarm, bool hourAlarm, bool minuteAlarm, bool generateInterrupt){ + //1. Initialize bits AIE and AF to 0. + //AF + uint8_t reg_value = readFromRegister(STATUS_REGISTER_ADDRESS) & ~ALARM_FLAG; + writeToRegister(STATUS_REGISTER_ADDRESS, reg_value); + //AIE + reg_value = readFromRegister(CONTROL2_REGISTER_ADDRESS) & ~ALARM_INTERRUPT_FLAG; + writeToRegister(CONTROL2_REGISTER_ADDRESS, reg_value); + + if (generateInterrupt){ + reg_value = readFromRegister(CONTROL2_REGISTER_ADDRESS) | ALARM_INTERRUPT_FLAG; + writeToRegister(CONTROL2_REGISTER_ADDRESS, reg_value); + } + + //2. Alarm settings + if (dateAlarm) + writeToRegister(WEEKDAY_DATE_ALARM_REGISTER_ADDRESS, DECtoBCD(weekdayOrDate) & ENABLE_ALARM_FLAG); + else + writeToRegister(WEEKDAY_DATE_ALARM_REGISTER_ADDRESS, DECtoBCD(weekdayOrDate) | ~ENABLE_ALARM_FLAG); + + if (hourAlarm) + writeToRegister(HOURS_ALARM_REGISTER_ADDRESS, DECtoBCD(hour) & ENABLE_ALARM_FLAG); + else + writeToRegister(HOURS_ALARM_REGISTER_ADDRESS, DECtoBCD(hour) | ~ENABLE_ALARM_FLAG); + + if (minuteAlarm) + writeToRegister(MINUTES_ALARM_REGISTER_ADDRESS, DECtoBCD(minute) & ENABLE_ALARM_FLAG); + else + writeToRegister(MINUTES_ALARM_REGISTER_ADDRESS, DECtoBCD(minute) | ~ENABLE_ALARM_FLAG); +} + +void Melopero_RV3028::disableAlarm(){ + writeToRegister(WEEKDAY_DATE_ALARM_REGISTER_ADDRESS, ~ENABLE_ALARM_FLAG); + writeToRegister(HOURS_ALARM_REGISTER_ADDRESS, ~ENABLE_ALARM_FLAG); + writeToRegister(MINUTES_ALARM_REGISTER_ADDRESS, ~ENABLE_ALARM_FLAG); +} + +void Melopero_RV3028::enablePeriodicTimer(uint16_t ticks, TimerClockFrequency freq, bool repeat, bool generateInterrupt){ + //1. set TE, TIE, TF to 0 + //TE + writeToRegister(CONTROL1_REGISTER_ADDRESS, readFromRegister(CONTROL1_REGISTER_ADDRESS) & ~TIMER_ENABLE_FLAG); + //TIE + writeToRegister(CONTROL2_REGISTER_ADDRESS, readFromRegister(CONTROL2_REGISTER_ADDRESS) & ~TIMER_INTERRUPT_ENABLE_FLAG); + //TF + writeToRegister(STATUS_REGISTER_ADDRESS, readFromRegister(STATUS_REGISTER_ADDRESS) & ~TIMER_EVENT_FLAG); + + //2. TRPT to 1 for repeat mode and select freq + if (repeat) + writeToRegister(CONTROL1_REGISTER_ADDRESS, readFromRegister(CONTROL1_REGISTER_ADDRESS) | TIMER_REPEAT_FLAG | freq); + else + writeToRegister(CONTROL1_REGISTER_ADDRESS, (readFromRegister(CONTROL1_REGISTER_ADDRESS) & ~TIMER_REPEAT_FLAG) | freq); + + //set countdown + uint8_t lsb = (uint8_t) ticks; + uint8_t msb = (ticks >> 8) & 0x0F; + + writeToRegister(TIMER_VALUE_0_ADDRESS, lsb); + writeToRegister(TIMER_VALUE_1_ADDRESS, msb); + + //hardware interrupt + if (generateInterrupt) + writeToRegister(CONTROL2_REGISTER_ADDRESS, readFromRegister(CONTROL2_REGISTER_ADDRESS) | TIMER_INTERRUPT_ENABLE_FLAG); + else + writeToRegister(CONTROL2_REGISTER_ADDRESS, readFromRegister(CONTROL2_REGISTER_ADDRESS) & ~TIMER_INTERRUPT_ENABLE_FLAG); + + //enable timer + writeToRegister(CONTROL1_REGISTER_ADDRESS, readFromRegister(CONTROL1_REGISTER_ADDRESS) | TIMER_ENABLE_FLAG); + +} + +void Melopero_RV3028::disablePeriodicTimer(){ + writeToRegister(CONTROL1_REGISTER_ADDRESS, readFromRegister(CONTROL1_REGISTER_ADDRESS) & ~TIMER_ENABLE_FLAG); +} + +void Melopero_RV3028::enablePeriodicTimeUpdate(bool everySecond, bool generateInterrupt){ + uint8_t orValue = everySecond ? 0 : 0x10; + andOrRegister(CONTROL1_REGISTER_ADDRESS, 0xFF, orValue); + if (generateInterrupt) + andOrRegister(CONTROL2_REGISTER_ADDRESS, 0xFF, 0x20); + else + andOrRegister(CONTROL2_REGISTER_ADDRESS, 0xDF, 0); +} + +void Melopero_RV3028::disablePeriodicTimeUpdate(){ + enablePeriodicTimeUpdate(true, false); +} + +void Melopero_RV3028::clearInterruptFlags(bool clearTimerFlag, bool clearAlarmFlag, bool clearPeriodicTimeUpdateFlag){ + uint8_t mask = 0xFF; + mask = clearTimerFlag ? mask & 0xF7 : mask; + mask = clearAlarmFlag ? mask & 0xFB : mask; + mask = clearPeriodicTimeUpdateFlag ? mask & 0xEF : mask; + andOrRegister(STATUS_REGISTER_ADDRESS, mask, 0); +} + +bool Melopero_RV3028::waitforEEPROM(){ + unsigned long timeout = millis() + 500; + while ((readFromRegister(STATUS_REGISTER_ADDRESS) & 0x80) && millis() < timeout); + return millis() < timeout; +} + +// Sets up the device to read/write from/to the eeprom memory. The automatic refresh function has to be disabled. +void Melopero_RV3028::useEEPROM(bool disableRefresh){ + if (disableRefresh){ + andOrRegister(CONTROL1_REGISTER_ADDRESS, 0xFF, 0x08); + writeToRegister(EEPROM_COMMAND_ADDRESS, 0); + } + else + andOrRegister(CONTROL1_REGISTER_ADDRESS, 0xF7, 0); +} + + +/* Reads an eeprom register and returns its content. + * user eeprom address space : [0x00 - 0x2A] + * configuration eeprom address space : [0x30 - 0x37] */ +uint8_t Melopero_RV3028::readEEPROMRegister(uint8_t registerAddress){ + writeToRegister(EEPROM_ADDRESS_ADDRESS, registerAddress); + + // read a register -> eeprom data = 0x00 -> eeprom data = 0x22 + writeToRegister(EEPROM_COMMAND_ADDRESS, 0x00); + writeToRegister(EEPROM_COMMAND_ADDRESS, 0x22); + if (!waitforEEPROM()) return 0xFF; + return readFromRegister(EEPROM_DATA_ADDRESS); +} + +/* Writes value to the eeprom register at address register_address. + * user eeprom address space : [0x00 - 0x2A] + * configuration eeprom address space : [0x30 - 0x37] */ +void Melopero_RV3028::writeEEPROMRegister(uint8_t registerAddress, uint8_t value){ + writeToRegister(EEPROM_ADDRESS_ADDRESS, registerAddress); + writeToRegister(EEPROM_DATA_ADDRESS, value); + + // write to a register in eeprom = 0x00 then 0x21 + writeToRegister(EEPROM_COMMAND_ADDRESS, 0x00); + writeToRegister(EEPROM_COMMAND_ADDRESS, 0x21); + waitforEEPROM(); +} +#endif diff --git a/SmartEVSE-3/src/qca.cpp b/SmartEVSE-3/src/qca.cpp new file mode 100644 index 00000000..e59cbd08 --- /dev/null +++ b/SmartEVSE-3/src/qca.cpp @@ -0,0 +1,82 @@ +#if SMARTEVSE_VERSION == 4 +#include +#include +#include "main.h" +#include "qca.h" + + +uint16_t qcaspi_read_register16(uint16_t reg) { + uint16_t tx_data; + uint16_t rx_data; + + tx_data = QCA7K_SPI_READ | QCA7K_SPI_INTERNAL | reg; + + digitalWrite(PIN_QCA700X_CS, LOW); + QCA_SPI1.transfer16(tx_data); // send the command to read the internal register + rx_data = QCA_SPI1.transfer16(0x0000); // read the data on the bus + digitalWrite(PIN_QCA700X_CS, HIGH); + + return rx_data; +} + +void qcaspi_write_register(uint16_t reg, uint16_t value) { + uint16_t tx_data; + + tx_data = QCA7K_SPI_WRITE | QCA7K_SPI_INTERNAL | reg; + + digitalWrite(PIN_QCA700X_CS, LOW); + QCA_SPI1.transfer16(tx_data); // send the command to write the internal register + QCA_SPI1.transfer16(value); // write the value to the bus + digitalWrite(PIN_QCA700X_CS, HIGH); + +} + +void qcaspi_write_burst(uint8_t *src, uint32_t len) { + uint16_t total_len; + uint8_t buf[10]; + + buf[0] = 0xAA; + buf[1] = 0xAA; + buf[2] = 0xAA; + buf[3] = 0xAA; + buf[4] = (uint8_t)((len >> 0) & 0xFF); + buf[5] = (uint8_t)((len >> 8) & 0xFF); + buf[6] = 0; + buf[7] = 0; + + total_len = len + 10; + // Write nr of bytes to write to SPI_REG_BFR_SIZE + qcaspi_write_register(SPI_REG_BFR_SIZE, total_len); + //log_d("Write buffer bytes sent: %u\n", total_len); + + // log_d("[TX] "); + // for(int x=0; x< len; x++) log_d("%02x ",src[x]); + // log_d("\n"); + + digitalWrite(PIN_QCA700X_CS, LOW); + QCA_SPI1.transfer16(QCA7K_SPI_WRITE | QCA7K_SPI_EXTERNAL); // Write External + QCA_SPI1.transfer(buf, 8); // Header + QCA_SPI1.transfer(src, len); // Data + QCA_SPI1.transfer16(0x5555); // Footer + digitalWrite(PIN_QCA700X_CS, HIGH); +} + +uint32_t qcaspi_read_burst(uint8_t *dst) { + uint16_t available; + + available = qcaspi_read_register16(SPI_REG_RDBUF_BYTE_AVA); + + if (available && available <= QCA7K_BUFFER_SIZE) { // prevent buffer overflow + // Write nr of bytes to read to SPI_REG_BFR_SIZE + qcaspi_write_register(SPI_REG_BFR_SIZE, available); + + digitalWrite(PIN_QCA700X_CS, LOW); + QCA_SPI1.transfer16(QCA7K_SPI_READ | QCA7K_SPI_EXTERNAL); + QCA_SPI1.transfer(dst, available); + digitalWrite(PIN_QCA700X_CS, HIGH); + + return available; // return nr of bytes in the rxbuffer + } + return 0; +} +#endif diff --git a/SmartEVSE-3/src/wchisp.cpp b/SmartEVSE-3/src/wchisp.cpp new file mode 100644 index 00000000..39a798af --- /dev/null +++ b/SmartEVSE-3/src/wchisp.cpp @@ -0,0 +1,315 @@ +/* +; +; WCH Serial ISP programming for use with WCH CH32V203 ICs +; +; +; This microcontroller series can be programmed using a WCH-link USB debug adapter +; Or by resetting the chip into its bootloader (hold BOOT0 high) and a USB or Serial connection to the host software (WCHISPTool) +; +; Both programming options are undocumented and WCH doesn't provide any documentation on the protocols used. +; +; This software offers a solution for the serial bootloader option. i.e., a WCH chip that will be programmed by another microcontroller. +; Please note that the flash programming obfuscation is only verified on two V203 chips. It's not guaranteed to be correct for every chip. +; +; Call WchFirmwareUpdate() from your main program. It will read a binary file from SPIFFS, and flash the microcontroller. +; Serial1 should be set to 115200 8N1 +; +; +; MIT License +; +; (C) 2023 Michael Stegen / Stegen Electronics +; +; Permission is hereby granted, free of charge, to any person obtaining a copy +; of this software and associated documentation files (the "Software"), to deal +; in the Software without restriction, including without limitation the rights +; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the Software is +; furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included in +; all copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +; THE SOFTWARE. +*/ + +#if SMARTEVSE_VERSION == 4 + + +#include +#include +#include +#include + +#include "main.h" +#include "wchisp.h" + +const uint8_t wch_start[] = {0x31, 0x19, 0x4d, 0x43, 0x55, 0x20, 0x49, 0x53, 0x50, 0x20, 0x26, 0x20, 0x57, 0x43, 0x48, 0x2e ,0x43, 0x4e}; +const uint8_t wch_read_option[] = {0x1f, 0x00}; +const uint8_t wch_write_option[] = {0x07, 0x00, 0xa5, 0x5a, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff}; +const uint8_t wch_set_key[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const uint8_t wch_erase_flash[] = {0xe0, 0x00, 0x00, 0x00}; // Size of Full Flash Erase, set to 224kB for a CH32V203 with 64kB flash? +const uint8_t wch_stop[] = {0x01}; // this will also soft reset the CPU + +uint8_t WchUID[8]; +unsigned long WchTimeout; +uint8_t WchWaitRX = 0; + +File file; +const char* WCHfirmware = "/CH32V203.bin"; + + +void WchEnterBootloader(void) { + + pinMode(WCH_NRST, OUTPUT); + pinMode(WCH_SWCLK, OUTPUT); + digitalWrite(WCH_NRST, LOW); // keep WCH32V in reset + digitalWrite(WCH_SWCLK, HIGH); // BOOT0 high, start bootloader + delay(100); + pinMode(WCH_NRST, INPUT); // WCH32V reset high + // WCH32V should now be in bootloader mode (system memory). + + delay(100); + digitalWrite(WCH_SWCLK, LOW); // BOOT0 low + pinMode(WCH_SWCLK, INPUT); +} + +void WchReset(void) { + + pinMode(WCH_SWCLK, OUTPUT); + digitalWrite(WCH_SWCLK, LOW); // BOOT0 low + pinMode(WCH_NRST, OUTPUT); // connected to NRST pin + digitalWrite(WCH_NRST, LOW); // keep WCH32V in reset + delay(200); + pinMode(WCH_NRST, INPUT); // WCH32V reset high + pinMode(WCH_SWCLK, INPUT); + // WCH32V should now be running program flash. +} + +void WchSendData(const uint8_t * command_data, uint16_t len, uint8_t wch_cmd ) { + uint8_t x, sum = 0; + uint8_t WchTXbuf[200]; + + WchTXbuf[0] = 0x57; + WchTXbuf[1] = 0xAB; + WchTXbuf[2] = wch_cmd; + WchTXbuf[3] = len & 0xff; + WchTXbuf[4] = (len >> 8) & 0xff; + if (len >= 190) len = 190; // should never be more then 190 bytes + memcpy(WchTXbuf+5, command_data, len); + + for (x=0; x] "); + for (x=0; x 0x1fff0000) break; // prevent writes outside of flash area + + filebuffer[0] = filepointer & 0xff; + filebuffer[1] = (filepointer >> 8) & 0xff; + filebuffer[2] = (filepointer >> 16) & 0xff; + filebuffer[3] = (filepointer >> 24) & 0xff; + filebuffer[4] = 0; + WchSendData(filebuffer, len + 5, WchState); + break; + + case WCH_STOP: + WchSendData(wch_stop, sizeof(wch_stop), WCH_STOP); + break; + + default: + break; + } + } + + while (Serial1.available()) { // Uart1 data available? +#ifdef WCHDEBUG + if (!RXlen) Serial.print("[<-] "); +#endif + RXbyte = Serial1.read(); +#ifdef WCHDEBUG + Serial.printf("%02X ",RXbyte); +#endif + WchRXbuf[RXlen] = RXbyte; + if (++RXlen >= 200) RXlen = 199; + } + + if (RXlen >= 8) { // minimal 8 bytes in reply +#ifdef WCHDEBUG + Serial.printf("\r\n"); +#endif + WchWaitRX = 0; // now process the data + sum = 0; + + for(x=2; x filesize) { + log_d("Verifying..."); + WchState = WCH_VERIFY_FLASH; + filepointer = 0; + } + } else log_e("Program Error"); + break; + + case WCH_VERIFY_FLASH: + if (WchRXbuf[7] == 0) { // (WchRXbuf[6] == 0 && WchRXbuf[7] == 0) TODO: NEEDS FIX! + filepointer += 56; + if (filepointer > filesize) WchState = WCH_STOP; + } else log_e("Verify Error"); + break; + + case WCH_STOP: + if (WchRXbuf[6] == 0 && WchRXbuf[7] == 0) { + WchState = WCH_EXIT; // Exit. + } else log_e("Stop Error"); + break; + + default: + break; + } + + } else log_e("Length Error"); + + } else log_e("Header or Sum error"); + + RXlen = 0; + } + + if (WchTimeout < millis() && WchWaitRX) { + log_e("Timeout"); + WchWaitRX = 0; + } + + + } while (WchState != WCH_EXIT); // keep looping until 0 +} + + +uint8_t WchFirmwareUpdate(void) { + + // Initialize SPIFFS + if(!SPIFFS.begin(true)){ + log_e("SPIFFS failed! already tried formatting."); + return 1; + } + log_i("Total SPIFFS bytes: %u, Bytes used: %u",SPIFFS.totalBytes(),SPIFFS.usedBytes()); + + if (SPIFFS.exists(WCHfirmware)) { + log_d("WCH firmware found on SPIFFS"); + file = SPIFFS.open(WCHfirmware, "r"); + if (!file) { + log_e("file open failed"); + return 2; + } else { + WchProgram(); // Program Chip + file.close(); // close file after use + // SPIFFS.remove(WCHfirmware); // erase file, so we only program once + } + } else { + log_e("WCH firmware not found. Already programmed?"); + return 3; + } + return 0; +} +#endif