tiny6410裸机之代码重定位学习笔记(包含串口,时钟初始化)

我们的C程序通常会经过:编译,链接,最后形成可执行文件。当编译过后,文件中出现的地址称为编译地址(编译地址就是编译器为每条指令指定的地址,有部分地址不能确定),而在链接后程序中出现的称为链接地址,与此相对的还有一个运行地址(程序正在运行时所处的地址)。而在嵌入式应用中因为成本的问题,SRAM通常很小,所以一般会将程序烧写到nandflash中,系统上电后,如果是从nandflash启动6410会将nandflash中前8K(共四页,每页2K,至于为什么这样我会在后面的nandflash中详解)的代码由硬件自动拷贝到SRAM(stepping stone)中,并且会将其映射到0x0(因为ARM上电后会从0x0地址处取指令运行)地址处。因此这就造成了一个问题,当我们的程序大于8K时该怎么让其运行。一般在这前8K代码中采用与位置无关的代码编写,让这前8K代码在完成系统的一些必要的初始化(中断向量表,关看门狗,nandflash以及DRAM的初始化)后会将自身“搬移”到SRAM中,并且跳转到SRAM中继续运行。因此这就涉及到链接地址和运行地址不一致的问题,当这两者不一致时,如果正在运行的是与位置无关代码则程序可正常运行,否则会出错,下面将通过一个厂家的源程序来说明。

start.S

// 启动代码
.global _start

_start:

	// 把外设的基地址告诉CPU
	ldr r0, =0x70000000 					
	orr r0, r0, #0x13					
	mcr p15,0,r0,c15,c2,4				
	
	// 关看门狗
	ldr r0, =0x7E004000
	mov r1, #0
	str r1, [r0] 
	
	// 设置栈
	ldr sp, =8*1024
	
	// 开启icaches可以提高运行速度
#ifdef  CONFIG_SYS_ICACHE_OFF
	bic r0, r0, #0x00001000 				@ clear bit 12 (I) I-cache
#else
	orr r0, r0, #0x00001000 				@ set bit 12 (I) I-cache
#endif
	mcr p15, 0, r0, c1, c0, 0

	// 设置时钟
	bl clock_init           //时钟的初始化

	// 重定位
	adr r0, _start  	/*adr是小范围的地址读取伪指令,ldr是大范围的读取地址伪指令。可实际上adr是将基于PC相对偏移的地址值或				*基于寄存器相对地址值读取的为指令,而ldr用于加载32为立即数或一个地址到指定的寄存器中。到这儿就会看到				*其中的区别了。如果在程序中想加载某个函数或者某个在联接时候指定的地址时请使用adr,例如在lds中需要重				*新定位的地址。当加载32为的立即数或外部地址时请用ldr。运用adr指令时标号最好和运用处在同一个代码段*/
				//r0存放的是当前地址也就是运行地址
ldr r1, =_start //r1存放的是链接地址,一般在链接脚本或者直接在链接命令里指定ldr r2, =bss_start //r2存放的是bss段的起始地址,也是一个链接地址cmp r0, r1 beq clean_bss//运行地址和链接地址不一致则代码要搬移,如果相等说明现在已经搬移完毕然后跳转去清除bss段copy_loop:ldr r3, [r0], #4 //将所有代码搬移到链接地址处,长度为r2-r1str r3, [r1], #4cmp r1, r2bne copy_loop// 搬移完后清BSS段clean_bss:ldr r0, =bss_startldr r1, =bss_endmov r2, #0cmp r0, r1beq on_ddrclean_loop:str r2, [r0], #4cmp r0, r1bne clean_loopon_ddr:// 调用main函数ldr pc, =mainhalt:b halt


link.lds

SECTIONS

{

	. = 0x1000;       //.表示当前地址,start.S中搬移代码那个地方的_start的链接地址

	.text : {

		start.o

		* (.text)

	}

    		

	.data : {

		* (.data)     //数据段

	}

	

	bss_start = .; 

	.bss : {

		* (.bss)

	}

	bss_end  = .;	

}

clock.c

// 功能:c语言初始化时钟

#define APLL_LOCK (*((volatile unsigned long *)0x7E00F000))
#define MPLL_LOCK (*((volatile unsigned long *)0x7E00F004))
#define EPLL_LOCK (*((volatile unsigned long *)0x7E00F008))

#define OTHERS    (*((volatile unsigned long *)0x7e00f900))

#define CLK_DIV0  (*((volatile unsigned long *)0x7E00F020))

#define ARM_RATIO    0   						// ARMCLK 	= DOUTAPLL / (ARM_RATIO + 1)  	= 532/(0+1) = 532  MHz
#define MPLL_RATIO   0   						// DOUTMPLL = MOUTMPLL / (MPLL_RATIO + 1)   = 532/(0+1) = 532  MHz
#define HCLKX2_RATIO 1   						// HCLKX2 	= HCLKX2IN / (HCLKX2_RATIO + 1) = 532/(1+1) = 266  MHz
#define HCLK_RATIO   1   						// HCLK 	= HCLKX2   / (HCLK_RATIO + 1)   = 266/(1+1) = 133  MHz
#define PCLK_RATIO   3   						// PCLK   	= HCLKX2   / (PCLK_RATIO + 1)   = 266/(3+1) = 66.5 MHz


#define APLL_CON  (*((volatile unsigned long *)0x7E00F00C))
#define APLL_CON_VAL  ((1<<31) | (266 << 16) | (3 << 8) | (1))

#define MPLL_CON  (*((volatile unsigned long *)0x7E00F010))
#define MPLL_CON_VAL  ((1<<31) | (266 << 16) | (3 << 8) | (1))

#define CLK_SRC  (*((volatile unsigned long *)0x7E00F01C))

void clock_init(void)
{	
	/* 1. 设置各PLL的LOCK_TIME,使用默认值 */
	APLL_LOCK = 0xffff;							// APLL_LOCK,供cpu使用 
	MPLL_LOCK = 0xffff;							// MPLL_LOCK,供AHB(存储/中断/lcd等控制器)/APB(看门狗,定时器,SD等)总线上的设备使用
	EPLL_LOCK = 0xffff;							// EPLL_LOCK,供UART,IIS,IIC使用 

	/* 2. 设置为异步模式(Asynchronous mode) */
	OTHERS &= ~0xc0;							//《linux installation for u-boot》3.7中:用MPLL作为HCLK和PCLK的Source是异步(ASYNC)模式,用APLL是同步(SYNC)模式
	while ((OTHERS & 0xf00) != 0);

	/* 3. 设置分频系数 */
	CLK_DIV0 = (ARM_RATIO) | (MPLL_RATIO << 4) | (HCLK_RATIO << 8) | (HCLKX2_RATIO << 9) | (PCLK_RATIO << 12);

	/* 4. 设置PLL,放大时钟 */	
	APLL_CON = APLL_CON_VAL;  
	MPLL_CON = MPLL_CON_VAL;  

	/* 5. 选择PLL的输出作为时钟源 */
	CLK_SRC = 0x03;
}
上面的始终初始化配合韦东山的一张图就很容易看了



man.c

#include "uart.h"

int main()

{
	char c;
	init_uart();

	while (1)

	{
		c = getchar();
		putchar(c+1);
	}

	return 0;
}

uart.c   串口初始化比较简单

// 功能:初始化串口
#include "uart.h" 

#define ULCON0     (*((volatile unsigned long *)0x7F005000))
#define UCON0      (*((volatile unsigned long *)0x7F005004))
#define UFCON0     (*((volatile unsigned long *)0x7F005008))
#define UMCON0     (*((volatile unsigned long *)0x7F00500C))
#define UTRSTAT0   (*((volatile unsigned long *)0x7F005010))
#define UFSTAT0    (*((volatile unsigned long *)0x7F005018))
#define UTXH0      (*((volatile unsigned char *)0x7F005020))
#define URXH0      (*((volatile unsigned char *)0x7F005024))
#define UBRDIV0    (*((volatile unsigned short *)0x7F005028))
#define UDIVSLOT0  (*((volatile unsigned short *)0x7F00502C))

#define GPACON     (*((volatile unsigned long *)0x7F008000))


void init_uart(void)
{
	/* 1. 配置引脚 */
	GPACON &= ~0xff;
	GPACON |= 0x22;
	
	/* 2. 设置数据格式等 */
	ULCON0 = 0x3;  					// 数据位:8, 无校验, 停止位: 1, 8n1 
	UCON0  = 0x5;  					// 时钟:PCLK,禁止中断,使能UART发送、接收 
	UFCON0 = 0x01; 					// FIFO ENABLE
	UMCON0 = 0;						// 无流控
	
	/* 3. 设置波特率 */
	// DIV_VAL = (PCLK / (bps x 16 ) ) - 1 = (66500000/(115200x16))-1 = 35.08
	// DIV_VAL = 35.08 = UBRDIVn + (num of 1’s in UDIVSLOTn)/16 
	UBRDIV0   = 35;
	UDIVSLOT0 = 0x1;
	
}

/* 接收一个字符 */
char getchar(void)
{
	while ((UFSTAT0 & 0x7f) == 0);  // 如果RX FIFO空,等待 
	return URXH0;                   // 取数据 
}

/* 发送一个字符 */
void putchar(char c)
{
	while (UFSTAT0 & (1<<14)); 		// 如果TX FIFO满,等待 
	UTXH0 = c;                      // 写数据 
}


uart.h

void putchar(char c);

char getchar(void);

void init_uart(void);


Makefile

link.bin: start.o main.o clock.o uart.o
	arm-linux-ld -T  link.lds -o link.elf $^
	arm-linux-objcopy -O binary link.elf link.bin
	arm-linux-objdump -D link.elf > link.dis

%.o : %.S
	arm-linux-gcc -o $@ $< -c

%.o : %.c
	arm-linux-gcc -o $@ $< -c -fno-builtin 
#上面的"-fno-builtin"是因为我们在这个例子中自己实现了一些内建函数(系统一些常见的函数,例如printf,putchar等,这些函数不需引用相关头文#件),此时不加该参数系统会报warning: conflicting types for built-in function 'putchar'错误
clean:
	rm *.o *.elf *.bin *.dis -rf




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用 Arduino 来初始化设置 Tiny RTC 12C 时钟模块的示例代码: ```cpp #include <Wire.h> #define DS1307_ADDRESS 0x68 // I2C address of the DS1307 RTC void setup() { Wire.begin(); // Initialize I2C communication Serial.begin(9600); // Initialize serial communication // Set the time and date on the DS1307 // Format: seconds, minutes, hours, day of the week, day, month, year setTimeDS1307(0, 0, 12, 5, 6, 11, 21); } void loop() { // Read the time from the DS1307 and print it to the serial monitor Serial.print(getHourDS1307()); Serial.print(":"); Serial.print(getMinuteDS1307()); Serial.print(":"); Serial.println(getSecondDS1307()); delay(1000); // Wait for one second } // Function to set the time and date on the DS1307 void setTimeDS1307(byte second, byte minute, byte hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year) { Wire.beginTransmission(DS1307_ADDRESS); Wire.write(0x00); // Set the register pointer to 00h Wire.write(decToBcd(second)); // Set the seconds Wire.write(decToBcd(minute)); // Set the minutes Wire.write(decToBcd(hour)); // Set the hours Wire.write(decToBcd(dayOfWeek)); // Set the day of the week (1=Sunday, 7=Saturday) Wire.write(decToBcd(dayOfMonth)); // Set the day of the month Wire.write(decToBcd(month)); // Set the month Wire.write(decToBcd(year)); // Set the year (0-99) Wire.endTransmission(); } // Function to get the hour from the DS1307 byte getHourDS1307() { Wire.beginTransmission(DS1307_ADDRESS); Wire.write(0x02); // Set the register pointer to 02h Wire.endTransmission(); Wire.requestFrom(DS1307_ADDRESS, 1); // Request one byte of data byte hour = bcdToDec(Wire.read() & 0x3f); // Get the hour (0-23) return hour; } // Function to get the minute from the DS1307 byte getMinuteDS1307() { Wire.beginTransmission(DS1307_ADDRESS); Wire.write(0x01); // Set the register pointer to 01h Wire.endTransmission(); Wire.requestFrom(DS1307_ADDRESS, 1); // Request one byte of data byte minute = bcdToDec(Wire.read()); // Get the minute (0-59) return minute; } // Function to get the second from the DS1307 byte getSecondDS1307() { Wire.beginTransmission(DS1307_ADDRESS); Wire.write(0x00); // Set the register pointer to 00h Wire.endTransmission(); Wire.requestFrom(DS1307_ADDRESS, 1); // Request one byte of data byte second = bcdToDec(Wire.read() & 0x7f); // Get the second (0-59) return second; } // Function to convert a decimal number to BCD byte decToBcd(byte val) { return ( (val/10*16) + (val%10) ); } // Function to convert a BCD number to decimal byte bcdToDec(byte val) { return ( (val/16*10) + (val%16) ); } ``` 这个示例代码展示了如何使用 Wire 库来读写 I2C 总线上的数据。首先,我们需要使用 `Wire.begin()` 来初始化 I2C 通信。然后,使用 `setTimeDS1307()` 函数来设置时间和日期。该函数会将当前时间和日期写入到 DS1307 的寄存器中。接下来,在 `loop()` 函数中,我们使用 `getHourDS1307()`、`getMinuteDS1307()` 和 `getSecondDS1307()` 函数来读取当前时间,并将其打印到串口监视器中。最后,我们使用 `delay(1000)` 函数来等待一秒钟,以确保每秒钟只打印一次时间。 请注意,上面的示例代码使用 DS1307 地址为 0x68。如果你使用的是不同的 RTC 时钟模块,可能需要修改地址。同时,你需要在 Arduino 中下载安装 Wire 库。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值