[C][ARM] changing variables also accessed by an interrupt routine - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: February 17, 2022, 10:43:15 am
content: I have an array that is the screen buffer to store characters that are repeatedly put onto a character display. When I want to change the display text I copy the data into the array. The screen is constantly updated from an interrupt routine that cycles through each character.Is there any possible issue that can arise due to the interrupt routine wanting to access a variable as it is being updated? I thinking no as the current instruction has to end if the interrupt fires, so the data will be there or the code that is about to write the data has to wait and then write after the now old data was read.

author: RoGeorge
author_rank: Super Contributor
timestamp: February 17, 2022, 11:07:12 am
content: If the array have some long datatype that requires consecutive assembler instructions to write a single variable value, then you have to make sure the writes/reads areatomic.For example, when the normal program read or write a variable that requires more than a single memory address access (lets' say a long long int of 64 bits or more), the interrupt may occur somewhere between the first and the second memory address access, so the interrupt might change the value in the middle of a read from the main program, which would be bad, the main program would end with a corrupt value.The interrupt occurs after each machine instructions, not after each C line, so it can happen in the middle of a read or halfway of a write of a C variable that is stored on multiple bytes.I think GCC has some "atomic" mechanisms, but I don't master C well enough. I only google about atomic in critical cases, otherwise I just disable the interrupts while working with the memory area that can be changed by an interrupt.

author: Simon
author_rank: Unknown
timestamp: February 17, 2022, 11:09:12 am
content: it's a 32 bit system and these are 8 bit variables.

author: tggzzz
author_rank: Super Contributor
timestamp: February 17, 2022, 11:56:08 am
content: Quote from: Simon on February 17, 2022, 10:43:15 amI would avoid that architecture.In general interrupts should capture the necessary information, and pass that to a background task for processing.I would have a task (or equivalent) that processes the information and updates the screen when necessary. The interrupt routine should capture the information and put it in a shared buffer then set a flag to indicate that the task should run. The scheduler (or equivalent) looks at that flag, not the buffer.The benefits are thatthe interrupt/background interaction is limited to a single simple variable, the flagit is easy to inspect the binary to determine that the compiler has generated the code you wantinterrupts are short durationyou can test and debug without hardware interrupts being present, and add the hardware when all that is satisfactory

author: Simon
author_rank: Unknown
timestamp: February 17, 2022, 12:12:26 pm
content: It's one of those hitachi knockoff character display controllers. A small amount of code runs each time the 100µs interrupt fires, it takes 2 cycles to put a character on the display so it's not taking much time. This means that it just runs in the background constantly updating the display with whatever I put in the buffer. I have now created a flag variable that stops the screen refresh function running while I am putting any data into the buffer array.I don't think this was causing any problem but I have locked out simultaneous access to the display buffer array to be on the safe side.

author: tggzzz
author_rank: Super Contributor
timestamp: February 17, 2022, 12:28:19 pm
content: Provided you have the appropriate modifiers on the variable declarations, that should work in this case.Nonetheless, "my" architecture is general purpose, scaleable, predictable, and testable. The last two are always advantageous.

author: Simon
author_rank: Unknown
timestamp: February 17, 2022, 12:30:55 pm
content: Well this was meant to be quite general purpose. If i take this board to a new project the code will start with this project with everything stripped out bu the display updater with it's buffer array ready to have data put into it.I have moved the screen updating call to the main program based on a flag. It works just the same.

author: tggzzz
author_rank: Super Contributor
timestamp: February 17, 2022, 12:33:54 pm
content: By general purpose I was implying a radically different project.With the screen updater in the main program, you can easilyknowwhen it will and won't execute. That might be advantageous in cases where it is less urgent than some other activity.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 17, 2022, 01:19:14 pm
content: Simon's system, if I understood correctly,generatesthe data outside of ISR, and does the display bus access in the ISR.It's exactly the correct way to do it. No need to turn it around.All you need is guarding against non-atomic operations.The problem iscontent updatehappening in parallel to transferring that content into the display. Because updating the display is a never-ending process, you can philosophically say the content is always partial. But the interesting notion is, how the content is partial. Is it:* Lacking part of a single value (for example, lowest 8 bits out of 16-bit value are old, highest 8 bits are new; this results in corruption, as the mixed value makes no sense)* Lacking part of a single full screen frame (say, upper half of screen is old, lower half of screen is new)* The whole display frame is old dataGiven this, the practical implementation options are, respectively:1) don't do anything - temporary display corruption occurs when the content is updated at bad timing2) guard against single word accesses by disabling ISR- no corruption, but it's unpredictablewhenthe modification is visible: this frame, or the next frame- in moving video, this could look like smear (part of the picture is from previous frame; what part, that depends and jitters)- this is likely perfectly OK in Simon's case- slight jitter caused to ISR timing because of atomic safeguards disabling the ISR3) double buffering of the whole display content- no corruption; all updates done to the buffer apply at once, to thenextfull frame- requires more memory- requires some buffer handling / swapping code- causes a delay in display updateTo implement 2, qualify the variable that needs to be written in the main thread loop,volatile, and surround writing to it by first disabling, then re-enabling interrupts.Unless you are 100% sure the operation is inherently atomic; then volatile alone is sufficient.You can also look at the C11 atomic types.

author: FlyingDutch
author_rank: Regular Contributor
timestamp: February 17, 2022, 01:31:36 pm
content: Quote from: Simon on February 17, 2022, 10:43:15 amHello,you can use simple semaphore variable (even bool), and set it to true when you starting update the array, and set back to false when updating is done.The in interrupt routine have to check if this variable is true - if yes then exit from routine without refreshing display. And vice-versa - set this variable in interrupt routine and check in place before updating an array.Best Regards

author: WatchfulEye
author_rank: Regular Contributor
timestamp: February 17, 2022, 01:39:55 pm
content: Potential issues that can occur when sharing a variable between a main program and ISR are: non-atomic operations and potentially reordering of operations.Not all memory loads and stores are atomic (occur in a single define operation) e.g. A uint32_t operation will likely be broken into multiple sub-operations on an 8bit architecture; and a double or int64_t operation will almost certainly be broken up on any MCU. Whether this matters depends on the data and its usage. For a frame buffer, this may not be relevant (if an ISR reads a shorn write, then it will be fixed next refresh) . For acquired data or a process control output this may be very relevant, as a partially completed operation could result in corrupt data being processed.The solution, if one is needed, is to use a datatype which is known to be atomic on your hardware, or to use an atomic operation library to ensure a logically atomic result (see atomic.h for c, or std::atomic for c++).The other issue is ordering of operations. Compilers, especially optimising compilers, may change the order of operations from what is written. The assumption the compiler makes is that the state of the program at the end should be correct, but not necessarily the state at any intermediate point. If you are sharing data with an ISR, correctness of the intermediate states may be relevant.Consider the following pseudo-scenario, If your ISR is forwarding status messages of a football match, to a main loop which will print the final score, then the order in which The main loop checks the "match over" variable and the "score" variable matters. If the program checks "score" first, the ISR may add an extra goal and finalise the match before the loop checks for finality, and there is a risk of a wrong final score being printed. So you write your loop to check "match over" first. The consistency problem is now fixed.Or is it? If the compiler or hardware has optimised your code and reordered the operations, then the code may not work correctly. In fact, the compiler might have noticed that the score and match over variables are never modified in the main loop, and simply deletes them (why check these variables if they never change?)The solution in this case is to advise the compiler that these access to these multiple variables must not be reordered or skipped and they must be accessed exactly as written. In c, this is done by marking the variables as volatile.In the case of a frame buffer, this should be marked volatile to ensure that operations are not deleted/skipped.

author: Simon
author_rank: Unknown
timestamp: February 17, 2022, 02:15:48 pm
content: Quote from: FlyingDutch on February 17, 2022, 01:31:36 pmThis is what i have been doing. Using a flag in the interrupt routine and updating when I see it set yields the same results.Corrupt data on the screen is not an issue, it's updating 62.5 times a second so the screen always looks flawless.

author: hans
author_rank: Super Contributor
timestamp: February 17, 2022, 03:11:41 pm
content: In this case, if the IRQ is only moving data around and cannot get stuck on some kind of control depending on the data.. then I don't see any issue with this. Worst-case you're printing out a string of 16 characters, and on char X the IRQ fires and writes the old character X to the display. On a PC you might say you observe some screen tearing, where the top and bottom half parts are of two separate frames. That is annoying but often quickly resolved, since I assume this program is not constantly refreshing but not rendering new frames.Do you really want to go to the effort to synchronize screen buffer pushes to the rendering functions? (such as Freesync technology on PCs now works)I guess you could, and perhaps it's an all round nicer approach, but I don't think this structure would compromise stability, with or without semaphores/atomic instructions. Certainly for larger framebuffers (e.g. pixel displays), I would go to the effort to mark a bounding box around the pixel data that is "dirty" and needs to be refreshed on the display. But depending on the display size, that is only an optimization step.If you're working with larger variables (16-bit on a 8-bit MCU, 64-bit on 32-bit etc.), and especially when that variable will be used for some kind of loop, then I would be a lot more careful, in both directions (IRQ write or main-code write). If you write to a variable in an IRQ, while the main code takes 2 instructions to load it, you can get race conditions and strange behaviour that only occurs very rarely (depending on how often the IRQ fires and/or variable is loaded). In that case I would make sure to work with atomic structures, such as exclusive load/store or disable IRQs.

author: tggzzz
author_rank: Super Contributor
timestamp: February 17, 2022, 04:11:41 pm
content: Quote from: Siwastaja on February 17, 2022, 01:19:14 pmIn that case why not have one thread generating the dataandwriting to the display. No need for interrupts.Possible variant: only write to the display when the data has changed.

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 17, 2022, 06:01:01 pm
content: Just disable interrupts before updating the data and reenable them after done.Don't disable the interrupts for the whole process of getting/parsing the data, use a temporal buffer instead, and when done transfer the data to the LCD buffer disabling interrupts as said.

author: Simon
author_rank: Unknown
timestamp: February 17, 2022, 07:07:16 pm
content: Quote from: tggzzz on February 17, 2022, 04:11:41 pmIt's a system of pages of data. Rather than use flash space to store the data in an array only to copy it to a same size array in RAM I call from the pages array that is a constant the page I want. I then fill in the blanks with the variables I want to dispaly. So I do a memcpy to the buffer when the page is changed and then update specific locations of the array once a second with the lastest data. All the time the buffer is being written to the display.To generate the data and put it on the display means taking a chunk of continuous time to do that, it also means delays because this is a parallel display (all they had in stock). So doing it in an interrupt with a variable that keeps track of where I am in the update sequence works very well as the time taken is small and at regular intervals rather than say 25ms of doing nothing but updating the dispaly and wasting the vast majority of the CPU's performance during that time.1) Interrupt fires and data is put on the bus and clock raised, exit interrupt.2) Interrupt fires and clock is lowered. exit interrupt3) goto 1)My problem seems to lie elsewhere. something is going wrong somewhere and I don't know where. Now my buttons are mot being read. but then they are, it's like the CPU randomly locks up.I start to wonder if it's something going wrong with memcpy() as this is the only piece of code I have not written and I cannot inspect it.

author: Simon
author_rank: Unknown
timestamp: February 17, 2022, 07:10:39 pm
content: It seems I need to learn to use the debugger, that is if anyone can be bothered to teach me to use it. It looks like I have lots of videos from microchip where they will show me what I could do if only I knew but actually showing me how breaks some law of idiocy and stringing this stuff out to make it look like it's more than it is.

author: Jeroen3
author_rank: Super Contributor
timestamp: February 17, 2022, 07:31:18 pm
content: Quote from: Simon on February 17, 2022, 02:15:48 pmStop worrying about the data corruption then. Obviously there is no harm in bad data integrity you probably won't even see it.

author: Simon
author_rank: Unknown
timestamp: February 17, 2022, 07:45:08 pm
content: I'm not worried about corrupt data as unlikely as that is as a mere 1 byte is moved at a time and the data is refreshed on screen every 16ms. What I am worried about is that my program now locks up with another rather unusual effect. I wanted to make sure I was not triggering something. From what I can now tell when I start, I get as far as the first memccy and data going onto the screen, then it all goes awol.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 18, 2022, 07:18:34 am
content: Nothaving any write guards can only lead to screen data corruption (in case the write isnotatomic; now I'd guess itisatomic, hence no corruption). But as writing to the LCD is one-way process, there simply is no way it could cause any hang-up, delay, or lockup. Your lockup problem is elsewhere. Keep looking.Be very careful with memcpy.

author: Simon
author_rank: Unknown
timestamp: February 18, 2022, 08:10:02 am
content: In my hunt for sample code when working with the non volatile memory controller I found what I think is some microchip code that seems to include it's own version named with a capital M:Code:void * Memcpy(void* dst, const void* src, unsigned int cnt){char *pszDest = (char *)dst;const char *pszSource =( const char*)src;if((pszDest!= NULL) && (pszSource!= NULL)){while(cnt) //till cnt{//Copy byte by byte*(pszDest++)= *(pszSource++);--cnt;}}return dst;}I assume this is returning a pointer (void * ) it is because "dst" is returned presumably as a pointer to the last location? maybe as a means of calling code checking it ended in the right place?I'm thinking of using that maybe with the returned value removed.Can compiler code optimization cause any issues?

author: tggzzz
author_rank: Super Contributor
timestamp: February 18, 2022, 09:31:06 am
content: Quote from: Simon on February 18, 2022, 08:10:02 amYes, many many many problems, both obvious and subtle.The higher the optimisation level, the more the compiler writers optimise against whatisn'tsaid in the C standard. Many people don't understand what the compiler flags and C keywords do and don't guarantee. Volatile is a classic example of that.Higher optimisation levels can lead to "surprising" removal and re-ordering of code.

author: langwadt
author_rank: Super Contributor
timestamp: February 18, 2022, 10:15:13 am
content: Quote from: Simon on February 18, 2022, 08:10:02 amthe return value is just the destination unchanged

author: Siwastaja
author_rank: Super Contributor
timestamp: February 18, 2022, 10:16:48 am
content: Don't reimplement memcpy. The standard from C library or compiler builtin will work.The only problem is, memcpy doesn't accept volatile arguments.memcpy in general is just prone to stupid programmer mistakes like getting the length wrong. Reimplementing it in the same way won't fix this.

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 18, 2022, 10:22:59 am
content: Atomic writes to a single variable won't cause issues, but probably you're updating more than a single variable (an array of few values, or a text string), thus it behaves very much like a non-atomic operation and it's very probable that at some point the interrupt gets weird data.Nobody said anything, about what I posted earlier. What's the problem with simply blocking interrupts during these few instructions writing to the variables?If you can't do that for whatever reason, a flag would do the job, ex. "buffer_busy", set before writing to the LCD buffer, and clear after.The flag should be checked by the LCD interrupt, and skip LCD update if active.Not easy to guess without any code to review.

author: Simon
author_rank: Unknown
timestamp: February 18, 2022, 11:11:27 am
content: Quote from: DavidAlfa on February 18, 2022, 10:22:59 amI have the flag but it has not helped. I have some other issue somewhere.

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 18, 2022, 12:20:24 pm
content: Are you updating the buffer between interrupts, or after all chars are sent? Otherwise you might start sending a string, get updated in the middle, and as result the LCD shows a mix of different strings.So you might need to set a 4 level status: idle, busy, pending and transferring.So:- The update process only executes if in idle state, sets busy before transfer and pending after done.- Interrupt only proceeds if in pending or transferring state. If pending, sets transferring state. Counts the sent chars, when all were sent It sets idle state to allow new update.That way there's no way you send random data.

author: Simon
author_rank: Unknown
timestamp: February 18, 2022, 05:07:44 pm
content: No it's something different and very subtle.So I use an up/down and select buttons to navigate my interface. I have things like a variable that keeps track of which page I am currently displaying. Now if I press dowwn from page 0 to page 1 that's fine. If I press up I go back to page 0. But if I press the up or down button twice or more I get something odd. the page does not go beyond where it should (array variable so I would get any jibberish), no, but when I go back it takes two presses to go back instead of one. This is no matter how many times more than once I pressed the button. Often when I press the first time I get a flash of the new other page but then it reverts straight back to the current page before this has loaded and I have to press again.I also get this when I display a variable on the screen that I want to edit. I have a maximum and minimum for that wariable, if I try to exceed the end stop it takes to presses of the oposite button to go back and I sometimes see the incremented or decremented value appear, but then it goes back to the last one and I have to press again.This is very strange. On the one hand it is the variables themselves that are tested to guard against them being edited. On the other hand these variables are being changed by something like they regain an old value. I really cannot fathom it.It's not interrupt related and I have turned off all code optimization.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 18, 2022, 05:37:45 pm
content: You just need to systematically debug the problem.For that, you need to add visibility; possible with a debugger, or by mechanisms like storing values in logging variables and print them out.It sounds your button press detection logic has a bug. Possibly with debouncing.

author: nctnico
author_rank: Super Contributor
timestamp: February 18, 2022, 05:44:13 pm
content: Maybe even test the code on a PC combined with using a debugger. That would take the embedded hardware with interrupts etc. out of the loop.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 18, 2022, 05:58:38 pm
content: On PC, you can just write test vectors likeint button_a[] = {0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0};Then printf() the heck out of every if-else branch with all variables of interest, and see what happens.Or you can single-step it in a debugger, no big difference.Doing it sucks, but then you get the aha! moment and fix it.Though, the ultimate target would be to learn to write more robust code, so that you actually understand what happens by just looking at that code. I rarely need to resort to tedious brute-force head-banging debug sessions of my program logic / algorithms anymore, although of course it sometimes happens.

author: Simon
author_rank: Unknown
timestamp: February 18, 2022, 06:05:51 pm
content: Yea, I need to learn to use the debugging. The little I have found just wants to show examples and results rather than describe the tools.I don't think there are switch bouncing issues:pushbutton.cCode:const uint8_t longpress = 50 ; // Long press threshold countsconst uint8_t shortpress = 1 ; // Short press threshold countsvolatile uint8_t buttonPressedCounts ; // loop counts button was held down for, one variable per pinvolatile uint32_t shortpressed, longpressed ; // short and long button states, one array variable per port#define setbit( x, y ) ( x |= ( 0x1 << y ) )#define clrbit( x, y ) ( x &= ~( 0x1 << y ) )#define retbit( x, y ) ( x & ( 0x1 << y ) )void buttonRead(uint8_t button){// If button is pressed increment button pressed counterif ( !pinRead( button ) ){buttonPressedCounts++ ;}// If "the button has been released" && "the press count is between" 0 && "the long press limit"// reset the press counter and set a button pressed state.if ( pinRead( button ) && ( buttonPressedCounts <= longpress ) && ( buttonPressedCounts >= shortpress ) ){shortpressed[(button >> 5 )] |= 0x1 << (button & 31U) ;buttonPressedCounts = 0 ;}if ( pinRead( button ) && ( buttonPressedCounts >= longpress ) ){setbit( longpressed[(button >> 5 )], ( button & 31U ) ) ;buttonPressedCounts = 0 ;}}uint8_t button_short_press(uint8_t button){if ( shortpressed[(button >> 5 )] & (0x1 << ( button & 31U ) ) ){return 1 ; // return 1 for short press}else return 0 ;}extract from 100ms task routineCode:void page_scroll(){if(editMode == PAGE_SCROLL){if ( (button_short_press(BT_DN)) && (currentDisplayPage < 1U) ){button_short_press_reset(BT_DN) ;currentDisplayPage ++ ;if (currentDisplayPage == MAIN_PAGE){print_current_values() ;}}if ( (button_short_press(BT_UP)) && (currentDisplayPage > 0U) ){button_short_press_reset(BT_UP) ;currentDisplayPage -- ;if (currentDisplayPage == MAIN_PAGE){print_current_values() ;}}}if (currentDisplayPage == SETTINGS_1_PAGE) // check to see if display is on edit settings page{display_u16(oilPressureThreshold, 3 , OIL_PRESSURE_THRESHOLD_SETTING_ROW , 14) ;}}So "currentDisplayPage" cannot be modified any further, it is it's own guard. But if I press the button more than once when I go the other way it takes 2 presses and "currentDisplayPage " is changing all by itself.

author: Simon
author_rank: Unknown
timestamp: February 18, 2022, 06:10:31 pm
content: The only think that comes to mind is, is my stack too small? I hardly know what I am talking about here but could I be falling off the edge of something?

author: cv007
author_rank: Frequent Contributor
timestamp: February 18, 2022, 09:35:24 pm
content: Code:if ( (button_short_press(BT_DN)) && (currentDisplayPage < 1U) ){button_short_press_reset(BT_DN) ;currentDisplayPage ++ ;if (currentDisplayPage == MAIN_PAGE){print_current_values() ;}}I would presume button_short_press_reset() clears the 'press' bit, so you are not clearing the presses when you are at page limits. Your presses are then stored for later, and then end up using them later, inadvertently.

author: Simon
author_rank: Unknown
timestamp: February 18, 2022, 09:42:52 pm
content: Yes that is it! The button pressed is held onto and when the other button tries to reverse the action suddenly the condition for the previous press has something to do.I suppose I need to reset all the button pressed states at the end of the whole 100ms task sequence as if it was not used then it needs discarding. The code is written such that only one thing can use up the button pressed, so it also shortens the code.

author: tggzzz
author_rank: Super Contributor
timestamp: February 18, 2022, 10:51:54 pm
content: Divide the solution into two concepts: button presses and what to do when a button is pressed.Implement and test those two concepts in isolation. Encode the concept of a button being pressed as an "event". Encode why to do when and event occurs as an FSM.Test the FSM on a PC using synthetic events. Test the events on the target hardware,to see that there is a reliable correspondence between key presses and event generation.Then couple the events to the FSM on the target hardware. If that doesn't work as expected, the cause ought to be easy to locate.Don't try to skip those stages.

author: Simon
author_rank: Unknown
timestamp: February 19, 2022, 07:33:24 am
content: It is what cv007 said, I just needed a second pair of eyes to see it quicker than me after the week I have spent on writing code. The problem is that I am writing multiple bits of functionality at the same time and as you say they need dealing with in isolation.What I also needed to establish was the button functionality, do I need multi-press or not, the answer is no so when I write the code totally separately as you suggest I should lock other button presses out by testing the flags to see if any have already been pressed before setting a flag for that button. That way the push button priority is on the first one pressed by the user and not what the program happens to dictate at that time.

author: brucehoult
author_rank: Super Contributor
timestamp: February 19, 2022, 08:07:19 am
content: Quote from: Simon on February 18, 2022, 08:10:02 amAn absolutely bog-standard small but slow memcpy(). Other than the checking for null pointers, for no obvious reason. No harm.Quotedst is, quite plainly, still pointing to the first location.QuoteFine.QuoteNo. For sure not if the function isn't inlined. If it is inlined, then no more so than with whatever code you've got now. especially without "restrict"

author: Simon
author_rank: Unknown
timestamp: February 21, 2022, 08:16:10 am
content: Quote from: brucehoult on February 19, 2022, 08:07:19 amSo is in-lined code an issue with compiler optimizations? I call any inline function a "static inline" and put it in a header file. I usually do it when the contents of the function is one or two lines but the function is just easier reading than the machinations contained. And of course it means I can write hardware specific code for different devices but if I use the same function names it makes life easier when reusing higher level code or just for memory sake of what to write.What is "restrict"?

author: brucehoult
author_rank: Super Contributor
timestamp: February 21, 2022, 08:38:39 am
content: Quote from: Simon on February 21, 2022, 08:16:10 amOnly to the same extent as if you wrote the same code in the same place by hand. No more and no less. The compiler is, as always, free to remove apparently useless code.For example code inside an if() where the test is always false, or replace repeated reads of an ordinary memory location by a copy of the first value read, or replace repeated writes of an ordinary memory location by the last value written.The usual things designed to clean up sloppy code, especially generic code generated from macros or inlined functions that can be simplified in the current situation.QuoteA promise that no other variable or pointer will access the same memory locations as the pointer with "restrict" on it.