Jump to content

ErikT

Members
  • Content Count

    32
  • Joined

  • Last visited

Community Reputation

1 Neutral

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. ErikT

    Event handler in thread

    @PeaShooter_OMO I never ever use Arduino. But thank you for the comparison. @DelphiUdIT You are correct – I have not built a TCP/IP stack from scratch. It is already hard-coded in the Wiznet W5500 Ethernet controller that I talk to via SPI. 😉 But I totally get your point, and I've made many other things in microcontrollers that could be stand-ins for the TCP/IP stack. You are very much nailing the trouble I have: With the microcontroller, I am in control of the island. When I write software for a computer, I govern only a tiny speck in a continent that is controlled by someone else. And many of the continent's rules are pretty difficult to figure out. On that note, googling how to do something specific comes with a serious caveat: More than once, I have found erroneous or overly complicated solutions (e.g. on stackoverflow and such sites), that can really send a newbie like me in the wrong direction. This is exactly what happened in this case. Thankfully, @PeaShooter_OMO and @Anders Melander were so kind to guide me back on track – and to a very simple solution, I might add. I prefer to ask questions on this forum, but I want to at least try to find my way first. I don't wish to be known as the "Please code it for me" guy.
  2. ErikT

    Event handler in thread

    Well... one of the things that I find very different is that when I write code for a microcontroller, nothing is "hidden" from me. There is nothing going on in the background, without my knowledge. My while (1) main loop is always running. If I happen to exit it, there is nothing. In Delphi, Qt and the like, there is always a bunch of stuff that I don't see. If I make a tiny program that is made solely from events (button clicks, timers, edit field changes and such), none of my code is doing anything until an event is triggered. This makes the whole thing very abstract to me. One of the microcontroller-equivalents to events is interrupts. For some reason, these are much more tangible to me. Received a byte on USART2, and the receive interrupt is enabled? Okay, let's jump to the code at the address listed in the interrupt vector table. Done. (Keeping in mind the non-atomic operations, of course.) Right now I'm trying to figure out home made events in Delphi. That's quite a different story. 😕 BUT: I definitely agree with you that having a solid understanding of hardware and the "small" stuff is a great advantage when moving to programming computers. It must be much harder going the other way: "Hey, where did all my megabytes go? Why do I get 20 or more interrupts when I only press the button once? And when I release it as well? Why doesn't the rest of the World pause, when I pause my emulator?" (Real-life situations.) I thank you and @Anders Melander for your help regarding my issue. Now I'll try to get into the home made event stuff. P.S. Anders, at the university, I sucked big time at chemistry. I think it takes a special breed of people to truly understand that stuff.
  3. ErikT

    Event handler in thread

    Thanks a lot @Anders Melander This, too, makes sense. Dang, I'm learning stuff today! My programming expertise is in microcontrollers. Bare metal. This is a very different world. @PeaShooter_OMO Thank you for the tip regarding naming conventions. I have found the style guide, and have modified my code accordingly.
  4. ErikT

    Event handler in thread

    Makes perfect sense. However, it isn't obvious to me which TComponent would be the owner.
  5. ErikT

    Event handler in thread

    Thanks guys – this is very much appreciated! All in all, the solution is simpler and makes much more sense than I feared. I have no idea if the COMPortDriver does thread-unsafe stuff. But I guess that I am going to find out.
  6. ErikT

    Event handler in thread

    Thanks a lot, @PeaShooter_OMOand @Anders Melander My project will be temporarily halted by a trip to the vet, and then I'll have a closer look at your posts. Edit: Okay, I had a quick look anyway. So what I gather from this is that I declare the event procedure (method) like I would do with any other in the class, and then the line CommPortDriver1.OnReceiveData := CommPortReceiveData; makes the link between the OnReceiveData event and my method? And line TCommPortReceiveData = procedure(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal) of object; should never have been there in the first place?
  7. ErikT

    Event handler in thread

    This might be answered somewhere around here, but I simply cannot find it. The scenario In order to avoid using Application.ProcessMessages when uploading a heap of data to an external device (which takes time), I'm currently learning more about threads. I have done this a little bit before, but never with an object that has events. I am using the CPDrv TCommPortDriver (ComDrv32) component for communicating with a serial port. I have used this a lot, and am quite familiar with it. I just haven't used it in a thread before, When I create the component manually, I don't have the luxury of having the IDE set the event system up for me behind the scenes. This is what I am trying to do now. Before I paint myself into a corner, I would very much appreciate if someone would confirm that I am doing it the right way – or let me know if I am on the wrong track. I have searched through several web sites, picking up bits and pieces here and there, and this is what I have come up with: My code I have defined a type for the "on receive data" event procedure (TCommPortReceiveData). In the definition of the thread, I use this type when declaring the event procedure (CommPortReceiveData). The lines I find relevant for this question are commented. unit Upload; interface uses System.Classes, System.SysUtils, CPDrv, TeCom, TeComStdRegs; type TCommPortReceiveData = procedure(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal) of object; // Type for receive data event procedure TUploadThread = class(TThread) private CommPortDriver1 : TCommPortDriver; // Declare an identifier for the TCommPortDriver object AbortUpld : Boolean; ProgressVal : Word; FName : string; PortNo : Byte; ModAddress : Byte; ModType : Cardinal; CommPortReceiveData : TCommPortReceiveData; // Declare the receive data event procedure procedure SendTelegram(TxTelegram : tcBuffer; TxTelegramLength : Word); procedure Execute; override; public constructor Create(AOwner : TComponent; FileName : string; PortNumber : Byte; Address : Byte; ModuleType : Cardinal); reintroduce; destructor Destroy; procedure AbortUpload; function Progress : Word; end; In the constructor, I create the object CommPortDriver1, and link the receive data event to my event procedure: constructor TUploadThread.Create(AOwner: TComponent; FileName: string; PortNumber : Byte; Address : Byte; ModuleType : Cardinal); begin inherited Create(false); AbortUpld := false; FName := FileName; PortNo := PortNumber; ModAddress := Address; ModType := ModuleType; CommPortDriver1 := TCommPortDriver.Create(AOwner); // Create COM port driver object CommPortDriver1.OnReceiveData := CommPortReceiveData; // Link event to procedure end; Now, in the code example above, I would have expected to type CommPortDriver1.OnReceiveData := @CommPortReceiveData; , to set the address of the procedure in CommPortDriver1.OnReceiveData. But then I get the error "Incompatible types: 'TReceiveDataEvent' and 'Pointer'" Finally, I write the event procedure itself, using the same parameters as the object's definition of the event: procedure CommPortReceiveData(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal); // Event handler for received data var i : Word; RxBuffer : tcBuffer; begin if (DataSize > 0) then begin Move(PByteArray(DataPtr)[0], RxBuffer[0], DataSize); ... It compiles, and I may make it work. By I'm toggling between "That wasn't so bad" and "This can't really be it, can it?" One thing I find odd is I can't declare the event procedure as procedure TUploadThread.CommPortReceiveData(Sender: TObject; DataPtr: Pointer; DataSize: Cardinal); // Event handler for received data var i : Word; RxBuffer : tcBuffer; ... When I do that, I get the error "Declaration of 'CommPortReceiveData' differs from previous declaration". I guess that this has something to do with the way the procedure has been declared in the TUploadThread type. A quick sanity check would be greatly appreciated. When I get this working properly, I intend post something for others to use as an example.
  8. I am unsure if this is even possible in Delphi. I have a GUI, designed for customer use, My employer wishes to have a version of the same software for internal use, where some extra features are available. I intend to put these extra features on an extra TabSheet in a PageControl object. For regular customers, this TabSheet is invisible. My intention was to have two different build configurations: One for customers and one for internal use. Then use a preprocessor symbol to let the code know which configuration it is. However, I have read elsewhere, that Delphi doesn't have a preprocessor, so is this even possible to do? I have defined this symbol in the IDE: Then, in the FormCreate procedure, I have added this line: {$IFDEF __BuilderGUI} TabSheet4.TabVisible := true; {$ENDIF} However, the pascal line is never run. The compiler doesn't seem to "see" the symbol. I've also tried defining the symbol this way (even though I didn't believe in it): If I add {$DEFINE __BuilderGUI} in my code, the $IFDEF recognizes the symbol and acts accordingly. So at least that part works. Am I doing something wrong in the symbol definition, or is what I'm trying to do simply not possible? Should I instead include the $DEFINE in a file that is not included in the customer configuration?
  9. ErikT

    Ping-pong between two Application.ProcessMessages

    I have no idea what an immutable object is. Will need to look into that.
  10. ErikT

    Ping-pong between two Application.ProcessMessages

    Of course! If the calls happen in the "wrong" order, so process B's Application.ProcessMessages call ends up waiting for process A to end, then they are well and surely stuck there. Thanks! Often, the only feasible way around using an Application.ProcessMessages call is to use multiple threads. And, to quote "dummzeuch" in a blog entry, that opens another can of worms. https://blog.dummzeuch.de/2018/09/29/calling-application-processmessages-in-a-delphi-program/
  11. This one might generate some frowns... I've used Application.ProcessMessages quite often, for several reasons. Most of my applications communicate with an external device via a serial port, USB or Ethernet. Each transaction can take some time, and since it is often crucial that my application gets a response before moving on, I include a while loop (with a timeout). In this loop, apart from checking if data has been received, I make sure to make an Application.ProcessMessages call, so the rest of my application isn't blocked. In the past, this has worked fine. Why this method? Because I think multiple threads are way too difficult to work with, so I don't use them unless it is absolutely necessary. And, as I wrote, it works fine. Until now, that is. In my current application, I communicate almost continuously with the external device, to get some monitoring values, status bits and such from the device. This is run by a timer, as this is the only way I can figure out how to make something run seemingly continuously. A bit like the while (1) loop in a C program for a microcontroller. Apart from the timer based "main loop", I have a few buttons that can be used for sending various commands to the device. These access the same physical port as the "main loop", so I have some boolean variables that are set and cleared, so one process can wait for the other to finish. Now for the juicy part. I have had problems with the application seemingly stalling once in a while, and couldn't figure out why. When this happens, debug stepping just keeps looping through a limited number of instructions in the CPU tab. So for quite some time, I didn't know which part of my own code started the problem. I also noticed that the application wasn't actually completely stalled, as there was a separate timer that kept feeding data into some graphs. So it was clearly only certain parts that didn't work. Now I added some global debug variables, and added lines in the code, giving the variables different values. Such as (...) repeat ButtonDebugValue := 2; while (CommBusy or CommCycle) do begin ButtonDebugValue := 3; Application.ProcessMessages; ButtonDebugValue := 4; (...) I did that in the suspected procedures, and made sure to place some before and after Application.ProcessMessages, as I had become particularly suspicious about this. As it turned out, two events (OnTimer and OnClick) were both stuck in an Application.ProcessMessages procedure. At the same time. Not even rolling around in the loop, but just stuck in the procedure. My theory: Application.ProcessMessages messes up if it is called more than once at a time. What I mean is: Process C (the button click) is waiting for process B (the timer), which is waiting for process A (comms receive). When the Application.ProcessMessages call in one event allows a different Application.ProcessMessages call in another event, things go south. The application doesn't stall as such, but those two Application.ProcessMessages never get any further, They are just blocked. What do you think? Is this theory plausible? I am not in need of help to fix this – I would just like to know if I am right or wrong. And maybe let this act as a warning against frivolous use of Application.ProcessMessages.
  12. ErikT

    TeeChart constraints on automatic axis

    When I began poking around, it turned out to be not-so-complicated after all. I've made this piece of code, which seems to work nicely: procedure AdjustAxis(WhichChart : TChart; MinInterval : Double); var ChartMaxY : Double; ChartMinY : Double; ChartCenter : Double; begin ChartMaxY := WhichChart.MaxYValue(WhichChart.LeftAxis); ChartMinY := WhichChart.MinYValue(WhichChart.LeftAxis); if ((ChartMaxY - ChartMinY) < MinInterval) then begin ChartCenter := (ChartMaxY + ChartMinY) / 2; WhichChart.LeftAxis.SetMinMax(ChartCenter - (MinInterval / 2), ChartCenter + (MinInterval / 2)); WhichChart.LeftAxis.AutomaticMaximum := false; WhichChart.LeftAxis.AutomaticMinimum := false; end else begin WhichChart.LeftAxis.AutomaticMaximum := true; WhichChart.LeftAxis.AutomaticMinimum := true; end; end; The parameters are WhichChart, which is the name of the chart, and MinInterval, which is the smallest acceptable numerical difference between minimum and maximum on the left axis. Must be positive, by the way. The chart graph is centered between maximum and minimum in the chart window. Of course it can be made smarter. For instance, this only works on the left axis. Feel free to use and modify.
  13. ErikT

    TeeChart constraints on automatic axis

    Okaaaay... Definitely one to consider. Thanks!
  14. I am using TeeChart in a GUI that continuously displays some readouts from a device. Generally, it works fine, but there is one annoyance. I use the automatic axis scaling, but would like to be able to set some constraints to that. A sort of "maximum zoom". The thing is that when a readout has been quite stable for a while, the automatic scaling feature zooms in so much that the numeric resolution becomes annoyingly visible. Example: If the example above is allowed to run for a while, the chart may be complete filled with one big blue rectangle due to this. It would be nice to be able to set a minimum distance between minimum and maximum, so the left axis in the example above would go from 42 to 52, for instance, with the blue graph being nicely centered in the chart. However, I haven't found such a setting. But since I don't exactly understand every explanation of the properties and methods in the help document, I wonder if I just haven't found it. So... does anyone have a cool solution, or do I need to go the complicated way of setting the axis minimum and maximum manually in my code, based on the series values?
×