ZeroOne AI
← 返回文章列表

IMX6ULL 裸机开发学习(二):C 语言点亮 LED

👁 30
分类:工业互联网

在汇编实验基础上,用 C 语言完成 IMX6ULL 裸机 LED 闪烁:start.S 启动文件(SVC模式+栈指针)、led.h/led.c 寄存器封装、链接脚本与 Makefile 编译,对比汇编方案体会工程化开发方式。

IMX6ULL 裸机开发学习(二):C 语言点亮 LED

一、项目背景

上一篇用汇编点亮了 LED,但真实项目不可能全部用汇编开发。C 语言才是嵌入式裸机与 Linux 驱动开发的主流语言。本篇文章在汇编实验基础上进行工程化升级:用 C 语言实现同样的 LED 闪烁功能,并引入启动文件、头文件封装、链接脚本与 Makefile 的完整工程范式。

二、技术方案:C 语言裸机工程结构

C 语言要运行起来,必须先由汇编准备好运行环境。整个工程分为三部分:

文件作用
start.S启动文件:配置 CPU 进入 SVC 模式、设置栈指针、跳转 main
led.h寄存器地址定义与 LED 操作宏封装
led.c时钟使能、GPIO 初始化、延时与 main 主逻辑
imxu.lds链接脚本:指定起始地址与段布局
Makefile编译、链接、烧写一体化

三、系统架构:启动文件 start.S

C 语言入口 main 需要两个前提:处理器处于特权模式、栈指针有效。start.S 完成这两件事:

/*******************
*文件名:  start.S
*描述  :裸机实验测试2 C语言点亮led
 ********************/

.global  _start      @全局标号

_start:

 /***** 1.配置CPU 进入SVC模式 *****/
 mrs r0,  cpsr                        @ 拷贝CPSR状态寄存器到R0
 bic r0,  r0,  #0x1f                  @ 清除CPU模式位BIT4-BIT0
 orr r0,  r0,  #0x13                  @ 配置BIT4-0为SVC模式
 msr cpsr, r0                         @ 写回CPSR

 /***** 2.设置堆栈指针并跳转C语言main函数 *****/
 ldr sp,  =0x80200000                 @ 设置堆栈 0x80200000 起点,2M空间
 b   main                             @ 跳转到C语言main函数

要点:

四、实施过程:C 代码与工程化构建

4.1 寄存器封装 led.h

/*******************
*文件名:  led.h
*描述  :led灯程序头文件,存放寄存器指针
 ********************/
#ifndef _H_LED_H
#define _H_LED_H

/* CCM时钟相关寄存器地址定义 */
#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)

/* IOMUX复用相关寄存器地址定义 */
#define SW_MUX_GPIO5_IO3           *((volatile unsigned int *)0x02290014)
#define SW_PAD_GPIO5_IO3           *((volatile unsigned int *)0x02290058)

/* GPIO5相关寄存器地址定义 */
#define GPIO5_DR                 *((volatile unsigned int *)0x020AC000)
#define GPIO5_GDIR               *((volatile unsigned int *)0x020AC004)

/* GPIO5_IO3 开漏输出控制宏 */
#define LED_ON()                  (GPIO5_DR &= ~(1<<3))
#define LED_OFF()                 (GPIO5_DR |= (1<<3))

#endif

使用 volatile 防止编译器优化掉寄存器读写,是操作物理寄存器的标准写法。

4.2 主程序 led.c

#include "led.h"

/* 使能所有外设控制时钟 */
void clk_enable(void)
{
    CCM_CCGR0 = 0xffffffff;
    CCM_CCGR1 = 0xffffffff;
    CCM_CCGR2 = 0xffffffff;
    CCM_CCGR3 = 0xffffffff;
    CCM_CCGR4 = 0xffffffff;
    CCM_CCGR5 = 0xffffffff;
    CCM_CCGR6 = 0xffffffff;
}

/* LED初始化:复用GPIO + 电气属性 + 方向输出 */
void led_Init(void)
{
    SW_MUX_GPIO5_IO3 = 0x5;        // ALT5 复用为GPIO
    SW_PAD_GPIO5_IO3 = 0x08B0;     // 电气属性配置
    GPIO5_GDIR = 0x08;             // BIT3输出
    LED_ON();
}

/* 约1ms单位延时 */
void delay_ms(volatile unsigned int nms)
{
    unsigned int nus;
    while (nms--)
    {
        nus = 0x7ff;               // 1ms延时
        while (nus--) { }
    }
}

int main(void)
{
    clk_enable();                  // 使能时钟
    led_Init();                    // 初始化led

    while (1)                      // LED闪烁循环
    {
        LED_OFF();
        delay_ms(500);
        LED_ON();
        delay_ms(500);
    }
    return 0;
}

4.3 链接脚本 imxu.lds

SECTIONS{
    . = 0x87800000;
    .text :
    {
        start.o       /* 启动文件必须放最前面 */
        led.o
        *(.text)
    }
    .rodata ALIGN(4) : { *(.rodata*) }
    .data   ALIGN(4) : { *(.data) }
    __bss_start = .;
    .bss   ALIGN(4) : { *(.bss) *(COMMON) }
    __bss_end = .;
}

链接脚本保证 start.S 的 _start 位于镜像最前面(0x87800000 起始),CPU 复位后第一条指令就是启动代码。

4.4 Makefile

objs := start.o led.o

led.bin : $(objs)
	arm-linux-gnueabihf-ld -Timxu.lds -o led.elf $^
	arm-linux-gnueabihf-objcopy -O binary -S led.elf $@

%.o : %.s
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

%.o : %.S
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

%.o : %.c
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

clean:
	rm -rf *.o led.bin led.elf led.dis *.imx

dump:
	arm-linux-gnueabihf-objdump -D -m arm led.elf > led.dis

dowmload:
	./imxdownload led.bin /dev/sdb

编译输出:

$ make
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o start.o start.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o led.o led.c
arm-linux-gnueabihf-ld  -Timxu.lds -o led.elf start.o led.o
arm-linux-gnueabihf-objcopy -O binary -S  led.elf led.bin

五、应用价值

六、SEO关键词

IMX6ULL、裸机开发、C语言点亮LED、start.S、链接脚本、arm-linux-gnueabihf、野火开发板、GPIO驱动、嵌入式开发、Makefile

推荐阅读

更多工业 AI、智能制造与技术实践文章

数码印刷

RIP 颜色管理之 LittleCMS:开源 ICC 色彩管理引擎

详解 LittleCMS(lcms)开源 ICC 色彩管理引擎在陶瓷喷码 RIP 软件中的核心作用:ICC Profile 与 cmsHPROFILE/cmsHTRANSFORM 数据结构、颜色转换流程、四种渲染意图选择、多色墨水多通道支持、批量转换性能优化与陶瓷 RIP 推荐架构。

数码印刷

CMYK色彩管理之色差调整

本文系统讲解CMYK色彩管理中的色差调整:RGB与CMYK色彩模式对比、CMYK色彩空间特点、色调/饱和度/亮度等色彩基本属性、印刷四色加法混色原理,以及色值精准调整的核心策略(判断偏色方向、定位问题通道、控制调整幅度)与Photoshop等图像处理软件中的色彩校对技巧,并给出常见色差问题的解决思路。

工业互联网

Linux系统篇(二十三)——通信(一):什么是进程间通信?一文搞懂背景与全貌

通过实验揭示进程隔离的本质,系统梳理Linux进程间通信的六种机制:管道、消息队列、共享内存、信号量、信号与Socket,并给出选型方法。

数码印刷

CMYK色彩管理之印刷配色

本文系统讲解CMYK色彩管理的全流程:RGB与CMYK加色法/减色法的原理差异、常见CMYK配比与总墨量限制(TAC)、ICC配置文件与常用标准(ISO Coated V2/Japan Color 2011等)、印刷色差五大原因与打样方式对比,以及从设计、输出到印刷沟通的标准工作流程与常见问题解答。

AI视觉

树莓派部署YOLOv8手势检测实战:USB IMX378摄像头 + Python + PyTorch + ONNX Runtime + Systemd开机自动运行

本文面向树莓派AI视觉、Python人工智能、YOLOv8目标检测初学者,以树莓派部署手势检测项目为案例,完整介绍从USB IMX378摄像头安装、Miniconda环境配置、Python虚拟环境创建、YOLOv8依赖安装,到手势检测程序Systemd开机自动运行的完整流程。在Raspberry Pi 5 / ARM64(aarch64)环境下建立独立的yolov8-env环境,安装PyTorch、Ultralytics、ONNX Runtime、NumPy等AI视觉组件,最后通过Linux systemd使fatigueDetect.py开机自动运行,实现无需手工登录的AI视觉自动检测,为后续手势识别、人体检测、疲劳驾驶检测、工业视觉检测与边缘AI视觉系统打下基础。

工业互联网

局域网服务器如何实现外网访问:Ubuntu 22.04 搭建 FRP 内网穿透服务器

工业现场、企业局域网和工厂 MES 系统中,服务器没有公网 IP 时如何实现外网远程访问?本文以 Ubuntu 22.04 为例,完整演示 FRP 内网穿透方案:frps 服务端配置、frpc 客户端配置、Systemd 开机自启、多工厂统一接入、HTTPS 安全架构以及完整的故障排查与安全建议。

评论(0)