-
Notifications
You must be signed in to change notification settings - Fork 9
/
hello_can.c
213 lines (188 loc) · 6.94 KB
/
hello_can.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/**
* Copyright (c) 2022-2023 Canis Automotive Labs Ltd
*
* Simple example of using the Canis Labs CAN SDK to send and receive frames with the MCP25xxFD CAN
* controller on the Canis Labs CANPico board with a Raspberry Pi Pico.
*
* See the blog post:
*
* https://kentindell.github.io/2022/07/26/canpico-c-debug/ for
*
* on setting up a picoprobe and VSCode on Ubuntu to perform SWD debugging on a Pico + CANPico
*
* For more on the Canis Labs CANPico see:
*
* https://kentindell.github.io/canpico
*
* To build this for a Pico:
*
* Install the C++/C SDK for the Pico:
*
* https://github.com/raspberrypi/pico-sdk
*
* and set the environment variable PICO_SDK_PATH to point to where it is cloned.
*
* Then build this example with:
*
* $ cmake CMakeFiles.txt
* $ make
*
* Copy the firmware file hello_can.uf2 to the Pico by powering up the board with boot button
* held down. Build the firmware either for USB serial (if standalone) or UART serial (if
* running via a picoprobe debugger). See CMakeLists.txt for details.
*
* The text output of the example can be displayed on a Linux host by connecting to the USB
* serial port using minicom:
*
* $ minicom -b115200 -o -D /dev/ttyACM0
*
* (Assuming the USB serial port is /dev/ttyACM0 - it might be on a different port, depending
* on what other serial devices are connected)
*/
#include <stdio.h>
#include "pico/stdlib.h"
// Include the WiFi chip access functions for driving the on-board LED on the Pico W
#ifndef PICO_DEFAULT_LED_PIN
#include "pico/cyw43_arch.h"
#endif
#include "canapi.h"
// Utility function to print a CAN frame to stdout
void print_frame(can_frame_t *f, uint32_t timestamp)
{
uint8_t len = can_frame_get_data_len(f);
printf(can_frame_is_extended(f) ? "0x%08x " : "0x%03x ", can_frame_get_arbitration_id(f));
if (can_frame_is_remote(f)) {
printf("R"); // Remote frame doesn't have any data, len will be zero
}
for (uint32_t i = 0; i < len; i++) {
printf("%02x", can_frame_get_data(f)[i]);
}
printf(" (%d)\n", timestamp);
}
static can_controller_t controller;
#include <stdio.h>
#include "pico/stdlib.h"
// Wrapper to parameterize the API's IRQ handler with the handle to the single CAN
// controller on the CANPico (the structure is defined above and configured by
// binding to the SPI port and pins used on the CANPico).
//
// NB: The RP2040 with execute-in-place (XIP) flash suffers from very long cache
// miss delays, and these can cause significant delays to the interrupt handler.
// Generally the entire chain of interrupt handling (from vector table to first-level
// handler to device-specific handlers) should be located in RAM to avoid these
// cache delays. The CAN drivers are allocated to RAM with the "TIME_CRITICAL"
// attribute that causes the compiler to place the function in RAM.
//
// This problem of delaying interrupts also extends to critical sections: code that
// disables an interrupt around some function also should execute from RAM while in
// that function to avoid delaying an urgent interrupt handler. This "priority
// inversion" is discussed further here:
//
// https://kentindell.github.io/2021/03/05/pico-priority-inversion/
void TIME_CRITICAL irq_handler(void)
{
// Work out if this interrupt is from the the MCP25xxFD. The bound interface
// defines the pin used for the interrupt line from the CAN controller.
uint8_t spi_irq = controller.host_interface.spi_irq;
uint32_t events = gpio_get_irq_event_mask(spi_irq);
if (events & GPIO_IRQ_LEVEL_LOW) {
mcp25xxfd_irq_handler(&controller);
}
}
void led_init(void)
{
#ifndef PICO_DEFAULT_LED_PIN
if (cyw43_arch_init()) {
printf("Wi-Fi init failed");
}
#else
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
#endif
}
void led_on(void)
{
#ifndef PICO_DEFAULT_LED_PIN
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);
#else
gpio_put(PICO_DEFAULT_LED_PIN, 1);
#endif
}
void led_off(void)
{
#ifndef PICO_DEFAULT_LED_PIN
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0);
#else
gpio_put(PICO_DEFAULT_LED_PIN, 0);
#endif
}
int main() {
stdio_init_all();
led_init();
// This Pico SDK call binds the GPIO vector to calling the CAN controller ISR. If there are
// other devices connected to GPIO interrupts (e.g. the WiFi chip on the Pico W)
// then the appropriate handler has to be called.
irq_add_shared_handler(IO_IRQ_BANK0, irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
can_errorcode_t rc;
// Example uses 500Kbit/sec, 75% sample point
can_bitrate_t bitrate = {.profile = CAN_BITRATE_500K_75};
// Bind the Pico SPI interface to the CANPico's pin layout
mcp25xxfd_spi_bind_canpico(&controller.host_interface);
// Set up the CAN controller on the CANPico using the bound SPI interface
while (true) {
rc = can_setup_controller(&controller, &bitrate, CAN_NO_FILTERS, CAN_MODE_NORMAL, CAN_OPTIONS_NONE);
if (rc != CAN_ERC_NO_ERROR) {
// This can fail if the CAN transceiver isn't powered up properly. That might happen
// if the board had 3.3V but not 5V (the transceiver needs 5V to operate).
printf("CAN setup error: %d\n", rc);
// Try again after 1 second
sleep_ms(1000);
}
else {
break;
}
}
// Create a CAN frame with 11-bit ID of 0x123 and 5 byte payload of deadbeef00
uint8_t data[5] = {0xdeU, 0xadU, 0xbeU, 0xefU, 0x00U};
can_frame_t my_tx_frame;
can_make_frame(&my_tx_frame, false, 0x123, sizeof(data), data, false);
uint32_t queued_ok = 0;
while (true) {
// Light on
led_on();
// Send our frame
rc = can_send_frame(&controller, &my_tx_frame, false);
if (rc != CAN_ERC_NO_ERROR) {
// This can happen if there is no room in the transmit queue, which can
// happen if the CAN controller is connected to a CAN bus but there are no
// other CAN controllers connected and able to ACK a CAN frame, so the
// transmit queue fills up and then cannot accept any more frames.
printf("CAN send error: %d, sent=%d\n", rc, queued_ok);
}
else {
queued_ok++;
}
can_frame_get_data(&my_tx_frame)[4]++; // Update last byte of frame payload
printf("Frames queued OK=%d\n", queued_ok);
// Wait
sleep_ms(250);
// Light off
led_off();
// Wait
sleep_ms(250);
// Print up to 10 received frames
can_rx_event_t rx_event;
uint32_t n = 0;
while (n < 10U) {
can_rx_event_t *e = &rx_event;
if (can_recv(&controller, e) && can_event_is_frame(e)) {
can_frame_t *f = can_event_get_frame(e);
print_frame(f, e->timestamp);
n++;
}
else {
break; // No events left to check for this loop
}
}
}
}