【Linux学习】正点原子裸机篇 C语言LED实验实现

2023-05-16

        上一篇使用汇编语言编写LED等驱动实验,在实际工作中较少使用汇编编写嵌入式驱动,毕竟汇编过于底层,难度较大。绝大部分情况下都是使用C语言编写主体程序,只是开始部分使用汇编来初始化C语言环境,比如初始化DDR、设置堆栈指针SP等。因此可理解为下面三部分:

1)设置处理器模式

        设置6ULL的处于SVC模式下,设置CPSR寄存器的bit4-0,也就是M[4:0]为10011=0X13。读写状态寄存器需要用到MRS和MSR指令。MRS将CPSR寄存器数据读出到通用寄存器里,MSR指令将通用寄存器的值写入到CPSR寄存器里面。

2)设置sp指针(C语言需要出栈和入栈)

        Sp可以指向内部RAM,可以指向DDR,目前指向DDR,Sp设置到哪里?512MB的范围是0X80000000-0X9FFFFFFF。栈大小,0x200000=2MB。处理器栈增长方式,对于A7而言向下增长,设置sp指向0x80200000。

3)C语言文件编写

        C语言文件是我们要完成的业务层代码,即实际例程要完成的功能。

1. 实验程序编写

        新建 VScode 工程,工程名字为“ledc”,新建三个文件:start.Smain.c main.h。其中 start.S 是汇编文件,main.c main.h C 语言相关文件。

  1.1 start.S 是汇编文件编写:

.global _start

_start:
/*设置 处理器进入SVC模式*/
    mrs r0, cpsr        /*读取cpsr到r0 */
    bic r0, r0, #0x1f   /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */
    orr r0, r0, #0x13   /* r0 或上 0x13,表示使用 SVC 模式 */
    msr cpsr, r0        /*将r0写入到cpsr */

/*设置SP指针 */
  ldr sp, 0x80200000   /*已经sp设置到ddr里面 */
  b main               /*跳转到C语言main函数 */

/*第1行是定义全局标号_start;

第3行是标号_start开始的地方,相当于_start函数;

第5-8行是设置处理器进入SVC模式;

第11行是通过ldr指令设置SVC模式下SP指针=0X80200000;

第12行是通过b指令,跳转到C语言函数,比如main函数;*/

  1.2 main.h 文件编写

        以宏定义的形式定义了要使用到的所有寄存器,后面的数字代表其地,比如 CCM_CCGR0 寄存器的地址就是 0X020C4068

#ifndef __MAIN_H
#define __MAIN_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_GPIO1_IO03 *((volatile unsigned int *)0X020E0068)
#define SW_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)
#define GPIO1_ICR1 *((volatile unsigned int *)0X0209C00C)
#define GPIO1_ICR2 *((volatile unsigned int *)0X0209C010)
#define GPIO1_IMR *((volatile unsigned int *)0X0209C014)
#define GPIO1_ISR *((volatile unsigned int *)0X0209C018)
#define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C)

#endif

  1.3 main.c文件编写

        main.c文件共6个函数:

        1.clk_enable 函数是使能 CCGR0~CCGR6 所控制的所有外设时钟;

        2. led_init 函数是初始化 LED 灯所使用的 IO,包括设置 IO 的复用功能、IO 的属性配置和 GPIO 功能,最终控制 GPIO 输出低电平来打开 LED 灯;

        3. led_on led_off ,用来控制 LED 灯的亮灭的;

        4.delay_short()delay() 这两个函数是延时函数,delay_short()函数是靠空循环来实现延时的,在396Mhz工作主频下实现约1ms的延时.

#include "main.h"
/* 使能外设时钟函数*/
void clk_enable(void)
{
    CCM_CCGR1 = 0xFFFFFFFF;
    CCM_CCGR2 = 0xFFFFFFFF;
    CCM_CCGR3 = 0xFFFFFFFF;
    CCM_CCGR4 = 0xFFFFFFFF;
    CCM_CCGR5 = 0xFFFFFFFF;
    CCM_CCGR6 = 0xFFFFFFFF;
}

/*初始化led函数
    bit 16:0 HYS 关闭
    bit [15:14]: 00 默认下拉
    bit [13]: 0 kepper 功能
    bit [12]: 1 pull/keeper 使能
    bit [11]: 0 关闭开路输出
    bit [7:6]: 10 速度 100Mhz
    bit [5:3]: 110 R0/6 驱动能力
    bit [0]: 0 低转换率*/
void led_init(void)
{
    SW_MUX_GPIO1_IO03 = 0x5;     /*复用为GPIO1-IO03*/
    SW_PAD_GPIO1_IO03 = 0X10B0;   /*设置GPIO1-IO03电气属性*/

    /*GPIO初始化*/
    GPIO1_GDIR = 0X8;    /*设置为输出*/
    GPIO1_DR = 0X0;      /*打开led灯*/
}
/*短延时*/
void delay_short(volatile unsigned int n)
{
    while(n--){}
}
/*延迟,一次循环大概是1ms,n:延时ms数,在主频396MHz*/
void delay(volatile unsigned int n)
{
    while(n--){
        delay_short(0x7ff);
    }
}
/*打开LED灯*/
void led_on(void)
{
    GPIO1_DR &=~(1<<3); /*bit3清零*/
}

/*关闭LED灯*/
void led_off(void)
{
    GPIO1_DR |=(1<<3); /*bit3置1*/
}


int main(void)
{
    clk_enable();
    led_init();
    /*初始化LED*/

    /*设置LED*/
    while(1){
        led_on();
        delay(500);

        led_off();
        delay(500);
    }
    return 0;
}

2. 编译下载 

  2.1 编写Makefile

objs = start.o main.o

ledc.bin: $(objs)
	arm-linux-gnueabihf-ld -Timx6u.lds -o ledc.elf $^
	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
	arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis

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

clean:
	rm -rf *.o ledc.bin ledc.elf ledc.dis

/*
第 1 行: 定义了一个变量 objs,objs 包含着要生成 ledc.bin 所需的材料:start.o 和 main.o, start.o 一定要放到 最前面!因为在链接的时 start.o 要在最前面,start.o 是最先要执行的文件!
 
第 3 行: 默认目标,目的是生成最终的可执行文件ledc.bin,ledc.bin 依赖 start.o 和 main.o 。

第 4 行: 是使用 arm-linux-gnueabihf-ld 进行链接,链接起始地址是 0X87800000,但是这一行 用到了自动变量“$^”,“$^”的意思是所有依赖文件的集合,在这里就是 objs 这个变量的值: arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^表示使用 imx6ul.lds 这个链接脚本文件

第 5 行: 使用 arm-linux-gnueabihf-objcopy 来将 ledc.elf 文件转为 ledc.bin,本行也用到了自动变量 “$@”,“$@”的意思是目标集合,在这里就是“ledc.bin”,那么本行就相当于: 
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf ledc.bin

第 6 行: 使用 arm-linux-gnueabihf-objdump 来反汇编,生成 ledc.dis 文件。 

第 8~13行: 针对不同的文件类型将其编译成对应的.o 文件,其实就是汇编.s(.S)和.c 文件。

第 14行: 工程清理规则,通过命令“make clean”就可以清理工程

*/

  2.2  链接脚本文件

SECTIONS{
    . = 0x87800000  //链接到0x87800000,从这里开始
    .text :
    {
        start.o   //第一个代码部分
        *(.text)
    }
    .rodata ALIGN(4) : {*(.rodata*)}     //指定只读数据段 4字节对齐
    .data ALIGN(4) : {*(.data)}          //指定读/写数据段 4字节对齐
    __bss_start=.                        //把__bss_start定义为当前位置(起始)
    .bss ALIGN(4) : {*(.bss)*(COMMON)}
    __bss_end=.;                         //把__bss_end定义为当前位置(终地址)
}
    /*. 代表当前
      .text 代表代码段
      .rodata 只读数据段
      .data读/写数据段
      ALIGN(4) 四字节对齐
      .bss 未初始化的数据段
第 1 行:关键字“SECTIONS”;

第 2 行:对一个特殊符号“.”进行赋值,“.”在链接脚本里面叫做定位计数器,默认的定位计数器为0。赋值代表代码链接到以 0X10000000 为起始地址的地方开始;

第 3 行的:“.text”是段名,后面的冒号是语法要求,冒号后面的大括号里面可以填上要链 接到“.text”这个段里面的所有文件,“*(.text)”中的“*”是通配符,表示所有输入文件的.text 段都放到“.text”中;

第 4 行:重新调整定位计数器为 0X30000000;

第 5 行:与第3行相同,ALIGN(4)表示 4 字节对齐。也就是说段“.data”的起始地址要能被 4 整除,一般常见的都是 ALIGN(4)或者 ALIGN(8),也就是 4 字节或者 8 字节对齐;

第 6 行:定义了一个“.bss”段,所有文件中的“.bss”数据都会被放到这个里面,“.bss”数据就是那些定义了但是没有被初始化的变量;

第11-12行:__bss_start” 和“__bss_end”是符号,第 11、13 这两行其实就是对这两个符号进行赋值,其值为定位符“.”, 这两个符号用来保存.bss 段的起始地址和结束地址

  2.3 make和烧写SD卡

        烧写到SD卡中:

        给予imxdownload可执行权限:chmod 777 imxdownload

        烧写命令:./imxdownload led.bin  /dev/sdb

        Imxdownload会向led.bin添加一个头部,生成新的load.imx文件,这个load.imx文件就是最终烧写到SD卡里面。

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux学习】正点原子裸机篇 C语言LED实验实现 的相关文章

  • 「NeurIPS 2020」基于局部子图的图元学习

    点击蓝字 xff0c 设为星标 NeurIPS 2020 的接收论文 Graph Meta Learning via Local Subgraphs xff0c G META 是第一个使用局部子图来进行元学习的模型 Graph Meta L
  • Keras:Input()函数

    目录 1 Keras Input 函数 2 函数定义 xff1a 3 参数解释 4 例子 1 Keras Input 函数 作用 xff1a 初始化深度学习网络输入层的tensor 返回值 xff1a 一个tensor 2 函数定义 xff
  • JDBC入门笔记

    目录 1 xff0c JDBC概述 1 1 JDBC概念 2 xff0c JDBC快速入门 Java操作数据库的流程 2 1 编写代码步骤 3 JDBC API详解 3 1 DriverManager 3 2 Connection 3 2
  • 对抗样本入门详解

    文章目录 对抗样本基本原理对抗样本的发生对抗样本防御难在哪里对抗训练隐藏梯度defensive distillation 对抗样本的生成对抗样本生成方法介绍利用GAN生成对抗样本利用FGSM生成对抗样本代码复现 xff08 基于mnist
  • white/black-box attack(黑盒白盒攻击基础)

    基本概念 攻击方法分类标准 xff1a 假正性攻击 false positive 与伪负性攻击 false negative 假正性攻击 xff1a 原本是错误的但被被攻击模型识别为正例的攻击 eg 一张人类不可识别的图像 xff0c 被D
  • KL散度公式详解

    目录 文章目录 Jensen 39 s inequality讲解KL散度 xff08 又名relative entropy xff09 mutual information Jensen s inequality f x
  • 元学习算法MAML论文详解

    论文信息 题目 xff1a Model Agnostic Meta Learning for Fast Adaptation of Deep Networks 作者 xff1a Chelsea Finn 伯克利大学 xff0c Pieter
  • PINN内嵌物理知识神经网络入门及文献总结

    喜欢可点赞关注 xff0c 并收藏 xff0c 您的支持就是我的写作的动力 文章目录 一 PINN介绍二 物理信息驱动深度学习动手编程教程三 物理信息驱动深度学习方法几点讨论四 相关论文1 定义问题 建立工程架构2 网络结构选择3 不确定性
  • 极限学习机(Extreme Learning Machine,ELM)详解

    ELM ELM的提出目的是为了解决人工神经网络训练时耗费的时间和高成本 对此 xff0c 2004年由南洋理工大学的黄广斌提出了ELM学习理论 机器或者生物学习可以不需要调整隐层节点 xff1a 给定任何连续目标函数或者可分类目标 xff0
  • PINN深度学习求解微分方程系列一:求解框架

    下面我将介绍内嵌物理知识神经网络 xff08 PINN xff09 求解微分方程 首先介绍PINN基本方法 xff0c 并基于Pytorch框架实现求解一维Poisson方程 内嵌物理知识神经网络 xff08 PINN xff09 入门及相
  • PINN深度学习求解微分方程系列三:求解burger方程逆问题

    下面我将介绍内嵌物理知识神经网络 xff08 PINN xff09 求解微分方程 首先介绍PINN基本方法 xff0c 并基于Pytorch的PINN求解框架实现求解含时间项的一维burger方程逆问题 内嵌物理知识神经网络 xff08 P
  • 深度学习求解微分方程系列五:PINN求解Navier-Stokes方程正逆问题

    下面我将介绍内嵌物理知识神经网络 xff08 PINN xff09 求解微分方程 首先介绍PINN基本方法 xff0c 并基于Pytorch的PINN求解框架实现求解含时间项的二维Navier Stokes方程 内嵌物理知识神经网络 xff
  • Keras解读:使用Model()类构建网络模型的底层原理

    目录 一 前言 xff1a 二 topology py脚本简述 三 继承了Layer类的子类一般要实现 xff08 重写 xff09 以下methods xff1a 四 Node layer tensor在网络构建过程中的关系 建议结合源码
  • 【Paraview教程】第一章安装与基础介绍

    1 Paraview介绍 1 1基本介绍 ParaView是一个开源的 xff0c 跨平台的数据处理和可视化程序 ParaView用户可以迅速的建立起可视化环境利用定量或者是定性的手段去分析数据 利用它的批量处理能力可以在三维空间内在工具栏
  • 一种基于物理信息极限学习机的PDE求解方法

    作者 PINN山里娃 xff0c 作者主页 研究方向 物理信息驱动深度学习 不确定性 人工智能 偏微分方程 极限学习机 该作者聚焦深度学习模型与物理信息结合前沿研究 xff0c 提供了一系列AI for science研究进展报告及代码实现
  • js文件分片上传,断点续传

    前言 文件上传是一个老生常谈的话题了 xff0c 在文件相对比较小的情况下 xff0c 可以直接把文件转化为字节流上传到服务器 xff0c 但在文件比较大的情况下 xff0c 用普通的方式进行上传 xff0c 这可不是一个好的办法 xff0
  • JavaScript 大文件分片上传处理

    一 功能性需求与非功能性需求 要求操作便利 xff0c 一次选择多个文件和文件夹进行上传 xff1b 支持PC端全平台操作系统 xff0c Windows Linux Mac 支持文件和文件夹的批量下载 xff0c 断点续传 刷新页面后继续
  • JS实现浏览器端大文件分片上传

    IE的自带下载功能中没有断点续传功能 xff0c 要实现断点续传功能 xff0c 需要用到HTTP协议中鲜为人知的几个响应头和请求头 一 两个必要响应头Accept Ranges ETag 客户端每次提交下载请求时 xff0c 服务端都要添
  • Lua识别Jwt令牌业务

    文章目录 业务场景业务实现lua resty jwt安装令牌识别令牌测试 业务场景 如果想使用Lua识别用户令牌 xff0c 我们需要引入lua resty jwt模块 xff0c 是用于 ngx lua 和 LuaJIT 的 Lua 实现
  • WebSocket从入门到实战

    文章目录 WebSocketWebSocket 介绍WebSocket APIWebSocket 对象WebSocket属性WebSocket事件 xff1a WebSocket方法 xff1a WebSocket 实例客户端服务端代码链接

随机推荐