/rant. - Page 1
Forum section: electronics / general

author: paulca
author_rank: Super Contributor
timestamp: April 04, 2023, 03:50:25 pm
content: Sending 16 floats and a frame sync byte via UART.00,00,00... F8Thumbs up, all good, no warnings, no errors, no fuss, all good. Even watched the UART TDR register clocking 00's and even caught an F8 going out.Working?Hell no. That's not what the chip sends. It sends something completely different. The scope sees this:00,00,00... 10 FFMaybe the scope is wrong.Well the receiver is just as bad. It reads the buffer about 50% of the time, errors the other 49% of the time and somehow gets itself out of a statemachine with all callbacks implemented and give up recieving.... and when it does come back as successful. It's just 0's.This should be easy. Why is it never actually straight forward?All of this for "reasons", *As there is no indication that any of this happening, both sides are absolutely sure they are sending and receiving it's just that both sides are making it up as they go along and neither realises it.F8 was seen leaving the UART TDR register. F8 is never seen on the scope and never by the receiver. Where did it go?This is like writing "Hello World" for god sake. What does a newbie do when the STM32 variant gives him back HHllllooa &^%&d! ?

author: abyrvalg
author_rank: Frequent Contributor
timestamp: April 04, 2023, 04:36:03 pm
content: Baud rate/frame format mismatch? Are you using some built in UART decoder on the scope? Could you post a raw waveform instead of decoded values?

author: rhodges
author_rank: Frequent Contributor
timestamp: April 04, 2023, 04:47:28 pm
content: This smells like something happens to the TX clock just after the TX empty flag. Does your TX empty interrupt do anything "interesting"?

author: eutectique
author_rank: Frequent Contributor
timestamp: April 04, 2023, 06:37:18 pm
content: What do you see when you send F8?Sending 00? Sending 00 F8? Sending 55? And AA?

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 04, 2023, 06:49:08 pm
content: Quote from: paulca on April 04, 2023, 03:50:25 pmIf you have a scope, can't you take a look at the actual UART signal and figure out why it interprets data in a way that is not what you intended?

author: langwadt
author_rank: Super Contributor
timestamp: April 04, 2023, 07:13:23 pm
content: gotta show some code, simply sending data with the uart isn't rocket surgery; wait for for the tx empty flag, stick a byte in the data register

author: cv007
author_rank: Frequent Contributor
timestamp: April 04, 2023, 09:09:23 pm
content: Maybe messing with the tx pin after the last byte put in txmit register, but not accounting for the fact a byte is still going out? The last byte gets corrupted and the receivers report what they see, including an additional start bit.

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 10:37:26 am
content: To be honest I spat the dummy out and went to eat something and watch a movie.I took another quick look before bed on the scope and I see patterns. Bunch of 0s then the 10FF (or 08FF) in LSB form. However I did see several F8's go past.My first check is going to be... what is in that memory I sent? It shows me as just a set of 0's for the floats. However, I have not checked the actual memory. There may be struct padding etc in there. The 10FF could be some form of padding and my believed length for that struct could be wrong and thus my receive buffer (nor scope decode) never sees a full struct.The other thing to check is... does the Statistics_t*2 take longer to send than to generate and it's "clobbering" the memory while it's being sent.On code, this is a basic generated HAL project.Code:typedef struct Statistics_struct {float rms;float peak;char FRAME_SYNC;} Statistics_t;Statistics_t stats;Statistics_t *busy_stats = &stats;Statistics_t *available_stats = &stats;Code:HAL_UART_Transmit_DMA(&huart2, (uint8_t*)&stats, sizeof(Statistics_t) * 2);Code:void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart) {if (huart == &huart2) {busy_stats = &stats;available_stats = &stats;return;}}void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {if (huart == &huart2) {busy_stats = &stats;available_stats = &stats;return;}Debug_UART_TxCpltCallback(huart);}void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {Debug_UART_ErrorCallback(huart);}void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {Debug_UART_RxCpltCallback(huart);}Code:bool handled = Analysis_Handle();if (handled) {haveHandled++;}if (haveHandled>10) {haveHandled=0;// snip LED indicator codeavailable_stats->rms = *output1L.active_rms;available_stats->rms = *output1R.active_rms;available_stats->rms = *output2L.active_rms;available_stats->rms = *output2R.active_rms;available_stats->peak = *output1L.active_peak;available_stats->peak = *output1R.active_peak;available_stats->peak = *output2L.active_peak;available_stats->peak = *output2R.active_peak;}

author: abyrvalg
author_rank: Frequent Contributor
timestamp: April 05, 2023, 12:52:25 pm
content: How can you be sure about bit alignment when you are reading from the scope? Do you count start/stop/parity(if any) bits? I.e. the 00 (with 8n1 config) will look like 0000000001, thelastF8 will be 0000111111111…. (endless 1 until next packet). What about bit time periods? Post a waveform, too many things to guess otherwise.

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 01:25:21 pm
content: The scope has a UART decoder. It has the correct params. 115200/8/0/1. It detects over/under runs, overlength bits etc.The waveform for the full struct is too long to fit on the scope screen or it's decode list.

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 01:37:13 pm
content: Something is fishy. Even if we assume the 0x80 0xFF's are actually coming as part of the data (I don't think they should).16 floats = 64 bytes.char = 1 byte (0xF8)However 0xF8 to 0xF8 there are 67 bytes. I can account for one, which may be the HAL quirk on DMA transfer sizes being length-1. But I can't account for 2. Nor can I keep 4 bytes alignment for floats that doesn't include the "char" somewhere.There has to be padding or memory corruption.Later I will replace the stats with "Hello World" and see how that looks.

author: eutectique
author_rank: Frequent Contributor
timestamp: April 05, 2023, 02:52:50 pm
content: Quote from: paulca on April 05, 2023, 01:37:13 pmIf you place your floats at the beginning of the structure, they are guaranteed to be aligned at the boundary offloat.Quote from: paulca on April 05, 2023, 01:37:13 pmIf you examine your structure in the debugger, what do you see? Is there a padding?

author: eutectique
author_rank: Frequent Contributor
timestamp: April 05, 2023, 03:04:29 pm
content: Quote from: paulca on April 05, 2023, 01:25:21 pmIs it 5 ms/division for the bottom trace?

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 03:19:25 pm
content: Interesting. Confers with the 67 bytes. Yet I only count 65.Memory looks "ok".Encouragingly, the memory looks like it has the first 4 channels of each populated with decibel readings for peak and rms.Note, I gave the device actual must input, rather than letting it idle at 0. 0s were easier to see problems... and I did.Leaves a few open questions though.Where does the 08 FF come from?Is the miss match in struct size padding/alignment around the 8 bit char?Are the two answer above the same?

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 03:23:48 pm
content: Oh. My DMA transfer size is set to "byte", not word. Is that likely to cause issues if the struct is padded around the char?I'll try "Word" soon and see.

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 03:28:44 pm
content: Quote from: eutectique on April 05, 2023, 03:04:29 pm5ms top, 20uS bottom. Although I'm not 100% if that's the divisions or the memory window.

author: AVI-crak
author_rank: Regular Contributor
timestamp: April 05, 2023, 03:29:09 pm
content: Very big nonsense to send a binary stream through UART. Not so - infinitely big nonsense!!!UART is designed to transmit text, any control character will drown in the binary stream.Any number can be made hex with minimal effort.Code:char* hex_char(char* tail_txt, uintptr_t* value)//44{int32_t tmp, jump; jump = 0;*tail_txt = 0;uint32_t vall = (uint32_t)value;do{tmp = vall & 0x0F;if (tmp > 9 ) tmp += 0x37; else tmp += 0x30;*(--tail_txt) = tmp;jump += 4095;vall >>= 4;}while ((vall )||((jump & 4096) == 0));*(volatile char*)--tail_txt = 'x';*(volatile char*)--tail_txt = '0';return tail_txt;};

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 03:35:13 pm
content: I have had that rumbling around in my head.Not least that I personally hate binary protocols as they are a nightmare to debug. If this was just the "Debug" UART statements I have peppered around... they work! I can read them!I was just being lazy avoiding the parsing and conversions.

author: eutectique
author_rank: Frequent Contributor
timestamp: April 05, 2023, 03:43:27 pm
content: For the record,sizeof(Statistics_t)is 68.

author: Siwastaja
author_rank: Super Contributor
timestamp: April 05, 2023, 03:59:58 pm
content: Absolutely nothing wrong with binary over UART.The worst debugging nightmares I have seen have been with ASCII parsers, specifically those where the authors are not fully aware of all possible linefeed, implicit linefeed conversions due to existing systems, and how to write code that is robust regardless of any linefeed type (or conversions) used. That is entirely possible to do, just that even big companies like HP fail at it regularly.Much more pain comes with deciding how to handle, if at all, backscpace and DEL controls. If not, there goes most of the human interoperability. If yes, that explodes parser complexity yet again. Whereas, the binary protocol has trivially simple parsers.Binary is much slower to read and produce "by hand", but in all other respects, it's so much easier to deal with that I go with binary protocols a lot. This ease includes ease of debugging, due to having much simpler parsers.

author: Siwastaja
author_rank: Super Contributor
timestamp: April 05, 2023, 04:03:47 pm
content: You simply forgot __attribute__((packed)). That is the enabler of simple binary protocols which do not need parsers at all. With the packed attribute, you can even do nasty and forbidden things like cast a pointer on top of any part of buffer, as the compiler won't assume alignment (works on all modern and sensible compilers). Does wonders on your codebase simplicity. Have been doing exactly that for almost a decade now and zero issues (regardless of what nctnico will say).Another option is to parse manually for every field from/to a regular struct. It is still easier and less work than with ASCII, but still some work. I personally like packed structs a lot, another benefit is you can enjoy the agony of language purists clenching their teeth while they try to prove how your code fails, but it's rock solid.You also forgot _Static_assert(sizeof(your_type) == 42, ""), where 42 is the placeholder for the size value you calculated manually. Double-checking, and using the language features to help automate that always pays off. A simple assert takes a minute to write, and possibly saves you from days of tedious debugging.

author: hans
author_rank: Super Contributor
timestamp: April 05, 2023, 04:24:03 pm
content: Quote from: AVI-crak on April 05, 2023, 03:29:09 pmSorry to put it; but thats Bullshit.The framing includes a start bit, payload, stop bit and parity. The UART hardware has no clue what is text or a control character. That is interpreted by your terminal. It makes sense you cannot interpret binary streams via a regular terminal. Some terminal tools like moserial have a 2nd tab that allows you to see the binary data. There are tons of bootloaders that work over UART, and they will send binary streams to devices without problems. This has been tried and tested on literally millions of devices. It's easy to count that amount; Arduino, ESP32, etc. all use UARTsTo be honest, I wouldn't be surprised if the scope traces are bad. UART is async so the transmitter/receiver statemachines are synchronized based on historic transitions of the line, e.g. a start/stop bit. These transitions will be used to run an internal statemachine, and proper sync is essential for a good decode, even for ASCII data. If you drag the scope into a random section of your (burst) transfer with the front part chopped off, it's very likely you won't get a good decode.You can't trust that a scope will try to start a decode at *any* position on the screen and then do some kind of error rate test to automagically figure out whats the right point to start. For scopes that only decode data that is presently visible on the screen, those decoders can be pure rubbish for async comms. I've got serial decode on my Rigol scope, but the UART is the one I use the least. I found it only works for a handful of characters that you can fit on 1 screen capture. If I want to capture more, I get a LA or (for UART) use a real serial cable. The only thing I do with a scope is verify timing.Now for a hw UART receiver to work properly, I also assume it can properly lock on to the data stream with a sufficiently long idle stream. Is there a reason to continuously send UART data while the baudrate is only 115200?

author: Siwastaja
author_rank: Super Contributor
timestamp: April 05, 2023, 04:26:10 pm
content: Quote from: hans on April 05, 2023, 04:24:03 pmBesides, the legacy of binary UART protocols go much further than that. MODBUS from 1970's is a relatively simple binary protocol over UART. There is an ASCII version because someone thought it would be simpler, but no one uses or wants to use that. MODBUS has a lot of weird complications but using binary is not among them - the ASCII version shares all the oddities, plus the increased agony of having to use ASCII.

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 04:30:54 pm
content: Quote from: Siwastaja on April 05, 2023, 03:59:58 pmI agree and disagree. With a textual protocol you can fabricate test and control messages to help diagnose and address issues without having to have an interface which itself may be bugged or unavailable. Examples would include JSON over REST versus say custom binary blob of socket.The issues you describe regarding the various issues with text format and what exact that is are pretty much solved and available publicly. Use an appropriate demonstrable pattern and format and use a well tested implementation of that.However in MCU land, as we have discussed, nobody shares anything and to be honest. Who wants a "full" JSON parse or YAML parser in their micro? Even I took short cuts with a piss poor performance serial tokeniser approach.For every shortcut there will be bugs.When you bring networks and operating systems in the loop, you run into many issues with the binary, such as host vs. network order and so on.

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 04:39:44 pm
content: Quote from: Siwastaja on April 05, 2023, 04:26:10 pmI think this highlights a point we agree on. It's the bloody users. If you dare to make an ASCII protocol, you absolutely must be prepared to handle ALL the human cases that the users will throw at it. If you say, "The spec says upper case there.", they will answer, "Yes, but... " If you make it binary, they haven't got a chance, don't know anything and are therefore less likely to know only enough to harm themselves or your lovely code.My JSON parser doesn't support numbers because I was lazy, I made the strings. If smoeone "out there" using my software expects sensible responses when he gives it "actual" JSON... the fun may be all his.

author: AVI-crak
author_rank: Regular Contributor
timestamp: April 05, 2023, 05:39:18 pm
content: Quote from: hans on April 05, 2023, 04:24:03 pmThe size of the data block in 99% of cases is not known to the receiving party. In the mode of full compatibility of different equipment - the block size is always unknown!!! You are passing a binary stream where the control characters are not unique!!! A control character in the wrong place can cause a data transfer error, and error correction will not help you. Just as waiting for the start control character after a crash doesn't help - because it might turn out to be false.UARTs are designed to transmit textual information, which literally means 1 character for every 4 bits of arbitrary data. The screams and screams of horror about the difficulty of deciphering the data are just childish babble. If it doesn't work, use ready-made libraries, there are a lot of them!!!If you want to send a binary stream through UARTs, then you need to use 9 bits of transmission. The ninth bit will act as a command/data switch, or an address/data switch.

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 06:14:47 pm
content: I fear "we" (royal) are confusing two (or more) protocol layers. I believe a lot of the issues described, IMHO reside not in the physical "link" layer UART provides, but in the upper layers surrounding what would be traditionally known as "Terminals", teletype or emulators. The VAST majority of differences occur there. I am thankfully below that layer. So if I want to be a cheap skate and funnel memory over it with no protocol in a loop, it should work.EDIT: Nobody mentioned the FRAME_SYNC. I'm both proud of that and ponderous. I'm not 100% on that, but the theory was.. it's a fixed length transmission, spin it in a circular array and update it as and when you can. Similarlly on the receiver. Effectively combining two MCU memory copies of that struct in a uni-directional link. (Oh... I want birectional later, but shit this uni-d should be easy?).The FRAME_SYNC is an "unlikely byte" such that alignment can be verified by checking the struct has indeed got 0xF8 as it's FRAME_SYNC. It also allows either synchronisation with another circular DMA Rx or single byte read until 0F8 and then read Frame length mechanic.I have simplified that on the receiver side to a single shot. Read 70 bytes. Find the 0xF8 before proceeding. It never finds a 0xF8. It's all 0's.I will pick the later up tomorrow and try and get it at least filling it's RDR register and DMA verified.

author: AVI-crak
author_rank: Regular Contributor
timestamp: April 05, 2023, 06:26:05 pm
content: I have seen many attempts by users to pass raw binary data over the UART, all of which failed.UART is for text.

author: langwadt
author_rank: Super Contributor
timestamp: April 05, 2023, 06:55:59 pm
content: Quote from: AVI-crak on April 05, 2023, 06:26:05 pmnonsense

author: voltsandjolts
author_rank: Unknown
timestamp: April 05, 2023, 06:59:46 pm
content: Holy cow, so much rant about something so simple!UART works just fine for binary.My preferred framing method isCOBS.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 05, 2023, 07:01:06 pm
content: Quote from: AVI-crak on April 05, 2023, 06:26:05 pm

author: agehall
author_rank: Frequent Contributor
timestamp: April 05, 2023, 07:13:24 pm
content: Yes, everyone knows you must use pigeons for binary.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 05, 2023, 07:36:26 pm
content: Well, I for one have tried learning the trumpet but I was never any good at it. Impossible to play any pleasant sequence of notes, so I decided that trumpet was an instrument just made to make noise and wake up soldiers.

author: paulca
author_rank: Super Contributor
timestamp: April 05, 2023, 07:53:43 pm
content: Quote from: SiliconWizard on April 05, 2023, 07:36:26 pm1*/s offset tangent... Very much like Python in my opinion. Where someone invents a technology to solve problem X for themselves (they felt, shit like this (post/thread), sucked doing it in C so they wrote a script language to call C for them). Everyone jumps on it because it's f'ing excellent.. Brilliant for beginners as it's so simple. For small scale RAD "rapid application development" - favourite of pre-sales and RandD teams - its perfect. Fast forward 10 years and people are trying to use it to solve stupid problems via 15 deep in libraries) for which it wasn't only never intended for , but it a significant liability for and actually shit at. And the language itself is so full of bugs and WTFs? that they simply cannot fix now because so many newbies learnt their entire career understanding how Python does things and that doesn't align with reality in so many cases....Aka. Over or extreme reuse/recycle. Surprisingly the hardware folks get that far more often than in software. We tend to get it the other way.C is just as bad, but at least C had the argument that it gives a pretty clear representation of what actually happens in the hardware. Python? It takes an RP2020 2 seconds to count to 10 million in python. I have seen people make python code 10^3 times faster by simply aligning the function calls to their C counter parts underneath and avoiding python. It still ran 10^16 times faster in C and resulted in only twice as much code.I watched a guy explain a particular audio filter for an hour. When it got to the interesting part I was hoping for a sample by sample or an 8-16 byte window, maybe a millisecond, but... he produced a python script with took 36 seconds to process a 3.2second mono guitar clip. What the actual? < Picard Face Palm MemeAnother /rant.

author: langwadt
author_rank: Super Contributor
timestamp: April 05, 2023, 09:21:28 pm
content: Quote from: paulca on April 05, 2023, 07:53:43 pmhe forgot the part about, once you have verified and tuned your filter in python/matlab/etc. you rewrite it all in c,assembler, or similar so it will actually work in real time

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 05, 2023, 10:02:45 pm
content: Quote from: langwadt on April 05, 2023, 09:21:28 pmOr, you don't, and it doesn't work, and then you start playing the trumpet instead.

author: Siwastaja
author_rank: Super Contributor
timestamp: April 06, 2023, 05:45:19 am
content: Quote from: paulca on April 05, 2023, 04:39:44 pmOf course, and you should.Because literally the only sensible argument for the ASCIIisthe human interoperability. But in the RealWorld^tm, you mostly come across UARTs parsers that fail exactly at that. Some fail more (to the point of being colossal pain), some fail less, but almost all fail.Typical experience with an ASCII-based embedded device (some examples that come into my mind: a super expensive IMU I interfaced with a year ago; or a hard disk debug interface; or a HP programmable power supply interface; CNC glue dispenser) is this:* You enter commands and nothing happens* Or maybe after you have entered two commands (wondering why the first did not work), then first of the two is processed* You Google for hours what terminal emulator programs to download and how to configure them to use one of the at least 4^2=16 line feed combinations (CR, LF, CRLF, LFCR, and yes, those can be different depending on direction)* When you finally get the thing to accept a simple command at all, you start using the thing* Because it's a terminal, meant to accessed by user, you type, use backspace, arrow keys, DEL - nothing of that works either at all, or worse, characters at wrong places get deleted or even more usually, what you see on the screen does not match what is internally in the buffer* Then you again Google for hours what terminal emulator programs to use and how to configure them to use the correct control codes or escape sequences. The number of combinations explodes to millions.* With the IMU mentioned, I had to find a specific version of a specific terminal program and compile it from sources to get the desired conversions; just to configure that IMU to enable it in BINARY MODE, after which it is usable.* You think you have finished a command and press enter and the thing does something completely different or crashes because the internal buffer did not match with your screen, because you pressing backspace was processed differently at the two ends.You can of course make a decision that the UART interface is not to be used by humans. But there goes the #1 argument for the thing. If you have to generate and parse responses using a Python script for example (which is a great idea to be honest), then you could have used a binary protocol just as well, and have the Python script make it human readable!The ASCII-controlled RS232 devices of the 1980's have all but disappeared. What remains are simple application-specific UART links, often in binary because it's more efficient use of tight resources, and easier to deal with.

author: westfw
author_rank: Super Contributor
timestamp: April 06, 2023, 07:54:54 am
content: QuoteKids today don't remember dial-up Internet, or the other protocols that did binary just fine.AX.25. SLIP. PPP. XRemote. ARAP. XMODEM, KERMIT, UUCP. even smart and graphics terminals transmitted stuff in binary.

author: AVI-crak
author_rank: Regular Contributor
timestamp: April 06, 2023, 01:19:37 pm
content: Physical layer protocols have a precise standard, violating publicly (to the outside world) is like screaming about your homosexuality. Data transfer protocols also have their own standard and precise description. You have the right to write your own new data transfer protocol, but you won’t be able to use it everywhere - they simply won’t understand you.Within one circuit board (at a short distance) - you can have any kind of sex. It does not matter how and where, the main thing is that there is mutual agreement.At the expense of UART, the transfer of 0x00 - for the terminal literally means the end of the communication session. Everything, now you can not send data to an external device, it no longer wants to communicate. Sending raw data will inevitably send 0x00. A normal terminal will still receive data, but it can no longer guarantee correctness. Because it expects 0xAA or 0x55.

author: paulca
author_rank: Super Contributor
timestamp: April 06, 2023, 01:47:43 pm
content: So debugging first the scope and then the receiver MCU and I believe the UART does not like the DMA loop. Neither scope nor reciever can stay locked onto the bit sync for some reason.Rx throws constant Overrun errors and occasionally Frame errors and start/stop errors, but doesn't receive a single byte.I think I've figure it out. The circular DMA is spamming the UART and giving no time to be idle. That makes the "START" conditions ... well... there aren't any.Doing a single capture on the scope "from boot" and the struct is correctly decoded. All 0s and a single 0xF8. Hitting "Single" again though while it's running and I get the weirdly rotated stuff again.I'll briefly look to see if there is a solution to that, or I'll just use a single shot DMA transfer instead of a circular array.I think I need idle line detection as well as inserting idle time into the TX stream.

author: newbrain
author_rank: Super Contributor
timestamp: April 06, 2023, 02:33:49 pm
content: Quote from: paulca on April 06, 2023, 01:47:43 pmWith one stop bit there is no way for the decoder to tell apart a stop+start situation from any set bit followed by a reset bit in the actual data..With more stop bits, the probability of getting it wrong goes slightly down, but it is still quite possible to have false matches.This is probably what the Rx part also experiences, leading to the errors you see, and it's (almost) completely independent from the choice of sending binary or text.It the receiver is enabled when the stream has already started, how is it supposed to synchronize?You need to insert an idle state for at least one character (10 bits for 8N1) duration every now and then to give it a chance to clear the previous errors and re-synchronize.Quote from: AVI-crak on April 06, 2023, 01:19:37 pmWhat? Where does this come from? Certainly not from the ANSI (~DEC VT) terminal standards?

author: Siwastaja
author_rank: Super Contributor
timestamp: April 06, 2023, 02:45:25 pm
content: Quote from: newbrain on April 06, 2023, 02:33:49 pmA very good point; UART is not really designed for non-stop datastream (text or binary, doesn't matter). You need to add idle times that act as synchronization points. For a perfect case with no errors, one synchronization point (e.g., at the beginning) would be enough, but in practice, you will want them "every now and then".Using two stop bits instead of one is equal to adding one byte-time of idle time after every transmitted byte, andsignificantlyimproves the probability of staying in sync or getting back in sync even if you do not add proper idle periods. What is proper? I don't know exactly, never thought about it deeply, but I'd think ten bit times should do it.

author: paulca
author_rank: Super Contributor
timestamp: April 06, 2023, 03:07:43 pm
content: Pfff...I made the transmission a one shot. It still writes 2 structs.When that completes it waits for 10ms before firing it again.The scope still decodes wrong. Although I can see my idle time.For the Rx code I tried using HAL_UARTEx_ReceiveToIdle_DMA(), expecting to get a partial or an error the first time, then have it restarted during the idle time and it should sync.... right?Well, The UART throws a set of random errors back so the ReceiveToIdle won't start. Grrr.. I got angry and put it in a while( broke ) loop and... it would not lock onto the stream.Stepping into the HAL code. The DMA aborts because having set the UART to Receive to idle mode and starting it, it throughs back and OverRun error or START/STOP error etc. Which immediately causes the DMA to abort.However, it doesn't clear the UART status. I have found this twice now. A failed UART does not stop the DMA and a failed DMA does not stop the UART, it just leave the other part hanging in LOCKED and BUSY state.So... I put HAL_UART_Abort() HAL_DMA_Abort() all over the place (angry code) and FINALLY it will lock onto the stream once in a while.I'm not even going further to check what it receives it's disgusted me now.I need to redo the angry code with something less like a hammer over the head.For example, surely there is a way to start the UART with HAL in "Wiat for idle", then in that interrupt reset the UART and DMA state and start the stream whlie the line is idle.

author: Siwastaja
author_rank: Super Contributor
timestamp: April 06, 2023, 03:16:48 pm
content: STM32 peripherals and their interaction with DMA is poorly designed and difficult to use. Poor, unhelpful libraries do not make that any better, as now you have to understand one more layer of crap.Compare this to nRF52 where youonlyhave the DMA mode, with just pointer register and byte count register in the peripheral itself (no mapping with a separate DMA), and a START triggering register where you write '1' to start. Could not get any simpler than that.

author: tooki
author_rank: Super Contributor
timestamp: April 06, 2023, 03:38:58 pm
content: Quote from: AVI-crak on April 05, 2023, 05:39:18 pmI’m sorry… what?!?As far as I know, the text encoding with the smallest word is 5-bit Baudot, but we don’t use that any more. Everything that’s not Unicode (which is 8, 16, or 32 bits) is some variant of 8-bit ASCII, with some EBCDIC and 7-bit ASCII lingering around.If you reeeaaaalllly stretch the definition, one could call Morse code a 4-bit encoding, since the longest codes use 4 dots and dashes. But that doesn’t translate cleanly to bits.

author: coppice
author_rank: Super Contributor
timestamp: April 06, 2023, 03:42:33 pm
content: Quote from: AVI-crak on April 05, 2023, 05:39:18 pmVery few people do that. Most people send 8 bit data with stuffing. Bit stuffing or byte stuffing is how the vast majority of digital communications, async or sync, deals with the need to distinguish data from control bits.

author: paulca
author_rank: Super Contributor
timestamp: April 06, 2023, 04:17:48 pm
content: Meh. HAL is a ballix.I started disabling the error interrupts I didn't want it to abort on UNTIL IDLE! and it sent HAL into an infinite interrupt loop. 0 main execution.Looks like I'll have to do the "Wait for idle" myself with the registers. Surely I can just blank the ISR, enable IDLE start it and wait for the interrupt. It's not going to be that simple, I know it.

author: rhodges
author_rank: Frequent Contributor
timestamp: April 06, 2023, 09:02:30 pm
content: Sorry if you wrote which processor you are using, but I seemed to have missed it.And just to get past two silly questions...Does your CPU have CCM, and can you verify you aren't trying to use it for DMA?Does your CPU have data cache that needs to be managed in software (like some MIPS)?

author: NorthGuy
author_rank: Super Contributor
timestamp: April 07, 2023, 02:16:18 am
content: @paulca You're approaching this very inefficiently.Imagine you would want to pick a digital lock - 4 digits - 10000 combinations - very time cosuming to try them all. That's, sort of, what you're doing - you wrote a program which uses many things at one - use floating numbers (with a little bit cryptic IEEE-754 format), pack them into the structure, use DMA to transmit, use your own receiver. Some of these things fail, but you don't know what it is, so you need to guess which part went wrong. If many different parts are failing, fixing one part may not solve the problem. This keeps you guessing and consequently you need lots of time (or lots of experience) to guess what is the exact problem.Now imagine you can pick the lock digit-by-digit - pick one digit, then move to the next. This would me much easier - only 40 possibilities instead of 10000. Similarly, you can approach programming software gradually, step-by-step. This is true for any software, but especially beneficial in embedded programming.So, if you would move step-by-step:- You first send a byte, watch it on the scope. Make sure the baud rate is ok, send different values, make sure they come out right.- Switch to the receiver. Write a simple program which receives characters and dump them on the screen. Receive different characters, make sure you receive what you send.- Go to the sender, build your structure, dump the binary content on the screen, make sure you understand it.- Send the structure to the receiver, make sure the receiver gets the binary content ok.- Fill in the structure on the receiver with binary content received from the sender. printf the structure and make sure it's identical to what you have sent.- Go to the sender, change to DMA. Make sure the receiver still receives everything fine- Change the receiver to DMA. Make sure everything is working.- Produce a stream of structures with pre-defined values. Add a checker to the receiver which can verify the stream of structures. Run it for an hour and make sure there's no errors.You can skip over steps if you feel confident, that is make steps bigger, but not bigger than you can handle. When you do a thing which you have done many times already, making bigger steps saves time. But if you're just starting with something new, try to take very very small baby steps ...Also, you probably need to change the design a bit - use a preamble (or a pause) to make sure that the receiver can find the beginning of the structure, add CRC if needed, add error handling (what to do with lost packets etc). This will require extra steps to the implementation.

author: paulca
author_rank: Super Contributor
timestamp: April 07, 2023, 08:26:02 am
content: I tend to take a different approach. Especially as I started with the auto-generator and HAL. When one calls a function to send data over a UART, one expects data sent over UART, not a tour of the camp and playing whack-a-mole with undocumented features.My general tact is to rapidly iterate. Start with the meat of the problem, the hardest bit. Assume it will work first time. Test that theory with a rapid disposable prototype.If it passes sanity tests, move on. If it fails, iterate in on it, peel back a layer or two and dig more deeply. If it still fails, try something else.When you have a basic straw man working, even if it's in parts, sometimes better if it is. You can loop backaround and refine.It's sometimes referred to as "fail fast", "learn fast" approach and it also minimises un-necessary effort by identifying possibly un-necessary rigor and skip it.I personally find it annoying, say in a screen share zoom meeting, when the person in control will diligently go through the process step by step each time. Did the request file get written? Yes. Open it and check. It's fine. Run the batch. Did it run. Did ... and so on and so on, all the way until 5 minutes later they fail due to a typo in the config of the last step. Do they go back and start again. Yep. Do they just run everything up to that point and start to debug there? Nope. They go back through the rigor of testing each individual step that led up to it. It's just wasteful. You have to work smarter than that. Be able to jump around, only dig holes you need to go down.As things firm up, you reiterate into the finer refinements and testing. There is no point refining, optimising or full testing something if it's not finished and you're not 100% sure what "finished" means anymore.

author: Siwastaja
author_rank: Super Contributor
timestamp: April 07, 2023, 09:43:54 am
content: Quote from: paulca on April 07, 2023, 08:26:02 amYour mistake is the same as with so many others - they assume ST's auto-generator and ST HAL are significantly helpful. They are not.This being said, take something whichshouldbe easier: higher level, general purpose OS designed for userland applications, much more tested, much more widely used: write UART TX/RX on linux, using the standard POSIX interface (and possibly linux-specific system calls). Surprise surprise: it's not any easier; you will spend hours when you do it for the first time. There is no simpleinit_uart("/dev/ttyS0", 115200"); uart_send(buf, sizeof buf); type stuff, you have to deal with weird interfaces and even more weird legacy things.With others (ST, etc.) being human and prone to poor design, laziness and mistakes, and with yourself being human and prone to the same issues, it's usually best to follow NorthGuy's strategy. Once you feel confident, you can write larger parts at once.

author: paulca
author_rank: Super Contributor
timestamp: April 07, 2023, 10:24:40 am
content: I got it syncing without DMA.Idle time on the Tx.ReceiveToIdle with a longer than required buffer.Detect successful idle to idle by confirming you got 2xStatistics_t size read and not the full buffer.Only then did the buffer begin at byte 1 of the struct and could be mapped to the struct and accessed. Only then did the FRAME_SYNC word end up correct. Only then did it render a far too fast to read negative dbm valueNext steps are to work out how to get that trick to work with DMA. Then torture test it a bit.

author: paulca
author_rank: Super Contributor
timestamp: April 07, 2023, 11:37:56 am
content: Grrr.I got DMA working, but with about 30,000 errors per second and only about 1 or 2 successes.The issue seems to be the interaction with the UART interrupts and DMA. If you start the DMA receive in the middle of the stream, it immediately errors out. It starts the DMA stream in "receive to idle" mode. However, if any error flags are set on the UART when the DMA attempts to start it will set the UART back to "Standard" mode. This is then validated in the HAL call and aborts returning HAL_ERROR;Spinning on it is literally the only way to get it to start while it's idle and only by luck. The first bit it receives, if it decides it's not a start condition, it flags it as an error, Frame error for example which aborts the process.I have a few more things to try before I either write my way around that HAL function or go back to circular and picking the data out by hand.I believe it's a timing issue with the HAL code. It starts the UART and immediately starts the DMA, then it goes and checks if everything started corrected. No surprises that, no, it didn't. Because the UART started in the middle of an existing stream and the very first thing it found was garbage, so it raised a concoction of errors. Overrun, frame error, etc. etc. I figure if I enable the UART myself, let it spit its errors out, clear the flags and then start the DMA on receive to idle while the UART flags are clear.Why DMA? It's a UI it's two main processes are both long winded transfers. UARTs and TFT SPI. At the same time it has to find time for the encoder polling (later) and also sending async on the UART. Doing all of those in sequence would drop the "frame rate" to about 0.5fps and make the encoders horrible.

author: langwadt
author_rank: Super Contributor
timestamp: April 07, 2023, 12:22:42 pm
content: Quote from: paulca on April 07, 2023, 11:37:56 amwhy not skip DMA and just use the RXF interrupt?

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 07, 2023, 04:35:05 pm
content: Peripheral ISRs are normally not required in DMA mode.You only care about the DMA, waiting to the transfer complete flag.All that HAL rant seems from people who actually didn't spend enough time with it.You can do most peripheral functionalities with just few calls.Now, you must first peek all the HAL functions and figure out what each one does, because the docs are anything close to great.Edit: For some reason HAL UART DMA needs both ISRs (DMA and UART) enabled.Anyways, this is working for me.Code:void uart_dma_test(void){ // Called from the main loopstatic uint32_t tim;const char msg[] = "This is a very long message sent using DMA!\r\n";if(tim <= HAL_GetTick()){tim = HAL_GetTick()+1000;HAL_UART_Transmit_DMA(&huart1, (uint8_t *)msg, sizeof(msg));}}So I wonder if you're going too fast, not properly checking if the DMA is done?Just check the HAL return. If getting HAL_BUSY, skip, but don't threat is as error.This works non-stop:Code:void uart_dma_test(void){static uint32_t tim;const char msg[] = "This is a very long message sent using DMA!\r\n";uint8_t res = HAL_UART_Transmit_DMA(&huart1, (uint8_t *)msg, sizeof(msg));if(res == HAL_OK || res == HAL_BUSY)return;else{uint8_t blink;if(res == HAL_ERROR)blink=50;else // HAL_TIMEOUTblink=200;while(1){if(tim <= HAL_GetTick()){tim = HAL_GetTick()+blink;HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);}}}}

author: paulca
author_rank: Super Contributor
timestamp: April 07, 2023, 05:07:17 pm
content: Do catch up. The Tx has been working since Wednesday night!Now, run the Tx in a loop and try and receive that. On another STM32 with DMA.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 07, 2023, 05:18:48 pm
content: Oh it's the RX then? Sorry, TL;DR.Probably the RX device can't hold up, as it needs more time to process the data after receiving?How about inserting a 10ms delay after each TX DMA transfer, to see if it fixes the issue, then reducing until finding the magic number?

author: paulca
author_rank: Super Contributor
timestamp: April 07, 2023, 05:50:08 pm
content: Quote from: DavidAlfa on April 07, 2023, 05:18:48 pmHow does it know when to start and how does it know when to stop? The stream is running already.If you try and start a DMA transfer with HAL the UART throws a frame error or other error as it picks up in the middle of the stream in the middle of a byte. That then aborts the DMA.So I have inserted some idle time (10ms) between transmissions. I am currently finding out how to start the UART. Let it settle, clear it's flags and then ... wait on it's idle flag (or interrupt) and immediately start the DMA while the UART is "idle".

author: cv007
author_rank: Frequent Contributor
timestamp: April 07, 2023, 06:48:33 pm
content: QuoteIf you will be using another pin later, use it now as a way to sync and when you go bidirectional later the syncing can be done via communications.

author: langwadt
author_rank: Super Contributor
timestamp: April 07, 2023, 07:45:35 pm
content: Quote from: paulca on April 07, 2023, 05:50:08 pmprocess the stream byte by byte using the RXF interrupt until you have found your a valid frame header, then start the DMA

author: paulca
author_rank: Super Contributor
timestamp: April 07, 2023, 08:43:50 pm
content: Quote from: langwadt on April 07, 2023, 07:45:35 pmAt one point I did have this in place. It wasn't working. However. That may have been a reflection of the unpacked struct over-running the buffer I had used and thus there were no consistent "FRAME_SYNC" bytes.I have considered this and other approaches, but the one I ended up with (not yet successful), is to start the UART, start a timeout. Spin BUSY=false and IDLE=true flags, then start the DMA.Not got that working yet. However the condition does readily exist in sane quantities (if that makes sense), it's not a sparse or rare event. Suggesting it might a good avenue.

author: AVI-crak
author_rank: Regular Contributor
timestamp: April 07, 2023, 09:30:47 pm
content: Quote from: langwadt on April 07, 2023, 07:45:35 pmIn 9-bit mode, the search for the start word (address) occurs at the hardware level. In 8-bit mode, you can hardware search for a start character, but only in text-receiving mode.In raw data receive mode, you will have to process each byte in software, because hardware tracking cannot be disabled in receive mode, and the UART itself will not have time to enter receive mode after an overload, and is guaranteed to lose data.paulca - there are many ready-made code examples on the net, completely free. It is not necessary to reinvent the wheel without having basic knowledge - it is always bad.

author: paulca
author_rank: Super Contributor
timestamp: April 07, 2023, 09:55:14 pm
content: Quote from: AVI-crak on April 07, 2023, 09:30:47 pmProvide one example.

author: paulca
author_rank: Super Contributor
timestamp: April 07, 2023, 09:57:18 pm
content: Look I get it. This SHOULD be simple.Why is it not? That's the point of the thread and it's title.

author: langwadt
author_rank: Super Contributor
timestamp: April 07, 2023, 10:08:50 pm
content: Quote from: AVI-crak on April 07, 2023, 09:30:47 pmnone of that makes a difference if you start receiving in the middle of a stream and no matter what you'll need the resources to process the data

author: MarginallyStable
author_rank: Regular Contributor
timestamp: April 07, 2023, 10:22:03 pm
content: Quote from: AVI-crak on April 05, 2023, 06:26:05 pmHaven't been around long have you.

author: langwadt
author_rank: Super Contributor
timestamp: April 07, 2023, 10:22:48 pm
content: Quote from: paulca on April 07, 2023, 09:57:18 pmhow could it be made simpler? if tx and rx starts a random times you can be off by bits or bytes so you have to devise a way to get the ends in sync

author: paulca
author_rank: Super Contributor
timestamp: April 07, 2023, 11:05:20 pm
content: Quote from: langwadt on April 07, 2023, 10:22:48 pmMy very first implementation of this I did, char by char interrupt, read the stream until receiving the last byte in the struct "0xF8", start the DMA stream in circular mode.

author: AVI-crak
author_rank: Regular Contributor
timestamp: April 08, 2023, 01:43:06 am
content: Quote from: paulca on April 07, 2023, 09:57:18 pmYes, much easier, you just need to do it differently.Let's say you have a structure that needs to be edited. Structure with raw data: double precision numbers, + unsigned numbers, + small bit fields, in general, complete inconsistency. It doesn't matter what's inside, the main thing is that the first in the structure should be a unique symbol. The characters "0-9,A-F" are already taken, zero cannot be transmitted, "\n,\r" - there is no point in transmitting (short distance). There are a lot of characters left for the role of the starter, let's say "G".You have a data sending event not in itself, but a very specific result of the program execution, when all the data in the structure becomes relevant and ready to be transferred. That's when it's ready, then you can call the function for forming a string from the structure into one of the buffers (there should be two). There, if the first buffer is already involved in the transfer, then you need to switch to the second one. When forming a string, you can discard the standard "0x" (short distance). After collecting the line, we check the operation of DMA, and if it is busy, then we wait, then everything is standard.Now we need to receive the data.DMA is configured for a ring buffer, and works to receive without stopping. The UART has a character match interrupt in which you just need to read the DMA position, and write (head) so that it is visible to your processing functions. The processing function itself is called either through a timer or non-stop. As soon as a number other than 0xFFFF appears, it must be read into a new variable, and then replaced with 0xFFFF.Next, we rewrite the contents of the ring buffer into the temporary buffer in the waiting loop for a new value of the DMA counter (read directly from the register). Or use a delay (the size and transmission time are known). You can restore binary data on the fly, or you can when a sufficient amount has already accumulated, it doesn’t matter.Data checksum verification and recognition of different structures - at your discretion. Once everything fits together, you can copy the buffer data into the link structure for further use.What the Internet does not have is the functions to handle the transmission failure mode. For the physical layer, it seems to be as it is, but write something purely into the debug port.

author: paulca
author_rank: Super Contributor
timestamp: April 08, 2023, 11:21:20 am
content: I did spot the character match interrupt while debugging. Interesting feature.There isn't really much in the project so I it's total disposable.... well on the Rx side. The Tx is very busy. So busy that if I want to run it in debug without optimisations I have to disable parts of the processing with an ifdef.You are suggesting not to use idle time to detect the start of the data, but to just loop it in a circular/double buffer on both sides and use the character match interrupt to point to the start of the data.That has some sharp edges too though. Knowing where the start of the data is doesn't tell you if the full set is received. I suppose it's just a state machine. DMA gives me something, I copy it from the "head" last pointed to by the character match interrupt until I reach the end of that section of ring buffer. If I find another delimiter/start char along the way I can mark that buffer as ready, if I don't, I go back to waiting on more.The only mechanic in that I don't like is the copy. Although, a memcopy of a 100 or so bytes isn't going to bother anything and makes the "shared buffer" problems a lot more simple when you can have public/private *copies* rather than pointers.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 08, 2023, 11:44:41 am
content: Quote from: paulca on April 05, 2023, 10:37:26 amSo where are your statistics variables, modified inside the ISR callback, declared as volatile?

author: Siwastaja
author_rank: Super Contributor
timestamp: April 08, 2023, 11:49:10 am
content: My favorite pattern lately:* no DMA* A lean, simple peripheral interrupt which parses just the stream delimiters (no content parsing), writing to one of the two buffers,* when said ISR detects full packet condition, it* swaps the two buffer pointers,* Triggers lower-priority software interrupt, which will run after the ISR returns (on Cortex-M, NVIC->STIR)* Software-triggered packet handler ISR processes the buffer (no memory copying), it has time until the next full buffer finishes thanks to double buffering.Of course, even a colossally simple ISR will be at least some 50 clock cycles including interrupt latency. Maybe if you set the UART in some highest possible baud rates, it might be too much.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 08, 2023, 12:25:39 pm
content: On every UART DMA transfer, three ISR are happening:- DMA HT- DMA TC- UART TCHAL enables all DMA interrupts by default:Code:HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size).../* Set the UART DMA transfer complete callback */huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt;/* Set the UART DMA Half transfer complete callback */huart->hdmatx->XferHalfCpltCallback = UART_DMATxHalfCplt;But UART_DMATxHalfCplt is empty by default, does nothing, made so the user can override this weak function for its own purposes, thus wasting resources when unused.You can avoid this by either modifying the HAL code:Code:huart->hdmatx->XferHalfCpltCallback = NULL;Or by inmediately disabling the HT interrupt after calling the HAL txfer:Code:__HAL_DMA_DISABLE_IT(huart1.hdmatx, DMA_IT_HT);

author: AVI-crak
author_rank: Regular Contributor
timestamp: April 08, 2023, 01:38:19 pm
content: Quote from: paulca on April 08, 2023, 11:21:20 amIt is possible to avoid copying from the ring buffer, for this it should have a size from 5X to 20X the size of the structure (data line).A small clarification - text is transmitted through the physical interface without a terminating zero, exactly twice the size of the structure, literally. It can be observed in the terminal with the automatic line forwarding mode ("\n,\r" do not pass!!) - without character distortion.In fact, the assembly of the string and subsequent processing of the string is as fast as possible, one byte requires 14 assembler instructions.And yet, the memory area of ring buffers and buffers of transmission lines - you need to locate in a memory area isolated from caching. Otherwise, debugging will be a lot of fun.

author: betocool
author_rank: Regular Contributor
timestamp: April 09, 2023, 04:11:04 am
content: If I may, @paulca, I think a better approach would be to use Cube MX to initialise all the peripherals, except DMA. As someone before mentioned, HAL DMA is clunky at best and confusing.I find it way easier to init UART, and then manually init the DMA channel for TX and RX. Admittedly, RX can be a bit trickier because if you don't know how many bytes you're expecting to receive, but it's still doable. You'd be surprised how easy it is to configure DMA transfers using registers and DMA half and complete interrupts.Another suggestion... I've never had any problems using binary protocols over UART. I use protocol buffers (protobufs on the PC end and nanopb on the micro end) to transfer what is effectively a struct packaged in another format.Believe me, it is a "fail fast" approach, and easy enough to make changes without looking at the HAL function, however, you do need to have a look at the DMA configuration. Once you get that under your belt, you'll see how easy it is to implement for other peripherals as well.Cheers,Alberto

author: paulca
author_rank: Super Contributor
timestamp: April 09, 2023, 10:50:43 am
content: I probably will go hybrid again.I have done this in another project/component. My WS2811 indicator LED code using a single timer for multiple purposes in a sequence. When I tried to reconfigure the timer from a PWM w/ DMA to a basic output compare "time elpased" affair, HAL just fought me at every step.In the end I set up and each timers working with HAL. Then "flattened" the hal code into a function, for each operation/config etc. Then removed almost all the crap, leaving only a few breadcrumb for hal such as the structures and reused the call back pointers etc.You are right. My WS2811_Indicator_LL.c ended up only about 50 lines of code, including callbacks.What I don't like about it. It's as brittle as glass. I have moved it to 4 STM32 micro's now and each and every time it needs some reference manual digging to find out where they move, renamed or resized some register between them. I suppose that IS why I split the LL file out. Hopefully that will firm up in time and be easier to reuse.I think there is a lot of low hanging fruit accessible by HAL, however, the green grass lawns of that basic stuff rapidly becomes a muddy slope upwards on a learning curve with HAL and the little mazes it presents which start to look like far more work than just reading the reference manual and doing it yourself! The low hanging fruit is fine. Grab it and run. The next stuff up, slightly higher, only looks "slightly higher" it's not, you will spend days trying to climb that tree to get them. Just go and get the long poles (the registers).