Skip to content

Commit d330616

Browse files
committed
huawei grid charger: avoid reboot on settings change
MCP215: make event-driven. in particular, this allows us to sleep for some time in the loop task instead of only yield()ing, which effectively keeps the core busy all the time because of this task. the newly added ISR now wakes the loop task once the IRQ pin goes low. use unique_ptr<> to manage the lifetime of the MCP2515 CAN lib object as well as the respective SPI host object.
1 parent 8c84343 commit d330616

File tree

8 files changed

+144
-63
lines changed

8 files changed

+144
-63
lines changed

include/gridcharger/huawei/HardwareInterface.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
#pragma once
33

4+
#include <atomic>
45
#include <array>
56
#include <mutex>
67
#include <map>
@@ -13,7 +14,9 @@ namespace GridCharger::Huawei {
1314

1415
class HardwareInterface {
1516
public:
16-
virtual ~HardwareInterface();
17+
HardwareInterface() = default;
18+
19+
virtual ~HardwareInterface() = default;
1720

1821
virtual bool init() = 0;
1922

@@ -52,20 +55,22 @@ class HardwareInterface {
5255
using can_message_t = struct CAN_MESSAGE_T;
5356

5457
bool startLoop();
58+
void stopLoop();
59+
60+
TaskHandle_t getTaskHandle() const { return _taskHandle; }
5561

5662
private:
5763
static void staticLoopHelper(void* context);
58-
void loopHelper();
5964
void loop();
6065

6166
virtual bool getMessage(can_message_t& msg) = 0;
6267

63-
virtual bool sendMessage(uint32_t valueId, std::array<uint8_t, 8> const& data) = 0;
68+
virtual bool sendMessage(uint32_t canId, std::array<uint8_t, 8> const& data) = 0;
6469

6570
mutable std::mutex _mutex;
6671

6772
TaskHandle_t _taskHandle = nullptr;
68-
bool _taskDone = false;
73+
std::atomic<bool> _taskDone = false;
6974
bool _stopLoop = false;
7075

7176
std::map<HardwareInterface::Property, HardwareInterface::property_t> _stats;

include/gridcharger/huawei/MCP2515.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
#pragma once
33

4+
#include <memory>
45
#include <SPI.h>
56
#include <mcp_can.h>
7+
#include <SpiManager.h>
68
#include <gridcharger/huawei/HardwareInterface.h>
79

810
namespace GridCharger::Huawei {
911

1012
class MCP2515 : public HardwareInterface {
1113
public:
14+
~MCP2515();
15+
1216
bool init() final;
1317

1418
bool getMessage(HardwareInterface::can_message_t& msg) final;
1519

16-
bool sendMessage(uint32_t valueId, std::array<uint8_t, 8> const& data) final;
20+
bool sendMessage(uint32_t canId, std::array<uint8_t, 8> const& data) final;
1721

1822
private:
19-
SPIClass *SPI;
20-
MCP_CAN *_CAN;
23+
// this is static because we cannot give back the bus once we claimed it.
24+
// as we are going to use a shared host/bus in the future, we won't use a
25+
// workaround for the limited time we use it like this.
26+
static std::optional<uint8_t> _oSpiBus;
27+
28+
std::unique_ptr<SPIClass> _upSPI;
29+
std::unique_ptr<MCP_CAN> _upCAN;
2130
uint8_t _huaweiIrq; // IRQ pin
2231
};
2332

include/gridcharger/huawei/TWAI.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ namespace GridCharger::Huawei {
77

88
class TWAI : public HardwareInterface {
99
public:
10+
~TWAI();
11+
1012
bool init() final;
1113

1214
bool getMessage(HardwareInterface::can_message_t& msg) final;
1315

14-
bool sendMessage(uint32_t valueId, std::array<uint8_t, 8> const& data) final;
16+
bool sendMessage(uint32_t canId, std::array<uint8_t, 8> const& data) final;
1517
};
1618

1719
} // namespace GridCharger::Huawei

src/WebApi_Huawei.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,5 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
201201

202202
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
203203

204-
// TODO(schlimmchen): HuaweiCan has no real concept of the fact that the
205-
// config might change. at least not regarding CAN parameters. until that
206-
// changes, the ESP must restart for configuration changes to take effect.
207-
yield();
208-
delay(1000);
209-
yield();
210-
ESP.restart();
204+
HuaweiCan.updateSettings();
211205
}

src/gridcharger/huawei/Controller.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ void Controller::updateSettings()
7171

7272
if (!_upHardwareInterface->init()) {
7373
MessageOutput.println("[HuaweiCanClass::init] Error initializing hardware interface");
74+
_upHardwareInterface.reset(nullptr);
7475
return;
7576
};
7677

src/gridcharger/huawei/HardwareInterface.cpp

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,48 @@
66

77
namespace GridCharger::Huawei {
88

9-
HardwareInterface::~HardwareInterface()
9+
void HardwareInterface::staticLoopHelper(void* context)
1010
{
11-
std::unique_lock<std::mutex> lock(_mutex);
12-
_taskDone = false;
13-
_stopLoop = true;
14-
lock.unlock();
15-
16-
if (_taskHandle != nullptr) {
17-
while (!_taskDone) { delay(10); }
18-
_taskHandle = nullptr;
11+
auto pInstance = static_cast<HardwareInterface*>(context);
12+
static auto constexpr resetNotificationValue = pdTRUE;
13+
static auto constexpr notificationTimeout = pdMS_TO_TICKS(500);
14+
15+
while (true) {
16+
ulTaskNotifyTake(resetNotificationValue, notificationTimeout);
17+
{
18+
std::unique_lock<std::mutex> lock(pInstance->_mutex);
19+
if (pInstance->_stopLoop) { break; }
20+
pInstance->loop();
21+
}
1922
}
23+
24+
pInstance->_taskDone = true;
25+
26+
vTaskDelete(nullptr);
2027
}
2128

22-
void HardwareInterface::staticLoopHelper(void* context)
29+
bool HardwareInterface::startLoop()
2330
{
24-
auto pInstance = static_cast<HardwareInterface*>(context);
25-
pInstance->loopHelper();
26-
vTaskDelete(nullptr);
31+
uint32_t constexpr stackSize = 4096;
32+
return pdPASS == xTaskCreate(HardwareInterface::staticLoopHelper,
33+
"HuaweiHwIfc", stackSize, this, 1/*prio*/, &_taskHandle);
2734
}
2835

29-
void HardwareInterface::loopHelper()
36+
void HardwareInterface::stopLoop()
3037
{
31-
std::unique_lock<std::mutex> lock(_mutex);
38+
if (_taskHandle == nullptr) { return; }
39+
40+
_taskDone = false;
3241

33-
while (!_stopLoop) {
34-
loop();
35-
lock.unlock();
36-
yield();
37-
lock.lock();
42+
{
43+
std::unique_lock<std::mutex> lock(_mutex);
44+
_stopLoop = true;
3845
}
3946

40-
_taskDone = true;
41-
}
47+
xTaskNotifyGive(_taskHandle);
4248

43-
bool HardwareInterface::startLoop()
44-
{
45-
uint32_t constexpr stackSize = 2048;
46-
return pdPASS == xTaskCreate(HardwareInterface::staticLoopHelper,
47-
"HuaweiHwIfc", stackSize, this, 1/*prio*/, &_taskHandle);
49+
while (!_taskDone) { delay(10); }
50+
_taskHandle = nullptr;
4851
}
4952

5053
void HardwareInterface::loop()
@@ -80,7 +83,6 @@ void HardwareInterface::loop()
8083
static_cast<uint8_t>(val & 0xFF)
8184
};
8285

83-
// Send extended message
8486
if (!sendMessage(0x108180FE, data)) {
8587
MessageOutput.print("[Huawei::HwIfc] Failed to set parameter\r\n");
8688
_sendQueue.push({setting, val});
@@ -101,6 +103,8 @@ void HardwareInterface::setParameter(HardwareInterface::Setting setting, float v
101103
{
102104
std::lock_guard<std::mutex> lock(_mutex);
103105

106+
if (_taskHandle == nullptr) { return; }
107+
104108
switch (setting) {
105109
case Setting::OfflineVoltage:
106110
case Setting::OnlineVoltage:
@@ -113,6 +117,8 @@ void HardwareInterface::setParameter(HardwareInterface::Setting setting, float v
113117
}
114118

115119
_sendQueue.push({setting, static_cast<uint16_t>(val)});
120+
121+
xTaskNotifyGive(_taskHandle);
116122
}
117123

118124
HardwareInterface::property_t HardwareInterface::getParameter(HardwareInterface::Property property) const

src/gridcharger/huawei/MCP2515.cpp

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,34 @@
44
*/
55
#include <gridcharger/huawei/MCP2515.h>
66
#include "MessageOutput.h"
7-
#include "SpiManager.h"
87
#include "PinMapping.h"
98
#include "Configuration.h"
109

1110
namespace GridCharger::Huawei {
1211

12+
TaskHandle_t sIsrTaskHandle = nullptr;
13+
14+
void mcp2515Isr()
15+
{
16+
if (sIsrTaskHandle == nullptr) { return; }
17+
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
18+
vTaskNotifyGiveFromISR(sIsrTaskHandle, &xHigherPriorityTaskWoken);
19+
// we assume we can wait until the lower-priority task is scheduled
20+
// anyways, so we ignore xHigherPriorityTaskWoken == pdTRUE.
21+
}
22+
23+
std::optional<uint8_t> MCP2515::_oSpiBus = std::nullopt;
24+
25+
MCP2515::~MCP2515()
26+
{
27+
detachInterrupt(digitalPinToInterrupt(_huaweiIrq));
28+
sIsrTaskHandle = nullptr;
29+
stopLoop();
30+
_upCAN.reset(nullptr);
31+
if (_upSPI) { _upSPI->end(); } // nullptr if init failed or never called
32+
_upSPI.reset(nullptr);
33+
}
34+
1335
bool MCP2515::init()
1436
{
1537
const PinMapping_t& pin = PinMapping.get();
@@ -22,51 +44,74 @@ bool MCP2515::init()
2244
return false;
2345
}
2446

25-
auto spi_bus = SpiManagerInst.claim_bus_arduino();
26-
if (!spi_bus) { return false; }
47+
if (!_oSpiBus) {
48+
_oSpiBus = SpiManagerInst.claim_bus_arduino();
49+
}
50+
51+
if (!_oSpiBus) {
52+
MessageOutput.printf("[Huawei::MCP2515] no SPI host available\r\n");
53+
return false;
54+
}
2755

28-
SPI = new SPIClass(*spi_bus);
56+
_upSPI = std::make_unique<SPIClass>(*_oSpiBus);
2957

30-
SPI->begin(pin.huawei_clk, pin.huawei_miso, pin.huawei_mosi, pin.huawei_cs);
58+
_upSPI->begin(pin.huawei_clk, pin.huawei_miso, pin.huawei_mosi, pin.huawei_cs);
3159
pinMode(pin.huawei_cs, OUTPUT);
3260
digitalWrite(pin.huawei_cs, HIGH);
3361

34-
pinMode(pin.huawei_irq, INPUT_PULLUP);
35-
_huaweiIrq = pin.huawei_irq;
36-
3762
auto mcp_frequency = MCP_8MHZ;
3863
auto frequency = Configuration.get().Huawei.CAN_Controller_Frequency;
3964
if (16000000UL == frequency) { mcp_frequency = MCP_16MHZ; }
4065
else if (8000000UL != frequency) {
4166
MessageOutput.printf("[Huawei::MCP2515] unknown frequency %d Hz, using 8 MHz\r\n", mcp_frequency);
4267
}
4368

44-
_CAN = new MCP_CAN(SPI, pin.huawei_cs);
45-
if (_CAN->begin(MCP_STDEXT, CAN_125KBPS, mcp_frequency) != CAN_OK) {
69+
_upCAN = std::make_unique<MCP_CAN>(_upSPI.get(), pin.huawei_cs);
70+
if (_upCAN->begin(MCP_STDEXT, CAN_125KBPS, mcp_frequency) != CAN_OK) {
4671
MessageOutput.printf("[Huawei::MCP2515] mcp_can begin() failed\r\n");
4772
return false;
4873
}
4974

5075
const uint32_t myMask = 0xFFFFFFFF; // Look at all incoming bits and...
5176
const uint32_t myFilter = 0x1081407F; // filter for this message only
52-
_CAN->init_Mask(0, 1, myMask);
53-
_CAN->init_Filt(0, 1, myFilter);
54-
_CAN->init_Mask(1, 1, myMask);
77+
_upCAN->init_Mask(0, 1, myMask);
78+
_upCAN->init_Filt(0, 1, myFilter);
79+
_upCAN->init_Mask(1, 1, myMask);
5580

5681
// Change to normal mode to allow messages to be transmitted
57-
_CAN->setMode(MCP_NORMAL);
82+
_upCAN->setMode(MCP_NORMAL);
83+
84+
if (!startLoop()) {
85+
MessageOutput.printf("[Huawei::MCP2515] failed to start loop task\r\n");
86+
return false;
87+
}
88+
89+
if (sIsrTaskHandle != nullptr) {
90+
// make the ISR aware of multiple instances if multiple instances of
91+
// this driver should be able to co-exist. only one is supported now.
92+
MessageOutput.printf("[Huawei::MCP2515] ISR task handle already in use\r\n");
93+
stopLoop();
94+
return false;
95+
}
96+
97+
sIsrTaskHandle = getTaskHandle();
98+
_huaweiIrq = pin.huawei_irq;
99+
pinMode(_huaweiIrq, INPUT_PULLUP);
100+
attachInterrupt(digitalPinToInterrupt(_huaweiIrq), mcp2515Isr, FALLING);
58101

59-
return startLoop();
102+
return true;
60103
}
61104

62105
bool MCP2515::getMessage(HardwareInterface::can_message_t& msg)
63106
{
107+
if (!_upCAN) { return false; }
108+
64109
INT32U rxId;
65110
unsigned char len = 0;
66111
unsigned char rxBuf[8];
67112

68113
while (!digitalRead(_huaweiIrq)) {
69-
_CAN->readMsgBuf(&rxId, &len, rxBuf); // Read data: len = data length, buf = data byte(s)
114+
_upCAN->readMsgBuf(&rxId, &len, rxBuf); // Read data: len = data length, buf = data byte(s)
70115

71116
// Determine if ID is standard (11 bits) or extended (29 bits)
72117
if ((rxId & 0x80000000) != 0x80000000) { continue; }
@@ -83,12 +128,14 @@ bool MCP2515::getMessage(HardwareInterface::can_message_t& msg)
83128
return false;
84129
}
85130

86-
bool MCP2515::sendMessage(uint32_t valueId, std::array<uint8_t, 8> const& data)
131+
bool MCP2515::sendMessage(uint32_t canId, std::array<uint8_t, 8> const& data)
87132
{
133+
if (!_upCAN) { return false; }
134+
88135
// MCP2515 CAN library requires a non-const pointer to the data
89136
uint8_t rwData[8];
90137
memcpy(rwData, data.data(), data.size());
91-
return _CAN->sendMsgBuf(valueId, 1, 8, rwData) == CAN_OK;
138+
return _upCAN->sendMsgBuf(canId, 1, 8, rwData) == CAN_OK;
92139
}
93140

94141
} // namespace GridCharger::Huawei

0 commit comments

Comments
 (0)