NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Help with code please

June 05, 2009
by dangraham
dangraham's Avatar

Hi, I am trying to merge the Servosquirter and Tempsensor code so they both operate. After what feels like a 1000 flashes this is the best I have found. For some reason when I compile it says "warning: 'temp_avg' may be used uninitialized in this function"

When you boot it up it displays the servo pos ok but line two is blank. As soon as you issue any command from the serial port the line two displays the temp but it is not logging and change in temp?

Here is the code

I am still getting to grips with C and not sure where it is wrong. Thanks for the advice.

June 05, 2009
by mcai8sh4
mcai8sh4's Avatar

Right, I'll have a go - I'm just reading through your code (I'm not great with the programming yet but...) and I think I can see your initial problem. Line 129 tc = uart_read(); At this point your program waits until there is an input then continues with your code - since you're sending the temperature to the display after this line, when you initially turn on it won't be displayed because it hasn't reached that part of your code.

Regarding the temp not displaying properly (this is just a guess) the first time you refer to temp_avg (other than declaring it on L.110) is line 144 - and you are writing the value to the display before it has had a value stored to it (line 149).

This could be the reason for the compile warning.

Try moving the temperature display section (L.142-147) to after line 157.

The temperature should be working (I haven't seen any problems with it), but it will only update when you send a character to the serial port (due to the reasons in the first paragraph). Maybe try sending a constant supply of characters to the serial and see if it updates!?

I can't think of a quick way around this 'waiting for input' at the minute (I'm sure someone will fill you in on that one), but if I have any ideas, I'll give you a shout

Hope the above's of some help.

(I'm assuming your editor has line numbering - otherwise most of the above will be meaningless - if thats the case let me know, I'll try to explain better)

Good luck

June 07, 2009
by dangraham
dangraham's Avatar

I have moved things around and realise the problem with the display now; thanks for the pointer. The problem I have now is the ADC does not appear to be reading. I know the hardware is ok because the servosquirter code and temosensor code work separately. I have updated the code in the above link; can you see why the ADC will not work? I am working through the C books and it is starting to make sense ;-)

When I issue commands to the servo the ADC readout will flux very slightly by say 1 of the 1024. I presume this is because of variation on the 5V caused by the servo. But I am still not sure why it is not reading the temp sensor?

Thanks,

June 07, 2009
by mcai8sh4
mcai8sh4's Avatar

Glad to here things are starting to make sense. Can I just check one thing, when you say the temp readout is varying slightly each time a command is issued to the servo, I just want to check it's not just the update frequency thats causing the problem.

I've looked though the code (briefly) and can't see anything major wrong with it. Turn on the device, make note of the reading (x/1024), then warm the LM34 with your fingers for a short time (30 sec), then issue another command to the servo. The temp reading should increase. I'm thinking that due to the temp reading and display update only occurring when a command is issued to the servo, there is no noticeable difference.

Hopefully this is the case, then you just need to figure out a way to have the screen/temp updating without being stalled waiting for a servo command to be issued.

If you have already tried this, then sorry, I miss-understand.

Another option is to briefly disable the servo by commenting out L.146 - then see if the temp part functions as expected - if it does then we may have found the problem.

June 08, 2009
by dangraham
dangraham's Avatar

Yep you are correct again! I found the connection issue with the LM34; this was not helping. The temp is not updated because it’s waiting for the serial command. I commented it out as suggested and it works ok. I just need to work out how to have the temp running in one loop and the servo in another? Back to the books I think! Thanks for the help...

June 08, 2009
by mcai8sh4
mcai8sh4's Avatar

There may be a method of reading a command if there's one there then controlling the servo (LED marquee example may help - not sure), but carrying on if there isn't, OR you could use a timer to trigger an interrupt to update the temp every x seconds.

To be honest I'm not really that competent with all this, but those are the paths I'd go down, until someone suggests a more elegant method.

Keep us posted on how you get on

June 08, 2009
by wayward
wayward's Avatar

dangraham,

unfortunately, you can't have two loops running at the same time. These microcontrollers can execute only one line of instructions at a time (i.e. no multitasking/multithreading, unless you implement some context switching of your own, but I digress). However, they are very well suited to managing multiple sources of information and controlling several different devices at the same time.

The loop paradigm works for demonstration purposes, when the workflow is clearly defined in terms of what gets computed before, what comes after, and you are not particularly interested in the possible delays from waiting on the various subsystems to do their job (i.e. ADC). However, in practice, we use interrupts most of the time. They tend to be easier to use if your program grows past a certain point. Using the ADC as an example, if we switch to interrupts, all code pertaining to ADC is stored inside the interrupt handler function, instead of being interspersed throughout the while(1) { ... } loop; there is no waiting on the ADC to give us the calculated value -- we get notified when it's available; less power is used since we can switch to power saving mode until an interrupt comes along.

As mcai8sh4 said, interrupts can be a little confusing at first. Let me try to explain the basic idea here.

In programming practice, there are certain situations when we write functions that we don't invoke directly. Those functions are called "callbacks" or "handlers", and are bound to events generated outside of the scope of our program (ADC reporting calculated value; graphical user interface notifying the program that the user has clicked a button on the screen; Javascript letting us know that the page has finished loading; etc.) Handlers need to follow a specific contract so we can successfully bind them to the event. Most commonly, the contract for a handler is simply stated as a declaration of a function:

void handle_my_interrupt_woohohoo(int, int);

By the way, in C, such "empty" function headers are called "function prototypes". More generally, you may encounter something like this:

typedef void (*int_handler) (int, int);

which is a type definition for a type called int_handler, which is a function, accepting two integer arguments, returning nothing (void).

To write an interrupt handler, we simply obey the contract for its handler. If you take a look at avr-libc documentation, you'll find that an ADC interrupt handler is written like this:

ISR(ADC_vect)
{
    // user code here
}

ISR macro actually writes out the function declaration for us; we pass it the name of the vector (address of the interrupt handler we're installing) and provide the actual function body. ADC_vect is another macro that expands to the correct address of the ADC interrupt handler on the device we're writing the program for, so the above will work on all microcontrollers supported by avr-libc. One less thing to worry about! The program will stop and this function will be called whenever ADC has finished the conversion we started previously. When ISR(ADC_vect) returns, program continues normally.

There are two more things we have to do in order to use this function. First, we need to actualy enable ADC interrupts; and second, we need to have a shared variable between the main program and the interrupt handler, which the former would read from, and the latter would write to. Such variables, subject to unpredictable changes, are called "volatile" and need to be specified as such in the C code, or else the compiler might decide to optimize them into registers instead of storing them in the RAM. That could ruin your other instructions when the interrupt handler blithely overwrites whatever values they had stored in said registers, and ultimately, ruin your day.

Here's the code I put together as a demo. I am under Vista now and can't test it, so it is more likely to not work than otherwise, but feel free to try for yourself and let me know how it goes.

#include <avr/interrupt.h>  // don't forget!

/* ... */

volatile float temp;  /* I forgot how the temperature is kept in tempsensor.c */

/* ADC "conversion completed" handler. */
ISR(ADC_vect) {
    uint16_t value_from_adc = ADC;  /* this may work instead of messing with ADCL/ADCH */
    temp = to_fahrenheit(value_from_adc);  /* I forgot actual function names */
}

void adc_init() {
    /* set analog to digital converter:
       external reference (5V), single ended input ADC0 */
    ADMUX = 0;

    ADCSRA = (1<<ADEN)    |  /* ADC enable */
             (1<<ADPS2)   |  /* prescaler 1/128: ADC clock 115.2kHz */
             (1<<ADPS1)   |  /* -||- */
             (1<<ADPS0)   |  /* -||- */
             (1 << ADATE) |  /* ADC to free-running mode */
             (1 << ADIE);    /* ADC interrupt enabled */
    sei();  /* enable interrupts, globally */

    // fire a conversion just to get the ADC warmed up
    ADCSRA |= (1<<ADSC);
}

/* ... */

int main(void) {

  /* ... */

  while(1) {
    /* variable temp will have the latest temperature in Fahrenheit
       at all times.  Use it in a loop, write it to the LCD, etc. */
  }
  /* ... */

}

Once again, avr-libc documentation is indispensable.

Hope this helps, Zoran

June 08, 2009
by wayward
wayward's Avatar

Ah, of course, remember to call adc_init() before the main loop. :)

Also this: sei() from adc_init() actually belongs in the main function body, not in the ADC initializer, as it affects all interrupts on the microcontroller. We're enabling just one, to be sure, but still, the context of sei() is such that it should be easily accessed in a single place of code regardless of the interrupt initializations elsewhere.

Zoran

June 09, 2009
by dangraham
dangraham's Avatar

Wow that has given me something to work towards now! Thanks for the info I will see if I can work my way through it.

June 12, 2009
by luisgarciaalanis
luisgarciaalanis's Avatar

this post will confuse readers

ADCSRA |= (1&lt;&lt;ADSC);

That is waky! what is this??? -> &lt;

yout copy paste is wrong, what editor are you using to copy paste from that is changing < to that wiked code &lt;

unexpiriensed C programmers might freakout and run away crying :(

June 12, 2009
by luisgarciaalanis
luisgarciaalanis's Avatar

LOL I think its Internet Explorer 8 on Win7 that is showing making this changes, bacuse chrome displays this right. although is I write it myself << it looks fine on IE8.. So I don't know.

Sorry about the randomization of the thread

July 09, 2009
by dangraham
dangraham's Avatar

Ok I am starting to understand and thanks for the help it is really appreciated. I kind of understand about the interrupts and when the data is there the interrupt will interrupt?!?

Please can you help me start from the very beginning? All I want to do is enable PC3 as an input with internal pull up, and store the amount of times it has changed state / pulled down per sampling period to an interrupt value. Sorry if that makes no sense but I am very new to this language! I am expecting the PC3 to change state between 1-2Khz I have no idea what my sampling period should be? I would guess and start at 1/10th of second see how that works.

So I will need to setup timers for the sampling period.

PC3 as input.

Interrupts and handlers....still lost on the code for these.

So that when my code runs, at the end of every sampling period the interrupt will tell my main, here is the value. I can then in my main use this value and make calculations. Think I will leave the ADC for now reading PC3 is more important at the moment. Once I have figured this out I will probably be able to manage the ADC!!

Please tell me I am on the right copper track! If anyone can help fill in the gaps I owe you a beer or 10! Thanks guys.

July 09, 2009
by dangraham
dangraham's Avatar

Or what would be even better, if the interrupt could log the amount of state changes per sampling period, subtract this from my target (based on 1/10 second sampling = 155) and interrupt me with the answer this would then be my error rate. Is this even possible or do I just get the amount of pin changes and have to do the calculations in the main?

July 09, 2009
by mcai8sh4
mcai8sh4's Avatar

Dangraham : I'm sure there's a few people out there (here) who are happy to help. I am one of those people - because this looks like fun AND I'm still learning all this stuff, so it's good practice for me.

I've got a busy schedule over the next couple of days, so I might not be able to offer much, but as soon as I return home, I'll do what I can.

Keep us all updated on where you are up to, and by Sunday I (hope) to be able to help.

For the mean time (and I have literally speed read your posts), I would think

1) you can use a pin state change interrupt (triggered whenever a particular pin changes fron 1 to 0 or 0 to 1) to count the passes of your magnets.

2) Then use a method as shown in the realtime clock example (or similar to) to count the seconds, when 1 sec has passed...

3) thats when the maths come in to calc the speed - after that..

4) ...

5) profit!?

I you have any quick questions, it might be worth popping into the IRC channel (still quiet, but it's picking up), and asking the folks there - everyones very friendly (like here) so don't be scared to ask (what you may think are) stupid questions.

September 20, 2009
by pbfy0
pbfy0's Avatar

I would rewrite the code like this. I edited the uart_read function so instead of looping on nothing when there isn't a char waiting, it loops on updating the temp and printing it to the lcd.

September 20, 2009
by rusirius
rusirius's Avatar

I'm not at home and don't really have time to go into it right now... But maybe it will help a little... If you don't mind the suggestion, just glancing at what you're trying to do...

You're reading and waiting for a character before continuing on... Why? You only want read a character from the port if one has been typed in...

In other words, you have: do stuff tc = uart_read(); act on tc ** continue with loop

Why not do this instead: do stuff if (uart_char_is_waiting()) { tc = uart_read(); act on tc } ** continue with loop

In that way, each time through the loop, all the regular stuff get's done, like updating the temp, etc... but you will only ever read the uart if there is actually something sitting in the uart to read...

September 20, 2009
by dangraham
dangraham's Avatar

Been away for a while but need to continue with my project. I have looked at code above but not working, I am not that concerned about temp for now I need speed control first. How can I use the timer interrupt compare to time stamp each time the pin is changed? I think I need similar to the TRACTOR PULL SLED MONITOR! I have my hall sensor with digital output and need to display the elapsed time from each change of pin state, then I can calculate from this value.

From data sheet, "ICP1, Input Capture Pin: The PB0 pin can act as an Input Capture Pin for Timer/Counter1. The Input Capture Register can capture the Timer/Counter value at a given external (edge triggered) event on either the Input Capture pin (ICP1) or on the Analog Comparator pins"

How would I go about basic code to display this? I am hoping that if I can show the elapsed time of each event I can then work out the control. I would also need to compare the current elapsed time with the previous, so I know if accelerating or decelerating. Once I have this I can then use the error to alter the servo position and control my system.

Below is what I want as end result, but for now just the basic speed control!

PB0 / PC4 - IC1P Tach Digital Input capture.- reset issues if PB0 used? PB2 - Servo PWM output PB3 - DC Motor/fuel pump on/off PB4 - Compression solenoid/exhaust fan on/off PB5 - Temperature cooling fan on/off PC0 - Temperature sensor 1 analog input. PC1 - Temperature sensor 2 analog input. PC2 - Manual Override switch (plant on/off) PC3 - Plant good indication output on/off

Serial connection for remote monitoring / starting

real time clock / timed events plant on/off by manual switch or timed event.

Use ICP1 to time pulses. and compare to target time 0.00064516129sec (rounding?) result = error rate

PI plant control to maintain speed via tach pulse period & servo compare value. Error rate. if greater than 15 Plant good output off. Error rate. if greater than 20 plant off/safety off - will need delay timer to prevent false shutdown Temp control of engine via PB5 & PC0 & PC1 If temp1 is greater than 60 + error is less than 15 Plant good output on. If temp1 is greater than 100 cooling fan output on. If temp1 is less than 80 cooling fan output off. If temp1 is greater than 130 Manual Override/safety off. If temp2 is greater than 60 Manual Override/safety off.

if plant is off/safety off shut down routine.

Plant good output off PC1 DC Motor/fuel pump off PB2 0% throttle/PWM compare value Delay "decompression - compression solenoid off" Compression solenoid off - PB4 Cooling off - PB5 Sleep on timer until next plant on event. Outputs for lcd / serial Timer - run time - count down until plant on

Hope that helps you understand what I am trying to do, thanks for help.

September 21, 2009
by dangraham
dangraham's Avatar

I think this is what i need from the datasheet, because PB0 is used for programing?

Bit 2 – ACIC: Analog Comparator Input Capture Enable When written logic one, this bit enables the input capture function in Timer/Counter1 to be triggered by the Analog Comparator. The comparator output is in this case directly connected to the input capture front-end logic, making the comparator utilize the noise canceler and edge select features of the Timer/Counter1 Input Capture interrupt. When written logic zero, no connection between the Analog Comparator and the input capture function exists. To make the comparator trigger the Timer/Counter1 Input Capture interrupt, the ICIE1 bit in the Timer Interrupt Mask Register (TIMSK1) must be set.

September 25, 2009
by dangraham
dangraham's Avatar

If I use PCINT1 as my input how can I work out my speed? Would it be possible to use the real time clock?

I understand ICP1, Input Capture Pin: The PB0 pin can act as an Input Capture Pin for Timer/Counter1. But I am worried about this pin being used for rest / programming. Should I avoid using this pin or will it be ok for my needs?

I would like to time the pulses as they arrive so I can compare the current pulse time to the previous, this will then tell me if I am speeding up or slowing down?

Thanks,

September 30, 2009
by pbfy0
pbfy0's Avatar

I think I would just do it like this for incrementing on pin change(I THINK it will work): int count;

void pci_init(){
    PORTB |= (1<<PB1)
    PCICR |= (1<<PCIE0);
    PCMSK0 |= (1<<PCINT1);
}

ISR(PCINT1_vect){
    count++;
}
November 29, 2009
by pbfy0
pbfy0's Avatar

should be

void pci_init(){
    PORTB |= (1<<PB1)
    PCICR |= (1<<PCIE0);
    PCMSK0 |= (1<<PCINT1);
}

ISR(PCINT0_vect){
    count++;
}
November 29, 2009
by pbfy0
pbfy0's Avatar

I mean

int count = 0;
void pci_init(){
    PORTB |= (1<<PB1)
    PCICR |= (1<<PCIE0);
    PCMSK0 |= (1<<PCINT1);
}

ISR(PCINT0_vect){
    count++;
}

Post a Reply

Please log in to post a reply.

Did you know that many systems in nature can be described by a first order response? Learn more...