Download presentation
Presentation is loading. Please wait.
1
Embedded ‘C’ for Zynq Cristian Sisterna
Universidad Nacional de San Juan Argentina Embedded C ICTP -IAEA
2
Embedded C Embedded C ICTP -IAEA 2 2
3
Difference Between C and Embedded C
Embedded systems programming is different from developing applications on a desktop computers. Key characteristics of an embedded system, when compared to PCs, are as follows: Embedded devices have resource constraints(limited ROM, limited RAM, limited stack space, less processing power) Components used in embedded system and PCs are different; embedded systems typically uses smaller, less power consuming components Embedded systems are more tied to the hardware Two salient features of Embedded Programming are code speed and code size. Code speed is governed by the processing power, timing constraints, whereas code size is governed by available program memory and use of programming language. Assembly language maps mnemonic words with the binary machine codes that the processor uses to code the instructions. Assembly language seems to be an obvious choice for programming embedded devices. However, use of assembly language is restricted to developing efficient codes in terms of size and speed. Also, assembly codes lead to higher software development costs and code portability is not there. Developing small codes are not much of a problem, but large programs/projects become increasingly difficult to manage in assembly language. Finding good assembly programmers has also become difficult nowadays. Hence high level languages are preferred for embedded systems programming. Embedded C ICTP -IAEA 3 3
4
Difference Between C and Embedded C
Though C and Embedded C appear different and are used in different contexts, they have more similarities than the differences. Most of the constructs are same; the difference lies in their applications. C is used for desktop computers, while Embedded C is for microcontroller based applications. Compilers for C (ANSI C) typically generate OS dependent executables. Embedded C requires compilers to create files to be downloaded to the microcontrollers/microprocessors where it needs to run. Embedded compilers give access to all resources which is not provided in compilers for desktop computer applications. Though C and embedded C appear different and are used in different contexts, they have more similarities than the differences. Most of the constructs are same; the difference lies in their applications. C is used for desktop computers, while embedded C is for microcontroller based applications. Accordingly, C has the luxury to use resources of a desktop PC like memory, OS, etc. While programming on desktop systems, we need not bother about memory. However, embedded C has to use with the limited resources (RAM, ROM, I/Os) on an embedded processor. Thus, program code must fit into the available program memory. If code exceeds the limit, the system is likely to crash. Compilers for C (ANSI C) typically generate OS dependant executables. Embedded C requires compilers to create files to be downloaded to the microcontrollers/microprocessors where it needs to run. Embedded compilers give access to all resources which is not provided in compilers for desktop computer applications. Embedded systems often have the real-time constraints, which is usually not there with desktop computer applications. Embedded systems often do not have a console, which is available in case of desktop applications. So, what basically is different while programming with embedded C is the mindset; for embedded applications, we need to optimally use the resources, make the program code efficient, and satisfy real time constraints, if any. All this is done using the basic constructs, syntaxes, and function libraries of ‘C’. Embedded systems often have the real-time constraints, which is usually not there with desktop computer applications. Embedded systems often do not have a console, which is available in case of desktop applications. Embedded C ICTP -IAEA 4 4
5
Advantages of Using Embedded C
It is small and reasonably simpler to learn, understand, program and debug C Compilers are available for almost all embedded devices in use today, and there is a large pool of experienced C programmers Unlike assembly, C has advantage of processor-independence and is not specific to any particular microprocessor/ microcontroller or any system. This makes it convenient for a user to develop programs that can run on most of the systems As C combines functionality of assembly language and features of high level languages, C is treated as a ‘middle-level computer language’ or ‘high level assembly language’ It is fairly efficient It supports access to I/O and provides ease of management of large embedded projects Objected oriented language, C++ is not apt for developing efficient programs in resource constrained environments like embedded devices. Many of these advantages are offered by other languages also, but what sets C apart from others like Pascal, FORTRAN, etc. is the fact that it is a middle level language; it provides direct hardware control without sacrificing benefits of high level languages. Compared to other high level languages, C offers more flexibility because C is relatively small, structured language; it supports low-level bit-wise data manipulation. Compared to assembly language, C Code written is more reliable and scalable, more portable between different platforms (with some changes). Moreover, programs developed in C are much easier to understand, maintain and debug. Also, as they can be developed more quickly, codes written in C offers better productivity. C is based on the philosophy ‘programmers know what they are doing’; only the intentions are to be stated explicitly. It is easier to write good code in C & convert it to an efficient assembly code (using high quality compilers) rather than writing an efficient code in assembly itself. Benefits of assembly language programming over C are negligible when we compare the ease with which C programs are developed by programmers. Objected oriented language, C++ is not apt for developing efficient programs in resource constrained environments like embedded devices. Virtual functions & exception handling of C++ are some specific features that are not efficient in terms of space and speed in embedded systems. Sometimes C++ is used only with very few features, very much as C. Java is another language used for embedded systems programming. It primarily finds usage in high-end mobile phones as it offers portability across systems and is also useful for browsing applications. Java programs require Java Virtual Machine (JVM), which consume lot of resources. Hence it is not used for smaller embedded devices. Embedded C ICTP -IAEA 5 5
6
Reviewing Embedded ‘C’ Basic Concepts
ICTP -IAEA 6
7
Basic Data Types Type Size Unsigned Range Signed Range
char 8 bits 0 to 255 –128 to 127 short int int 16 bits 0 to 65535 –32768 to 32767 long Int 32 bits 0 to – to typedef unsigned char UINT8; typedef signed char SINT8; typedef unsigned int UINT16; typedef int SINT16; typedef unsigned long int UINT32; typedef long int SINT32; Easily the greatest savings in code size and execution time can be made by choosing the most appropriate data type for variables. This is particularly true for 8-bit microcontrollers where the natural internal data size is 8-bits (one byte) whereas the C preferred data type is ‘int’. The ANSI standard does not precisely define the size of its native types, but compilers for 8-bit microcontroller’s usually implement ‘int’ as a signed 16-bit value. As 8-bit microcontrollers can process 8-bit data types more efficiently than 16-bit types, ‘int’ and larger data types should only be used where required by the size of data to be represented. Double precision and floating point operations are particularly inefficient and should be avoided wherever efficiency is important. This may seemobvious, but it is often overlooked and has a huge impact on code size and execution time. --- As well as the magnitude of the required data type, the signedness must also be specified. The ANSI standard for C specifies ‘int’ to be signed by default, but the of ‘char’ is not defined and may vary between compilers. Thus to create portable code, the data type ‘char’ should not be used at all. Instead the signedness should be defined explicitly: ‘unsigned char’or ‘signed char’. It is good practice to create type definitions for these data types in a header file which is then included in every other file. It isalso worthwhile to create type definitions for all the other data types which are used as well, for consistency, and to allow for portabilitybetween compilers. Something like the following may be used: AN2093-Freescale Embedded C ICTP -IAEA 7 7
8
‘SDK’ Basic Data Types xbasic_types.h xil_types.h 8 Embedded C
ICTP -IAEA 8
9
Local vs Global Variables
Variables in C can be classified by their scope Local Variables Global Variables Accesible only by the function within which they are declared and are allocated storage on the stack Accesible by any part of the program and are allocated permanent storage in RAM The ‘static’ access modifier causes that the local variable to be permanently allocated storage in memory, like a global variable Returning a pointer to a GLOBAL or STATIC variable is quite safe Variables can be classified by their scope. Global variables are accessible by any part of the program and are allocated permanent storage in RAM. Local variables are accessible only by the function within which they are declared and are allocated storage on the stack. Local variables therefore only occupy RAM while the function to which they belong is running. Their absolute address cannot be determined when the code is compiled and linked so they are allocated memory relative to the stack pointer. To access local variables the compiler may use the stack pointer addressing mode. This addressing mode requires one extra byte and one extra cycle to access a variable compared to the same instruction in indexed addressing mode. If the code requires several consecutive accesses to local variables, the compiler will usually transfer the stack pointer to the 16-bit index register and use indexed addressing instead. Conversely, GLOBAL and STATIC variables are stored in the main data memory area, so their address stays constant for the duration of the program module, so returning a pointer to a GLOBAL or STATIC variable is quite safe. The 'static' access modifier may be used with local variables. This causes the local variable to be permanently allocated storage in memory, like a global variable, so the variable's value is preserved between function calls. However the static local is still only accessible by the function within which it is declared. Global variables are allocated permanent storage in memory at an absolute address determined when the code is linked. The memory occupied by a global variable cannot be reused by any other variable. Global variables are not protected in any way, so any part of the program can access a global variable at any time. This gives rise to the issue of data consistency for global variables of more than a single byte in size. This means that the variable data could be corrupted if part of the variable is derived from one value and the rest of the variable is derived from another value. Inconsistent data arises when a global variable is accessed (read or written) by one part of the program and before every byte of the variable has been accessed the program is interrupted. This may be due to a hardware interrupt for example, or an operating system, if one is used. If the global variable is then accessed by the interrupting routine then inconsistent data may result. This must be avoided if reliable program execution is desired and this is often achieved by disabling interrupts while accessing global variables. The 'static' access modifier may also be used with global variables. This gives some degree of protection to the variable as it restricts access to the variable to those functions in the file in which the variable is declared. The compiler will generally use the extended addressing mode to access global variables or indexed addressing mode if they are accessed though a pointer. The use of global variables does not generally result in significantly more efficient code than local variables. There are some limited exceptions to this generalisation, one being when the global variable is located in the direct page. However, use of global variables prevents a function from being recursive or reentrant, and often does not make the most efficient use of RAM, which is a limited resource on most microcontrollers. The programmer must therefore make a careful choice in deciding which variables, if any, to make global in scope. Worthwhile gains in efficiency can sometimes be obtained by making just a few of the most intensively used variables global in scope, particularly if these variables are located in the direct page. Summary: • Careful analysis is required when deciding which variables to make global in scope Embedded C ICTP -IAEA 9 9
10
Local Variables The ‘static’ access modifier causes that the local variable to be permanently allocated storage in memory, like a global variable, so the value is preserved between function calls (but still is local) Local variables only occupy RAM while the function to which they belong is running Usually the stack pointer addressing mode is used (This addressing mode requires one extra byte and one extra cycle to access a variable compared to the same instruction in indexed addressing mode) If the code requires several consecutive accesses to local variables, the compiler will usually transfer the stack pointer to the 16-bit index register and use indexed addressing instead Variables can be classified by their scope. Global variables are accessible by any part of the program and are allocated permanent storage in RAM. Local variables are accessible only by the function within which they are declared and are allocated storage on the stack. Local variables therefore only occupy RAM while the function to which they belong is running. Their absolute address cannot be determined when the code is compiled and linked so they are allocated memory relative to the stack pointer. To access local variables the compiler may use the stack pointer addressing mode. This addressing mode requires one extra byte and one extra cycle to access a variable compared to the same instruction in indexed addressing mode. If the code requires several consecutive accesses to local variables, the compiler will usually transfer the stack pointer to the 16-bit index register and use indexed addressing instead. The 'static' access modifier may be used with local variables. This causes the local variable to be permanently allocated storage in memory, like a global variable, so the variable's value is preserved between function calls. However the static local is still only accessible by the function within which it is declared. Global variables are allocated permanent storage in memory at an absolute address determined when the code is linked. The memory occupied by a global variable cannot be reused by any other variable. Global variables are not protected in any way, so any part of the program can access a global variable at any time. This gives rise to the issue of data consistency for global variables of more than a single byte in size. This means that the variable data could be corrupted if part of the variable is derived from one value and the rest of the variable is derived from another value. Inconsistent data arises when a global variable is accessed (read or written) by one part of the program and before every byte of the variable has been accessed the program is interrupted. This may be due to a hardware interrupt for example, or an operating system, if one is used. If the global variable is then accessed by the interrupting routine then inconsistent data may result. This must be avoided if reliable program execution is desired and this is often achieved by disabling interrupts while accessing global variables. The 'static' access modifier may also be used with global variables. This gives some degree of protection to the variable as it restricts access to the variable to those functions in the file in which the variable is declared. The compiler will generally use the extended addressing mode to access global variables or indexed addressing mode if they are accessed though a pointer. The use of global variables does not generally result in significantly more efficient code than local variables. There are some limited exceptions to this generalisation, one being when the global variable is located in the direct page. However, use of global variables prevents a function from being recursive or reentrant, and often does not make the most efficient use of RAM, which is a limited resource on most microcontrollers. The programmer must therefore make a careful choice in deciding which variables, if any, to make global in scope. Worthwhile gains in efficiency can sometimes be obtained by making just a few of the most intensively used variables global in scope, particularly if these variables are located in the direct page. Summary: • Careful analysis is required when deciding which variables to make global in scope Embedded C ICTP -IAEA 10 10
11
Global Variables Global variables are allocated permanent storage in memory at an absolute address determined when the code is linked The memory occupied by a global variable cannot be reused by any other variable Global variables are not protected in any way, so any part of the program can access a global variable at any time This means that the variable data could be corrupted if part of the variable is derived from one value and the rest of the variable is derived from another value The 'static' access modifier may also be used with global variables This gives some degree of protection to the variable as it restricts access to the variable to those functions in the file in which the variable is declared The compiler will generally use the extended addressing mode to access global variables or indexed addressing mode if they are accessed though a pointer Variables can be classified by their scope. Global variables are accessible by any part of the program and are allocated permanent storage in RAM. Local variables are accessible only by the function within which they are declared and are allocated storage on the stack. Local variables therefore only occupy RAM while the function to which they belong is running. Their absolute address cannot be determined when the code is compiled and linked so they are allocated memory relative to the stack pointer. To access local variables the compiler may use the stack pointer addressing mode. This addressing mode requires one extra byte and one extra cycle to access a variable compared to the same instruction in indexed addressing mode. If the code requires several consecutive accesses to local variables, the compiler will usually transfer the stack pointer to the 16-bit index register and use indexed addressing instead. The 'static' access modifier may be used with local variables. This causes the local variable to be permanently allocated storage in memory, like a global variable, so the variable's value is preserved between function calls. However the static local is still only accessible by the function within which it is declared. Global variables are allocated permanent storage in memory at an absolute address determined when the code is linked. The memory occupied by a global variable cannot be reused by any other variable. Global variables are not protected in any way, so any part of the program can access a global variable at any time. This gives rise to the issue of data consistency for global variables of more than a single byte in size. This means that the variable data could be corrupted if part of the variable is derived from one value and the rest of the variable is derived from another value. Inconsistent data arises when a global variable is accessed (read or written) by one part of the program and before every byte of the variable has been accessed the program is interrupted. This may be due to a hardware interrupt for example, or an operating system, if one is used. If the global variable is then accessed by the interrupting routine then inconsistent data may result. This must be avoided if reliable program execution is desired and this is often achieved by disabling interrupts while accessing global variables. The 'static' access modifier may also be used with global variables. This gives some degree of protection to the variable as it restricts access to the variable to those functions in the file in which the variable is declared. The compiler will generally use the extended addressing mode to access global variables or indexed addressing mode if they are accessed though a pointer. The use of global variables does not generally result in significantly more efficient code than local variables. There are some limited exceptions to this generalisation, one being when the global variable is located in the direct page. However, use of global variables prevents a function from being recursive or reentrant, and often does not make the most efficient use of RAM, which is a limited resource on most microcontrollers. The programmer must therefore make a careful choice in deciding which variables, if any, to make global in scope. Worthwhile gains in efficiency can sometimes be obtained by making just a few of the most intensively used variables global in scope, particularly if these variables are located in the direct page. Summary: • Careful analysis is required when deciding which variables to make global in scope The use of global variables does not generally result in significantly more efficient code than local variables The programmer must therefore make a careful choice in deciding which variables, if any, to make global in scope Embedded C ICTP -IAEA 11 11
12
Other Application for the ‘static’ modifier
By default, all functions and variables declared in global space have external linkage and are visible to the entire program. Sometimes you require global variables or functions that have internal linkage: they should be visible within a single compilation unit, but not outside. Use the static keyword to restrict the scope of variables. Embedded C Embedded C ICTP -IAEA 12
13
Volatile Variable The value of volatile variables may change from outside the program. For example, you may wish to read an A/D converter or a port whose value is changing. Often your compiler may eliminate code to read the port as part of the compiler's code optimization process if it does not realize that some outside process is changing the port's value. You can avoid this by declaring the variable volatile. Without "volatile", the first write may be optimized out Embedded C ICTP -IAEA 13
14
Volatile Variable = 0; n code 1, compiler will optimize the code it will ignore the else part in final executable, because the variable “x” will never ever become other than 0. In code 2, the compiler will never optimized the code (else part) because, by declaring x as volatile compiler comes to know that this variable can change at any point of time. So compiler does not ignore the else part and consider it in final executable. Embedded C ICTP -IAEA 14 14
15
A function data type defines the value that a subroutine can return
Functions Data Types A function data type defines the value that a subroutine can return A function of type int returns a signed integer value Without a specific return type, any function returns an int To avoid confusion, you should always declare main()with return type void Embedded C ICTP -IAEA 15
16
Parameters Data Types Indicate the values to be passed into the function and the memory to be reserved for storing them Embedded C ICTP -IAEA 16
17
Structures Embedded C ICTP -IAEA 17
18
In ‘C’, the pointer data type corresponds to a MEMORY ADDRESS
Review of ‘C’ Pointer In ‘C’, the pointer data type corresponds to a MEMORY ADDRESS a int x = 1, y = 5, z = 8, *ptr; b ptr = &x; // ptr gets (point to) address of x c y = *ptr; // content of y gets content pointed by ptr d *ptr = z; // content pointed by ptr gets content of z a b c d x 1 ptr 1 1 ptr 8 ptr 9.5.1 Review of C pointer In C, the pointer data type corresponds to a memory address. The concept of a pointer can be explained by a simple code segment: int x=l, y=5, z=8, *ptr; p t r = &x; // ptr gets address of x y = *ptr; // content of y gets content pointed by ptr *ptr = z; // content pointed by ptr gets content of z In C, a non-pointer variable can be thought as an abstract memory location identified by the name of the variable and a value is stored to the location in an assignment. A pointer variable is designated with *, as in i n t *ptr, which indicates that p t r is a pointer (i.e., a memory address) and it points to a location with the int data type. A snapshot after the initial declaration and assignment of this segment is shown in Figure 9.15(a). We use an arrow to indicate that p t r is a pointer variable. It is pointed to nowhere (i.e., null) since it is unassigned initially. Two unary operators, & and *, are associated with pointer operations. The & operator returns the address of a variable and is known as the address-of operator. For example, in statement p t r = &x, &x returns the address of x, which is then assigned to ptr. The result is shown in Figure 9.15(b). The * operator returns the content pointed by the pointer and is known as the dereference operator. For example, in statement y = *ptr, content pointed by p t r is assigned to y and in statement *ptr = z, the value of z is stored to the location pointed by p t r . The graphical representations are shown in Figure 9.15(c) and (d). The value of a pointer variable is usually manipulated implicitly, as illustrated by the previous segment. The actual value of p t r is system dependent. In a desktop programming environment, we usually do not and need not know the explicit value. y 5 5 1 1 z 8 8 8 8 ptr = ?? ptr = &x y = *ptr *ptr = z Embedded C ICTP -IAEA 18 18
19
‘C’ Techniques for low-level I/O Operations
Because an embedded program interacts with low-level I/O devices, it frequently needs to manipulate a bit or a field of a data object. We briefly examine some relevant techniques in following subsections. Embedded C ICTP -IAEA 19 19
20
Bit Manipulation in ‘C’
Bitwise operators in ‘C’: ~ (not), & (and), | (or), ^ (xor) which operate on one or two operands at bit levels u8 mask = 0x60; //0110_0000 mask bits 6 and 5 u8 data = 0xb3 //1011_0011 data u8 d0, d1, d2, d3; //data to work with in the coming example . . . d0 = data & mask; // 0010_0000; isolate bits 6 and 5 from data d1 = data & ~mask; Bit manipulation C has several bitwise operators, including " (not), & (and), I (or), and ~ (xor), which operate on one or two operands at bit levels. The " operator inverts all individual bits. For example, if d is 0xb3 (i.e., ), ~d becomes 0x8c (i.e., ). The statement max = ~0 inverts all bits from 0's to l's and max becomes the all-one pattern, which corresponds to the largest number in any unsigned data type. The &, I, and ~ operators can be used to manipulate a bit or a group of bits in a data object. The operation involves a data operand and a mask operand, which specifies the bits to be modified. The operations are shown in the following C segment: In the example, we assume that d is an 8-bit data and bits 6 and 5 represent a special 2-bit field. The mask variable identifies this field by asserting bits 6 and 5. We can isolate this field from d (i.e., clear all other bits to 0) by applying the and operation with the mask, as in d&mask. Conversely, we can clear this field and keep the remaining bits intact by using the inverted mask, as in d&~mask. Similarly, we can set this field to 11 and keep the remaining bits intact by applying the or operation with the mask, as in d | mask. The toggle operation is based on the observation that for any 1-bit Boolean variable x, x XOR 0 = x and x XOR 1 = x'. We can toggle the desired field by applying the xor operation with the mask, as in d ^ mask (XOR) // 1001_0011; clear bits 6 and 5 of data d2 = data | mask; // 1111_0011; set bits 6 and 5 of data d3 = data ^ mask; // 1101_0011; toggle bits 6 and 5 of data Embedded C ICTP -IAEA 20 20
21
Both operands of a bit shift operator must be integer values
Bit Shift Operators Both operands of a bit shift operator must be integer values The right shift operator shifts the data right by the specified number of positions. Bits shifted out the right side disappear. With unsigned integer values, 0s are shifted in at the high end, as necessary. For signed types, the values shifted in is implementation-dependant. The binary number is shifted right by number bits. x >> number; The left shift operator shifts the data right by the specified number of positions. Bits shifted out the left side disappear and new bits coming in are 0s. The binary number is shifted left by number bits x << number; Embedded C ICTP -IAEA 21
22
Bit Shift Example void led_knight_rider(XGpio *pLED_GPIO, int nNumberOfTimes) { int i=0; int j=0; u8 uchLedStatus=0; // Blink the LEDs back and forth nNumberOfTimes for(i=0;i<nNumberOfTimes;i++) for(j=0;j<8;j++) // Scroll the LEDs up uchLedStatus = 1 << j; XGpio_DiscreteWrite(pLED_GPIO, 1, uchLedStatus); delay(ABOUT_ONE_SECOND / 15); } for(j=0;j<8;j++) // Scroll the LEDs down uchLedStatus = 8 >> j;
23
Unpacking Data There are cases that in the same memory address different fields are stored Example: let’s assume that a 32-bit memory address contains a 16-bit field for an integer data and two 8-bit fields for two characters io_rd_data num ch1 ch0 u32 io_rd_data; int num; char chl, ch0; To save address space, an I/O register frequently contains multiple fields. These fields are extracted and separated (i.e., unpacked) after an application program reads the I/O register. Conversely, these fields needed to be packed into one object when they are written to the I/O register. The unpacking and packing processes can be done by using the bitwise manipulation and shift operation. For example, assume that a 32-bit I/O register contains a 16-bit field (for an integer) and two 8-bit fields (for two characters), as shown in Figure The code segment to unpack a retrieved I/O word is: We first apply an and mask, such as OxffffOOOO, to clear the the irrelevant bits, and then shift a proper amount to remove trailing O's. In this process, the interpretation of a field changes from "a collection of bits" to a specific data type, such as i n t or char. It is good practice to use type casting to indicate the change of interpretation and data type of the extracted field. io_rd_data = my_iord(...); (int) ((io_rd_data & 0xffff0000) >> 16); Unpacking num = chl = (char)((io_rd_data & 0x0000ff00) >> 8); ch0 = (char)((io_rd_data & 0x000000ff )); Embedded C ICTP -IAEA 23 23
24
Packing Data There are cases that in the same memory address different fields are written Example: let’s assume that a 32-bit memory address will be written as a 16-bit field for an integer data and two 8-bit fields for two characters num ch1 io_wr_data ch0 u32 wr_data; int num = 5; char chl, ch0; The code segment to pack three fields to an I/O word reverses the previous operation: The first statement puts num between bit 15 and bit 0. The second statement first shifts num to the left by 8 bits, which makes the 8 LSBs all O's, and then uses the bitwise or operation to fill the 8 LSBs with the value of chl. The same process is repeated to append the chO field. Again, proper type casting should be used in the process. wr_data = (u32) (num); //num[15:0] wr_data = (wr_data << 8) | (u32) ch1; //num[23:8],ch1[7:0] Packing wr_data = (wr_data << 8) | (u32) ch0; //num[31:16],ch1[15:8] my_iowr( , wr_data) ; //ch0[7:0] Embedded C ICTP -IAEA 24 24
25
I/O Read Macro Read from an Input int switch_s1; . . .
switch_s1 = *(volatile int *)(0x ); #define SWITCH_S1_BASE = 0x ; . . switch_s1 = *(volatile int *)(SWITCH_S1_BASE); In the flashing-LED system, an I/O register is assigned with a memory address, which can be thought as a value of a pointer. Unlike a normal desktop program discussed in the previous subsection, we know the explicit value of the address and must use this value to access the register. Recall that the base addresses of the switch and led modules are 0x and 0x For example, we can read the value from the switch module and write a pattern to the led module: int sw; char pattern=0x01; sw = *(0x ); *(0x ) = pattern; The statements are primitive and difficult to comprehend. Several improvements can be made. Let us consider the read statement. First, we can add a type cast, ( v o l a t i l e int *), to describe the nature of this value: sw = * ( v o l a t i l e int *) (0x ); The i n t * portion indicates that the constant value is a pointer that points to an object with the i n t data type. The keyword v o l a t i l e gives the compiler the hint that the value of the object may be modified without processor interaction and thus certain optimizations should not be performed. Second, we can define a symbolic constant to replace the hard literal: «define SWITCH.BASE 0x sw = * ( v o l a t i l e int *) SWITCH_BASE; To maintain modularity and enhance readability, we can define a macro to encapsulate the type casting and dereference operations. A macro to read an I/O register can be defined as «define SWITCH_BASE 0x «define demo_iord(addr) ( » ( v o l a t i l e int *)(addr)) sw = demo_iord(SWITCH_BASE) ; #define SWITCH_S1_BASE = 0x ; #define my_iord(addr) (*(volatile int *)(addr)) . . . Macro switch_s1 = my_iord(SWITCH_S1_BASE); // Embedded C ICTP -IAEA 25 25
26
I/O Write Macro Write to an Output char pattern = 0x01; . . .
*(0x ) = pattern; #define LED_L1_BASE = 0x ; . . . *(LED_L1_BASE) = pattern; Like the names indicate, the two macros are only for demonstration purposes. While this approach works most of the time, it suffers from several subtle problems. Altera provides three simple modules to assist the low-level I /O access and to make the code more robust. This alternative is discussed in the next section. #define LED_L1_BASE = 0x ; #define my_iowr(addr, data) (*(int *)(addr) = (data)) . . . Macro my_iowr(LED_L1_BASE, (int)pattern); // Embedded C ICTP -IAEA 26 26
27
Basic ‘C’ Program Template
Embedded C ICTP -IAEA 27
28
Basic Embedded Program Architecture
An embedded application consists of a collection tasks, implemented by hardware accelerators, software routines, or both. #include “nnnnn.h” #include <ppppp.h> main() { sys_init();// while(1){ task_1(); task_2(); . . . task_n(); } An embedded application consists of a collection tasks, implemented by hardware accelerators, software routines, or both. Unlike a normal desktop application, an embedded program may run continuously and does not terminate. The top-level program (main program) schedules, coordinates, and manages these tasks. The simplest control architecture is an infinite "super loop," in which the tasks are executed sequentially. The pseudo code for a super-loop architecture is The system runs the sys_init() function once to perform initialization and then enters the infinite loop and invokes the task functions in turn. A task function handles certain I/O activities. Some tasks may have timing constraints and must be processed within the given time limits. This scheme works properly if the overall loop execution time is small and the processor can respond to each task in a timely manner. Additional control architectures and scheduling issues are discussed in Section 12.3. Embedded C ICTP -IAEA 28 28
29
Basic Example The flashing-LED system turns on and off two LEDs alternatively according to the interval specified by the ten sliding switches Tasks ???? The flashing-LED system turns on and off two LEDs alternatively according to the interval specified by the ten sliding switches. The two main tasks are reading the interval value from the switches and toggling the two LEDs after a specific amount of time. The top-level program of this LED-flashing system is shown in Listing It follows the basic program architecture discussed in Section and consists of two major routines. reading the interval value from the switches toggling the two LEDs after a specific amount of time Embedded C ICTP -IAEA 29 29
30
Basic Example #include “nnnnn.h” #include “aaaaa.h” main() { while(1){
. . . task_1(); task_2(); } main() { int period; while(1){ read_sw(SWITCH_S1_BASE, &period); led_flash(LED_L1_BASE, period); } The sw_get_command_vO() function reads the value of the switch and the code is shown in Listing 9.3. Since the same functionality is repeated in the subsequent chapters with modified codes, the _v0 (for version 0) suffix is added. Embedded C ICTP -IAEA 30 30
31
Basic Example - Reading
/********************************************************************** * function: read_sw () * purpose: get flashing period from switches * argument: * sw-base: base address of switch PIO * period: pointer to period * return: * updated period * note : **********************************************************************/ void read_sw(u32 switch_base, int *period) { *period = my_iord(switch_base) & 0x000000ff; //read flashing period // from switch } The sw_get_command_vO() function reads the value of the switch and the code is shown in Listing 9.3. Since the same functionality is repeated in the subsequent chapters with modified codes, the _v0 (for version 0) suffix is added. Since the switch is 10 bits wide, we use a mask 0x000003ff to clear the unrelated bits to 0's. Since 8-bit data is the smallest unit in C, we use an 8-bit variable, led_pattern, to store the LED pattern. It is declared as a static variable so that its value can be kept between function calls. The two LSBs are toggled by xoring with the 0x03 mask when this function is executed. The delay is achieved by a dummy for loop. We assume that each loop iteration takes two instructions, each instruction takes 10 clock cycles in a Nios ÉÉ/e (economic) configuration, and the system clock is 50 MHz (i.e., 20-ns period). Each iteration will take 400 ns (2 * 10 * 20 ns) and it requires 2500 iterations for a 1-ms delay. Delay obtained by this method is just a rough estimation and is not very accurate. Note that we do not use any global variable or constant in the two task functions and module-dependent constants, such as LED JASE and SWITCH-BASE, are confined in main program. Thus, only a top-level program needs to be revised if an I/O module is modified. Embedded C ICTP -IAEA 31 31
32
Basic Example - Writing
/****************************************************************************************** * function: led.flash () * purpose: toggle 2 LEDs according to the given period * argument: * led-base: base address of discrete LED PIO * period: flashing period in ms * return : * note : * — The delay is done by estimating execution time of a dummy for loop * — Assumption: 400 ns per loop iteration (2500 iterations per ms) * - 2 instruct. per loop iteration /10 clock cycles per instruction /20ns per clock cycle(50-MHz clock) *******************************************************************************************/ void led_flash(u32 addr_led_base, int period) { static u8 led_pattern = 0x01; // initial pattern unsigned long i, itr; led_pattern ^= 0x03; // toggle 2 LEDs (2 LSBs) my_iowr(addr_led_base, led_pattern); // write LEDs itr = period * 2500; for (i=0; i<itr; i++) {} // dummy loop for delay } The sw_get_command_vO() function reads the value of the switch and the code is shown in Listing 9.3. Since the same functionality is repeated in the subsequent chapters with modified codes, the _v0 (for version 0) suffix is added. Since the switch is 10 bits wide, we use a mask 0x000003ff to clear the unrelated bits to 0's. Since 8-bit data is the smallest unit in C, we use an 8-bit variable, led_pattern, to store the LED pattern. It is declared as a static variable so that its value can be kept between function calls. The two LSBs are toggled by xoring with the 0x03 mask when this function is executed. The delay is achieved by a dummy for loop. We assume that each loop iteration takes two instructions, each instruction takes 10 clock cycles in a Nios ÉÉ/e (economic) configuration, and the system clock is 50 MHz (i.e., 20-ns period). Each iteration will take 400 ns (2 * 10 * 20 ns) and it requires 2500 iterations for a 1-ms delay. Delay obtained by this method is just a rough estimation and is not very accurate. Note that we do not use any global variable or constant in the two task functions and module-dependent constants, such as LED JASE and SWITCH-BASE, are confined in main program. Thus, only a top-level program needs to be revised if an I/O module is modified. Embedded C ICTP -IAEA 32 32
33
Basic Example – Read / Write
void read_sw(u32 switch_base, int *period) { *period = my_iord(switch_base) & 0x000003ff; } main() { int period; while(1){ read_sw(SWITCH_S1_BASE, &period); led_flash(LED_L1_BASE, period); } void led_flash(u32 addr_led_base, int period) { static u8 led_pattern = 0x01; unsigned long i, itr; led_pattern ^= 0x03; my_iowr(addr_led_base, led_pattern); itr = period * 2500; for (i=0; i<itr; i++) {} } The sw_get_command_vO() function reads the value of the switch and the code is shown in Listing 9.3. Since the same functionality is repeated in the subsequent chapters with modified codes, the _v0 (for version 0) suffix is added. Embedded C ICTP -IAEA 33 33
34
Read/Write From/To GPIO Inputs and Outputs
35
Steps for Reading from a GPIO
Create a GPIO instance Initialize the GPIO Set data direction Read the data Embedded C ICTP -IAEA 35
36
Steps for Reading from a GPIO – Step 1
Create a GPIO instance #include “xparameters.h” #include “xgpio.h” int main (void) { XGpio switches; XGpio leds; . . . The XGpio driver instance data. The user is required to allocate a variable of this type for every GPIO device in the system. A pointer to a variable of this type is then passed to the driver API functions. Embedded C ICTP -IAEA 36
37
Steps for Reading from a GPIO – Step 2
Initialize the GPIO (int) XGpio_Initialize(XGpio *InstancePtr, u16 DeviceID); InstancePtr: is a pointer to an XGpio instance. The memory the pointer references must be pre-allocated by the caller. Further calls to manipulate the component through the XGpio API must be made with this pointer. DeviceID: is the unique id of the device controlled by this XGpio component. Passing in a device ID associates the generic XGpio instance to a specific device, as chosen by the caller or application developer. * Initialize the XGpio instance provided by the caller based on the * given DeviceID. * * Nothing is done except to initialize the InstancePtr. is a pointer to an XGpio instance. The memory the *pointer references must be pre-allocated by the caller. Further *calls to manipulate the instance/driver through the XGpio API *must be made with this pointer. is the unique id of the device controlled by this XGpio *instance. Passing in a device id associates the generic XGpio *instance to a specific device, as chosen by the caller or *application developer. *- XST_SUCCESS if the initialization was successfull. * - XST_DEVICE_NOT_FOUND if the device configuration data was not *found for a device with the supplied device ID. @return - XST_SUCCESS if the initialization was successfull. - XST_DEVICE_NOT_FOUND if the device configuration data was not xstatus.h Embedded C ICTP -IAEA 37 37
38
Steps for Reading from a GPIO – Step 2(cont’)
(int) XGpio_Initialize(XGpio *InstancePtr, u16 DeviceID); // AXI GPIO switches initialization XGpio_Initialize (&switches, XPAR_BOARD_SW_8B_DEVICE_ID); // AXI GPIO leds initialization XGpio_Initialize (&led, XPAR_BOARD_LEDS_8B_DEVICE_ID); * Initialize the XGpio instance provided by the caller based on the * given DeviceID. * * Nothing is done except to initialize the InstancePtr. is a pointer to an XGpio instance. The memory the *pointer references must be pre-allocated by the caller. Further *calls to manipulate the instance/driver through the XGpio API *must be made with this pointer. is the unique id of the device controlled by this XGpio *instance. Passing in a device id associates the generic XGpio *instance to a specific device, as chosen by the caller or *application developer. *- XST_SUCCESS if the initialization was successfull. * - XST_DEVICE_NOT_FOUND if the device configuration data was not *found for a device with the supplied device ID. Embedded C ICTP -IAEA 38 38
39
xparameters.h The xparameters.h file contains the address map for peripherals in the created system This file is generated from the hardware platform description from Vivado Ctrl + Mouse Over xparameters.h file can be found underneath the include folder in the ps7_cortexa9_0 folder of the BSP main folder Embedded C ICTP -IAEA 39 39
40
xparameters.h 9.6.1 system, h We can determine the base addresses of each I /O device by examining the Base column of SOPC Builder and define them as constant, as described in the previous section. Since the base addresses are automatically assigned in SOPC Builder, we need to examine and update these addresses for each new Nios II system and any subsequent revision. This is a tedious and error-prone process. To solve the problem, the Nios II EDS framework automates this process. During the BSP library construction, the BSP Builder examines the .sopcinfo file, extracts information on each module, and creates a file, system. h, to record the information. Note that this file is listed under the ledl_bsp directory in Figure In system.h, each I /O device is identified by the symbolic name given in the Module Name column of SOPC Builder and suffixes are used to represent the corresponding properties. Note that the constants are always in uppercase in the file. The system.h file is regenerated automatically when the . s o p c i n f o file is updated and thus should not be edited manually. The base address of an instantiated module is specified as *_BASE in system.h, as in # define SWITCH_BASE 0x11000 After including this file, we don't need to define the base address manually Embedded C ICTP -IAEA 40 40
41
xgpio.h – Outline Pane 9.6.1 system, h
Definitions (#define statemens) Includes (#include statemens) Structures Declarations Types Definitions Functions 9.6.1 system, h We can determine the base addresses of each I /O device by examining the Base column of SOPC Builder and define them as constant, as described in the previous section. Since the base addresses are automatically assigned in SOPC Builder, we need to examine and update these addresses for each new Nios II system and any subsequent revision. This is a tedious and error-prone process. To solve the problem, the Nios II EDS framework automates this process. During the BSP library construction, the BSP Builder examines the .sopcinfo file, extracts information on each module, and creates a file, system. h, to record the information. Note that this file is listed under the ledl_bsp directory in Figure In system.h, each I /O device is identified by the symbolic name given in the Module Name column of SOPC Builder and suffixes are used to represent the corresponding properties. Note that the constants are always in uppercase in the file. The system.h file is regenerated automatically when the . s o p c i n f o file is updated and thus should not be edited manually. The base address of an instantiated module is specified as *_BASE in system.h, as in # define SWITCH_BASE 0x11000 After including this file, we don't need to define the base address manually Embedded C ICTP -IAEA 41 41
42
Steps for Reading from a GPIO - Step 3
Set data direction void XGpio_SetDataDirection (XGpio *InstancePtr, unsigned Channel, u32 DirectionMask); InstancePtr: is a pointer to an XGpio instance to be worked on. Channel: contains the channel of the XGpio (1 o 2) to operate with. * Set the input/output direction of all discrete signals for the specified * GPIO channel. * is a pointer to an XGpio instance to be worked on. contains the channel of the GPIO (1 or 2) to operate on. is a bitmask specifying which discretes are input *and which are output. Bits set to 0 are output and bits set to 1 *are input. hardware must be built for dual channels if this function *is used with any channel other than 1. If it is not, this *function will assert. DirectionMask: is a bitmask specifying which bits are inputs and which are outputs. Bits set to ‘0’ are output, bits set to ‘1’ are inputs. Return: none Embedded C ICTP -IAEA 42 42
43
Steps for Reading from a GPIO - Step 3 (cont’)
void XGpio_SetDataDirection (XGpio *InstancePtr, unsigned Channel, u32 DirectionMask); // AXI GPIO switches: bits direction configuration XGpio_SetDataDirection(&switches, 1, 0xffffffff); * Set the input/output direction of all discrete signals for the specified * GPIO channel. * is a pointer to an XGpio instance to be worked on. contains the channel of the GPIO (1 or 2) to operate on. is a bitmask specifying which discretes are input *and which are output. Bits set to 0 are output and bits set to 1 *are input. hardware must be built for dual channels if this function *is used with any channel other than 1. If it is not, this *function will assert. Embedded C ICTP -IAEA 43 43
44
Steps for Reading from a GPIO – Step 4
Read the data u32 XGpio_DiscreteRead (XGpio *InstancePtr, unsigned Channel); InstancePtr: is a pointer to an XGpio instance to be worked on. Channel: contains the channel of the XGpio (1 o 2) to operate with. Read state of discretes for the specified GPIO channnel. * is a pointer to an XGpio instance to be worked on. contains the channel of the GPIO (1 or 2) to operate on. copy of the discretes register. hardware must be built for dual channels if this function *is used with any channel other than 1. If it is not, this *function will assert. Return: read data Embedded C ICTP -IAEA 44 44
45
Steps for Reading from a GPIO – Step 4 (cont’)
u32 XGpio_DiscreteRead (XGpio *InstancePtr, unsigned Channel); // AXI GPIO: read data from the switches sw_check = XGpio_DiscreteRead(&switches, 1); Read state of discretes for the specified GPIO channnel. * is a pointer to an XGpio instance to be worked on. contains the channel of the GPIO (1 or 2) to operate on. copy of the discretes register. hardware must be built for dual channels if this function *is used with any channel other than 1. If it is not, this *function will assert. Embedded C ICTP -IAEA 45 45
46
Steps for Writing to GPIO
Create a GPIO instance Initialize the GPIO Read the data Embedded C ICTP -IAEA 46
47
Steps for Writing to a GPIO – Step 1
Create a GPIO instance #include “xgpio.h” int main (void) { XGpio switches; XGpio leds; . . . The XGpio driver instance data. The user is required to allocate a variable of this type for every GPIO device in the system. A pointer to a variable of this type is then passed to the driver API functions. Embedded C ICTP -IAEA 47
48
Steps for Writing to a GPIO – Step 2
Initialize the GPIO (int) XGpio_Initialize(XGpio *InstancePtr, u16 DeviceID); InstancePtr: is a pointer to an XGpio instance. The memory the pointer references must be pre-allocated by the caller. Further calls to manipulate the component through the XGpio API must be made with this pointer. DeviceID: is the unique id of the device controlled by this XGpio component. Passing in a device ID associates the generic XGpio instance to a specific device, as chosen by the caller or application developer. * Initialize the XGpio instance provided by the caller based on the * given DeviceID. * * Nothing is done except to initialize the InstancePtr. is a pointer to an XGpio instance. The memory the *pointer references must be pre-allocated by the caller. Further *calls to manipulate the instance/driver through the XGpio API *must be made with this pointer. is the unique id of the device controlled by this XGpio *instance. Passing in a device id associates the generic XGpio *instance to a specific device, as chosen by the caller or *application developer. *- XST_SUCCESS if the initialization was successfull. * - XST_DEVICE_NOT_FOUND if the device configuration data was not *found for a device with the supplied device ID. @return - XST_SUCCESS if the initialization was successfull. - XST_DEVICE_NOT_FOUND if the device configuration data was not xstatus.h Embedded C ICTP -IAEA 48 48
49
Steps for Writing to a GPIO – Step 2(cont’)
(int) XGpio_Initialize (XGpio *InstancePtr, u16 DeviceID); // AXI GPIO switches initialization XGpio_Initialize (&switches, XPAR_BOARD_SW_8B_DEVICE_ID); // AXI GPIO leds initialization XGpio_Initialize (&led, XPAR_BOARD_LEDS_8B_DEVICE_ID); * Initialize the XGpio instance provided by the caller based on the * given DeviceID. * * Nothing is done except to initialize the InstancePtr. is a pointer to an XGpio instance. The memory the *pointer references must be pre-allocated by the caller. Further *calls to manipulate the instance/driver through the XGpio API *must be made with this pointer. is the unique id of the device controlled by this XGpio *instance. Passing in a device id associates the generic XGpio *instance to a specific device, as chosen by the caller or *application developer. *- XST_SUCCESS if the initialization was successfull. * - XST_DEVICE_NOT_FOUND if the device configuration data was not *found for a device with the supplied device ID. Embedded C ICTP -IAEA 49 49
50
Steps for Writing to a GPIO – Step 3
Write the data void XGpio_DiscreteWrite (XGpio *InstancePtr, unsigned Channel, u32 Data); InstancePtr: is a pointer to an XGpio instance to be worked on. Channel: contains the channel of the XGpio (1 o 2) to operate with. Read state of discretes for the specified GPIO channnel. * is a pointer to an XGpio instance to be worked on. contains the channel of the GPIO (1 or 2) to operate on. copy of the discretes register. hardware must be built for dual channels if this function *is used with any channel other than 1. If it is not, this *function will assert. Data: Data is the value to be written to the discrete register Return: none Embedded C ICTP -IAEA 50 50
51
Steps for Writing to a GPIO – Step 3 (cont’)
void XGpio_DiscreteWrite (XGpio *InstancePtr, unsigned Channel, u32 Data); // AXI GPIO: read data from the switches sw_check = XGpio_DiscreteRead(&switches, 1); // AXI GPIO: write data (sw_check) to the LEDs XGpio_DiscreteWrite(&led, 1, sw_check); Read state of discretes for the specified GPIO channnel. * is a pointer to an XGpio instance to be worked on. contains the channel of the GPIO (1 or 2) to operate on. copy of the discretes register. hardware must be built for dual channels if this function *is used with any channel other than 1. If it is not, this *function will assert. Embedded C ICTP -IAEA 51 51
52
IP Drivers for Custom IP
Embedded C ICTP -IAEA 52
53
My VHDL Code for the Future IP
Address Decode & Write Enable AXI4-Lite IP Embedded C ICTP -IAEA 53
54
Custom IP Embedded C ICTP -IAEA 54
55
System Level Address Map
The comprehensive system level address map is shown in Table 4-1. The shaded entries indicate that the address range is reserved and should not be accessed. Table 4-2 identifies reserved address ranges. (UG585) Notes: 1. The other bus masters include the S_AXI_GP interfaces, Device configuration interface (DevC), DAP controller, DMA controller and the various controllers with local DMA units (Ethernet, USB and SDIO). 2. The OCM is divided into four 64 KB sections. Each section is mapped independently to either the low or high addresses ranges, but not both at the same time. In addition, the SCU can filter addresses destined for the OCM low address range to the DDR DRAM controller instead. A detailed discussion of the OCM is explained in Chapter 29, On-Chip Memory (OCM). 3. For each 64 KB section mapped to the high OCM address range via slcr.OCM_CFG[RAM_HI] which is not also part of the SCU address filtering range will be aliased for CPU and ACP masters at a range of (0x000C_0000 to 0x000F_FFFF). See Chapter 29, On-Chip Memory (OCM) for more information. 4. When a single device is used, it must be connected to QSPI 0. In this case, the address map starts at FC00_0000 and goes to a maximum of FCFF_FFFF (16 MBs). When two devices are used, both devices must be the same capacity. The address map for two devices depends on the size of the devices and their connection configuration. For the shared 4-bit stacked I/O bus, the QSPI 0 device starts at FC00_0000 and goes to a maximum of FCFF_FFFF (16 MBs). The QSPI 1 device starts at FD00_0000 and goes to a maximum of FDFF_FFFF (another 16 MBs). If the first device is less than 16 MBs in size, then there will be a memory space hole between the two devices. For the 8-bit dual parallel mode (8-bit bus), the memory map is continuous from FC00_0000 to a maximum of FDFF_FFFF (32 MBs). In the Zynq achoitecture, each piece of hardware in the Zynq has an address. So, from the point of view of the ARM as a Host, when it wants to access to different component in the Zynq system, each of this component has an address, and the ARM Host can use this address to access to this component. For the MGP1 and MGP0, that they are the AXI Master ports that we have in the border between the PS and the PL in the Zynq devices, for these ports we also have an address. The addresses are set, and they are detailed in the Zynq documentation. So, this table represent the address assigned to each piece of hardware in the Zynq device. PL AXI Interface Note There are two general purpose interconnect ports that go to the PL, M_AXI_GP{1,0}. Each port is addressable by masters in the PS and each port occupies 1 GB of system address space in the ranges specified in Table 4-1. The M_AXI_GP addresses are directly from the PS; they are not remapped on their way to the PL. The addresses outside of these ranges are not presented to the PL. The range addresses from 4000_0000 to 7FFF_FFFF are the address to be used to execute Rd/Wr transactions over the GP0 port. The range addresses from 8000_0000 to BFFF_FFFF are the address to be used to execute Rd/Wr transactions over the GP1 port. Embedded C UNSL - UNSJ 55
56
My IP – Memory Address Range
Embedded C ICTP -IAEA 56
57
Custom IP Drivers The driver code are generated automatically when the IP template is created. The driver includes higher level functions which can be called from the user application. The driver will implement the low level functionality used to control your peripheral. led_ip.c led_ip\ip_repo\led_ip_1.0\drivers\led_ip_v1_0\src LED_IP_mWriteReg(…) LED_IP_mReadReg(…) led_ip.h Embedded C ICTP -IAEA 57
58
Custom IP Drivers: *.c led_ip\ip_repo\led_ip_1.0\drivers\led_ip_v1_0\src\led_ip.c Embedded C ICTP -IAEA 58
59
Custom IP Drivers: *.h led_ip\ip_repo\led_ip_1.0\drivers\led_ip_v1_0\src\led_ip.h Embedded C ICTP -IAEA 59
60
Custom IP Drivers: *.h (cont’ 1)
led_ip\ip_repo\led_ip_1.0\drivers\led_ip_v1_0\src\led_ip.h Embedded C ICTP -IAEA 60
61
Custom IP Drivers: *.h (cont’ 2)
led_ip\ip_repo\led_ip_1.0\drivers\led_ip_v1_0\src\led_ip.h Embedded C ICTP -IAEA 61
62
Custom IP Drivers: *.h (cont’ 3)
led_ip\ip_repo\led_ip_1.0\drivers\led_ip_v1_0\src\led_ip.h Embedded C ICTP -IAEA 62
63
Custom IP Drivers: *.h (cont’ 4)
led_ip\ip_repo\led_ip_1.0\drivers\led_ip_v1_0\src\led_ip.h Embedded C ICTP -IAEA 63
64
‘C’ Code for Writing to My_IP
Embedded C ICTP -IAEA 64
65
IP Drivers – Xil_Out32/Xil_In32
#define LED_IP_mWriteReg(BaseAddress, RegOffset, Data) Xil_Out32((BaseAddress) + (RegOffset), (Xuint32)(Data)) #define LED_IP_mReadReg(BaseAddress, RegOffset) Xil_In32((BaseAddress) + (RegOffset)) For this driver, you can see the macros are aliases to the lower level functions Xil_Out32( ) and Xil_In32( ) The macros in this file make up the higher level API of the led_ip driver. If you are writing your own driver for your own IP, you will need to use low level functions like these to read and write from your IP as required. The low level hardware access functions are wrapped in your driver making it easier to use your IP in an Application project. Embedded C ICTP -IAEA 65
66
IP Drivers – Xil_In32 (xil_io.h/xil_io.c)
/*****************************************************************************/ /** * Performs an input operation for a 32-bit memory location by reading from the * specified address and returning the Value read from that address. * Addr contains the address to perform the input operation at. The Value read from the specified input address. None. ******************************************************************************/ u32 Xil_In32(INTPTR Addr) { return *(volatile u32 *) Addr; } Embedded C ICTP -IAEA 66
67
IP Drivers – Xil_Out32 (xil_io.h/xil_io.c)
/*****************************************************************************/ /** * Performs an output operation for a 32-bit memory location by writing the * specified Value to the the specified address. * Addr contains the address to perform the output operation at. Value contains the Value to be output at the specified address. None. None. ******************************************************************************/ void Xil_Out32(INTPTR Addr, u32 Value) { u32 *LocalAddr = (u32 *)Addr; *LocalAddr = Value; } Embedded C ICTP -IAEA 67
68
IP Drivers – SDK ‘Activation’
Select <project_name>_bsp in the project view pane. Right-click Select Board Support Package Settings Select Drivers on the Overview pane If the led_ip driver has not already been selected, select Generic under the Driver Column for led_ip to access the dropdown menu. From the dropdown menu, select led_ip, and click OK> Embedded C ICTP -IAEA 68
69
IP Drivers – SDK ‘Activation’ (cont’)
Embedded C ICTP -IAEA 69
70
Read and Write From/To Memory
Embedded C ICTP -IAEA 70
71
Note On Reading from / Writing to Memory
Generally speaking processors work on byte (8bit) address boundaries. If we wish to write byte-wide data values into the first four consecutive locations in a region of memory starting at "DDR_BASEADDR", we must write the first to DDR_BASEADDR + 0, the second to DDR_BASEADDR + 1, the third to DDR_BASEADDR + 2, and the last to DDR_BASEADDR + 3. However, if we wish to write four half-word wide (16 bit) data values to four memory addresses starting at the same location, we must write the first to DDR_BASEADDR + 0, the second to DDR_BASEADDR + 2, the third to DDR_BASEADDR + 4, and the last to DDR_BASEADDR + 6. When writing word wide (32 bit) data values, we must do so on 4 byte boundaries; 0x0, 0x4, 0x8, and 0xC. Embedded C ICTP -IAEA 71
72
Reading from / Writing to Memory: xil_io.h
Writing Functions Xil_Out8(memory_address, 8_bit_value); Xil_Out16(memory_address, 16_bit_value); Xil_Out32(memory_address, 32_bit_value); Reading Functions 8_bit_value = Xil_In8(memory_address); 16_bit_value = Xil_In16(memory_address); 32_bit_value = Xil_In32(memory_address); Embedded C ICTP -IAEA 72
73
Reading from / Writing to Memory: xil_io.h
int main(void) { int result1; // integers are 32 bits wide! int result2; // integers are 32 bits wide! Xil_Out8(XPAR_PS7_RAM_0_S_AXI_BASEADDR + 0, 0x12); Xil_Out8(XPAR_PS7_RAM_0_S_AXI_BASEADDR + 1, 0x34); Xil_Out8(XPAR_PS7_RAM_0_S_AXI_BASEADDR + 2, 0x56); Xil_Out8(XPAR_PS7_RAM_0_S_AXI_BASEADDR + 3, 0x78); result1 = Xil_In32(XPAR_PS7_RAM_0_S_AXI_BASEADDR); Xil_Out16(XPAR_PS7_RAM_0_S_AXI_BASEADDR + 4, 0x9876); Xil_Out16(XPAR_PS7_RAM_0_S_AXI_BASEADDR + 6, 0x5432); result2 = Xil_In32(XPAR_PS7_RAM_0_S_AXI_BASEADDR + 4); return(0); } #include "xparameters.h" #include "xil_io.h“ For this code example, predict the values of “result1” and “result2” after this software has been executed? Read the code carefully and consider the width of each read and write transaction. End of Exercise Challenge Write some software that will write eight data values, each one byte wide with the following values; 0xAB, 0xFF, 0x34, 0x8C, 0xEF, 0xBE, 0xAD, 0xDE. The values should be written to consecutive addresses, starting at the base address of the Zynq OCM region. Embedded C ICTP -IAEA 73 73
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.