​ ​ ​ ​ Simple Ring Buffer
Results 1 to 6 of 6

Thread: Simple Ring Buffer

  1. #1
    New Member
    Join Date
    Dec 2017
    Location
    Winterville, GA USA
    Posts
    4

    Default Simple Ring Buffer

    Hi all, I am a relative Picaxe noob, and this is my first real post on the forum....

    This may be such a common routine that it hasn't merited prior, specific discussion, but I could not find any information on the forum while searching for the terms "ring buffer, circular buffer, FIFO, LILO, etc." As part of my weather station project, I want to know the "most recent X values" of a particular sensor reading, with "now" being the first value. Specifically, I want to know the following:

    1. Max wind gust in the last minute, and last 10 minutes,
    2. Average wind direction for the same time periods
    3. Last 24 (one-hour) barometer readings

    Below is a simple "Ring Buffer" routine that I came up with that will handle these needs. I am running it on a 20x2 so I can start my EEPROM pointers at zero. For non-X2 chips, you would need to consult the EEPROM command doc for usable locations, or use one of the other types of available memory. I welcome comments/criticisms, and any suggestions for more efficient code or methods.

    Thanks to all for the wealth of information on this site...., Greg

    Code:
    #picaxe 20x2
    #terminal 9600
    #no_data
    #no_table
    
    ;Simple Ring Buffer for 10 sequential readings of a byte value
    ;New data enters one end, old data out the other end
    ;Simply modifiy counter ranges for other data (array) sizes
    ;Should work with word variables with slight tweaking
    ; Greg Derda, "doppler", 4/30/2018
    
    symbol data_pointer = b0 ;primary data-array location pointer (e.g.,  0 - 9)
    symbol temp_pointer = b1 ;temporary data-array location (e.g., 10 - 19)
    symbol in_data = b2 ;incomming data
    symbol temp_data = b3 ;temporary data
    symbol out_data = b4 ;output data
    
    main:
    
    inc in_data  ;Debug, for generating test incomming data
    
    ;Read and copy existing data-array to temporary-array before writing new in_data
    for data_pointer = 0 to 9 ;change to indicate data-array range
    	read data_pointer, temp_data
    		temp_pointer = data_pointer + 10 ;offset to first location in temp data array
    	write temp_pointer,temp_data
    	write 0, in_data ;now write new data to first location of data array
    next data_pointer
    
    ;Now write temp-data back to data-array, starting at second element 
    for temp_pointer = 10 to 19 ;change to indicate temporary-data range
    	data_pointer = temp_pointer - 9 ;start at element 1, not overwriting elelment 0
    	read temp_pointer, temp_data
    	write data_pointer, temp_data
    next temp_pointer	
    
    ;Debug Output, to watch the pointer and data movement
    sertxd("ptr / data",13,10)
    for data_pointer = 0 to 9
    	read data_pointer, out_data
    	sertxd(#data_pointer,"   /   ",#out_data,13,10)
    next data_pointer
    sertxd (13,10)
    pause 1000  ;Debug, to slow the output down a bit
    ; End Debug Output
    
    goto main
    Last edited by doppler; 01-05-2018 at 13:48. Reason: typo in title fixed

  2. #2
    New Member
    Join Date
    Dec 2017
    Location
    Winterville, GA USA
    Posts
    4

    Default

    Here is one example of my usage. My remote weather station does three, 3-second loops for the measurement of wind speed. It averages those three readings for a wind speed value, and picks the highest of the three for a wind gust value. Those three loops combined with other routines add up to a once per 10-second broadcast timing (serial out to an Arduino/RFM69HCW assembly for a 100m transmission to the house). For my one-minute wind gust value, I am using a data array of six values (6 x 10sec = 1 minute), and adding the following to my ring buffer code (with appropriate data array size adjustments):


    Code:
    symbol max_value = b6
    
    for data_pointer = 0 to 5
    	read data_pointer, out_data	
    		if out_data > max_value then
    			max_value = out_data
    		endif
    next data_pointer
    
    max_value = 0

  3. #3
    Technical Support
    Join Date
    Jan 1970
    Location
    UK
    Posts
    24,346

    Default

    One issue with your code is that you seem to be shuffling your samples around in EEPROM and that only has a limited write capability. Possibly more than you will need but it's best to WRITE as little as possible.

    Avoiding multiple writes is usually done by only writing the latest data and keeping a separate variable pointing to where that data was written, determining which were the latest from the pointer. That avoids having to copy or shuffle data.

    Keeping the data in RAM or Scratchpad ( using PEEK/POKE, GET/PUT, @bptr/@ptr ) avoids the issue of EEPROM wearing out. That won't retain the data previously read across power-cycles but it is often acceptable to build the data up from noting and it likely will have settled down and be correct when one comes to use the data later.

  4. #4
    New Member
    Join Date
    Dec 2017
    Location
    Winterville, GA USA
    Posts
    4

    Default

    Thank you for the great insight and suggestions hippy! It occurred to me in a "Duh!" moment (after reading your reply) that I don't need the readings in chronological order to obtain the maximum wind gust and average wind direction, I only need the most recent X values. Simply writing the values using a moving pointer that loops back to the beginning will do that. I came up with the following, using 5 data points as an example. I'm guessing that there may be a more elegant way of doing it, but this works:
    Code:
    symbol data_pointer = b0
    symbol temp_pointer = b1
    symbol in_data = b2
    symbol out_data = b3
    data_pointer = 59
    	
    main:
    	inc in_data ;debug data
    	inc data_pointer
    
    	poke data_pointer, in_data
    
    		if data_pointer = 64 then
    			data_pointer = 59
    		endif
    		
    	for temp_pointer = 60 to 64
    		peek temp_pointer, out_data
    		sertxd(#temp_pointer," / ",#out_data,13,10) ;debug output
    	next temp_pointer
    		sertxd(13,10) ;debug output
    		
    pause 1000
    goto main
    For chronological readings, like a 24hr barograph, I'll have to "press" my simple brain as to how to have a pointer handle the wrap at the end of the array, for both writing and reading. In the meantime, I simply swapped 'read' and 'write' in my original code, with 'peek' and 'poke, and moved the pointers to 60 and above. I sort of like making computational things work a little, following a stigmatizing experience I had as a graduate student (many years ago): After two years and thousands of hours of field and lab work, when it came time to analyze my data using a popular computer program for my type of research, it only took an old IBM 286 PC about one second to do the job :-0

  5. #5
    Technical Support
    Join Date
    Jan 1970
    Location
    UK
    Posts
    24,346

    Default

    Exactly how I meant. Elegance is always secondary to what works but things you can consider using are 'clever maths' to keep the pointer within the right location areas, for example your 'increment pointer' so it's always between 60 and 64 inclusive without the IF ...

    Code:
    data_pointer = data_pointer + 1 // 65 Min 60
    And you can also use '@bptr' for the data_pointer to avoid PEEK and POKE and even reduce code ...

    Code:
    @bPtr = latest_data
    bPtr = bPtr + 1 // 65 Min 60
    Code:
    For bPtr = 60 To 64
      SerTxd( "Location ", #bPtr, " holds ", #@bPtr, CR, LF )
    Next
    That's not always overly useful because it messes with what bPtr is. Probably best to keep bPtr for last data location written.

    It can get a bit tricky for chronological data but that can be handled with more 'clever maths' ...

    Code:
    Symbol latest_data  = b0
    Symbol item         = b1
    Symbol loc          = b2
    Symbol samples      = b3
    Symbol average      = w2 ; b5:b4
    
    ; Set some data
    
    latest_data = 10 : Gosub AddData
    latest_data = 20 : Gosub AddData
    latest_data = 30 : Gosub AddData
    latest_data = 40 : Gosub AddData
    latest_data = 50 : Gosub AddData
    latest_data = 60 : Gosub AddData
    latest_data = 70 : Gosub AddData
    
    ; Now holds 5 items : 30,40,50,60,70
    
    ; Show location contents
    
    For loc = 60 To 64
      Peek loc, latest_data
      SerTxd( "Location ", #loc, " holds ", #latest_Data, CR, LF )
    Next
    SerTxd( CR, LF )
    
    ; Show chronological order, last data first 
    
    loc = bPtr
    For item = 1 To 5
      Peek loc, latest_data
      SerTxd( "Item ", #item, " location ", #loc, " holds ", #latest_Data, CR, LF )
      loc = loc - 60 - 1 Max 4 + 60
    Next
    SerTxd( CR, LF )
    
    ; Show Average 
    
    average = 0
    For loc = 60 To 64
      Peek loc, latest_data
      average = average + latest_Data
    Next
    average = average / samples
    SerTxd( "Average ", #average, CR, LF )
    
    End
    
    AddData:
      bPtr = bPtr + 1 // 65 Min 60
      @bPtr = latest_data
      samples = samples + 1 Max 5
      Return

  6. #6
    New Member
    Join Date
    Dec 2017
    Location
    Winterville, GA USA
    Posts
    4

    Default

    This is great stuff, thank you hippy! I will have to study your chronological code a bit more to fully understand it, but I will definitely use your “average” routine in my future coding.

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •