access to a structure i eeprom ( MODBUS)

Go To Last Post
18 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi.

I have make a structure i eeprom 

like:

eeprom struct eeprom_structure {
             char stid[32];     // 16 holder register
             int  add;
             int copw; 
             int powi;
             int prot;
             char SIP[4];  // 4 holder register
             int prpo;
             int funk;
             int lock;
             }IOG;

it is not finish , I have 320 holding register 

to init every value is easy.

but I want read data from a pointer 

like

temp = (*IOG_pointer + 10), mean I read the value of adresse + 10

It is easy to do with a buffer [320] .. but I can not see how with a structure

I use Codevision but it is not a question of compiler.

Thank you

Thierry

 

 

Thierry Pottier

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1
temp = (*IOG_pointer + 10)

Err why on earth would you access the data like that? Also what is IOG_pointer defined as anyway? If it is "IOG * IOG_pointer" then IOG_pointer + 10 is a location that is the base of the data plus ten lots of the sizeof(IOG) ?!?!?

 

If you want to access specific fields why on earth don't you just do things like:

int foo = IOG.prot;
char buff[10];
strcpy(buff, IOG.SIP);

etc.

 

You should not be hardcoding +10 offsets from pointers to access this. The only time offsets might come into play is when you do something like:

char bar = IOG.stid[10];

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To access elements in a struct via a pointer to the struct, use the standard pointer dereference operator:

result = struct_pointer -> member_name;

https://en.wikipedia.org/wiki/De...

 

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

In which case my examples simply become:

int foo = IOGptr->prot;
strcpy(buff, IOGptr->SIP);
char bar = IOGptr->stid[10];

though, unless you are passing the address of the "IOG" into a function, I'm not sure why you would chose to employ a pointer? Let's face it the IOG struct is a global so the names can be globally accessed as IOG.lock etc.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK 

I see at I have not been clear.

data com from SMS and easy to update with a structur.

but data will be read by modbus ( read xx number of holding register ) 

that mean at I must get "int from eeprom from " address ask from Modbus" and send xx number of register.

I was thinking at a pointer init with the adress of the table and read the xx number follow data.

all data are saved in eeprom and get some problem to init and declare this structure / pointer .

Thank you

Thierry

Thierry Pottier

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why not simply something like:

switch (RX_data_ID) {
    case STID: memcpy(IOG.stid, data, 32); break;
    case ADD: IOG.add = data; break;
    case COPW: IOG.copw = data; break;
    case POWI: IOG.powi = data; break;
    etc.
}

Obviously there's an issue here about reading some generic received "data" as different type interpretations but that may be as simple as a (typecast) in fact, depending on exactly what format each data item arrives in.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes this it is my idea to copy value from SMS to the eeprom structure 

but when I must answer to Modbus message, I must send x number of int some are saved in eeprom 

No way to read eeprom via a pointer? 

pointer is just one address and it is only this you need to get the data from eeprom. 

Thiery

Thierry Pottier

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

What is gained by using a pointer? Of course I can do this:

eeprom struct eeprom_structure {
             char stid[32];     // 16 holder register
             int  add;
             int copw; 
             int powi;
             int prot;
             char SIP[4];  // 4 holder register
             int prpo;
             int funk;
             int lock;
             }IOG;
             
eeprom uint8_t * ptr;

ptr = (eeprom uint8_t *)&IOG;

dest = *(ptr + 10);
// or a simpler syntax
dest = ptr[10];

and that would read the 11th byte of this data. But to what effect? Why would you hard code a 11 byte offset into the structure? As it stands the 11th byte is part way through stid[] but suppose someone later re-arranged the structure layout into (say) alpha order:

eeprom struct eeprom_structure {
             int  add;
             int copw; 
             int funk;
             int lock;
             int powi;
             int prot;
             int prpo;
             char SIP[4];  // 4 holder register
             char stid[32];     // 16 holder register
             }IOG;

Now when you access the 11th byte you happen to hit the lower half of IOG.prot.

 

If you want to access IOG.prot why would you not simply refer to it by name?

 

Is this because the "sender" at the other end of the MODBUS is simnply going to update IOG.prot by saying "here are the 11th/12th bytes to be stored"? Why don't you extend the "protocol" to have a MODBUS message to say "I want to write the following to the PROT location" and then "here are those 2 bytes".

 

The receiver keeps a look up to say PROT means IOG.prot. Now the sender does not need to know anything about the actual layout of IOG just that somewhere within it is an entry it can refer to as "PROT".

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
temp = (*IOG_pointer + 10) Err why on earth would you access the data like that? Also what is IOG_pointer defined as anyway? If it is "IOG * IOG_pointer" then IOG_pointer + 10 is a location that is the base of the data plus ten lots of the sizeof(IOG) ?!?!?

Let's start with the code fragment, and your question on the type of the pointer.

 

--  You C gurus might always remember, but I don't --is the dereferencing done, and then the value 10 added to the result?  Or is the pointer adjusted by 10 elements and then the value fetched?  I'll always use a set of parens, but I'm not a guru.  I don't remember all of the precedence table either.

 

-- Then, on to:

Err why on earth would you access the data like that?

...which was reinforced by Cliff and awneil.  I'm on Earth, and I use in my second incarnation of such in AVR8 apps exactly the mechanism proposed by TPE.

 

-- Digressing to my first incarnation:  My "config table" of EEPROM parameters was a series of EEPROM variables in CodeVision C.  In 1.x, CV always laid them out in declaration order.  Not so in 2.x and beyond.  So the config structure was added where the order is guaranteed to be declaration order by the rules of C, and for this AVR8 toolchain the granularity is 1 so there is no filler or other realignment added by the toolchain.  [kind of general practice nowadays with mature AVR8 C toolchains...a single structure declaration, with perhaps instances in flash ("restore factory defaults"); EEPROM (non-volatile copy of configuration values); SRAM (sometimes; for e.g. fast access during runtime/ISR work).  these mentioned instances can be copied as an entity rather than each member]

 

-- Now on to Modbus.  I can only speak to ModbusRTU (which I think nowadays is usually referred to as ModbusSIO), but for this discussion AFAIK would apply to other Modbus flavours.  There is a set of transaction types dealing with bits (Read/Write Coil(s)) but put that aside for now.  The basic unit for the topic under discussion is a "register" or "holding register" that is 16 bits.  Hence OP's (and my) layout.  In fact, I'll tend to make even single-bit parameters (enable/disable) as a full register for more orderly operations.  [At some point that becomes impractical, but out of every e.g. 100 parameters I'll only have a few of these enable/disable so I can live with it.]

 

--  Assume your AVR is a Modbus slave, and you have hundreds of Modbus registers as OP mentioned.  Indeed, a family of my apps on a Mega329 has nearly 600 registers.  A family on Mega1280 might have a thousand or more, depending on configuration.

 

-- Thus,

awneil wrote:
To access elements in a struct via a pointer to the struct, use the standard pointer dereference operator: result = struct_pointer -> member_name;
and
clawson wrote:
Why not simply something like: switch (RX_data_ID) { case STID: memcpy(IOG.stid, data, 32); break; case ADD: IOG.add = data; break; case COPW: IOG.copw = data; ...
become very impractical.

 

-- Unroll the above to hundreds, and look at the size of the generated code as well as the execution overhead to get deep into the switch cases.  Compare with OP's pointer to the structure, and an offset ...

 

-- ... So, if the AVR is a ModbusRTU slave the master asks for the contents of register 123.  The AVR code sets the pointer to the beginning of the configuration structure, and gets/sets the 123rd element where the element size is always 16 bits.  The validity range check is a wash --needs to be done with any approach.  123rd element is a simplification; depending on the master it might be 30123 or 40122 but no matter -- you can see that the described routine with the pointer is very straightforward.

 

--  This could be done with array and array indexing.  However, the pointer is attractive because, as OP's example showed, one might have some packed 8-bit values in there, or long double-word values.

 

--  How you generate the offsets is up to you.  In smaller interface chunks indeed I'll use offsetof().  That gets tricky in large structures of mixed heritage.

 

--  The "nearly orderly" layout described is also used for my onboard parameter-based editing of those configuration values.  I don't have hundreds of separate routines, but rather code snippets/cases for each parameter type.  An aux table has offset, prompt(s), min/max, type.

 

===========================

Summary:  Don't pooh-pooh OP's approach until you've walked a mile in his shoes and done Modbus apps with hundreds of registers in different application chunks and memory spaces.  I'll post fragments upon request if anyone is really interested to see how I do it.

 

What was OP's question again?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
eeprom uint8_t * ptr; ptr = (eeprom uint8_t *)&IOG; dest = *(ptr + 10); // or a simpler syntax dest = ptr[10]; and that would read the 11th byte of this data. But to what effect? Why would you hard code a 11 byte offset into the structure?

In practice, Cliff, [at least for me] one makes ptr uint16_t for Modbus purposes.  It is up to the other end to take e.g. the four Modbus registers where my 8-character system name is mapped and do something with it.  (I also have some 8-bit contiguous items "packed" into a Modbus register.)

 

In the actual AVR app code, indeed one refers to the item for app purposes with struct.member or structptr->member.

 

So, my answer is you don't hard-code an 11 byte offset.

 

What was OP's question again?  I think it has to do with what I already described.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you " clawson" 

you way work, ( change the pointer to "int 16 bit" because easiere to send to modbusRTU.

 

it is perhaps to much to ask but I can not see or found out to declare the structure at I can use in another file/ routines

 

Normal I define my variable in "xxx.c" and define extern my variabe in the ***.h 

and include the ***.h in file I need .

But I can not see how to do with struct.

Thank you

Thierry Pottier

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you " clawson" 

you way work, ( change the pointer to "int 16 bit" because easiere to send to modbusRTU.

 

it is perhaps to much to ask but I can not see or found out to declare the structure at I can use in another file/ routines

 

Normal I define my variable in "xxx.c" and define extern my variabe in the ***.h 

and include the ***.h in file I need .

But I can not see how to do with struct.

Thank you

Thierry Pottier

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm going to post some fragments which I think would pertain to real-world Modbus use with an AVR8 slave and the Modbus master being a higher power.  PLC; HMI; Cloud interface; ...

 

The fragments are to an SRAM structure of "live data" of the controller's cycles.  It will turn into a log record on an SD card when the cycle has completed.  The higher power may want to access this information for display or other purposes.

 

[The same app also has configuration parameters in EEPROM mapped to another set of Modbus registers, as well as other sections of "live data" in SRAM.]

 

Start of the structure (which has a total size of 512 bytes, one SD card sector, and thus 256 Modbus registers):

struct logcip_struct
{
// Fixed info (12) (also part of the file "header" record)
unsigned int	record_type;	// two halves of 8-bits each; 2nd half is EOR of the first half TYPE_END or TYPE_MANUAL
utime_t			starttimestamp;	// cycle-start time (UNIX format)
unsigned long	cycle_num;		// 32-bit master cycle number
unsigned int	cycle_term;		// Termination cause (two 8-bit halves)  TERM_NORMAL or TERM_CANCEL

// Cycle info (4)
unsigned char	cycle_type;		// If TYPE_MANUAL, product number; else recipe number (TYPE_END)
unsigned char	seq;			// 1-relative Sequencer number
unsigned int	steps;			// Step count for TYPE_END; product cost if TYPE_MANUAL

// Timekeeping (6)
utime_t			endtimestamp;	// cycle-end time
unsigned int	elapsed_time;	// seconds; 18-hour reach (ask Andy if that is enough)
...
unsigned long	operator_id;	// from RFID card

// 20 bytes for dispenser
unsigned char	sname[8];		// system name
unsigned char	rname[8];		// recipe name

utime_t is  "unsigned long"

 

You can see that in practice there is a mix of data widths, as well as the fragment I pulled at the end for 8-character "names".

 

All Modbus apps need to agree on a register map on both sides.  A fragment for the above:

// System1 -- 1101-1300				
// System2 -- 1301-1500			represented as 1xNN below	
// System3 -- 1501-1700				
// System4 -- 1701-1900				
				
		PER-SEQUENCER SECTION--RECIPE		
Register Number		Description		Notes
1x01		Record Type		0xF609 for Manual; 0xFC03 for Recipe
1x02		Start Timestamp (low half)		32-bit Unix timestamp
1x03		Start Timestamp (high half)		
1x04		Master Cycle Number (low half)		32-bit master cycle number (all Sequencers)
1x05		Master Cycle Number (high half)		
1x06		Termination Cause		16-bit encoded value
1x07		Low byte:  Cycle Type		Recipe number
		High byte: Sequencer number 1-4		
1x08		Step count		Steps carried out in this recipe invocation
1x09		End Timestamp (low half)		32-bit Unix timestamp
1x10		End Timestamp (high half)		
1x11		Elapsed Time (seconds)		
...
1x21		Operator ID (low half)		32-bit RFID number
1x22		Operator ID (high half)		
1x23		System Name		8-character system name
1x24		System Name		
1x25		System Name		
1x26		System Name		
1x27		Recipe Name		8-character recipe name
1x28		Recipe Name		
1x29		Recipe Name		
1x30		Recipe Name		

 

In the app code, a simple example of app access via a pointer to that struct's instance:

 

// Regardless of cycle type and phase, update the elapsed time
			tptr->elapsed_time = (unsigned int)(unixtimestamp - tptr->starttimestamp);

The Modbus "helper" function that turns the register number into an offset, >>for this particular data chunk<<.

 

//=============================================================================
// Helper function to translate a Modbus register number into an SRAM address
//=============================================================================
//
//	Modbus Register mapping
//
//
// System1 -- 1101-1300
// System2 -- 1301-1500			represented as 1xNN below
// System3 -- 1501-1700
// System4 -- 1701-1900
//

unsigned int *	mb_helper	(unsigned int regno)
{
unsigned int	*regptr = NULL;
unsigned int	work = regno;

// Each system has a block of 99 register addresses, arbitrarily starting at 1.
//	"regno" has been pre-qualified to be between 1101 and 1900.

	// Double-check too high
	if (work > 1900)
		{
		return regptr; // null
		}

	// Set the base ...
	if (work > 1700)
		{
		regptr = (unsigned int *)(&buffer_thread[3]);
		work -= 1701;
		}
	else if (work > 1500)
		{
		regptr = (unsigned int *)(&buffer_thread[2]);
		work -= 1501;
		}
	else if (work > 1300)
		{
		regptr = (unsigned int *)(&buffer_thread[1]);
		work -= 1301;
		}
	else if (work > 1100)
		{
		regptr = (unsigned int *)(&buffer_thread[0]);
		work -= 1101;
		}
	else
		{
		// Double-check too low
		return regptr; // null
		}

	// ... and incorporate the offset
	regptr += work;

	return regptr;
}

The above part is what Cliff and awneil don't approve of.  I guess I'm open to discussion but after 5+ years of yielding to the higher powers mentioned earlier I probably ain't gonna rip out the guts.  The agreed-upon Modbus register map, in this case starting at say 1101, is converted into a pointer to the base of the struct instance along with an offset of n 16-bit registers.

 

This helper function sits in the app code that "knows" where e.g. buffer_thread[] lives.  The Modbus engine doesn't...

//	Instead of MBS0_ALT_RHR, call a helper function that translates a ReadHoldingRegister
//	base number into an (unsigned int *) and then use the "engine" as is.
//
	if (PDUCount > 99)
		{
		// Too many registers
		MBS0App_SendExceptionMessage(MBException_IllegalDataValue);
		return;
		}

	if (	(PDUAddr >= MBS0_ZONE1_BASE) &&
			(PDUAddr <= (MBS0_ZONE1_BASE + MBS0_ZONE1_COUNT)) &&
			((PDUAddr + PDUCount) <= (MBS0_ZONE1_BASE + MBS0_ZONE1_COUNT)))
		{
		// SRAM register
		unsigned int 	*ptr_source;			// pointer for internal SRAM address space

		ptr_source = mb_helper (PDUAddr);

		// Check for validity
		if (ptr_source == NULL)
			{
			// Address could not be classified
			MBS0App_SendExceptionMessage(MBException_IllegalDataAddress);
			return;
			}

		// Build the response
		pbDest = (BYTE *)(&pRPDU->RegisterValues[0]);
		RegCount = (BYTE)(PDUCount & 0xff);
		pRPDU->nBytes = (BYTE)(RegCount * 2);

		for (RegIndex=0; RegIndex < RegCount; RegIndex++)
			{
			PutWord(pbDest, *ptr_source++);
			pbDest += 2;
			}
		}
	else if (	(PDUAddr >= MBS0_ZONE9_BASE) &&
			(PDUAddr <= (MBS0_ZONE9_BASE + MBS0_ZONE9_COUNT)) &&
			((PDUAddr + PDUCount) <= (MBS0_ZONE9_BASE + MBS0_ZONE9_COUNT)))
		{
		    ...

You see the hint of the different "chunks" that are different types of data and could be different address spaces, the ZONEs.

 

Indeed there is "manual" work in doing the layout, and both sides must agree.  Indeed, if the layout changes, both sides must agree.  But with the above, you can change the layout all you want and the app code to do the real stuff doesn't change.  So you can indeed claim that the offsets are hard-coded.  They are.  If you do that, then IMO/IME the above becomes straightforward, even though a bit complex?  Yes, one could develop some mechanism to request a piece of information by name or whatever.  Very tricky in the usual PLC/HMI world.  In Cloud interfaces, we do a bit of that but it is indeed very tricky.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi " theusch"

you have explain the problem very nice.

Yes it is a slave modbus RTU

with GSM LTE modem for take SMS & connected to a websever. it is why I want structure to write data and pointer to send on modbus RTU 

I use a ATMEGA1284

picture top component for this board.

CPU is on the bottom

 

Attachment(s): 

Thierry Pottier

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
The above part is what Cliff and awneil don't approve of.
Au contraire. The above is pretty much what Cliff was suggesting wasn't it....

clawson wrote:
Is this because the "sender" at the other end of the MODBUS is simnply going to update IOG.prot by saying "here are the 11th/12th bytes to be stored"? Why don't you extend the "protocol" to have a MODBUS message to say "I want to write the following to the PROT location" and then "here are those 2 bytes". The receiver keeps a look up to say PROT means IOG.prot. Now the sender does not need to know anything about the actual layout of IOG just that somewhere within it is an entry it can refer to as "PROT".
Your (work > N) and then casting the buffer is pretty much what I was suggesting isn't it? In fact it's really just another syntax for:

switch (RX_data_ID) {
    case STID: memcpy(IOG.stid, data, 32); break;
    case ADD: IOG.add = data; break;
    case COPW: IOG.copw = data; break;
    case POWI: IOG.powi = data; break;
    etc.
}

(and I did say that you would need to cast "data" to the right interpretation in these).

 

So looks like I ended up reinventing your wheel after all cheeky

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Why don't you extend the "protocol" to have a MODBUS message to say "I want to write the following to the PROT location" and then "here are those 2 bytes". The receiver keeps a look up to say PROT means IOG.prot. Now the sender does not need to know anything about the actual layout of IOG just that somewhere within it is an entry it can refer to as "PROT".
theusch wrote:
Yes, one could develop some mechanism to request a piece of information by name or whatever. Very tricky in the usual PLC/HMI world. In Cloud interfaces, we do a bit of that but it is indeed very tricky.

What is "tricky"?  Hard to describe.

 

Certainly not as straightforward IMO as "gimme the 23 register values starting at 456".  Not nearly as fast or efficient, either.  Got to add a protocol on top of the well-defined Modbus, with timeouts and error responses and retries and such.  My above mechanism along with a full Modbus slave can fit into about half of a Mega88.  IMO any extension such as the switch/case or other ways to determine the PROT location would blow that away.  And in the end, PROT is just a register number anyway so why not use the Read Single Register?  Don't both sides need to agree how to specify PROT anyway?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
gimme the 23 register values starting at 456
Isn't that predicated on all 23 being the same size/type? Or do you simply mean "the block of 46 bytes staring at 456"? OPs structure had some char arrays and some ints though I suppose it could be argued that everything is a multiple of 2 so the first [32] is really just "the first 16 words starting at 0" I guess? Similarly the [4] is really just 2 words at word offset 21"?

 

I guess that if everything is created in terms of multiples of minimal granularity (16 bits) then it does become as simple as "N words at word offset M"

 

But it still requires the two ends to know the common layout. I was simply suggesting (OK "over complicating"?) by giving each item of data an ID so you didn't need to know that it was at word offset 21 but could say "the piece of data we both know as item ID 37" and neither end needs to know the fixed layout maintained by the other.

 

But, yes, this would add a layer to the comms.

 

I  guess it's a two edged sword. What I had in the back of my mind was maintenance where sender knows layout version 17 but the AVR its talking to is still using layout 14 from last Summer, etc etc

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Isn't that predicated on all 23 being the same size/type?

Well, yes -- [ignoring the "bit" part] all Modbus "registers" are 16 bits, but indeed ...

clawson wrote:
"the block of 46 bytes staring at 456"

is how you'd think of the "mixed" case.  And remember, as in my examples above, in some kind of full app with hundreds of registers, it is likely that it isn't a simple 1-n.  In my ZONE1 example, an offset might be 1101.  Or commonly, 30001 or 40001 or 50001.

 

This manual https://www.ccontrols.com/suppor... has thousands of Modbus registers, and the table of descriptions takes hundreds of pages.  The base offset is 40000 (or maybe 40001).

 

So except in a simple interface with "0-based addressing", one cannot just use the Modbus register number directly to offset the pointer.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Wed. May 3, 2017 - 04:31 PM