-
Notifications
You must be signed in to change notification settings - Fork 232
added a HAL for CAN interfaces #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite happy about the can-utils
dependency. This should really only contain the bare minimum traits to transmit data over a can bus and leave the implementation details (like setting the speed) up to the concrete HAL implementations.
So, to clarify, is your objection to having a new dependency at all, or is it to having one on a crate that also contains implementation logic? I could easily move the data types into a new crate (eg can_types) and have both embedded_hal and can_utils depend on it. Would that be suitable? Additionally, I think we're interpreting "minimum" differently. It seems like (and please correct me if I'm wrong here!) to you the minimum is only |
@dunmatt Both I would say. Let me try to clarify: The point of the traits is to abstract the hardware insofar as you can easily set up peripherals by calling implementation specific setup and then have drivers which can use a common set of standard traits to send out the data. So the question really is: Does the hardware (after setup, which is implementation specific) need to know what kind of speeds to transfer at or what kind of data you want to transfer or is it simply some opaque data that you send over an established channel. Only the details actually required to send a chunk of data (like amount of bytes to follow, sender/destination addresses which are not part of a frame) should be part of the traits we're modelling here. |
Ah, I think I see a disconnect! There are two types of set up that are relevant here. There's the peripherals set up, which is (as you say) implementation specific, and there's the bus set up, which is part of the CAN spec and thus 0% implementation specific. Thus, to maximize code reuse the bus set up should be part of the generic driver that consumes the HAL. My specific use case is this, I would like to have a driver that is able to automatically determine the bus parameters. So the choices seem to be A) expose the bus parameters in this HAL B) write my own HAL and expose the bus parameters there C) have tons of redundant code or D) give up. Do you see where I'm coming from? |
I don't see anything wrong about providing your own more specific (and reusable) traits providing a higher level abstraction over CAN bus specifics. The traits in this crate represent the lowest possible abstraction required to universally interface with hardware peripherals. In a way you could consider what you want to achieve a universal CAN bus driver and the question you should ask yourself is: What facilities would such a driver need from CAN traits so that any HAL implementing them could be used by your driver and thus should be defined here. |
I did ask myself exactly that, and this PR was my answer ;-) But that's cool, if the intent for this HAL is to be that spartan I'm totally ok starting my own. Thanks for at least having a look! |
@dunmatt I'm not sure I follow, you actually wrote down the key aspect of the concept yourself:
This sentence sums up nicely the layering:
The next layer would be: I think it would be great to have CAN hardware traits in here and might even be able to provide an HAL implementation for the STM32F042 (although I don't have the setup or knowledge to properly test it) to get things started. |
... I guess I'm not understanding what you're asking me to change, it sounded like you were asking me to remove the bus parameters from the HAL, moving the onus of configuring the bus into the hardware bringup code (outside the HAL) rather than as a function of the driver. |
You may have noticed that anything hardware specific is not part of any of the traits; there's no speeds, no hardware specifics, no encoding specifics, etc. All of that is part of the HAL implementation, i.e. number 2) and the user of the HAL (i.e. the application) needs to know about it, not even the driver. The purpose of a driver would be to provide the higher layers of "communication" by using the HAL (1) on any hardware implementing it (2). Such a higher could be a LED driver using the GPIO interface to light up a led (or even using a I2C interface to talk to a chip driving the LEDs, really doesn't matter) or a magnetometer driver using the I2C interface to talk to a separate chip and provide data in a usable form without having to read 20 pages documentation or (as in your case) a CAN bus driver allowing an application to send data over a CAN bus and taking care of the framing, error handling and data munching in general. Of course if there's anything bus specific such a driver would need on the physical layer to operate, like changing states, transmitting data, etc., then that should be in the trait. If it's not absolutely necessary then it should be not. Everything you design into the trait needs to be implemented by every single HAL implementation implementing this particular trait, so by packing in everything and a kitchensink you're enforcing code duplication in 2), not preventing it! I'm no CAN expert, you obviously are. What I'm asking for is: Reduce your trait to an absolute minimum needed to talk to a CAN and then get working on a driver that implements the rest of the interface based on the minimal hardware abstraction. My gut feeling (which could be wrong!) tells me that framing should be gone, so:
Rust works a bit differently than other languages which is exactly what makes it so great but sometimes requires a bit different thinking to take full advantages of the capabilities. |
…cific speed setter into the appropriate trait
Ah, so the framing is absolutely mandatory, it is the one and only way to make sense of the incoming data. Sometimes in CAN you don't even get a data payload, the entire message is carried by the framing. That said, I have reduced the footprint to functions without which there is no way to recover the exposed information or functionality. It is minimal in that sense. It is not minimal in the sense that you could in theory operate a CAN bus just fine without being able to tell the interface to enter or exit sleep mode. |
Does the hardware need to know about the framing? I.e. do you need to set a register per frame type and the hardware will do the correct framing? If so I'd expect to have a frame type parameter and raw data in the send function and vice versa in the receive function instead of a new frame type. If the hardware just takes the raw data and doesn't care what kind of frame it contains, it really should go to a higher level generic driver. Note: For a HAL trait to become proven, at least 2 HAL implementations (preferably for two different vendors) must exist. So it might be worthwhile to pick at least two MCUs now and figure out how one would implement a HAL on those and such what a good signature of the trait methods might be. |
Bare minimum can interfaceTo make a minimal interface for CAN you actually will need to include a CanFrame. This is because.
Priority on the can bus is given from the ID. Realtime systems need to reason about framing to give guarantees. A single call to transmit/receive functions can therefore not do any weird framing stuff. We will also need guidelines of how the CAN drivers should be programmed (Must avoid the can buffer priority inversion problems etc). This will probably require something like:
Another (bare minimum) thing is to check for read/write errors or bus off errors (these HW counters exists for every device following the CAN specification)
Would be nice to include (but can be done at a later point)Everything in the last section is absolutely the bare minimum for a can interface. Since some things are 100% set for the can specification and is required in every compatible hardware it could also make sense to include things as.
Probably not general enough to includeeverything related to mailboxes, FIFO buffers, etc
I might implement this for NXP s32k144 |
I just checked the implementation on the STM32 side and looked into the CAN Wikipedia article. Sending on a STM32 chip essentially means setting:
The actual framing is done in hardware so even if you did the framing in software it wouldn't help much. @kjetilkjeka How would data and remote frames differ from e.g. read/write operations on I2C? In the end it looks like you could have have different traits for standard and extended ID operation and separate methods for sending data and remote frames each of them taking a matching |
Maybe I am wrong, but this is how I see it:
I believe that this crate is only concerned about (1), (4) and (5); and not about (2), (3) and (6). If you look at I2C trait, for example, you won't see functions for setting up interface speed, because (5) don't need to change the speed at run-time, only (6) may want to do that. |
Also keep in mind
Both of these will need to be enforced programatically.
I2C is single master while CAN is multi master (read: P2P). This means that both sending a remote or data frame is something any node on the network may do at any time. Sending a remote frame (and reading the answer) is actually somewhat close to an I2C read. But at the same time, any node might also send you data that you didn't request. This
I like the idea of this approach but I'm not sure if it's worth it in practice. One problem is that if you have an interface that supports In theory not all For CAN-FD it probably makes more sense. I would still want the most general interface (CAN-FD) to return frames of less general types to avoid having to call different methods in different traits to read a can frame. |
Maybe this is a side track but I feel it's one thing that is beeing forgotten @pftbest. I've started on a library for interfacing dynamixel servos in rust The protocol is on top of rs485 or rs232. The protocol requires things as timing out after 100ms and keeping track of what baudrate the different servos should be talked to with. (If the user was required to keep track of different baudrates and setting them manually on the interface between transmission it would be kind of horrible to use) There are a lot of similar things that can be regarded as "user level stuff" but still needs to be modular between different architectures (because it's distributed as libraries). If you use the dynamixel library on desktop computers you can flip the "serialport" feature and use it with any serial<->RS485 adapter. The reason for this is that the serialport library contains the "set timeout" and "set baudrate" functions required to do so. I would love to make this possible for embedded as well. My take on what this crate should be: I like the drawing from @pftbest. Let me try explain what i mean using it as a basis: My answer is: Everything generic (as much as possible) should go through (5). Everything special (only the things required) should go directly to (3). |
@kjetilkjeka What is preventing you from implementing that? The only thing that is rather lame from an interface point of view is the interrupt handling which needs to be done manually per platform instead of an generic way. But it still is possible to use interrupts e.g. to implement timeouts or to react on incoming data, and the baudrate is something you can usually specify during initialisation. If you think anything is missing for RS485, please open a new ticket and let's extend the Serial trait(s) or define new ones. |
Hoo boy, go AFK for a few hours and the thread explodes! I'll respond to each in order (starting from my last post): @therealprof re: "Does the hardware need to know about the framing?" Yes. On STM32s, for example, there is a hardware priority queue that transmits packets according to specific in the framing (the id). Type parameters are insufficient for this, unless you want to have 2^29 types. They also have maskable filter banks in hardware (again, as described in the CAN spec, not just an STM thing), which means you'd need 2^58 types to get around representing the information as a couple u32s in a struct. @kjetilkjeka You mentioned having transmit return a frame that has been bumped from the priority queue. Why wouldn't we just return WouldBlock in that case? |
@pftbest There's more than made it into your diagram. There should be an item (4.5) that is the CAN driver and network stack. As is CAN takes care of OSI levels 1 and 2, but in practice everyone uses higher level protocols when they're interacting on a CAN bus, so there needs to be code there that acts as a CAN driver (ie controlling the hardware by way of the HAL) and dipatches incomming traffic to the appropriate OSI level 3(+) protocol, which would then be interacted with by (5) in an attempt to communicate with (1). |
@dunmatt Who said something about type parameters? Simply put it in the call signature. Please have a look at the existing HALs, especially the I2C one. As far as I can see the |
@kjetilkjeka "I would still want the most general interface (CAN-FD) to return frames of less general types to avoid having to call different methods in different traits to read a can frame." Exactly, I strongly agree, let the driver sort out what sort of frame it was. |
Absolutely! Go for it.
No. A HAL is not a fancy proxy between any layer you desire, it is a set of basic primitives you can expect to always get from a driver implementing this HAL. If you want to hardcode a CAN implementation for a specific chip or a set of chips, please go ahead but this is NOT what embedded-hal is all about. |
@therealprof Oop, sorry, I internally mishyp-henated the phrase "frame type parameter" to be "frame type-parameter" rather than "frame-type parameter". It could be made to work as you're suggesting (assuming that you also broke out the other relevant metadata into arguments), but it's less clear for the reader to have two separate values that have to be passed around in lockstep everywhere. In I2C land addresses are a different sort of thing than the data you store at them (one is a what, the other is a where), in CAN land the CAN header is not meaningfully separable from the data (both things are what, and the line between them blurs as you go up the network stack), at this level CAN doesn't even have a concept of where. |
@dunmatt I understand that an address is different from a CAN id. Just trying to point out that all HALs are implemented like that and that's the point of it. Actually if you prefer to have a CAN structure that combines ID and data into one that'd be fine for me, too. As long it's not a fully assembled CAN frame because that doesn't translate well into typical implementations which automatically do things like CRC, length computation and other things so you would actually have to disable the frame and pull out the relevant bits in pretty much all cases which doesn't make any sense at all. |
@therealprof Agreed, and if you look closely I think you will find that is exactly how it's implemented in can_utils. It is definitely not assembled. If there's a thing I can let the hardware do for me you can bet I'm going to let it. |
@dunmatt I never suggested otherwise, but I am suggesting that a HAL is the means by which the driver controls the hardware. Do you disagree? Fully agree. But the interface needs to be simple and straightforward enough to allow almost any HAL implementation (and you may have noticed I'm trying to use implementation instead of driver because although strictly speaking a HAL implementation is a driver, the drivers we are talking about are consumers of the HAL, so either abstractions for a specific device sitting behind the the hardware or even the application itself). HAL implementations can range from MCU specific register accesses over domain specific controller chips using a different subsystem (like external SPI/CAN interfaces) over interfaces using a library on a real OS to a bitbanged GPIO implementations. Other aspects include that we want to cater for all possible shortcuts like hardware computation where available. If the HAL interface is too complex you're preventing implementations from happening and if you're operating at a too high level (like accepting frames fully assembled by an external crate) you're just wasting resources. |
@therealprof It sounds like we're violently agreeing. I think the only point of disagreement is your assessment of how simple and straightforward my proposal is. The way I wrote this PR was by pulling up the CAN 2.0 spec in one tab, the CAN-FD 1.0 spec in another, and an editor and a terminal. Everything you see in this PR comes straight from the spec with one exception: |
@dunmatt In the end it's not up to me but I strongly dislike the fact that something happens in another crate and I would like to have something that is in one place, straight forward, nicely documented and simple enough that one can see on a glance: "Yes, this is so simple it's not going to break, ever!" |
@dunmatt I believe we're in agreement as soon as I see your proposed code changed to address my concerns. 😉 Specs are nice and great but that doesn't mean you have to model the whole thing into a single block. |
As to your concerns about stability, upkeep and cross platform support, the structure of a CAN frame hasn't changed in more than 25 years, so I have my doubts that it's going to. I'll go so far as to say that the implementation as it is now in The thing I'd like to avoid is having that struct in embedded-hal, because crates like can_utils would depend on it despite having nothing to do with hardware. |
@dunmatt I'm proposing the trait is changed/broken up into simpler methods using standard primitives and your |
@therealprof Huh? I think you're completely not understanding what I was saying about adding a box 4.5 to the diagram, but that doesn't matter. Tell me if I'm wrong here, but it's starting to seem like what you're really objecting to is having any external dependencies in embedded-hal. Or perhaps dependencies you don't control? I don't know, but I'm grasping at straws trying to figure out how your proposal improves clarity, safety, correctness, or anything else. CAN interfaces really do have all of that functionality, it really is all in one place (both on the chip and in the datasheet), what challenge do you expect implementors to face that is somehow improved by fragmenting what it means to be a CanInterface? |
@dunmatt Yes, I am objecting to the external dependencies and to having a high level CAN bus interface in there. Do you have an HAL implementation already? Sometimes working on the implementation makes you realise whether an interface is suited or not. |
I don't, I wanted to float the design to gauge support before dropping time on implementing it, but the response seems to be mostly positive. Give me a few hours. What exactly to you mean "high level"? This is nothing like high level can, in most implementations the fields in these structs are going to map directly to register regions. (This representation is so stable they build it into hardware...) But don't take my word for it, I'll have a full implementation done in a couple of hours and you'll be able to see it for yourself. |
@therealprof You're very right. I did learn a lot from the process of implementing it. But. On the other hand, considering that I had 3 hours and I've been programming in Rust for less than a month, here's your proof that the API isn't crazy pants: https://github.com/dunmatt/stm32f439-hal/blob/sampleCode/src/can.rs The sample implementation is shit, it doesn't even build and I don't understand the errors at all. I'm sure that a lot of the repitition might be solvable via macros, and the whole E0117 thing caught me completely off guard... perhaps there's some more idiomatic way of doing it? But anyway. If a n00b can get the basics done in 3 hours it can't be that hard |
... The more I work on this the less convinced I am that the protections provided by svd2rust are actually worth the trouble for more complicated peripherals. Basic things, like iterating over identically structured registers, or writing a function that perform an action on whatever register is passed in, seem to be impossible (or at least so far beyond my current skill that I can't even figure out what I might search for), and as a result I end up having 28 copies of many actions, one for each of the register type. Where's the right place to talk about this? I'm trying the #rust-embedded channel, but it's a ghost town... |
I know nothing about CAN but given the discussion so far and the contents of this PR it seems to me that this is a complex enough topic that this should be iterated out of tree. Once the out of tree implementation is mature enough that it has implementations for several devices and has been tested (with different multitasking models) and benchmarked on hardware then we can revisit this topic and decide what parts to land in embedded-hal. How knows? Competing implementations with different tradeoffs may appear; in that case it would make sense to land an interface (traits) that lets you use all of them. It has been mentioned that the CAN frame format has not changed in the past N years and will not change because it's part of the spec. The pub struct CanFrame {
pub id: u32,
pub dlc: u8,
pub data: [u8; 8],
pub rtr: bool,
pub ide: bool,
pub reserved0: bool,
pub reserved1: bool,
} But this "parsed" representation is not only possible representation of a CAN frame. Another valid representation looks like this (based off smoltcp packet types): // simplified, i.e. not generic
pub struct CanFrame2<'a> {
bytes: &'a mut [u8],
} This These are implementation choices that have implications on the performance characteristics. These choices may also affect things like Send-ness and Sync-ness which can make the CAN stack unusable with some multitasking models, etc. So I think it's premature to settle on any kind of concrete type at this time. |
@japaric Out of curiosity, what does Out Of Tree mean? Is it like OOB for PRs... or for repos? |
Not in the master branch of this repo. So that could be a fork of this repo or some other crate. |
I'm working on a can implementation for STM32F103x. It works already, but interrupt, error handling is missing, and I'm planning to refactor a few things. There are lots of implementation specific details, so I agree with @japaric, that we need to have a few platform specific implementations before abstracting the things which all they have in common. Probably the initialization / configuration part will be fully device specific.
On the other hand, traits for canid, canmessage/frame, transmit mailbox(es), receive fifos/mailbox(es) may be a good subject for embeded-hal. I'll post my implementation here for review, as soon as i'm happy with it. |
Ping from triage: No update since quite a long time. Should we close that and work on other branch? |
314: Controller Area Network (CAN) Take 4 r=eldruin a=timokroeger Updated to the latest HAL changes: * Removed `try_` prefix * Moved non-blocking implementation to `nb` module * Removed default `blocking` implementaions ## Usage Example [stm32-fwupdate](https://github.com/timokroeger/pcan-basic-rs/blob/eh-take-4/pcan-basic/examples/stm32-fwupdate.rs) ## Implementations Updated for this PR: * [pcan-basic](https://github.com/timokroeger/pcan-basic-rs/blob/eh-take-4/pcan-basic/src/lib.rs) on top of an existing software API * [bxcan](https://github.com/timokroeger/bxcan/blob/eh-take-4/src/lib.rs#L460) Based on the very similar predecessor traits `embedded-can` v0.3 ([diff v0.3 -> this PR](https://github.com/timokroeger/embedded-can/compare/eh-take-4)) * [candev](https://github.com/reneherrero/candev) implementing the traits for linux SocketCAN * [socketcan-isotc](https://github.com/marcelbuesing/socketcan-isotp) ## Previous Discussion * #212 * #77 * #21 * #53 Co-authored-by: Timo Kröger <[email protected]>
Closed by #314. Thanks everyone for all the contributions and discussions! |
53: Update for release v0.4.0-alpha.0 r=posborne a=ryankurte Co-authored-by: ryan <[email protected]>
Note: this is not yet ready to go, I'm just creating a PR for the sake of discussion in #21