­

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程序的运行环境,并且将寄存器地址逐一检查并确认无误,由于所涉及的寄存器在更复杂的程序中会更加繁多,所以对寄存器的准确操作尤为重要