0%

2018年2月2日 下午3:34

总结

  1. 内存管理器(memory control)有两个能力:
    1. 通过地址选择不同的 寄存器
    2. 通过地址选择不同的 片选
  2. 从外接芯片有无地址线,来分类不同的外接设备
    1. 有地址线——>”类内存”设备
    2. 无地址线——>GPIO高低位输出/入,UART通讯类,Nand硬盘类
  3. 共用的好处/劣势:节省空间,但复杂度高,导致出现片选功能
  4. 对于GPIO来说:控制高低电平的方式是寄存器中的0/1,原理我们不知道
  5. 对于UART来说:如何将数据显示在屏幕,其实我们是不知道的,我们知道到将值放入到一个寄存器中,这个值好像就神奇的显示在屏幕了,原理我们不用管
  6. 对于键盘点亮LED(代码6)来说:我们代码检测亮/不亮的标准,就是单GPFDAT寄存器的值,从我们按键到GPFDAT寄存器的值发生改变这一过程我们是不知道的

2018年1月31日 下午10:36

几个简单知识点:

  1. 讲解arm—>pc串口通信参数:
  2. 内存—>UART—>pc数据传输过程
  3. 如何知道UART中数据寄存器中数据的情况?有没有
    1. 通过中断
    2. 通过轮询:不断地查看UART中的状态寄存器标志位

2018年1月31日 下午4:44

注:能到达目的就行,看手册不是唯一的方法,百度也可以。

几个不知道:

  1. 大致的组成、原理是什么?
  2. 使用寄存器的个数不知道?
  3. 具体使用那几个寄存器也不知道?
  4. 具体如何给寄存器置位也不知道?

怎么知道:我们需要干什么❓

首先从自己的需求功能开始推理,从手册中提取出完成自己需要功能的线条最终定位到使用哪些寄存器、以及如何使用这些寄存器

线条:有些功能的实现需要不仅仅一个步骤,需要多个寄存器进行配合。要把这些步骤的先后顺序梳理清楚
定位寄存器:Manual中有明确指明是哪个寄存器,这个最关键的问题,也是最好解决的问题

关于手册的几个小的tips

  1. 每一章分三个模块:overview + operate(function) + register
  2. overview一定仔细看看
  3. operate挑着看
  4. register注意看description
  5. 当碰见一个词不认识的时候,comment + F 查查上下文有没有解释,毕竟是挑着看,容易落下东西

例子🌰:时钟clock

2018年1月31日 下午1:15

实现arm中的printf函数源代码(注释是重点):

.c文件

  1. my_printf.c
  2. uart.c
  3. main.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    my_printf.c 
    #include "my_printf.h"
    ==================================================================================================
    typedef char * va_list;

    // 下面这段是利用宏来完成功能,用宏来代替函数,牛逼
    #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
    //宏本身就是一个赋值语句,牛逼
    #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )

    //逗号表达式功能:(表达式1,表达式2)运算顺序是表达式1-->表达式2,返回表达式2的结果
    //下面两种写法作用一致
    //#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    #define va_arg(ap,t) ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )
    #define va_end(ap) ( ap = (va_list)0 )

    //==================================================================================================
    unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\
    '8','9','a','b','c','d','e','f'};

    // 输出显示char
    static int outc(int c)
    {
    __out_putchar(c);
    return 0;
    }

    // 输出显示string
    static int outs (const char *s)
    {
    while (*s != '\0')
    __out_putchar(*s++);
    return 0;
    }

    // 输出显示number:传入n,进制,前导位,最大宽度。最后要转化成字符串输出!!
    //这个函数得打断点看输出,我没有看!
    static int out_num(long n, int base,char lead,int maxwidth)
    {
    unsigned long m=0;
    char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
    int count=0,i=0;


    *--s = '\0';

    if (n < 0){
    m = -n;
    }
    else{
    m = n;
    }

    do{
    *--s = hex_tab[m%base];
    count++;
    }while ((m /= base) != 0);

    if( maxwidth && count < maxwidth){
    for (i=maxwidth - count; i; i--)
    *--s = lead;
    }

    if (n < 0)
    *--s = '-';

    return outs(s);
    }

    //my_vprintf函数,通过调整指向+步长去完成printf的功能
    /*reference : int vprintf(const char *format, va_list ap); */
    static int my_vprintf(const char *fmt, va_list ap)
    {
    char lead=' ';
    int maxwidth=0;

    for(; *fmt != '\0'; fmt++)
    {
    //初始化
    lead=' ';
    maxwidth=0;

    if (*fmt != '%') {
    outc(*fmt);
    continue;
    }

    //format : %08d, %8d,%d,%u,%x,%f,%c,%s
    fmt++;
    //处理前导零
    if(*fmt == '0'){
    lead = '0';
    fmt++;
    }


    //设置最大宽度
    while(*fmt >= '0' && *fmt <= '9'){
    maxwidth *=10;
    maxwidth += (*fmt - '0');
    fmt++;
    }

    //根据列类型符:先取值,再移动指针。
    //va_arg的作用是:取出当前ap指向的数据,然后ap再根据“步长”去移动到下一个位置
    //这样做的好处:这时的“步长”就是当前取出这个数据的长度,牛逼🐂!
    //va_arg的“返回值”=当前ap指向的数据
    //out_num()函数的参数: 当前ap指向的数据 +
    switch (*fmt) {
    case 'd': out_num(va_arg(ap, int), 10,lead,maxwidth); break;
    case 'o': out_num(va_arg(ap, unsigned int), 8,lead,maxwidth); break;
    case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;
    case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;
    case 'c': outc(va_arg(ap, int )); break;
    case 's': outs(va_arg(ap, char *)); break;

    default:
    outc(*fmt);
    break;
    }
    }
    return 0;
    }

    // 重点:实现printf
    //reference : int printf(const char *format, ...);
    int printf(const char *fmt, ...)
    {
    /*这个指针ap,我觉得倒是很有话说
    *因为指针体现出了printf这个函数的本质,最精华的地方:所有的取值都是通过ap这一个指针来实现的!!
    *通过调整指针的指向+步长,来最终确定我们的操作。
    */
    va_list ap;

    //va_start这个宏,就是一个赋值语句。作用:给ap赋值,初始化ap指针的指向为第一个参数fmt
    va_start(ap, fmt);
    //my_vprintf函数,通过调整指向+步长去完成printf的功能
    my_vprintf(fmt, ap);
    //防止“野指针”
    va_end(ap);

    return 0;
    }


    //测试函数
    int my_printf_test(void)
    {
    printf("This is www.100ask.org my_printf test\n\r") ;
    printf("test char =%c,%c\n\r", 'A','a') ;
    printf("test decimal number =%d\n\r", 123456) ;
    printf("test decimal number =%d\n\r", -123456) ;
    printf("test hex number =0x%x\n\r", 0x55aa55aa) ;
    printf("test string =%s\n\r", "www.100ask.org") ;
    printf("num=%08d\n\r", 12345);
    printf("num=%8d\n\r", 12345);
    printf("num=0x%08x\n\r", 0x12345);
    printf("num=0x%8x\n\r", 0x12345);
    printf("num=0x%02x\n\r", 0x1);
    printf("num=0x%2x\n\r", 0x1);

    printf("num=%05d\n\r", 0x1);
    printf("num=%5d\n\r", 0x1);

    return 0;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
uart.c
#include "s3c2440_soc.h"

// 初始化UART芯片
/* 115200,8n1 */
void uart0_init()
{
//这是根据电路原理图来设定的,本身GPIO就是一个通用口
/* 设置引脚用于串口 */
/* GPH2,3用于TxD0, RxD0 */
GPHCON &= ~((3<<4) | (3<<6));
GPHCON |= ((2<<4) | (2<<6));

GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */

//manual中专门以小结将波特率,直接告诉你使用那些寄存器,怎么使用,公式是啥,写的很完善。
/* 设置波特率 */
/* UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
* UART clock = 50M
* UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26
*/
UCON0 = 0x00000005; /* PCLK,中断/查询模式 */
UBRDIV0 = 26;

/* 设置数据格式 */
ULCON0 = 0x00000003; /* 8n1: 8个数据位, 无较验位, 1个停止位 */

/* */

}

// 向pc显示器发送显示数据,按char
int putchar(int c)
{
/* UTRSTAT0 */
/* UTXH0 */
//为啥是UTRSTAT0,这里的0是哪里来的?Each UART channel contains two 64-byte FIFOs for receiver and transmitter.
//这个问题应该一开始就问,根本原因是原理图中使用的是TxD0,RxD0。版子的厂家电路就是这样焊好的!!!你要咋吧
while (!(UTRSTAT0 & (1<<2)));

//manual中总体介绍中,并没有提到UTXH0寄存器,但是在手册中最后说明寄存器中有:There are three UART transmit buffer registers including UTXH0, UTXH1 and UTXH2 in the UART block.
//也许像这种数据寄存器,就当成默认有的吧,所以manual就直接写在寄存器部分!!
UTXH0 = (unsigned char)c;

}
// 拿回从pc显示器输入的值
int getchar(void)
{
while (!(UTRSTAT0 & (1<<0)));
return URXH0;
}

// 向pc显示器发送显示数据,按字符串
int puts(const char *s)
{
while (*s)
{
putchar(*s);
s++;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
main.c
// 这里就是这节函数牛逼的地方,不用像stdio.h这种系统函数库,全是自己写的
#include "s3c2440_soc.h"
#include "uart.h"
#include "my_printf.h"

int main(void)
{
unsigned char c;

//初始化uart
uart0_init();
//pc显示字符串:Hello, world!
puts("Hello, world!\n\r");
//测试printf函数
my_printf_test();

//pc与arm的交互行为:又来有往
while(1)
{
c = getchar();
if (c == '\r')
{
putchar('\n');
}

if (c == '\n')
{
putchar('\r');
}

putchar(c);
}
return 0;
}

.h文件

1
2
3
4
5
6
7
8
9
10
uart.h
#ifndef _UART_H
#define _UART_H
//声明本身的函数
void uart0_init();
int putchar(int c);
int getchar(void);
int puts(const char *s);

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
my_printf.h
#ifndef _MY_PRINTF_H
#define _MY_PRINTF_H
// 说明my_printf.c依赖于uart.c
#include "uart.h"
// 宏定义
#define __out_putchar putchar

#define MAX_NUMBER_BYTES 64
// 声明本身的所带有的函数
extern int my_printf_test(void);
int printf(const char *fmt, ...);

#endif /* _MY_PRINTF_H */

2018年1月29日 上午10:26

我做这幅图的思路说明,我一开始是想着能不能把我做的笔记按知识点一节一节的讲出来,但是,但我去复习我的笔记的时候,我提出很多的问题,但是我却没有没有一个合适的答案。毕竟我是第一遍学,具体的讲清楚一个事情不太符合实际。于是,我就想能不能将这作为一个开端,创造一个好开端让雪球越滚越大。

2018年1月25日 上午11:11

步骤:

  1. 使用sublime,操作command + shift + p+输入并安装 install package controll
  2. 提示安装成功后,再次command + shift + p+ 输入并安装 CovertToUTF8
  3. 安装Codecs,操作command + shift + p+输入并安装Codecs
  4. 完成

提示:

  1. 使用UltraEdit,也可以支持打开,但是这个软件花钱,只有30天试用期
  2. 使用mac自带的文本软件打开,但是始终看的不爽!


::图1::第二步按装成功后会出现上图页面


::图2::缺少第三步会提示error

2018年1月25日 上午9:55

最重要的内容:

下面的注释就是我现在对makefile的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#一些变量习惯性的放在最前面(下面10行都是变量)
objs = a.o b.o c.o

#字符串处理函数
dep_files := $(patsubst %,.%.d, $(objs))
#判断文件是否存在的函数,将结果返回
dep_files := $(wildcard $(dep_files))

CFLAGS = -Werror -Iinclude

#首要目标一定要放在最前面
test: $(objs)
gcc -o test $^

#文件存在你才可以包含进来呀(是针对第一次make或者clean之后第一次make优化)
ifneq ($(dep_files),)
include $(dep_files)
endif

%.o : %.c
gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d

clean:
rm *.o test
#这里是这个程序的一个bug,如果执行make distclean 那么就必须make clean初始化程序。
#否则.h文件修改就不起作用了

#这个程序现在的功能是没有能力单独生成.d文件的,是和.o文件绑定在一起
#只有两种情况能让.h文件一种正常1.clean之后make 2..d文件存在
#这个程序要是优化的化,就是让其具有单独生产.d文件的能力,并且在程序执行一开始就产生,那么任何时候都是正确的
distclean:
rm $(dep_files)

.PHONY: clean

注:配套的源码在:D:\005_ARM裸机1期加强版\源码文档图片\源码_201180109_添加gcc_pointer_makefile_printf\008_gcc_pointer_Makefile_009\003_Makefile\003_example_009_008

调试过程:

为了体现上面makefile的bug

Makefile的详细笔记:

Makefile目标,伪目标,头文件自动依赖 - Abnor - 博客园
Makefile是根据依赖关系,时间戳和生成规则来判断哪些文件需要更新

  1. makefile每次执行,要保证生成的对象的所有依赖串中的依赖都必须检查一遍是否更新过

  2. Makefile的核心1—规则 :

    1
    2
    目标1 : 依赖1 依赖2 ...
    [TAB]命令
    1. 指令执行条件:
      1. 当”目标文件”不存在,
      2. 某个依赖文件比目标文件”新”,
      3. 则: 执行”命令”
  3. Makefile的核心2—规则 :

    1
    目标2 : 依赖1 依赖2 ...
    1. 当执行条件成立时,如何找到对应执行的语句?
      1. 去找 目标1=目标2
      2. 执行目标2的[TAB]命令
  4. Makefile的语法

    1. 通配符: %.o
    2. $@ 表示目标
    3. $< 表示第1个依赖文件
    4. $^ 表示所有依赖文件
    5. 假想目标: .PHONY
    6. 即时变量、延时变量, export
      1
      2
      3
      4
      5
      6
      7
      8
      简单变量(即时变量) :
      A := xxx # A的值即刻确定,在定义时即确定
      B = xxx # B的值使用到时才确定

      := # 即时变量
      = # 延时变量
      ?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句
      += # 附加, 它是即时变量还是延时变量取决于前面的定义
      参考文档:
      a. 百度搜 “gnu make 于凤昌”
      b. 官方文档: http://www.gnu.org/software/make/manual/
  5. Makefile函数

    1. $(foreach var,list,text)
    2. $(filter pattern…,text)
      1. 在text中取出符合patten格式的值

    3. $(filter-out pattern…,text)
      1. 在text中取出不符合patten格式的值

    4. $(wildcard pattern)
      1. pattern定义了文件名的格式,

      2. wildcard取出其中存在的文件

    5. $(patsubst pattern,replacement,$(var))
      1. 从列表中取出每一个值

      2. 如果符合pattern

      3. 则替换为replacement

  6. Makefile实例
    改进: 支持头文件依赖
    Linux Makefile生成*.d依赖文件及 gcc -M -MF -MP等相关选项说明 - CSDN博客

    1
    2
    3
    gcc -M c.c // 打印出依赖
    gcc -M -MF c.d c.c // 把依赖写入文件c.d
    gcc -c -o c.o c.c -MD -MF c.d // 编译c.o, 把依赖写入文件c.d

    b. 添加CFLAGS