How to Build a C Program in OS Windows

Sometimes a situation arises when you need to calculate something according to a complex algorithm directly on LapTop/NetTop/DeskTop PC. Moreover, this algorithm is written in C. This could be a digital filter, a discrete Fourier transform, a QR code generator, a piece of linear algebra with vectors, some kind of trigonometric calculation, a software modulator, statistical processing of a random variable. Yes, anything! That is, you want to use the C language as a flexible and fast calculator in Windows. Here you need to write a program in C.

A computer is a universal computer

Or, for example, you program microcontrollers in C and want to make a firmware simulator as a console application. For example, you need to run unit tests of platform-independent code on a “large computer”. Then there is an 80% chance that you will need a firmware configurator via UART. Then you will need the Loader console application to load the firmware itself via UART via BootLoader.

Then you will need a tiny PC synchronization utility for the real time clock with the PC.

Why C?

1–This LapTop utility should be written in the same language as the firmware, if only for the reason that you can reuse the code base from microcontrollers for programming on DeskTop(e).

2–The fact is that the C programming language is the simplest programming language one that is still more or less used in industrial development. In fact, in C there are only functions and variables. There are no virtual functions, templates, delegates or other concepts here. In C, everything is extremely simple and specific.

Development for a PC is not cross compilation, as is the case with assembling artifacts for a microcontroller, and here on a PC everything is to some extent simpler. When building a C application, you don’t need to think about the linker configuration file, as we are used to doing for cross compilation of artifacts for microcontrollers (*.ld files).

First, let’s decide for which Target(s) we need to build a binary. We need to find out what microprocessor is installed on our motherboard. The CPUZ utility can show this information.

In this case, I have an Intel Celeron J4125 2Ghz, 4x cores, L1 32kByte, L2 4MByte, 10W on one computer. Another computer has a 64-bit AMD Ryzen 5 PRO 3400GE 3.30 GHz microprocessor installed.

But that’s not even that important. It is important what Instruction Set we have. In this case – x86-64. This means that we have a 64-bit processor. It turns out that we have a choice: either install a 64-bit C compiler, or roll out a 32-bit C compiler.

As a rule, we have to work on different processors, but in OS Windows we don’t even notice this.

Units:

text

bit

N

N

kByte

kByte

MByte

No.

CPU

bitness

cores

Threads

L1

L2

L3

1

AMD Ryzen 5 PRO 3400GE

64

4

8

32

512

4

2

Intel Celeron J4125

4

4

32

3

Intel Core i7 8550U

64

4

8

32

256

8

At the same time, our firmware simulator must be assembled and run on all desktop computer environments: at work, at home, in the garage.

As in any business, you first need to decide on the terminology.

Terminology

Compiler – a program that translates human-readable text written in a programming language into a set of machine codes (human-unreadable binary code). Programmers are people who are closer to abstractions than anyone else. Any programming language, in particular C, is a short level where you don’t have to think about the command set of a given microprocessor. The processor does not have any variables or functions. Variables exist only in the programmer’s mind. That’s why we need a C compiler. For each C file, the compiler produces a *.o file with machine code native to the given processor.

Linker– a utility that glues *.o binary files into one monolithic executable binary program file. In our case it is *.exe.

Artifact – the result of the ToolChain(a). In our case, this is a *.exe file with a binary program file.

What software do you need?

Text editor (Text Editor)

First of all, you need some kind of text editor to write this very source code of the C program. There are a lot of options here. NotePad++, Eclipse, MS VS Code.

Build Tools

In computers, nothing happens by itself. Computers are the most lazy and uninitiative creatures. Everything needs to be explained to them as thoroughly and clearly as possible. Therefore, we must explicitly indicate from which *.c files we want to compile the program. These files need to be listed and indexed somehow. For this purpose, a special utility was created called make. The idea is simple. A text file (Makefile) is created and the correct sequence of calling console utilities is written in it, which will lead to an executable file with the program appearing on the hard drive.

Preprocessor

This is a console utility (cpp.exe) that inserts and replaces pieces of text. It is needed purely for the sake of convenience of writing the text. The preprocessor allows you to completely eliminate such a bad phenomenon as duplication of program code. At the same time, the preprocessor absolutely does not care what programming language it works with (Ci, C++, DeviceTree, Graphviz, layout scripts, etc.). For a preprocessor, any programming language is just text.

Now let’s look at the practical aspects.

Which C code compiler to choose?

There are several free options to choose from.

No.

Compiler

width of the generated code

1

СygWin

64

2

MinGW

32

3

Mingw-w64

64

4

clang

64

For microcontroller programmers, I highly recommend choosing MinGW. The fact is that MinGW generates 32-bit code. This exactly corresponds to the fact that most microcontrollers (for example ARM Cortex Mx) are 32-bit. And this way you will achieve greater compatibility between the microcontroller firmware code and the console application in Windows.

The second reason why you need to use the C:\MinGW\bin\gcc.exe compiler is that the MinGW environment has a header file conio.h that defines the function kbhit(). We will need this to simulate a UART-CLI console text terminal on stdout/stdin on a PC.

>C:\MinGW\bin\gcc.exe  --version
gcc.exe (MinGW.org GCC-6.3.0-1) 6.3.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

The MinGW set of utilities can be easily downloaded and installed like any other program under OS Windows.

Linker

The layout is handled by the ld utility, which calls collect2. It is the collect2.exe utility that will generate errors if you call functions without defining their bodies.

What do you really need from software?

Here is the minimum gentleman’s kit for putting together a program in C.

No.

Purpose of the utility

Utility name

1

Text editor

NotePad++.exe

2

Preprocessor

cpp.exe

3

Compiler

gcc.exe

4

Console utility for deleting files or folders

rm

5

Linker

ld.exe

6

ToolChain management utility. She decides what to collect and in what order to get artifacts

make.exe

7

A utility for analyzing the resulting binary. An analogue of readelf.exe from the world of microcontroller programming

PE explore.exe

Traditionally, C programs are compiled from make scripts. Here is a minimalist makefile for building a multi-file C project on Windows

MK_PATH:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
#@echo $(error MK_PATH=$(MK_PATH))
INCDIR += -I$(MK_PATH)

WORKSPACE_LOC:= $(MK_PATH)../../
$(info  WORKSPACE_LOC= $(WORKSPACE_LOC))
INCDIR += -I$(WORKSPACE_LOC)

BUILDDIR := $(MK_PATH)/Build

SRC_PATH :=  $(dir $(abspath $(dir $$PWD) ))
#@echo $(error SRC_PATH=$(SRC_PATH))

OBJDIR := $(SRC_PATH)obj
# the compiler to use

OPT += -DHAS_GCC
CC = C:\MinGW\bin\gcc.exe

# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS += -g

#Generate code for 32-bit ABI
CFLAGS += -m32

CFLAGS += -std=c11 -fshort-enums
#CFLAGS += -Og
CFLAGS  += -O0
#CFLAGS  += -Wall
#CFLAGS  +=-pedantic
#CFLAGS += -ftime-report

#files to link:
LFLAGS += -static
#LFLAGS += -lm

EXECUTABLE=firmware_simulator_x86_m

include $(MK_PATH)config.mk

ifeq ($(CLI),Y)
    include $(MK_PATH)cli_config.mk
endif

ifeq ($(UNIT_TEST),Y)
    include $(MK_PATH)test_config.mk
endif

ifeq ($(UNIT_TEST),Y)
    include $(MK_PATH)diag_config.mk
endif

include $(WORKSPACE_LOC)code_base.mk


#@echo $(error SOURCES_C= $(SOURCES_C))
INCDIR := $(subst /cygdrive/c/,C:/, $(INCDIR))
#@echo $(error INCDIR= $(INCDIR))
OBJ := $(patsubst %.c, %.o, $(SOURCES_C))
OBJ := $(subst /cygdrive/c/,C:/, $(OBJ))
#@echo $(error OBJ= $(OBJ))

.PHONY:all

all:$(OBJ) $(EXECUTABLE)

$(EXECUTABLE): $(OBJ)
	$(CC) $(CFLAGS)  $(OBJ) $(LFLAGS) -o $(EXECUTABLE).exe 

%.o: %.c
	$(CC) $(CFLAGS) $(INCDIR) $(OPT) -c $< -o $@ 

clean:
	rm -r $(EXECUTABLE) $(OBJ) 

Here are the files config.mk, cli_config.mk, test_config.mk and diag_config.mk are simply files listing a set of environment variables for selectively building specific sources from a common code base. Here is the root makefile for connecting different software components code_base.mk

ifneq ($(CODE_BASE_MK),Y)
    CODE_BASE_MK=Y
    $(info CodeBase Config)

    #@echo $(error WORKSPACE_LOC=$(WORKSPACE_LOC))
    INCDIR += -I$(WORKSPACE_LOC)

    ifeq ($(THIRD_PARTY),Y)
        include $(WORKSPACE_LOC)/third_party/third_party.mk
    endif

    ifeq ($(APPLICATIONS),Y)
        include $(WORKSPACE_LOC)/applications/applications.mk
    endif

    ifeq ($(CONNECTIVITY),Y)
        include $(WORKSPACE_LOC)/connectivity/connectivity.mk
    endif

    ifeq ($(CONTROL),Y)
        include $(WORKSPACE_LOC)/control/control.mk
    endif
    
    ifeq ($(COMPUTING),Y)
        #@echo $(error COMPUTING=$(COMPUTING))
        include $(WORKSPACE_LOC)/computing/computing.mk
    endif

    ifeq ($(SENSITIVITY),Y)
        #@echo $(error SENSITIVITY=$(SENSITIVITY))
        include $(WORKSPACE_LOC)/sensitivity/sensitivity.mk
    endif

    ifeq ($(STORAGE),Y)
        #@echo $(error STORAGE=$(STORAGE))
        include $(WORKSPACE_LOC)/storage/storage.mk
    endif

    ifeq ($(UNIT_TEST),Y)  
        include $(WORKSPACE_LOC)/unit_tests/unit_test.mk
    endif
endif

The final makefile might look like this: This is where the final *.c files are indexed and keywords for the preprocessor are defined (starting with HAS_XXXXX).


$(info SCHMITT_TRIGGER_MK_INC=$(SCHMITT_TRIGGER_MK_INC))
ifneq ($(SCHMITT_TRIGGER_MK_INC),Y)
    SCHMITT_TRIGGER_MK_INC=Y

    mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
    $(info Build  $(mkfile_path) )

    SCHMITT_TRIGGER_DIR = $(COMPUTING_DIR)/schmitt_trigger

    INCDIR += -I$(SCHMITT_TRIGGER_DIR)
    SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger.c

    SCHMITT_TRIGGER=Y
    OPT += -DHAS_SCHMITT_TRIGGER

    ifeq ($(DIAG),Y)
        OPT += -DHAS_SCHMITT_TRIGGER_DIAG
        SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger_diag.c
    endif

    ifeq ($(CLI),Y)
        ifeq ($(SCHMITT_TRIGGER_COMMANDS),Y)
            OPT += -DHAS_SCHMITT_TRIGGER_COMMANDS
            SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger_commands.c
        endif
    endif
endif

When building the project for the first time, this error will most likely pop up. This means that you need to reinstall MinGW.


C:\Users\username\AppData\Local\Temp\ccT9XWou.s:54: Error: invalid instruction suffix for `push'
C:\Users\username\AppData\Local\Temp\ccfvWBon.s:19: Error: invalid instruction suffix for `pop'

After installation, the gcc.exe -dumpmachine command should show mingw32

>C:\MinGW\bin\gcc.exe -dumpmachine
mingw32

The contents of that Makefile can be represented in the form of this simple block diagram ToolChain(a). Here you can visually trace the path the *.c file takes from the moment it is written to execution in Windows.

Otherwise, assembly on a PC is no different from assembly for a microcontroller. This is the advantage of building from Make. When working with make, the build for any processor looks plus/minus the same. It’s just that the environment variables are defined differently: CC LD, etc. All the same *.mk files will be matched as in the code base for the firmware.

You can read about how firmware is assembled from scripts in this text:
Setting up ToolChain(s) for Win10+GCC+C+Makefile+ARM Cortex-Mx+GDB https://habr.com/ru/articles/673522/

When the entire code base was assembled, the binary turned out to be only 1.9MByte

Debugging the firmware simulator

The assembly artifact is the *.exe file. It can be launched by calling it from the cmd command line.

Here an imitation of a UART-CLI terminal occurs, only instead of UART, the files stdin (analogous to UART-RX)/stdout (analogous to UART-TX) are used.

You can see that the execution speed of the application is simply crazy. In one second, the super-cycle manages to spin as many as 11,240,590 times! In microcontrollers this value was usually on the order of 7000 times. It turns out that on a PC the application runs 1600 times faster. Three orders of magnitude faster.

Conclusion

Building C code for DeskTop is a very useful skill when debugging microcontroller firmware. You can debug huge chunks of platform independent code: CRC, line processing, Schmitt trigger, binary protocols, etc.

You can also compile the project using different compilers: GCC, Clang. And thereby find and eliminate more errors in the code base.

As you can see, there is absolutely nothing complicated about assembling C programs on a PC. I hope this text will help more microcontroller programmers to debug their applications on DeskTop PC and thereby create excellent software products.

Links

https://metanit.com/c/tutorial/1.2.php

https://www.youtube.com/watch?v=SRMUJHY-0gA

https://www.youtube.com/watch?v=-lLctanPefY

https://sourceforge.net/projects/mingw/

Generating dependencies within a program
https://habr.com/ru/articles/765424/

Building firmware for CC2652 from Makefile
https://habr.com/ru/articles/726352/

Why is it Important to Collect Code from Scripts
https://habr.com/ru/articles/723054/

Setting up ToolChain(s) for Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB
https://habr.com/ru/articles/673522/

Questions

1–Which path does the file take from the moment it is written to the moment it is executed?

2–What happens between pressing Enter when running a console utility in cmd and running the main() function?

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *