Developing/compiling software

Stuff like that may be hidden by the compiler, much as one might stick a bandaid on a sore to hide it, but the inefficiency is still there. C on

8 bit micros only gets away with it in most cases because the later cpu performance is far greater than cpu's used in the old assembler days. This is not a critique of avr either. Its a long time since I looked at the output from Keil C, but remember being horrified by a function with a simple switch statement + a few other lines of code producing over 2 A4 pages of 8051 assembler. We get away with it, but let's not pretend that it's anything like elegant - it's a compromise made to make software development easier, portable and more maintainable using cheap micros. The downside is that such limitations can make it much more difficult to produce structured, robust and portable code.

As I said, I haven't used avr, but why not 16 bit registers instead of 8 ?. Sorry, but I don't see that as being particularly compiler friendly. Compiler friendly to me means n * 16 bit registers at a minimum and addressing modes to allow use for vars or addresses.

That's a bit of a red herring. Not all versions of the 430 have the multiplier in any case and it looks like it was intended as a fast dsp type 'peripheral'. The compiler must provide support via shift and add or similar to get multiply and divide in C.

Agreed, that could be a problem, but no doubt the compilers will be updated to make use of the added address space. They manage fine on the

24 bit 80C87 series and probably others as well...

Regards,

Chris

Reply to
ChrisQ
Loading thread data ...

A single-stack compiler must use Y or Z (invariably Y, since Z is also tied to other functions like lpm) if a frame pointer is needed. My point is that a frame pointer is seldom needed. The great majority of code (in my experience) uses the registers, and there is no data on the stack (except for preserving registers if needed, and that uses pushes and pops without a frame pointer).

With a single-frame solution, the code can use Y as a frame pointer or as a general pointer, and can change the usage within a function.

The AVR is certainly pointer-poor - it would be much nicer with at least one more general pointer (at the very least, X+index modes would be useful). I don't know anything about the H8/300, so I can't comment on it.

Reply to
David Brown

You are making a mountain out of a molehill. It is true that there are a few places where the AVR could have been made slightly more C-friendly

- I cannot think of any cpu where that is not the case (and I know the architectural details of at least a dozen, including several 32-bit architectures). As I said in another post, in the 20 KB program I checked there are a total of *2* functions which require a stack frame at all - and by compiling with -mtiny-stack there is *no* interrupt disabling at function entry. It is not a problem in the huge majority of cases.

I am a great believer in knowing your tools and knowing your target. There are times when code size or speed is critical, and you should be aware of the advantages and disadvantages of your tools and targets when working with such code. However, you must keep things in perspective - when you have a function that takes 1000 cycles to run (it must be big to need a stack frame), 3 or 4 extra prologue cycles do not matter.

You have not used the avr, you have not used avr-gcc. I don't know how familiar you are with the 8051 or with gcc on other targets, but your knowledge about the AVR and avr-gcc's inefficiencies seems to be based entirely on this thread. I have difficulty understanding why you see the occasional interrupt disable in avr-gcc function prologues as such a big issue based solely on some comments in this newsgroup.

Once you have tried using C on an AVR and looked at the resulting assembly code, you'll see that there are *no* limitations to making standard, robust and portable C code run perfectly well on an AVR. You will see that there are times when you get better code by writing slightly different C than you would for a larger device (such as using "uint8_t" instead of "int" for index variables). And occasionally you might want to re-structure your code a little to fit the processor better in critical code. But there is nothing more to it than that.

I certainly agree that 16-bit is /more/ C-friendly than 8-bit. I'm not going to try and tell you that the AVR is more C-friendly than the ColdFire or the ARM (or the AVR32). But it is undoubtedly in a totally different class of C-friendliness than the 8051.

Marvellous rationalising! The AVR is C-unfriendly and needs "band-aids" because on the rare occasion that you have a function requiring a large stack frame, it needs to disable interrupts for a couple of cycles. The msp430 needs to disable interrupts every time you use a multiplier (unless you are very careful with your interrupt code), yet that is a "red herring". For your information, multiplies turn up a good deal more often than just "dsp" code.

The msp430 has the multiplier as a peripheral because it takes space and power to make a fast multiplier - more than TI wanted in a low-power, low cost core. Making in a peripheral rather than part of the core was the cheapest way to make it optional.

I don't know anything about the 80C87, but I *do* know that 20-bit registers fit very badly with C - worse than 8-bit, and worse than 24-bit.

Reply to
David Brown

By all means use pointers to structs as function arguments if you want - the AVR will handle them fine. My point is just that you don't *have* to use them to get good code from the AVR.

Personally, I think the general idea of "all global data is evil" is silly, especially for embedded development. But if you've got a programming method that helps you write better code, then that's far more important than what other people think!

Reply to
David Brown

And the msp430 gcc backend is able to do a pretty good job of code generation -- and I'm pretty sure far fewer resources have gone into the '430 port than went into the AVR port.

The '430 HW multipler issue is very similar to issues that many processors have with floating-point units.

As you can till by how long it's taking to get 20-bit support into gcc for the '430.

--
Grant
Reply to
Grant Edwards

That's an excellent description. As soon as you start doing anything using pointers (either explicity or implicitly) on the AVR, it falls over. OTOH, something like the msp430 with

16-bit registers and ALU handles pointers effortlessly.

The H8/300 handles 32-bit data and pointers atomically as well which makes the life of a compiler writer (and that of an RTOS implementer/user) very easy.

--
Grant
Reply to
Grant Edwards

That's not really the C programming attitude. I don't care what steps to the left or right the program takes while getting where I want it to go. If it meets specs and performance targets, then it's a product, and that's why we're there.

On one project I prototyped with an Atmega168. The client had more experience (plus all the programming/debugging stuff) with PIC, so they tweaked the I/O statements in the C code and now the product runs on PIC18. C friendliness or un- made no difference.

Mel.

Reply to
Mel

Having had to wade through impenetrable code too many times in the past, where it's hard to tell what's poking what and when, I try not to inflict the same on others. One of the ways to do this is to encapsulate functionality, keep data local as possible and communicate through well defined inband interfaces. The typical embedded design can be split into subsystems - for example, uarts / comms, timers, ports etc. There's no reason why the internal data relating to a subsystem should be public, even though it may need to be visible internally. Avoiding this makes for a more reliable system design, it's easier to make changes, add modules and to keep track of the big picture. Of course, none of this is new, but it does seem to work...

Regards,

Chris

Reply to
ChrisQ

Partly agree with that but having no idea what the complier is doing hardly sounds like due diligence to me. With Keil and 8051, I don't expect code efficiency and rarely if ever look at the asm output. That is, I have confidence in the tools. But as a hardware and software bod, have to decide on hardware / software tradeoffs for new projects. Cpu architecture and features are pretty important at that level, though modern micros often have far more grunt than is needed for the application.

As a programmer, I want a broad canvas, big linear address space with no gotchas or speed limits, but engineering isn't like that. It helps to know what's going on under the hood. Besides, cpu architecture is interesting. Well, to some anyway...

Regards,

Chria

Reply to
ChrisQ

If my memory is right, I was playing with the H8/300 back in 1982 or '83. Regardless of the timing, I remember carefully wiring up an adapter so that I could program it in a standard EPROM socket. It was wonderful that they'd arranged it such that the standard EPROM sequences would work. And they did. And the instruction set was a dream, by comparison with many devices then (and sometimes, I think, even now.) I think I still have a few in a box of cpus, here. I've never used them for anything but play, but I am glad to hear they are still around.

Jon

Reply to
Jon Kirwan

For small systems, I fully agree - it's good to be able to write neat, portable C code, but you should understand what your compiler is doing and how your target works. That's how you get the best from the combination of tools and targets. For larger systems, the details get less important (though not irrelevant - for example, you would want to know how well the target supports floating point before using floats and doubles). And for really "big" systems (such as a PC), just code in Python and forget the low-level details.

Absolutely.

Reply to
David Brown

I agree 100% about encapsulation (at least, I agree about it as an ideal

- sometimes you have to compromise in practice). What I take issue with is when people get obsessive about globals and think that it is somehow better to have functions called "SetUartTimeout" and "GetUartTimeout", doing nothing but accessing a static variable, instead of just using a globally visible variable "uartTimeout". The global must be part of the well-defined interface, not a backdoor into the module's interior, but there is absolutely no reason to pretend that having the global variable is somehow "riskier" or "unclear", or harder to use or change.

Reply to
David Brown

Yes it is better.

  • Perhaps it could be necessary to add flushing the UART before changing timeouts. You just have to modify one function.
  • What if there is a need to have more then one uart? How about accessing N uarts?
  • What if there is a need to access the timeouts from more then one thread?
  • What if there is a name conflict due to the use of the global objects?

I do require all access to such variables via member functions. I like an overloaded function UART.Timeout() instead of separate Set.. and Get.. functions.

Well defined interfaces exist only in the textbooks.

As the project grow large, is gets much harder to maintain the code which is controlled by the global variables.

Vladimir Vassilevsky DSP and Mixed Signal Design Consultant

formatting link

Reply to
Vladimir Vassilevsky

Reply to
Arlet

If a function results in simpler code, they should be used, of course. But if you know that you're not going to be using threads, there's only one UART on the device, and the UART doesn't need to be flushed, using a global may be appropriate. If the circumstances change, the code can always be modified.

If you know the requirements are fixed, for instance the baud rate must be 9600, using 8N1, there is also no need to add functions to change these things.

Not likely if you start the name with 'uart' or something like that.

Sure, but many projects never grow large.

Reply to
Arlet

[First off, I want to make sure no one reading this is conflating the idea of 'global' with 'static'. A global definition (in c) is always a static definition. But not all static definitions are global.]

There are few cases where a global static is better than a module- local static; where a function mediates access rather than allowing any part of the code in any other module to directly access the global static. The conceptual region where I imagine you may have a point is where there is a requirement for frequent access across a wide range of the total code space and the cost of a function call, both in code size and execution time, is too high to afford in the application. While that usually means the code factoring is wrongly arranged, it doesn't always mean that. And there are ways around this. In general, the code I can recall doing over the last few decades, even on smaller PICs, doesn't use global statics. Good factoring seems to almost always eliminate the need.

An example where I've used global access to a static (for reading) is the current PID for the active, running process. In this case, access to reading the current process ID is so widely needed throughout other modules in an O/S and the memory and execution constraints may be so tight that it makes some sense. However, this can be carefully crafted together with the use of #define so that later changes could be reasonably performed without having to edit all the references.

So you are right that one should be so afraid that they will not accept it for any use, at all. But be very, very wary of the idea.

An example of how it can be done in c is something like this. The .h file for a module might include some code like this:

#ifdef SOMECOMPILEOPTION extern unsigned int GetCurrentPID( void ); #else extern unsigned int currpid; #define GetCurrentPID( ) (currpid) #endif

A function in the .c module can be provided (or selectively provided depending on a compile option) in the module written like this:

#ifdef SOMECOMPILEOPTION static unsigned int currpid; unsigned int (GetCurrentPID)( void ) { return currpid; } #else unsigned int currpid; #endif

(You can get trickier.) The reason for wrapping the function name in parentheses, if this isn't already obvious, is to avoid having the #define mess with the function definition.

Now you can go either way by changing a compile option.

Jon

Reply to
Jon Kirwan

How does that work, like:

timeout = UART.Timeout(GET,UART1);

...

UART.Timeout(SET,10000);

?

I seem to end up with a lot of globals at what I would call the "application" level. By this I mean the code that describes what the machine does rather than its internal operation.

So you end up with lots of code looking like:

status_t status; /* global */ set_t set; /* global */ ... status.temperature = adc(0); status.pressure = adc(1); status.test_number++;

if(set.test_time > SET_TEST_TIME_MAX) { status.flags |= STATUS_FLAG_ERROR_TEST_TIME; }

...

Any part of the application can read and write settings, status variables etc.

--

John Devereux
Reply to
John Devereux

My assumption was he was meaning a more directly OO approach.

// Declare two UART objects UART uart0( 0, 'n', 8, 1 ), uart2( 1, 'n', 8, 1 );

// Send to uart 0 uart0.send( "message to send" );

// Send to uart 1 uart1.send( "another message to send" );

--
Regards,
Richard.

+ http://www.FreeRTOS.org
Designed for Microcontrollers.  More than 7000 downloads per month.

+ http://www.SafeRTOS.com
Certified by TÜV as meeting the requirements for safety related systems
Reply to
FreeRTOS info

Then you've changed the API - your UartTimeout (variable or accessor functions) are not doing what they were doing before. It's okay to break existing code when you make significant changes to the API.

Since you are now actually doing something when setting uartTimeout, you have to change the global variable to a function with a slightly different name. You need to grep through your source code that uses the uart, and modify that source code (or just attempt a rebuild - the compiler will tell you where the problems lie).

Again, that's a big change in the functionality of the module, and requires a big change in the API.

If you need a module that supports multiple threads reading and/or writing the data, then you must take that into account. If that means that accesses must be wrapped in locks, then you don't have a simple read-write data item, and can't use a global.

Global data is not appropriate for all uses. But when you have a clearly defined data item that can be safely read or written (according to whatever restrictions you have documented in the module interface), then wrapping the data in simple accessor functions is a waste of source code, a waste of object code, and a waste of runtime, and gives exactly zero benefits for modularisation, encapsulation, structure, clarity, code maintenance, or any other buzz words you like.

Now you are getting silly. "uartTimeout" has no more or less a potential for name conflicts than "SetUartTimeout" and "GetUartTimeout". C works with a single global namespace - use appropriate naming conventions for your global objects (variables, functions or whatever). If your project is too big for that, try C++ and namespaces.

It's easy to give clear and safe interfaces for small modules.

This is comp.arch.embedded - a great many of the projects here do not grow "large" because they are for small systems. The code is small enough that is not a big problem to keep track of modules and their interfaces, and that includes global variables. On the other hand, the waste of runtime and code space caused by unnecessary accessor functions /can/ be significant.

For large projects, there are different balances - then there is much more reason to avoid global variables. But it is still a minor detail - finding a good way to specify your interfaces and keep your structure clean is more important.

To take another example, suppose you have a module that collects samples from somewhere and puts it in an array dataSamples[]. Should that only be accessible through a function "int GetDataSample(int n)" ? That would make a summation function very slow. Should you have a "CopyDataSamples(int * dest, int n)" to let users have a local copy? Again, it's slow, and a waste of data space. Perhaps just "const int * AccessDataSamples(void)" to return a pointer to the data - it's fast, but loses the compile-time checking you get with simple clear access to the raw global data.

Reply to
David Brown

I guess you would take issue with a lot of my code then, because it uses a lot of that type of construct to hide internals and there are no globals other than const data at all in systems designed here. Within modules, all vars and const data is declared static, so it doesn't get exported to the linker. If you accept that all computing is just data movement and processing, the interfaces between modules become the defining nodes of system design.

A typical example might be timer pool support which needs only a system interrupt tick timer call to function. The programming interface consists of a group of functions to do stuff like request, cancel, check, refresh timer etc and all you get back from or provide to the interface is a U8 timer handle or a status. There is also a callback facility on timeout, though this is only for short code sections as it runs in interrupt context. The whole point of it is that it's a proven library module that I never have to rewrite, can take it to any project, compile it and it just works.

That's probably a simple and unfair example, as it's easy to fully encapsulate, but the same process is used for ports, where a more or less standard driver module abstracts the hardware into a port id and bit which are defined for the project in a project_defs file. If fast access is needed to a port, a special driver is written for that function alone and all hardware references are again abstracted out as far as possible.

The similar process is used for comms, which is usually built as an upper and lower layer. Upper layer has the app call interface, lower layer interrupt handler. The two are connected by queues in both directions. At this level, a call interface structure contains all the data and references for a uart and it's queues and this structure is shared between the layers. In this way, the code becomes completely reentrant and one set of upper level code and queue functions is shared across all uarts. On some projects with 4 or more comms channels, this save a lot of code space even though it's larger than it would normally be for a single uart channel. Only the interrupt handlers are uart specific and this is because it's more efficient in terms of interrupt handler times.

A lot of the ideas for this came from classic os design and while you might argue that it's inappropriate for small systems, it's surprising how usefull it is at any level. There's quite a bit more effort up front to start with, but the payback is not on this project but on the next one and so on. You find that there are fewer bugs and a lot of stuff works the first time because you think systems design, not programs. On subsequent projects, you find that a lot of the code is reusable without change and all you have to write is the application layer, or part of it. Over time, you build up functionality to the point where you hardly ever need to look at stuff like the standard c lib, (which has all the wrong types anyway) and you thus have complete visibility of all the code...

Regards,

Chris

Reply to
ChrisQ

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.