[序言]
学习嵌入式开发的基本都是先从C语言开始学起的,而你的C语言的第一份代码也离不开一个“hello world!”的输出,自此走向了一条“不归路”。而这也仅仅是你学习C语言的过程,当你再深入的学习后,掌握C语言的基本语法,你开始慢慢的打开硬件的大门,接触了各种各样的硬件模块,LED、蜂鸣器、按键、LCD、陀螺仪、GPS、传感器、以太网、USB等等,而大家也基本都是不约而同的从点亮一个LED开始,了解硬件的控制原理以及嵌入式代码的开发流程,慢慢熟悉代码的编写、编译、烧录、运行的过程。
自此,当你学习完一个平台后继续学习下一个平台的开发时,你的第一个项目不出意外的应该也是点亮一个LED灯,那么在不同的开发平台及操作系统下,点灯的代码有什么区别呢?今天就来一起探讨下在51、Arduino、STM32、Linux下的点灯代码吧,各位读者也可以看下你们都在哪个段位呢?
“点灯大师”也是一直被网友调侃,各种花式点灯、流水灯、RGB灯、呼吸灯、频谱灯等等,从入门到点灯,再到放弃。
[硬件电路]
先来看下我们今天的点灯代码基于的硬件电路吧。
从图上不难知道,LED_GPIO给低电平的时候点亮LED,高电平则关闭LED。下面就来看下各个平台下是怎么控制LED的吧!
[51]
P1 = 0xFE; /* 点亮LED */
完事了,把LED接在P10口上就能点亮这个LED。代码最简单。
[Arduino]
void setup() { pinMode(3, OUTPUT); /* 设置GPIO3为输出模式 */ } void loop() { digitalWrite(3, LOW); /* 点亮LED */ digitalWrite(3, HIGH); /* 关闭LED */ }
代码也很简单,比51多了一个配置GPIO的输入/输出方向。
[STM32]
#include "stm32f4xx.h" int main(void) { // 初始化系统时钟 SystemInit(); // 使能GPIOC时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; // 配置GPIOC引脚为推挽输出模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOC, &GPIO_InitStructure); while (1) { // 关闭LED GPIO_SetBits(GPIOC, GPIO_Pin_13); // 点亮LED GPIO_ResetBits(GPIOC, GPIO_Pin_13); } }
到这里好像就有点感觉了,代码变复杂了,但是,还是能看的,也就多了时钟、模式、频率、上下拉的配置项,全部配置完之后就可以用标准库提供的接口来控制LED了。
[Linux]
Linux下点亮一个LED就复杂很多了,先简单介绍下思路。
1- 找到设备树,添加设备节点,在设备节点中描述GPIO引脚信息。
2- 根据linux的字符设备的驱动框架写驱动程序
3- 编写Makefile,把写好的驱动编译成一个模块
4- 在开发板上加载模块
以上可以通过加载和卸载驱动的时候点亮LED,但终归是在驱动上做的控制LED,为了完善,你还要继续编写代码,写一个应用程序去控制LED,最终实现在命令行去控制LED或自动的运行。所以还需要有下面的操作。
5- 编写led app程序,并使用交叉编译器编译
6- 在开发板上运行app程序,点亮led
怎么样,是不是看起来都复杂很多,下面来看下代码实现,分为驱动程序、Makefile、APP程序、设备树。
驱动代码:
led.c:
#include "led.h" struct chardev char_dev; /* 函数申明 */ static int char_open(struct inode *inode, struct file *pfile); static int char_release(struct inode *inode, struct file *pfile); static ssize_t char_read(struct file *pfile, char __user *buf, size_t len, loff_t *offt); static ssize_t char_write(struct file *pfile, const char __user *buf, size_t len, loff_t *offt); static struct file_operations char_dev_fops = { .owner = THIS_MODULE, .open = char_open, .release = char_release, .read = char_read, .write = char_write, }; /* * @ brief : Open the character device driver. * @ param : {struct inode *} inode: device node. {struct file * } pfile: device file, the file structure has a member variable called private_data Normally private_data is pointed to the device structure at open time. * @ return: {int} ret: status. * @ author: Barry * @ modify: None */ static int char_open(struct inode *inode, struct file *pfile) { /* 设置私有数据 */ pfile->private_data = &char_dev; return 0; } /* * @ brief : Open the character device driver. * @ param : {struct inode *} inode: device node. {struct file * } pfile: device file, the file structure has a member variable called private_data Normally private_data is pointed to the device structure at open time. * @ return: {int} ret: status. * @ author: Barry * @ modify: None */ static int char_release(struct inode *inode, struct file *pfile) { return 0; } /* * @ brief : Open the character device driver. * @ param : {struct file *} pfile: device file. {char __user *} buf : Data buffer returned to userspace. {size_t } len : Length of buf. {loff_t *} offt : Offset relative to the first address of the file. * @ return: {int} ret: status. * @ author: Barry * @ modify: None */ static ssize_t char_read(struct file *pfile, char __user *buf, size_t len, loff_t *offt) { return 0; } /* * @ brief : Open the character device driver. * @ param : {struct file *} pfile: device file. {char __user *} buf : Data buffer returned to userspace. {size_t } len : Length of buf. {loff_t *} offt : Offset relative to the first address of the file. * @ return: {int} ret: status. * @ author: Barry * @ modify: None */ static ssize_t char_write(struct file *pfile, const char __user *buf, size_t len, loff_t *offt) { int ret = 0; unsigned char data = 0; struct chardev *dev = pfile->private_data; ret = copy_from_user(&data, buf, len); if(ret < 0) { printk("kernel write failed.\r\n"); return -EFAULT; } printk("data:%d.\r\n", data); gpio_set_value(dev->gpio_num, (int)data); return 0; } /* * @ brief : Character device driver entry functions. * @ param : None * @ return: {int} ret: status. * @ author: Barry * @ modify: None */ static int __init char_dev_init(void) { int ret = 0; /* 获取设备节点:led-gpio1 */ char_dev.node = of_find_node_by_path("/led-gpio1"); if(char_dev.node == NULL) { printk("led-gpio1 node not find.\r\n"); goto fail_find_node; } else { printk("find the led-gpio1 node.\r\n"); } /* 获取设备树中的gpio属性,得到LED所使用的LED编号 */ char_dev.gpio_num = of_get_named_gpio(char_dev.node, "gpios", 0); if(char_dev.gpio_num < 0) { printk("can't get led-gpio number.\r\n"); goto fail_gpio_number; } printk("led gpio number:%d.\r\n", char_dev.gpio_num); ret = gpio_direction_output(char_dev.gpio_num, 1); if(ret < 0) printk("can't set gpio number:%d output high.\r\n", char_dev.gpio_num); gpio_set_value(char_dev.gpio_num, 1); /* 判断是否定义了设备号,定义了则走这个分支 */ if(char_dev.major) { char_dev.devid = MKDEV(char_dev.major, 0); /* 注册设备号 */ ret = register_chrdev_region(char_dev.devid, CHAR_DEV_NUM, CHAR_DEV_NAME); /* 注册失败 */ if(ret != 0) { printk("Failed to register device number.\r\n"); goto fail_devid; } else { printk("Register Device Number Successfully.\r\n"); } } /* 没有定义设备号则先申请设备号 */ else { /* 申请设备号 */ ret = alloc_chrdev_region(&char_dev.devid, 0, CHAR_DEV_NUM, CHAR_DEV_NAME); /* 申请失败 */ if(ret != 0) { printk("Failed to request device number. ret:%d\r\n", ret); goto fail_devid; } else { /* 获取分配的主设备号 */ char_dev.major = MAJOR(char_dev.devid); /* 获取次设备号 */ char_dev.minor = MINOR(char_dev.devid); printk("Successful application for device number. major number:%d, minor number:%d.\r\n", char_dev.major, char_dev.minor); } } /* 初始化cdev */ char_dev.cdev.owner = THIS_MODULE; cdev_init(&char_dev.cdev, &char_dev_fops); /* 添加字符设备 */ ret = cdev_add(&char_dev.cdev, char_dev.devid, CHAR_DEV_NUM); if(ret != 0) goto fail_cdev_add; /* 创建类 */ char_dev.class = class_create(THIS_MODULE, CHAR_DEV_NAME); if(IS_ERR(char_dev.class)) { ret = PTR_ERR(char_dev.class); goto fail_class_creat; } /* 创建设备 */ char_dev.device = device_create(char_dev.class, NULL, char_dev.devid, NULL, CHAR_DEV_NAME); if( IS_ERR(char_dev.device)) { ret = PTR_ERR(char_dev.device); goto fail_device_creat; } printk("%s Driver loaded successfully.\r\n", CHAR_DEV_NAME); return ret; fail_device_creat: /* 删除类 */ class_destroy(char_dev.class); printk("Device creation fails, destroying created classes.\r\n"); fail_class_creat: /* 删除cdev */ cdev_del(&char_dev.cdev); printk("Class creation fails, deleting added character devices.\r\n"); fail_cdev_add: /* 注销设备号 */ unregister_chrdev_region(char_dev.devid, CHAR_DEV_NUM); printk("Failure to add a character device, cancel the registered device number.\r\n"); fail_devid: fail_gpio_number: fail_find_node: return ret; } /* * @ brief : Deregister the character device driver, go here when uninstalling the driver. * @ param : None * @ return: None * @ author: Barry * @ modify: None */ static void __exit char_dev_exit(void) { gpio_set_value(char_dev.gpio_num, 0); /* 删除cdev */ cdev_del(&char_dev.cdev); printk("Delete cdev.\r\n"); /* 注销设备号 */ unregister_chrdev_region(char_dev.devid, CHAR_DEV_NUM); printk("Write-off equipment number.\r\n"); /* 删除设备 */ device_destroy(char_dev.class, char_dev.devid); printk("Delete device.\r\n"); /* 删除类 */ class_destroy(char_dev.class); printk("Delete class.\r\n"); printk("%s Driver uninstallation is complete.\r\n", CHAR_DEV_NAME); } module_init(char_dev_init); module_exit(char_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("bagy"); MODULE_DESCRIPTION("Character device driver template program");
led.h:
#ifndef __CHAR_DEV_TEMP_H__ #define __CHAR_DEV_TEMP_H__ #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 <linux/cdev.h> #include <linux/device.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #define CHAR_DEV_NUM 1 #define CHAR_DEV_NAME "led_gpio1" struct chardev { dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ struct device_node *node; /* 设备节点 */ int gpio_num; /* GPIO编号 */ }; #endif /* __CHAR_DEV_TEMP_H__ */
Makefile:
KERNELDIR := /home/bagy/linux-code/kernel 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
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 LED_ON 1 #define LED_OFF 0 int main(int argc, char *argv[]) { int fd = -1, ret = 0; char *filename = NULL; unsigned char data = 0; if(argc != 3) { printf("param error.\r\n"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0) { printf("file %s open failed.\r\n", filename); return -1; } data = atoi(argv[2]); ret = write(fd, &data, 1); if(ret < 0) { printf("led control failed.\r\n"); close(fd); return -1; } ret = close(fd); if(ret < 0) { printf("file close failed.\r\n"); return -1; } return 0; }
设备树:
led-gpio1 { compatible = "gpio1_led"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpio1_leds>; gpios = <&gpio1 1 GPIO_ACTIVE_LOW>; status = "okay"; };
好家伙,完活了,是不是有点懵,一下子咋这么复杂了,其实只要了解了字符设备的驱动框架就会觉得这个也没啥复杂的,就是使用一些现成的API接口。
好了,到此文章就结束啦,后续小编将会在RK3588S上做一些有趣的开发,再分享给到公众号上。大家也可以评论区评论下当前的段位哦!
如果觉得本篇文章多少有点帮助的话,求赞、求关注、求评论、求转发,创作不易!你们的支持是小编创作最大的动力。