AVR GCC code review - Page 1
Forum section: electronics / beginners

author: bertjeivens
author_rank: Newbie
timestamp: August 13, 2015, 07:42:53 pm
content: Hello all,I'm not sure if this should go here or the "microcontrollers & fpga" subforum because i'm a beginner at this i thought it would better fit in here.Anyway here is my question could you please review my code, its working nicely. I'm just want to know if i'm on the right track of proper coding.this program reads the temperature of a one wire T sensor. I know there is a one wire library but i wanted to do this as a learning experience by just using the gcc reference documents and the datasheet for the sensor.next step is CRC checking and sending T to the terminal.then multiple temperature sensors.maybe a version with timers and not delays.Thanks for any advice/constructive criticismCode:#define F_CPU 20000000L#include <avr/io.h>#include <util/delay.h>void OWreset();void OWread();void OWwrite(unsigned char sample);void OWCRC(unsigned char input_data);unsigned char input_data;int main(void){while(1){OWreset();OWwrite(0xCC); //no romOWwrite(0x44); //start conversion_delay_ms(100); //wait fot temperature conversionOWreset();OWwrite(0xCC); //no romOWwrite(0xBE); //request scratchpadOWread();_delay_ms(500);}}//function to read data from One wire devicevoid OWread(){for (int x=0; x<9; x++) //cycle through bytes{for (int y=0; y<8;y++) //cycle through bits{PORTD &= ~0x04;//pull output lowDDRD |= 0x04;//set channel as output_delay_us(4);DDRD &= ~0x04;//set channel as inputPORTD |= 0x04;//internal pullup without it takes 3us to charge from 0v to 5v with it 60ns (external pullup 10k)_delay_us(8);input_data |= (PIND & 0x04) >> 2 << y; // shift right 2 to get the bit in bit0 of byte, then shift to the correct position in the input_data byte, this should work assuming all bytes in array are 0 when array is created_delay_us(48);}}}//function to write to one wire clientsvoid OWwrite(unsigned char sample){PORTD |= 0x04;DDRD |= 0x04;for (unsigned char z = 0; z < 8; z++){unsigned char test=0;test |= (1 << z); //create mask to get correct bitif (test &= sample) //test mask to sample without modifying sample{//write 1PORTD &= ~0x04;_delay_us(8);PORTD |= 0x04;_delay_us(52);}else{//write 0PORTD &= ~0x04;_delay_us(60);PORTD |= 0x04;_delay_us(4);}}}//function to reset One Wire data line and clientsvoid OWreset(){DDRD |= 0x04; //set bit 2 as outputPORTD &= ~0x04; //pull output low_delay_us(500);DDRD &= ~0x04; //set all bits as input, external pullup resistor will pull DI up_delay_us(500);}

author: bingo600
author_rank: Super Contributor
timestamp: August 13, 2015, 08:03:08 pm
content: I don't want to do a review.But have a look here , for an excellent ds18x20 libIt's in normal avr-gcc , and contains some neat featureshttp://siwawi.bauing.uni-kl.de/avr_projects/tempsensor//Bingo

author: Ribster
author_rank: Frequent Contributor
timestamp: August 14, 2015, 12:11:00 am
content: Just passing by.What caught my eye: #define F_CPU 20000000LYou can change this to UL (Unsigned Long)

author: nuno
author_rank: Frequent Contributor
timestamp: August 14, 2015, 01:56:31 am
content: Just a few misc comments, some are personal preferences:- One day you'll want to declare "static" all variables and functions that are used only on the file where they're implemented; this generates smaller code.- Don't leave empty lines "at random" and don't mix tabs and spaces, it looks sloppy.- Instead of things like 1<<3, WinAVR has a macro for that: _BV(3) (from, I think, Bit Value)- Leave spaces in your expressions, (y = 0; y < 8; y++) is easier to read than (y=0; y<8;y++); also leave same line comments more than 1 space apart from the code, to make the separation very easy to and clear to read.- In expressions such as (PIND & 0x04) >> 2 << y, I prefer to use more parenthesis to make things explicit, like ((PIND & 0x04) >> 2) << y- This is a dangerous technique: if (test &= sample) . Variable test is changed in the process.- There's a set of types guaranteed to be a certain size and sign, for example, uint8_t (8 bit unsigned integer), int8_t, uint16_t, int16_t, uint32_t, int32_t, maybe others.- Missing a comment explaining what's the "high level" purpose of the application. (something like // Read the temperature sensor and display it in the LCD, ad eternum).

author: FreddyVictor
author_rank: Regular Contributor
timestamp: August 14, 2015, 06:37:58 am
content: the slightly lazy use of int rather than using specific length types and the >> 2 << y as mentioned by nunoalso some compilers don't like:void OWreset();they want:void OWreset(void);

author: bertjeivens
author_rank: Newbie
timestamp: August 14, 2015, 07:58:01 am
content: thanks all for the tips and resources. Its true that i did not really looked at the aesthetics of the code.I'm going to implement the things you guys pointed out.thanks again for taking the time to have a look!

author: Ribster
author_rank: Frequent Contributor
timestamp: August 14, 2015, 09:17:44 am
content: There is a ton out there how to make good C code.If you are a practical person, make mistakes, a lot of mistakes.You can also read up on the subject. You can find a lot of examples online / guides.Decide what works for you, and stay curious.For instance:http://www.embedded.com/design/programming-languages-and-tools/4215552/Seventeen-steps-to-safer-C-codeMake google your friend, use keywords like AVR, C, embedded to get exactly what you need.Places likehttp://stackoverflow.com/have a lot of general C programming help.You can search for the error code that the compiler gives you in those places when you are in trouble.While C & C++ are very different, the basics are the same. A good reference:http://www.cplusplus.com/doc/tutorial/Happy embedded coding !

author: Psi
author_rank: Super Contributor
timestamp: August 14, 2015, 09:36:09 am
content: omg, it has hard delays, my eyes, they hurt.

author: mikerj
author_rank: Super Contributor
timestamp: August 14, 2015, 02:09:40 pm
content: It looks mostly functional, but there are some things that could be done more elegantly or efficiently.The main bug that stands out is that you are setting bits in input_data[] when you receive a logic 1 value from the sensor, but you are never clearing the bits. This means you will only get the correct value the first time the OWRead() function works. To fix this, clear the input_data[] value before you run the inner loop.Next try not to use "magic numbers" in your code. Defining commonly used values at the top of the module (or within a header file if they are needed in other modules) allows the values to easily changed by editing a single line in the code, instead of having to hunt through and find where the value has been used.The AVR has an 8 bit processor, it can deal with only 8 bits at a time. Processing 16 or 32 bit values requires many more instructions since the operations and moves have to be performed 8 bit as a time. For this reason it's wasteful (in both memory space and execution speed) to define variables as 'int' (16 bit word on the AVR) unless you actually need the numerical range that an int provides, which you don't in most cases.The AVR doesn't have a barrel shifter, it can only shift one 8 bit value by one bit position at a time. To shift a value an unknown number of positions requires that the compiler generate a loop to perform a number of single bit shifts. If you are shifting a 16 or 32 bit value, this can add up to a surprising number of cycles being used up. Often there are better ways to approach this, e.g.Code:/* Pin, port and DDR defines for one wire interface */#define OW_PORTPORTD#define OW_DDRDDRD#define OW_PINPIND#define OW_BIT_BV(PD4)/* Timing values for one wire read function */#define OW_READ_LO_TIME_US 4#define OW_READ_HI_TIME_US 8#define OW_READ_BIT_TIME_US 48void OWread(){for (int x=0; x<9; x++) //cycle through bytes{// Clear data register prior to receiving data from the sensorinput_data = 0;for (uint8_t bitmask=1; bitmask>0; bitmask>>=1) //cycle through bits 0 to 7{OW_PORT &= ~OW_BIT; //pull output lowOW_DDR |= OW_BIT; //set channel as output_delay_us(OW_READ_LO_TIME_US);OW_DDR &= ~OW_BIT; //set channel as inputOW_PORT |= OW_BIT; //internal pullup without it takes 3us to charge from 0v to 5v with it 60ns (external pullup 10k)_delay_us(OW_READ_HI_TIME_US);if (OW_PIN & OW_BIT){input_data |= bitmask;}_delay_us(OW_READ_BIT_TIME_US);}}}The other thing is that you have hard coded the OWRead() function to always read 9 bytes into input_data. This function would be a more flexible if you passed the function the number of bytes to read, and a pointer to the array that will hold the data. There are lots of one wire devices and they don't all have the same register layout as the DS18B20.If your OWCRC() function is going to validate the a CRC, then you probably want to return a value to indicate whether the check has passed or failed.

author: bertjeivens
author_rank: Newbie
timestamp: August 15, 2015, 08:22:09 am
content: thanks again for taking the time.seeing psi's comment i'll push the use of timers to my top priorityow yes the code i tested was initiating input_data[] inside the OWread() function and that was working fine because it iniciated each time you call OWread(). before posting the code here i cleaned up some parts which weren't relevant and i moved the data_input to outside of the function to be used easily to start the CRC checking part of the code. Now it seems obvious that its not going to work without modification of the code.

author: Spork Schivago
author_rank: Frequent Contributor
timestamp: August 15, 2015, 04:59:54 pm
content: Hey, a quick tip for you. I don't know what your programming background is, but it seems you're fairly new to C. I don't have any experience compiling programs for microcontrollers or anything, but I do have some experience compiling programs for PCs. If you ever get a bunch of errors in your compiler, always look at the first error and fix that first before worrying about any of the others. A lot of times, one error can cause a bunch of others and as soon as you fix that one error, the others disappear.For example, simply forgetting a ; at the end of a function call might make the compiler generate 10 errors or so, when in reality, there's only that one.Remembering this might help you in the future. Good luck.

author: bertjeivens
author_rank: Newbie
timestamp: August 21, 2015, 02:14:12 pm
content: Quote from: Psi on August 14, 2015, 09:36:09 amI have been working on cleaning up my code. I was thinking about the best way to use real timers for this application but when I checked the lib which was referenced in this thread I noticed it uses hard coded delays as well....Oh well figuring it out myself is the best way to learn anyway and I think I have a good way to do it but i'll have to rewrite most of my code!

author: nuno
author_rank: Frequent Contributor
timestamp: August 21, 2015, 02:33:54 pm
content: If by "hard delays" you mean "busy loop delays", they're not necessarily "evil". They're much simpler to use and you don't always need the least power consumption. Furthermore, when the time is very small compared to the CPU's "processing power", it may not be worth it to go another route, or it may simply be the only way to achieve the small delay you're after.