embedded c - lecture 4

45
Prepared by: Mohamed AbdAllah Embedded C-Programming Lecture 4 1

Upload: mohamed-abdallah

Post on 15-Apr-2017

1.106 views

Category:

Engineering


2 download

TRANSCRIPT

Page 1: Embedded C - Lecture 4

Prepared by: Mohamed AbdAllah

Embedded C-Programming

Lecture 4

1

Page 2: Embedded C - Lecture 4

Agenda

Memory alignment.

Bitwise operations.

HW and I/O concepts (Memory mapped registers, polling, interrupts, DMA),

Bootloader, Startup file.

Inline Assembly.

Software layered architecture.

MISRA rules.

Task 4.

2

Page 3: Embedded C - Lecture 4

Agenda

3

Page 4: Embedded C - Lecture 4

Memory alignment

4

Page 5: Embedded C - Lecture 4

What is alignment

The way our C compiler lays out basic C data types in memory is constrained in order to make memory accesses faster.

Storage for the basic C data types on an x86 or ARM processor doesn’t normally start at arbitrary byte addresses in memory. Rather, each type except char has an alignment requirement; chars can start on any byte address, but 2-byte shorts must start on an even address, 4-byte ints or floats must start on an address divisible by 4, and 8-byte longs or doubles must start on an address divisible by 8. Signed or unsigned makes no difference.

The jargon for this is that basic C types on x86 and ARM are self-aligned. Pointers, whether 32-bit (4-byte) or 64-bit (8-byte) are self-aligned too.

5

Memory alignment

Page 6: Embedded C - Lecture 4

What is alignment

Self-alignment makes access faster because it facilitates generating single-instruction fetches and puts of the typed data.

Without alignment constraints, on the other hand, the code might end up having to do two or more accesses spanning machine-word boundaries.

Characters are a special case; they’re equally expensive from anywhere they live inside a single machine word. That’s why they don’t have a preferred alignment.

6

Memory alignment

Page 7: Embedded C - Lecture 4

Structure alignment and padding

1. Alignment

Consider this structure:

struct foo1 {

char *p;

char c;

long x;

};

Assuming a 32-bit machine, the memory layout of one of these looks like this:

struct foo1 {

char *p; /* 4 bytes */

char c; /* 1 byte

char pad[3]; /* 3 bytes */

long x; /* 4 bytes */

}; 7

Memory alignment

Page 8: Embedded C - Lecture 4

Structure alignment and padding

1. Alignment

In general, a structure instance will have the alignment of its widest scalar member. Compilers do this as the easiest way to ensure that all the members are self-aligned for fast access.

Consider previous structure but char c is put first then it will look like:

struct foo2 {

char c; /* 1 byte */

char pad[3]; /* 3 bytes */

char *p; /* 4 bytes */

long x; /* 4 bytes */

};

If the members were separate variables, c could start at any byte boundary and the size of pad might vary. Because struct foo2 has the pointer alignment of its widest member, that’s no longer possible. Now c has to be pointer-aligned, and following padding of 3 bytes is locked in.

8

Memory alignment

Page 9: Embedded C - Lecture 4

Structure alignment and padding

2. Padding

Consider this example on a 32-bit machine:

struct foo3 {

char *p; /* 4 bytes */

char c; /* 1 byte */

};

struct foo3 quad[4]; /* Array of structures */

sizeof(struct foo3) is 8 not 5. Thus, in the quad array, each member has 3 bytes of trailing padding, because the first member of each following struct in the array wants to be self-aligned on an 4-byte boundary. The memory layout is as though the structure had been declared like this:

struct foo3 {

char *p; /* 4 bytes */

char c; /* 1 byte */

char pad[3];

}; 9

Memory alignment

Page 10: Embedded C - Lecture 4

Structure alignment and padding

If our structure has structure members, the inner structs want to have the alignment of longest scalar too. Suppose we write this:

struct foo5 {

char c;

struct foo5_inner {

char *p;

short x;

} inner;

};

The char *p member in the inner struct forces the outer struct to be pointer-aligned as well as the inner.

10

Memory alignment

Page 11: Embedded C - Lecture 4

Structure alignment and padding

Actual layout will be like this on a 32-bit machine:

struct foo5 {

char c; /* 1 byte*/

char pad1[3]; /* 3 bytes */

struct foo5_inner {

char *p; /* 4 bytes */

short x; /* 2 bytes */

char pad2[2]; /* 2 bytes */

} inner;

};

This structure gives us a hint of the savings that might be possible from repacking structures. Of 12 bytes, 5 of them are padding. That’s more than 40% waste space!

11

Memory alignment

Page 12: Embedded C - Lecture 4

#pragma pack()

GCC supports a set of #pragma directives that change the maximum alignment of members of structures (other than zero-width bit-fields), unions, and classes subsequently defined. The n value below always is required to be a small power of two and specifies the new alignment in bytes:

#pragma pack(push) /* Store packing settings */

#pragma pack(1) /* Align on 1 byte basis */

struct foo11 {

struct foo11 *p;

short x;

char c;

};

#pragma pack(pop) /* Restore packing settings */

The structure will be aligned on 1 byte basis, so it will have 7 bytes size.

12

Memory alignment

Page 13: Embedded C - Lecture 4

Bitfields

They give us the ability to declare structure fields of smaller than character width, down to 1 bit, like this:

struct foo6 {

short s;

char c;

int flip:1;

int nybble:4;

int septet:7;

};

foo6 f1;

f1.nybble = 0b1001;

The thing to know about bitfields is that they are implemented with word- and byte-level mask and rotate instructions operating on machine words, and cannot cross word boundaries.

Bitfields member can be accessed by its name as any normal structure member.

13

Memory alignment

Page 14: Embedded C - Lecture 4

Bitfields

The memory layout of the structure will be something like that:

struct foo6 {

short s;

char c;

char pad[1];

int flip:1;

int nybble:4;

int septet:7;

int pad2:20;

};

The size of the structure will be 8 bytes, but if we made 3 different integers for flip, nybble and septet the size would have been 16 bytes, which save 50% of memory.

14

Memory alignment

Page 15: Embedded C - Lecture 4

Bitwise operations

15

Page 16: Embedded C - Lecture 4

To set a specific bit in a specific register or variable:

16

Bitwise operations

/*--------------------------------------------------------------------

Description : Macro used to set a specific bit in a specific register

Input : .Register : register name

.Pin_Num : Pin number to set

--------------------------------------------------------------------*/

#define SET_BIT(Register,Pin_Num) ((Register) |= (1<<(Pin_Num)))

To clear a specific bit in a specific register or variable:

/*----------------------------------------------------------------------

Description : Macro used to clear a specific bit in a specific register

Input : .Register : register name

.Pin_Num : Pin number to clear

----------------------------------------------------------------------*/

#define CLEAR_BIT(Register,Pin_Num) ((Register) &= (~(1<<(Pin_Num))))

Page 17: Embedded C - Lecture 4

To check if a specific bit in a specific register or variable is set:

17

Bitwise operations

/*----------------------------------------------------------------

Description : Macro used to check if a specific bit in a specific register is set

Input : .Register : register name

.Pin_Num : Pin number to check

----------------------------------------------------------------*/

#define IS_BIT_SET(Register,Pin_Num) ((Register) & (1<<(Pin_Num)))

To check if a specific bit in a specific register or variable is clear:

/*----------------------------------------------------------------

Description : Macro used to check if a specific bit in a specific register is clear

Input : .Register : register name

.Pin_Num : Pin number to check

----------------------------------------------------------------*/

#define IS_BIT_CLEAR(Register,Pin_Num) (!((Register) & (1<<(Pin_Num))))

Page 18: Embedded C - Lecture 4

To toggle a specific bit in a specific register or variable:

18

Bitwise operations

/*--------------------------------------------------------------------

Description : Macro used to toggle a specific bit in a specific register

Input : .Register : register name

.Pin_Num : Pin number to toggle

--------------------------------------------------------------------*/

#define TOGGLE_BIT(Register,Pin_Num) ((Register) ^= (1<<(Pin_Num)))

To shift a register or variable left or right:

var1 = var1 >> 1; /* Shift all bits one step right, equals to dividing variable by 2 */

var2 = var2 << 1; /* Shift all bits one step left , equals to multiplying variable by 2 */

Page 19: Embedded C - Lecture 4

To rotate a register or variable left, assuming 8-bits register or variable:

19

Bitwise operations

/*-------------------------------------------------

Description : Macro used to rotate a register left

Input : .Register : register name

.Num : Number of rotations

-------------------------------------------------*/

#define ROTATE_L(Register,Num) (REG = (REG<<NUM) | (REG>>(8-NUM)))

To rotate a register or variable right, assuming 8-bits register or variable:

/*-------------------------------------------------

Description : Macro used to rotate a register right

Input : .Register : register name

.Num : Number of rotations

-------------------------------------------------*/

#define ROTATE_R(Register,Num) (REG = (REG>>NUM) | (REG<<(8-NUM)))

Page 20: Embedded C - Lecture 4

HW and I/O concepts

20

Page 21: Embedded C - Lecture 4

Memory mapping

Device drivers communicate with peripheral devices through device registers. A driver sends commands or data to a device by storing into its device register, or retrieves status or data from a device by reading from its device register.

Many processors use memory-mapped I/O, which maps device registers to fixed addresses in the conventional memory space. To a C or C++ programmer, a memory-mapped device register looks very much like an ordinary data object. Programs can use ordinary assignment operators to move values to or from memory-mapped device registers.

Example on Arm-CortexM4 processor:

#define GPIO_PORTF_DATA_R (*((volatile unsigned long *)0x400253FC))

Then when typing:

GPIO_PORTF_DATA_R |= 0x01;

It will output logic high on pin0 of PORTF. 21

HW and I/O concepts

Page 22: Embedded C - Lecture 4

Polling vs. Interrupt

A single microcontroller can serve several devices, that are two ways to do that: interrupts or polling

• Polling: In the Polling method, the microcontroller must "access by himself" the device and “ask” for the information it needs for processing. In fact we see that in the Polling method the external devices are not independent systems; they depend on the microcontroller, and only the micro is entitled to obtain access to the information it needs.

The main drawback of this method when writing program is waste of time of microcontroller, which needs to wait and check whether the new information has arrived.

The microcontroller continuously monitors the status of a given device until required information is ready. After that, it moves on to monitor the next device until everyone is serviced. The microcontroller checks all devices in a round robin fashion.

22

HW and I/O concepts

Page 23: Embedded C - Lecture 4

Polling vs. Interrupt

• Interrupt: Interrupt is the signal sent to the micro to mark the event that requires immediate attention.

Interrupt is “requesting" the processor to stop performing the current program and to “make time” to execute a special code. Whenever any device needs its service, the device notifies the microcontroller by sending it an interrupt signal.

Upon receiving an interrupt signal, the microcontroller interrupts whatever it is doing and serves the device. The program which is associated with the interrupt is called the interrupt service routine (ISR) or interrupt handler.

23

HW and I/O concepts

Page 24: Embedded C - Lecture 4

Polling vs. Interrupt

24

HW and I/O concepts

Is info ready

?

No

yes

Process information

Polling Interrupt

Page 25: Embedded C - Lecture 4

DMA

Direct memory access (DMA) is a feature of computer systems that allows certain hardware subsystems to access main system memory (RAM) independently of the central processing unit (CPU).

Without DMA, when the CPU is using programmed input/output, it is typically fully occupied for the entire duration of the read or write operation, and is thus unavailable to perform other work.

With DMA, the CPU first initiates the transfer, then it does other operations while the transfer is in progress, and it finally receives an interrupt from the DMA controller when the operation is done.

This feature is useful at any time that the CPU cannot keep up with the rate of data transfer, or when the CPU needs to perform useful work while waiting for a relatively slow I/O data transfer.

25

HW and I/O concepts

Page 26: Embedded C - Lecture 4

Bootloader

On reset, processor will always start executing a specific code located at a particular memory location, this piece of software is called “Bootloader”.

The job of the bootloader then, is to do one of two things:

1. Replace the existing application code with a new application (Flashing).

2. Start the existing application (By jumping to the application startup code).

How it determines which of these two things to do varies by implementation, but generally some external signal (such as a command on the serial port or a particular I/O line being pulled low) will tell it to load a new application from the communication port (or some other location) and write it to the application memory.

26

HW and I/O concepts

Page 27: Embedded C - Lecture 4

Startup file

One of the things that traditional software development tools do automatically is insert startup code: a small block of assembly language code that prepares the way for the execution of software written in a high-level language.

Each high-level language has its own set of expectations about the runtime environment. For example, programs written in C use a stack. Space for the stack has to be allocated before software written in C can be properly executed. That is just one of the responsibilities assigned to startup code for C programs.

Most cross-compilers for embedded systems include an assembly language file called startup.asm, crt0.s (short for C runtime), or something similar. The location and contents of this file are usually described in the documentation supplied with the compiler.

27

HW and I/O concepts

Page 28: Embedded C - Lecture 4

Startup file

Startup code for C programs usually consists of the following series of actions:

• Disable all interrupts.

• Copy any initialized data from ROM to RAM.

• Zero the uninitialized data area.

• Allocate space for and initialize the stack.

• Initialize the processor’s stack pointer.

• Call main.

Typically, the startup code will also include a few instructions after the call to main. These instructions will be executed only in the event that the high-level language program exits (i.e., the call to main returns). Depending on the nature of the embedded system, we might want to use these instructions to halt the processor, reset the entire system, or transfer control to a debugging tool.

28

HW and I/O concepts

Page 29: Embedded C - Lecture 4

Inline Assembly

29

Page 30: Embedded C - Lecture 4

Inline assembly is a feature of some compilers that allows very low level code written in assembly to be embedded in a high level language like C. This embedding is usually done for one of three reasons:

• Optimization (ex. Increase speed).

• Access to processor specific instructions (ex. Test and Set).

• System calls.

Example for GCC compiler:

asm("movl %ecx, %eax"); /* moves the content of ecx to

eax */

The main problem of using inline assembly in our code is portability because assembly instructions and syntax is hardware and compiler dependent.

30

Inline Assembly

Page 31: Embedded C - Lecture 4

Software layered architecture

31

Page 32: Embedded C - Lecture 4

The drive to reduce product development cycle times has led to the need for designing reusable code. It's apparent that reusing code across software projects decreases project development time.

In a simple embedded system, the layered architecture can be divided into this layers:

32

Software layered architecture

Microcontroller HW

DIO UART I2C SPI

External EEPROM WiFi module On/Off Device

Application

MCAL Abstraction

HW Abstraction

Page 33: Embedded C - Lecture 4

MCAL: Micro-Controller Abstraction layer which contains the different device drivers for controller hardware parts.

HW/AL: Hardware Abstraction Layer which abstracts the external connected hardware, it uses the APIs provided by the MCAL layer to control the external connected hardware.

Application layer: which contains the logic of the whole application using the APIs provided by the HW/AL.

Layered architecture has a lot of advantages like:

• code reusability: We can reuse all different modules in other projects without major changes.

• code portability: We can change the used micro controller and only change the MCAL layer.

• code maintainability: Maintaining code will be easier as only one or few modules will get affected with code maintenance.

but it also has its draw back of execution speed, design complexity. 33

Software layered architecture

Page 34: Embedded C - Lecture 4

Layered architecture goals are achieved through making each module concerned with a specific part (ex. UART driver) and provide some APIs to be used by higher layers (ex. UART_send, UART_receive), so any need to change this module (ex. Changing the microcontroller) will not affect all modules but only a specific module (MCAL in our example including UART) and any new module will also provide the same APIs (In our example UART_send, UART_receive and other MCAL APIs) to be used inside the same project without changing higher layers code.

This means that higher layers use the provided APIs to call lower layers, but what if a lower layer want to call the higher layers ? This is achieved by the call back function concept.

34

Software layered architecture

Page 35: Embedded C - Lecture 4

Call back function

It is using a pointer to function to be used by the lower layers to call higher layers.

Example: if the Timer driver wants to know how to notify higher layers when the timer fires, then the initialization function of the timer would be something like that:

void timer_Init(uint32 time_ms,void (*callBack_ptr)(void));

Input parameters are:

time_ms: required time to make the timer fire after.

callBack_ptr: the required function to be called after timer fire.

So when timer_Init function is called by higher layers, it will store the pointer to function provided by higher layer, then when timer fires it will use the pointer to function to notify the higher layer by calling this function.

35

Software layered architecture

Page 36: Embedded C - Lecture 4

MISRA rules

36

Page 37: Embedded C - Lecture 4

MISRA

The Motor Industry Software Reliability Association (MISRA) is an organization that produces guidelines for the software developed for electronic components used in the automotive industry.[1] It is a collaboration between vehicle manufacturers, component suppliers and engineering consultancies.

MISRA C

MISRA C is a set of software development guidelines for the C programming language developed by MISRA.

Its aims are to facilitate code safety, portability and reliability in the context of embedded systems, specifically those systems programmed in ISO C. There is also a set of guidelines for MISRA C++.

MISRA has evolved as a widely accepted model for best practices by leading developers in sectors including aerospace, telecom, medical devices, defense, railway, and others. 37

MISRA rules

Page 38: Embedded C - Lecture 4

Rule classification

Rules are divided into 2 main types:

• Required rules: These are mandatory requirements placed on the programmer. There are 93 ‘required’ rules. C code which is claimed to conform to MISRA rules shall comply with every required rule (with formal deviations required).

• Advisory rules: These are requirements placed on the programmer that should normally be followed. However they do not have the mandatory status of required rules. There are 34 ‘advisory’ rules. Note that the status of ‘advisory’ does not mean that these items can be ignored, but that they should be followed as far as is reasonably practical. Formal deviations are not necessary or advisory rules, but may be raised if it is considered appropriate.

38

MISRA rules

Page 39: Embedded C - Lecture 4

Rules examples

Rule 1 (required): All code shall conform to ISO 9899 standard C, with no

extensions permitted.

Rule 10 (advisory): Sections of code should not be ‘commented out’.

Rule 13 (advisory): The basic types of char, int, short, long, float and double should not be used, but specific-length equivalents should be typedef’d for the specific compiler, and these type names used in the code.

Rule 14 (required): The type char shall always be declared as unsigned char or signed char.

39

MISRA rules

Page 40: Embedded C - Lecture 4

Rules examples

Rule 20 (required): All object and function identifiers shall be declared before use.

Rule 21 (required): Identifiers in an inner scope shall not use the same name as an identifier in an outer scope, and therefore hide that identifier.

Rule 22 (advisory): Declarations of objects should be at function scope unless a wider scope is necessary.

Rule 23 (advisory): All declarations at file scope should be static where possible.

Rule 25 (required): An identifier with external linkage shall have exactly one external definition.

40

MISRA rules

Page 41: Embedded C - Lecture 4

Rules examples

Rule 50 (required): Floating point variables shall not be tested for exact equality or inequality.

Rule 52 (required): There shall be no unreachable code.

Rule 56 (required): The goto statement shall not be used.

Rule 57 (required): The continue statement shall not be used.

Rule 58 (required): The break statement shall not be used (except to terminate the cases of a switch statement).

Rule 59 (required): The statements forming the body of an if, else if, else, while, do ... while or for statement shall always be enclosed in braces.

41

MISRA rules

Page 42: Embedded C - Lecture 4

Rules examples

Rule 28 (advisory): The register storage class specifier should not be used.

Rule 30 (required): All automatic variables shall have been assigned a value before being used.

Rule 32 (required): In an enumerator list, the ‘=’ construct shall not be used to explicitly initialise members other than the first, unless all items are explicitly initialised.

Rule 37 (required): Bitwise operations shall not be performed on signed integer types.

Rule 43 (required): Implicit conversions which may result in a loss of information shall not be used.

42

MISRA rules

Page 43: Embedded C - Lecture 4

Rules examples

Rule 60 (advisory): All if, else if constructs should contain a final else clause.

Rule 61 (required): Every non-empty case clause in a switch statement shall be terminated with a break statement.

Rule 62 (required): All switch statements should contain a final default clause.

Rule 63 (advisory): A switch expression should not represent a Boolean value.

Rule 64 (required): Every switch statement shall have at least one case.

Rule 70 (required): Functions shall not call themselves, either directly or indirectly.

Rule 82 (advisory): A function should have a single point of exit. 43

MISRA rules

Page 44: Embedded C - Lecture 4

Task 4

44

Page 45: Embedded C - Lecture 4

Mohamed AbdAllah Embedded Systems Engineer [email protected]

45