弄好了,有同样问题的请参考这个。
最近在开发板上准备使用非标准波特率的串口处理通讯,但linux下的串口设置总是报错,于是打起更改驱动的主意,开源世界就是自由自在,更改驱动是个艰辛的过程,整的内核经常down掉,但最后终于有些进展,达到目标。
作者Kozmers kozmers@yahoo.com http://www.kozmers.com
配置:
ARM9 : S3C2440
系统 : linux-3.0.4
1. 设置非标准波特率的应用程序
需要使用ioctl()
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
set_baud(int fd, int baud)
{
int status;
struct termios Opt;
struct serial_struct Serial;
tcgetattr(fd, &Opt);
tcflush(fd, TCIOFLUSH);
printf("\ncfsetispeed(&Opt,B38400)\n");
cfsetispeed(&Opt, B38400);
cfsetospeed(&Opt, B38400);
tcflush(fd,TCIOFLUSH);
status = tcsetattr(fd, TCSANOW, &Opt);
if (status != 0)
{
perror("tcsetattr fd1");
return;
}
if((ioctl(fd,TIOCGSERIAL,&Serial))<0)
{
printf("Fail to get Serial!\n");
return;
}
printf("TIOCGSERIAL: OK.\n");
Serial.flags = ASYNC_SPD_CUST;
Serial.custom_divisor=Serial.baud_base/baud;
printf("divisor is %x\n",Serial.custom_divisor);
if((ioctl(fd,TIOCSSERIAL,&Serial))<0)
{
printf("Fail to set Serial\n");
return;
}
printf("TIOCSSERIAL: OK.\n");
ioctl(fd,TIOCGSERIAL,&Serial);
printf("\nBAUD: success set baud to %d,custom_divisor=%d,baud_base=%d\n",baud
,Serial.custom_divisor,Serial.baud_base);
}
int
main(int argc, char **argv)
{
int nonstd_baud = 11000;
if(argc !=2)
{
printf("Usage: elf /dev/ttyS0");
return (-1);
}
int fd = open(argv[1], O_RDWR | O_NOCTTY ); // | O_NDELAY);
if(-1 == fd)
{
perror("Open");
return -1;
}
set_baud(fd, 10400);
close(fd);
return 0;
}
kozmers kozmers@yahoo.com http://www.kozmers.com
注意这里需要先设置波特率到38400,然后设置flag为custom,最后用ioctl函数设定serial struct结构给驱动。
2. 那么驱动中是如何接受这个ioctl呢。
kozmers kozmers@yahoo.com http://www.kozmers.com
S3C2440的UART驱动在内核中是drivers/tty/serial/s3c2440.c
而它尽实现了设备注册等等相关的方法和时钟设定方法。
处理串口初始化和设定波特率的真正代码在drivers/tty/serial/samsung.c中,每个函数都以s3c24xx开头,被具体的s3c2440.c中调用。而samsung.c中也未实现ioctl,这部分是被serialc_core.c中统一完成的。
我们知道设定串口波特率的真正方式是向寄存器中设定时钟和分频。
那么就要确定好初始化时候的时钟选择了什么,如何根据目标波特率得到合理的分频值。
分别为port->uartclk和custom_divisor。
原有的驱动在init_port函数中仅对port->uartclk=1。经测试无法完成后续自定义目标。
kozmers kozmers@yahoo.com http://www.kozmers.com
于是做此更改:
drivers/tty/serial/samsang.c
kozmers kozmers@yahoo.com http://www.kozmers.com
init_port{
port->uartclk = clk_get_rate(clk_get(&platdev->dev, "pclk")); // 使用pclk作为uartclk时钟。
// port->uartclk = 1; // 将原来的注释掉。
...
}
kozmers kozmers@yahoo.com http://www.kozmers.com
但完成后编译好并调试,使用应用程序后内核就挂掉了,原因是17号空指针,定义在set_termios函数中的serial_set_source方法。
经查找发现判断38400波特率后绕开了clksrc的赋值,clksrc仍然为NULL,下边还把它传递给port设定,当然不可。于是将set_source包含在非38400的else里,如下:
kozmers kozmers@yahoo.com http://www.kozmers.com
set_termios{
...
if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
quot = port->custom_divisor;
else{ // kozmers modify
quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud);
if (ourport->clksrc != clksrc || ourport->baudclk != clk) {
dbg("selecting clock %p\n", clk);
s3c24xx_serial_setsource(port, clksrc);
...
}
}
kozmers kozmers@yahoo.com http://www.kozmers.com
这次再编译,在开发板上运行就OK了。非标准波特率使用成功。
总结:在内核中更改一个文件drivers/tty/serial/samsung.c
1.将init_port中的port->uartclk = clk_get_rate(clk_get(&platdev->dev, "pclk"));
2.将set_termios中的if判断的else包含内容扩宽,更改为:
if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
quot = port->custom_divisor;
else{ // kozmers modify
quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud);
if (ourport->clksrc != clksrc || ourport->baudclk != clk) {
dbg("selecting clock %p\n", clk);
s3c24xx_serial_setsource(port, clksrc);
...
}
kozmers kozmers@yahoo.com http://www.kozmers.com