Yeah, but how do I... tips and techniques for getting a uC to do X

Started by R.G., March 10, 2006, 10:12:31 AM

Previous topic - Next topic

R.G.

This thread is for asking questions on how you get a uC to do ... whatever...

F'rinstance, and from my recent reply to David:
QuoteOK, here's something I'm having some issues and confusion about. I've just gotten my STK500, and I've dinked around with it a little, but I'm feeling a bit lost with this whole programming methodology. I'm used to high-level languages, and sugary features like event handlers.

So, how exactly would you handle something like a held-down button programmatically? Do you just keep sampling the thing to make sure it's held down in some sort of sub-loop, or can you link a digital input up to an interrupt or something? So much of this stuff seems like an event-driven system would make sense, though clearly it works without it.
Actually, you have put your fingers on the issues already. There are really only two ways to deal with events - polling and interrupts. Actually, now that I think about it, there's a third way - interrupt driven polling.

Programming in applications level languages hides the details of what is happening in real-time events by hiding behind an interrupt handler. And you WANT it that way, because you don't want to have every single line of programming have to know about the real time events. But you have to either be programming right down at the bare metal or highly disciplined and modular to deal with it.

Here's how you handle real time events in any computer.
Whatever suffices for an operating system is always a never ending loop. It starts at "MAIN" and ends with a "GOTO Main", or the structured equivalents "WHILE  TRUE" and "WEND", or other ways to say the same thing in different languages. You either check the event status by polling the I/O once or more times per main loop, using an interrupt handler to break into and out of the main loop, or using a timer to break out of the main loop at regular intervals to poll the external event.  Just thinking about it, I don't think there are any other than those three ways.

Polling is ugly, nasty, and only suitable for tiny applications where you know with no doubts that your main loop always executes in less time than you need between polling events.

Interrupts are ugly, and nasty, but they are suitable for getting the offending real world event out of your hair so you can concentrate on what you're doing (either interrupt code or main loop code) and not have to worry about the other all the time.

Timer interrupts are ugly, but they offer a way to deal with the real time event while restricting the amount of time in the main loop spent on it, and still letting you separate real time events from interrupting events in your head.

Notice that only purely interrupt driven stuff can cope with real time events as quickly as the computer can respond. Polling, even interrupt polling, will on average respond to the real world even in half the polling interval, and as bad as the entire polling interval. So you have to be sure that your real-time event can stand for you to wait the polling interval before servicing the event.
Modular block structured programming is a good fit to the human mind. We compartmentalize and analogize incredibly easily. If you have to do something, whether incredibly minute or gigantically complex, you name the whole process, and deal with it as a unit. We deal with "First do this, then this, then.... then this and now start over" very well. The "this" things can be big or small. They just have to be non-interacting, or more properly, interacting only at entry and exit points.  If there are two processes that interact in detail that can't be separated out so they interact at entry and exit points only, then they are really not two processes, and should be handled as one.

That was part of what I was trying to get across with one of my recent polemics on programming here. MODULARIZE.

Once it's modularized, you can always get some help on how to write the modules.

In the case of any uC, if the main loop task is short enough, you can simply check the real world event once per main loop, or even sprinkle tests on the real world event throughout the main loop.  It is possible to write the main loop so that it always takes the same amount of time to execute, no matter what. This is so-called isochronous code, and is a marvel of elegance. The top octave generator code that you may have heard me talk about is isochronous code.

Isochronous code is perhaps the worst of all possible code to write, leaving aside self modifying code, wihch is itself a death wish.

If the main task is too long for the polling time, I would set up a timer interrupt and use that to tell me when to poll. When the timer goes off, go look at the event you're watching.

If I absolutely, positively have to be on top of the event the instant it happens, nothing except pure interrupt driven will work. Sometimes that's not fast enough if you have a complex interrupt handler. The interrupt latency, the time between the interrupt signal being asserted and the time when you actually service the event, can be too long in some situations.

You have to know how often the real world event happens, and how long you can wait to service it before you can deal with writing code for it. Fortunately, humans and mechanical devices are slow compared to computers.

For instance, sensing the state of a momentary switch. Humans push switches. Human reaction time (that is, the human interrupt response time) is somewhere longer than 1/20 of a second. Faster than that is not detectable from instant by most humans in most circumstances. Human mechanical response time, (as in pressing a switch based on seeing a light) is on the order of 100mS to 250mS if the human is not   o o o n n  n n   d r u   g s, ill, or otherwise indisposed. So if you can respond in under 0.02 to 0.2 seconds, you usually are fast enough for humans. Switches bounce. You ... can ... not... tell if a switch is making, unmaking, or bouncing in less time than the switch bounce clearing time. There have been books written about debouncing switches.

R.G.

In response to the questions in the forum - PCB Layout for Musical Effects is available from The Book Patch. Search "PCB Layout" and it ought to appear.

R.G.

Your program will always have a Main Loop.

It will start at some point, do everything it can, and then go back and start it all over. This main loop is what actually processes all the work. Hidden inside the main loop is all the actual code.

Common programming problems are

  • accidentally jumping to a place outside the main loop, so your uC continues down the list of unrelated and misaligned data, picking daisys, relieving itself in the corner, playing with its toes and setting off explosions
  • unending loops - your program somewhere INSIDE the main loop locks up in a sub-loop that never exits, so the program looks like it simply halts. Chances are it's not halted, it's just sitting there counting its toes over and over and over and over and over and over and over and over and over ...
  • deadly embrace - two subroutines inside the main loop either call one another making in effect an unending loop, or if you have a scheme where one subroutine waits for another, you can have two subroutines waiting for something held by the other, same result as an unending loop, just another way.
The main loop will always take some time to execute. This may be fixed and it always executes in the same amount of time, or it may be variable, if things it reads in make it take more time in one loop than another to execute based on that data.
R.G.

In response to the questions in the forum - PCB Layout for Musical Effects is available from The Book Patch. Search "PCB Layout" and it ought to appear.

R.G.

Subroutines are programming's way of saying "dang, I'm tired of typing".

If you do the same thing in several places, don't retype it - make it a subroutine. That way you can just CALL it and only have to type it once. This makes your code much more readable and understandable. It also helps you keep things clear in your head.

And you only have to type it once.... 8-)
R.G.

In response to the questions in the forum - PCB Layout for Musical Effects is available from The Book Patch. Search "PCB Layout" and it ought to appear.

R.G.

Interrupts are subroutines that are kicked off by something outside the main line programming.

A real world event, like a pin going high, or a serial port detecting an incoming character, will assert an interrupt. The processor hardware is set up to force the processor to start executing at some specific point when this happens. It's your job as the programmer to write code that deals with whatever has happened.

You can
- ignore it and just return to the main program
- fix or interpret whatever happened
- cause execution of different code when you return to the main program
- store data away for the main program

Interrupts necessarily have to save all of the stuff that's in progress when they happen. Most hardware saves most or all of the processor state - all of the registers, counters, etc exactly as they were when the interrupt happened, and restores them like they were when you finally execute the 'return from interrupt'  instructions. Some processors you will need to programmatically save more. This is one of the frustrations with PICs - they don't save all you need in hardware.
R.G.

In response to the questions in the forum - PCB Layout for Musical Effects is available from The Book Patch. Search "PCB Layout" and it ought to appear.

R.G.

Reading a pot with one pin was always magic to me.

On a PIC (actually, any uC chip where you can change pins from input to output and vice versa, and that have well defined input threshold voltages) it's easy.

You just attach the pot to the pin, connected as a variable resistor, and the other end of the pot through a capacitor to ground.

1. make the pin be an output, set it to high (for instance) and let it stay that way for long enough to guarantee that the cap is fully charged even if the pot is max resistance.
2. make the pin be an output, set it to the opposite state from the charging in (1)
3. wait for a short, highly specific time; this lets the pot discharge the cap
4. make the pin an input, and see if you read a one or a zero; the well defined input threshold means that the pin will snap over to opposite of the charged state from (1) at the same voltage every time
5. add one to the "times I waited" number
6. if it has not changed yet, go back and start from (2) until it does
7. the "times I waited" number is now proportional to the pot setting.

You have to scale the resistor and cap to fit with a reasonable number of times so you get good resolution. For instance, you'd like the maximum to always be 255 times in 8 -bit machines. 
R.G.

In response to the questions in the forum - PCB Layout for Musical Effects is available from The Book Patch. Search "PCB Layout" and it ought to appear.

Peter Snowberg

:icon_cool: <- click for a special message from Bob Pease.

Very nice R.G. 8) 8) 8)


Note to everyone getting started in microcontrollers: Print this thread out and keep it handy. Don't expect it to make sense right away, but it will in time.

.
Eschew paradigm obfuscation

davebungo

Keep ISRs (Interrupt Service Routines) as short as humanly possible.  i.e. Just do enough to decouple the real world event from the software and then get the hell out of there.  This can normally be achieved by using a buffering system especially in communication applications.  i.e. The ISR responds to the hardware event and queues up an event or a character or whatever in a buffer.  The main application process reads the buffer at its leisure, so the buffer takes up the slack.  As long as a) the buffer is large enough to soak up the timing mismatch between the main application process and real world event, and b) the overall throughput can be maintained by the overall application as a whole then this system works fine.

An additional advantage to having ISRs as efficient as possible is that they do not need to use so many registers and so less need to be stacked up/saved away resulting in increased performance.  Keeping simple also means you can probably quite easily write them in native assembler which would be difficult if the ISR started trying to understand/interpret incoming data using some complex algorithm.  Keep application code in the main application and use ISRs for efficient real time transport and decoupling of events.

Keeping ISRs as short and to the point as possible is one of the secrets of good quality real time software.

R.G.

Good advice.

In my prior life when I was managing a unix kernel development group, we had found that the maximum time needed to spawn a subsidiary process was ( some specific number I've forgotten). In program reviews, you had to prove that you could service an interrupt in less than that amount of time to be allowed to do the servicing in the interrupt level rather than spawning a process to handle it and getting off the interrupt level.

One other thing I picked up early - the most likely thing to be executed after you return from interrupt is ... another interrupt. You had the hardware masked off while you were in there in the interrupt handler, so no other asychronous process could get in. It's lurking out there, just waiting for you to do a RETFI. Interrupt routines had better be clean going in and clean coming out.
R.G.

In response to the questions in the forum - PCB Layout for Musical Effects is available from The Book Patch. Search "PCB Layout" and it ought to appear.

amz-fx

Quote from: Peter Snowberg on March 10, 2006, 03:36:29 PM
:icon_cool: <- click for a special message from Bob Pease.

hahahaha..  I was just reading his column in the new issue of ElecDesign at lunch today where he was ranting about autosave and word processors...  Bob is an analog guy in a digital world  :icon_mrgreen: :icon_mrgreen:

regards, Jack

jrem

great thread, my only point would be that if you are new to mu's then do yourself a favor and get either a basic stamp (www.parallax.com) or a picaxe (www.phanderson.com) (both stateside) and dig on them first.  High level programming, and if you're just reading pins and pots and firing outputs, it's insanely easier than assembly.  But if you're a tinkerer, you will need to do assembly just to hang your hat.

R.G.

Quote from: Jackhahahaha..  I was just reading his column in the new issue of ElecDesign at lunch today where he was ranting about autosave and word processors...  Bob is an analog guy in a digital world
No, Bob is a hero in an insane world...

He's what Bob Widlar could have been if he had any ability to talk to humans and/or thought that he, himself, was one.    :icon_lol:

Quote from: jremmy only point would be that if you are new to mu's then do yourself a favor and get either a basic stamp (www.parallax.com) or a picaxe (www.phanderson.com) (both stateside) and dig on them first.  High level programming, and if you're just reading pins and pots and firing outputs, it's insanely easier than assembly.  But if you're a tinkerer, you will need to do assembly just to hang your hat.
I'd go with that.

On the other hand, nothing we've said here applies just to assembly. This is all stuff that good programmers developed independent of language or hardware system or language level. What's important is to get away from ADDINDir Reg3, HOQZAT, %H33ffee01 as fast as possible and on to "now we go look at the switch and return 1 if it's on".
R.G.

In response to the questions in the forum - PCB Layout for Musical Effects is available from The Book Patch. Search "PCB Layout" and it ought to appear.

bioroids

Quote from: R.G. on March 10, 2006, 10:38:46 AM
Reading a pot with one pin was always magic to me.

On a PIC (actually, any uC chip where you can change pins from input to output and vice versa, and that have well defined input threshold voltages) it's easy.

You just attach the pot to the pin, connected as a variable resistor, and the other end of the pot through a capacitor to ground.

1. make the pin be an output, set it to high (for instance) and let it stay that way for long enough to guarantee that the cap is fully charged even if the pot is max resistance.
2. make the pin be an output, set it to the opposite state from the charging in (1)
3. wait for a short, highly specific time; this lets the pot discharge the cap
4. make the pin an input, and see if you read a one or a zero; the well defined input threshold means that the pin will snap over to opposite of the charged state from (1) at the same voltage every time
5. add one to the "times I waited" number
6. if it has not changed yet, go back and start from (2) until it does
7. the "times I waited" number is now proportional to the pot setting.

You have to scale the resistor and cap to fit with a reasonable number of times so you get good resolution. For instance, you'd like the maximum to always be 255 times in 8 -bit machines. 

This is great. I'm trying to implement it on the SKT500 but I'm failing at some point. I think there must be some problem arising when changing output to input, or maybe it has to do with the pull-up resistors...

Is it allright if I post some code here? I need some help debugging this thing

Luck!

Miguel
Eramos tan pobres!

R.G.

Sure, post your code.

Let me get some of the basics of this process out of the way though - it may be that you will find your mistake directly.

1. You can't let this code be interrupted. Turn off interrupts while you're doing this, because an interrupt routine will halt execution of this time-critical code.
2. This is best done in assembly, because you don't know what your compiler is doing as overhead on each statement. Some compilers stick all kinds of gook in between what you think are single statements in high level language. Note that you only have to get this to work once, then you case it up as a subroutine that you put into your new programs whenever you want to read a pot.
3. You need to know that the voltage thresholds of your uC's inputs are. Refer to the chip's datasheet. On a PIC, most of the pins are CMOS schmitt trigger inputs, so the high voltage is pretty near 2/3 Vcc and the low voltage is near 1/2Vcc, and they're well defined. Inputs that have sloppily defined input tick-over points will give sloppy (and hence lower resolution) pot reading. PICs work easily between 7 and 8 bits of resolution. I don't know about other uC's.
4. The max/min resistance and the cap have to work together with the software. It's best if you pick an R-C combination that charges in about 255 times whatever your waiting time is.  For instance: if you use a 10K (max) pot and a 0.1uF cap, then the time constant is 10K*0.01E-6 = 100uS if I did the math right. When discharged to 0V and charging toward Vcc through a 10K pot, the voltage rises to 1/e times (or 0.632) times Vcc in 100uS. You'd like for that to be a significant number of counts, but 100uS is awfully fast for uC 's that execute between 1 and 20 instructions per uS. It would be better to use a 0.1uF cap, or a 100K resistor or both. That gets the time constant up so that 0.632 of Vcc in 1mS to 10mS, and now you can use a timer set at 1/255th of that to tell you when to read the pot and see if that pin is now reading "1".  Time constants that are too short or too long get very confusing.

But sure, post your code.
R.G.

In response to the questions in the forum - PCB Layout for Musical Effects is available from The Book Patch. Search "PCB Layout" and it ought to appear.

bioroids

Ok, here it is ;) Now the code works somehow!

Quote
.include "8515def.inc"
.def delay =r16
.def delay2 =r17
.def time =r18
.def potValue =r19
.def portRead =r20
.def potCount =r21
.org 0
resetvector:
       rjmp main

main:
;set stack pointer
      ldi delay, high(RAMEND)
      out SPH, delay
      ldi delay, low(RAMEND)
      out SPL, delay
      
      ldi time, 0xFF   ;set amount of wait
      sts $37, time   ;turn all LEDs on (PORTB)
      ldi potValue, 0   ;initialize pot value

loop:   
      sbi DDRD, 0         ;set bit 0 at PORTD as output
      sbi PORTD, 0      ;logical 1 at the output
      rcall wait          ;wait while cap charges
      rcall wait          ;wait a little more
      ldi potCount, 0x00   ;initialize count
count:
      sbi DDRD, 0         ;set bit 0 at PORTD as output
      cbi PORTD, 0      ;logical 0 at the output
      nop               ;wait a few cycles
      nop
      cbi DDRD, 0         ;set bit 0 at PORTD as input
      cbi PORTD, 0      ;turn off pull-up resistor
      sbis PIND, 0      ;read pin, skip instruction if still set
      rjmp discharge      ;zero reached, jump
      cpi potCount, 0xFF   ;not zero, check if max value reached
      breq discharge      ;yes? then leave count
      inc potCount      ;not yet, increase count
      rjmp count         ;return to keep counting
discharge:
      mov potValue, potCount   ;get final count value
      sts $38, potValue      ;output pot value to PORTB
      rjmp loop         ;start everything again
      
wait:
;nested loop counting down to implement wait
      mov delay, time
chequea:
      dec delay
        breq sigue
      rcall espera2
      rjmp chequea
sigue:
      ret

espera2:
      mov delay2, time
chequea2:
      dec delay2
        breq sigue2
      rjmp chequea2
sigue2:
      ret

I'm using Atmel 8515, reading the pot with pin 0 of PORTD, and outputting the read value at PORTB (8 bits).
The pot is 10K linear and the cap is 220nF.

It seems to be working now, as you said is VERY important to get the pot, cap and soft to work together for this.

Is still a little sloppy anyway, I didn't use timers or interrupts, just the pot reading code on the main loop.

This is basically a test code for learning, I'm sure it can be improved a lot.

The main problem now is that, I get the "zero" reading at some resistance, say 8k, then if I keep turning the pot, the value the uC reads starts to decrease instead of keep increasing. This happens with different caps too, and with different potentiometers (I tried 100K), but a different angles of the pot rotation.

Thanks a lot!

Miguel
Eramos tan pobres!

The Tone God


bioroids

Yes, now that I think about it, this is a software implementation of a sucesive approximation AD converter, right? LOL I never understood how that worked until now (I think).

Sadly the 8515 has no ADC, but it has an analog comparator that can be put to good use in this case. But that takes two pins, and for what I read, saving a pin is always a good idea.

Luck!

Miguel
Eramos tan pobres!

R.G.

Quote from: AndrewUmmm...A/D ?
Sure - if your chip HAS an A-D in it. I've done it both ways. It is awfully nice to get even crude pot reading sometimes. For instance - a pot can function as an eight position switch, depending on position. I've done that a lot. But getting to a one-of-128 positions is something that's very reasonable to do, and...

it's a built-in-function of many PIC compilers, including the one I use...  :icon_biggrin:

But inline assembly is great for stuff like that. ... er... your compiler DOES let you stick assembly language clots in line in the program somehow, yes?   :icon_question:

QuoteYes, now that I think about it, this is a software implementation of a sucesive approximation AD converter, right? LOL I never understood how that worked until now (I think).
Actually, no, it's not even that smart. It's just counts the time interval that it takes for the cap to rise. Successive approximation only needs eight intervals to reach a one-of-2^8 resolution. This scheme needs 255 intervals.

It's nonlinear, too. The rise of voltage on a cap through a resistor is a decaying exponential, so the slope is not constant. If you count equal time intervals, the voltages are further apart at the start of the conversion, and closer together at the end because the voltage changes fastest at the start.

But in most cases, a pot is just a human's foggy idea of how to set things, and having non-identical steps is not a big problem.

Analog comparators are good, but as you note, they eat up two pins. Some variants of PICs with internal comparators have internal reference voltages too, so that gets you back to one pin.

I'm sorry I can't be more help with your code. The commentary looks OK, from what I can read of it, but I am not conversant in 8515 assembly, and won't be until I have to program something in it.
R.G.

In response to the questions in the forum - PCB Layout for Musical Effects is available from The Book Patch. Search "PCB Layout" and it ought to appear.

Peter Snowberg

If you can use an input voltage to program a current source which then charges the cap, you can get much better results. ;)
Eschew paradigm obfuscation

The Tone God

Quote from: bioroids on March 12, 2006, 06:17:08 PM
Sadly the 8515 has no ADC, but it has an analog comparator that can be put to good use in this case. But that takes two pins, and for what I read, saving a pin is always a good idea.

I would presume that you are using the AT90S8515 that came with the STK500. That uC has been EOL'd by Atmel so I would not get too carried away with creating designs with that IC. Grab something newer and cheaper that can do the job better.

Quote from: R.G. on March 12, 2006, 07:27:24 PM
Sure - if your chip HAS an A-D in it. I've done it both ways. It is awfully nice to get even crude pot reading sometimes. For instance - a pot can function as an eight position switch, depending on position. I've done that a lot. But getting to a one-of-128 positions is something that's very reasonable to do, and...

The VAST majority of AVRs have A/D built in. Only a handful of the old ones, like the 8515 that Miguel is speaking of, does not. As for what you can do with a pot reading, done all that and more. Fun stuff. :)

Quote from: R.G. on March 12, 2006, 07:27:24 PM
But inline assembly is great for stuff like that. ... er... your compiler DOES let you stick assembly language clots in line in the program somehow, yes?   :icon_question:

Yes almost all if not all compilers for AVR including GCC allow inline assembly.

Andrew