最近学完了ARM的一些基础知识,开始在mini2440上开发一些简单的程序,串口发送程序是一开始涉及多个寄存器的例子,稍有繁多的步骤应该是开发过程中要慢慢适应的境况
下面的程序的目的是实现mini2440串口的发送功能,向超级终端打印简单字符。 设备:mini2440如图,软件为gcc交叉编译工具,minitools与超级终端,主机环境为Windows虚拟机WSL(版本为ubuntu18.04)start.s:
.text
.global _start
_start:
ldr pc, _reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
nop
ldr pc, _irq
ldr pc, _fiq
_reset:
.word reset
_undefined_instruction:
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_irq:
.word irq
_fiq:
.word fiq
reset: @先设置向量表的位置为0x30001000
ldr r0,= 0x30001000
mcr p15, 0, r0, c12, c0, 0
init_stack: @初始化栈
ldr r0,= sk
mov sp, r0 //svc
sub r0, #512
msr cpsr, #0xd2
mov sp, r0 //irq
sub r0, #512
msr cpsr, #0xd1
mov sp, r0 //fiq
sub r0, #0
msr cpsr, #0xd7
mov sp, r0 //abort
sub r0, #0
msr cpsr, #0xdb
mov sp, r0 //undefine
sub r0, #0
msr cpsr, #0x10
mov sp, r0 //user
b main
b main_end
undefined_instruction:
software_interrupt:
stmfd sp!, {r1 - r3, lr}
ldmfd sp!, {r1 - r3, pc}^
prefetch_abort:
data_abort:
irq:
stmfd sp!, {r1 - r3, lr}
bl main
ldmfd sp!, {r1 - r3, pc}^
fiq:
main_end:
b main_end
.data
buf:
.space 512 * 8
sk:
.end
不过由于在这个串口程序中没有中断介入,所以这部分最主要的就是初始化栈(buf与ks之间的512*8Byte的栈空间),为之后的C主程序创造环境
2.之后便是最主要的工作:编写C程序操作串口相关寄存器。首先需要阅读minii2440原理图与S3C2440的芯片手册
从原理图上得知,我们要用的0号串口对应的GPIO引脚是GPH2号与3号引脚显然我们要把GPH的四五位和六七位分别置为10,表示这两个管脚进入uart模式
之后要配置S3C2440的uart控制器,到手册的UART部分ULCON0寄存器与串口通信方式设置有关,我们只需要修改字长为八位:
UCON0寄存器则涉及收发模式、时钟选择等等,这里我们只需要设置收发为polling模式:
波特率的设置则需要借助UBRDIV0,这里由于前面的时钟默认选择为pclk,也就是50MHZ,而波特率是115200,那么按照下面的公式,得到UBRDIV0的值应该为(50000000 / (115200 * 16)) - 1 约等于26
收发状态寄存器为UTRSTAT0,收发内容寄存器为URXH0和UTXH0,注意,UTRSTAT0的0位为0时,接受寄存器为空,而1位为1时,发送寄存器为空,这一点区别要注意一下
因为之前我们不选择FIFO模式,所以主要通过这一个寄存器来确定当前是否处于可以发送数据的状态以及是否接收到了数据
3.下一步是编写C程序main.c
/*
*配置串口传输主程序
*S3C2440
*
*/
#define GPHCON (*(unsigned int *)(0x56000070))
#define ULCON0 (*(unsigned int *)(0x50000000))
#define UCON0 (*(unsigned int *)(0x50000004))
#define UBRDIV0 (*(unsigned int *)(0x50000028))
#define UTXH0 (*(unsigned int *)(0x50000020))
#define URXH0 (*(unsigned int *)(0x50000024))
#define UTRSTAT0 (*(unsigned int *)(0x50000010))
void configGPH(void);
void configULC(void);
void configUC(void);
void configUBR(void);
void uart_init(void);
void RTX0(void)
{
while (1)
{
if (UTRSTAT0 & 2)
{
break;
}
}
UTXH0 = 'c';
int i;
for (i = 1; i <= 2000000; ++i)
{
;
}
}
int main(void)
{
uart_init();
while (1)
{
RTX0();
}
return 0;
}
void uart_init(void)
{
configGPH();
configULC();
configUC();
configUBR();
}
void configGPH(void) //设置GPH2、3分别为TXD0,RXD0
{
unsigned int GPH23 = GPHCON;
GPH23 |= 1 << 5;
GPH23 |= 1 << 3;
GPH23 &= ~(1 << 4);
GPH23 &= ~(1 << 2);
GPHCON = GPH23;
}
void configULC(void) //设置传输字长为8bit
{
unsigned int ULC = ULCON0;
ULC |= 1 << 1;
ULC |= 1;
ULCON0 = ULC;
}
void configUC(void) //设置接受与发送为polling模式
{
unsigned int UC = UCON0;
UC |= (1 << 2);
UC |= 1;
UC &= ~(1 << 3);
UC &= ~(1 << 1);
UCON0 = UC;
}
/*
* 设置波特率 寄存器设置为(时钟频率50MHZ)/(波特率115200 * 16)-1 最后为26
*/
void configUBR(void)
{
unsigned int UBR = UBRDIV0;
UBR = 26;
UBRDIV0 = UBR;
}
这个程序令开发板每隔一定时间通过串口打印一个“c”字符,功能简单但是每个步骤都要正确,特别是地址的设置以及位运算操作
4.之后我们要编写连接脚本来使汇编程序与C程序按照正常顺序生成组合之后的二进制文件OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x30001000; /*start address*/
. = ALIGN(4);
.text :
{
start.o(.text)
*(.text)
}
. = ALIGN(4);
.data :
{
*(data)
}
. = ALIGN(4);
.bss :
{
*(.bss)
}
}
这个脚本告诉连接器将start.s生成的start.o作为代码的开始,其中入口就是里面的_start标号,同时设置各部分起始地址
补充一点为什么选择程序的起始地址为0x30001000:我的mini2440的RAM是64MB,地址是从0x30000000-0x34000000,不过在高地址运行着superboot,所以选择大于0x30000000的附近地址段0x30001000,当然也可以选择0x30000000,但不能接近上限。
这样make之后,交叉编译、处理出了uart_main.bin文件,用方孔usb和usb转串口线连接mini2440与主机,同时打开超级终端接收串口打印信息,连接mini2440电源,使开发板从Nor Flash启动进入superboot,用配套的minitools工具将uart_main.bin文件写入开发板的目标地址并运行就可以了。超级终端出现字符,说明程序运行成功: