Skyward boardcore
Loading...
Searching...
No Matches
CanDriver.cpp
Go to the documentation of this file.
1/* Copyright (c) 2018 Skyward Experimental Rocketry
2 * Author: Luca Erbetta
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "CanDriver.h"
24
25#include <kernel/scheduler/scheduler.h>
26#include <utils/ClockUtils.h>
27#include <utils/KernelTime.h>
28
29#include <algorithm>
30#include <cmath>
31
32#include "CanInterrupt.h"
34
35using namespace std::chrono;
36
37namespace Boardcore
38{
39
40namespace Canbus
41{
42
44
46 AutoBitTiming bitTiming)
47 : CanbusDriver(can, config, calcBitTiming(bitTiming))
48{
49}
50
52 BitTiming bitTiming)
53 : can(can)
54{
55 if (can == CAN2)
56 {
57 // CAN2 also need the CAN1 clock
59 }
60 // Enable the peripheral clock
62
63 // Enter init mode
64 can->MCR &= ~CAN_MCR_SLEEP;
65 can->MCR |= CAN_MCR_INRQ;
66
67 while ((can->MSR & CAN_MSR_INAK) == 0)
68 ;
69
70 // Automatic wakeup when a new packet is available
71 if (config.awum)
72 can->MCR |= CAN_MCR_AWUM;
73
74 // Automatically recover from Bus-Off mode
75 if (config.abom)
76 can->MCR |= CAN_MCR_ABOM;
77
78 // Disable automatic retransmission
79 if (config.nart)
80 can->MCR |= CAN_MCR_NART;
81
82 // Bit timing configuration
83 can->BTR &= ~CAN_BTR_BRP;
84 can->BTR &= ~CAN_BTR_TS1;
85 can->BTR &= ~CAN_BTR_TS2;
86 can->BTR &= ~CAN_BTR_SJW;
87
88 can->BTR |= bitTiming.BRP & 0x3FF;
89 can->BTR |= ((bitTiming.BS1 - 1) & 0xF) << 16;
90 can->BTR |= ((bitTiming.BS2 - 1) & 0x7) << 20;
91 can->BTR |= ((bitTiming.SJW - 1) & 0x3) << 24;
92
93 if (config.loopback)
94 can->BTR |= CAN_BTR_LBKM;
95
96 // Enter filter initialization mode
97 can->FMR |= CAN_FMR_FINIT;
98
99 if (can == CAN1)
100 canDrivers[0] = this;
101 else
102 canDrivers[1] = this;
103
104 // Enable interrupts
105 can->IER |= CAN_IER_FMPIE0 | CAN_IER_FMPIE1 | CAN_IER_TMEIE;
106
107 // Enable the corresponding interrupts
108 if (can == CAN1)
109 {
110 NVIC_EnableIRQ(CAN1_RX0_IRQn);
111 NVIC_SetPriority(CAN1_RX0_IRQn, 14);
112
113 NVIC_EnableIRQ(CAN1_RX1_IRQn);
114 NVIC_SetPriority(CAN1_RX1_IRQn, 14);
115
116 NVIC_EnableIRQ(CAN1_TX_IRQn);
117 NVIC_SetPriority(CAN1_TX_IRQn, 14);
118 }
119 else if (can == CAN2)
120 {
121 NVIC_EnableIRQ(CAN2_RX0_IRQn);
122 NVIC_SetPriority(CAN2_RX0_IRQn, 14);
123
124 NVIC_EnableIRQ(CAN2_RX1_IRQn);
125 NVIC_SetPriority(CAN2_RX1_IRQn, 14);
126
127 NVIC_EnableIRQ(CAN2_TX_IRQn);
128 NVIC_SetPriority(CAN2_TX_IRQn, 14);
129 }
130 else
131 {
132 PrintLogger ls = l.getChild("constructor");
133 LOG_ERR(ls, "Unsupported peripheral");
134 }
135}
136
143
144CanbusDriver::BitTiming CanbusDriver::calcBitTiming(AutoBitTiming autoBt)
145{
146 PrintLogger ls = l.getChild("bittiming");
147
148 BitTiming cfgOpt;
149 float costOpt = 1000;
150 uint8_t NOpt = 5;
151
152 BitTiming cfgIter;
153 cfgIter.SJW = 0;
155
156 // Iterate over the possible number of quanta in a bit to find the best
157 // settings
158 for (uint8_t N = 3; N <= 25; N++)
159 {
160 // Calc optimal baud rate prescaler
161 cfgIter.BRP = std::max(
162 std::min((int)roundf(apbclk * 1.0f / (autoBt.baudRate * N) - 1),
163 1 << 10),
164 1);
165
166 // Given N, calculate BS1 and BS2 that result in a sample time as
167 // close as possible to the target one
168 cfgIter.BS1 =
169 std::min(std::max((int)roundf(autoBt.samplePoint * N - 1), 1),
170 std::min(N - 2, 16));
171
172 cfgIter.BS2 = N - cfgIter.BS1 - 1;
173
174 float brErrPercent =
175 fabs(apbclk * 1.0f / (N * (cfgIter.BRP + 1)) - autoBt.baudRate) /
176 autoBt.baudRate;
177 float sp = (1 + cfgIter.BS1) * 1.0f / (1 + cfgIter.BS1 + cfgIter.BS2);
178
179 float spErrPercent = fabs(sp - autoBt.samplePoint) / autoBt.samplePoint;
180
181 // Calculate the cost function over N
182 float cost = BR_ERR_WEIGHT * brErrPercent +
183 SP_ERR_WEIGHT * spErrPercent +
184 N_ERR_WEIGHT * fabs(N - 10) / 25;
185
186 // Find config that minimizes the cost function
187 if (cost < costOpt)
188 {
189 cfgOpt = cfgIter;
190 costOpt = cost;
191 NOpt = N;
192 }
193 }
194
195 cfgOpt.SJW = fminf(ceilf(NOpt * 1.0f / 5), 4);
196
197 LOG_DEBUG(ls,
198 "Optimal Bit Timing Registers: BRP={}, BS1={}, BS2={}, SJW={}",
199 cfgOpt.BRP, cfgOpt.BS1, cfgOpt.BS2, cfgOpt.SJW);
200
201 float brTrue = apbclk * 1.0f / ((cfgOpt.BRP + 1) * NOpt);
202 float spTrue = (1 + cfgOpt.BS1) * 1.0f / (1 + cfgOpt.BS1 + cfgOpt.BS2);
203
204 LOG_DEBUG(ls,
205 "Optimal Bit Timing: BR_true={:.2f}, spTrue:{:.2f}%, "
206 "BR_error={:.2f}%, SP_error={:.2f}%",
207 brTrue / 1000, spTrue * 100,
208 fabsf(brTrue - autoBt.baudRate) / autoBt.baudRate * 100,
209 fabs(spTrue - autoBt.samplePoint) / autoBt.samplePoint * 100);
210
211 return cfgOpt;
212}
213
214bool CanbusDriver::init(std::chrono::milliseconds timeout)
215{
216 if (isInit)
217 return true;
218
219 PrintLogger ls = l.getChild("init");
220
221 can->FMR &= ~CAN_FMR_FINIT; // Exit filter init mode
222 can->MCR &= ~CAN_MCR_INRQ; // Enable canbus
223
224 LOG_DEBUG(ls, "Waiting for CAN bus synchronization...");
225
226 auto timeoutTs = timeout == milliseconds::zero()
227 ? steady_clock::time_point::max()
228 : steady_clock::now() + timeout;
229
230 // Wait until the can peripheral synchronizes with the bus
231 while ((can->MSR & CAN_MSR_INAK) > 0)
232 {
233 if (steady_clock::now() > timeoutTs)
234 {
235 LOG_ERR(ls, "CAN bus synchronization timed out after {} ms",
236 timeout.count());
237 return false;
238 }
239 Thread::sleep(1);
240 }
241
242 LOG_INFO(ls, "CAN bus synchronized, init done!");
243
244 isInit = true;
245 return true;
246}
247
249{
250 PrintLogger ls = l.getChild("addfilter");
251 uint8_t index = filterIndex;
252
253 // CAN2 filters start from the 15th position
254 if (can == CAN2)
255 index = index + 14;
256
257 if (isInit)
258 {
259 LOG_ERR(ls, "Cannot add filter: CAN bus already initialized");
260 return false;
261 }
262
263 if (index >= NUM_FILTER_BANKS)
264 {
265 LOG_ERR(ls, "Cannot add filter: no more filter banks available");
266 return false;
267 }
268
269 // NOTE: the filters are set in CAN1 peripheral because the filter registers
270 // between the peripherals are in common.
271 CAN1->sFilterRegister[index].FR1 = filter.FR1;
272 CAN1->sFilterRegister[index].FR2 = filter.FR2;
273
274 CAN1->FM1R |= (filter.mode == FilterMode::MASK ? 0 : 1) << index;
275 CAN1->FS1R |= (filter.scale == FilterScale::DUAL16 ? 0 : 1) << index;
276 CAN1->FFA1R |= (filter.fifo & 0x1) << index;
277
278 // Enable the filter
279 CAN1->FA1R |= 1 << index;
280
281 ++filterIndex;
282
283 return true;
284}
285
287{
288 PrintLogger ls = l.getChild("send");
289
290 if (!isInit)
291 {
292 LOG_ERR(ls, "CAN bus is not initialized!");
293 return 0;
294 }
295
296 bool didWait = false;
297
298 {
299 miosix::FastInterruptDisableLock d;
300
301 // Wait until there is an empty mailbox available to use
302 while ((can->TSR & CAN_TSR_TME) == 0)
303 {
304 didWait = true;
305 waiting = Thread::IRQgetCurrentThread();
306 Thread::IRQwait();
307 {
308 miosix::FastInterruptEnableLock e(d);
309 Thread::yield();
310 }
311 }
312 }
313
314 if (didWait)
315 {
316 // Warn that the function blocked. We are probably transmitting too fast
317 LOG_WARN_ASYNC(ls, "Had to wait for an empty mailbox!");
318 }
319
320 // Index of first empty mailbox
321 uint8_t mbxCode = (can->TSR & CAN_TSR_CODE) >> 24;
322
323 if (mbxCode > 2)
324 {
325 LOG_ERR(ls, "Invalid TSR_CODE!");
326 return 0;
327 }
328
329 uint32_t seq = txSeq++;
330 txMailboxSeq[mbxCode] = seq;
331
332 CAN_TxMailBox_TypeDef* mailbox = &can->sTxMailBox[mbxCode];
333
334 can->sTxMailBox[mbxCode].TIR &= CAN_TI0R_TXRQ;
335 if (packet.ext)
336 {
337 can->sTxMailBox[mbxCode].TIR |= ((packet.id & 0x1FFFFFFF) << 3);
338 can->sTxMailBox[mbxCode].TIR |= 1 << 2;
339 }
340 else
341 {
342 can->sTxMailBox[mbxCode].TIR |= ((packet.id & 0x7FF) << 21);
343 }
344
345 can->sTxMailBox[mbxCode].TIR |= (packet.rtr ? 1 : 0) << 1;
346
347 mailbox->TDTR = (packet.length & 0xF);
348
349 // Reset data registers
350 mailbox->TDLR = 0;
351 mailbox->TDHR = 0;
352
353 // Fill data registers
354 for (uint8_t i = 0; i < packet.length; ++i)
355 if (i < 4)
356 mailbox->TDLR |= packet.data[i] << i * 8;
357 else
358 mailbox->TDHR |= packet.data[i] << (i - 4) * 8;
359
360 // Finally send the packet
361 can->sTxMailBox[mbxCode].TIR |= CAN_TI0R_TXRQ;
362
363 return seq;
364}
365
367{
368 CanRXStatus status;
369 status.fifo = fifo;
370 volatile uint32_t* RFR;
371 CAN_FIFOMailBox_TypeDef* mailbox;
372
373 mailbox = &can->sFIFOMailBox[fifo];
374 if (fifo == 0)
375 RFR = &can->RF0R;
376 else
377 RFR = &can->RF1R;
378
379 status.fifoOverrun = (*RFR & CAN_RF0R_FOVR0) > 0;
380 status.fifoFull = (*RFR & CAN_RF0R_FULL0) > 0;
381
382 CanPacket p;
383 bool hppw = false;
384
385 // Note: Bit position definitions are the same for both FIFOs
386 // eg: CAN_RF0R_FMP0 == CAN_RF1R_FMP1
387
388 if ((*RFR & CAN_RF0R_FMP0) > 0)
389 {
391
392 status.rxStatus = *RFR & (CAN_RF0R_FULL0 | CAN_RF0R_FOVR0) >> 3;
393 status.errCode = (can->ESR | CAN_ESR_LEC) >> 4;
394 status.rxErrCounter = (can->ESR | CAN_ESR_REC) >> 24;
395
396 p.ext = (mailbox->RIR & CAN_RI0R_IDE) > 0;
397 p.rtr = (mailbox->RIR & CAN_RI0R_RTR) > 0;
398
399 if (p.ext)
400 p.id = (mailbox->RIR >> 3) & 0x1FFFFFFF;
401 else
402 p.id = (mailbox->RIR >> 21) & 0x7FF;
403
404 p.length = mailbox->RDTR & CAN_RDT0R_DLC;
405
406 for (uint8_t i = 0; i < p.length; i++)
407 if (i < 4) // Low register
408 p.data[i] = (mailbox->RDLR >> (i * 8)) & 0xFF;
409 else // High register
410 p.data[i] = (mailbox->RDHR >> ((i - 4) * 8)) & 0xFF;
411
412 *RFR |= CAN_RF0R_RFOM0;
413
414 // Put the message into the queue
415 bufRxPackets.IRQput(CanRXPacket{p, status}, hppw);
416 }
417
418 if (hppw)
419 miosix::Scheduler::IRQfindNextThread();
420}
421
423{
424 if (waiting)
425 {
426 waiting->IRQwakeup();
427 waiting = 0;
428 }
429}
430
431} // namespace Canbus
432
433} // namespace Boardcore
void int fifo
#define LOG_INFO(logger,...)
#define LOG_ERR(logger,...)
#define LOG_WARN_ASYNC(logger,...)
#define LOG_DEBUG(logger,...)
Low level CanBus driver, with support for both peripherals (CAN1 and CAN2) on stm32f4 microcontroller...
Definition CanDriver.h:47
uint32_t send(CanPacket packet)
Sends a packet on the bus. This function blocks until the message has been successfully put into a TX...
CanbusDriver(CAN_TypeDef *can, CanbusConfig config, AutoBitTiming bitTiming)
Construct a new Canbus object, automatically calculating timing register values from high level requi...
Definition CanDriver.cpp:45
void handleRXInterrupt(int fifo)
Handles an incoming RX interrupt. ONLY to be called from the Canbus RX interrupt handler routine.
bool addFilter(FilterBank filter)
Adds a new filter to the bus, or returns false if there are no more filter banks available.
~CanbusDriver()
Disables the peripheral clock.
void wakeTXThread()
Wakes the transmission thread. ONLY to be called from the Canbus TX interrupt handler routine.
bool init(std::chrono::milliseconds timeout=std::chrono::milliseconds::zero())
Exits initialization mode and starts CanBus operation.
static PrintLogger getLogger(const string &name)
PrintLogger getChild(const string &name)
CanbusDriver * canDrivers[2]
PrintLogger l
Definition CanDriver.cpp:43
bool disablePeripheralClock(void *peripheral)
Disables a peripheral clock source from the APB1 and APB2 peripheral buses.
Definition ClockUtils.h:531
bool enablePeripheralClock(void *peripheral)
Enables a peripheral clock source from the APB1 and APB2 peripheral buses.
Definition ClockUtils.h:155
uint32_t getAPBPeripheralsClock(APB bus)
Computes the output clock frequency for peripherals on the given APB.
Definition ClockUtils.h:73
long long IRQgetOldTick()
Get the current time in milliseconds.
Definition KernelTime.h:53
Driver for the VN100S IMU.
bool ext
Whether to use extended packet id.
Struct defining high level bit timing requirements. Register values will then be calculated automatic...
Definition CanDriver.h:83
Struct specifying exact bit timing registers values.
Definition CanDriver.h:101
Configuration struct for basic CanBus operation.
Definition CanDriver.h:64
Base class for a Canbus filter bank.
Definition Filters.h:49