Basic Tools:
What is the source for tools? Usually it is something that is created to address a need. It is really hard to pull a nail out of a board by hand, but using basics of mechanics, things like the claw on a hammer use a lever to make the task possible, the list is endless. For software and embedded systems, the ideas are the same, using programming techniques. If a task seems too big, make tools to manage the problem so the task can be solved. Always remember, look first. There is a good chance you are not the first to encounter this problem, and someone before you has already made the tool you need. The other thing to remember is, tools are never perfect, and have either been created for a specific instance (not yours), or could be so generic as to make them nearly useless. They still have value for “a way” to solve the problem and “just need a little modification”. Need being the driver for tools, for me, the greatest need is communication, and providing control and output. So when I am faced with a new system, the first thing I do is make code to manage the serial port.
UART
Because a serial port is very slow, relative to the speed of the processor, and wait loops are wasted time, writing interrupt handlers for serial ports is a very good skill to master. The serial input interrupt is pretty simple to implement, because it makes sense and almost writes itself. The byte comes in, and interrupts the process, the interrupt routine reads the status and the input byte, manages any status issues, and saves the input byte in a queue for the mainline. From the other side, the mainline checks the queue to see if a byte has been received, if not it goes on about its task, if there is a byte it retrieves the byte from the queue and processes it according to its manager.
Output, operates in the same way, but for some reason, confuses people. This results in the output routine performing a “wait” until the output register is clear before outputting the character, and like I said before, wait loops are wasted time. The output register empty condition also provides an interrupt to the processor, and an interrupt process can be created for output as well. Turns out the process is quite similar and uses a queue as well. Output normally starts with the interrupt turned off. The mainline checks the state of the interrupt, and if it is OFF, it means the output register is clear, so the byte is placed directly into the output register, and the interrupt is turned ON. If the interrupt state is ON the mainline puts the byte into the output queue instead and continues on. From the interrupt side, the interrupt occurs when the output register is clear, so it checks the output queue to see if there is another byte available, if yes, it takes the byte from the queue and puts it in the output register. If the queue is empty, the interrupt turns the interrupt state OFF.
Using the described techniques, it is now possible to build a queued input output system so the mainline does not have to implement any wait loops, unless the queue is full. If the queue is getting full all the time, then the system can be adjusted to use larger queues to prevent delay, or loss of data. I believe the tool is described well enough, it is time to build the code. If this is done correctly, the input and output sections should look very similar. Both sections will have two stages, and the stages will booth look very similar. Stage one puts data into the queue, stage two takes data out of the queue. For the input side, stage one is the interrupt and stage two is the interface. For the output, stage two is the interrupt, and stage one is the interface.
Here is the code…
Remembering from the last installment, types.h contained includes for the processor defines so the registers and bits are defined there and just used here.
/*********************************************************************************************** uart.c History: [JAC] Original Creation when the earth was cooling Copyright(c) 2016 Chaney Firmware ***********************************************************************************************/ #include "types.h" #define UBAUD_57_6 34 #define UBAUD_38_4 51 #define UBAUD_19_2 103 #define UBAUD__9_6 207 #define UCSRA_INIT (1<<U2X0) // Timebase for UART 2x clock #define UCSRB_INIT (1<<TXEN0)|(1<<RXEN0)|(1<<RXCIE0) // Enable transmit and receive lines and receive interrupt #define UCSRC_INIT (3<<UCSZ00)|(0<<UPM00)|(0<<UMSEL00) // n-8-1 #define COMMSPEED UBAUD_57_6 // 57600 #define BUFFSIZE 32 static UByte rxBuf[BUFFSIZE]; static volatile UByte rxHed, rxTal, rxSiz; static UByte txBuf[BUFFSIZE]; static volatile UByte txHed, txTal, txSiz; static UByte iOn; bool isCommNotEmpty(void) { return (rxSiz != 0); } void initUart(void) { UCSR0A = UCSRA_INIT; UCSR0B = UCSRB_INIT; UCSR0C = UCSRC_INIT; UBRR0 = COMMSPEED; rxHed = 0; rxTal = 0; rxSiz = 0; txHed = 0; txTal = 0; txSiz = 0; iOn = 0; } //=========================== // *** WARNING *** // Do not use these functions // within an interrupt void putComm(UByte c) { if (iOn == 0) { iOn = 1; UDR0 = c; UCSR0B |= (1<<TXCIE0); } else { while (txSiz >= BUFFSIZE); cli(); txBuf[txTal] = c; txTal = (txTal + 1) < BUFFSIZE ? txTal + 1 : 0; txSiz++; sei(); } } UByte getComm(void) { UByte c = 0; if (rxSiz > 0) { cli(); c = rxBuf[rxHed]; rxHed = (rxHed + 1) < BUFFSIZE ? rxHed + 1 : 0; --rxSiz; sei(); } return c; } void putStr(char* s) { while (*s != 0) { putComm((UByte)(*s++)); } } //=========================== ISR(USART_TX_vect) { /* USART, Tx Complete */ UByte c; if (txSiz > 0) { c = txBuf[txHed]; txHed = (txHed + 1) < BUFFSIZE ? txHed + 1 : 0; --txSiz; UDR0 = c; } else { iOn = 0; UCSR0B &= ~(1<<TXCIE0); } } ISR(USART_RX_vect) { /* USART, Rx Complete */ UByte c = UDR0; if (rxSiz < BUFFSIZE) { rxBuf[rxTal] = c; rxTal = (rxTal + 1) < BUFFSIZE ? rxTal + 1 : 0; rxSiz++; } else { /* Dropped bytes go here */ } } /* end of file */
The code does not have any error checking for frame or overrun, and doesn’t have adjustable baud rate. Also, note the warning. The output does impose a wait loop if the buffer is full, so it should not be within an interrupt, and the input queue dumps bytes in the bit bucket if it overflows. It might also be noted, that there aren't many comments, mostly because this code is redone over and over and over based on the hardware, also repeated use has squished the code to single lines instead of spread out for easy debugging. You are welcome to make adjustments as you like.
This installment gets a “twofer”
EEPROM is mostly a hardware feature, particularly in this device, the great thing is the hardware specification document provides source code, both in assembly and C. So the C version is presented here with minor adjustment to match my style. Since the code is not mine, there isn't a copyright.
/************************************************************************************************ eeprom.c History: [JAC] Original Creation when the earth was cooling ************************************************************************************************/ #include "types.h" #define EEPROM_LIMIT 1024 UByte getEEPROM(UWord ofs) { UByte rVal = 255; if (ofs < EEPROM_LIMIT) { while(EECR & (1<<EEPE)); /* Wait for completion of previous write */ EEAR = ofs; /* Set up address register */ EECR |= (1<<EERE); /* Start eeprom read by writing EERE */ rVal = EEDR; /* Return data from Data Register */ } return rVal; } void setEEPROM(UWord ofs, UByte d) { if (ofs < EEPROM_LIMIT) { while(EECR & (1<<EEPE)); /* Wait for completion of previous write */ EEAR = ofs; EEDR = d; /* Set up address and Data Registers */ EECR |= (1<<EEMPE); /* Write logical one to EEMPE */ EECR |= (1<<EEPE); /* Start eeprom write by setting EEPE */ } } /* end of file */
Other types of EEPROM use serial links like I2C or SPI, and require handshaking protocols to do data transfer, currently there are not any of these types of devices in the system so methodology for usage will be left for later.
NEXT TIME
The 328P processor has two more devices, the timers, and analog to digital converters, which will be covered next installment. Then I will put things together to make a generic controller device that is small, lightweight (from a code point of view), and easily expandable.