C与ARM汇编结合实现mini2440串口uart简单程序

Posted int-me-x

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C与ARM汇编结合实现mini2440串口uart简单程序相关的知识,希望对你有一定的参考价值。

最近学完了ARM的一些基础知识,开始在mini2440上开发一些简单的程序,串口发送程序是一开始涉及多个寄存器的例子,稍有繁多的步骤应该是开发过程中要慢慢适应的境况

下面的程序的目的是实现mini2440串口的发送功能,向超级终端打印简单字符。

 

设备:mini2440如图,软件为gcc交叉编译工具,minitools与超级终端,主机环境为Windows虚拟机WSL(版本为ubuntu18.04)

技术图片

 

首先应该为C语言主程序的运行初始化环境(设定好堆、栈、入口、中断向量表),这部分要用汇编实现,下面是这一部分

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主程序创造环境

 

之后便是最主要的工作:编写C程序操作串口相关寄存器。首先需要阅读minii2440原理图与S3C2440的芯片手册

 

从原理图上得知,我们要用的0号串口对应的GPIO引脚是GPH2号与3号引脚

技术图片技术图片

 

 

 

 

之后去芯片手册找GPIO部分的GPH如下:

 

技术图片 显然我们要把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模式,所以主要通过这一个寄存器来确定当前是否处于可以发送数据的状态以及是否接收到了数据

 

 

下一步是编写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”字符,功能简单但是每个步骤都要正确,特别是地址的设置以及位运算操作

之后我们要编写连接脚本来使汇编程序与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文件写入开发板的目标地址并运行就可以了。

超级终端出现字符,说明程序运行成功:

技术图片

 

 

程序最终顺利实现的关键是正确初始化C程序的运行环境,并且将寄存器地址逐一检查并确认无误,由于所涉及的寄存器在更复杂的程序中会更加繁多,所以对寄存器的准确操作尤为重要

 

以上是关于C与ARM汇编结合实现mini2440串口uart简单程序的主要内容,如果未能解决你的问题,请参考以下文章

mini2440系统引导串口UART

mini2440裸机试炼之——Uart与pc端实现文件字符传输

TQ2440开发板学习纪实--- 基于中断的UART串口接收

s3c2440裸机编程-UART编程(UART编程实现)

Mini2440裸机开发之中断控制器

S3C2440-裸机篇-06 | UART数据发送接收实验(扫描模式)