
deshalb jetzt ganz kurz:
warum ist mein Vorschlag scheise, 1 can modul per expander (kascade) auf die 24 akkus zu verteilen?
Moderatoren: TDI, Heaterman, Finger, Sven, Marsupilami72, duese
Weil die Teilnehmer auf dem CAN-BUS regelmäßig Daten senden. Die werden dann übersehen, und alle Teilnehmer benötigen alle 100ms einen Datensatz damit die wach bleiben.
Ich habe mir das mal angesehen und versucht so umzubauen wie ich die Aufgabe verstehe:
Code: Alles auswählen
#include <Arduino.h>
#include "main.h"
#include <SPI.h>
#include <mcp2515.h> // https://github.com/atc1441/arduino-mcp2515
#include <PCF8574.h>
#include "conti.h"
void setup()
{
Serial.begin(115200);
// init port expanders
pcf_01.begin();
pcf_02.begin();
pcf_03.begin();
pcf_04.begin();
pcf_05.begin();
pcf_06.begin();
// init each mcp2515
for (int a = 0; a < 24; a += 1)
{
Serial.print("Start MCP2515 ");
Serial.println(a, 10);
akku[a].mcp.setBitrate(CAN_250KBPS);
akku[a].mcp.setNormalMode();
akku[a].mcp.reset();
}
}
boolean light_status = 0;
uint8_t power_setting = 0;
char out_string[100];
char test;
uint32_t lastsend = 0;
uint32_t lastmsg = 0;
void loop()
{
i2c_datasend(&pcf_03, 3, 1);
_delay_us(100);
i2c_datasend(&pcf_03, 3, 0);
_delay_us(100);
if (millis() - lastsend >= 100)
{ // Keeps the Battery alive needs to be send periodically
lastsend = millis();
// send keepalive to each
for (int a = 0; a < 24; a += 1)
{
sendCAN(&akku[a].mcp, 0x201, 4, 0, 1, 0, 0, 0, 0, 0, 0);
}
}
// check all mcp2515 for errors and new messages
for (int a = 0; a < 24; a += 1)
{
if (akku[a].mcp.getErrorFlags() == 0x15)
{ // On Can Error 0x15 restart CAN & Toggle Akku-DATA+
Serial.println("CAN-Error 0x15, CAN-RESET & Toggle Data+");
akku[a].mcp.reset();
akku[a].mcp.setBitrate(CAN_250KBPS);
akku[a].mcp.setNormalMode();
}
if (akku[a].mcp.readMessage(&canMsg) == MCP2515::ERROR_OK)
{
if (1 == 1)
{ // Turn 0 into a 1 to enable debug prints
Serial.print(canMsg.can_id, HEX); // print ID
Serial.print(" ");
Serial.print(canMsg.can_dlc, HEX); // print DLC
Serial.print(" ");
for (int i = 0; i < canMsg.can_dlc; i++)
{ // print the data
Serial.print(canMsg.data[i], HEX);
Serial.print(" ");
}
Serial.println();
}
switch (canMsg.can_id)
{
case 0x300:
{
power_setting = canMsg.data[0];
if (canMsg.data[2] == 0x64)
light_status = 1;
else
light_status = 0;
break;
}
case 0x404:
{
uint16_t voltage = ((uint16_t)canMsg.data[2] | canMsg.data[3] << 8);
int16_t ampere = ((uint16_t)canMsg.data[0] | canMsg.data[1] << 8);
uint8_t percent = canMsg.data[4];
sprintf(out_string, "%u%% %i Light:%i ", percent, power_setting, light_status);
Serial.println(out_string);
if (ampere < 0)
sprintf(out_string, "%u,%02uV -%i,%02iA ", voltage / 1000, (voltage % 999) / 10, abs(ampere) / 1000, (abs(ampere) % 999) / 10);
else
sprintf(out_string, "%u,%02uV %i,%02iA ", voltage / 1000, (voltage % 999) / 10, abs(ampere) / 1000, (abs(ampere) % 999) / 10);
Serial.println(out_string);
break;
}
}
}
}
}
Code: Alles auswählen
#include <mcp2515.h>
#include <PCF8574.h>
PCF8574 pcf_01(0x26);
PCF8574 pcf_02(0x25);
PCF8574 pcf_03(0x24);
PCF8574 pcf_04(0x23);
PCF8574 pcf_05(0x22);
PCF8574 pcf_06(0x21);
struct can_frame canMsg;
void sendCAN(uint32_t id, uint8_t length, uint8_t data0 = 0x00, uint8_t data1 = 0x00, uint8_t data2 = 0x00, uint8_t data3 = 0x00, uint8_t data4 = 0x00, uint8_t data5 = 0x00, uint8_t data6 = 0x00, uint8_t data7 = 0x00);
int Akku_Nr = 0;
void i2c_datasend(const PCF8574 *pcf, int pin, bool state) // i2c Adresse , Pin, Low/High
{
bool status;
if (!(status = pcf->isConnected()))
{
Serial.print("Not connected ");
}
if (pcf->lastError())
{
Serial.print("I2C Fehler Adresse 0x");
Serial.println(pcf->getAddress());
}
pcf->write(pin, state);
}
class MCPCONTI: public MCP2515 {
private:
const PCF8574 *PCF;
int CS;
public:
MCPCONTI(const PCF8574 *_PCF, const uint8_t _CS) : MCP2515(_CS)
{
CS = _CS;
PCF = _PCF;
endSPI();
}
void startSPI() {
SPI.beginTransaction(SPISettings(SPI_CLOCK, MSBFIRST, SPI_MODE0));
i2c_datasend(PCF, CS, 0);
}
void endSPI() {
i2c_datasend(PCF, CS, 1);
SPI.endTransaction();
}
};
typedef struct
{
MCPCONTI mcp;
int toggle_pin;
int intr_pin;
} akku_t;
akku_t akku[24] = {
{.mcp = MCPCONTI(&pcf_01, 3), 0, 49},
{.mcp = MCPCONTI(&pcf_01, 2), 1, 48},
{.mcp = MCPCONTI(&pcf_01, 5), 6, 47},
{.mcp = MCPCONTI(&pcf_01, 4), 7, 46},
{.mcp = MCPCONTI(&pcf_02, 1), 4, 45},
{.mcp = MCPCONTI(&pcf_02, 3), 5, 44},
{.mcp = MCPCONTI(&pcf_02, 2), 6, 43},
{.mcp = MCPCONTI(&pcf_02, 0), 7, 42},
{.mcp = MCPCONTI(&pcf_03, 3), 0, 30},
{.mcp = MCPCONTI(&pcf_03, 2), 1, 31},
{.mcp = MCPCONTI(&pcf_03, 5), 6, 32},
{.mcp = MCPCONTI(&pcf_03, 4), 7, 33},
{.mcp = MCPCONTI(&pcf_04, 1), 4, 34},
{.mcp = MCPCONTI(&pcf_04, 3), 5, 35},
{.mcp = MCPCONTI(&pcf_04, 2), 6, 36},
{.mcp = MCPCONTI(&pcf_04, 0), 7, 37},
{.mcp = MCPCONTI(&pcf_05, 3), 0, 22},
{.mcp = MCPCONTI(&pcf_05, 2), 1, 23},
{.mcp = MCPCONTI(&pcf_05, 5), 6, 24},
{.mcp = MCPCONTI(&pcf_05, 4), 7, 25},
{.mcp = MCPCONTI(&pcf_06, 1), 4, 26},
{.mcp = MCPCONTI(&pcf_06, 3), 5, 27},
{.mcp = MCPCONTI(&pcf_06, 2), 6, 28},
{.mcp = MCPCONTI(&pcf_06, 0), 7, 29}
};
void sendCAN(MCPCONTI *mcp, uint32_t id, uint8_t length, uint8_t data0, uint8_t data1, uint8_t data2, uint8_t data3, uint8_t data4, uint8_t data5, uint8_t data6, uint8_t data7)
{
struct can_frame canMsg1;
canMsg1.can_id = id;
canMsg1.can_dlc = length;
canMsg1.data[0] = data0;
canMsg1.data[1] = data1;
canMsg1.data[2] = data2;
canMsg1.data[3] = data3;
canMsg1.data[4] = data4;
canMsg1.data[5] = data5;
canMsg1.data[6] = data6;
canMsg1.data[7] = data7;
mcp->sendMessage(MCPCONTI::TXB1, &canMsg1);
}
Code: Alles auswählen
typedef struct
{
MCPCONTI mcp;
int toggle_pin;
int intr_pin;
} akku_t;
akku_t akku[24] = {
{.mcp = MCPCONTI(&pcf_01, 3), 0, 49},
{.mcp = MCPCONTI(&pcf_01, 2), 1, 48},
{.mcp = MCPCONTI(&pcf_01, 5), 6, 47},
{.mcp = MCPCONTI(&pcf_01, 4), 7, 46},
{.mcp = MCPCONTI(&pcf_02, 1), 4, 45},
{.mcp = MCPCONTI(&pcf_02, 3), 5, 44},
{.mcp = MCPCONTI(&pcf_02, 2), 6, 43},
{.mcp = MCPCONTI(&pcf_02, 0), 7, 42},
{.mcp = MCPCONTI(&pcf_03, 3), 0, 30},
{.mcp = MCPCONTI(&pcf_03, 2), 1, 31},
{.mcp = MCPCONTI(&pcf_03, 5), 6, 32},
{.mcp = MCPCONTI(&pcf_03, 4), 7, 33},
{.mcp = MCPCONTI(&pcf_04, 1), 4, 34},
{.mcp = MCPCONTI(&pcf_04, 3), 5, 35},
{.mcp = MCPCONTI(&pcf_04, 2), 6, 36},
{.mcp = MCPCONTI(&pcf_04, 0), 7, 37},
{.mcp = MCPCONTI(&pcf_05, 3), 0, 22},
{.mcp = MCPCONTI(&pcf_05, 2), 1, 23},
{.mcp = MCPCONTI(&pcf_05, 5), 6, 24},
{.mcp = MCPCONTI(&pcf_05, 4), 7, 25},
{.mcp = MCPCONTI(&pcf_06, 1), 4, 26},
{.mcp = MCPCONTI(&pcf_06, 3), 5, 27},
{.mcp = MCPCONTI(&pcf_06, 2), 6, 28},
{.mcp = MCPCONTI(&pcf_06, 0), 7, 29}
};
*/
Code: Alles auswählen
mpiling .pio/build/sparkfun_megapro16MHz/libc0b/PCF8574/PCF8574.cpp.o
src/main.cpp: In function 'void i2c_datasend(const PCF8574*, int, bool)':
src/main.cpp:25:35: warning: passing 'const PCF8574' as 'this' argument discards qualifiers [-fpermissive]
if (!(status = pcf->isConnected()))
^
In file included from src/main.cpp:4:0:
.pio/libdeps/sparkfun_megapro16MHz/PCF8574/PCF8574.h:37:11: note: in call to 'bool PCF8574::isConnected()'
bool isConnected();
^~~~~~~~~~~
src/main.cpp:30:22: warning: passing 'const PCF8574' as 'this' argument discards qualifiers [-fpermissive]
if (pcf->lastError())
^
In file included from src/main.cpp:4:0:
.pio/libdeps/sparkfun_megapro16MHz/PCF8574/PCF8574.h:80:11: note: in call to 'int PCF8574::lastError()'
int lastError();
^~~~~~~~~
src/main.cpp:33:36: warning: passing 'const PCF8574' as 'this' argument discards qualifiers [-fpermissive]
Serial.println(pcf->getAddress());
^
In file included from src/main.cpp:4:0:
.pio/libdeps/sparkfun_megapro16MHz/PCF8574/PCF8574.h:43:11: note: in call to 'uint8_t PCF8574::getAddress()'
uint8_t getAddress();
^~~~~~~~~~
src/main.cpp:36:24: warning: passing 'const PCF8574' as 'this' argument discards qualifiers [-fpermissive]
pcf->write(pin, state);
^
In file included from src/main.cpp:4:0:
.pio/libdeps/sparkfun_megapro16MHz/PCF8574/PCF8574.h:52:11: note: in call to 'void PCF8574::write(uint8_t, uint8_t)'
void write(const uint8_t pin, const uint8_t value)
Ich wollte dazu noch eine Rückmeldung geben:Toni hat geschrieben: ↑So 8. Jan 2023, 11:29 OK, damit sollte alles klar sein![]()
Mein Probem mit dem Clock-Vorteiler ist auf der MC Seite auch explizit beschrieben:
"Reaktivieren beim CLKPR-Problem (Attiny13)
Beim Attiny13 (und anderen) kann man aus dem Programm heraus die Taktquelle mit dem Vorteilerregister CLKPR heruntersetzen. Beim nächsten ISP-Programmieren kann das zu Problemen führen, wenn das Programm anläuft, die Taktquelle sehr langsam schaltet und dann erst in den ISP-Programmiermodus geschaltet wird. Travel Rec. beschreibt wie man mit einem Pull-Down-Widerstand an RESET Anlaufen des problematischen Programms verhindert [1]."
Muss man halt wissen dass es so ist. Für mich ließ sich das lösen, indem ich nochmal ein Bootloader aufgebraten hatte, und den Vorteiler vermied.
Beim fehlerhaften Brennen des Bootloaders wurden vermutlich Fuses verpfuscht, da krame ich mal nach einem externen Quarzoszillator.
...dann versuche ich demnächst mal vorsichtig über AVRDUDE harmlose Fuses zu ändern.
Danke euch![]()
Das funktioniert! Ich habe jetzt standardmäßig einen Quarzoszillator mit C=470pF in Reihe am Programmieradapter, der bei Bedarf an den CLKI-Pin gejumpert werden kann.Bastelbruder hat geschrieben: ↑So 8. Jan 2023, 01:04 Wenn so ein Controller "verfust" ist, bedeutet das im Allgemeinen daß die Takterzeugung nicht mehr läuft. Und ohne Takt gehts nicht. Aber es gibt da einen Trick, ein Kollege hat die Zusatzschaltung Defi genannt.Ein Oszillator mit mindestens 100 kHz, der mit 100 pF an den Takteingang gekoppelt wird. Das darf ein Oszillator im DIL-Gehäuse sein oder auch ein 555 mit Minimalbeschaltung.
Code: Alles auswählen
bool setAddress(const uint8_t deviceAddress) sets the device address after construction.
Can be used to switch between PCF8574 modules runtime. Note this corrupts internal buffered values, so one might need to call read8() and/or write8().
Returns true if address can be found on I2C bus.
Code: Alles auswählen
class HighTechMCP2515 : public MCP2515 {
void startSPI() {
SPIn->beginTransaction(SPISettings(SPI_CLOCK, MSBFIRST, SPI_MODE0));
i2c_datasend(..., 3, 0);
}
void endSPI() {
i2c_datasend(..., 3, 1);
SPIn->endTransaction();
}
}
Ich habe mir gerade den Sourcecode von MCP2515 angesehen. Du hast Recht, die Klasse ist nicht darauf vorbereitet abgeleitet zu werden, dazu müssten die Methoden virtual sein. Eine der vielen Fallgruben in C++.Hightech hat geschrieben: ↑Fr 13. Jan 2023, 20:29
[überladene Methode]
Wenn ich das richtig verstanden habe, kann ich damit zwar mit
HighTechMCP2515 htmcp2515;
htmcp2515.startSPI
die neue Funktion benutzen, und die MCP2515 Funktion startSPI wird überschrieben, aber wenn von der MCP2515 Klasse eine Funktion die Funktion startSPI aufruft, wird wieder die original Funktion benutzt oder?
CAN-Bus ist doch genau dafür gemacht.
Ich?
Darauf aufbauend habe ich meine 1Wire - Modbus Adapter gebaut. Die funktionieren mittlerweile sehr zufriedenstellend. Das wollte ich mal noch etwas "schön machen" (Race Conditions und RAM Verschwendung entfernen und den I2C BME280 non-blocking einbinden...) und dann hier vorstellen. Momentan sieht der Code nach Frankensteins Monster aus. Aus 3 oder 4 Quellen zusammengeklaut (1W von uCnet, Modbus von yaMBSiavr, BME von Bosch & Fritzler...) und mit heißer Nadel zusammengestrickt.Kenakapheus hat geschrieben: ↑Di 17. Jan 2023, 21:38 Ich kann sonst auch das hier sehr Empfehlen:
https://github.com/mbs38/yaMBSiavr
Das geht allerdings von komplett Baremetal aus.
Das geht nur weil jede blöde Birne in dem Käfig einen eigenen speziellen Controller hat der die Frames mit eindeutigen Inhalten verhackstückt. Es gibt bei CAN ja keine adressierbaren Komponenten, lediglich Nachrichtentypen. Hätte nicht wirklich jedes Teil einen eigenen Controller würde es Dir überhaupt nichts nützen wenn da sowas wie "Birne defekt" auf dem Bus ankommt. Man weiss ja nicht welche. So ist das auch bei den Akkus, die senden alle brav ihre Nachricht "Akkuspannung 39.75V", aber man hat dann keinen Schimmer von welchem Akku das kommt. Das Conti-System war ja nie dafür gemacht mehr als einen Akku am Rad spazieren zu fahren.
Code: Alles auswählen
pinMode(2, OUTPUT);
Code: Alles auswählen
/*
* ATTINY412 Pinout Convention:
* ______
* VCC-|1 8|-GND
* D0 PA6(TX)---|2 7|-PA3(SCK) D4
* D1 PA7(RX)---|3 6|-PA0(UPDI) D5
* D2 PA1(MOSI)-|4____5|-PA2(MISO) D3
*/
#include <SPI.h>
#include <avr/sleep.h>
#include "Adafruit_MAX31855.h"
#define MAXDO 3 // software SPI
#define MAXCLK 4 // software SPI
#define MAXCS 2 // ADC Chip Select
int VADC = 1; // ADC Power Supply
int SENSE = 0; // ADC TC+ Input
Adafruit_MAX31855 thermocouple(MAXCLK, MAXCS, MAXDO); // software SPI
//Adafruit_MAX31855 thermocouple(MAXCS); // hardware SPI
void setup() {
thermocouple.begin();
RTC_init(); // Initialize RTC
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode to POWER DOWN mode
sleep_enable(); // Enable sleep mode, but not going to sleep yet
pinMode(VADC, OUTPUT); // ADC Power Supply
}
void loop() {
digitalWrite(VADC, HIGH); // Power on ADC
pinMode(SENSE, INPUT_PULLUP); // Check if Thermocouple is connected
delay(2); // Time constant with 10n between TC+ and TC- is about .5 ms
if (digitalRead(SENSE)== LOW) { // If TC is connected
pinMode(SENSE, INPUT); // sense pin floating
pinMode(2, OUTPUT); // ADC CS Pin
pinMode(4, OUTPUT); // ADC SCK Pin
delay(98); // First valid Data will be available 200 ms after Power-Up
thermocouple.readCelsius();
delay(1);
pinMode(2, INPUT); // deactivate ADC CS Pin
pinMode(4, INPUT); // deactivate ADC SCK Pin
}
digitalWrite(VADC, LOW); // Power down ADC
pinMode(SENSE, INPUT); // sense pin floating
sleep_cpu();
}
void RTC_init(void)
{
while (RTC.STATUS > 0); // Wait for Synchronization
RTC.CLKSEL = RTC_CLKSEL_INT32K_gc; // 32.768kHz Internal Ultra-Low-Power Oscillator (OSCULP32K)
RTC.PITINTCTRL = RTC_PI_bm; // PIT Interrupt: enabled
RTC.PITCTRLA = RTC_PERIOD_CYC16384_gc | RTC_PITEN_bm; // RTC Clock Cycles 16384 | Enable PIT counter
}
ISR(RTC_PIT_vect)
{
RTC.PITINTFLAGS = RTC_PI_bm; // Clear interrupt flag
}
On taking an MCP2515 interrupt, the first thing we do is to disable interrupts from that chip in the interrupt controller. Then read canintf, eflg and canstat2 in the one three-byte read..
If either (or both) RX Interrupts are set we service them, ELSE if a transmit interrupt is set we service it, ELSE if an error interrupt is set we service it. Then the interrupts are enabled and the routine returns. It does NOT try to service more than "one event per customer". as we've got three of them to handle in rotation and don't want a long service on one starving the other two.
The interrupt flags are cleared in the status register with the "Bit Modify" instruction, only clearing the status that has just been serviced.