零、前言
本文章为第三方撰写,仅用作教学参考,与课程安排无关。
因盲目 「参考」 本文内容而导致被评判为 「抄袭」 的,本人概不负责。
本文内容可能过时,具体要求以 《计算机原理与嵌入式系统专题实验指导手册》 为准。
这学期开了计算机原理与嵌入式系统的实验。上学期就非常感兴趣,等着这学期做了手痒难耐渴望编程 。
打算每次做完实验之后就写点心得下来,之后写实验报告也方便。
粗略翻了一遍指导书。什么代码都没有,和之前的数字系统设计实验形成鲜明的对比。给的代码模板也基本是空的,需要自己写。总的来说,嵌入式系统的实验还是比较有挑战性的。
壹、环境配置
我一向不喜欢用别人配好的环境,因为我根本不知道他做了什么。因此,只要是可以自己装软件配置的实验课,我一概在自己的电脑上配好。哪怕花点时间,我也心甘情愿。毕竟,如果问题真的发生,我没有任何借口来辩解,只能督促着自己去解决。
机房用的是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_CPU0
ICDIPTR_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
进行赋值,或是打印其值。