首页 > 系统相关 >linuxC语言点灯

linuxC语言点灯

时间:2024-05-10 10:45:40浏览次数:28  
标签:r0 点灯 linux bss rodata ledc 寄存器 C语言 链接

大部分情况下都是使用 C 语言去编写的。只是在开始部分用汇编来初始化一下 C 语言环境,比如初始化 DDR、设置堆栈指针 SP 等等,当这些工作都做完以后就可以进入 C 语言环境,也就是运行 C 语言代
码,一般都是进入 main 函数。所以我们有两部分文件要做:
①、汇编文件
汇编文件只是用来完成 C 语言环境搭建。
②、C 语言文件
C 语言文件就是完成我们的业务层代码的,其实就是我们实际例程要完成的功能。

配置vscode语言环境

这节课我们可以下载vscode去编写代码,用windows远程虚拟机中.具体配置过程如下:
首先下载版本低于等于1.85的。并把自动更新给关闭掉。下载链接为:1.85版本
在这里插入图片描述
然后安装插件:
在这里插入图片描述
然后这里要配置好你虚拟机端口的环境,确认22端口不在防火墙内。
在Ubuntu中打开终端。运行以下命令1下载安装SSH。

sudo apt update
sudo apt install openssh-server

等待安装完成后,运行以下命令查看SSH服务的状态。

sudo systemctl status ssh

在这里插入图片描述
接下来还要来配置防火墙,看看openssh是否在防火墙内。
查看防火墙中SSH服务的名字。
在这里插入图片描述
在这里插入图片描述
接着要知道虚拟机linux的ip地址是啥,并记录下来。
在这里插入图片描述
输入命令remote ssh并按下回车,
在这里插入图片描述
然后输入用户名和ip地址,在这里插入图片描述
然后登陆时候会要求输入你linux的密码。输入后就可以打开你linux的文件夹了。
在这里插入图片描述
这里我选择我们这次编写程序的文件夹。
在这里插入图片描述

编写程序

首先创建一个文件夹,这里我创建的文件夹名字是:在这里插入图片描述
接下来依次创建start.S,main.c,main.h。
在 STM32 中,启动文件 startup_stm32f10x_hd.s 就是完成 C 语言环境搭建的,这里我们也要使用start.S创建c语言环境,这样我们编写的c语言代码才能实现。
这里汇编程序为:

.global _start

_start:
    mrs r0,cpsr
    bic r0,r0,#0x1f
    orr r0,r0,#0x13
    msr cpsr,r0/*  将r0的数据写入到cpsr_c */
    ldr sp,=0x80200000
    b main

汇编程序这里文档给提供的很简单,表面看起来只操作了cpsr寄存器。以及给sp指针赋值,以及跳转到main函数。

  1. mrs r0, cpsr
    mrs(Move to Register from Special register)指令用于将特殊寄存器的值移动到通用寄存器中。r0是目标寄存器,用于存储读取的值。cpsr是当前程序状态寄存器(Current Program Status Register),它包含了处理器的当前状态信息,如条件标志和当前处理器模式。这条指令的作用是将CPSR的当前值读取到r0寄存器中。
    具体来说为啥要去操作这个寄存器,我们需要回顾Cortex-A7 MPCore 架构这部分知识点。
    以前的 ARM 处理器有 7 中运行模型:User、FIQ、IRQ、Supervisor(SVC)、Abort、Undef和 System,其中 User 是非特权模式,其余 6 中都是特权模式。但新的 Cortex-A 架构加入了TrustZone 安全扩展,所以就新加了一种运行模式:Monitor,新的处理器架构还支持虚拟化扩展,因此又加入了另一个运行模式:Hyp,所以 Cortex-A7 处理器有 9 种处理模式,如表 所示模式:
    在这里插入图片描述
    所有的处理器模式都共用一个 CPSR 物理寄存器,因此 CPSR 可以在任何模式下被访问。CPSR 是当前程序状态寄存器,该寄存器包含了条件标志位、中断禁止位、当前处理器模式标志等一些状态位以及一些控制位。所有的处理器模式都共用一个 CPSR 必然会导致冲突,为此,除了 User 和 Sys 这两个模式以外,其他 7 个模式每个都配备了一个专用的物理状态寄存器,叫做 SPSR(备份程序状态寄存器),当特定的异常中断发生时,SPSR 寄存器用来保存当前程序状态寄存器(CPSR)的值,当异常退出以后可以用 SPSR 中保存的值来恢复 CPSR。
    因为 User 和 Sys 这两个模式不是异常模式,所以并没有配备 SPSR,因此不能在 User 和Sys 模式下访问 SPSR,会导致不可预知的结果。由于 SPSR 是 CPSR 的备份,因此 SPSR 和CPSR 的寄存器结构相同,如图 6.3.2.1 所示:
    在这里插入图片描述
    这里我们用到的为:M[4:0]
    M[4:0]:处理器模式控制位,含义如表 6.3.2.2 所示:
    在这里插入图片描述
  2. bic r0, r0, #0x1f
    bic(Bit Clear)指令用于对寄存器中的位进行清零操作。
    第一个r0是目标寄存器,存储结果。
    第二个r0是操作数,其内容会被修改。

0x1f是一个立即数,二进制表示为0001 1111。

这条指令的作用是将r0寄存器中对应于0x1f(即最低的5位)的位清零。这实际上是在清除CPSR中的模式位,因为CPSR的最低5位定义了处理器的模式。
3. orr r0, r0, #0x13
orr(Logical OR)指令用于执行逻辑或操作。
第一个r0是目标寄存器。
第二个r0是第一个操作数。

0x13是第二个操作数,二进制表示为0001 0011。

这条指令的作用是将r0寄存器的当前值与0x13进行逻辑或操作。0x13(十六进制)对应于SVC(Supervisor)模式的模式位。这条指令将处理器切换到SVC模式。
4. msr cpsr, r0
msr(Move to Special register from Register)指令用于将通用寄存器的值移动到特殊寄存器中。
cpsr是目标特殊寄存器。
r0包含了要写入的值。
这条指令的作用是将r0寄存器中的值(已经被修改为指示SVC模式的值)写回到CPSR中,从而改变处理器的模式。
ldr 指令设置 SVC 模式下的 SP 指针=0X80200000,因为 I.MX6U-ALPHA 开发板上的 DDR3 地址范围是 0X80000000~0XA0000000(512MB) 或 者0X80000000~0X90000000(256MB),不管是 512MB 版本还是 256MB 版本的,其 DDR3 起始地址都是 0X80000000。由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针设置为 0X80200000,因此 SVC 模式的栈大小 0X80200000-0X80000000=0X200000=2MB,2MB 的栈空间已经很大了,如果做裸机开发的话绰绰有余。
我们上面编写的start.s 文件中却没有初始化 DDR3 的代码,但是却将 SVC 模式下的 SP 指针设置到了 DDR3 的地址范围中,这不会出问题吗?肯定不会的,DDR3 肯定是要初始化的,但是不需要在 start.s 文件中完成。在 9.4.2 小节里面分析 DCD 数据的时候就已经讲过了,DCD 数据包含了 DDR 配置参数,I.MX6U 内部的 Boot ROM 会读取 DCD 数据中的 DDR 配置参数然后完成 DDR 初始化的。
接下来就是编写C语言程序。
首先main.c

#include "main.h"

void clock_enable(void)
{
    CCM_CCGR0 = 0xFFFFFFFF;
    CCM_CCGR1 = 0xFFFFFFFF;
    CCM_CCGR2 = 0xFFFFFFFF;
    CCM_CCGR3 = 0xFFFFFFFF;
    CCM_CCGR4 = 0xFFFFFFFF;
    CCM_CCGR5 = 0xFFFFFFFF;
    CCM_CCGR6 = 0xFFFFFFFF;
}
void gpio_enable(void)
{
    SW_MUX_GPIO1_IO03 = 0x5;
    /* 2、配置 GPIO1_IO03 的 IO 属性 
    *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 低转换率
    */
    SW_PAD_GPIO1_IO03 = 0X10B0;
    GPIO1_GDIR = 0X0000008;
    GPIO1_DR = 0X0;
}
void led_state_init(int state)
{
    if(state == 1)
    {
        GPIO1_DR = 0;
    }
    else if(state == 0)
    {
        GPIO1_DR = 1<<3;
    }
}
void delay_ms(int ms)
{
    int i,j;
    for(i=0;i<ms;i++)
    {
        for(j=0;j<1000;j++);
    }
}
int  main()
{   
    clock_enable();
    gpio_enable();
    while(1)
    {
    led_state_init(1);
    delay_ms(500);
    led_state_init(0);
    delay_ms(500);
    }
    return 0;
}

接下来是main.h编写:

#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

编写完所有程序后,我们要进行Makefile文件的编写,在该工程文件夹建立Makefile文件。

objs := start.o main.o
ledc.bin:$(objs)
	arm-linux-gnueabihf-ld -Ttext 0X87800000 -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:%.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

我们可以看到要烧录的文件就是ledc.bin。它的依赖文件包括start.o和main.o。位于这个规则下面的有三条语句:
1:arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^:

这条命令使用ARM的链接器ld将所有依赖的目标文件($^,即start.o和main.o)链接成一个ELF格式的可执行文件ledc.elf。-Ttext 0X87800000指定程序的加载地址,-o选项后跟输出文件名ledc.elf。
2:arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@:

这条命令使用objcopy工具将ledc.elf转换成一个纯二进制格式的文件ledc.bin($@代表规则的目标,即ledc.bin)。-O binary指定输出格式为二进制,-S选项用于去除符号表和重定位信息,以减小最终二进制文件的大小。
3:arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis:

使用objdump工具以反汇编的形式打印ledc.elf的内容,并将输出重定向到ledc.dis文件中。这对于调试和理解程序的执行流程非常有用。-D表示反汇编所有内容,-m arm指定目标架构为ARM。
上面这三条规则需要start.o和main.o。所以会自动向下寻找规则并执行。
4:
%.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 $@ $<

这三条规则说明了如何将汇编文件(.s或.S)和C源文件(.c)编译成目标文件(.o)。对于每种文件类型,使用arm-linux-gnueabihf-gcc编译器,带有-Wall(开启所有警告),-nostdlib(不链接标准库,因为这是裸机程序),-c(只编译不链接)。\(@代表规则的目标文件,\)<代表规则的第一个依赖,即输入文件。
接下来我们引入一个知识点,也就是链接脚本的概念来改写这句代码:arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^
是通过“-Ttext”来指定链接地址是 0X87800000 的,这样的话所有的文件都会链接到以 0X87800000 为起始地址的区域。但是有时候我们很多文件需要链接到指定的区域,或者叫做段里面。
链接脚本(Linker Script)是用于控制链接过程的一个重要工具,尤其在嵌入式系统和操作系统开发中非常重要。链接脚本为链接器(Linker)提供了一种方式来控制程序的内存布局,即决定程序的各个部分(或称为“段”)在内存中的位置。通过链接脚本,开发者可以细致地指定哪些代码和数据应该放在内存的哪个位置,这对于需要精确控制内存布局的场景(比如嵌入式设备或操作系统开发)来说非常重要。
接下来来了解下段的概念:
在编译和链接过程中,程序被分为多个段(Section),常见的段包括:

.text段:存放程序的执行代码。
.data段:存放初始化过的全局变量和静态变量。
.bss段:存放未初始化的全局变量和静态变量。
.rodata段:存放只读数据,比如字符串常量。
特殊的段,如.init和.fini段,分别用于存放程序初始化前和终止前需要执行的代码。
接下来创建文件,具体内容为:

SECTIONS{
    . = 0X87800000;
    .text :
    {
        start.o 
        main.o 
        *(.text)
    }
    .rodata ALIGN(4) : {*(.rodata*)} 
    .data ALIGN(4) : { *(.data) } 
    __bss_start = .; 
    .bss ALIGN(4) : { *(.bss) *(COMMON) } 
    __bss_end = .;
 }

这段代码是一个链接脚本的示例,用于控制程序在内存中的布局。链接脚本通过指定不同的段(如.text.rodata.data.bss等)在内存中的位置和顺序,来控制最终可执行文件的内存布局。下面是对这段链接脚本的详细分析:

段地址和顺序

SECTIONS{
    . = 0X87800000;
  • SECTIONS关键字开始定义段的映射和布局。这是链接脚本中用于定义输出段如何映射到内存中的部分。
  • . = 0X87800000;设置当前位置计数器.的值为0X87800000,意味着接下来定义的段将从这个地址开始放置。.代表当前的地址指针,可以被视为“到目前为止已使用的内存大小”的累加器。

.text段

.text :
{
    start.o 
    main.o 
    *(.text)
}
  • .text定义了一个名为.text的段,这个段通常包含程序的执行代码。
  • start.o main.o指定了start.omain.o这两个目标文件中的.text段要首先被放置在这个段中。这通常用于确保程序的入口点或初始化代码位于特定的位置。
  • *(.text)表示将所有输入文件中的.text段都收集到这里。*代表所有输入文件,.text指的是这些文件中的.text段。

.rodata段

.rodata ALIGN(4) : {*(.rodata*)}
  • .rodata定义了一个名为.rodata的段,用于存放只读数据,比如字符串常量等。
  • ALIGN(4)确保.rodata段在内存中的地址是4的倍数,这有助于提高访问效率。
  • {*(.rodata*)}表示将所有输入文件中的.rodata段和以.rodata开头的任何其他段(比如.rodata.str1.1等)都收集到这里。
  • 通配符的作用: * 在链接脚本中作为通配符使用,表示匹配所有的可能性。因此,.rodata* 匹配 .rodata 以及所有以 .rodata 开头的段名。这样做的好处是,即使在项目的后续开发中引入了新的以 .rodata 开头的段,链接脚本也无需修改,可以自动包含这些新段。
  • 合并段:外层的星号表示对所有选中的输入文件或库中的对象文件进行操作。在这个上下文中,它告诉链接器将所有输入文件中名字匹配 .rodata* 的段合并到一起。这意味着,如果你有多个源文件,每个文件都可能产生一个或多个 .rodata 开头的段,链接器会将这些段全部合并,按照链接脚本指定的规则放置。

.data段

.data ALIGN(4) : { *(.data) }
  • .data定义了一个名为.data的段,用于存放已初始化的全局变量和静态变量。
  • ALIGN(4)确保.data段在内存中的地址是4的倍数。
  • { *(.data) }表示将所有输入文件中的.data段都收集到这里。

.bss段

__bss_start = .; 
.bss ALIGN(4) : { *(.bss) *(COMMON) } 
__bss_end = .;
  • __bss_start = .;.bss段开始前定义了一个符号__bss_start,其值为当前的位置计数器.的值,即.bss段的起始地址。
  • .bss定义了一个名为.bss的段,用于存放未初始化的全局变量和静态变量。
  • ALIGN(4)确保.bss段在内存中的地址是4的倍数。
  • { *(.bss) *(COMMON) }表示将所有输入文件中的.bss段和COMMON块都收集到这里。COMMON块用于存放未明确放在其他段中的未初始化全局变量。
  • __bss_end = .;.bss段结束后定义了一个符号__bss_end,其值为当前的位置计数器.的值,即.bss段的结束地址。
    在上一小节中我们已经编写好了链接脚本文件:imx6ul.lds,我们肯定是要使用这个链接脚本文件的,将 Makefile 中的如下一行代码:
    arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^改为:
    arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
    其实就是将-T 后面的 0X87800000 改为 imx6ul.lds,表示使用 imx6ul.lds 这个链接脚本文件。修改完成以后使用新的 Makefile 和链接脚本文件重新编译工程,编译成功以后就可以烧写到 SD 卡中验证了

标签:r0,点灯,linux,bss,rodata,ledc,寄存器,C语言,链接
From: https://www.cnblogs.com/bathwind/p/18183787

相关文章

  • Linux下RTC子系统驱动
    Linux下RTC子系统驱动1引入RTCCPU内部有很多定时器,像看门狗WDT,PWM定时器,高精度定时器Timer等等,只在“启动”即“通电时”运行,断电时停止。当然,如果时钟不能连续跟踪时间,则必须手动设置。那么当关机后就没办法自动计数统计时间了。RTC就很好的解决了这个问题,RTC是实时时钟,用......
  • linux VIP 自动切换虚拟IP
    脚本内容如下#!/bin/bashset-oxtracePGCANDIDATES=(192.168.0.92192.168.0.93192.168.0.94192.168.0.95)#HOSTNAME=`hostname-i`VIP=192.168.0.110GW=192.168.1.1DEVICE=enp0s3STEP1="RemovetheVIPonallnodes"STEP2="Checkifvipstillonl......
  • 在 Linux 中将可执行文件设为全局可用
    一、将可执行文件复制到/usr/local/bin/目录:sudocp<binary-name>/usr/local/bin/或生成链接:sudoln-s/absolute/path/to/binary/usr/local/bin/<binary-name>二、echo"exportPATH=/home/wzy/go/bin:$PATH">>~/.profile&&source~/.pr......
  • linux中离线安装docker
    一、linux中离线安装docker1、从官方下载Docker安装包并上传至虚拟机https://download.docker.com/linux/static/stable/x86_64/2.解压安装包tar-xvfdocker-19.03.9.tgz3.将解压出来的docker文件内容移动到/usr/bin/目录下#移动命令复制命令请用cpmvdocker/*/usr......
  • Linux问题--docker启动mysql时提示3306端口被占用(kill不掉3306端口)
    使用kill-9杀掉mysqld服务时一直失败。mysql启动时会启动mysqld和mysqld_safe两个进程,当使用kill-9杀掉mysqld进程时,mysqld_safe会自动重新启动mysqld。当使用正常方式退出mysqld时,mysqld_safe也会退出。如果需要kill掉mysqld服务可以先通过lsof-i:3306查询到占用3306......
  • linux增加环境变量示例
    首先,通过 vim~/.bashrc 命令进入我这个用户的.bashrc文件内 然后在这个文件末尾添加环境变量,比如下面红框中的内容表示添加了路径/home/nfs_new/wangpeng/VSCode-linux-x64/bin为环境变量,实际上这里是把vscode启动命令添加作为环境变量了。其中, $PATH 表示之前所有的环......
  • linux openCV编译
    1、修改平台文件opencv/platforms/linux/arm-gnueabihf.toolchain.cmakeset(GCC_COMPILER_VERSION""CACHESTRING"GCCCompilerversion")set(GNU_MACHINE"arm-linux-gnueabihf"CACHESTRING"GNUcompilertriple")set(CMAKE_C_COMP......
  • Linux I2C子系统驱动
    1LinuxI2C驱动框架由上到下分为3层结构:i2c设备驱动层:作为client使用者使用i2c子系统。提供操作接口给应用层,与应用层交互数据。I2C核心层:提供transfersendrecv函数。把client设备挂载到I2C总线上;维护i2cdriver和i2cclient链表,实现i2c_client和i2c_driver匹配。......
  • Linux nohup 命令
    Linuxnohup命令应用场景使用PyCharm连接服务器跑模型虽然很方便,但是如果遇到网络不佳、PyCharm出BUG、急需转移阵地等情况就只能中断训练,前面的全白跑了。因此可以尝试直接在服务器上使用命令跑模型,这个命令好说,笨一点的方法直接抄用PyCharm运行时输出的命令嘛:但是这样......
  • Linux系统资源监控
    系统资源查看freefree displays the totalamountoffreeandusedphysicalandswapmemoryinthesystem,aswellasthebuffersandcachesusedbythekernel.Theinformationisgatheredbyparsing/proc/meminfo.Thedisplayedcolumnsare:free命令可......