目录
实战篇:开发一个完整的 BSP
在嵌入式系统开发中,开发一个完整的 Board Support Package (BSP) 是一个复杂但有趣的过程。本实战篇将通过一个具体的案例,演示如何开发一个完整的 BSP,包括硬件初始化、设备驱动编写、Bootloader 配置等。我们将以一个基于 ARM Cortex-A9 处理器的开发板为例进行说明。
1. 硬件平台介绍
假设我们使用的是一个基于 ARM Cortex-A9 处理器的开发板,具有以下硬件特性:
- 处理器:ARM Cortex-A9
- 内存:512MB DDR3
- 存储:16GB eMMC
- 外设:UART、SPI、I2C、GPIO、Ethernet、USB
- 开发板型号:STM32MP157
2. 硬件初始化
2.1 获取硬件手册和开发资料
首先,获取开发板的硬件手册和开发资料,了解开发板的具体硬件配置和接口。
2.2 创建硬件初始化代码
在 U-Boot 中,硬件初始化通常在 board_init_f
和 board_init_r
函数中完成。我们需要创建一个特定于开发板的初始化函数。
2.2.1 创建开发板目录
在 U-Boot 源码目录中创建开发板目录:
sh
深色版本
mkdir -p board/stmicroelectronics/stm32mp157
2.2.2 创建初始化文件
在 board/stmicroelectronics/stm32mp157
目录中创建 stm32mp157.c
文件:
c
深色版本
#include <common.h>
#include <asm/arch/stm32.h>
#include <asm/arch/sys_proto.h>
#include <asm/io.h>
#include <asm/gpio.h>
int board_init_f(struct bd_info *bis) {
// 初始化 DDR3 内存控制器
stm32mp157_ddr_init();
// 初始化 UART
serial_init();
// 初始化 GPIO
gpio_init();
// 初始化 Ethernet
eth_init();
return 0;
}
int board_init_r(gd_t *gd, struct bd_info *bis) {
// 初始化 USB
usb_init();
// 初始化 I2C
i2c_init();
// 初始化 SPI
spi_init();
return 0;
}
3. 设备驱动编写
3.1 创建字符设备驱动
我们将创建一个简单的字符设备驱动,用于控制 GPIO。
3.1.1 创建驱动程序文件
在内核源码目录中创建一个名为 drivers/gpio/gpio_driver.c
的文件:
c
深色版本
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#define DEVICE_NAME "gpio_driver"
#define CLASS_NAME "gpio_class"
static dev_t dev_num;
static struct cdev gpio_cdev;
static struct class *gpio_class;
static struct device *gpio_device;
static int gpio_major = 0;
static int gpio_minor = 0;
static int gpio_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "gpio_driver: device opened\n");
return 0;
}
static int gpio_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "gpio_driver: device closed\n");
return 0;
}
static ssize_t gpio_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
char gpio_value;
gpio_value = gpio_get_value(12); // 假设 GPIO 12 是我们要读取的引脚
copy_to_user(buf, &gpio_value, 1);
printk(KERN_INFO "gpio_driver: read value %d from GPIO 12\n", gpio_value);
return 1;
}
static ssize_t gpio_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
char gpio_value;
copy_from_user(&gpio_value, buf, 1);
gpio_set_value(12, gpio_value); // 假设 GPIO 12 是我们要控制的引脚
printk(KERN_INFO "gpio_driver: wrote value %d to GPIO 12\n", gpio_value);
return 1;
}
static const struct file_operations gpio_fops = {
.owner = THIS_MODULE,
.open = gpio_open,
.release = gpio_release,
.read = gpio_read,
.write = gpio_write,
};
static int __init gpio_driver_init(void) {
int ret;
// 请求 GPIO 引脚
ret = gpio_request(12, "gpio_12");
if (ret < 0) {
printk(KERN_ALERT "Failed to request GPIO 12\n");
return ret;
}
// 设置 GPIO 方向
gpio_direction_input(12); // 假设初始方向为输入
// 动态分配设备号
ret = alloc_chrdev_region(&dev_num, gpio_minor, 1, DEVICE_NAME);
if (ret < 0) {
gpio_free(12);
printk(KERN_ALERT "Failed to register device\n");
return ret;
}
// 初始化字符设备
cdev_init(&gpio_cdev, &gpio_fops);
gpio_cdev.owner = THIS_MODULE;
ret = cdev_add(&gpio_cdev, dev_num, 1);
if (ret < 0) {
unregister_chrdev_region(dev_num, 1);
gpio_free(12);
printk(KERN_ALERT "Failed to add character device\n");
return ret;
}
// 创建设备类
gpio_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(gpio_class)) {
cdev_del(&gpio_cdev);
unregister_chrdev_region(dev_num, 1);
gpio_free(12);
printk(KERN_ALERT "Failed to create device class\n");
return PTR_ERR(gpio_class);
}
// 创建设备文件
gpio_device = device_create(gpio_class, NULL, dev_num, NULL, DEVICE_NAME);
if (IS_ERR(gpio_device)) {
class_destroy(gpio_class);
cdev_del(&gpio_cdev);
unregister_chrdev_region(dev_num, 1);
gpio_free(12);
printk(KERN_ALERT "Failed to create device file\n");
return PTR_ERR(gpio_device);
}
printk(KERN_INFO "gpio_driver: device registered successfully\n");
return 0;
}
static void __exit gpio_driver_exit(void) {
device_destroy(gpio_class, dev_num);
class_destroy(gpio_class);
cdev_del(&gpio_cdev);
unregister_chrdev_region(dev_num, 1);
gpio_free(12);
printk(KERN_INFO "gpio_driver: device unregistered\n");
}
module_init(gpio_driver_init);
module_exit(gpio_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple GPIO driver");
3.1.2 创建 Makefile
在 drivers/gpio
目录中创建一个 Makefile 文件:
makefile
深色版本
obj-$(CONFIG_GPIO_DRIVER) += gpio_driver.o
3.1.3 修改 Kconfig 文件
在 drivers/gpio
目录中修改 Kconfig
文件,添加新的配置选项:
makefile
深色版本
config GPIO_DRIVER
tristate "GPIO Driver Support"
help
This option enables support for the GPIO driver.
3.1.4 编译内核模块
编译内核模块:
sh
深色版本
make menuconfig
在 Device Drivers -> GPIO Support
中启用 GPIO Driver Support
。
编译内核:
sh
深色版本
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
4. Bootloader 配置
4.1 获取 U-Boot 源码
从 U-Boot 的官方网站或 GitHub 仓库获取最新的源码:
sh
深色版本
git clone https://github.com/u-boot/u-boot.git
cd u-boot
4.2 配置 U-Boot
根据目标平台选择合适的配置文件。配置文件通常位于 configs
目录下,文件名以 _defconfig
结尾。例如,对于 STM32MP157 处理器,可以使用以下命令:
sh
深色版本
make stm32mp157_defconfig
4.3 修改配置
如果需要自定义配置,可以使用 menuconfig
或直接编辑 .config
文件:
sh
深色版本
make menuconfig
4.4 编译 U-Boot
使用交叉编译工具链编译 U-Boot。假设交叉编译工具链为 arm-linux-gnueabihf-
:
sh
深色版本
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
编译完成后,会在 u-boot
目录下生成以下几个主要文件:
u-boot.bin
:U-Boot 的二进制文件。u-boot.img
:U-Boot 的镜像文件,用于某些启动方式。spl/u-boot-spl.bin
:SPL(Secondary Program Loader)的二进制文件,用于某些处理器的早期初始化。
5. 烧录和测试
5.1 烧录 U-Boot
将生成的 U-Boot 文件烧录到目标板的 Flash 存储器中。常见的烧录工具包括:
- JTAG/SWD:使用 JTAG 或 SWD 接口进行烧录。
- UART:通过 UART 接口使用 U-Boot 的
loadb
命令进行烧录。 - SD 卡:将 U-Boot 文件写入 SD 卡,然后通过 SD 卡启动。
5.1.1 使用 JTAG/SWD 烧录
使用 JTAG 或 SWD 接口进行烧录。例如,使用 OpenOCD 烧录工具:
sh
深色版本
openocd -f interface/jtag.cfg -f target/stm32mp157.cfg -c "init; reset init; flash write_image erase u-boot.bin 0x08000000; reset run"
5.1.2 使用 UART 烧录
通过 UART 接口使用 U-Boot 的 loadb
命令进行烧录。首先,将 U-Boot 二进制文件通过 UART 传输到目标板:
sh
深色版本
loadb 0x40008000
然后,将 U-Boot 写入 Flash 存储器:
sh
深色版本
cp.b 0x40008000 0x08000000 ${filesize}
5.1.3 使用 SD 卡烧录
将 U-Boot 文件写入 SD 卡,然后通过 SD 卡启动。例如,使用 dd
命令将 U-Boot 写入 SD 卡的第一个扇区:
sh
深色版本
sudo dd if=u-boot.bin of=/dev/sdX bs=1k seek=8
5.2 测试 Bootloader
将烧录好的 U-Boot 固件启动到目标板上,通过串口终端(如 PuTTY、minicom)连接到目标板,观察 U-Boot 的启动信息。
5.3 测试字符设备驱动
加载内核模块:
sh
深色版本
sudo insmod gpio_driver.ko
创建设备节点:
sh
深色版本
sudo mknod /dev/gpio_driver c $(cat /proc/devices | grep gpio_driver | awk '{print $1}') 0
测试读写操作:
sh
深色版本
echo 1 > /dev/gpio_driver
cat /dev/gpio_driver
6. 总结
通过上述步骤,我们可以系统地开发一个完整的 BSP,包括硬件初始化、设备驱动编写、Bootloader 配置等。希望这些详细的解释和示例能够帮助你更好地理解和实施 BSP 的开发。