欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

Linux嵌入式学习第二节:C语言版本点亮LED灯

程序员文章站 2022-12-02 17:03:35
目录前言本文作为本人学习Linux嵌入式记录文档,仅供参考。开发板:正点原子阿尔法开发板(I.MX6ULL)环境:Ubuntu 20.04 (LTS) (内核版本:Linux 5.4.0)交叉编译器:arm-linux-gnueabihf 4.9.4一、目的搭建C语言环境熟悉Linux开发板的裸机开发流程。二、原理1.设置处理器模式如果Arm首先查看开发板原理图中有关LED设备的电路。可以看到LED采用了共阳的接线,也就是当“LED0”端为......

目录

前言

一、目的

二、原理

1.硬件分析

2.C语言环境

(1)设置用户模式

(2)逻辑运算指令

(3)编写C语言GPIO配置代码

总结




前言

本文作为本人学习Linux嵌入式记录文档,仅供参考。

开发板:正点原子阿尔法开发板(I.MX6ULL)

环境:Ubuntu 20.04 (LTS)  (内核版本:Linux 5.4.0)

交叉编译器:arm-linux-gnueabihf 4.9.4


一、目的

  1. 搭建C语言环境
  2. 通过C语言编程点亮LED

二、原理

1.硬件分析

同第一节“Linux嵌入式学习第一节:汇编点亮LED灯”中“硬件电路分析”小节。

2.C语言环境

(1)设置用户模式

如果看过ARM嵌入式相关书籍的话可以知道,Contex-A7 架构芯片一般拥有9种用户模式,如下图所示(图片来自网络)

Linux嵌入式学习第二节:C语言版本点亮LED灯

我们选择SVC特权模式。据本人查找资料得知,一般芯片上电后会默认进入此模式(未测试)。

每种模式对应一组寄存器。如下图

Linux嵌入式学习第二节:C语言版本点亮LED灯

其中R0和R1我们在第一节已经使用过了,一般情况下R0~R7共8个低寄存器组和R15(程序计数器)是所有用户公用的。所以数据可能被损坏。当我们处理中断时可以将其数据备份到相应用户组的R8~R14几个高寄存器组中。公用相关内容本节不做过多解释,有兴趣可自行查找相关资料。主要讲解一下同样是所有用户公用的CPSR(程序状态寄存器)寄存器。SPSR寄存器是CPSR的备份寄存器。

CPSR寄存器定义如下

Linux嵌入式学习第二节:C语言版本点亮LED灯

这里我们本节用到的是[4:0]低5位。相应功能如下:

Linux嵌入式学习第二节:C语言版本点亮LED灯

我们要进入的是SVC模式,所以需要将CPSR寄存器的[4:0]设置为:10011(bin)即13(hex)。但是值得注意的是,除了前5位,剩下的位也是有功能的,比如设置指令集、大小端模式之类、现在还用不到且不知道怎么设置的情况下怎么办呢?所以我们不能像上一节设置时钟使能那样简单粗暴的全部写1开启。这时候就需要我们学会ARM汇编中的逻辑运算指令了。还好ARM指令中有非常方便的清除位和写位的逻辑运算指令。

(2)逻辑运算指令

Linux嵌入式学习第二节:C语言版本点亮LED灯

如图所示。本次我们需要清除CPSR的[4:0]位,所以我们首先需要使用位清除指令:BIC Rd,Rn ,#immed。根据相应计算公式,我们可以知道Rd是我们需要清除相应位的目标寄存器,即CPSR寄存器,Rn和~Rm相与。然后再将0x13写到CPSR的[4:0]低5位。然后设置sp指针位置为0x80200000(imx.6u的DDR3地址为0x80000000向上扩充,而Cortex-A7的堆栈向下增长。这里我们设置为0x80200000已经有2MB大小的栈空间)。最后我们将代码指向C文件的主函数main就可以了。

.global start

__start:
    mrs r0, cpsr         @读cpsr到r0
    bic r0, r0, #0x1f    @清除位[4:0]
    orr r0, r0, #0x13    @将0x13写入低5位[4:0]
    msr cpsr, r0         @写入cpsr寄存器

    ldr sp, =0x80200000    @设置sp指针(栈指针)

    b main        @指向main函数

(3)编写C语言GPIO配置代码

编写C语言点亮LED登的思路和汇编是一样的。

首先编写头文件,对相关寄存器地址进行宏定义。

文件1:main.h

#ifndef __MAIN_H
#define __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)

#define SW_MUX_GPIO1_IO03   *((volatile unsigned int *) 0x020E0068)
#define SW_PAD_GPIO1_IO03   *((volatile unsigned int *) 0x020E02F4)

#define GPIO1_GDIR          *((volatile unsigned int *) 0x0209C004)
#define GPIO1_DR            *((volatile unsigned int *) 0x0209C000)

#endif

然后是主函数文件,对相关寄存器进行配置。(配置内容与第一节一致,只不过这里用C来写)

文件2:main.c

#include "main.h"

void Clk_Init(void)
{
    CCM_CCGR0 = 0xFFFFFFFF;
    CCM_CCGR1 = 0xFFFFFFFF;
    CCM_CCGR2 = 0xFFFFFFFF;
    CCM_CCGR3 = 0xFFFFFFFF;
    CCM_CCGR4 = 0xFFFFFFFF;
    CCM_CCGR5 = 0xFFFFFFFF;
    CCM_CCGR6 = 0xFFFFFFFF;
}

void Led_Init(void)
{
    SW_MUX_GPIO1_IO03 = 0x5;        //IO复用
    SW_PAD_GPIO1_IO03 = 0x10B0;     //电气属性 
    GPIO1_GDIR = 0x8;               //设置为输出
    GPIO1_DR = 0x0;     //输出低电平 打开LED
//    GPIO1_DR = 0x8;     //输出高电平 关闭LED
}

void Led_off(void)
{
    GPIO1_DR |= (1<<3); //0x8
}

void Led_on(void)
{
    GPIO1_DR &= ~(1<<3);   //~(0x8)
}

void delay_ms(volatile unsigned int n)
{
    //主频396MHz下空循环0x7FF约为1Ms,计算方法涉及指令周期等问题较为复杂
    volatile unsigned int i,j;
    for (i = 0; i < n; i++)
        for (j = 0; j < 0x7FF; j++);
}

int main(void)
{
    Clk_Init();
    Led_Init();

    while (1)
    {
        delay_ms(500);  //延时
        Led_off();      //关闭LED
        delay_ms(500);
        Led_on();       //打开LED
    }
    
    return 0;
}

 

 


总结

用C语言进行点亮LED灯的思路与汇编版本基本一致,不同之处是需要先将函数入口导向main。

在我的理解中,在嵌入式开发(包括其他开发)中,无论是C语言还是汇编都只是一个工具,采用什么语言、什么工具并不重要,怎么去实现目标,具体的过程和逻辑才是最重要的。选择更好的工具,搭建环境是为了让我们更加方便的开发,没有核心,其他什么东西都是徒有其表。

本文地址:https://blog.csdn.net/Youngqqqq/article/details/108783663