[TUT][SOFT][C] Learning C on the PC

Go To Last Post
2 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Before attempting to program an AVR in C it's best to actually learn the basics of the C language on your PC. While it is possible to learn C using an AVR the fact that you don't (usually) have easy access to a keyboard for input and a screen for output can making trying a "quick experiment" to learn some new feature of C very tricky. Also on an AVR (depending on the C compiler you use) even if you have a UART terminal or an LCD screen available you may find that you cannot just write printf("hello") and expect to see that output without knowing about some confusing extra stuff that takes away from the simplicity of learning C.

Now on a PC there are many different ways to get, install and use a C compiler. For example you can get gcc (similar to the gcc that is use for AVR) and write programs with that but as it works on the command line (more often in Linux than Windows) and does not come with an easy to use debugger it can then be tricky to find out what's wrong if something your try does not immediately work.

You can get an integrated development environment (IDE) such as Eclipse, Netbeans or Code::Blocks and use that with a compiler such as gcc but it often requires quite a lot of detailed knowledge to actually get things setup in the first place and your one might not work quite the same as someone else's who's tutorial you are trying to follow because of some small detail you missed when configuring things.

So it's probably better to get a complete IDE that comes with compiler, editor and debugger and everything you need as a single download. Because there are many versions of Linux but principally just one Windows I'm just going to talk about solutions for use on Windows here.

Now love them or hate them Microsoft have, for a long time, been keen to get people to use their development tools (because you'll then write Windows programs which may ultimately sell more copies of Windows for them) so they give away their own development tools in "Express" versions which are slightly cut-down versions of the corporate tools that big companies use to write Windows software. They have various .Net languages (C+=, Visual Basic. C# etc) and there are Visual Studio Express versions of each. There's a lot of knowledge and a lot of example code out on the internet for these so they are well worth considering if your goal erally is to write Windows programs.

However if your plan here is just to learn C then note that although there is a Visual Studio Express version of Microsoft C++ and in theory you can write just plain C (rather than C++) with it, it's not exactly that easy as Microsoft focus everything towards C++. This is where a package called Pelles C comes to the rescue.

It's principally a C compiler within an integrated development environment and the good news is that it costs nothing. You can get it here:

http://www.smorgasbordet.com/pel...

(it's very reminiscent of Microsoft Visual Studio from the early to mid 90's in fact)

So here's a quick guide to using Pelles both to write a command line app but also a windows app that both do effectively the same thing. When you use File-New Project... pick one of the two circled options here:

Use the red one for a simple command line app and the blue one to make a windows app. Taking the first of those:

Unless you really want them to write "printf("hello world")" for you then just select "simple". The gives you something like this:

I'm not a great fan of their pre-written comments so I delete that and then to the line that's already there I added #include as the program will use atoi() that is documented in it.

I then wrote a few lines to meet a recent spec which was for a program that would allow a string to be input then the C function atoi() might be used to convert the input characters to binary and then this value would then be output again both in decimal and hex.

The system function gets() will wait for an entire line up to [Enter] to be input and then the result comes back in the string array you specify (buff[] in this case) so I plan for this to contain something like "123".

To then print out in both decimal and hex I figure that printf() is the simplest solution. Use %u to print an unsigned value in decimal and %X to print it in hex (X=upper case A..F while x=lower case a..f). I added %02X to say that I want any number with only one digit padded with a leading 0 to make 2 digits. Putti9ng all that together gets me:

When run and I type in 123 that gives:

Obviously the output in decimal is the same as the input but by using atoi() I have the value in a numeric variable which is what allows it to also be printed as 0x7B.

In passing one thing I would point out is that by default Pelles does not seem to enable debug so there are two places you need to switch that on. Both are under Porject-Project options.., the first on the "Compiler" tab:

Set that drop list at the top left to "Full". Then on the "Linker" tab:

Set that top left drop list to "Codeview and COFF format". Having done this when you build you can start with debugging and single step through the lines of the program and inspect variables at each step and so on.

Now going back to the original New Project choice. If you chose the blue circled "Win32 application" then, "hello world":

as you've seen, you get a lot of pre-written code. It's well worth tracking down a book by Charles Petzold to learn about this stuff but actually Pelles takes quite a lot of headaches out of writing Windows programs by delivering all this pre-made stuff. They also have a very handy HANDLE_MSG() macro that can make it easy to add support for some more of the messages that Windows may send to your application.

The way Windows programs work is that they just it there waiting for some event (like input, mouse movement, button click, etc.) to occur and when it does Windows send s message (usually a WM_something where WM_ = Windows Messsage) to tell you what has occurred. You then handle that message by doing whatever is necessary.

To write the atoi() program there are various way to handle the input and then the output but I'll show just one variant.

But first just build and run the Hello World program that Pelles created. It creates a typical desktop window (too large for my liking!) and prints "Hello Windows" in the middle.

As you don't actually want that "Hello Windows" find the line where it occurs and comment it:

    //DrawText(ps.hdc, _T("Hello, Windows!"), -1, &rc, DT_SINGLELINE|DT_CENTER|DT_VCENTER);

Now if the program is built and run you just get a blank window (the white bit is known as the "client area").

To do the atoi() thing you need somewhere for the user to type into and somewhere for the result to appear. Starting with the place to type into that would suggest using an "Edit box". In order to add one of those to the client area you need to handle another message Windows sends. When the program first starts it sends WM_CREATE. You can intercept and act upon this message to add new bits to the client area. So find the bit that says:

static void Main_OnCommand(HWND, int, HWND, UINT);
static void Main_OnDestroy(HWND);

near the top which are a couple of the existing message handlers and add:

static void Main_OnCommand(HWND, int, HWND, UINT);
static void Main_OnDestroy(HWND);
static BOOL Main_OnCreate(HWND, LPCREATESTRUCT);

That new entry is a function that will be added that will get called when Windows send WM_CREATE.

Oh in passing find the bit where it already does:

    /* Create the main window */
    hwnd = CreateWindow(_T("winatoiClass"),
        _T("winatoi Program"),
        WS_OVERLAPPEDWINDOW|WS_HSCROLL|WS_VSCROLL,

and change the following four lines to be:

    /* Create the main window */
    hwnd = CreateWindow(_T("winatoiClass"),
        _T("winatoi Program"),
        WS_OVERLAPPEDWINDOW|WS_HSCROLL|WS_VSCROLL,
        200,
        200,
        400,
        300,

So instead of the program window being created at (0,0) = top left of the desktop it is created at (200,200) and it wil be created with width=400, height=300 which is a far more sensible size.

Now back to the WM_CREATE. We need to tell Windows we want to handle that message so find:

        HANDLE_MSG(hwnd, WM_COMMAND, Main_OnCommand);
        HANDLE_MSG(hwnd, WM_DESTROY, Main_OnDestroy);

and add the additional line:

        HANDLE_MSG(hwnd, WM_COMMAND, Main_OnCommand);
        HANDLE_MSG(hwnd, WM_DESTROY, Main_OnDestroy);
        HANDLE_MSG(hwnd, WM_CREATE, Main_OnCreate);

That is saying that if the program is sent a WM_CREATE message (and it will be) that the function Main_OnCreate() will be called. Now find:

static void Main_OnDestroy(HWND hwnd)
{
    PostQuitMessage(0);
}

and after it add:

static BOOL Main_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) {
	// Create an edit box
	hEdit=CreateWindowEx(WS_EX_CLIENTEDGE,
		"EDIT",
		"",
		WS_CHILD|WS_VISIBLE|
		ES_MULTILINE|ES_AUTOVSCROLL|ES_AUTOHSCROLL,
		50,
		120,
		200,
		30,
		hwnd,
		(HMENU)IDC_MAIN_EDIT,
		GetModuleHandle(NULL),
		NULL);
	// label the edit box
	CreateWindowEx(WS_EX_CLIENTEDGE,
                        TEXT("STATIC"),                   /*The name of the static control's class*/
                        TEXT("Enter a number then press OK"),                  /*Label's Text*/
                        WS_CHILD | WS_VISIBLE | SS_LEFT,  /*Styles (continued)*/
                        50,                                /*X co-ordinates*/
                        70,                                /*Y co-ordinates*/
                        200,                               /*Width*/
                        30,                               /*Height*/
                        hwnd,                             /*Parent HWND*/
                        (HMENU) IDC_MAIN_STATIC,              /*The Label's ID*/
                       GetModuleHandle(NULL),                        /*The HINSTANCE of your program*/ 
                        NULL);                            /*Parameters for main window*/
	CreateWindow (TEXT("button"),                      // The class name required is button
                           TEXT("OK"),                  // the caption of the button
                           WS_CHILD |WS_VISIBLE | BS_PUSHBUTTON,  // the styles
                           240,120,    // the left and top co-ordinates
                           50,30,   // width and height
                           hwnd,      // parent window handle
                           (HMENU)IDC_MAIN_BUTTON,    // the ID of your button
                           GetModuleHandle(NULL),   // the instance of your application
                           NULL) ;    
	return TRUE;
}

that looks daunting but its really just adding 3 child window controls to the client area of our main window. There is an EDIT box into which the user wil type input. There is a static label which just says "Enter a number then press OK" to tell the user how to operate the program. Finally there is an OK button. Each of the 3 windows has four numbers. The first two are an (x,y) of where the item appears in the window and the next to are (w,h) to dictate its width and height. On the whole I'd make anything with Standard" text 30 pixels high.

All the elements added need a unique number so they can be identified. These are the IDC_... fields. These should be defined in main.h as:

#define IDC_MAIN_EDIT 1001
#define IDC_MAIN_STATIC 1002
#define IDC_MAIN_BUTTON 1003

I picked the numbers kind of at random though there is a convention to say that they normally number from 1001 upwards. the key thing is that they have unique numbers as these will be used for Windows to tell us which thing just sent us a message (such as when the OK button is clicked).

The CreateWindowEx() call that adds these child window elements to the main client area will return a value. Sometimes it can be ignored because once placed you won't need to interact with the control again. But in the case of the edit text it will be necessary to later ask it what number was entered so it's "handle" is assigned to a global called hEdit. That variable needs to be created near the top of the program as the hEdit in:

/** Global variables ********************************************************/

static HANDLE ghInstance;
HWND hEdit;

The program is already to go and already you can build and run it and enter text into the edit box and press the OK button. Only thing is that when you do nothing happens. So code must be added to finally do the atoi() thing that is the whole purpose of the program. Now when a button like OK is clicked Windows actually sends WM_COMMAND and along with it there will be a magic number to say which thing was clicked. That's where IDC_MAIN_BUTTON comes into play. When the OK is clicked Windows will send WM_COMMAND along with IDC_MAIN_BUTTON to let us know it was the OK button that was clicked. Already there is a handler for WM_COMMAND called Main_OnCommand so that's where to add the atoi() code:

    switch (id)
    {
        case IDM_ABOUT:
            DialogBox(ghInstance, MAKEINTRESOURCE(DLG_ABOUT), hwnd, (DLGPROC)AboutDlgProc);
	   break;

	case IDC_MAIN_BUTTON:
	   SendMessage(hEdit, WM_GETTEXT, 100, (LPARAM)mybuff);
	   n = atoi(mybuff);
	   sprintf(result, "n = %u, 0x%02X", n, n);
	   MessageBox(hwnd, result, "The result is", MB_OK);
	   break;
        /* TODO: Enter more commands here */
    }

(their template code forgot the break in the existing case: by the way!). This has the case: to handle IDC_MAIN_BUTTON added. What the code does is sends a message to the Edit control: WM_GETTEXT. This is why we needed hEdit when the edit control was created so we could send it a message. It's own message handler will have a HANDLE_MSG() for WM_GETTEXT and what we send along with that message is how big out char[] buffer is and the address of it. When the Edit control gets the message it will copy the text that was entered into our buffer and SendMessage() (unlike a similar call called PostMessage) will not return to our program until the Edit window has finished handling the WM_GETTEXT we sent to it. So after this call we know that mybuff[] will hold whatever text the user had entered before he clicked [Ok]. We kind of hope it will be "123"!

Finally and at last the program can do the same thing as the DOS program did. It calls atoi() to convert the entered string "123" to the numeric value 123 and then does printf() to convert it to decimal and hex output strings. Only this time sprintf()is used to print it into a string called result(). Now I thought about getting the static text "Enter a number and press OK" to give us a window handle when it was created and we could just use WM_SETTEXT (which is like WM_GETTEXT) to tell it to change the text it's displaying for this result[]. But it's more fun to explore MessageBox() as it's a VERY useful Windows call when you just quickly want to show some text in a little pop-up window. All it takes is the hwnd (window handle of our main window which will "own" the message box) then two pieces of text - one for inside the box and one for its title bar and finally a type. MB_OK asks for a message box that only has an OK button. There are other types such as MB_YESNO that shows two buttons - one "Yes", one "No" (and you can use the return value of the MessageBox call to find out which was clicked. But MB_OK is the simplest and this time we just ignore any return.

So the way the program works is that this appears:

As shown here you type something like 123 into the box. When you press OK the result is displayed something like this:

(and no, don't ask why those text labels have grey background - I'm not entirely sure yet!)

Last Edited: Mon. Oct 29, 2012 - 06:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh and these are the project files for the Win version..

Attachment(s):