Can bus battery monitor with OLED
Synopsis
This code is designed for the Arduino UNO. Attached is a SSD1306 compatible OLED module and a MCP2515 CAN module. This is designed to monitor cell voltages in large battery packs containing LiFePO4 cells in arrays. In this particular instance, there are 5 packs, each containing 8 cells. The output is delivered on 1 second intervals into a spreadsheet friendly format.
Notes
There is unexplained behavior in this sketch. Two DEFINE clauses were created EN_SCREEN (enable screen) and EN_ARRAY (enable array).
If EN_SCREEN is defined, and EN_ARRAY is undefined : The screen demo in setup() performs normally
If EN_SCREEN is undefined, and EN_ARRAY is defined : The CAN bus demo in loop() performs normally
If EN_SCREEN is defined, and EN_ARRAY is defined : An initialization error exists with the display.begin() function in in the setup() section, even though the added lines that are in the loop() section and not yet executed. The existence of these array functions in the code segment cause the display.begin() to fail.
The EN_SCREEN code is within lines 68-75.
The EN_ARRAY code is within lines 240-243, 263, 265 and 267.
Code
1 //#define EN_SCREEN
2 //#define EN_ARRAY
3
4 //Arduino UNO
5 //MCP2515
6 ////Pin 13 SPI clock
7 ////Pin 12 MISO
8 ////Pin 11 MOSI
9 ////Pin 10 Cable Select
10 //SSD1306
11 ////Pin A4 SDA
12 ////Pin A5 SCL
13 //ALARM
14 ////Pin 08 Enable
15
16 #include <SPI.h>
17 #include "mcp_can.h"
18
19 //Graphics
20 #include <Wire.h>
21 #include <Adafruit_GFX.h>
22 #include <Adafruit_SSD1306.h>
23
24 #define SCREEN_WIDTH 128 // OLED display width, in pixels
25 #define SCREEN_HEIGHT 64 // OLED display height, in pixels
26 // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
27 #define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
28 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
29
30 //Software constants
31 #define NUMPACKS 5 //Number of packs installed on line. Must edit knownID[] array
32 #define NUMCELLS 8 //Hazardous to modify
33 #define REFRESH 1000 //Refresh rate display
34 #define ALARM 3.645 //Alarm threshold
35
36
37 bool buzzer = HIGH;
38 const int spiCSPin = 10;
39 boolean ledON = 1;
40 float voltage[NUMPACKS][NUMCELLS];
41 float sum[NUMPACKS];
42 long knownID[NUMPACKS] = {0x00000004, 0x00000006, 0x00000020, 0x00000021, 0x00000022};
43 long packID;
44 int slot;
45
46 float Pack = 0.0;
47
48 unsigned char bufA[10];
49 unsigned char buf0[10];
50 unsigned char buf3[10];
51 unsigned char buf5[10];
52 unsigned char buf20[10];
53 unsigned char buf21[10];
54 unsigned char buf22[10];
55
56 unsigned long time;
57 unsigned long TriggerTime = 0;
58 unsigned long unknownId = 0;
59
60 MCP_CAN CAN(spiCSPin);
61
62 void setup()
63 {
64 Serial.begin(115200);
65
66 //Graphics
67
68 #ifdef EN_SCREEN
69 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
70 { // Address 0x3D for 128x64
71 Serial.println(F("SSD1306 allocation failed"));
72 for(;;); // Don't proceed, loop forever
73 }
74 testdrawchar();
75 #endif
76
77 //Pin 8 voltage alarm
78 pinMode(8, OUTPUT);
79 //Initialize against undefined values
80 for(int i=0;i<NUMPACKS;i++)
81 {
82 for(int j=0;j<NUMCELLS;j++)
83 {
84 voltage[i][j]=0.0;
85 }
86 }
87
88
89
90 while (CAN_OK != CAN.begin(CAN_125KBPS,MCP_8MHz))
91 {
92 Serial.println("CAN BUS Init Failed, waiting for hardware...");
93 delay(100);
94 }
95 Serial.println("CAN BUS Init OK!");
96
97 }
98
99 void loop()
100 {
101
102 long msgID = 0x00000FFF;
103 bool skip = LOW;
104 unsigned char len = 0;
105 unsigned char buf[10];
106 unsigned long canId = 0x00000000;
107
108 if(CAN_MSGAVAIL == CAN.checkReceive())
109 {
110
111 CAN.readMsgBuf(&len, buf);
112 canId = CAN.getCanId();
113 if (canId & 0xFFFFF000 != 0x08010000)
114 {
115 Serial.println("Unsupported device attached! Correct and restart monitor.");
116 Serial.print("Device: 0x");
117 Serial.print(canId & 0xFFFFF000,HEX);
118 Serial.println(".");
119 //Sound alarm
120 digitalWrite(8,1);
121 while(1){}
122 }
123 //detect if pack is registered
124 packID = canId & 0x000000FF;
125 slot = 0xFF;
126 for(int i = 0; i<NUMPACKS ; i++)
127 {
128 if(knownID[i]==packID)
129 {
130 slot=i;
131 }
132 if(i==NUMPACKS-1 && slot==0xFF)
133 {
134 Serial.println("Unregistered Device attached! Skipping!");
135 Serial.print(" Device : 0x");
136 Serial.print(packID,HEX);
137 Serial.println(".");
138 skip = HIGH;
139 }
140 }
141 //If pack is registered, collect it's data into local storage.
142 if(!skip)
143 {
144
145 msgID = canId & 0x00000F00;
146 //Interpret message 3
147 if (msgID == 0x00000300)
148 {
149 for(int i = 0; i<len; i++)
150 {
151 buf3[i] = buf[i];
152 }
153 }
154 //Interpret message 3
155 else if (msgID == 0x00000500)
156 {
157 for(int i = 0; i<len; i++)
158 {
159 buf5[i] = buf[i];
160 }
161 }
162 //Interpret message A
163 else if (msgID == 0x00000A00)
164 {
165 for(int i = 0; i<len; i++)
166 {
167 bufA[i] = buf[i];
168 }
169 }
170 //Interpret message 0
171 else if (msgID == 0x00000000)
172 {
173 for(int i = 0; i<len; i++)
174 {
175 buf0[i] = buf[i];
176 }
177 }
178 //Interpret message 2
179 else if (msgID == 0x00000200)
180 {
181 if (buf[0] == 0)
182 {
183 unsigned int Cell = buf[2] * 256 + buf[1];
184 voltage[slot][7] = Cell / 1000.0;
185 Cell = buf[4] * 256 + buf[3];
186 voltage[slot][6] = Cell / 1000.0;
187 Cell = buf[6] * 256 + buf[5];
188 voltage[slot][5] = Cell / 1000.0;
189
190 for(int i = 0; i<len; i++)
191 {
192 buf20[i] = buf[i];
193 }
194 }
195 if (buf[0] == 1)
196 {
197 unsigned int Cell = buf[2] * 256 + buf[1];
198 voltage[slot][4] = Cell / 1000.0;
199 Cell = buf[4] * 256 + buf[3];
200 voltage[slot][3] = Cell / 1000.0;
201 Cell = buf[6] * 256 + buf[5];
202 voltage[slot][2] = Cell / 1000.0;
203
204 for(int i = 0; i<len; i++)
205 {
206 buf21[i] = buf[i];
207 }
208 }
209 if (buf[0] == 2)
210 {
211 unsigned int Cell = buf[2] * 256 + buf[1];
212 voltage[slot][1] = Cell / 1000.0;
213 Cell = buf[4] * 256 + buf[3];
214 voltage[slot][0] = Cell / 1000.0;
215
216 for(int i = 0; i<len; i++)
217 {
218 buf22[i] = buf[i];
219 }
220 }
221 }
222 else
223 {
224 unknownId = canId;
225 Serial.println("Unexpected Message type detected!");
226 Serial.println(canId & 0x00000F00, HEX);
227 }
228
229 //Run every REFRESHms
230 time = millis();
231 if (time >= TriggerTime)
232 {
233
234 //Detect for alarm condition
235 for(int i=0; i<NUMPACKS;i++)
236 {
237 for(int j=0;j<NUMCELLS;j++)
238 {
239 #ifdef EN_ARRAY
240 if(voltage[i][j]>=ALARM) //KILLER LINE
241 buzzer=HIGH;
242 else
243 buzzer=LOW;
244 #endif
245 }
246 }
247
248 //Execute alarm condition
249 if(buzzer)
250 digitalWrite(8,1);
251 else
252 digitalWrite(8,0);
253 TriggerTime = TriggerTime + REFRESH;
254 Pack=0.0;
255
256 //Dump cell voltages to CSV
257 for(int i=0; i<NUMPACKS;i++)
258 {
259 Pack=0.0;
260 for(int j=0; j<NUMCELLS ; j++)
261 {
262 #ifdef EN_ARRAY
263 Serial.print(voltage[i][j],3); //KILLER
264 #endif
265 Serial.print(",");
266 #ifdef EN_ARRAY
267 Pack += voltage[i][j]; //KILLER
268 #endif
269 }
270 Serial.print(Pack,3);
271 if(NUMPACKS-1!=i)
272 Serial.print(',');
273 else
274 Serial.println();
275 }
276
277 if(unknownId != 0)
278 {
279 Serial.print("Unknown Message: ");
280 Serial.print(unknownId, HEX);
281 Serial.println();
282 }
283
284 }
285
286 }
287 else skip=LOW;
288 }
289
290 }
291
292 void testdrawchar(void)
293 {
294 display.clearDisplay();
295 display.setTextSize(1); // Normal 1:1 pixel scale
296 display.setTextColor(SSD1306_WHITE); // Draw white text
297 display.setCursor(0, 0); // Start at top-left corner
298 display.cp437(true); // Use full 256 char 'Code Page 437' font
299 Serial.println("Testing...");
300 display.setCursor(0,0); // Start at top-left corner
301 display.println(F(" * Testing *"));
302 display.display();
303 display.clearDisplay();
304 delay(500);
305 display.setCursor(28,25); // Start at top-left corner
306 display.println(F(" * Testing *"));
307 display.display();
308 delay(500);
309 display.clearDisplay();
310 }