9.2 Linux LED 驱动开发

本文详细解释了Linux下LED驱动的工作原理,特别是MMU地址映射的过程,介绍了ioremap和iounmap函数在硬件寄存器映射中的作用,以及如何通过编程实现GPIO配置和I/O内存访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Linux 下的 LED 驱动原理

  Linux 下的任何驱动,最后都是要配置相应的硬件寄存器。

1. 地址映射

  MMU 全称叫做 MemoryManage Unit,也就是内存管理单元。 现在的 Linux 支持无 MMU 处理器。MMU 主要完成的功能为:

  1、完成虚拟空间到物理空间的映射。

  2、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

  虚拟空间到物理空间的映射其实就是 地址映射。 虚拟地址(VA,Virtual Address)、物理地址(PA, Physcical Address)。对于 32位的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 1GB 的 DDR3,这 1GB 的内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间:

  Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址。比如 STM32MP157 的 PI0 引脚的端口模式寄存器 GPIOI_MODER 物理地址为0x5000A000。如果没有开启 MMU 的话直接向 0x5000A000 这个寄存器地址写入数据就可以配置 PI0 的引脚功能(输入、输出、复用或模拟等)。

  那有人会有疑惑,那为什么要开启MMU呢?开启 MMU 使得操作系统能够更好地管理内存资源、提供虚拟内存、实施内存保护和权限控制,并提供了更高效、安全、灵活的内存访问方式。总而言之,开启 MMU 的好处大大滴。

  现在开启了 MMU,并且设置了内存映射,因此就不能直接向 0x5000A000 这个地址写入数据了。我们必须得到 0x5000A000 这个物理地址在Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到
两个函数: ioremap 和 iounmap。 

① ioremap 函数

/*
 * @description : 获取指定物理地址空间对应的虚拟地址空间
 * @param - res_cookie : 映射的物理起始地址
 * @param - size : 映射的内存空间大小
 * @return : __iomem 类型的指针,指向映射后的虚拟空间首地址
 * __iomen:可以修饰指针类型,将其标记为 I/O 内存指针, I/O 内存指针目的是告知编译器该指针指向的内存区域用于与 I/O 设备进行直接交互,需要采取特殊的读写方式和对齐规则
 */
void __iomem *ioremap(resource_size_t res_cookie, size_t size)
{
    return arch_ioremap_caller(res_cookie, size, MT_DEVICE, __builtin_return_address(0));
}

  如果需要 STM32MP157-ATK 的 GPIOI_MODER 寄存器对应的虚拟地址 ,代码如下:

#define GPIOI_MODER        (0X5000A000)
static void __iomen        *GPIO_MODER_PI;
GPIO_MODER_PI = ioremap(GPIOI_MODER, 4);

  宏 GPIOI_MODER 是寄存器物理地址, GPIO_MODER_PI 是映射后的虚拟地址。对于 STMP32MP157 来说一个寄存器是 4 字节(32 位),因此映射的内存长度为 4。映射完成以后直接对 GPIO_MODER_PI 进行读写操作即可。 

② iounmap 函数

  卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射:

/*
 * @description : 释放 ioremap 所做的映射
 * @param - addr : 取消映射的虚拟地址空间首地址
 */
void iounmap (volatile void __iomem *addr);

  比如现在要卸载 GPIO_MODER_PI 寄存器的地址映射:

iounmap(GPIO_MODER_PI);

2. I/O 内存访问函数

  I/O 是输入/输出的意思, 这里涉及到两个概念: I/O 端口和 I/O 内存。当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。当外部寄存器或内存映射到内存空间时,称为 I/O 内存。 但 ARM 空间只有 I/O 内存。Linux 内核建议使用一组操作函数来对映射后的内存进行读写操作。 

① 读操作函数

u8 readb(const volatile void __iomem *addr);
u16 readw(const volatile void __iomem *addr);
u32 readl(const volatile void __iomem *addr);

/*
readb、 readw 和 readl 这三个函数分别对应 8bit、 16bit 和 32bit 读操作。
参数 addr 就是要读取写内存地址,返回值就是读取到的数据。
 */

②写操作函数

void writeb(u8 value, volatile void __iomem *addr);
void writew(u16 value, volatile void __iomem *addr);
void writel(u32 value, volatile void __iomem *addr);

/*
writeb、 writew 和 writel 这三个函数分别对应 8bit、 16bit 和 32bit 写操作。
参数 value 是要写入的数值, addr 是要写入的地址。
 */

二、硬件原理图分析

  LED0 接到了 PI0 上, PI0 就是 GPIOI 组的第 0 个引脚,当 PI0 输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当 PI0 输出高电平(1)的时候发光二极管LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 PI0 的输出电平,输出 0 就亮,输出 1 就灭。 

三、实验程序编写

1. LED 驱动程序编写

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名字 */

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

/* 寄存器物理地址 */
#define PERIPH_BASE (0x40000000)
#define MPU_AHB4_PERIPH_BASE (PERIPH_BASE + 0x10000000)
#define RCC_BASE (MPU_AHB4_PERIPH_BASE + 0x0000)
#define RCC_MP_AHB4ENSETR (RCC_BASE + 0XA28)
#define GPIOI_BASE (MPU_AHB4_PERIPH_BASE + 0xA000)
#define GPIOI_MODER (GPIOI_BASE + 0x0000)
#define GPIOI_OTYPER (GPIOI_BASE + 0x0004)
#define GPIOI_OSPEEDR (GPIOI_BASE + 0x0008)
#define GPIOI_PUPDR (GPIOI_BASE + 0x000C)
#define GPIOI_BSRR (GPIOI_BASE + 0x0018)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;

void led_switch(u8 sta)
{
    u32 val = 0;
    if(sta == LEDON) {
        val = readl(GPIOI_BSRR_PI);
        val |= (1 << 16);
        writel(val, GPIOI_BSRR_PI);
    }else if(sta == LEDOFF) {
        val = readl(GPIOI_BSRR_PI);
        val |= (1 << 0);
        writel(val, GPIOI_BSRR_PI);
    }
}

void led_unmap(void)
{
    iounmap(MPU_AHB4_PERIPH_RCC_PI);
    iounmap(GPIOI_MODER_PI);
    iounmap(GPIOI_OTYPER_PI);
    iounmap(GPIOI_OSPEEDR_PI);
    iounmap(GPIOI_PUPDR_PI);
    iounmap(GPIOI_BSRR_PI);
}

static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf,
                        size_t cnt, loff_t *offt)
{
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];

    if(ledstat == LEDON) {
        led_switch(LEDON);
    } else if(ledstat == LEDOFF) {
        led_switch(LEDOFF);
    }
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

static int __init led_init(void)
{
    int retvalue = 0;
    u32 val = 0;

    MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);
    GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);
    GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);
    GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);
    GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);
    GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);

    val = readl(MPU_AHB4_PERIPH_RCC_PI);
    val &= ~(0X1 << 8);
    val |= (0X1 << 8);
    writel(val, MPU_AHB4_PERIPH_RCC_PI);

    val = readl(GPIOI_MODER_PI);
    val &= ~(0X3 << 0);
    val |= (0X1 << 0);
    writel(val, GPIOI_MODER_PI);

    val = readl(GPIOI_OTYPER_PI);
    val &= ~(0X1 << 0);
    writel(val, GPIOI_OTYPER_PI);

    val = readl(GPIOI_OSPEEDR_PI);
    val &= ~(0X3 << 0);
    val |= (0x2 << 0);
    writel(val, GPIOI_OSPEEDR_PI);

    val = readl(GPIOI_PUPDR_PI);
    val &= ~(0X3 << 0);
    val |= (0x1 << 0);
    writel(val,GPIOI_PUPDR_PI);

    val = readl(GPIOI_BSRR_PI);
    val |= (0x1 << 0);
    writel(val, GPIOI_BSRR_PI);

    retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if(retvalue < 0){
        printk("register chrdev failed!\r\n");
        goto fail_map;
    }
    return 0;

fail_map:
    led_unmap();
    return -EIO;
}

static void __exit led_exit(void)
{
    led_unmap();
    unregister_chrdev(LED_MAJOR, LED_NAME);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("LXS");
MODULE_INFO(intree, "Y");

2. 测试 APP

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDOFF 0
#define LEDON 1

int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];

    if(argc != 3){
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("file %s open failed!\r\n", argv[1]);
        return -1;
    }

    databuf[0] = atoi(argv[2]);

    retvalue = write(fd, databuf, sizeof(databuf));
    if(retvalue < 0){
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }

    retvalue = close(fd);
    if(retvalue < 0){
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

三、运行测试

1. 编译驱动程序

  依然是利用 Makefile 文件把 chrdevbase.c 编译为 chrdevbase.ko 模块:

KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31	# Linux内核源码路径
CURRENT_PATH := $(shell pwd)		# 获取当前所处路径
obj-m := led.o		

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

  之后在 /linux/atk-mpl/Drivers/2_led 路径下输入:

make -j32

2. 编译测试 APP

  因为要在 ARM 上面运行,所以要用交叉编译器:

arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp

3. 运行测试

  将 led.ko 和 ledApp 拷贝到 /linux/nfs/rootfs/lib/modules/5.4.31 路径下:

sudo cp ledApp led.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f

  加载 led.ko 驱动模块:ls

depmod
modprobe led    # 加载驱动
lsmod           # 查看加载的驱动

  驱动加载成功后创建 "/dev/led" 设备节点:

mknod /dev/led c 200 0

  输入以下命令测试 LED:

# 打开 LED
./ledApp /dev/led 1

# 关闭 LED
./ledApp /dev/led 0

  如果开发版的红色 LED 能打开和关闭说明测试成功。卸载驱动:

rmmod led.ko
<think>我们参考了用户提供的引用,特别是引用[3]和[4]中提到的安装路径问题和环境变量问题。用户现在想要安装CCS9.2并配置环境变量。根据之前的回答和引用,我们需要注意以下几点: 1. 安装路径必须全英文且无空格(如引用[3]和[4]所述)。 2. 环境变量配置主要是为了在命令行中能够使用CCS的工具链,特别是编译器(如gcc)。 安装步骤与之前类似,但需要额外强调环境变量的配置。 ### 安装Code Composer Studio 9.2并配置环境变量的步骤 #### **步骤1:下载安装包** - 从TI官网下载CCS 9.2.0版本,选择适合操作系统的安装包(Windows、Linux或macOS)[^1]。 - 确保下载的安装包存放在全英文路径下(如`D:\Downloads\CCS9.2`)[^4]。 #### **步骤2:运行安装程序** 1. 以管理员身份运行安装程序(右键点击安装文件,选择“以管理员身份运行”)[^4]。 2. 安装过程中注意: - 接受许可协议。 - 选择安装组件:默认全选即可。 - **设置安装路径**:必须为全英文且无空格的路径(如`D:\TI\CCS9.2`)[^3][^4]。 - 如果安装过程中出现Unicode检查失败,请确保安装包所在路径也是全英文的[^4]。 #### **步骤3:配置环境变量(关键步骤)** 安装完成后,需要将CCS的工具链(特别是编译器)添加到系统环境变量PATH中,以便在命令行中使用。 **Windows系统配置方法:** 1. 打开“系统属性”: - 按`Win+R`,输入`sysdm.cpl`,回车。 - 切换到“高级”选项卡,点击“环境变量”按钮。 2. 在“系统变量”区域,找到并选中`Path`变量,点击“编辑”。 3. 添加CCS的编译器路径(根据实际安装路径调整): - 例如,如果安装路径为`D:\TI\CCS9.2`,则编译器路径通常为: ``` D:\TI\CCS9.2\ccs\tools\compiler\gcc-arm-none-eabi\bin ``` - 点击“新建”,粘贴上述路径,然后点击“确定”[^3]。 4. 验证环境变量: - 打开命令提示符(cmd),输入: ```bash arm-none-eabi-gcc --version ``` - 如果显示版本信息,则配置成功。 **Linux/macOS系统配置方法:** 1. 打开终端,编辑`~/.bashrc`(或`~/.zshrc`)文件: ```bash nano ~/.bashrc ``` 2. 在文件末尾添加(根据实际安装路径调整): ```bash export PATH=$PATH:/path/to/ccs9.2/ccs/tools/compiler/gcc-arm-none-eabi/bin ``` 3. 保存文件,然后运行: ```bash source ~/.bashrc ``` 4. 验证: ```bash arm-none-eabi-gcc --version ``` #### **步骤4:验证安装和环境变量** 1. 启动CCS,创建新项目(如MSP430的`hello_world`项目)。 2. 编译项目,确保无错误。 3. 在命令行中,尝试使用`arm-none-eabi-gcc`命令,检查是否能够调用编译器。 #### **常见问题解决** - **环境变量配置后命令仍不可用**:检查路径是否正确,重启命令行窗口或电脑。 - **编译失败**:确保项目使用的编译器与配置的环境变量路径一致(在CCS项目属性中可设置编译器路径)[^3]。 > 注意:如果使用第三方编译器(如IAR),则需要在CCS的项目属性中手动指定编译器路径,但环境变量配置通常只针对TI自带的GCC工具链[^1]。 ### 相关问题 1. 如何为CCS 9.2安装特定器件的支持包(如MSP430或C2000)? 2. CCS 9.2中如何切换不同的编译器(如TI编译器、GCC、IAR)? 3. 在Windows 11上安装CCS 9.2需要注意哪些兼容性问题?</think>### 安装 CCS 9.2 并配置环境变量的完整指南 #### **步骤 1:安装前准备** 1. **下载安装包** - 访问 [TI 官网](https://www.ti.com/tool/CCSTUDIO) 下载 **CCS 9.2.0** - 选择对应系统版本: - Windows: `CCS9.2.0.00013_win64.exe` - Linux: `.bin` 安装包 - macOS: `.dmg` 安装包 [^1] 2. **路径规范(关键!)** - 安装包存放路径:**全英文无空格**(如 `D:\TI\CCS92_Download`)[^4] - 安装目标路径:**全英文无空格**(如 `D:\TI\CCS92`,禁止使用 `C:\Program Files`)[^3][^4] - 用户名:系统账户名需为**英文**(避免Unicode字符问题)[^4] #### **步骤 2:执行安装** ```bash # Windows 静默安装示例(管理员权限运行) CCS9.2.0.00013_win64.exe --mode unattended --prefix "D:\TI\CCS92" ``` 1. 图形化安装流程: - 右键安装程序 → **以管理员身份运行** - 跳过 Unicode 检查(确保路径符合规范)[^4] - 选择组件:默认全选(包括编译器、仿真器驱动等) - 安装耗时:约 20-40 分钟 #### **步骤 3:配置环境变量** 1. **Windows 系统配置** - 打开:控制面板 → 系统 → 高级系统设置 → 环境变量 - 在 **系统变量** 中编辑 `Path`,添加以下路径: ``` D:\TI\CCS92\ccs\tools\compiler\gcc-arm-none-eabi\bin D:\TI\CCS92\ccs\utils\bin ``` [^3] - 新建系统变量 `CCS_BASE`: ``` 变量名:CCS_BASE 变量值:D:\TI\CCS92\ccs ``` 2. **Linux/macOS 配置** 编辑 `~/.bashrc` 或 `~/.zshrc`: ```bash export PATH=$PATH:/opt/ti/ccs92/ccs/tools/compiler/gcc-arm-none-eabi/bin export CCS_BASE=/opt/ti/ccs92/ccs ``` #### **步骤 4:验证环境变量** 1. 打开命令提示符/终端,执行: ```bash # 检查编译器 arm-none-eabi-gcc --version # 检查工具链 hex6x -h ``` 2. 预期输出: ``` arm-none-eabi-gcc (TI v9.2.0) 9.2.0 ... 版本信息 ... ``` #### **步骤 5:测试安装** 1. 启动 CCS → 创建新项目: - File → New → CCS Project - 选择器件(如 MSP430F5529) - 使用 `hello_world` 模板 2. 编译并调试: ```c #include <msp430.h> void main() { WDTCTL = WDTPW | WDTHOLD; // 停用看门狗 P1DIR |= BIT0; // 设置P1.0为输出 while(1) { P1OUT ^= BIT0; // 翻转LED __delay_cycles(1000000); } } ``` - 点击 **Build** 图标(✔️),确认无报错 - 连接开发板,点击 **Debug** 图标(🐞) #### **故障排除** - **环境变量失效**: - 检查路径拼写(避免空格/中文)[^3] - 重启计算机使配置生效 - **编译失败**: - 检查项目属性:右键项目 → Properties → Build → Compiler 路径是否为 `$CCS_BASE` 下的工具链 [^3] > 提示:通过 **View → Resource Explorer** 可访问官方例程库(如 Automotive 或工业控制示例)[^1] --- ### 相关问题 1. 如何为 CCS 9.2 添加新的器件支持包(如 C2000 系列)? 2. CCS 9.2 的环境变量与旧版本(如 CCS 8.x)冲突时如何解决? 3. 如何在 Linux 系统中为 CCS 9.2 配置 USB 仿真器(XDS110)权限?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值