I.MX6U 裸机开发5.准备C环境并用C语言控制LED

I.MX6U 裸机开发5.准备C环境并用C语言控制LED

  • 一、C运行环境
    • [1. 设置处理器模式](#1. 设置处理器模式)
    • [2. CPSR 寄存器](#2. CPSR 寄存器)
      • [CPSR 寄存器结构](#CPSR 寄存器结构)
      • 模式位
      • [MRS 指令](#MRS 指令)
      • [MSR 指令](#MSR 指令)
    • [3. 设置SP指针](#3. 设置SP指针)
      • [设置 SP 指针](#设置 SP 指针)
      • [保存和恢复 SP 指针](#保存和恢复 SP 指针)
    • [4. 跳转到C语言](#4. 跳转到C语言)
  • 二、程序编写
    • [1. 启动文件 start.S](#1. 启动文件 start.S)
    • [2. main.h 定义寄存器](#2. main.h 定义寄存器)
    • [3. 主程序main.c](#3. 主程序main.c)
    • [4. Makefile](#4. Makefile)
    • [5. 链接文件 linker.ld](#5. 链接文件 linker.ld)
    • [6. 编译烧写测试](#6. 编译烧写测试)

一、C运行环境

1. 设置处理器模式

在Cortex-A 的架构中一共有9种处理模式,如下表所示:

模式 描述
USR (User Mode) 用户模式,非特权模式,大部分程序运行的时候就处于此模式。
FIQ (Fast Interrupt Request Mode) 快速中断模式,进入 FIQ 中断异常。
IRQ (Interrupt Request Mode) 一般中断模式。
SVC (Supervisor Mode) 超级管理员模式,特权模式,供操作系统使用。
MON (Monitor Mode) 监视模式,用于安全扩展模式。
ABT (Abort Mode) 数据访问终止模式,用于虚拟存储以及存储保护。
HYP (Hypervisor Mode) 超级监视模式,用于虚拟化扩展。
UND (Undefined Mode) 未定义指令终止模式。
SYS (System Mode) 系统模式,用于运行特权级的操作系统任务。

I.MX6ULL 处理器中,可以通过修改 CPSR(Current Program Status Register)寄存器的模式位来设置处理器模式。以下是一些常见模式的设置方法:

进入用户模式 (User Mode)

c 复制代码
MRS R0, CPSR        ; 读取当前 CPSR 寄存器值到 R0
BIC R0, R0, #0x1F   ; 清除模式位
ORR R0, R0, #0x16   ; 设置模式位为 Monitor Mode (0b10110)
MSR CPSR_c, R0      ; 将修改后的值写回 CPSR 寄存器

2. CPSR 寄存器

CPSR(Current Program Status Register)用于存储当前程序的状态信息。它包含了处理器模式、条件标志、中断屏蔽位等信息。

CPSR 寄存器结构

CPSR 是一个 32 位的寄存器,其各个位的含义如下:

名称 描述
31 N (Negative) 负标志,表示最近一次运算结果为负数。
30 Z (Zero) 零标志,表示最近一次运算结果为零。
29 C (Carry) 进位标志,表示最近一次运算结果产生了进位或借位。
28 V (Overflow) 溢出标志,表示最近一次运算结果产生了溢出。
27-8 Reserved 保留位,未使用。
7 I (IRQ disable) IRQ 中断禁用位,设置为 1 时禁用 IRQ 中断。
6 F (FIQ disable) FIQ 中断禁用位,设置为 1 时禁用 FIQ 中断。
5 T (Thumb) Thumb 状态位,设置为 1 时处理器处于 Thumb 状态。
4-0 Mode 模式位,表示当前处理器的工作模式。

模式位

模式位用于设置处理器的工作模式,常见的模式及其对应的值如下:

模式 描述
User 0b10000 用户模式,非特权模式。
FIQ 0b10001 快速中断模式。
IRQ 0b10010 一般中断模式。
Supervisor 0b10011 超级管理员模式,特权模式。
Monitor 0b10110 监视模式,用于安全扩展模式。
Abort 0b10111 数据访问终止模式。
Hypervisor 0b11010 超级监视模式,用于虚拟化扩展。
Undefined 0b11011 未定义指令终止模式。
System 0b11111 系统模式,用于运行特权级的操作系统任务。

MRS 指令

MRS(Move Register from Special register)指令用于将特殊寄存器(如 CPSR 或 SPSR)的值移动到通用寄存器中。常用于读取当前程序状态寄存器(CPSR)或保存程序状态寄存器(SPSR)的值。

MSR 指令

MSR(Move Special register from Register)指令用于将通用寄存器的值移动到特殊寄存器中。常用于修改 CPSR 或 SPSR 的值。

3. 设置SP指针

在 ARM 处理器中,SP(Stack Pointer)指针用于指向当前栈的顶部。设置 SP 指针通常用于初始化栈或切换栈。以下是一些常见的设置 SP 指针的方法:

设置 SP 指针

可以使用 MOV 指令将一个值加载到 SP 寄存器中,以设置栈指针的位置。

示例
assembly 复制代码
; 将栈指针设置为地址 0x8000
MOV SP, #0x8000

保存和恢复 SP 指针

在某些情况下,可能需要保存当前的 SP 指针值,并在稍后恢复它。可以使用通用寄存器来保存和恢复 SP 指针。

示例
assembly 复制代码
; 保存当前的 SP 指针值到 R0
MOV R0, SP

; 执行一些操作,可能会改变 SP 指针

; 恢复 SP 指针值
MOV SP, R0

SP可以指向内部RAM,也可以指向DDR内存。 对于512M的DDR来说,内存范围是 0x80000000~0x9FFFFFFF,栈大小一般设置为2M,由于A7栈是向下增长的,可以将SP设置为 0x80200000。

4. 跳转到C语言

一般跳到 main 函数,下面是一个示例:

c 复制代码
.global _start
.extern main

_start:
    /* 设置栈指针 */
    LDR SP, =0x8000

    /* 调用 C 语言的 main 函数 */
    BL main

    /* 死循环,防止程序返回 */
1:  B 1b

二、程序编写

1. 启动文件 start.S

c 复制代码
.global __start

__start:
    /** 设置处理进入 SVC 模式 */
    MRS R0, CPSR        /** 读取当前的 CPSR */
    BIC R0, R0, #0x1F   /** 清除 CPSR 的低 5 位 */
    ORR R0, R0, #0x13   /** 设置 CPSR 为 SVC 模式 */
    MSR CPSR, R0        /** 设置 CPSR */

    /** 设置堆栈 */
    LDR R0, =0x80200000
    B main

2. main.h 定义寄存器

c 复制代码
//
// Created by Xundh on 2024/11/9.
//

#ifndef LEARN_I_MX6U_MAIN_H
#define LEARN_I_MX6U_MAIN_H

/** 定义要使用的寄存器 **/
#define CCM_CCGR0 (*(volatile unsigned int *)0x020C4068)
#define CCM_CCGR1 (*(volatile unsigned int *)0x020C406C)
#define CCM_CCGR2 (*(volatile unsigned int *)0x020C4070)
#define CCM_CCGR3 (*(volatile unsigned int *)0x020C4074)
#define CCM_CCGR4 (*(volatile unsigned int *)0x020C4078)
#define CCM_CCGR5 (*(volatile unsigned int *)0x020C407C)
#define CCM_CCGR6 (*(volatile unsigned int *)0x020C4080)

/** IOMUXC 寄存器地址 **/
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 (*(volatile unsigned int *)0x020E0068)
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 (*(volatile unsigned int *)0x020E02F4)

/** GPIO1 寄存器地址 **/
#define GPIO1_DR   (*(volatile unsigned int *)0x0209C000)
#define GPIO1_GDIR (*(volatile unsigned int *)0x0209C004)
#define GPIO1_PSR  (*(volatile unsigned int *)0x0209C008)

#endif //LEARN_I_MX6U_MAIN_H

3. 主程序main.c

c 复制代码
//
// Created by Xundh on 2024/11/9.
//
#include "main.h"
/**
 * 使能时钟
 * @return
 */
int enable_clock(void){
    CCM_CCGR1 = 0xffffffff;
    CCM_CCGR2 = 0xffffffff;
    CCM_CCGR3 = 0xffffffff;
    CCM_CCGR4 = 0xffffffff;
    CCM_CCGR5 = 0xffffffff;
    CCM_CCGR6 = 0xffffffff;

    return 0;
}
/**
 * 初始化LED
 * @return
 */
 void init_led(void){
    IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x5; // 设置复用为GPIO1_IO03
    IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0x10B0;  // 设置电气属性
    GPIO1_GDIR |= (1<<3);   // 设置为输出
}
void led_on(void){
    GPIO1_DR &= ~(1<<3);
}
void led_off(void){
    GPIO1_DR |= (1<<3);
}
void delay(volatile unsigned int n){
    while(n--);
}
int main(void){
    /** 初始化LED **/
    enable_clock();

    // 初始化LED
    init_led();

    while(1){
        led_on();
        delay(500);
        led_off();
        delay(500);
    }

    return 0;
}

4. Makefile

c 复制代码
objs = start.o main.o

ledc.bin : $(objs)
	# 把.o文件链接成.elf文件, 其中的 $^ 代表所有的.o文件
	arm-linux-gnueabihf-ld -T linker.ld $^ -o ledc.elf
	# 把.elf文件转换成.bin文件
	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
	# 把.elf文件转换成.dis文件
	arm-linux-gnueabihf-objdump -D ledc.elf > ledc.dis

%.o : %.c
	# 把.c文件编译成.o文件, 其中的 $@ 代表目标文件, $< 代表第一个依赖文件
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

%.o : %.S
	# 把.S文件编译成.o文件
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

clean:
	rm -f *.bin *.o *.dis *.elf

download:
	../tools/imxdownload ledc.bin /dev/sdb

5. 链接文件 linker.ld

上面的 Makefile 直接编译的话,会出现 0x87800000 地址放的不是 _start 函数,而是 main 函数。为了确保 0x87800000,需要使用链接脚本 。

链接脚本(linker script)是用于控制链接器(如 ld)如何将目标文件(object files)链接成可执行文件的脚本,链接脚本通常以 .ld 为扩展名,主要有以下作用:

  1. 定义内存布局 :指定程序各个段(如 .text.data.bss)在内存中的位置。
  2. 设置入口点:指定程序的入口点,即程序开始执行的地址。
  3. 分配段:将目标文件中的段分配到内存中的特定位置。
  4. 定义符号:可以定义一些符号,用于在程序中引用特定的内存地址。

以下是本示例使用的链接脚本文件 linker.ld

linkerscript 复制代码
SECTIONS
{
    . = 0x87800000; /* 起始地址 */

    .text : {
        *(.text)
    }

    .data : {
        *(.data)
    }

    .bss : {
        *(.bss)
    }

    . = ALIGN(8);
    __stack_top = .; /* 定义栈顶 */
}

ENTRY(_start) /* 指定入口点为 _start */

本次实验使用的链接脚本 文件:

c 复制代码
SECTIONS{
    . = 0x87800000;
    .text :
    {
        start.o
        *(.text)
    }
    .rodata ALIGN(4) : {*(.rodata*)}
    .data ALIGN(4) : {*(.data)}
    __bss_start=.;
    .bss ALIGN(4) : {*(.bss) *(COMMON)}
    __bss_end=.;
}

定位计数器

链接脚本中的定位计数器(location counter)是一个特殊的符号,用于跟踪当前分配的内存地址。它通常用 . 表示,并在链接脚本中用于定义段的起始地址和大小。上例中:
. = 0x87800000 , 前面的点就是定位计数器,设置定位计数器的初始值为 0x87800000,表示从这个地址开始分配内存。

.text

.text : {
    start.o
    *(.text)
}

定义 .text 段,包含 start.o 和所有 .text 段的内容。定位计数器会自动增加,以跟踪分配的内存地址。

*(.text)表示所有程序源代码编译出来的段。

.rodata 段

只读数据段,如字符串变量,使用const关键字定义的全局或静态变量。

.rodata ALIGN(4) : {
    *(.rodata*)
}

定义 .rodata 段,并将其对齐到 4 字节边界。定位计数器会更新为 .text 段结束后的地址,并对齐到 4 字节。

其中前面一个* 表示匹配所有的输入文件,

(.rodata*)表示匹配所有以.rodata 开头的段,如 .rodata,rodata1,.rodata2。

.data数据段

c 复制代码
.data ALIGN(4) : {
    *(.data)
}

定义 .data 段,并将其对齐到 4 字节边界。定位计数器会更新为 .rodata 段结束后的地址,并对齐到 4 字节。

.bss段

__bss_start = .;
.bss ALIGN(4) : {
    *(.bss)
    *(COMMON)
}
__bss_end = .;

记录 .bss 段的起始地址为 __bss_start,然后定义 .bss 段,并将其对齐到 4 字节边界。定位计数器会更新为 .data 段结束后的地址,并对齐到 4 字节。最后,记录 .bss 段的结束地址为 __bss_end。

*(.bss):匹配所有输入文件中的 .bss 段,并将其内容放入输出文件的 .bss 段中。在输入文件中,未初始化的全局变量和静态变量会被放入 .bss 段。 这些变量在编译时会被放入 .bss 段,并在程序启动时被初始化为零。

6. 编译烧写测试

按第3课操作,将程序烧写到SD卡,可以看到LED1在闪烁。

本文代码开源地址:

https://gitee.com/xundh/learn_i.mx6u.git

相关推荐
Dream_Snowar35 分钟前
速通Python 第三节
开发语言·python
XH华1 小时前
初识C语言之二维数组(下)
c语言·算法
高山我梦口香糖2 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
信号处理学渣2 小时前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客2 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
析木不会编程2 小时前
【小白51单片机专用教程】protues仿真独立按键控制LED
单片机·嵌入式硬件·51单片机
jasmine s2 小时前
Pandas
开发语言·python
biomooc2 小时前
R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)
开发语言·r语言
骇客野人2 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
black^sugar2 小时前
纯前端实现更新检测
开发语言·前端·javascript