User Tools

Site Tools


coding_standards

Coding Standards and ECEn 330

Teams that work on programming projects must rely on coding standards to be successful. Often, a company will impose a specific coding standard on all of its programmers. Some companies even go so far as to restrict what data structures you may use (especially with C and C++). Using a coding standard for ECEn 330 labs provides the following benefits:

  1. Your code will be more usable (by you and others later on). Much of the code that you will write for this class will likely be useful in ECEn 390. By following the coding standard outlined here you will more easily be able to reuse your code in various projects.
  2. Your code will be tested. The coding standard requires that you write test code that is included as an essential part of the code that you will write.
  3. Your code will be readable by you and others.

There are two general sections discussed below. The first is a discussion of the general coding standard. The second discusses some coding standards specifics as they pertain to state machines.


The General Coding Standard

General Rules

  • You will write C-code. No class definitions are allowed. If you need a data-structure, it must be something supported by 'C', such as a struct.
  • When compiled, your code will not cause the compiler to issue any warnings or errors. Some of the provided files in “supportFiles” generate warnings, those can be ignored. The files in “supportFiles” were obtained from a variety of places and I didn't modify them to fix the various warnings.
  • Use #define for all scalar constants. You may use the constants 0 and 1 in if-expressions, for-loop expressions and while-loop expressions. You may also use 0 or 1 in arithmetic expressions.
  • Use static const arrays if you need to hard-code test data (or otherwise initialize an array), e.g., static const uint16_t testData[] = {1, 2, 3, 4};, or if you need to declare some series of constant values as an array.
  • Your code, when submitted, must be in a finished form. You are allowed a total of 5 lines of “commented-out” code per file.
  • You must have at least an 50% comment-to-code ratio. If you have 10 lines of code, you must have at least 5 lines of comments. A comment “line” can either be on its own line or start at the end of the source-code line. Either counts as a “line” of comment code.
  • For your own code, avoid the base types such as “int”. Use the types that are contained in <stdint.h>. They are far more descriptive. int main() is OK. See stdint.h
  • You may use the char base type when working with C string functions such as strcpy(), strcat(), etc.
  • Make sure that your code completely describes your intent. If your code is unclear or does not completely describe what is going on, comment accordingly.
  • Avoid using repetitive sections of copied and pasted code in your programs.

Specifics

  • The ”.h” file must contain declarations for all constant values, function prototypes, etc., that you want to “advertise” for use.
  • The ”.h” file and the ”.c” file must have the same base name.
  • ”.h” files do not contain executable code, only function declarations, #define, etc.
  • ”.h” files must not contain any definitions of any variables whatsoever.
  • ”.c” files contain code.
  • All function names and constant values contained in .h files must be prefixed by the base-name of the file + underscore, e.g., buttons_read(), #define BUTTONS_BTN0_MASK 0x1. Function names and constant values that are used only within .c files do not need to be prefixed by the base-name of the file.
  • All single constant values must be defined using #define and anything defined by a #define must use all uppercase letters.
  • Use the const key word only if you are defining an array of constant values, e.g., const int foo[2] = {1, 2};
  • The constants 0 and 1 can be used without a #define within if-expressions, and while- and for-loops, e.g., for (int16_t i=0;), if (i==0), and while (1). You can use '1' and '0' to initialize variables. You can also use 0 and 1 in arithmetic expressions. No other exceptions are allowed. See the code below for examples of how to use '1' and '0'.
  • #define names should be meaningful, e.g., #define TEN 10 may sound clever but you will lose points if you use #define this way.
  • Use proper and consistent indention techniques.
  • Use meaningful comments and add the comments as you develop your code.
  • Busy loops (delays based on for-loops) are OK for test code but are not allowed anywhere else.
  • You will provide an “init” function (base-name_init) that must be called before any of your provided functions can be called. I suggest that you follow the example shown in the code below. The “init” function is required in any case (even if it does nothing).
  • You will provide a “runTest” function (base-name_runTest). This function serves two purposes. First, it tests the code to make sure that it works. Second, you will be able to look at the code later to see how it is used, in other words, the “runTest” code can provide an example of how to use the code.
  • Only code written by you is graded against the coding standard. If you include code from one of the class web-pages and that code violates the coding standard, you will not lose points.

Grading Guidelines (examples of things that cause you to lose points)

  • Bogus #define names, e.g., #define TEN 10
  • Directly using numbers such as 5, 3.14, etc., instead of using a #define (so-called magic numbers).
  • Not using the file-name as the prefix in all function names and #define that show up in the .h file.
  • Not using all upper-case for #define.
  • Each infraction will lose points, e.g., each use of a constant value, e.g., 2, will lose points.
  • Use of types such as int, etc., anywhere in your code. Use integer types from stdint.h instead.
  • Any other infractions of the rules spelled out in General Rules and Specifics above.

Declarations versus Definitions

.h files may contain the following (these are forms of declarations):

  • function prototypes,
  • typedef declaration,
  • #define

.h files may not contain (these are definitions):

  • function definitions,
  • variable definitions.

Examples of function prototypes can be found in the examples of circularBuffer.h shown below. A function prototype “advertises” the name of the function, its return type, and the types of the arguments that it accepts. It does not contain any of the code for the function. A typedef declaration is also shown below. It “declares” types but does not result in any variable definitions. Function definitions are found in circularBuffer.c where the “code” for the entire function can be found for each of the function prototypes in the circularBuffer.h file.

A good but rather detailed discussion of this topic can be found at: declaration versus definition.

Example

“circularBuffer.h” and “circularBuffer.c” are provided below as examples of files that follow the coding standard for this class. For this example, “circularBuffer” is the base-name. Thus the ”.c” file is named “circularBuffer.c” and the ”.h” file is named “circularBuffer.h”. These are a couple of files that I never finished so they have not been debugged and some functions have not been completely implemented. They are typical of my commenting style. To give you an idea of how to count lines of comments and lines of code, circularBuffer.h contains 13 lines of code (I don't count the ifndef for the .h file) and 13 lines of comments. circularBuffer.c contains about 25 lines of code and 30 lines of comments. I don't count braces on empty lines as code lines.

You will see legal examples of '1' and '0' used as constants in the code below.

Note that this code was never tested so only use it as an example of commented code. Accordingly, it does not contain init() or runTest() functions.

#ifndef CIRCULARBUFFER_H_
#define CIRCULARBUFFER_H_
 
#include <stdint.h>
#include <stdbool.h>
 
// Keep things integer powers of 2 so that address calculations are fast.
#define CIRCULAR_BUFFER_INDEX_MASK 0xFF
 
// The data-structure that will hold the circular-buffer.
typedef struct {
	uint32_t writeIndex;	// You write new data here.
	uint32_t readIndex;	// You read data from here.
	bool wrapAroundFlag;	// True if you have rolled over.
	bool firstPassFlag;	// True if this is your first write pass through the array.
	uint32_t *data;		// Data are stored here.
} circularBuffer_t;
 
// Init's the buffer to the empty state, allocating fresh memory.
void circularBuffer_init(circularBuffer_t* cb);
 
// Just resets the index pointers to start at the zero position, and resets the overflow flag.
void circularBuffer_reset(circularBuffer_t* cb);
 
// Adds a new data item to the buffer.
void circularBuffer_addData(circularBuffer_t* cb, uint32_t datum);
 
// Reads a data item at the given index. The index is relative, an index of 0 just means the first (oldest)
// element of the circular buffer.
uint32_t circularBuffer_readDataAt(circularBuffer_t* cb, uint32_t index);
 
// Returns the number of elements contained in the buffer.
uint32_t circularBuffer_size(circularBuffer_t* cb);
 
#endif /* CIRCULARBUFFER_H_ */
#include "circularBuffer.h"
#include <stdlib.h>         // I get malloc() from here.
#include <stdio.h>          // printf().
 
// Init's the buffer to the empty state, allocating fresh memory.
void circularBuffer_init(circularBuffer_t* cb) {
	circularBuffer_reset(cb);   // This inits all of the slots.
	// Allocate the memory to hold the data.
	cb->data = (uint32_t *) malloc((CIRCULAR_BUFFER_INDEX_MASK + 1) * sizeof(uint32_t));
}
 
// Just resets the index pointers to start at the zero position, and resets the overflow flag.
// The buffer becomes empty after reset().
// Buffer must be reset prior to writing anytime it has been read.
void circularBuffer_reset(circularBuffer_t* cb) {
	cb->writeIndex = cb->readIndex = 0; // Read and write indicies both start at 0.
	cb->wrapAroundFlag = false;         // Start out not wrapped around.
	cb->firstPassFlag = true;           // On your first pass through the buffer.
}
 
// Adds a new data item to the buffer. Addressing scheme
// assumes that the initial data was added starting at index = 0.
// writeIndex always points to the location to be written and then is incremented.
void circularBuffer_addData(circularBuffer_t* cb, uint32_t datum) {
	cb->data[cb->writeIndex] = datum;  // Write the data to the buffer at current write-index.
	// Wrap around has occurred if you are back at 0 and not on your first-pass.
	if (cb->writeIndex == 0 && !cb->firstPassFlag)
		cb->wrapAroundFlag = true;
	// Increment the write-index ala modulo.
	cb->writeIndex = (cb->writeIndex + 1) & CIRCULAR_BUFFER_INDEX_MASK;
        // Check for wrap-around and compute addresses and update the firstPassFlag.
	if (cb->writeIndex == 0) {         // You know that you are starting the second pass if you are back at 0.
		cb->firstPassFlag = false; // but don't know if you will wrap-around until next write.
	}
}
 
// NYI. Have not completed this function. Compiles returns bogus values when data are wrapped around.
// Returns the number of items in the buffer.
// User must call this to determine how many elements are in the buffer.
uint32_t circularBuffer_size(circularBuffer_t* cb) {
	if (cb->wrapAroundFlag)  //  OK, you are wrapped around, compute data-element size accordingly.
		return CIRCULAR_BUFFER_INDEX_MASK + 1;  // NYI. Haven't implemented this yet.
	else  // Not wrapped around, data are from the read-index to the write-index.                                           
		return (cb->writeIndex - cb->readIndex + 1);
}
 
// Reads a data item at a given index. The index is relative, an index of 0 just means the first
// element (oldest) of the circular buffer. Index is always masked so it will always be in range.
// Does not check the index to see if it is accessing stored data (for speed). User is responsible
// to access data in range.
uint32_t circularBuffer_readDataAt(circularBuffer_t* cb, uint32_t index) {
	if (cb->wrapAroundFlag) {   // Data are indexed differently if wrapped around.
//		printf("address->%ld\r\n", (cb->writeIndex + 1 + index) & CIRCULAR_BUFFER_INDEX_MASK);
                // Return the data using modulo addressing.
		return cb->data[(cb->writeIndex + 1 + index) & CIRCULAR_BUFFER_INDEX_MASK]; 
	} else {    // Not wrapped around.
                // Don't think you need to wrap here but it doesn't hurt anything.
		return cb->data[index & CIRCULAR_BUFFER_INDEX_MASK];    
	}
}

coding_standards.txt · Last modified: 2018/09/25 11:58 by hutch