Linux系统中利用C语言控制LED的方法

简介: 大家好, 今天主要和大家聊一聊,如何利用C语言控制LED灯的实验。

328198f3d4632370235b821c862a904a.png

第一:C语言板控制LED灯简介

     实际工作中很少会使用到汇编去编写嵌入式驱动,毕竟汇编太难,写出来也不好理解,大部分情况下都使用C语言去编写。只是在开始部分用汇编初始化一下C语言环境,比如初始化DDR、设置堆栈指针SP等。当这些工作都做完以后就可以进入C语言环境,也就是运行C语言代码,一般都是进入main函数。所以都是进入main函数,有两部分文件要做:


      1、汇编文件


      汇编文件只是用来完成C语言环境搭建的。


      2、C语言文件


      C语言文件就是完成我们的业务层代码的,其实就是我们实际要完成的功能。其实STM32也是这样的,只是我们在开发STM32的时候没有想到这一点,以STM32中启动文件startup_stm32f10x_hd.s这个汇编文件就是完成C语言环境搭建的,当然还有一些其他处理,比如中断向量表等。

第二:实验程序实现

      在STM32中,启动文件startup_hd.s就是完成C语言环境搭建的,当然还有一些其他的处理。

Stack_Size EQU 0x00000400 Stack_Size EQU 0x00000400
                 AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
 __initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000200
   AREA HEAP, NOINIT, READWRITE, ALIGN=3
   __heap_base
   Heap_Mem SPACE Heap_Size
__heap_limit
//省略掉部分代码
Reset_Handler PROC
    EXPORT Reset_Handler [WEAK]
    IMPORT __main
    IMPORT SystemInit
    LDR R0, =SystemInit
    BLX R0 
    LDR R0, =__main
    BX R0
    ENDP

代码分析:设置栈的大小,这里设置为0X400=1024字节。下面遇到的__initial_sp就是初始化SP指针。设置堆的大小,复位中断服务函数,STM32复位完成以后会执行中断服务函数。 调用SystemInit()函数来完成其他初始化工作,会调用__main是库函数实现。

.global _start /* 全局标号 */
_start:
/* 进入 SVC 模式 */
 mrs r0, cpsr
 bic r0, r0, #0x1f /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */
 orr r0, r0, #0x13 /* r0 或上 0x13,表示使用 SVC 模式 */
 msr cpsr,r0       //将r0的数据写入到cpsr_c中   
 ldr sp, =0X80200000 /* 设置栈指针 */
 b main /* 跳转到 main 函数 */

这里我们可以设置处理器运行于SVC模式下,处理器模式的设置是通过修改CPSR程序状态寄存器来完成的。上面编写的start.s文件中却没有初始化DDR3的代码,但是却将SVC模式下的SP指针设置到了DDR3的地址范围中,这不会出问题吗?肯定不会的,DDR3肯定还是要初始化的,DCD数据包含了DDR配置参数,内部的Boot ROM会读取DCD数据中的参数完成DDR初始化的。

第三:C语言实验控制程序

   C语言部分有两个文件件 main.c 和 main.h,main.h 里面主要是定义的寄存器地址,在 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)
//相关寄存器地址
#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

在main.h中以宏定义的形式定义要使用到所有的寄存器,后面的数字就是其地址信息,比如CCM_CCGR0 寄存器的地址就是 0X020C4068,这个很简单,很好理解。main.c函数的具体实现。

#include "main.h"
//使能外设的所有时钟
void clk_enable(void)
{
 CCM_CCGR0 = 0xffffffff;
 CCM_CCGR1 = 0xffffffff;
 CCM_CCGR2 = 0xffffffff;
 CCM_CCGR3 = 0xffffffff;
 CCM_CCGR4 = 0xffffffff;
 CCM_CCGR5 = 0xffffffff;
 CCM_CCGR6 = 0xffffffff;
 }
 //初始化LED对应的GPIO时钟
 void led_init(void)
 {
    /* 1、初始化 IO 复用, 复用为 GPIO1_IO03 */
     SW_MUX_GPIO1_IO03 = 0x5;
   //配置GPIO1_IO03属性
     SW_PAD_GPIO1_IO03 = 0X10B0;
   //初始化GPIO,GPIO_IO03设置为输出
     GPIO1_GDIR=0X0000008;
   //设置GPIO1_IO03输出低电平,打开LED0
      GPIO1_DR = 0x0;
 }
 //打开对应的LED灯
 void led_on(void)
 {
   //将GPIO1_DR 的 bit3 清零 
   GPIO1_DR &= ~(1<<3);
 }
 //关闭LED灯
 void led_off(void)
 {
     GPIO1_DR |= (1<<3);
 }
 //短暂的延时函数
 void delay_short(volatile unsigned int n)
 {
    while(--){}
 }
 //延时大约1ms的函数
 void delay(volatile unsigned int n)
 {
     while(n--)
     {
       delay_short(0x7ff);
     }
 }
 int main(void)
 {
     clk_enable(); /* 使能所有的时钟 */
     led_init(); /* 初始化 led */
     while(1) /* 死循环 */
 { 
    led_off(); /* 关闭 LED */
    delay(500); /* 延时大约 500ms */
    led_on(); /* 打开 LED */
    delay(500); /* 延时大约 500ms */
 }
     return 0;
 }

  利用Makefile文件可以进行编译,将对应的可执行文件,放到开发板上,可以看到LED大概500ms闪烁一次。

总结:利用C语言实现底层驱动的控制,要注意可执行程序放的位置,以及如何链接编译等。

目录
相关文章
|
3天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
19 3
|
3天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
16 2
|
3天前
|
安全 网络协议 Linux
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。通过掌握 ping 命令,读者可以轻松测试网络连通性、诊断网络问题并提升网络管理能力。
18 3
|
6天前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
23 6
|
6天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
31 6
|
7天前
|
机器学习/深度学习 自然语言处理 Linux
Linux 中的机器学习:Whisper——自动语音识别系统
本文介绍了先进的自动语音识别系统 Whisper 在 Linux 环境中的应用。Whisper 基于深度学习和神经网络技术,支持多语言识别,具有高准确性和实时处理能力。文章详细讲解了在 Linux 中安装、配置和使用 Whisper 的步骤,以及其在语音助手、语音识别软件等领域的应用场景。
35 5
|
7天前
|
监控 网络协议 算法
Linux内核优化:提升系统性能与稳定性的策略####
本文深入探讨了Linux操作系统内核的优化策略,旨在通过一系列技术手段和最佳实践,显著提升系统的性能、响应速度及稳定性。文章首先概述了Linux内核的核心组件及其在系统中的作用,随后详细阐述了内存管理、进程调度、文件系统优化、网络栈调整及并发控制等关键领域的优化方法。通过实际案例分析,展示了这些优化措施如何有效减少延迟、提高吞吐量,并增强系统的整体健壮性。最终,文章强调了持续监控、定期更新及合理配置对于维持Linux系统长期高效运行的重要性。 ####
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
6天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
22 6
|
26天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10