零、前言 
本文章为第三方撰写,仅用作教学参考,与课程安排无关。
因盲目 「参考」  本文内容而导致被评判为 「抄袭」  的,本人概不负责。
本文内容可能过时,具体要求以 《计算机原理与嵌入式系统专题实验指导手册》  为准。
这学期开了计算机原理与嵌入式系统的实验。上学期就非常感兴趣,等着这学期做了手痒难耐渴望编程 。
打算每次做完实验之后就写点心得下来,之后写实验报告也方便。
粗略翻了一遍指导书。什么代码都没有,和之前的数字系统设计实验形成鲜明的对比。给的代码模板也基本是空的,需要自己写。总的来说,嵌入式系统的实验还是比较有挑战性的。
    
壹、环境配置 
我一向不喜欢用别人配好的环境,因为我根本不知道他做了什么。因此,只要是可以自己装软件配置的实验课,我一概在自己的电脑上配好。哪怕花点时间,我也心甘情愿。毕竟,如果问题真的发生,我没有任何借口来辩解,只能督促着自己去解决。 
机房用的是Ubuntu 16.04 LTS x32  ,真的是老古董了。我记得我初一用的是15.04,算来也有八年了。32位系统的性能有限制,因此去官网下了64位的镜像本地安装了一遍。不知道咋搞的,Hyper-V既不能卸载也不能安装,搞得虚拟化技术根本没法打开。等Windows 11 24H2的正式镜像出来后覆盖安装一遍吧。唉DevChannel 
之后在VMware的网络适配器选项中配置一下虚拟网卡为静态IP,再设置一下DNS,就可以本地连SSH上去操作了。毕竟虚拟机的GUI操作并不是很舒服。此外,还可以用Visual Studio Code远程连接。使用IDE开发可比用文本编辑器一个一个敲舒服多了。
这是一块搭载三星Exynos(猎户座)4412处理器的开发版,三星Note2使用的也是这颗CPU,真是怀念啊。手机主板被我拆出来封存在铁盒之中,不知道接上屏幕总成之后,还能否正常工作?
阅读芯片技术手册后发现,RS232接口对应的串口就是UART0,因此可以直接将串口读取的设备插线接到开发版的UART0排针,一样可以使用minicom查看日志,且不用拖个很大很笨重的转接头了。使用自制的DapLink可以正常传输数据,不过需要看dmesg日志更换一下设备路径,比如我的是/dev/ttyACM0。
不过DapLink的供电是3V3,插上去LDO要烧化了。换成CH340G之后正常,且不用改设备路径,依旧是/dev/ttyUSB0。不过供电没有DC口稳,感觉有时候会断。还是用适配器供电吧,实在不行就接个5V的Type-C转DC诱骗头,毕竟配套的插口太松了,接触不良。
对于64位的系统,按照说明配置完后会报找不到arm-linux-gcc的错误。
解决方法:sudo apt install lsb-core
贰、点灯 
没啥好说的,实在比较简单。
叁、汇编调用C 
当时问了老师,只要代码模板文件含有C文件,就可以使用C写。那不是所有的都可以在汇编里搞个栈指针指进去然后拿C跑了?
指导书中还详细讲了出栈入栈,但是实验中并未用到。
肆、C调用汇编 
在汇编文件内定义好函数,之后在C内调用:
1 2 3 4 5 @ start.S .global delay delay: 	// your function 
1 2 3 extern  void  delay (int  r0) ; 
和之前的实验结果进行对比,可以很明显看出:LED的闪烁频率更高。这说明汇编的效率比C高得多!
伍、流水灯 
查一下Datasheet。可以知道:设置连接性的地址是0x110002E0,低电平有效;数据地址是0x110002E4,低电平有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int  my (void ) {     GPM4CON = 0x00001111 ;     while  (1 )     {         GPM4DAT = 0xf  ^ 0x1 ;         delay(0x10000000 );         GPM4DAT = 0xf  ^ 0x2 ;         delay(0x10000000 );         GPM4DAT = 0xf  ^ 0x4 ;         delay(0x10000000 );         GPM4DAT = 0xf  ^ 0x8 ;         delay(0x10000000 );     } } 
或者用循环移位的方法也行。下次试试吧。
陆、打开iCache 
这个实验当时卡了一段时间。乍一看确实没头绪,不过多读几遍指导书和Datasheet对应的地方就知道要干啥了。
首先是CP15寄存器的结构。只有C1是配置,那就改它。
“系统刚上电时,iCache中的内容是无效的,往CP15协处理器中的C1的bit[12]写1可以启动iCache,写0可以关闭iCache。”
 
那就构造指令来开启关闭。辅导书里告诉了我们两个拓展指令:
从协处理器传送到ARM寄存器:MRC {<cond>} p15, <Cop1>, Rd, CRn, CRm{,<Cop2>} 
从ARM寄存器传送到协处理器:MCR {<cond>} p15, <Cop1>, Rd, CRn, CRm{,<Cop2>} 
 
为了不改变原来的指令,应当先读取配置,再改动对应的配置位,其他保持不变。因此要用到BIC/ORR这两个按位操作指令。
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 .globl _start .globl delay .globl icache_on .globl icache_off _start: //disable watch dog     ldr r0, =0x10060000       // 把地址0x10060000加载到寄存器r0中     mov r1, #0                // 将立即数0移动到寄存器r1中     str r1, [r0]              // 将寄存器r1的值0存储到r0指向的内存地址中     //set stack     ldr sp, =0x02050000       // 将sp设置为地址0x02050000     bl main                   // 调用主函数main, 跳转到该函数并保存返回地址 //turn on icache icache_on:     // 构造指令     // C 开启整个iCache 优先级高于I     // Z 开启跳转预测     // BITS 4PVI ZFRS BLDP WCAM     // 0000 0xx1 1xxx xxxx x1xx     // 0000 1    8    0    3     // 这是非法立即数 需要拆分成 0x00001800 与 0x00000003     mrc p15, #0x0, r1, c1, c0, 0 // 先读取原来的设置参数存在 r1 内, 之后对相应位重新设置就行     orr r1, r1, #0x00001800      // orr指令按位取1, 剩下的忽略     @ orr r1, r1, #0x00000003    // 本实验不需要对dCache进行操作     mcr p15, #0x0, r1, c1, c0, 0 //再把修改后的 r1 存进去     mov pc, lr //turn off icache icache_off:     mrc p15, #0x0, r1, c1, c0, 0 // 先读取原来的设置参数存在 r1 内, 之后对相应位重新设置就行     bic r1, r1, #0x00001800      // orr的逆指令是bic, 按位取0, 剩下的忽略     @ bic r1, r1, #0x00000003     mcr p15, #0x0, r1, c1, c0, 0 //再把修改后的 r1 存进去     mov pc, lr delay:     sub r0, r0, #1     cmp r0, #0x0     bne delay     mov pc, lr halt:     b halt 
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 #define  GPM4CON (*(volatile unsigned long *)0x110002E0) #define  GPM4DAT (*(volatile unsigned long *)0x110002E4) void  delay (int  count) ;void  icache_on () ;void  icache_off () ;#define  delaytime 0x500000 void  led_blink_assembly ()        {     GPM4CON &= 0xFFFF0000 ;           GPM4CON |= 0x00001111 ;           int  i = 0 ;     while  (i < 4 )                    {         GPM4DAT = 0b1111 ;                delay(16  * delaytime);           GPM4DAT = 0b1100 ;                delay(16  * delaytime);           i++;     } } void  led_blink_c ()           {     GPM4CON &= 0xFFFF0000 ;     GPM4CON |= 0x00001111 ;       int  j = 0 ;     while  (j < 4 )                {         GPM4DAT = 0b1111 ;            delay(delaytime);            GPM4DAT = 0b0011 ;            delay(delaytime);            j++;     } } int  main () {     while  (1 )     {         led_blink_assembly();         icache_off();         led_blink_c();         icache_on();     } } 
柒、蜂鸣器 
测试代码可以正常让蜂鸣器响起,但是仅进行while循环、无条件判断时蜂鸣器不响起,仅有LED闪烁。原因尚不明确。 
地址写错了,我是傻B。只能说细心再细心吧。实验还是很简单的。 
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 #define  GPD0CON (*(volatile unsigned long *)0x114000A0) #define  GPD0DAT (*(volatile unsigned long *)0x114000A4) #define  GPM4CON (*(volatile unsigned long *)0x110002E0) #define  GPM4DAT (*(volatile unsigned long *)0x110002E4) void  delay (int  r0) {     volatile  int  count = r0;     while  (count--); } void  buzzer_on (void ) {     GPD0DAT = 0xf ; } void  buzzer_off (void ) {     GPD0DAT = 0x0 ; } void  buzzer_init (void ) {     GPD0CON = 0x00001111 ;     buzzer_off(); } void  led_blink () {     buzzer_init();     GPM4CON = 0x00001111 ;     while  (1 )     {         GPM4DAT = 0xf ;             buzzer_off();              delay(0x100000 );           GPM4DAT = 0x0 ;             buzzer_on();               delay(0x100000 );       } } 
捌、轮询检测按键 
上课做了两个小时没做出来。检查了几遍代码,自以为没有逻辑漏洞。等上课去问问老师吧。
在Tiny4412裸机开发-按键检测-腾讯云 找到了一点灵感。
为什么之前一直检测不到按键?
最开始,我直接用if (GPX3DAT == 0x11110111)这样子的语句进行判断,但会有个问题,即只有中间四位是变化的,其他四位我们不知道也不关心。可能初始有值,但和我们比较的不一样。因此,我们只比较中间四位即可;或者,只判断某一位是否为0即可。
这里也借鉴了一下设置连接性的代码:只将需要的位置1/置0,最大化避免潜在的数据错误,类似上个实验中的BIC/ORR。
还是用循环右移了。可能写的更多,但是至少好看,而且还能复用。
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 void  delay (int  r0) {     volatile  int  count = r0;     while  (count--);     {         if  (!(GPX3DAT == 0xff ))              return ;     } } int  circular_shift (int  hex_data) {     int  lowest_bit   = hex_data & 1 ;     hex_data       >>= 1 ;     hex_data        |= lowest_bit << 3 ;     return  hex_data; } void  main () {    ...     while (1 ){         if  (!(GPX3DAT & 1  << 2 ))           {             GPM4DAT = 0b1110 ;             for  (i = 0 ; i < 4 ; i++)             {                 delay(0x100000 );                 GPM4DAT = circular_shift(GPM4DAT);             }         }     } } 
玖、时钟体系 
做的想④,一得阁拉米的。  看Datasheet看的头都大了。
BUILD CIRCUITS. WRITE CODE. RTFM. —— SHENZHEN I/O 
参考:第四章、TIny4412 U-BOOT移植四 配置时钟频率源码分析 
先看结构图。
    
    [{"url":"https://webp.esing.dev/img/image-20240922152455350_20240922_1524.png","alt":"CMU_CPU"},{"url":"https://webp.esing.dev/img/image-20240922152524763_20240922_1525.png","alt":"CMU_DMC"},{"url":"https://webp.esing.dev/img/image-20240922152557127_20240922_1525.png","alt":"CMU_TOP"}] 
    
    加载更多  
图很乱,一点点看。
首先是PLL的设置。查指导书上的表格和Datasheet P580的指令格式可以知道:1.4GHz的APLL的MPS值分别为175 3 0,而APLL_CON0的第16-25、8-13、0-2为分别为MPS。
因此,设置频率的代码应该为APLL_CON0 = (0x1 << 31 | 0xAF << 16 | 0x3 << 8 | 0x0);。
这里用左移是为了看起来更加直观。同理可以设置MPLL_CON0、EPLL_CON0和VPLL_CON0。要注意的是表中EPLL只有192MHz,要想整出96MHz的话还需要再除以2。用计算公式带进去就行了。
之后的一个坑是代码模板里的注释写的是ACLK_COREM1 = 188MHz,但是指导书上要求的是ACLK_COREM1 = 175MHz。这个就很容易设置了,拿350MHz二分频就行。
对于MUX的设置,对着上面图片一点一点看吧。以CMU_TOP为例:
因为ACLK_160=160MHz, ACLK_100=100MHz,可以看出这俩频率都是从800MHz分出来的,因此上游时钟应当选择800MHz的MPLL,则MUXMPLL_CTRL_USER_T 应当选择1。 
我们要选择MUXMPLL_CTRL_USER_T 的输出给MUXACLK_100 ,因此MUXACLK_100 应当选择0。 
其余同理。 
 
然后还有一个坑是锁定时间。指导书上说是“一般设置为270cycle的两倍 ”,但实际上要根据PMS的P来设置。
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 #define  CLK_SRC_CPU      (*(volatile unsigned int *)0x10044200)   #define  CLK_DIV_CPU0     (*(volatile unsigned int *)0x10044500)   #define  CLK_DIV_CPU1     (*(volatile unsigned int *)0x10044504)   #define  CLK_SRC_DMC      (*(volatile unsigned int *)0x10040200)   #define  CLK_DIV_DMC0     (*(volatile unsigned int *)0x10040500)   #define  CLK_DIV_DMC1     (*(volatile unsigned int *)0x10040504)   #define  CLK_SRC_TOP0     (*(volatile unsigned int *)0x1003C210)   #define  CLK_SRC_TOP1     (*(volatile unsigned int *)0x1003C214)   #define  CLK_DIV_TOP      (*(volatile unsigned int *)0x1003C510)   #define  CLK_SRC_LEFTBUS  (*(volatile unsigned int *)0x10034200)   #define  CLK_DIV_LEFTBUS  (*(volatile unsigned int *)0x10034500)   #define  CLK_SRC_RIGHTBUS (*(volatile unsigned int *)0x10038200)   #define  CLK_DIV_RIGHTBUS (*(volatile unsigned int *)0x10038500)   #define  APLL_LOCK        (*(volatile unsigned int *)0x10044000)   #define  MPLL_LOCK        (*(volatile unsigned int *)0x10044008)   #define  EPLL_LOCK        (*(volatile unsigned int *)0x1003C010)   #define  VPLL_LOCK        (*(volatile unsigned int *)0x1003C020)   #define  APLL_CON1        (*(volatile unsigned int *)0x10044104)   #define  APLL_CON0        (*(volatile unsigned int *)0x10044100)   #define  MPLL_CON0        (*(volatile unsigned int *)0x10040108)   #define  MPLL_CON1        (*(volatile unsigned int *)0x1004010c)   #define  EPLL_CON2        (*(volatile unsigned int *)0x1003C118)   #define  EPLL_CON1        (*(volatile unsigned int *)0x1003C114)   #define  EPLL_CON0        (*(volatile unsigned int *)0x1003C110)   #define  VPLL_CON0        (*(volatile unsigned int *)0x1003C120)   #define  VPLL_CON1        (*(volatile unsigned int *)0x1003C124)   #define  VPLL_CON2        (*(volatile unsigned int *)0x1003C128)   void  system_clock_init (void ) {           #ifndef  slow     APLL_CON0 = (0x1  << 31  | 0xAF  << 16  | 0x3  << 8  | 0x0 );   #else      APLL_CON0 = (0x1  << 31  | 0x64  << 16  | 0x3  << 8  | 0x1 );   #endif      MPLL_CON0 = (0x1  << 31  | 0x64  << 16  | 0x3  << 8  | 0x0 );       EPLL_CON0 = (0x1  << 31  | 0x40  << 16  | 0x2  << 8  | 0x3 );       VPLL_CON0 = (0x1  << 31  | 0xAF  << 16  | 0x3  << 8  | 0x2 );            CLK_SRC_CPU  = (0x1  << 24  | 0x1  << 0 );        CLK_SRC_DMC  = (0x1  << 12 );                   CLK_SRC_TOP0 = (0x1  << 8  | 0x1  << 4  |                         0x0  << 20  | 0x0  << 16 );       CLK_SRC_TOP1 = (0x1  << 12 );                        CLK_DIV_CPU0 = (0x7  << 8  | 0x3  << 4 );         CLK_DIV_DMC0 = (0x1  << 16  | 0x1  << 12 );       CLK_DIV_TOP  = (0x4  << 8  | 0x7  << 4 );              APLL_LOCK = (270  * 0x3 );        MPLL_LOCK = (270  * 0x3 );        EPLL_LOCK = (3000  * 0x2 );       VPLL_LOCK = (3000  * 0x3 );   } 
拾、UART实验 
看手册配置吧。代码模板里也有较为详细的注释。唯一要仔细想一想的就是如何判断数据发送/接收完毕。有专门的标志位用于判断,每次进行完操作之前判断一下就行。
当时死活不出结果,查了一圈发现是UFCON0设置错了。当时直接无脑拉满的,但是应该按照数据包来设置FIFO的缓冲区大小。此处设置为0x111。
关于C中的while(){}和while();区别:
如果while后无分号,那么默认下一行为循环内容;如果while后跟了分号,那么这是一个空循环。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 while (test){    printf ("hello" ); } while (test)    printf ("hello" ); while (test);printf ("hello" );while (test){ }; printf ("hello" );
当时在这里卡了一下,结果死活不出结果。
拾壹、ADC转换器 
当时也卡了很久,后来发现是判断条件的地方有问题。
将check_bits = ((0x8 << 12) & ADCCON) && (~(0x1 & ADCCON)); while (check_bits)换成while ((0x8000 & ADCCON) && (~(0x1 & ADCCON)))就好了。
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 #include  "init.h"  #include  "print.h"  #define  ADCCFG (*(volatile unsigned long *)0x10010118) #define  ADCCON (*(volatile unsigned long *)0x126C0000) #define  ADCDAT (*(volatile unsigned long *)0x126C000C) #define  ADCMUX (*(volatile unsigned long *)0x126C001C) void  delay (int  r0) ;unsigned  char  num2char[10 ] = {'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' };void  ADCInit (void ) {     ADCCFG = 0x0000 ;            ADCMUX = 0x0 ;               ADCCON = (0x1  << 16  |                 0x1  << 14  |                 0x41  << 6  |                 0x1  << 0 );        puts ("\r\nADC Initialized." ); } void  read_adc (void ) {     int  temp_adc;     unsigned  int  bit_3, bit_2, bit_1, bit_0;     ADCCON   = ADCCON | (0x1  << 2 );     temp_adc = ADCDAT & 0xfff ;     puts ("adc = " );     bit_3     = temp_adc / 1000 ;     temp_adc -= bit_3 * 1000 ;     putc(num2char[bit_3]);     bit_2     = temp_adc / 100 ;     temp_adc -= bit_2 * 100 ;     putc(num2char[bit_2]);     bit_1     = temp_adc / 10 ;     temp_adc -= bit_1 * 10 ;     putc(num2char[bit_1]);     bit_0 = temp_adc;     putc(num2char[bit_0]);     puts ("\r\n" ); } int  main (void ) {     UartInit();     ADCInit();     while  (1 )     {         while  (             (0x8000  & ADCCON) &&             (~(0x1  & ADCCON)))           {             read_adc();             delay(0x0A000000 );             ADCCON = ((1  << 16 ) | (1  << 14 ) | (65  << 6 ) | (1  << 0 ));         }     } } 
拾贰、中断体系 
第二恶心的实验。寄存器量大,很多还要你自己去查手册。
查手册可知:按键对应的中断源为XEINT26,中断号64。
ICDIPR_CPU:SPI32对应0x0440,因此是ICDIPR16_CPU0ICDIPTR_CPU:SPI32对应0x0840,因此是ICDIPTR16_CPU0 
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 #include  "bunfly.h"  #include  "init.h"  #include  "print.h"  void  do_irq () ;void  led_on () ;void  led_off () ;void  delay (int  r0) {     volatile  int  count = r0;     while  (count--); } int  led_status = 0 ;int  main () {               GPX3CON &= 0xFF0000FF ;       GPX3CON |= 0x00FFFF00 ;            GPM4CON &= 0xFFFF0000 ;       GPM4CON |= 0x00001111 ;            EXT_INT43_CON  &= 0xFF0000FF ;       EXT_INT43_CON  |= 0x00222200 ;       EXT_INT43_MASK  = 0x0 ;                   ICDIPR16_CPU0  = 0x1 ;       ICDIPTR16_CPU0 = 0x1 ;       ICDISER2_CPU0  = 0x1 ;       ICCICR_CPU0    = 0x1 ;       ICCPMR_CPU0    = 0x4 ;       ICDDCR         = 0x1 ;       while  (1 )     {         if  (led_status == 0 )         {             led_off();         }         else          {             led_on();         }         do_irq();     } } void  do_irq () {     int  interrupt_data = ICCIAR_CPU0;          int  interrupt_id   = interrupt_data & 0x3FF ;     int  cpu_id         = interrupt_data & (0x7  << 10 );          if  (interrupt_id == 64 )     {         if  (led_status == 0 )         {             led_on();             led_status = 1 ;         }         else          {             led_off();             led_status = 0 ;         }         EXT_INT43_PEND = (0x1  << 2 );           ICDICPR2_CPU0  = (0x1  << 1 );         ICCEOIR_CPU0   = interrupt_id;     } } void  led_on () {     GPM4DAT |= 0xF ; } void  led_off () {     GPM4DAT &= 0x0 ; } 
效果应该类似自锁开关:每次按下按键,LED会改变一次状态。但实际上,每次按下按键之后LED有几率 闪烁。造成这种现象的原因是按键抖动。加入去抖函数效果会好一点,不过我比较懒,没写。 
2024.09.26 还是写了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void  do_irq () { 	     if  (interrupt_id == 64 )     {         delay(0xA0000 );         if  (interrupt_id == 64 )         { 			         }         else          {             return ;         }     } } 
拾叁、Printf函数 
现成的代码,直接自己编译一下就能用。这玩意可比串口一点点打印好多了,毕竟可以格式化输出。
拾肆、RTC 
最恶心的实验。做了六个小时。此外,给的代码模板还有错误,真的佛了。 
Include\rtc.h中第55行的#define CPU0 (*(volatile cpu0 *)0x1048000)中,0x1048000应修改为0x1048000。
本次实验将所有的地址都封装为结构体,增强了代码可读性,并便于调用。
首先是RTC_ALARM和RTC_TIC的中断号。查询可知:一个是76,一个是77。之后用中断号去查找对应的ICDISER寄存器,可以知道这俩都对应ICDISER2。此外,还要查询对应的ICDIPTR寄存器,计算一下就知道对应的是ICDIPTR19。
rtc.c中对中断的处理仿照之前按键中断,复制一下就行了。
有一个要注意的地方就是对ICDIPTR.ICDIPTR19寄存器的写入。本次实验都对目标字段的第0位使能,即CPU Interface 0。但这俩都要对应0,怎么办?
阅读ICDIPTR寄存器的地址说明,可以看到前面按照Byte Offset分成四组。这个就是说,每个ICDIPTR寄存器可以添加四组不同的中断CPU控制配置。因此,写入时,一个直接写入,一个需要偏移八位到Byte Offset 1。
目录结构: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 └─RTC    │  init.c    │  main.c    │  Makefile    │  printf.lds    │  start.S    │    ├─Include    │      bunfly.h    │      init.h    │      print.h    │      rtc.h    │      uart.h    │    └─Lib            Makefile            print.c            rtc.c            uart.c 
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 #include  "init.h"  #include  "print.h"  #include  "rtc.h"  #define  EXT_INT43_CON  (*(volatile unsigned long *)0x11000e0c) #define  EXT_INT43_MASK (*(volatile unsigned long *)0x11000f0c) #define  EXT_INT43_PEND (*(volatile unsigned long *)0x11000f4c) #define  GPX3CON        (*(volatile unsigned long *)0x11000c60) #define  ICDIPR16_CPU0  (*(volatile unsigned long *)0x10490440) #define  ICDIPTR16_CPU0 (*(volatile unsigned long *)0x10490840) void  key_init (void ) {               GPX3CON &= 0xFF0000FF ;       GPX3CON |= 0x00FFFF00 ;            EXT_INT43_CON  &= 0xFF0000FF ;       EXT_INT43_CON  |= 0x00222200 ;       EXT_INT43_MASK  = 0x0 ;                   ICDIPR16_CPU0    = 0x1 ;       ICDIPTR16_CPU0   = 0x1 ;       ICDISER.ICDISER2 = 0x1 ;       CPU0.ICCICR      = 0x1 ;       CPU0.ICCPMR      = 0x4 ;       ICDDCR           = 0x1 ;   } void  do_irq (void ) {     int  interrupt_data = CPU0.ICCIAR;          int  interrupt_id   = interrupt_data & 0x3FF ;     int  cpu_id         = interrupt_data & (0x7  << 10 );     switch  (interrupt_id)     {     case  64 :         printf ("Key interrupt!\r\n" );         EXT_INT43_PEND   |= (0x1  << 2 );           ICDICPR.ICDICPR2 |= (0x1  << 1 );           break ;     case  76 :         printf ("\r\nRTC ALARM 0721 interrupt! RTC ALARM 0721 interrupt!\r\n" );         RTCINTP          |= (0x1  << 1 );            ICDICPR.ICDICPR2 |= (0x1  << 12 );           break ;     case  77 :         printf ("RTC TIC interrupt!\r\n" );         RTCINTP          |= (0x1  << 0 );            ICDICPR.ICDICPR2 |= (0x1  << 13 );           break ;     default :         break ;     }     CPU0.ICCEOIR &= ~(0x3FF );       CPU0.ICCEOIR  = interrupt_id; } int  main (void ) {     int  old_sec, new_sec;     key_init();     rtc_init();     rtc_alarm();     rtc_tic();     while  (1 )     {         do_irq();         new_sec = RTC.BCDSEC;         if  (new_sec != old_sec)         {             printf ("20%x年 %x月 %x日 %x:%x:%x\r\n" ,                    RTC.BCDYEAR,                    RTC.BCDMON,                    RTC.BCDWEEK,                    RTC.BCDHOUR,                    RTC.BCDMIN,                    RTC.BCDSEC);             old_sec = new_sec;         }     } } 
rtc.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 #include  "rtc.h"  #include  "print.h"  #define  RTCALM_ALM  (*(volatile unsigned long *)0x10070050) #define  RTCALM_SEC  (*(volatile unsigned long *)0x10070054) #define  RTCALM_MIN  (*(volatile unsigned long *)0x10070058) #define  RTCALM_HOUR (*(volatile unsigned long *)0x1007005C) void  rtc_init (void ) {     RTCCON      = 0x1 ;        RTC.BCDYEAR = 0x24 ;       RTC.BCDMON  = 0x7 ;        RTC.BCDDAY  = 0x1 ;        RTC.BCDHOUR = 0x12 ;       RTC.BCDMIN  = 0x7 ;        RTC.BCDSEC  = 0x0 ;        RTC.BCDWEEK = 0x21 ;       RTCCON      = 0x0 ;        printf ("RTC init done!\r\n" ); } void  rtc_tic (void ) {     RTCCON = RTCCON & (~(0xf  << 4 )) | (1  << 8 );       TICCNT = 0x7FFF ;                                  ICDDCR = 1 ;                                       ICDISER.ICDISER2  |= (0x1  << 13 );                 ICDIPTR.ICDIPTR19  = ICDIPTR.ICDIPTR19 &                             (~(0xFF  << 0 )) |                              (0x1  << 0 );                   CPU0.ICCPMR = 0xFF ;                               CPU0.ICCICR = 1 ;                              } void  rtc_alarm (void ) {     RTCALM.ALM = (0x1  << 6  |                     0x1  << 0  |                     0x1  << 1 );       RTCALM.SEC = 0x21 ;     RTCALM.MIN = 0x07 ;            ICDDCR             = 1 ;                                     ICDISER.ICDISER2  |= (0x1  << 12 );          ICDIPTR.ICDIPTR19  = ICDIPTR.ICDIPTR19 &                             (~(0xFF  << 8 )) |                           (0x1  << 8 );                CPU0.ICCPMR = 0xFF ;                            CPU0.ICCICR = 0x1 ;                         } 
RTC的日期和星期的寄存器地址反了,因此要想正常打印日期应当给RTC.BCDWEEK进行赋值,或是打印其值。