手把手教你用pcDuino的A10点灯
本帖最后由 哆啦A梦 于 2014-2-19 18:54 编辑很多人问,我们板子上的arduino IDE或者c_environment做了什么让板子上的IO口有输入输出的功能。这篇文章将会告诉你,pcDuino是怎么工作的。
pcDuino板载的是嵌入式ubuntu或者android系统,这里必然要涉及到linux操作底层硬件的原理。大家都知道linux系统里面一切皆文件,那么linux系统怎么让硬件IO变成文件呢,请看下面一张图。
搭建环境
sudo apt-get update
sudo apt-get installvimpcduino-linux-headers-3.4.29+
两条命令就在pcDuino上面把嵌入式开发环境搭建好了。
看手册
这篇文章的目的是手把手教你将板子上面的RX,TX闪烁起来,首先要搞清楚板子RX和TX接在哪儿了,这个需要看原理图。
https://s3.amazonaws.com/pcduino/Hardware/PC+Duino_V01-20130128.pdf
0—27和PH的28和GPIO一一对应,下面就可以根据这些信息写出相应的宏定义了。10 #define GPIO_BASE 0x01C20800
11 #define GPH_CFG1 (GPIO_BASE + 0×100)
12 #define GPH_CFG2 (GPIO_BASE + 0×104)
13 #define GPH15_CFG (1 << 28)
14 #define GPH16_CFG (1 << 0)
15 #define GPH_DATA (GPIO_BASE + 0x10c)
写Linux LED驱动
这里就不再啰嗦了,我尽量在程序里面注释的详细一些。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
/*根据芯片手册得到的宏定义*/
#define GPIO_BASE 0x01C20800
#define GPH_CFG1(GPIO_BASE + 0×100)
#define GPH_CFG2(GPIO_BASE + 0×104)
#define GPH15_CFG (1 << 28)
#define GPH16_CFG (1 << 0)
#define GPH_DATA(GPIO_BASE + 0x10c)
/*自动创建设备节点结构体*/
static struct class *leddrv_class;
/*cfg1里面有PH15,cfg2里面有PH16*/
volatile unsignedlong*gph_cfg1 = NULL;
volatile unsignedlong*gph_cfg2 = NULL;
volatile unsignedlong*gph_date = NULL;
/*设备打开函数,当你在应用程序里面对led执行open函数这个函数就会被执行*/
static int led_drv_open(struct inode *inode, struct file *file)
{
/*将PH15_CFG位清零*/
*gph_cfg1 &= ~(GPH15_CFG);
/*将PH15_CFG位置1,即设置为输出模式*/
*gph_cfg1 |= GPH15_CFG;
*gph_cfg2 &= ~(GPH16_CFG);
*gph_cfg2 |= GPH16_CFG;
/*在内核里面打印要用printk*/
printk(KERN_ALERT”led openi\n”);
return 0;
}
/*设备写函数,当你在应用程序里面对led执行write函数这个函数就会被执行*/
static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk(KERN_ALERT”led write\n”);
int val ;
/*内核空间和用户空间相互独立,两者之间数据交换要用这个函数*/
copy_from_user(&val,buf,count);
if(val == 1)
{
/*置0,相当pin_write写0*/
*gph_date &= ~(0×03<<15);
}
else
{
/*置1,相当pin_write写1*/
*gph_date |= (0×03<<15);
}
return 0;
}
/*linux设备驱动里面每个设备都得要一个设备描述符,其实就可以结构体,有兴趣的可以看看他的原型*/
static struct file_operations led_drv_fops = {
.owner= THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = led_drv_open, /*函数指针赋值*/
.write= led_drv_write, /*函数指针赋值*/
};
/*主设备号变量*/
int major;
/*当执行insmod命令的时候这个函数会被执行 */
static int led_drv_init(void)
{
/*注册一个LED设备,参数0表示让操作系统分配主设备号,分配好了之后返回给major */
major = register_chrdev(0, “led_drv”, &led_drv_fops);
/*创建设备节点,有了这两个函数,我们在文件系统里面就会看到/dev/led */
leddrv_class = class_create(THIS_MODULE,”led_drv”);
device_create(leddrv_class,NULL,MKDEV(major,0),NULL,”led”);
/*linux系统里面都是虚拟地址 ioremap就是将虚拟地址转换为物理地址,然后我们可以对寄存器进行操作
*第一个参数表示需要映射的基地址,第二个参数表示长度,即基地址+0×10*/
gph_cfg1 = (volatile unsigned long*)ioremap(GPH_CFG1,16);
/*0×104 = 0×100 +4*/
gph_cfg2 = gph_cfg1 + 1;
/*0×104 = 0×100 +4*3*/
gph_date = gph_cfg1 + 3;
printk(KERN_ALERT”register\n”);
return 0;
}
/*当执行rmmod的时候会被执行*/
static void led_drv_exit(void)
{
/*注销LED设备*/
unregister_chrdev(major, “led_drv”); // 卸载
/*释放申请的地址*/
iounmap(gph_cfg1);
/*销毁创建的设备节点*/
device_destroy(leddrv_class,MKDEV(major,0));
class_destroy(leddrv_class);
printk(KERN_ALERT”unregister\n”);
}
/*linux设备驱动的两个修饰符*/
module_init(led_drv_init);
module_exit(led_drv_exit);
/*遵循GPL协议*/
MODULE_LICENSE(“GPL”);
编写Makefile
KERN_DIR = /usr/src/linux-headers-3.4.29+/
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led.o
编译:make
加载:sudo insmod led.ko
卸载:sudo rmmod led
测试
根据驱动程序,只需要写一个简单的测试代码就可以了。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int fd;
int val = 1;
fd = open(“/dev/led”,0666);
if(fd < 0)
{
printf(“cannot open\n”);
return 0;
}
while(1)
{
write(fd,&val,sizeof(int));
sleep(1);
val = 0;
write(fd,&val,sizeof(int));
val = 1;
sleep(1);
}
close(fd);
return 0;
编译:gcc led_test.c
执行:sudo ./a.out
LinkSprite学习中心
你好上面程序有些看不懂的地方,我问问,那个左移15位是起什么作用,15位对应的是什么?
页:
[1]