最近学完了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, , r0, c12, c0, init_stack: @初始化栈
ldr r0,= sk mov sp, r0 //svc
sub r0, # msr cpsr, #0xd2
mov sp, r0 //irq
sub r0, # msr cpsr, #0xd1
mov sp, r0 //fiq
sub r0, # msr cpsr, #0xd7
mov sp, r0 //abort
sub r0, # msr cpsr, #0xdb
mov sp, r0 //undefine
sub r0, # 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 *
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 ()
{
if (UTRSTAT0 & )
{
break;
}
}
UTXH0 = 'c';
int i;
for (i = ; i <= ; ++i)
{
;
}
} int main(void)
{
uart_init();
while ()
{
RTX0();
}
return ;
} void uart_init(void)
{
configGPH();
configULC();
configUC();
configUBR();
} void configGPH(void) //设置GPH2、3分别为TXD0,RXD0
{
unsigned int GPH23 = GPHCON;
GPH23 |= << ;
GPH23 |= << ;
GPH23 &= ~( << );
GPH23 &= ~( << );
GPHCON = GPH23;
} void configULC(void) //设置传输字长为8bit
{
unsigned int ULC = ULCON0;
ULC |= << ;
ULC |= ;
ULCON0 = ULC;
} void configUC(void) //设置接受与发送为polling模式
{
unsigned int UC = UCON0;
UC |= ( << );
UC |= ;
UC &= ~( << );
UC &= ~( << );
UCON0 = UC;
} /*
* 设置波特率 寄存器设置为(时钟频率50MHZ)/(波特率115200 * 16)-1 最后为26
*/
void configUBR(void)
{
unsigned int UBR = UBRDIV0;
UBR = ;
UBRDIV0 = UBR;
}

这个程序令开发板每隔一定时间通过串口打印一个“c”字符,功能简单但是每个步骤都要正确,特别是地址的设置以及位运算操作

之后我们要编写连接脚本来使汇编程序与C程序按照正常顺序生成组合之后的二进制文件

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x30001000; /*start address*/
. = ALIGN();
.text :
{
start.o(.text)
*(.text)
}
. = ALIGN();
.data :
{
*(data)
}
. = ALIGN();
.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简单程序的更多相关文章

  1. GNU ARM 汇编基础

    ARM GNU汇编基础 0 前言 全文补充提醒: 笔者在阅读ARM官方文档及查阅实际的u-boot源码中的汇编代码后,发现了一些不同于ARM官方文档中的汇编语法,查阅相关资料后,才发现主要由于汇编器的 ...

  2. ARM汇编编程概述

    1.为什么需要学些汇编指令 2.ARM汇编指令分类 3.汇编程序框架 4.编程准备 +++++++++++++++++++++++++++++++++++ 1.为什么需要学些汇编指令 bootload ...

  3. linux驱动系列之arm汇编

    在arm平台学习linux时,会遇到arm汇编指令,arm汇编指令与8086汇编指令很多地方都不同,在此记下来以免后面忘了,同时在学习了汇编指令之后分析一些汇编指令编写的代码. 一.相对跳转指令b.b ...

  4. GNU ARM 汇编指令

    第一部分 Linux下ARM汇编语法尽管在Linux下使用C或C++编写程序很方便,但汇编源程序用于系统最基本的初始化,如初始化堆栈指针.设置页表.操作 ARM的协处理器等.初始化完成后就可以跳转到C ...

  5. 生成ARM汇编

    使用ndk即可生成arm汇编 1.首先写好hello.c 2.编写makefile #ndk根目录 NDK_ROOT=E:\Android\android-ndk-r10b #编译器根目录 TOOLC ...

  6. ARM汇编指令调试方法

    学习ARM汇编时,少不了对ARM汇编指令的调试.作为支持多语言的调试器,gdb自然是较好的选择.调试器工作时,一般通过修改代码段的内容构造trap软中断指令,实现程序的暂停和程序执行状态的监控.为了在 ...

  7. ARM汇编

    ARM汇编 ISA ISA即指指令集架构(Instruction Set Architecture)是与程序设计有关的计算机架构的一部分,包括本地数据类型.指令.寄存器.地址模式.内存架构.中断和意外 ...

  8. 3.1 ARM汇编编程概述

    1. 汇编编程 为什么要学习汇编 1). Bootloader初始化 2). Linux kernel 3). 高效 2. ARM汇编分类 1. ARM标准汇编:ARM公司得汇编器适合在Windows ...

  9. arm汇编进入C函数分析,C函数压栈,出栈,传参,返回值

    环境及代码介绍 环境和源码 由于有时候要透彻的理解C里面的一些细节问题,所有有必要看看汇编,首先这一切的开始就是从汇编代码进入C的main函数过程.这里不使用编译器自动生成的这部分汇编代码,因为编译器 ...

随机推荐

  1. Codeforces Round #579 (Div. 3) Complete the Projects(贪心、DP)

    http://codeforces.com/contest/1203/problem/F1 Examples input 1 - - output 1 YES input 2 - - output 2 ...

  2. 系统学习Javaweb6----JavaScript2

    感想:感觉自己还是只是学到皮毛,仍需继续努力,明天开始需要学习Android和阅读感想的书写. 学习笔记: 2.3.运算符 JavaScript运算符与java运算符基本一致. 这里我们来寻找不同点进 ...

  3. 关于Apache Commons的简介

    Apache Commons是对JDK的拓展,包含了很多开源的工具,用于解决平时编程经常会遇到的问题,减少重复劳动.官网网址:http://commons.apache.org Commons Bea ...

  4. python中使用自定义类实例作为字典的key

    python中dict类型的key值要求是不可变类型,通常来说,我们一般采用int或者str类型来作为字典的key,但是在某些场景中,会造成一定的麻烦. 如我们有一个处理http Request的规则 ...

  5. unittest如何在循环遍历一条用例时生成多个测试结果

    引用自:http://blog.csdn.net/kaku21/article/details/42124593 参考网址:http://programmaticallyspeaking.com/te ...

  6. Linux下实现与Internet时间同步

    一.Linux下实现与Internet时间同步 1.安装ntp [root@server-2 ~]# yum install -y ntpdate 2.同步时间 // 方式一.使用域名连接,要经过DN ...

  7. JS UTC 昨天

    var birthday = new Date("Jan 01, 1983 01:15:00") var formatDate = function (date) {       ...

  8. JVM核心组成部分与作用介绍

    jvm由多个部分组成运作的 1.class loader类加载器: 加载类到内存里面,Class loader只需负责加载. 符合条件结构就加载到里面跑, 是否能运行顺利或者有没有错误异常,则需要Ex ...

  9. JS的时间差换算(String to 自己想要的时间格式)

    JS的时间差换算(String to 标准的时间格式) 1.字符串到标准时间格式: 字符串: var time1="2018-05-11 00:00:00" var time2=& ...

  10. SWUST OJ 有趣的三位数(0319)

    有趣的三位数(0319) Time limit(ms): 1000 Memory limit(kb): 65535 Submission: 158 Accepted: 62   Description ...