ARM:Makefile编写、链接脚本编写、裸板shell框架

《Makefile编写、链接脚本编写、裸板shell框架》


' 工具:UtraEdit 代码编辑工具
// 此工具里面 Ctrl + h 查看ASCII码


vi中命令模式下:
':e main.c   // 打开main.c
':bn            // 回到刚才的.c文件

1. 关于 \r \n的问题
windows系统中:
\r    回到行首    0x0d
\n    到下一行    0x0a
linux系统中,只需要一个字符:\n
hexdump -C 1.txt    // 1.txt 里面敲回车

一、关于Makefile - // 查着用即可不用学
1) env\GNU make v3.80完整版中文指南.pdf
2) 跟我一起写Makefile(后补--->)
makefile主要分为三部分:
目标文件[target]:依赖文件[dependencies]
    [1个Tab]规则


'make与makefile 的作用
make工具用来管理大型软件的编译:
- 自动判断一个程序的哪些文件需要编译,并且能用命令来执行编译操作;
- 实现类似集成开发环境(IDE)自动化编译;
makefile定义了一系列的规则来指定:
- 哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译更复杂的功能操作。
- makefile中可以执行操作系统的命令


现阶段达标:
可以写最简单的Makefile。
$:'vi Makefile
' $< 代表依赖文件
' $@ 代表目标文件
/** 代码演示 - Makefile **/
NAME=shell
ELF=$(NAME).elf  #shell.elf
BIN=$(NAME).bin  #shell.bin
OBJS=main.o uart.o

CC=arm-cortex_a9-linux-gnueabi-gcc
LD=arm-cortex_a9-linux-gnueabi-ld
OBJCOPY=arm-cortex_a9-linux-gnueabi-objcopy

CFLAGS=-nostdlib -Wall
LDFLAGS=-nostdlib -nostartfiles

$(BIN):$(ELF)
         $(OBJCOPY) -O binary $(ELF) $(BIN)
         cp $(BIN) /tftpboot/
$(ELF):$(OBJS)
         $(LD) $(LDFLAGS) -Ttext=0x48000000 -emain $(OBJS) -o $(ELF)
%.o:%.c
       $(CC) $(CFLAGS) -c $< -o $@

clean:
rm -rf $(BIN) $(ELF) $(OBJS) /tftpboot/$(BIN)
// rm -vf ... 可以显示删除的文件列表提示信息

二、关于链接脚本
【链接器】
- 将若干输入文件(.o file) 根据一定规则合并为一个输出文件;
- 将标号的地址??; // 半句话
【链接脚本】
- 连接工具的输入文件;
- 链接脚本有自己的语法;
'链接脚本的作用
1) 主要用于规定如何把输入文件内的SECTION放入输出文件内;
2) 控制输出文件内各部分在程序地址空间内的布局;


链接脚本文件
$:'vi shell.lds
/** 代码演示 - shell.lds **/
ENTRY (main)  // -emain
SECTIONS {
    . = 0x48000000;  // 指定 .text的起始位置
    .text : { // 代码段
        main.o (.text)  // 可执行文件以main.o最先链接
        * (.text)
    }   
    .data : { // 数据段
        * (.data)
    }   
    .bss : { // BSS堆
        * (.bss)
    }
}
// 写完链接脚本文件后,Makefile 文件中对应可修改此句:
$(ELF):$(OBJS)
        $(LD) $(LDFLAGS) -Tshell.lds $(OBJS) -o $(ELF)

' C语言指针复习【关注内存】
char s1;
char* s2;
char s3[10];
char* s4[10];
s1 = 'a';
s2 = 'b'; // 不合理,警告
s2 = "12345";
s2++;
(*s2)++; // 【段错误】,12345是常量,在只读代码段,自增段错误!
strcpy (s2, "hello"); // 【段错误】,(readonly)非法写入。
s3[0] = 'c';
s3[0] += 1;
s3++; // 【错误】数组首地址,是地址常量,自增编译不通过(非左值)
strcpy (s3, "world");

/** 特例演示 - 指针 **/
#include 
#include 
int main (void) {
    char* s1 = "hello";
    char* s2 = s1;  // 【内存】s2 在代码段 - 只读常量区
    strcpy (s2, "world..");
    printf ("s2 = %s\n", s2); // 段错误!
    return 0;
}

/** 函数指针 **/
方法一:
int (*pfunc1) (int, int);
pfunc1 = add;
pfunc1 (1, 2); // 3
方法二:
typedef int (*PFUNC) (int, int);
PFUNC pfunc2 = add;
pfunc2 (10, 20); // 30
方法三: // 最难的用法
(*((int (*) (int, int))0x0000000000400544)) (1000, 2000);  // 3000
/** 代码演示 - 函数指针、typedf、函数常地址 **/
#include 
int add (int x, int y) {
    printf ("Enter add func...\n");
    return x + y;
}
// 声明一个指针变量pFunc
// 该4字节存储函数的地址
// 存 <返回值是int,参数为int,int类型的函数地址>
typedef int (*pFunc) (int, int);
int main (void) {
    int res = 0;
    // res = add (1, 2); 正常调用
    // 定义一个指针变量pfunc1
    // 这个变量是个指针,bit32-4字节
    // 该4字节存储1个函数的地址
    // 存 <返回值是int,参数为int,int类型的函数地址>
    int (*pfunc1) (int, int);
    pfunc1 = add; // 函数名和数组名一样,可以代表其首地址
    res = pfunc1 (10, 20);
    printf ("res = %d\n", res); // 30
    // ---------- typedef -----------
    pFunc pfunc2;
    pfunc2 = add;
    res = pfunc2 (100, 200);
    printf ("res = %d\n", res); // 300
    // 函数指针最难的用法
    res = (*((int (*) (int, int))0x0000000000400544)) (1000, 2000);
    printf ("res = %d\n", res); // 3000
    return 0;
}

三、编写一个shell框架
/** 代码演示 - 主函数 main.c **/
/* 不需要写 include ,关键字 extern 可以实现跨文件使用函数。
#include "uart.h"
#include "led.h"
#include "beep.h"
#include "mystrcmp.h"
*/
#define CMD_MAX_LED 32
char cmd_buf[CMD_MAX_LED];
int main (void) {
    // 8N1 115200 non-FIFO polling
    uart_init (); // uart 串口初始化
    led_init (); // led 初始化
    beep_init (); // beep 初始化
    while (1) {
        // 输出命令提示符
        uart_puts ("\nmyArmShell#: ");
        // 接收用户输入的数据
        uart_gets (cmd_buf, CMD_MAX_LED);
        if (! mystrcmp (cmd_buf, "ledon")) {
            led_on (); 
            uart_puts ("\nledon success...");
        }   
        if (! mystrcmp (cmd_buf, "ledoff")) {
            led_off (); 
            uart_puts ("\nledoff success...");
        }   
        if (! mystrcmp (cmd_buf, "beepon")) {
            beep_on (); 
            uart_puts ("\nbeepon success...");
        }   
        if (! mystrcmp (cmd_buf, "beepoff")) {
            beep_off (); 
            uart_puts ("\nbeepoff success...");
        }   
    }   
    return 0;
}
/** 代码演示 - uart.h **/
#ifndef _UART_H_
#define _UART_H_
extern void uart_init (void);
extern void uart_puts (char*);
extern void uart_gets (char*, int);
#endif //_UART_H_

/** 代码演示 - uart.c **/
#define UART0CLKENB     *((volatile unsigned int*)0xc00a9000)
#define UART0CLKGEN0L   *((volatile unsigned int*)0xc00a9004)
#define GPIOD_ALTFN0    *((volatile unsigned int*)0xc001d020)
#define GPIOD_ALTFN1    *((volatile unsigned int*)0xc001d024)
#define GPIOD_PULLENB   *((volatile unsigned int*)0xc001d060)
#define ULCON0          *((volatile unsigned int*)0xc00a1000)
#define UCON0           *((volatile unsigned int*)0xc00a1004)
#define UFCON0          *((volatile unsigned int*)0xc00a1008)
#define UTRSTAT0        *((volatile unsigned int*)0xc00a1010)
#define UTXH0           *((volatile unsigned int*)0xc00a1020)
#define URXH0           *((volatile unsigned int*)0xc00a1024)
#define UBRDIV0         *((volatile unsigned int*)0xc00a1028)
#define UFRACVAL0       *((volatile unsigned int*)0xc00a102c)
void uart_init (void) {
    /* uart0 clk disable */
    UART0CLKENB &= ~(1 << 2); 
    // GPIOD18(Tx 接收管脚) GPIOD14(Rx 发送管脚) 配置功能Function1
    GPIOD_ALTFN0 &= ~(3 << 28); // GPIOD14
    GPIOD_ALTFN0 |= (1 << 28);
    GPIOD_ALTFN1 &= ~(3 << 4); // GPIOD18
    GPIOD_ALTFN1 |= (1 << 4); 
    // 时钟配置:选择PLL[1] 800MHz
    UART0CLKGEN0L &= ~(7 << 2); 
    UART0CLKGEN0L |= (1 << 2); 
    // 分频设置 800/(0x0f+1)=50MHz
    UART0CLKGEN0L &= ~(0xff << 5); // [12:5] 8个位
    UART0CLKGEN0L |= (0xf << 5); // [12:5] 4个位设置为1111
    // UART控制器设置
    ULCON0  = 0x03; // 8N1
    UCON0   = 0x05; // 0101 == 0x05 polling
    UFCON0 |= (3 << 1); // 清空FIFO,解决开发板命令行下运行有多余字符的bug 
    UFCON0 &= ~(1 << 0); // non-FIFO disable
    UBRDIV0 = 26; // 50000000/(115200*16) - 1 == 26.13
    UFRACVAL0 = 2; // 0.13*16 == 2.08
    /* uart0 clk enable */
    UART0CLKENB |= (1 << 2); 
}
void uart_putc (char c) {
    // UTRSTAT0 bit[1] == 1, 缓存寄存器为empty
    // 轮询是否为空
    while (! (UTRSTAT0 & 0x02)); // 分析?
    UTXH0 = c;
    if (c == '\n')
        uart_putc ('\r');
}
void uart_puts (char* str) {
    if (! str)
        return ;

    while (*str) {
        uart_putc (*str);
        str++;
    }   
}
char uart_getc (void) {
    // 轮询 polling UTRSTAT0 bit[0] = 1 received data
    while (! (UTRSTAT0 & 0x01));
    return (char)(URXH0 & 0xff); // 只取低8位,是有效数据
}
void uart_gets (char* buf, int len) {
    int i = 0;
    char tmp = 0;
    while (i < len - 1) {
        tmp = uart_getc (); 
        // 回显,注释掉该句验证效果?
        uart_putc (tmp);
        buf[i] = tmp;
        if (tmp == '\r')
            break;
        i++;
    }   
    // 添加字符串结束标志
    buf[i] = '\0';
}
/** 代码演示 - mystrcmp.h **/
#ifndef _MYSTRCMP_H
#define _MYSTRCMP_H
int mystrcmp (const char*, const char*);
#endif //_MYSTRCMP_H

/** 代码演示 - mystrcmp.c **/
#include "mystrcmp.h"
int mystrcmp (const char* s1, const char* s2) {
    while (*s1) {
        if (*s1 > *s2)
            return 1;
        else if (*s1 < *s2)
            return -1;
        s1++;
        s2++;
    }
    return *s2 == 0 ? 0 : -1;
}
/** 代码演示 - led.h **/
#ifndef _LED_H_
#define _LED_H
extern void led_text (void);
extern void led_on (void);
extern void led_off (void);
#endif // _LED_H

/** 代码演示 - led.c **/
#define  GPIOC_OUT     *((volatile unsigned int*)0xc001c000)
#define  GPIOC_OUTENB  *((volatile unsigned int*)0xc001c004)
#define  GPIOC_ALTFN0  *((volatile unsigned int*)0xc001c020)
void led_init (void) {
    // 配置对应管脚为GPIO功能
    GPIOC_ALTFN0 &= ~ (3 << 24); // clear bit 24,25
    GPIOC_ALTFN0 |= (1 << 24); // set bit 24
    // 选择为输出功能
    GPIOC_OUTENB |= (1 << 12); // OUTPUT
}
void led_on (void) {
    GPIOC_OUT &= ~ (1 << 12); // clear bit 12
}
void led_off (void) {
    GPIOC_OUT |= (1 << 12); // set bit 12
}
/** 代码演示 - beep.h **/
#ifndef _BEEP_H_
#define _BEEP_H_
extern void beep_init (void);
extern void beep_on (void);
extern void beep_off (void);
extern void delay (unsigned int);
#endif //_BEEP_H_

/** 代码演示 - beep.c **/
#define GPIOC_ALTFN0  *((volatile unsigned int*)0xc001c020)
#define GPIOC_OUTENB  *((volatile unsigned int*)0xc001c004)
#define GPIOC_OUT     *((volatile unsigned int*)0xc001c000)
void beep_init (void) {
    // 配置GPIO管脚
    GPIOC_ALTFN0 &= ~(3 << 28);
    GPIOC_ALTFN0 |= (1 << 28);
    // 设置输出功能
    GPIOC_OUTENB |= (1 << 14);
}
void beep_on (void) {
//    while (1) {  // 暂无法中断,故先注销
        GPIOC_OUT |= (1 << 14); // 鸣叫 
//        delay (10000000);
//        GPIOC_OUT &= ~(1 << 14); // 不叫
//        delay (10000000);
//    }
}
void beep_off (void) {
    GPIOC_OUT &= ~(1 << 14); 
}
void delay (unsigned int n) {
    while (--n);
}

自我验证补充:
"extern - 跨文件调用函数 "
// add.h
#ifndef _ADD_H
#define _ADD_H
extern int add (int, int);
#endif //_ADD_H
// add.c
int add (int x, int y) {
    return x + y;
}
// main.c
#include 
int main (void) {
    int i = 10; 
    int j = 20; 
    int sum = add (i, j); 
    printf ("sum = %d\n", sum);
    return 0;
}
" static - 静态局部变量 "
// 数据段,作用域:当前函数,生命周期:整个当前程序。
#include 
int add (int x, int y) {
    static int sum;
    return x + y;
}
int* add1 (int x, int y) {
    static int s = 1; // static int s = x + y; 错误:初始值设定元素不是常量
    printf ("&s is : %p\n", &s); // 0x601020
    printf ("s is : %d\n", s); // 1
    return &s;
}
int main (void) {
    int* p_num = add1 (10, 20); // s is : 1
    printf ("p_num is : %p\n", p_num); // 0x601020
    printf ("*p_num is : %d\n", *p_num); // 1
    *p_num = 2;
    add1 (11, 22); // s is : 2
//  printf ("&sum is : %p", &sum); // sum 未声明
    return 0;
}



你可能感兴趣的:(ARM)