|
Without doubt, the presence of the FIFO buffer helps (as we have seen before). The pieces of software that are needed to handle the FIFO are:
- the producer: Rx ISR (the receive interrupt service routine)
- the consumer: GetChar() function itself.
Assuming the USART is identified by the name USART1 and the implementation does not involve an RTOS yet, their code will look as follows:
Producer code (Rx ISR)
void Com_ISR (void) // USART Rx handler (ISR)
{/////////////////////////////////////////////////////////////////////////////////////////////
COM_RX_BUFFER *pcom_buf; // pointer to COM Rx buffer
U8 b_char; // byte holder
. . . . . . // identify the interrupt
pcom_buf = Com_GetBufPtr (USART1); // obtain the pointer to the circular buffer
. . . . . . // do other things as required
b_char = USART_ReceiveData (USART1); // get one byte from USART1
Com_WriteRxBuf (pcom_buf, b_char); // write the byte into the circular buffer
. . . . . . // do other things as required
} ////////////////////////////////////////////////////////////////////////////////////////////
Consumer code (GetChar):
S32 ComGetChar (U32 timeout) // the "GetChar()" function
{/////////////////////////////////////////////////////////////////////////////////////////////
COM_RX_BUFFER *pcom_buf; // pointer to COM buffer
U8 b_val; // byte holder
pcom_buf = Com_GetBufPtr (USART1); // obtain the pointer to the circular buffer
. . . . . . // do other things as required
b_val = Com_ReadRxByte (pcom_buf); // extract the character from the circular buffer
return (S32)b_val & 0xFF; // return the character as a 32-bit signed int
} ////////////////////////////////////////////////////////////////////////////////////////////
Simple, isn’t it? Well, not that simple. A quick look will find two functions that are not implemented yet (Com_WriteRxBuf and Com_ReadRxByte) and one parameter (timeout) that is not used in ComGetChar(). I will ignore the parameter for now and concentrate on the two functions just mentioned. Their role is to be the interface between the code above and the FIFO buffer we intend to use.
At a second glance the embedded developer will realize that the producer and the consumer run in two different contexts independent to each other: the ISR and the main thread where the function ComGetChar() is invoked sharing the FIFO buffer (common resource). As consequence, the access to the buffer needs to disciplined ensuring that only one process — producer or consumer — can access the buffer at one given time, i.e. mutual exclusion policy.
Let’s examine the FIFO buffer implementation. The following picture shows one of the most used methods of designing a FIFO in software. With a buffer of a given size and two pointers (named here Head and Tail) the FIFO can be described as a data structure and, eventually, as a data type — COM_RX_BUFFER:
typedef struct // Rx Circular Bufer structure
{
U8 volatile *rx_buf; // pointer to (volatile) Rx buffer
U32 rx_buf_size; // Rx buffer size
U32 volatile rx_head; // Rx head index
U32 volatile rx_tail; // Rx tail index
U32 volatile rx_count; // Rx byte counter
} COM_RX_BUFFER;
As C programmers know, the relationship between pointers and arrays allow us to interchange subscripting with C pointers — I will prefer the first for clarity. I added a counter to keep track of how many bytes we have in the FIFO without the need of subtracting the head and tail pointers. The idea is simple: whenever an item is inserted I will use the tail pointer (one end of the buffer) and whenever an item is extracted I will use the head pointer (the other end of the buffer).
U8 Com_ReadRxByte (COM_RX_BUFFER *pcom_buf)
{/////////////////////////////////////////////////////////////////////////////////////////////
U8 b_val; // byte holder
b_val = pcom_buf->rx_buf[pcom_buf->rx_head]; // extract one byte and
pcom_buf->rx_head = (pcom_buf->rx_head + 1) % // advance the head index and wrap
pcom_buf->rx_buf_size; // around if past end of buffer
pcom_buf->rx_count--; // decrement the byte counter
return b_val; // return the extracted byte
} ////////////////////////////////////////////////////////////////////////////////////////////
void Com_WriteRxBuf (COM_RX_BUFFER *pcom_buf, U8 b_char)
{/////////////////////////////////////////////////////////////////////////////////////////////
pcom_buf->rx_buf [pcom_buf->rx_tail] = b_char; // insert one byte
pcom_buf->rx_tail = (pcom_buf->rx_tail + 1) % // advance the tail index and wrap
pcom_buf->rx_buf_size; // around if past end of buffer
pcom_buf->rx_count++; // increment the byte counter
} ////////////////////////////////////////////////////////////////////////////////////////////
Look at the code: now you’ve got the idea. But don’t rush to use these functions yet. They may hide some flaws or omissions at least. Can you identify them? Please send me your comments. In the next posting I will examine in detail the problem of mutual exclusion and a way to solve it.
int i;main(){for(;i["]<i;++i){--i;}"];read(’-’-’-’,i+++"hell\
o, world!\n",’/’/’/’));}read(j,i,p){write(j/p+p,i---j,i/i);}
— Dishonorable mention, Obfuscated C Code Contest, 1984.
Author requested anonymity.
As the following posts will contain snippets of code, I will introduce few elements of style that may help improve the readability of the text. When the time will come, more about coding will be discussed.
Here is an example of a code fragment:
#define USART1 ((USART_TypeDef *) USART1_BASE)
#define VER_STR "Version 2.1.3"
void Com_ISR (void) // USART Rx handler (ISR)
{/////////////////////////////////////////////////////////////////////////////////////////////
COM_BUFFER *pcom_buf; // pointer to COM buffer
U8 b_char; // byte holder
. . . . . . // identify the interrupt
pcom_buf = Com_GetBufPtr (USART1); // obtain the pointer to the circular buffer
. . . . . . // do other things as required
b_char = USART_ReceiveData (USART1); // get one byte from USART1
Com_WriteRxBuf (pcom_buf, b_char); // write the byte into the circular buffer
. . . . . . // do other things as required
} ////////////////////////////////////////////////////////////////////////////////////////////
Notes regarding style:
- The syntax coloring scheme shows a bit more than what you see in your everyday editor — here is the key:
- C reserved keywords
- Function names
- Macros
- Enumerators
- User data types
- Preprocessor directives
- Characters and strings
- Numbers
- Comments
- Operators (+, -, *, etc)
- The code uses portable data types (see the CoOS Real Time Kernel – Part-2).
- Functions that are inside the same module use a common prefix — Com in our case.
- Local functions (visible only in the module) insert a _ (underscore) between the prefix and the rest of the name; to make them local, use the C keyword static (in examples I will omit the static attribute for reasons that will be explained in another post and for simplicity).
- Externalized (or global) functions will concatenate the prefix with the rest of the name.
- Variables that are internal to functions (and not visible anywhere else) will use only lower case letters and underscores; the same convention applies to members of data structures and unions.
- Symbolic constants and macros will use only upper case letters; the same thing applies to user data types.
- Everything else will use capitalized notation (e.g. PrefixName).
- Snippets of code that come from other people’s work will preserve their original style.
- Library functions, constants, macros and type definitions will remain in their original notation.
Try to remember: there are many ways to code programs in C (or any other language). The style adopted is just as good as any other as long as the following goals are attained:
- Portability
- Consistency
- Neatness
- Easy maintenance
- Easy understanding
- Simplicity
This might be enough to get as going…
Aside form programming skills the embedded developer has to think of solving problems in a different mindset than the average PC programmer. A good example is the interrupt handling that the embedded developer has to deal with directly. I’m not going to explain what an interrupt is or why interrupts are important in real-time programming: there are plenty of books or tutorials explaining these concepts and their usefulness. I will rather show a typical example where interrupts are used extensively to achieve a responsive and trouble free communication over a serial interface — USART in particular — in two distinct situations: with and without an RTOS. As a side note, most of the examples or code snippets were tested on a STM32F103 evaluation board, but I’ll try to be as generic as possible.
Let’s assume that we need to implement the function GetChar() that receives one byte from the serial interface USART and return it when GetChar() is invoked. Many microcontrollers have a simple serial interface equipped on the receive side with one de-serialization register and one data holding register: this means that while one character is in the de-serialization process, another one may be already available in the data holding register from a previously received character. When the character is de-serialized and sent to the data holding register the receive status flip-flop (Rx Stat FF) is set. When the CPU reads the byte available in the data holding register the Rx Stat flip-flop is reset. The receive status flip-flop is usually available as part of a status register (meaning that the data is ready) and can also generate an Rx interrupt. If the CPU does not read the character in the data holding register in time and another one is de-serialized, the incoming character will overwrite the previous one and that character is lost (the so-called overrun error).
As we can see, the CPU has two options: either polling (i.e. wait in a loop for the data to become available by reading the Rx Stat FF) or by using interrupts (when Rx Stat FF is set the USART generates an interrupt getting CPU’s attention and forcing it to read the character). While polling looks simple and straightforward, it is inefficient and difficult to handle in complex systems where the CPU must execute many other tasks. As consequence, the second solution will be discussed here.
From the above, we observe that one obvious fact is the requirement to handle all these interrupts in a way that will ease the CPU burden to ask all the time for the availability of one single byte. The solution is to store the incoming characters into a buffer and let the CPU get them from this reservoir until it becomes empty; only in such a situation, if the CPU cannot continue without data, it will wait until more data will be available. With or without RTOS and multitasking, this “reservoir” placed between the receive interrupt handler and the GetChar() needs to be organized as a first-in-first-out buffer (FIFO). Between the USART receiver and the CPU via GetChar() it was established a producer-consumer relationship that I intend to study in more detail.
The role of the FIFO buffer is to temporarily store the characters until the CPU becomes available to grab them. While the characters arrive slowly the CPU can drain the FIFO buffer quickly and become available for other tasks until the buffer gets full (or almost full) again. As a side note, most modern USARTs have their own internal FIFO implemented in hardware; this way they will generate interrupts less frequently helping the system to increase in efficiency. A hardware FIFO is helpful but it small compared with the FIFO buffer implemented in the software as suggested above. That is why in the next post I will examine the implementation of such a FIFO buffer together with its real-time programming aspects.
As I noted in my previous posting, CoOS was initially intended for ARM Cortex M3 and M0 architecture. With a bit of work CoOS can be ported on the popular ARM7 as well. The authors assume that the development is done using the following tool chains: Keil, IAR and GCC. The CooCox CoBuilder is an internet-based component-oriented embedded development platform that is based on GCC. It is somewhat similar with LPCXpresso which is an Eclipse-based IDE, a GNU C compiler, linker, libraries, and a GDB debugger all hosted online with the exception of the front-end client that requires local installation. The same thing applies to CoBuilder.
Personally, I appreciate more the offline tools because I’m a control freak and because of the speed and comfort of my own setup but I must admit that having access to your projects online is an attractive option, especially for people on the go; plus the peace of mind that your project is hosted on a server in a data center where reliability and safety should be the norm.
The code style is clean without being pedantic. Personally, I would use a more strict naming convention but, without doubt, CoOS code uses meaningful names for functions, variables and constants that makes the code readable. I would also argue for a longer source line — with the wide screen monitors available today a 100…120 character line size would make a lot of sense.
Comments are formatted for Doxygen (a documentation system for C++, C, Java, etc. that extracts the information from commented source files).
Each module (i.e. source file) begins with a comment block clearly stating the version number, name, date, description:
/**
*******************************************************************************
* @file core.c
* @version V1.12
* @date 2010.03.01
* @brief Core implementation code of CooCox CoOS kernel.
*******************************************************************************
* @copy
*
* INTERNAL FILE,DON'T PUBLIC.
*
* <h2><center>© COPYRIGHT 2009 CooCox </center></h2>
*******************************************************************************
*/
Each function is preceded by its own comment block describing the purpose, input and output parameters, return value and notes:
/**
*******************************************************************************
* @brief Enter a ISR.
* @param[in] None
* @param[out] None
* @retval None
*
* @par Description
* @details This function is called to notify OS when enter to an ISR.
*
* @note When you call API in ISR,you must call CoEnterISR() before your
* interrupt handler code,and call CoExitISR() after your handler
* code and before exiting from ISR.
*******************************************************************************
*/
void CoEnterISR (void)
{
. . . . .
}
I like the trailing comments used almost everywhere in the code — they bring clarity separating the code from the story. However, the short line length (~80 characters) makes this task difficult: one more reason for a long line.
void CoStartOS (void)
{
TCBRunning = &TCBTbl[0]; /* Get running task */
TCBNext = TCBRunning; /* Set next scheduled task as running task */
TCBRunning->state = TASK_RUNNING; /* Set running task status to RUNNING */
RemoveFromTCBRdyList(TCBRunning); /* Remove running task from READY list */
OsSchedUnlock(); /* Enable Schedule, call task schedule */
}
The code uses portable data types, a good practice to achieve portability but a bit more difficult when it comes to reconcile them with your own type definitions. However, many embedded software developers agree that portable data types are a necessity in order to avoid hard to find problems when the code is moved from one CPU to another (especially if they differ in word size). Here is an example of portable data types used in CoOS.
typedef signed char S8;
typedef unsigned char U8;
typedef short S16;
typedef unsigned short U16;
typedef int S32;
typedef unsigned int U32;
typedef long long S64;
typedef unsigned long long U64;
typedef unsigned char BOOL;
I used similar definitions for years (e.g. UINT8, UINT16, UINT32, BOOLEAN, etc.). As one can see they are similar, except the names. For compatibility with my own code I use either macros or derived types to reconcile the two definition sets. For example:
typedef SINT8 S8;
typedef UINT8 U8;
typedef SINT16 S16;
typedef UINT16 U16;
typedef SINT32 S32;
typedef UINT32 U32;
typedef SINT64 S64;
typedef UINT64 U64;
#define BOOL bool // available with a C99 compiler
I will end here my comments regarding style. The next postings will reflect my struggles to put this RTOS to work. So, don’t expect any particular order.
Few weeks ago I found a web site — www.coocox.org — that promotes a new and free real-time multi-task OS kernel devoted to ARM Cortex M0 and M3 series together with a set of tools for development. When it comes to software tools I must say that I’ve been using quite a few in my career and I stopped to a few that I feel comfortable with. I’ve been more interested in the RTOS — CoOS — offered by CooCox. Reading the terms and conditions I found that “CooCox CoOS is free and open-source, it is licensed under the Berkeley Software Distribution (BSD) and can be used in commercial applications under this license.” Good enough for the moment.
It is less clear who is behind this enterprise. The so-called “CooCox Business Model” is also foggy — it is apparently based on sponsorship relations that the company owners hope to find partners for. The marketing face of the company seems to be Miss Anny Lee from China that has her own profile on LinkedIn and that has been very active on the social media networks like Tweeter and several forums dedicated to embedded systems. I do not intend to spend time on the debates between Ms Lee and people representing other groups involved in RTOS development. I will try to limit the discussion to technical aspects regarding CoOS RTOS.
I began to dissect the CoOS code initially with the purpose to evaluate and eventually use this RTOS in my development. In the years I created my own real-time frameworks, one being a small footprint RTOS for 8051 microcontroller. I also used few proprietary real-time kernels and the well respected µC/OS (versions 1.xx and 2.xx) in my projects while being hired by various employers.
At a first glance the CoOS kernel looks modern, scalable and reach in services without a complex API. Once used to programming in a RTOS environment, CoOS will quickly feel familiar to almost any embedded developer. The API can be organized in groups of methods (functions) as suggested by the authors in the manual:
- System Management
- Task Management
- Time Management
- Software Timer
- Memory Management
- Mutex Sections
- Semaphores
- Mailboxes
- Message Queues
- Flags
- System Utilities
The manual is organized and helpful but not very well written. It is obvious that the language used in this document lacks precision and accuracy, especially for someone looking for a training material or for a plethora of examples and details. There are examples but sketchy and sometimes confusing but by no means useful for a starter. Here is the table of contents:
- Chapter 1 – CooCox CoOS Overview
- Chapter 2 – Task Management
- Chapter 3 – Time Management
- Chapter 4 – Memory Management
- Chapter 5 – Inter-task Synchronization & Communication
- Chapter 6 – API Reference
The web site looks also clean but, with the risk of repeating, it is sketchy when it comes to content. I would recommend downloading the examples, especially if you can find the target board they were written for. There is also a discussion forum that may become a principal source of additional information and support. Registering as a user is straightforward and painless. The support team seems to be active judging by the response time that in my case has been less than 12 hours after posting. I will continue to place my inquiries regarding CoOS and monitor the response time (not only to questions but also to bug fixing).
In the following days I will continue my investigation and, eventually, get into more juicy (read “technical“) details.
Working in the lighting industry for the last few years I’ve seen how the lighting technology is moving gradually from the incandescent light bulb to CFLs, HIDs and ultimately to Solid-State Lighting (SSL) i.e. LEDs and OLEDs. While the SSL lamps primarily promise high efficiency, they are also highly controllable, an attribute partially ignored by the marketing people, mostly due to the lack of an adequate lighting infrastructure (with all its consequences – read below).
It is obvious that an infrastructure almost 150 years old, devoted mainly to incandescent bulbs, will hardly meet the needs of the emerging controllable light engines. Attempts to overcome the obstacles created by the current infrastructure have been made but they lack vision and consumer appeal: adding more wires or wireless transceivers, inventing lighting specific protocols, replacing switches and dimmers with fancy (and quirky) user interaction devices, did not help either the electrician nor the end user to avoid the peculiar nature of such solutions.
The historian Thomas Hughes has attributed Edison’s success to the fact that he invented an entire, integrated system of electric lighting (and not only the incandescent bulb which was the result of 22 prior inventions according to historians):
“The lamp was a small component in his system of electric lighting, and no more critical to its effective functioning than the Edison Jumbo generator, the Edison main and feeder, and the parallel-distribution system. Other inventors with generators and incandescent lamps, and with comparable ingenuity and excellence, have long been forgotten because their creators did not preside over their introduction in a system of lighting.”
Is the lighting industry ready in investing and supporting the development of a better lighting infrastructure that will enable the full potential of the new lighting technologies?
While technical comments are welcome, I am more interested in your opinions and suggestions from a marketing and business perspective.
This is just the beginning of a long journey.Technical, but no less personal.
Keep an eye on these “enlightening” pages: there will be light, there will be color and maybe a better world tomorrow.
|
|