Akem
的
SAMSUNG_S3C2410串口驱动
今天写了一个2410的串口驱动.在此总结一下.也当作一次复习. 废话不多说了.进入正题.
2410支持3个COM口,在u-boot中已经打开了COM1, 所以现在要打开COM2口.以模块方式加载驱动程序.并在COM2口打印测试. 开发板上有两个串口接口,所以要用两台PC机进行测试.一台作为宿主机COM1,一台测试串口读写COM2.
在开发板已经下载了Linux2.4内核.通过NFS文件系统.和宿主机进行开发通信.也就是说在PC机上进行开发和交叉编译,把目标文件通过NFS加载到开发版上运行
操作步骤:
串口数据发送
1. 设置串口工作在中断和轮训模式
2. 设置串口的数据位长度, 停止位, 是否有奇偶校验位
3. 设置串口收发数据波特率
4. 将数据写到发送寄存器
5. 检测状态寄存器相关位判断是否发送完成
串口数据读取
1. 设置串口工作在中断和轮询模式
2. 设置串口收发数据位长度, 停止位, 是否有奇偶校验
3. 设置串口收发数据的波特率
4. 检测状态寄存器是否有数据
5. 读取数据
代码
----------------------------------------------------------------------
utrs_driver.h 配置相关寄存器
-------------------------------------------------------------------------------
#include<asm/hardware.h>
/* 通过2410手册得到的串口寄存器地址 ,驱动要设置的相关寄存器 */
/* 控制寄存器: 设置数据位, 停止位, 校验位 0x50004000*/
#define UART_ULCON1 __REG(0x50004000 + 0x00)
/* 设置串口工作模式, UCON1是第二串口,偏移量0x04设置中断轮询模式 */
#define UART_UCON1 __REG(0x50004000 + 0x04)
/* 设置接收寄存器, 发送数据偏移量0x24是L模式 */
#define UART_URXH1 __REG(0x50004000 + 0x24)
/* 设置接收寄存器, 发送和接收状态,偏移量0x20是L模式 */
#define UART_UTXH1 __REG(0x50004000 + 0x20)
/* 设置串口通讯波特率寄存器 */
#define UART_UBRDIV1 __REG(0x50004000 + 0x28)
/* 设置接受状态偏移量0x10设置清空buffer */
#define UART_UTRSTAT1 __REG(0x50004000 + 0x10)
int s3c2410_serial_open(void);
int s3c2410_serial_release(void);
int s3c2410_serial_read(unsigned char *buf, int count);
int s3c2410_serial_write(unsigned char *buf, int count);
---------------------------------------------------------------------------------------
utrs_driver.c 驱动模块
---------------------------------------------------------------------------------------
#include<linux/config.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include"utrs_driver.h"
/* 打开串口并且初始化 */
int s3c2410_serial_open(void)
{
UART_UCON1 = 0x05; /* 初始化控制寄存器的 [3 : 0] 读写模式为中断轮询 */
UART_ULCON1 = 0x03; /* 初始化线控制寄存器 [1 : 0] 读写为8-bit即一个字符 */
UART_UBRDIV1 = 26; /* 初始化串口波特率 26 为115200 */
return 0;
}
int s3c2410_serial_release(void)
{
return 0;
}
/* 接收模块 */
int s3c2410_serial_read( unsigned char *buf, int count )
{
int i;
unsigned char ch;
for(i = 0; i < count; i++)
{
while(!(UART_UTRSTAT1 & 0x1)); /* 监控接收寄存器是否有数据 */
ch = UART_URXH1; /* 如果有数据用一个字符保存 */
*(buf + i) = ch; /* 然后放在缓冲区buffer里 */
}
return count;
}
/* 发送模块 */
int s3c2410_serial_write( unsigned char *buf, int count )
{
int i;
unsigned char ch;
for(i = 0; i < count; i++)
{
ch = *(buf + i); /* 从缓冲区读一个字符 */
while(!((UART_UTRSTAT1 & 0x2) == 0x2)); /* 判断接收寄存器里是否有为空 */
UART_UTXH1 = ch; /* 如果为空就向寄存器里写入数据 */
}
return 0;
}
/* 串口的读写模块是分开的,并不是说write发送read就是接收,当写过去的时候COM2屏幕就已经打印了, read是在COM2写数据在宿主机打印出来 */
---------------------------------------------------------------------------------------
utrs_test.c 测试模块
---------------------------------------------------------------------------------------
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/string.h>
#include"utrs_driver.h"
int s3c2410_serial_init(void)
{
unsigned char str[200] = "HELLO FUCK!/r/n";
unsigned char ch;
int len;
len = strlen(str);
printk("init moule!/n");
s3c2410_serial_open(); /* 打开串口 */
s3c2410_serial_release();
s3c2410_serial_write(str, len); /* 相串口里写数据, 此时COM2连接的PC会打印出HELLO FUCK! */
while(ch != '#')
{
s3c2410_serial_read(&ch, 1); /* 在COM2的PC上输入数据会写入到接收寄存器, 在宿主机打印 */
/* 寄存器是以一个字节读写 */
printk("%c",ch); /* 把在COM2端写入的数据打印到主机 */
}
return 0;
}
void s3c2410_serial_cleanup(void)
{
printk("clean moule!/n");
}
int init_module(void) /* 加载模块 */
{
s3c2410_serial_init();
return 0;
}
void cleanup_module(void) /* 卸载模块 */
{
s3c2410_serial_cleanup();
}
-------------------------------------------------------------------------------------------
驱动程序是在内核启动时,通过系统脚本创建设备文件的,而应用程序要和驱动打交道就必须通过打开相应的设备文件进行操作。上述的例子不能算是一个完整的驱动程序,因为是通过模块加载的方式对设备进行操作,并没有创建设备文件。也就是说要手工的进行加载。以下的例子是通过创建真实设备文件的形式,所以是货真价实的驱动程序。
----------------------------------------------------------------------------------------------------------------
utrs_driver.h
----------------------------------------------------------------------------------------------------------------
#include<asm/hardware.h>
#define UART_ULCON1 __REG(0x50004000 + 0x00)
#define UART_UCON1 __REG(0x50004000 + 0x04)
#define UART_URXH1 __REG(0x50004000 + 0x24)
#define UART_UTXH1 __REG(0x50004000 + 0x20)
#define UART_UBRDIV1 __REG(0x50004000 + 0x28)
#define UART_UTRSTAT1 __REG(0x50004000 + 0x10)
----------------------------------------------------------------------------------------------------------------
utrs_driver.c
----------------------------------------------------------------------------------------------------------------
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <asm/hardware.h>
#include "utrs_driver.h"
#define DEVICE_NAME "akem" //设备文件名
#define BUTTON_MAJOR 232 //主设备号
/* 打开驱动inode结构在内部表示文件,file结构是打开上面传来的文件描述符fd对应的file结构,file结构都指向单个inode */
static int akem_open(struct inode *inode, struct file * file)
{
/* 设置寄存器 */
UART_UCON1 = 0x05;
UART_ULCON1 = 0x03;
printk("Kernel : in open,we do nothing....../n");
return 0;
}
/* 从设备读取数据,上层的read函数会调用到这里,file是read的fd对应的结构, ppos是系统回调 */
static int akem_read(struct file * file, char * buff, size_t count, loff_t *ppos)
{
int i;
unsigned char ch;
for(i = 0; i < count; i++)
{
while(!(UART_UTRSTAT1 & 0x1)); /* 扫描寄存器是否有数据 */
ch = UART_URXH1; /* 从寄存器里读出一个字节 */
*(buff + i) = ch; /* 存放在buff里 */
}
return strlen(buff); /* 返回buff的长度 */
}
/* 向设备写入数据,上层的write函数会调用到这里,file是write的fd对应的结构, offp是系统回调 */
int akem_write( struct file *filp, const char *buff, size_t count, loff_t *offp )
{
int i;
unsigned char ch;
for(i = 0; i < count; i++)
{
ch = *(buff + i);
while(!((UART_UTRSTAT1 & 0x2) == 0x2)); /* 扫描寄存器是否有数据 */
UART_UTXH1 = ch; /* 把一格字节写入寄存器 */
}
return 0;
}
/* 设置串口的波特率 */
static int akem_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int ret;
ret = -EINVAL;
switch(cmd)
{
case 111: /* 功过命令来配置波特率 */
{
if(arg == 115200)
{
UART_UBRDIV1 = 26;
ret = 0;
}
}; break;
default:
return -EINVAL;
}
printk("Kernel : in ioctl, (%d, %d)/n", cmd, arg);
}
int akem_release(struct inode *inode, struct file *filp)
{
printk("release!!/n");
return 0;
}
/* file_operations结构是建立驱动程序和设备编号的连接,内部是一组函数指针,每个打开的文件,也就是file结构,和一组函数关联,这些操作主要用来实现系统调用的 */
static struct file_operations akae_fops = {
owner: THIS_MODULE,
open: akae_open,
ioctl: akae_ioctl,
read: akae_read,
write: akae_write,
release: akae_release
};
/* 初始化驱动模块,驱动程序要注册一个设备文件,并对其进行操作 */
static int __init akem_init(void)
{
int ret;
int ready = 0;
/* 注册一个字符设备,并分配设备编号,major是设备号,name是驱动程序名称 fops是file_operations结构 */
ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &akae_fops);
if (ret < 0)
{
printk(DEVICE_NAME " can't register major number/n");
return ret;
}
printk(" init ok!....../n");
return 0; /* 注册成功返回0 */
}
/* 卸载模块 */
static void __exit akem_exit(void)
{
/* 如果不使用该设备时释放编号 */
unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
printk("bye !/n");
}
module_init(akem_init);
module_exit(akem_exit);
MODULE_LICENSE("GPL");
----------------------------------------------------------------------------------------------------------------
test.c 测试模块
这个测试模快是以应用程序调用驱动,而不以模块的形式加载,所以这正是驱动本质
----------------------------------------------------------------------------------------------------------------
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define BOUNDRATE 111 /* 设置伯特率命令参数 */
int main(void)
{
int fd;
int ret = 0;
char buf[100] = "HELLO FUCK!/n/r";
char ch;
fd = open("/temp/akem", O_RDWR); /* open会直接调用到驱动里的akem_open */
if(fd == -1)
{
perror("open");
exit(1);
}
ret = ioctl(fd, BOUNDRATE, 115200); /* 设置伯特律115200 */
ret = write(fd, buf, strlen(buf)); /* write会调用驱动里的akem_write */
if(ret == -1)
{
perror("write error");
exit(1);
}
while(ch != '#')
{
read(fd, &ch,1);
printf("%c",ch);
fflush(stdout);
}
close(fd);
}