Skip to content

Commit 5f97d1e

Browse files
committed
panda: switch to new bitrate API
1 parent 4517256 commit 5f97d1e

4 files changed

Lines changed: 173 additions & 12 deletions

File tree

src/can/adapter.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@
44
pub fn get_adapter() -> Result<crate::can::AsyncCanAdapter, crate::error::Error> {
55
#[cfg(feature = "panda")]
66
{
7-
if let Ok(panda) = crate::panda::Panda::new_async() {
7+
let bitrate_cfg = crate::can::bitrate::BitrateBuilder::new::<crate::panda::Panda>()
8+
.bitrate(500_000)
9+
.sample_point(0.8)
10+
.data_bitrate(2_000_000)
11+
.data_sample_point(0.8)
12+
.build()
13+
.unwrap();
14+
15+
if let Ok(panda) = crate::panda::Panda::new_async(bitrate_cfg) {
816
return Ok(panda);
917
}
1018
}
@@ -24,10 +32,8 @@ pub fn get_adapter() -> Result<crate::can::AsyncCanAdapter, crate::error::Error>
2432
let bitrate_cfg = crate::can::bitrate::BitrateBuilder::new::<crate::vector::VectorCan>()
2533
.bitrate(500_000)
2634
.sample_point(0.8)
27-
.sjw(1)
2835
.data_bitrate(2_000_000)
2936
.data_sample_point(0.8)
30-
.data_sjw(1)
3137
.build()
3238
.unwrap();
3339

src/panda/constants.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ pub enum HwType {
2020
pub enum Endpoint {
2121
CanWrite = 0x3,
2222
HwType = 0xc1,
23+
CanSpeed = 0xde,
2324
SafetyModel = 0xdc,
2425
CanResetCommunications = 0xc0,
2526
CanRead = 0x81,
2627
PacketsVersions = 0xdd,
2728
PowerSave = 0xe7,
2829
CanFDAuto = 0xe8,
2930
HeartbeatDisabled = 0xf8,
31+
CanDataSpeed = 0xf9,
3032
}
3133

3234
#[repr(u8)]

src/panda/mod.rs

Lines changed: 151 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ mod usb_protocol;
77
pub use error::Error;
88
use std::collections::VecDeque;
99

10-
use crate::can::AsyncCanAdapter;
11-
use crate::can::CanAdapter;
12-
use crate::can::Frame;
10+
use crate::can::bitrate::{AdapterTimingConst, BitTimingConst, BitrateConfig};
11+
use crate::can::{AsyncCanAdapter, CanAdapter, Frame};
1312
use crate::panda::constants::{Endpoint, HwType, SafetyModel};
1413
use crate::Result;
1514
use tracing::{info, warn};
@@ -19,6 +18,34 @@ const USB_PIDS: &[u16] = &[0xddee, 0xddcc];
1918
const EXPECTED_CAN_PACKET_VERSION: u8 = 4;
2019
const MAX_BULK_SIZE: usize = 16384;
2120
const PANDA_BUS_CNT: usize = 3;
21+
const PANDA_NOMINAL_SAMPLE_POINT: f64 = 0.8;
22+
const SUPPORTED_CAN_BITRATES_BPS: &[u32] = &[
23+
10_000, 20_000, 50_000, 100_000, 125_000, 250_000, 500_000, 1_000_000,
24+
];
25+
const SUPPORTED_CAN_DATA_BITRATES_BPS: &[u32] = &[
26+
10_000, 20_000, 50_000, 100_000, 125_000, 250_000, 500_000, 1_000_000, 2_000_000, 5_000_000,
27+
];
28+
const PANDA_BIT_TIMING_CONST: BitTimingConst = BitTimingConst {
29+
clock_hz: 80_000_000,
30+
tseg1_min: 1,
31+
tseg1_max: 1 << 8,
32+
tseg2_min: 1,
33+
tseg2_max: 1 << 7,
34+
sjw_max: 1 << 7,
35+
brp_min: 1,
36+
brp_max: 1 << 10,
37+
brp_inc: 1,
38+
};
39+
const PANDA_TIMING_CONST: AdapterTimingConst = AdapterTimingConst {
40+
nominal: PANDA_BIT_TIMING_CONST,
41+
data: Some(PANDA_BIT_TIMING_CONST),
42+
};
43+
44+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45+
struct PandaBitrateConfig {
46+
nominal_kbps: u16,
47+
data_kbps: Option<u16>,
48+
}
2249

2350
/// Blocking implementation of the panda CAN adapter
2451
pub struct Panda {
@@ -38,13 +65,16 @@ unsafe impl Send for Panda {}
3865

3966
impl Panda {
4067
/// Convenience function to create a new panda adapter and wrap in an [`AsyncCanAdapter`]
41-
pub fn new_async() -> Result<AsyncCanAdapter> {
42-
let panda = Panda::new()?;
68+
pub fn new_async(bitrate_cfg: BitrateConfig) -> Result<AsyncCanAdapter> {
69+
let panda = Panda::new(bitrate_cfg)?;
4370
Ok(AsyncCanAdapter::new(panda))
4471
}
4572

4673
/// Connect to the first available panda. This function will set the safety mode to ALL_OUTPUT and clear all buffers.
47-
pub fn new() -> Result<Panda> {
74+
pub fn new(bitrate_cfg: BitrateConfig) -> Result<Panda> {
75+
let resolved_bitrate_cfg = resolve_bitrate_config(&bitrate_cfg)?;
76+
warn_if_non_default_sample_points(&bitrate_cfg);
77+
4878
for device in rusb::devices().unwrap().iter() {
4979
let device_desc = device.device_descriptor().unwrap();
5080

@@ -69,19 +99,25 @@ impl Panda {
6999
return Err(Error::WrongFirmwareVersion.into());
70100
}
71101

102+
let hw_type = panda.get_hw_type()?;
103+
warn_if_fd_unsupported(hw_type, resolved_bitrate_cfg.data_kbps.is_some());
104+
72105
panda.set_safety_model(SafetyModel::AllOutput)?;
73106
panda.set_power_save(false)?;
74107
panda.set_heartbeat_disabled()?;
75108
panda.can_reset_communications()?;
76109

77110
for i in 0..PANDA_BUS_CNT {
111+
panda.set_can_speed_kbps(i, resolved_bitrate_cfg.nominal_kbps)?;
112+
if let Some(data_kbps) = resolved_bitrate_cfg.data_kbps {
113+
panda.set_can_data_speed_kbps(i, data_kbps)?;
114+
}
78115
panda.set_canfd_auto(i, false)?;
79116
}
80117

81118
// can_reset_communications() doesn't work properly, flush manually
82119
panda.flush_rx()?;
83120

84-
let hw_type = panda.get_hw_type()?;
85121
info!("Connected to Panda ({:?})", hw_type);
86122

87123
return Ok(panda);
@@ -125,6 +161,20 @@ impl Panda {
125161
self.usb_write_control(Endpoint::CanFDAuto, bus as u16, auto as u16)
126162
}
127163

164+
fn set_can_speed_kbps(&self, bus: usize, speed_kbps: u16) -> Result<()> {
165+
if bus >= PANDA_BUS_CNT {
166+
return Err(crate::Error::NotSupported);
167+
}
168+
self.usb_write_control(Endpoint::CanSpeed, bus as u16, speed_kbps * 10)
169+
}
170+
171+
fn set_can_data_speed_kbps(&self, bus: usize, speed_kbps: u16) -> Result<()> {
172+
if bus >= PANDA_BUS_CNT {
173+
return Err(crate::Error::NotSupported);
174+
}
175+
self.usb_write_control(Endpoint::CanDataSpeed, bus as u16, speed_kbps * 10)
176+
}
177+
128178
/// Get the hardware type of the panda. Usefull to detect if it supports CAN-FD.
129179
pub fn get_hw_type(&self) -> Result<HwType> {
130180
let hw_type = self.usb_read_control(Endpoint::HwType, 1)?;
@@ -217,4 +267,98 @@ impl CanAdapter for Panda {
217267
}
218268
}
219269
}
270+
271+
fn timing_const() -> AdapterTimingConst
272+
where
273+
Self: Sized,
274+
{
275+
PANDA_TIMING_CONST
276+
}
277+
}
278+
279+
fn resolve_bitrate_config(bitrate_cfg: &BitrateConfig) -> Result<PandaBitrateConfig> {
280+
Ok(PandaBitrateConfig {
281+
nominal_kbps: validate_supported_bitrate(
282+
bitrate_cfg.nominal.bitrate,
283+
SUPPORTED_CAN_BITRATES_BPS,
284+
"Panda arbitration bitrate",
285+
)?,
286+
data_kbps: bitrate_cfg
287+
.data
288+
.map(|data| {
289+
validate_supported_bitrate(
290+
data.bitrate,
291+
SUPPORTED_CAN_DATA_BITRATES_BPS,
292+
"Panda data bitrate",
293+
)
294+
})
295+
.transpose()?,
296+
})
297+
}
298+
299+
fn validate_supported_bitrate(bitrate_bps: u32, supported: &[u32], label: &str) -> Result<u16> {
300+
if !supported.contains(&bitrate_bps) {
301+
return Err(crate::Error::InvalidBitrate(format!(
302+
"{label} {bitrate_bps} bps is unsupported; expected one of {supported:?}"
303+
)));
304+
}
305+
306+
Ok((bitrate_bps / 1000) as u16)
307+
}
308+
309+
fn warn_if_non_default_sample_points(bitrate_cfg: &BitrateConfig) {
310+
if !sample_point_matches(bitrate_cfg.nominal.sample_point, PANDA_NOMINAL_SAMPLE_POINT) {
311+
warn!(
312+
bitrate = bitrate_cfg.nominal.bitrate,
313+
requested_sample_point = bitrate_cfg.nominal.sample_point,
314+
panda_sample_point = PANDA_NOMINAL_SAMPLE_POINT,
315+
"Panda ignores the requested nominal sample point"
316+
);
317+
}
318+
319+
if let Some(data) = bitrate_cfg.data {
320+
let panda_sample_point = default_data_sample_point(data.bitrate);
321+
if !sample_point_matches(data.sample_point, panda_sample_point) {
322+
warn!(
323+
bitrate = data.bitrate,
324+
requested_sample_point = data.sample_point,
325+
panda_sample_point,
326+
"Panda ignores the requested CAN-FD data sample point"
327+
);
328+
}
329+
}
330+
}
331+
332+
fn warn_if_fd_unsupported(hw_type: HwType, using_fd_bitrate: bool) {
333+
if using_fd_bitrate && !supports_can_fd(hw_type) {
334+
warn!(
335+
?hw_type,
336+
"Configured CAN-FD bitrate on panda hardware that does not support CAN-FD"
337+
);
338+
}
339+
}
340+
341+
fn supports_can_fd(hw_type: HwType) -> bool {
342+
!matches!(
343+
hw_type,
344+
HwType::Unknown
345+
| HwType::WhitePanda
346+
| HwType::GreyPanda
347+
| HwType::BlackPanda
348+
| HwType::Pedal
349+
| HwType::Uno
350+
| HwType::Dos
351+
)
352+
}
353+
354+
fn default_data_sample_point(data_bitrate: u32) -> f64 {
355+
if data_bitrate == 5_000_000 {
356+
0.75
357+
} else {
358+
0.8
359+
}
360+
}
361+
362+
fn sample_point_matches(actual: f64, expected: f64) -> bool {
363+
(actual - expected).abs() < 1e-9
220364
}

tests/adapter_tests.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ static BULK_NUM_FRAMES_ASYNC: usize = 0x1000;
1010
static BULK_SYNC_TIMEOUT_MS: u64 = 1000;
1111
static BULK_ASYNC_TIMEOUT_MS: u64 = 5000;
1212

13+
#[cfg(feature = "test-panda")]
14+
fn panda_bitrate_config() -> automotive::can::bitrate::BitrateConfig {
15+
automotive::can::bitrate::BitrateBuilder::new::<automotive::panda::Panda>()
16+
.bitrate(500_000)
17+
.sample_point(0.8)
18+
.build()
19+
.unwrap()
20+
}
21+
1322
#[cfg(feature = "test-vector")]
1423
fn vector_bitrate_config() -> automotive::can::bitrate::BitrateConfig {
1524
automotive::can::bitrate::BitrateBuilder::new::<automotive::vector::VectorCan>()
@@ -107,15 +116,15 @@ async fn bulk_send(adapter: &AsyncCanAdapter) {
107116
#[test]
108117
#[serial_test::serial]
109118
fn panda_bulk_send_sync() {
110-
let mut panda = Panda::new().unwrap();
119+
let mut panda = Panda::new(panda_bitrate_config()).unwrap();
111120
bulk_send_sync(&mut panda);
112121
}
113122

114123
#[cfg(feature = "test-panda")]
115124
#[tokio::test]
116125
#[serial_test::serial]
117126
async fn panda_bulk_send_async() {
118-
let panda = automotive::panda::Panda::new_async().unwrap();
127+
let panda = automotive::panda::Panda::new_async(panda_bitrate_config()).unwrap();
119128
bulk_send(&panda).await;
120129
}
121130

0 commit comments

Comments
 (0)