1
\$\begingroup\$

I have ATMEGA328P-PU and I have hooked up the photocell to it. I'm using the following code to read the analog values and send them back to computer for debugging.

#define F_CPU     800000UL
#define BAUD      11400
#define BRC       ((F_CPU/8/BAUD) - 1)

#include <stdlib.h>

#include <avr/io.h>
#include <avr/interrupt.h>

#include <util/delay.h>
#include <util/setbaud.h> 
#include <avr/io.h>
#include <avr/interrupt.h>

double dutyCycle = 0;

int main(void)
{
    initSerial();


    DDRD = (1 << PORTD6);

    TCCR0A = (1 << COM0A1) | (1 << WGM00) | (1 << WGM01);
    TIMSK0 = (1 << TOIE0);

    setupADC();

    sei();

    TCCR0B = (1 << CS00) | (1 << CS02);

    while(1)
    {
        unsigned int  value = ADCW;
        UDR0 = (value*100/1024) ;                                                                                                                                                                                            
        _delay_ms(1);  
    }
}

void initSerial()
{
    UBRR0H = (BRC >> 8);
    UBRR0L = BRC;

    UCSR0B = ( 1 << TXEN0 );                                                                                                                                                                                                
    UCSR0C = ( 1 << UCSZ00 ) | ( 1 << UCSZ01 );                                                                                                                                                                             

}

void setupADC()
{
    ADMUX = (1 << REFS0) | (1 << MUX0) | (1 << MUX2);
    ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2);
    DIDR0 = (1 << ADC5D);

    startConversion();
}

void startConversion()
{
    ADCSRA |= (1 << ADSC);
}

ISR(TIMER0_OVF_vect)
{
    OCR0A = dutyCycle;
}

ISR(ADC_vect)
{
    dutyCycle = ADC;
    startConversion();
}

I use the following command to view the sensor output:

od -d /dev/ttyACM3 -w2

I can see the output change based on the amount of the light, but it changes really weird, if I slowly remove the light source from the sensor I get this output:

      3542
      3298
      3142
      2799
      2243
      2142
      1799
      1543
      1542
      1537
      1542
       262
       257
      1542
       262
       257
      1542
       257
      1537
      1542
         1
       256
       257
       262
     65535
       255
         0
     65535
         0
     65278
     65535
     65278
     30969
     30968
     63736
     63993
     63737
     63479
     63992

First half looks great, but then it suddenly jumps to 63k, this happens every 10cm, then goes down from 63k to 0 then again jumps back to to 63k. can anyone please tell me how to convert ADCW to decimal value? I;m expecting number between 0 to 1024 as the output.

The manual says "ADC has a resolution of 10 bits, it requires 10 bits to store the result" doesn't that mean that it's a 10bit (16bit) instead of 8bit?

\$\endgroup\$
  • \$\begingroup\$ I cannot understand how your code sends data through the serial, can you elaborate a bit on that? \$\endgroup\$ – Vladimir Cravero Jul 22 '16 at 21:51
  • \$\begingroup\$ @VladimirCravero I'm using POLOLU PGM03A which has "a TTL-level serial port for general-purpose communication" \$\endgroup\$ – K666 Jul 22 '16 at 22:06
  • 1
    \$\begingroup\$ You have a 16-bit signed integer coming from the AtMega, and you are displaying a 16-bit unsigned integer. You're seeing the wrap around, with -1 displaying as 65,535. \$\endgroup\$ – Mark Jul 22 '16 at 22:07
  • 1
    \$\begingroup\$ Most importantly, it seems to me that you never read ADCL and ADCH. Maybe ADCW is a macro for that, something like #define ADCW ((ADCH<<8)|ADCL)? Then again, you set the ninth bit to always be 1 and then set up the serial in 8 bit mode. And finally, you expect data to be in 0..1023 range but you do ADCW*100/1024 so data should be in 0..100 range and fit in 8 bit. Something is missing here... \$\endgroup\$ – Vladimir Cravero Jul 22 '16 at 22:09
  • 1
    \$\begingroup\$ You can't, and you do not need it. I find really odd that you get 16 bits numbers, this is entirely because of how od works I guess. \$\endgroup\$ – Vladimir Cravero Jul 22 '16 at 22:21
2
\$\begingroup\$

You cannot shove a multi-byte value through an 8-bit serial channel in a single operation. As you are not selecting various bitfields in the source value, what actually happens is that you are sending the least significant 8 bits of successive readings. And then misinterpreting 2 or 4 of them in a row as bogus multi-byte values.

If you can tolerate 256 distinct values, you could modify your code to right shift the conversion value (or operate in 8-bit mode to start) so that you send a legitimate unsigned single-byte measurement. You will then need to make sure the Linux serial device is operating in a fully raw mode such that no codes are given special interpretations. Finally, you would need to set your display code to interpret the values as 8-bit unsigned integers.

Sending higher resolution measurements is tricky

  • One naive approach is to write sending code which sends each byte of the value in sequence, remembering to make sure that the bytes all come from the same ADC reading. But this approach is naive because it provides no mechanism to synchronize the transmitter and receiver. If the receiver is not already listening before the transmitter starts, or if a byte is ever dropped due to an error, then the receiver may start combining bytes of different readings in shifted order to generate false values. It is possible to work around this with special prohibited start codes that are then escaped if they appear in data, periodic magic-value multi-byte resynchronization headers, or least reliably time gaps between readings (which is especially unwise if USB serial converters may ever be used).

    • In the specific case where your readings are 10-bit you could do something like split them into two 5-bit words and use the high bits of each as a field code, for example 0b011mmmmm and 0b010nnnnn where 0bmmmmmnnnnn is a 10-bit value you want to send. However this would need custom receiving software which looks for a reading beginning 0b011xxxxx and a following one beginning 0b010xxxxx and recombining the appropriated bits of each. (The 0x40 bit is on in each case in the hopes of steering the codes into the printable range, perhaps preserving the possibility of using commandline tools to manipulate the data)

But generally this creates a lot of headache, so it's worth first seeing if a non-binary approach could be viable.

  • Send human readable strings encoding the ASCII representation of digits with a space or newline delimiter. Because the delimiting character cannot be mistaken for a numeric digit, it provides ready splitting of the readings and implicit synchronization. Even in the case of an error, the impact won't persist but will be contained to one or two successive readings. Using a newline is often the best choice, as many stream processing schemes are naturally line buffered, a behavior which may have to be explicitly changed to avoid delay when using a different delimiter.

    • Decimal values are most easily interpreted by eye, and may be fine for your purpose, but you'll either need to zero pad or accept that the number of characters per values is variable.

    • Hex values (padded with leading zeros) yield a clean fixed-size conversion of the data. The value itself will be exactly twice the length of the binary value - ie, 16 bits becomes 4 8-bit characters or 32 bits, but the delimiter will also take a byte, so a 16-bit value requires five characters overall. Given your source resolution is 10 bits, you actually could use just 3 hex digits and a delimiter, for a total of 4 characters per reading. Of course consuming software will need to interpret hex values, but this tends to work well with most things you'd run on Linux; still, figuring it out in some contexts may be a little tricky, might require injecting a prefixing "0x" before interpreting the received string, etc.


There are some additional potential problems in that you attempt to use a fixed but jittery programmatic delay to regulate two things which have more appropriate explicit ready conditions.

  • You are failing to verify that the serial UART is ready for new data before you write to the data register.

  • you should check the data sheet to determine if it is legitimate to read the ADC conversion register when a conversion is not complete; if not, you need to wait for the conversion complete flag.

\$\endgroup\$
1
\$\begingroup\$

You are sending single, unsigned bytes over the serial.

The correct command you need is:

od -t u1 /dev/ttyACM3 -w1

which means that the type of your data is unsigned integer one byte wide, while w1 should print one byte per line.

Also, in your line:

UDR0 = (ADCW*100/1024)  ;

You are not using value, which you properly cast to unsigned int. I am not sure of what the compiler does, but using value on that line would indeed be more clear.

\$\endgroup\$
  • 1
    \$\begingroup\$ Apparently he wants to send 10bit numbers yet the conversion in the code is done incorrectly \$\endgroup\$ – Eugene Sh. Jul 22 '16 at 22:28
  • \$\begingroup\$ @EugeneSh. Sorry I'm super confused now. The manual says "ADC has a resolution of 10 bits, it requires 10 bits to store the result" doesn't that mean that it's a 10bit (16bit) instead of 8bit? \$\endgroup\$ – K666 Jul 22 '16 at 23:09
  • \$\begingroup\$ @VladimirCravero sorry my mistake about the value, I fixed the code, but still getting similar result. Everything seems fine, but about 8cm from the sensor, the value "breaks" and stop making sense. Then few cm further again everything seems to be measuring fine. \$\endgroup\$ – K666 Jul 22 '16 at 23:12
  • 1
    \$\begingroup\$ Are you using od in single byte mode? What do you mean by 'break'? \$\endgroup\$ – Vladimir Cravero Jul 23 '16 at 7:55
  • \$\begingroup\$ @VladimirCravero By break I mean - I have a sensor on a table. I put a flashlight to it, the reading says 3,000, then I move the flashlight away, says 2,000.. 1,500.., 1,000.. and suddenly 12,000.. 11,000.. 10,000.... and so on till it reaches 0 and then jumps back to 12,000 again and goes smoothly down to 0k and so on... seems like instead of one big range, it's reporting me 4 sub-ranges. \$\endgroup\$ – K666 Jul 23 '16 at 13:44
-1
\$\begingroup\$

Use the

od -t d2 /dev/ttyACM3 -w2

or

od -s /dev/ttyACM3 -w2

command instead.

According to man od the -d switch is displaying 2-byte unsigned integers. -s or -t d2 will display signed ones.

\$\endgroup\$
  • \$\begingroup\$ Thanks, but I still get 'sin waves' of numbers as well as negative numbers. Any suggestions? \$\endgroup\$ – K666 Jul 22 '16 at 22:04
  • \$\begingroup\$ It's not sine waves, it's just very noisy. Maybe not enough decoupling in the AtMega, maybe a poor reference voltage, maybe bad impedance matching, maybe other things. \$\endgroup\$ – Mark Jul 22 '16 at 22:10
  • \$\begingroup\$ Yep. That's the way your signal is. Check your connections and grounds. \$\endgroup\$ – Eugene Sh. Jul 22 '16 at 22:19
  • \$\begingroup\$ @Mark it definitely acts like a sin wave. With the commands above, it goes from 12k to 0 to 12k but as in a wave not from 0 to 12k straight. \$\endgroup\$ – K666 Jul 22 '16 at 22:21
  • \$\begingroup\$ Does it change if you cover the photocell? \$\endgroup\$ – Eugene Sh. Jul 22 '16 at 22:22
-1
\$\begingroup\$

Changing the line

UDR0 = ( ADCW * 100 / ( 1024 ) );

To

UDR0 = ( ADCW * 10 / ( 1024 ) );

And calling the command:

od -t d4 /dev/ttyACM2 -w4 

Gave me correct value in a range from 0 to 50,529,027, where 0 being complete darkness. The only problem is that the 'steps' update only every 4cm, where normally I should be able to detect the difference within couple millimeters.

\$\endgroup\$
  • 3
    \$\begingroup\$ ADCW is a 16 bit value ranging from 0~1023. You are multiplying it by 10 to get 0~10230, then dividing it by 1024 to get 0~9. You should just divide ADCW by 4 to get 0~255 (the largest number that can be put in UDR0 or OCR0A), then your steps will be much smaller. \$\endgroup\$ – Bruce Abbott Sep 21 '16 at 18:11
  • \$\begingroup\$ Your are still operating under the mistaken belief that you can shove a wider value through an 8-bit channel in a single operation. You cannot. What is actually happening is that you are sending 8-bit values, and incorrectly using OD to misinterpret four of them as a bogus 32 bit value. Even if you \$\endgroup\$ – Chris Stratton Jun 25 '17 at 12:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.