【裸机驱动LED】使用C代码驱动LED(三)—— C代码编写篇

news2024/11/23 10:48:17

前面只使用了汇编代码来驱动LED,但是对于后续一些比较复杂的逻辑,使用汇编代码编写驱动的难度太大,因此,这次我们要使用C语言代码来驱动LED。

除了C代码外,依然需要编写汇编代码,在没有OS的情况下,环境的初始化是需要通过汇编代码来完成的。

  • 汇编文件:用于完成C代码的环境搭建
  • C文件:实现驱动逻辑

目录

一、start.s 汇编文件

1、编写思路

2、具体实现

3、完整汇编实现

二、头文件 register.h

1、C代码访问寄存器的方式

2、register.h 文件

三、C 文件 led.c

1、初始化时钟源

2、设置IO复用、初始化GPIO

3、GPIO 输出(LED 亮灭控制)

4、延时函数

5、完整 led.c 文件

四、Makefile文件


一、start.s 汇编文件

1、编写思路

这里的汇编代码仅用于C代码的环境搭建,初始化环境的方式和 stm32 类似,这里我们可以先参考stm32 的初始化步骤:

  • 设置栈大小
  • 初始化SP指针
  • 设置堆大小
  • 中断复位
  • 其他初始化(如DDR初始化)
  • 跳转到 main 函数

但是对于 I.MX6U 而言,无需这么复杂,也不需要进行DDR的初始化,因为在 bin 文件的头部信息中,DCD部分已经初始化了DDR。基本步骤如下:

  • 设置为 SVC 模式
  • 初始化 SP 指针
  • 跳转到 main 函数

2、具体实现

切换 SVC 模式

切换到SVC模式要用到控制寄存器 CPSR,其中控制模式切换的是第 4-0 bit。因此,切换到 SVC 模式的步骤如下:

  • 取出 cpsr 寄存器中的值,保存到 r0。(目的是防止因为误操作而改变 cpsr 其他位的值)
  • r0 寄存器将低五位清零。(低五位用于控制模式切换)
  • r0 的低五位或上 10011(0x13)
  • 将修改以后的值写入到 cpsr 

工作模式取值(左边高位,右边低位)
User10000
FIQ10001
IRQ 10010
SVC10011
Abort 10111
Undef 11011
System11111
Monitor10110
/* CPSR 比较特殊,不能使用mov或者ldr访问 */
/* 必须使用 mrs指令访问 */
mrs r0, cpsr        @ 将 cpsr 寄存器的内容读取到 r0 寄存器
bic r0, r0, #0x1f   @ 将 r0 的低五位清零,运算结果放到 r0
orr r0, r0, #0x13   @ 让 r0 的低五位或上 10011(0x13),结果保存到 r0
msr cpsr, r0        @ 将 r0 的值写入到 cpsr 寄存器

参考链接:

 CPSR 寄存器详解

 访问(读写)CPSR寄存器

数据处理指令 —— 位清零(bic)、或(orr)

初始化 SP 指针 

C 语言调用main函数时,会为main函数创建堆栈,其中 SP指针表示栈顶的地址。那么SP 地址应该指向多少呢?对此我们需要知道如下几点:

  • 栈区大小分配 2M。2 M的栈空间只要不出现无限递归,一般没什么大问题
  • 代码的运行在DDR,DDR的范围为 0x80000000 ~ 0xA0000000(512M)
  • ARM 处理器是满减栈,即由高地址向低地址存储。也就是说,每存一个数据,地址自减

栈是反向增长的,而且栈大小为 2M,因此,SP 一开始应指向 0x80200000 

/* 初始化栈指针 */
ldr sp,=0x80200000  @ 设置栈指针
b main              @ 跳转到 main 函数

3、完整汇编实现

.global _start

_start:
    /* 切换到 SVC 模式 */
    mrs r0, cpsr        @ 将 cpsr 寄存器的内容读取到 r0 寄存器
    bic r0, r0, #0x1f   @ 将 r0 的低五位清零,运算结果放到 r0
    orr r0, r0, #0x13   @ 让 r0 的低五位或上 10011(0x13),结果保存到 r0
    msr cpsr, r0        @ 将 r0 的值写入到 cpsr 寄存器

    /* 初始化栈指针 */
    ldr sp,=0x80200000  @ 设置栈指针
    b main              @ 跳转到 main 函数

二、头文件 register.h

这里我们依然是通过地址来访问寄存器,只不过直接使用地址可读性太差,我们可以将要用到的寄存器地址保存到头文件,并声明成宏。

1、C代码访问寄存器的方式

之前在使用汇编驱动LED的时候,我们是通过访问寄存器的地址来写入内容的,C代码也可以访问地址,因为C语言中的指针,某种意义上就可以看做是一个地址。

比如我们要访问 0x20C4068,如果我们直接 *(0x20C4068),那么解引用的时候,应该访问多少个字节呢?指针类型决定了每次访问的字节数。一个寄存器的大小是4个字节,所以应该对这个地址强制类型转换成 unsigned int

*((unsigned int*)0x20C4068)

这里最好再加一个 volatile 关键字,volatile提醒编译器它后面所定义的变量随时都有可能改变,每次使用该变量时都去读取内存地址。(因为编译器可能图省事,直接把内容保存到CPU的寄存器,每次读的是CPU寄存器的内容)

*((volatile unsigned int*)0x20C4068)

2、register.h 文件

比如我们要初始化 CCR0 时钟源,CCR0 寄存器的地址为 0x20C4068,那就是

*((volatile unsigned int*)0x20C4068) = 0xffffffff;

这样的可读性实在太差,所以我们打算给等号左边起个好懂一点的名字,下面就是 register.h 文件中的内容(关于寄存器的地址,参考这篇:裸机驱动LED —— 寄存器解析篇)

#ifndef _register_h
#define _register_h

typedef unsigned int uint32_t;

/*
 * 时钟相关寄存器地址
 */
#define CCM_CCGR0          *((volatile uint32_t*)0x20C4068)
#define CCM_CCGR1          *((volatile uint32_t*)0x20C406C)
#define CCM_CCGR2          *((volatile uint32_t*)0x20C4070)
#define CCM_CCGR3          *((volatile uint32_t*)0x20C4074)
#define CCM_CCGR4          *((volatile uint32_t*)0x20C4078)
#define CCM_CCGR5          *((volatile uint32_t*)0x20C407C)
#define CCM_CCGR6          *((volatile uint32_t*)0x20C4080)

/*
 * IOMUX 相关寄存器地址
 */
#define SW_MUX_GPIO1_IO03   *((volatile uint32_t*)0x020E0068)        // 设置IO复用
#define SW_PAD_GPIO1_IO03   *((volatile uint32_t*)0x020E02F4)        // 设置电气属性

/*
 * 设置GPIO输出相关寄存器地址
 */
#define GPIO1_DR            *((volatile uint32_t*)0x0209C000)         // GPIO输出
#define GPIO1_GDIR          *((volatile uint32_t*)0x0209C004)         // 设置输入还是输出

#endif

三、C 文件 led.c

C 代码的实现就可以参考之前使用汇编代码驱动LED的步骤了。

  • 初始化时钟源
  • 设置IO复用为GPIO
  • 初始化GPIO(设置电气属性)
  • 设置GPIO输出

初始化寄存器的值,在之前的博客已经介绍了,可以参考:裸机驱动LED —— 寄存器解析篇

1、初始化时钟源

void clk_enable()
{
    GPIO_CCGR0 = 0xffffffff;
    GPIO_CCGR1 = 0xffffffff;
    GPIO_CCGR2 = 0xffffffff;
    GPIO_CCGR3 = 0xffffffff;
    GPIO_CCGR4 = 0xffffffff;
    GPIO_CCGR5 = 0xffffffff;
    GPIO_CCGR6 = 0xffffffff;
}

2、设置IO复用、初始化GPIO

void led_init()
{
    /* 1、设置GPIO复用 */
    SW_MUX_GPIO1_IO03 = 0x5;

    /* 2、设置GPIO电气属性 */
    SW_PAD_GPIO1_IO03 = 0x10B0;

    /* 3、GPIO 设为输出 */
    GPIO1_GDIR = 0x00000008;
}

3、GPIO 输出(LED 亮灭控制)

 LED低电平亮,高电平灭。0x08 代表第 3 bit的位置为高电平,其实简单粗暴一点,GPIO1_DR = 0 也是可以的,GPIO1_DR |= 0x08 的目的是不影响其他位置。

void led_on()
{
    GPIO1_DR &= (~0x08);
}

void led_off()
{
    GPIO1_DR |= 0x08;
}

4、延时函数

因为本次的目的不止是让LED亮,还要让LED闪烁,这就需要用到延时函数

void delay(unsigned int n)
{
    while (n--)
    {
        delay_short(0x7ff);
    }
}

void delay_short(unsigned int n)
{
    while(n--) {}
}

5、完整 led.c 文件

#include "register.h"

void clk_enable();                      // 时钟源初始化
void led_init();                        // 设置IO复用为GPIO、初始化GPIO
void delay_short(unsigned int n);       // 短时延时
void delay(unsigned int n);             // 延时
void led_on();                          // 点灯
void led_off();                         // 熄灯

int main(void)
{
    /* 1、初始化时钟源 */
    clk_enable();

    /* 2、初始化LED */
    led_init();

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

        led_off();
        delay(500);
    }

    return 0;
}

void clk_enable()
{
    CCM_CCGR0 = 0xffffffff;
    CCM_CCGR1 = 0xffffffff;
    CCM_CCGR2 = 0xffffffff;
    CCM_CCGR3 = 0xffffffff;
    CCM_CCGR4 = 0xffffffff;
    CCM_CCGR5 = 0xffffffff;
    CCM_CCGR6 = 0xffffffff;
}

void led_init()
{
    /* 1、设置GPIO复用 */
    SW_MUX_GPIO1_IO03 = 0x5;

    /* 2、设置GPIO电气属性 */
    SW_PAD_GPIO1_IO03 = 0x10B0;

    /* 3、GPIO 设为输出 */
    GPIO1_GDIR = 0x00000008;
}

void delay(unsigned int n)
{
    while (n--)
    {
        delay_short(0x7ff);
    }
}

void delay_short(unsigned int n)
{
    while(n--) {}
}

void led_on()
{
    GPIO1_DR &= (~0x08);
}

void led_off()
{
    GPIO1_DR |= 0x08;
}

四、Makefile文件

之前只有 .s 文件,现在既有 .s 文件,也有 .c 文件,因此我们需要分别将 .s 文件和 .c 文件先转化为 .o 文件,然后再转化为 elf 文件

注意下面的 OBJS 变量,start.o 必须放在其他 .o 文件的前面,因为 start.s 作用是搭建C环境,所以必须在运行 led.c 之前运行。下面就是 start.o 没有放在最开始的后果:

TOOLCHAIN_PATH		:= /home/pigeon/workspace/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
CC					:= $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-gcc
LD					:= $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-ld
OBJCOPY				:= $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-objcopy
OBJDUMP				:= $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-objdump

OBJS				:= start.o led.o

led.bin: $(OBJS)
	$(LD) -Ttext 0x87800000 -o led.elf $^
	$(OBJCOPY) -O binary -S -g led.elf $@
	$(OBJDUMP) -D led.elf > led.dis

# 将 .s 转化为 .o
%.o: %.s
	$(CC) -c -Wall -nostdlib -o $@ $<
# 将 .c 转化为 .o
%.o: %.c
	$(CC) -c -Wall -nostdlib -o $@ $<

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/625154.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于黄金莱维引导机制的阿基米德优化算法(MSAOA)-附代码

基于黄金莱维引导机制的阿基米德优化算法(MSAOA) 文章目录 基于黄金莱维引导机制的阿基米德优化算法(MSAOA)1.阿基米德优化算法2. 改进阿基米德优化算法2.1 变区间初始化策略2.2 黄金莱维引导机制2.3 自适应波长算子 3.实验结果4.参考文献5.Matlab代码6.Python代码 摘要&#x…

什么是接口测试?怎么做接口测试?Apifox 教你做!

目录 前言&#xff1a; 一、什么是接口测试&#xff1f; 二、接口测试的步骤 三、接口测试工具的选择 四、总结 前言&#xff1a; 随着互联网和移动互联网的发展&#xff0c;企业面对着越来越庞大和复杂的系统和数据接口。在这种情况下&#xff0c;手动测试不再能够满足测…

修复uproject右键菜单完全解决方案办法

在你电脑中找到UnrealVersionSelector通常和epiclauncher是同一个根目录 epiclauncher路径是:D:\MyEpic\Epic Games\Launcher\Portal\Binaries\Win32\EpicGamesLauncher.exe UnrealVersionSelector.exe的路径是:D:\\MyEpic\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\Un…

探索新科技:3DVR电子楼书引领视觉体验革命

导语&#xff1a; 近年来&#xff0c;科技的迅猛发展引领着我们进入一个全新的数字化时代。在这个时代&#xff0c;虚拟现实和电子书成为了许多领域的热门话题。 接下来&#xff0c;让我们一起探索一个引人注目的技术创新&#xff0c;将虚拟现实和电子书完美结合的3DVR电子楼书…

python基本语法知识(二)

杂项 如果一个制表符/t不能对齐&#xff0c;可以多加几个&#xff1b; 只有将字典转换为字符串的时候才会保留字典的value&#xff0c;转成集合、列表、元组、都会丢失value 函数 例子1&#xff1a; str1 "hello world" # 函数定义 def my_len(data):count 0f…

基于 Ray 的大规模离线推理

本文整理自字节跳动基础架构资深研发工程师王万兴在火山引擎开发者社区 Meetup 中的分享。大模型离线推理&#xff0c;是指在具有数十亿或数千亿参数的大规模模型上进行分布式推理的过程。相较于常规模型推理&#xff0c;在模型切分、数据处理和数据流、提升 GPU 利用率方面面临…

ROCK PI S音频开发(一)系统准备

1、连接WIFI sudo nmcli r wifi on sudo nmcli dev wifi sudo nmcli dev wifi connect "SSID" password "PASSWORD" 2、更新源 sudo apt-get update sudo apt-get install git wget export DISTROfocal-stable wget -O - apt.radxa.com/$DISTRO/publ…

PHP快速实战20-PHP7中的垃圾回收机制与原理讲解

文章目录 前言PHP垃圾回收实现的原理垃圾回收机制引用计数循环垃圾收集 实现原理 总结 前言 本文已收录于PHP全栈系列专栏&#xff1a;PHP快速入门与实战 在计算机程序中&#xff0c;垃圾回收指的是一种自动管理内存的技术。在程序执行过程中&#xff0c;分配给它的内存会随着…

2023年软件测试趋势?测试人的发展前景?“我“到底该如何走...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 现在开始学习软件…

NetSuite 中国财务常用报表功能包

目录 1.致谢 2.功能说明 2.1 概述 2.2 报表说明 3.安装 4.操作指南 4.1 CLR_资产负债表 4.2 CLR_资产负债表&#xff08;期初/发生/结余&#xff09; 4.3 CLR_利润表 4.4 CLR_利润表季报 4.5 CLR_现金流量表 4.6 CLR_现金流量表季报 4.7 CLR_总账 4.8 CLR_序时账…

《消息队列高手课》课程学习笔记(八)

如何实现高性能的异步网络传输&#xff1f; **异步与同步模型最大的区别是&#xff0c;同步模型会阻塞线程等待资源&#xff0c;而异步模型不会阻塞线程&#xff0c;它是等资源准备好后&#xff0c;再通知业务代码来完成后续的资源处理逻辑。**这种异步设计的方法&#xff0c;…

深入理解深度学习——注意力机制(Attention Mechanism):注意力评分函数(Attention Scoring Function)

分类目录&#xff1a;《深入理解深度学习》总目录 《深入理解深度学习——注意力机制&#xff08;Attention Mechanism&#xff09;&#xff1a;注意力汇聚与Nadaraya-Watson 核回归》中使用了高斯核来对查询和键之间的关系建模。式中的高斯核指数部分可以视为注意力评分函数&a…

Spark笔记

DBeaver数据库连接器 Download | DBeaver Community shell命令 bin/spark-submit –class cn.edu.ncut.sparkcore.wordcount.Test03_WordCount_cluster –deploy-mode cluster –master yarn ./sparkcore-1.0-SNAPSHOT.jar 10 血缘关系查看 toDebugString()&#xff1a…

深入篇【Linux】学习必备:【文本编辑器】vim的基本介绍及使用

深入篇【Linux】学习必备&#xff1a;【文本编辑器】vim的基本介绍及使用 Ⅰ.vim基本简介Ⅱ.vim的基本操作⏰【命令模式下】1.移动光标2.复制删除粘贴3.替换更改4.撤销指令 ⏰【底行模式下】1.查找字符2.保存退出3.查看所有模式 Ⅲ.简单vim配置1.配置文件位置2.使用插件 Ⅰ.vim…

前后端交互三、Ajax加强

零、文章目录 前后端交互三、Ajax加强 1、XMLHttpRequest的基本使用 &#xff08;1&#xff09;什么XMLHttpRequest XMLHttpRequest&#xff08;简称 xhr&#xff09;是浏览器提供的 Javascript 对象&#xff0c;通过它&#xff0c;可以请求服务器上的数据资源。jQuery 中的…

力扣 209. 长度最小的子数组

一、题目描述 给定一个含有 n 个正整数的数组和一个正整数 target。 找出该数组中满足其和大于等于 target 的长度最小的连续子数组&#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0。 示例 1&#xff1a; 输入&#xff1a;target 7, nums [2,3,1…

Servlet详解

目录 一. Servlet介绍 1.1 概念 2.2 Servlet架构 二. 创建一个Servlet程序 2.1 创建一个Maven项目 2.2 引入 jar 包 2.3 创建目录 2.4 编写代码 2.5 打包程序 2.6 部署程序 2.7 验证程序 三. Servlet常用API 3.1 HttpServlet 3.2 HttpServletRequest 3.2 HttpServlet…

深入了解SSM框架(案例(SSM+Jsp) + 详细分析 + 思维导图)

1.Spring Spring就像是整个项目中装配bean的大工厂&#xff0c;在配置文件中可以指定使用特定的参数去调用实体类的构造方法来实例化对象。也可以称之为项目中的粘合剂。 Spring的核心思想是IoC&#xff08;控制反转&#xff09;&#xff0c;即不再需要程序员去显式地new一个…

swagger 接口测试,用 python 写自动化时该如何处理?

在使用Python进行Swagger接口测试时&#xff0c;可以使用requests库来发送HTTP请求&#xff0c;并使用json库和yaml库来处理响应数据。以下是一个简单的示例代码&#xff1a; 如果你想学习自动化测试&#xff0c;我这边给你推荐一套视频&#xff0c;这个视频可以说是B站百万播…

vim编辑器基本使用

一、写在前面 今天在练习git相关操作时&#xff0c;无意间发现当你使用commit命令提交代码时&#xff0c;忘记添加备注信息会自动进入一个奇怪的模式&#xff0c;按esc键亦或是ctrlC都无法退出&#xff0c;这个奇怪的模式也就是vim编辑器。如下图&#xff1a; vim是一种文本…