<re_irc>
<adamgreig> I don't fully understand what was incorrect with stable accepting them but maybe a future compatibility thing?
<re_irc>
<jannic> I also don't understand why inline asm shouldn't use those registers as in/output if specified directly. Sure, if you just use the register class, you probably want "r0-r7" so the selected register works with all opcodes. But if you say "r8" and then use it only with opcodes which can handle that, why not?
<re_irc>
Sure, it may be more work for the compiler to actually set that register, but it seems like that's correctly handled by current stable.
<re_irc>
<jannic> Perhaps there are edge cases which are not handled correctly, and which are difficult to fix?
<re_irc>
<jannic> But then, I also didn't find an example where using the high registers was really necessary, and which wasn't extremely contrived.
<re_irc>
<adamgreig> I gather it's because "I'm working on a fallback register allocator when the backend can't support inline assembly. The problem is that the reg register class is currently defined to include all registers r0-r14 so high registers may be allocated for the reg register class."
<re_irc>
<adamgreig> i.e. in the current llvm backend it might work fine, but perhaps that's a coincidence and not due to rustc doing the right thing
<re_irc>
<adamgreig> you can still work around it by manually writing the asm that uses the high registers in permitted instructions, right? just not with in/out operands (and you'd have to supply the clobber yourself)
<re_irc>
It would only be problematic if I actually needed to pass more than 6 registers in or out. Which in fact was how I found the issue in the first place: Instead of the table of function pointers, I tried to pass in the function pointers one by one. However, using the table isn't bad at all, and I guess that's generally a good solution when some assembly code needs lots of inputs.
<re_irc>
<jannic> And now that I look at the code again, I notice that I forgot to remove an obsolete comment regarding the registers.
rardiol has joined #rust-embedded
<re_irc>
<chemicstry> I'm looking for some library to ser/de configuration to flash memory, but so far haven't found anything that suits my needs. I want it to serialize like hashmap "(name, value)", so that it could survive firmware updates with different config structure and also do CRC in case flash memory is uninitialized. Is there anything like this? If not I was thinking of doing struct -> hashmap -> postcard -> crc, but maybe there...
<re_irc>
... is some better solution?
<re_irc>
<mabez> chemicstry: You can do that with postcard, you'll probably want a config representation that you use in your program (this could be the hashmap for example), then the in flash representation struct which you have to be more careful about adding stuff to. I believe if the struct is "repr(C)" fields won't be reordered so you'll always be able to add new fields at the end of struct with updates
<re_irc>
<mabez> I would also highly recommend putting a config version in, that way in the future you could have code that reads in v1 and writes it out in v2 format if you really needed to make breaking changes
<re_irc>
<mabez> +format
<re_irc>
<chemicstry> that would work, but I'm worried that you have to be too careful not to introduce breaking changes and also handle all versions manually. I was thinking of something more dynamic, maybe bson, where if it can not deserialize field (different type) or it is missing, it just takes default val
<re_irc>
<ryan-summers> I do exactly that with postcard, and yes you have to be careful not to break things to maintain backward/forward compatibility
<re_irc>
<ryan-summers> And I store a semver token at the start of the struct to identify what version is stored
<re_irc>
<ryan-summers> It is a fairly manual process though, and a hashmap based approach would definitely be more robust
<re_irc>
<ryan-summers> However, even that assumes that the format of the value in the hashmap is the same between firmwares
<re_irc>
<ryan-summers> E.g. if you switch from a float to a struct later on, it wouldn't be compatible
<re_irc>
<chemicstry> hm yeah, are there serializers that also store "type_id()"? Although that is also unstable IIRC and could change with different compiler version, hm
<re_irc>
<ryan-summers> If you have alloc, you can have it store a dynamic JSON object
<re_irc>
<ryan-summers> I think
<re_irc>
<ryan-summers> Similar to e.g. Python hashmaps, but I assume you ultimately will need to know the concrete type, so that may be a dead end
<re_irc>
<chemicstry> seems like serde_json can use default if field is missing https://serde.rs/attr-default.html but wonder how it works if types are incompatible
<re_irc>
<ryan-summers> serde_json also assumes alloc. Definitely make sure that is acceptable for your app
<re_irc>
<chemicstry> yeah, alloc was already forced upon me 😅
<re_irc>
<Chris [pwnOrbitals]> What’s the recommended way to listen on a high-speed serial port ? It’s 4Mhz. I’ve been looking at using DMA or interrupts, not sure which is the best and why
<re_irc>
<Chris [pwnOrbitals]> (on stm32h7)
<re_irc>
<ryan-summers> Depends heavily on your use case. If you know a set number of bytes you want to read before processing, use DMA to save CPU processing time. If you're not doing anything else, you can poll or use interrupts
<re_irc>
<ryan-summers> DMA is best to eliminate the overhead of multiple interrupts - you get one interrupt once you e.g. receive 128 bytes
<re_irc>
<ryan-summers> If you don't know how much data you want to read, you may have to use an ISR on each byte
<re_irc>
<ryan-summers> DMA is likely going to be more complex, but save you processor cycles
<re_irc>
<Chris [pwnOrbitals]> Hmm, yeah single-byte ISR looks better for my use case. I couldn’t find a clear way to do it in the HAL docs or examples though
<re_irc>
<ryan-summers> You would just listen on the Tx event and bind a handler to that ISR
<re_irc>
<ryan-summers> Then read the rx buffer
<re_irc>
<ryan-summers> s/tx/rx
<re_irc>
<ryan-summers> You would just listen on the Rx event and bind a handler to that ISR
<re_irc>
<Chris [pwnOrbitals]> the binding of the handler is done through some mechanism from the "cortex_m" crate I’d imagine ?
<re_irc>
<ryan-summers> Or RTIC
<re_irc>
<Chris [pwnOrbitals]> I’m using FreeRTOS so no RTIC for me
<re_irc>
<ryan-summers> Essentially, that depends on what framework you're writing your app in. Just look up how to bind in FreeRTOS
<re_irc>
<ryan-summers> generally some section on ISRs or interrupts etc. should show you how
<re_irc>
<adamgreig> I've found DMA + the idle interrupt works well for receiving variable/unknown length data
<re_irc>
<ryan-summers> Ah that's also a good idea - then cancel early if idling sufficiently
<re_irc>
<adamgreig> configure a DMA reception for the maximum possible size, on the IDLE interrupt you can cancel the DMA and read out what you got (or leave DMA going in a circ/double buf if you need uninterrupted transfer)
<re_irc>
<adamgreig> this assumes there's a transmission gap between frames of course
<re_irc>
<adamgreig> you can also DMA with half-complete interrupt, or double buffered, and process chunk by chunk for streaming large reception (and still interrupt on idle for completion of a frame)
<Lumpio->
Some STM32s also have a framing character interrupt which might be useful
<re_irc>
<adamgreig> at 4MBd your worst case is 400k interrupts/second which you could do but it's not nothing
<re_irc>
<ryan-summers> We have an app that supports up to like 600k interrupts/sec. It's doable for sure, you just need to make sure the ISRs are sufficiently short
<re_irc>
<Chris [pwnOrbitals]> this all makes sense, first I need to figure out how to bind the handler then I’ll try the single-byte interrupt and compare to the DMA solutions :)
<re_irc>
<ryan-summers> Essentially, complexity scales as you want more and more of the CPU cycles back. The easiest, most wasteful solution of polling the UART takes 100% CPU. Fully-DMA controlled likely takes <1%, but takes a lot more effort
<re_irc>
<Chris [pwnOrbitals]> Yeah, right now I’ve been using a low-priority polling thread but the bus traffic is pushing this to the limit now so I definitely need to upgrade
<re_irc>
<ryan-summers> Check what FreeRTOS has to say first. Yes you can write your own ISR, but you need to check docs to make things safe for the kernel
<re_irc>
<Chris [pwnOrbitals]> ah yeah, found some stuff. Looks like I can write my own through the pac Interrupts but I need to be careful to call the right Freertos functions ("somethingFromISR()"-like)
<re_irc>
<Chris [pwnOrbitals]> "Only FreeRTOS API functions that end in "FromISR" can be called from an interrupt service routine - and then only if the priority of the interrupt is less than or equal to that set by the configMAX_SYSCALL_INTERRUPT_PRIORITY configuration constant (or configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY). "
<re_irc>
<James Munns> ryan-summers: btw, now that there is a schema api (in unstable), it might be reasonable to make a crate on top of postcard that handles this in a better way.
<re_irc>
<ryan-summers> What do you mean by schema api? In postcard or in Rust?
<re_irc>
<ryan-summers> Ah gotcha. But even with a schema, you'd just be able to tell if something matched the schema or not. That doesn't necessarily indicate semver compatibility
<re_irc>
<James Munns> Yeah, that's true! Though with some host side testing, it's likely to proactively check
<re_irc>
<ryan-summers> E.g. you can have the same schema, but swap two floats around potentially, each with different permissible ranges?
<re_irc>
<James Munns> Yeah, you're limited to type info and name
Socke has quit [Ping timeout: 240 seconds]
<re_irc>
<James Munns> that being said: semantical values is something that NO tool handles, afaik, without some kind of constraint metadata or something?
<re_irc>
<James Munns> IMO if you have a type with limited acceptable ranges, you SHOULD make that at least a wrapper type
<re_irc>
<James Munns> which this could catch
<re_irc>
<James Munns> (you get the name of fields, and the name of types themselves)
<re_irc>
<ryan-summers> Yeah, I'm not sure there's a perfect (generic) fit for semantic data
<re_irc>
<James Munns> but honestly schema + snapshot tests, and/or maybe some logic (e.g. if you just add an enum field), would be a pretty good start
<re_irc>
<ryan-summers> Say for example you add a new field between 1.0 and 1.1 - for 1.1, if you read 1.0 data, you have to provide a default for the new field since it won't exist
<re_irc>
<ryan-summers> Enum field is an interesting approach. It's also worth noting that for my use case, I've touched the format of the structure like 1 or 2 times in 2-3 years, so the manual process of making sure compatibility is there isn't too hard
<re_irc>
<ryan-summers> But it _does_ require thought
<re_irc>
<ryan-summers> If you anticipate frequently changing the format of stored data, maybe you need to think through your app a bit more first
Socke has joined #rust-embedded
<re_irc>
<James Munns> My "best advice" so far is typically to have a top level version enum, where the field of the version doesn't change
<re_irc>
<James Munns> then have From impls from v2 -> v3.
<re_irc>
If you want to drop support for a version, you can replace the enum with an empty tuple or something
<re_irc>
<dirbaio> one struct per version will 2x your code size
<re_irc>
<James Munns> maybe, if everything is inlined
<re_irc>
<dirbaio> the deserialize code size I Mean
<re_irc>
<dirbaio> * mean
<re_irc>
<James Munns> if v2 and v3 have lots of the same children data structures, there's at least a chance it won't fully be doubled, especially if you set opt level size
<re_irc>
<dirbaio> ah right yup
<re_irc>
<dirbaio> still
<re_irc>
<James Munns> ymmv, benchmark for your use case, not one size fits all, you could do it with async, etc. etc.
<re_irc>
<James Munns> im not saying it's the only way
<re_irc>
<James Munns> just A way
<re_irc>
- if config comes from a controller/cloud server: the device tells the server the config version it wants, and have the server obeys
<re_irc>
<dirbaio> a few alternatives:
<re_irc>
- encode structs as key-value pairs where each field has an ID, so you can add fields bckwards-compatibly. Protobuf does this, dunno if there are serde impls that do
<re_irc>
<dirbaio> I kinda do a mix of the two: 2 for small additions, 1 if I completely refactor the schema of something, which is rare but I've had to do once
<re_irc>
<James Munns> your second suggestion could be done with field names and options
<re_irc>
<James Munns> oh, no, just kidding
<re_irc>
<James Munns> would be a problem if you added fields
<re_irc>
<James Munns> postcard does support k/v pairs, but only homogenous k/v pairs, so you'd need a second level of parsing
<re_irc>
<dirbaio> (and on 1, after all device firmwares are updated, you can completely drop the code from the server for the old version after a while, which is quite nice :P)
<re_irc>
<James Munns> but you could have a k/v of "int:bytes", and do a match on the int to decode the bytes
<re_irc>
<James Munns> since the first step is zero copy, it's actually pretty cheap
<re_irc>
<dirbaio> I just do it by hand 😂
<re_irc>
<James Munns> heh
<re_irc>
<James Munns> actually what I described is basically just serializing an array of enums with more steps
<re_irc>
<James Munns> basically it would make every type "Vec<Field>" :p
<re_irc>
<dirbaio> also you can do things like "if you see an unknown field, and the lowest bit of the field ID is set, then fail"
<re_irc>
<dirbaio> so if adding a "can only access when full moon and high tide" field, older firmwares will always give "permission denied" instead of incorrectly letting you through
<re_irc>
and if adding a "random uninteresting metadata for logging" field,, then older firmwares ignore it
<re_irc>
<dirbaio> depending what you're doing "always ignore unknown fields" is dangerous :D
<re_irc>
<James Munns> Yeah
<re_irc>
<James Munns> postcard is basically a collection of the MOST spartan choices
<re_irc>
<James Munns> since the answer is always "it depends"
<re_irc>
<James Munns> for config, message protocols, etc.
<re_irc>
<chemicstry> I'm pretty sure that "serde_json" fits my use case, it can use default value if field is missing and "serde_with" has a "DefaultOnError" for incompatible types. Since it's json, it should have no problem converting between changed integer types. Although I haven't tried it yet. It would have been nice to use BSON, but it's std only
<re_irc>
<dirbaio> and is there a way to get it to do the second? or at least something saner
<re_irc>
<dirbaio> it annoys me so much 😂
<re_irc>
<jhillyerd> Sometimes one-per-line method chaining looks good, but probably only with shorter argument lists. I have the same complaint as you on some of async web code I've been poking around with.
<re_irc>
<dirbaio> it's almost like it sees "oh this line is too long" and then goes split it at "one-line-per-method", except it has multiline "{}" so the actual prefix/sufix aren't actually long! :(
dc740 has quit [Remote host closed the connection]
<re_irc>
<pwychowaniec> > and is there a way to get it to do the second? or at least something saner
<re_irc>
I usually just split the code into two parts ("let result = self.spi.transaction(...); result.await().unwrap();"; same with collections, where I frequently do ".collect()" in a separate line, to keep the previous ".map()" / ".filter()" etc. formatted neatly)
<re_irc>
<dirbaio> feels bad to change the code due to rustfmt issues though :(
<re_irc>
<stealyourface> `#[rustfmt::skip]`
<re_irc>
<stealyourface> Not really an ideal solution either