開發板 arduino,在pcduino開發板上寫驅動控制板載LED的閃爍

 2023-11-19 阅读 25 评论 0

摘要:???? 由于關于pcduino的資料比較少,所以這篇文章是參考了pcduino愛好者論壇的一篇教程《手把手教你用A10點燈》,并且系統的結合了linux驅動的開發步驟。讀完這篇文章,你不但可以對pcduino開發板的硬件結構有所了解,更重要的是可以對linux的驅動開

???? 由于關于pcduino的資料比較少,所以這篇文章是參考了pcduino愛好者論壇的一篇教程《手把手教你用A10點燈》,并且系統的結合了linux驅動的開發步驟。讀完這篇文章,你不但可以對pcduino開發板的硬件結構有所了解,更重要的是可以對linux的驅動開發步驟有一個系統的認識。我也是一個linux驅動的新手,所以,寫的不對的地方,請大家指正。

1.Linux驅動框架

???? 這一部分將會手把手教你創建一個Linux的驅動程序框架,在下一部分,我們只需要將控制pcduino硬件部分的代碼填入這個框架就可以了。像所有的應用程序都有一個main函數作為函數的入口一樣,linux驅動程序的入口是驅動的初始化函數。這個初始化函數是 module_init 來指定的,同樣,與初始化函數對應的驅動程序的退出函數是由? module_exit函數來指定的。下面就讓我們動手寫第一個版本的驅動程序吧。

#include <linux/module.h>
#include <linux/init.h>
static int __init led_init(void)
{printk("led init\n");return 0;
}static void __exit led_exit(void)
{printk("led exit\n");
}module_init( led_init );
module_exit( led_exit );


將上面代碼保存為 led.c,接下來就要編寫Makefile文件對剛剛編寫的驅動程序進行編譯了。新建Makefile文件,在里面輸入:

obj-m := led.o
all: make -C /usr/src/linux-headers-3.8.0-35-generic/ M=/home/asus/drive/
clean: rm *.o rm *.ko rm *.order rm *.symvers rm *.mod.c

注意,Makefile? 中的第三行,-C 后面的參數為你當前使用的內核的頭文件所在的目錄,你只需要修改為? "/usr/src/linux-headers-你的內核版本/"? 即可,如果你不知道,當前使用的內核版本,可以輸入:

uname -r

開發板 arduino?來進行查看。M 后面表示你的驅動所在的目錄。改好之后保存,注意,這個文件的名字一定得是? "Makefile"? 才行,make 和 rm命令前面一定是一個TAB符才行。輸入命令:

make

進行編譯,完成之后,使用ls查看,可以看到得到的文件如下:

built-in.o  led.c  led.ko  led.mod.c  led.mod.o  led.o  Makefile  modules.order  Module.symvers

這里面的? led.ko? 是我們得到的驅動文件,使用:
sudo insmod led.ko


安裝驅動。使用
dmesg

命令,會看到最后一行輸出的是?? “led init” ?? ,這句話就是在? led_init? 函數中輸出的。使用命令:
 sudo rmmod led.ko

來卸載? led? 驅動。再使用: dmesg 命令,會發現,最后一行為? “led exit”。

開發版入門選擇什么板,

???? 上面寫的這個驅動程序是沒有什么作用的,在linux中,應用程序是通過設備文件來和驅動程序進行交互的。所以我們需要在驅動程序中建立設備文件,這個設備文件建立之后,就會存在于?? /dev/ ? 目錄下,應用程序就是通過對這個文件的讀寫,來向驅動程序發送命令,并通過驅動程序控制硬件的動作。每一個驅動程序對應著一個設備文件。要建立一個設備文件,首先必須擁有設備號才行,這個設備號就需要我們向linux系統提出申請,由linux系統為我們分配。設備號有主設備號和從設備號之分,主設備號使用來表示驅動的類型,從設備號表示使用同一個驅動的設備的編號,這里要申請的就是主設備號。使用?? alloc_chrdev_region?? 函數來申請一個設備號。設備號的類型為?? dev_t?? ,它是一個 32 位的數,其中 12 位用來表示主設備號,另外 20 位用來表示從設備號。可以使用?? MAJOR?? 宏和?? MINOR?? 宏來直接獲取主設備號和從設備號。我們第二個版本的程序如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>//驅動名
#define DEV_NAME "led"
//從設備的個數
#define DEV_COUNT 1//聲明設備號
static dev_t dev_number;//初始化
static int __init led_init(void)
{//錯誤標記int err;printk("led init\n");//申請設備號err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);if(err){printk("alloc device number fail\n");return err;}//如果申請成功,打印主設備號printk("major number : %d\n",MAJOR(dev_number));return 0;
}static void __exit led_exit(void)
{printk("led exit\n");//注銷申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);
}

這個程序申請了一個設備號,并且打印出來,同樣使用?? dmesg?? 命令來查看,程序的注釋已經很詳細了,就不再多解釋了。 保存之后,編譯,安裝新的驅動程序。在安裝新的驅動程序之前,需要使用命令?? sudo? rmmod? led.ko ? 將之前安裝的驅動程序卸載,使用?? dmesg?? 命令查看輸出的結果:
[  384.225850] led init
[  384.225854] major number : 250


還可以使用命令?? cat? /proc/devices | grep? ‘led’? 查看獲得的設備號。

???? 設備號申請完畢后,就可以在?? /dev/?? 目錄下創建設備文件了。需要了解的是設備在內存中,使用結構體?? cdev?? 來表示,并且將我們申請的設備號,以及對文件操作的回調函數,統統的關聯起來。最后使用這個結構體,用函數?? class_create?? 和?? device_create?? 來創建一個設備文件。說了一下基本思路,還是先看程序吧:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>//驅動名
#define DEV_NAME "led"
//從設備的個數
#define DEV_COUNT 1//三個回調函數,當在應用程序執行相應的操作時
//驅動程序會調用相應的函數來進行處理
ssize_t led_write(struct file *, const char __user *, size_t, loff_t *);
int led_open(struct inode *, struct file *);
int led_release(struct inode *, struct file *);//聲明設備號
static dev_t dev_number;
//設備在內存中表示的結構體
static struct cdev* cdevp;
//注冊文件操作的回調函數的結構體
static struct file_operations fops = 
{.owner = THIS_MODULE,//注冊相應的回調函數.open = led_open,.release = led_release,.write = led_write,
};
//用來創建設備文件的class
static struct class* classp;//初始化
static int __init led_init(void)
{//錯誤標記int err;printk("led init\n");//申請設備號err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);     	if(err){printk("alloc device number fail\n");return err;}//如果申請成功,打印主設備號printk("major number : %d\n",MAJOR(dev_number));//給cdev結構體在內存中分配空間cdevp = cdev_alloc();//如果分配失敗if( cdevp==NULL ){printk("cdev alloc failure\n");//注銷前面申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);return -1;}//將cdev結構體與//注冊文件操作的回調函數的結構體file_operations關聯起來cdev_init(cdevp,&fops);//將cdev結構體和申請的設備號關聯起來err = cdev_add(cdevp,dev_number,DEV_COUNT);if(err){printk("cdev add failure\n");//釋放申請的cdev空間cdev_del(cdevp);//注銷申請的設備編號unregister_chrdev_region(dev_number,DEV_COUNT);return err;}//給class分配空間classp = class_create(THIS_MODULE,DEV_NAME);if( classp==NULL ){printk("class create failure\n");//釋放申請的cdev空間cdev_del(cdevp);//注銷申請的設備編號unregister_chrdev_region(dev_number,DEV_COUNT);return -1;}//創建設備文件device_create(classp,NULL,dev_number,"%s",DEV_NAME);printk("/dev/%s create success\n",DEV_NAME);return 0;
}static void __exit led_exit(void)
{printk("led exit\n");//釋放分配的class空間if( classp ){device_destroy(classp,dev_number);class_destroy(classp);}//釋放分配的cdev空間if( cdevp ){cdev_del(cdevp);}//注銷申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);
}module_init( led_init );
module_exit( led_exit );//當在應用程序中執行  open  函數時,
//會調用下面的這個函數
int led_open(struct inode* pinode,struct file* pfile)
{printk("led open\n");return 0;
}//當在應用程序中執行  close  函數時,
//會調用下面的函數
int led_release(struct inode* pinode,struct file* pfile)
{printk("led release\n");return 0;
}//當在應用程序中調用   write   函數時,
//會調用下面的這個函數
ssize_t led_write(struct file* pfile,const char __user* buf,size_t count,loff_t* l)
{printk("led write");return 0;
}//指定采用的協議
MODULE_LICENSE("GPL");

最后一行是指定采用的協議,一定得寫上,否則會造成雖然編譯通過,但是在安裝時,會出現
insmod: error inserting 'led.ko': -1 Unknown symbol in module
這個錯誤。編譯,安裝好,之后,我們就可以在 ? /dev/ ? 目錄下找到?? led ?? 文件,使用命令:
ls -l /dev/led

結果如下:
crw------- 1 root root 250, 0 Dec 26 10:52 /dev/led

led燈板能貼圖嗎?

???? 至此我們的linux設備驅動框架,已經完全建立起來了。接下來要做的工作,就是對?? pcduino?? 開發板進行編程了。

2.對?? pcduino?? 進行編程,控制? LED? 閃爍

???? 所使用的開發板是pcduino開發板,如下圖:

這是一款開源硬件,采用的是cortex-A8的核心,板上可以安裝ubuntu,android系統,我們使用的板子已經安裝了?? ubuntu?? 系統,通過?? HDMI轉VGA?? 線連接屏幕,并且通過usb接口,連接鍵盤和鼠標,直接在其自帶的ubuntu系統上,編寫驅動并運行。我們仔細的查看板子,會發現板上一共帶有 3 個led燈,分別是? RX_LED,TX_LED,ON_LED,分別用來指示接收,發送和電源的狀態。這里我們只控制? TX_LED? 燈進行閃爍。查看? pcduino? 的硬件原理圖,查找? TX_LED? 的連接位置,如下圖:

led燈板怎么換。會看到第三行?? TX_LED?? 連接到? CPU? 的PH15引腳,并且? L? 即低電平時為激活狀態,H 高電平時,為熄滅狀態。得到這個信息說明,我們只需要控制? CPU? 的引腳? PH15? 的狀態,就可以控制? TX_LED? 的狀態了。

???? 所以接下來就需要我們去查看? A10 的芯片手冊,來看一看到底怎么控制? PH15? 這個引腳。


可以看到? A10? 芯片的引腳有很多,而我們只關注? PH,因為我們要控制的就是? PH15? 這個引腳。這里需要的一個概念就是,對一個引腳的控制至少需要有兩個寄存器,一個是控制寄存器,一個是數據寄存器。控制寄存器用來控制引腳的工作模式,比如輸出或者輸入;數據寄存器用來向引腳輸出數據或者從引腳讀入數據。所以我們要先查看一下? PH15? 的配置寄存器,如下圖:


開發板控制led燈。我們發現?? PH15?? 控制寄存器一共有3位28-30,共有 8 種工作模式,由于要控制 led 的狀態,我們將它設置為輸出模式,所以? PH15? 控制寄存器的內容應該為 001。那么這個寄存器在哪個位置呢,在表上有?? Offset:0x100?? 我們知道,PH寄存器的偏移地址是? 0x100,但是基地址是多少呢。再往前面查閱就會發現

所以基地址就是? 0x01C20800。基地址和偏移地址都有了,我們就可以定位? PH_CFG1? 寄存器的地址就是(0x01C20800+0x100),我們只需要將這個寄存器的第28-30位置為:

30  29  28
0    0   1

就可以了。

???? 當控制寄存器配置完成之后,我們就需要向數據寄存器寫入數據來控制? led? 的閃爍。我們同樣查看芯片手冊:


可以看到,PH的數據寄存器用每一位來表示一個引腳的狀態。我們要控制? PH15 引腳,就需要對這個寄存器的第15位進行操作。所以,接下來就是,開始動手向驅動框架中添加對硬件操作的時候:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>//驅動名
#define DEV_NAME "led"
//從設備的個數
#define DEV_COUNT 1//定義與硬件相關的宏
//基地址
#define BASE_ADDRESS 0x01C20800
//PH_CFG1寄存器的地址
#define PH_CFG1     (BASE_ADDRESS+0x100)
//PH_DAT寄存器的地址
#define PH_DAT	    (BASE_ADDRESS+0x10C)//三個回調函數,當在應用程序執行相應的操作時
//驅動程序會調用相應的函數來進行處理
ssize_t led_write(struct file *, const char __user *, size_t, loff_t *);
int led_open(struct inode *, struct file *);
int led_release(struct inode *, struct file *);//聲明設備號
static dev_t dev_number;
//設備在內存中表示的結構體
static struct cdev* cdevp;
//注冊文件操作的回調函數的結構體
static struct file_operations fops = 
{.owner = THIS_MODULE,//注冊相應的回調函數.open = led_open,.release = led_release,.write = led_write,
};
//用來創建設備文件的class
static struct class* classp;//聲明用來表示PH_CFG1內存地址的變量
volatile static unsigned long* __ph_cfg1;
//用來表示PH_DAT內存地址的變量
volatile static unsigned long* __ph_dat;//初始化
static int __init led_init(void)
{//錯誤標記int err;printk("led init\n");//申請設備號err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);     	if(err){printk("alloc device number fail\n");return err;}//如果申請成功,打印主設備號printk("major number : %d\n",MAJOR(dev_number));//給cdev結構體在內存中分配空間cdevp = cdev_alloc();//如果分配失敗if( cdevp==NULL ){printk("cdev alloc failure\n");//注銷前面申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);return -1;}//將cdev結構體與//注冊文件操作的回調函數的結構體file_operations關聯起來cdev_init(cdevp,&fops);//將cdev結構體和申請的設備號關聯起來err = cdev_add(cdevp,dev_number,DEV_COUNT);if(err){printk("cdev add failure\n");//釋放申請的cdev空間cdev_del(cdevp);//注銷申請的設備編號unregister_chrdev_region(dev_number,DEV_COUNT);return err;}//給class分配空間classp = class_create(THIS_MODULE,DEV_NAME);if( classp==NULL ){printk("class create failure\n");//釋放申請的cdev空間cdev_del(cdevp);//注銷申請的設備編號unregister_chrdev_region(dev_number,DEV_COUNT);return -1;}//創建設備文件device_create(classp,NULL,dev_number,"%s",DEV_NAME);printk("/dev/%s create success\n",DEV_NAME);return 0;
}static void __exit led_exit(void)
{printk("led exit\n");//釋放分配的class空間if( classp ){device_destroy(classp,dev_number);class_destroy(classp);}//釋放分配的cdev空間if( cdevp ){cdev_del(cdevp);}//注銷申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);
}module_init( led_init );
module_exit( led_exit );//當在應用程序中執行  open  函數時,
//會調用下面的這個函數
int led_open(struct inode* pinode,struct file* pfile)
{//臨時變量unsigned long tmp; printk("led open\n");//將PH15管腳設置為輸出狀態//將PH_CFG1這個硬件寄存器的地址,映射到linux內存,并獲取映射后的地址//通過對這個地址的操作,就可以控制PH_CFG1__ph_cfg1 = (volatile unsigned long*)ioremap(PH_CFG1,4);//將設置PH15寄存器tmp = *__ph_cfg1;tmp &= ~(0xf<<28);tmp |= (1<<28);*__ph_cfg1 = tmp;//將燈初始化為熄滅的狀態__ph_dat = (volatile unsigned long*)ioremap(PH_DAT,4);tmp = *__ph_dat;tmp |= (1<<15);*__ph_dat = tmp;	return 0;
}//當在應用程序中執行  close  函數時,
//會調用下面的函數
int led_release(struct inode* pinode,struct file* pfile)
{printk("led release\n");//注銷分配的內存地址iounmap(__ph_dat);iounmap(__ph_cfg1);return 0;
}//當在應用程序中調用   write   函數時,
//會調用下面的這個函數
ssize_t led_write(struct file* pfile,const char __user* buf,size_t count,loff_t* l)
{int val;volatile unsigned long tmp;printk("led write\n");//從用戶空間讀取數據copy_from_user(&val,buf,count);	printk("write %d\n",val);//從應用程序讀取命令//來控制led燈tmp = *__ph_dat;if( val==1 ){//燈亮tmp &= ~(1<<15); }else{//燈滅tmp |= (1<<15);}*__ph_dat = tmp;return 0;
}MODULE_LICENSE("GPL");

32開發板。

???? 上面的是完整的控制pcduino上led閃爍的驅動程序,寫完這個驅動程序之后,再寫一個下面的測試程序就可以使 led 閃爍了,測試的代碼如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>int main(void)
{int fd;int val = 1;//打開驅動對應的設備文件fd = open("/dev/led",O_RDWR);if( fd<0 ){printf("open /dev/led error\n");return -1;}while(1){//寫入高電平write(fd,&val,sizeof(int));//睡眠一秒sleep(1);//將電平反轉val = 0;//寫入低電平write(fd,&val,sizeof(int));//睡眠一秒sleep(1);val = 1;}close(fd);return 0;
}

使用? gcc testled.c 將該應用程序編譯,假設生成a.out,安裝新版的驅動程序后,使用
sudo ./a.out

就可以看到? pcduino? 上的? led? 就開始閃爍了。

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/183626.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息