Linux嵌入式学习第二节:C语言版本点亮LED灯
目录
前言
本文作为本人学习Linux嵌入式记录文档,仅供参考。
开发板:正点原子阿尔法开发板(I.MX6ULL)
环境:Ubuntu 20.04 (LTS) (内核版本:Linux 5.4.0)
交叉编译器:arm-linux-gnueabihf 4.9.4
一、目的
- 搭建C语言环境
- 通过C语言编程点亮LED
二、原理
1.硬件分析
同第一节“Linux嵌入式学习第一节:汇编点亮LED灯”中“硬件电路分析”小节。
2.C语言环境
(1)设置用户模式
如果看过ARM嵌入式相关书籍的话可以知道,Contex-A7 架构芯片一般拥有9种用户模式,如下图所示(图片来自网络)
我们选择SVC特权模式。据本人查找资料得知,一般芯片上电后会默认进入此模式(未测试)。
每种模式对应一组寄存器。如下图
其中R0和R1我们在第一节已经使用过了,一般情况下R0~R7共8个低寄存器组和R15(程序计数器)是所有用户公用的。所以数据可能被损坏。当我们处理中断时可以将其数据备份到相应用户组的R8~R14几个高寄存器组中。公用相关内容本节不做过多解释,有兴趣可自行查找相关资料。主要讲解一下同样是所有用户公用的CPSR(程序状态寄存器)寄存器。SPSR寄存器是CPSR的备份寄存器。
CPSR寄存器定义如下
这里我们本节用到的是[4:0]低5位。相应功能如下:
我们要进入的是SVC模式,所以需要将CPSR寄存器的[4:0]设置为:10011(bin)即13(hex)。但是值得注意的是,除了前5位,剩下的位也是有功能的,比如设置指令集、大小端模式之类、现在还用不到且不知道怎么设置的情况下怎么办呢?所以我们不能像上一节设置时钟使能那样简单粗暴的全部写1开启。这时候就需要我们学会ARM汇编中的逻辑运算指令了。还好ARM指令中有非常方便的清除位和写位的逻辑运算指令。
(2)逻辑运算指令
如图所示。本次我们需要清除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