基本知识:
ARM一般会使用电平转换芯片把串口TTL电平转换成相应的232或485电平信号。与232稍有不同的是,一般的485是半双工的,意思就是说同一时刻要么处于接收状态要么处于发送状态。485芯片有许多种,但基本大同小异,一般有2个引脚给用户来控制收发状态。当RE为低电平时,485芯片数据输入有效(低电平接收状态);当DE为高电平时,485芯片数据输出有效(高电平发送状态)。在半双工使用中,通常可以将这两个脚直接相连,然后通过输出的高低电平就可以让485芯片在接收和发送状态之间转换了。
方案:
不管怎样的方案都需要一个485芯片,不同的是收发状态的控制,可以由电路本身控制,也可以接到CPU的IO口由驱动来控制。
1.电路本身控制:市面上可以买到的485转换器都使用这种方案,简单来讲,就是从485芯片的DI脚(接到ARM的TXD)取得收发状态,进而设置485芯片的收发状态。
2.IO口控制:单片机很多使用此方案,因为实现起来简单,但是用在ARM上就不简单了,本文就介绍此方案。
单片机控制的话,大概是这么个流程:一开始是接收状态,数据发送前先置高,并稍作延时,等待485芯片初始化为发送状态,然后开始发送,发送完成后稍作延时,等待发送完成,然后置低进入接收状态。
在ARM中虽然需要修改内核驱动,而且还要占用额外的IO口,但仍然是一个可行的方案,经测试各种波特率都使用正常,连续几天使用过程中也挺稳定。详细请看具体实现方法。
具体实现方法:
S3C2416共有4个串口,UART0、UART1、UART2、UART3。其中UART0用于调试用,将剩余三个改为RS485。最终实现的效果:和一般的232串口一样使用,应用层无需改变,占用的3个GPIO口就不能再做其它用了。
使用用的内核版本为Linux 3.8.5
需要修改内核源代码路径下的 drivers/tty/serial/samsung.c
1.因为要操作GPIO,所以要引用相关头文件
#include <asm/gpio.h>#include <mach/regs-gpio.h> 2.把static struct uart_ops s3c24xx_serial_ops复制一份,并修改成如下,有3处 //2014-8-26 add by whl然后修改://在s3c24xx_serial_ports中,定义了串口相关操作——s3c24xx_serial_ops
static struct uart_ops s3c24xx_485_ops = {.pm = s3c24xx_serial_pm, //电源管理.tx_empty = s3c24xx_serial_tx_empty, //发送缓存区空.get_mctrl = s3c24xx_serial_get_mctrl, //得到modem控制设置.set_mctrl = s3c24xx_serial_set_mctrl, //设置modem控制.stop_tx = s3c24xx_485_stop_tx, //停止发送.start_tx = s3c24xx_485_start_tx, //开始发送.stop_rx = s3c24xx_serial_stop_rx, //停止接受.enable_ms = s3c24xx_serial_enable_ms, //modem状态中断使能.break_ctl = s3c24xx_serial_break_ctl, //控制break信号的传输.startup = s3c24xx_485_startup, //启动端口.shutdown = s3c24xx_serial_shutdown, //禁止端口.set_termios = s3c24xx_serial_set_termios, //设置端口参数.type = s3c24xx_serial_type, //返回描述特定端口的常量字符串指针.release_port = s3c24xx_serial_release_port, //释放端口所占的内存和资源.request_port = s3c24xx_serial_request_port, //申请端口所需的内存和资源.config_port = s3c24xx_serial_config_port, //配置端口.verify_port = s3c24xx_serial_verify_port, //验证端口#ifdef CONFIG_CONSOLE_POLL.poll_get_char = s3c24xx_serial_get_poll_char,.poll_put_char = s3c24xx_serial_put_poll_char,#endif
};
//whl: uart驱动的结构为:
static struct uart_driver s3c24xx_uart_drv = {
.owner = THIS_MODULE,
.driver_name = "s3c2410_serial", //whl: 驱动名,在/proc/tty/driver/目录下显示的名字
.nr = CONFIG_SERIAL_SAMSUNG_UARTS, //whl: uart的端口数
.cons = S3C24XX_SERIAL_CONSOLE,
.dev_name = S3C24XX_SERIAL_NAME, //whl: 设备名——ttySAC
.major = S3C24XX_SERIAL_MAJOR, //whl: 主设备号——204
.minor = S3C24XX_SERIAL_MINOR, //whl: 次设备号——64
};
static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
[0] = {//端口0
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
.iotype = UPIO_MEM, //(2)
.uartclk = 0, //时钟值
.fifosize = 16, //FIFO缓存区大小
.ops = &s3c24xx_serial_ops, //串口相关操作
.flags = UPF_BOOT_AUTOCONF,
.line = 0, //线路
}
},
[1] = { //端口1
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),
.iotype = UPIO_MEM,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_485_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 1,
}
},
#if CONFIG_SERIAL_SAMSUNG_UARTS > 2
[2] = {//端口2
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),
.iotype = UPIO_MEM,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_485_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 2,
}
},
#endif
#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
[3] = {//端口3
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),
.iotype = UPIO_MEM,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_485_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 3,
}
}
#endif
};
3.定义s3c24xx_485_startup
//启动端口
static int s3c24xx_485_startup(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
int ret;
dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n",
port->mapbase, port->membase);
rx_enabled(port) = 1; //接收数据使能
//申请接收数据中断,s3c24xx_serial_rx_chars为中断处理函数
ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
s3c24xx_serial_portname(port), ourport);
if (ret != 0) {
dev_err(port->dev, "cannot get irq %d\n", ourport->rx_irq);
return ret;
}
ourport->rx_claimed = 1; //标志
dbg("requesting tx irq...\n");
tx_enabled(port) = 1; //发送数据使能
//申请发送数据中断,s3c24xx_serial_tx_chars为中断处理函数
ret = request_irq(ourport->tx_irq, s3c24xx_485_tx_chars, 0,
s3c24xx_serial_portname(port), ourport);
if (ret) {
dev_err(port->dev, "cannot get irq %d\n", ourport->tx_irq);
goto err;
}
ourport->tx_claimed = 1; //标志
dbg("s3c24xx_serial_startup ok\n");
/* the port reset code should have done the correct
* register setup for the port controls */
//============================================================
//add by whl 2014-8-26
if (port->line == 1)
{
s3c_gpio_cfgpin(S3C2410_GPH(9), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPH(9), 0);
}
//串口2 对应的端口初始化 add by whl
if (port->line == 2)
{
s3c_gpio_cfgpin(S3C2410_GPH(4), S3C2416_GPH4_TXD2);
s3c_gpio_setpull(S3C2410_GPH(4), 1);
s3c_gpio_cfgpin(S3C2410_GPH(5), S3C2416_GPH5_RXD2);
s3c_gpio_setpull(S3C2410_GPH(5), 1);
s3c_gpio_cfgpin(S3C2410_GPH(8), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPH(8), 0);
}
if (port->line == 3)
{
s3c_gpio_cfgpin(S3C2410_GPE(5), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPE(5), 0);
//printk("%s: channl 3 low\n", __func__);
}
//============================================================
return ret;
err:
s3c24xx_serial_shutdown(port);
return ret;
}
4.定义s3c24xx_485_tx_chars
{
struct s3c24xx_uart_port *ourport = id;
struct uart_port *port = &ourport->port;
struct circ_buf *xmit = &port->state->xmit;
unsigned long flags;
int count = 256; //一次最多发送256个字符
//add by whl 2014-8-26
//printk("s3c24xx_485_tx_chars port->line=%d\n", port->line);
if (port->line == 1)
{
s3c_gpio_cfgpin(S3C2410_GPH(9), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPH(9), 1);
}
if (port->line == 2)
{
s3c_gpio_cfgpin(S3C2410_GPH(8), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPH(8), 1);
}
if (port->line == 3)
{
s3c_gpio_cfgpin(S3C2410_GPE(5), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPE(5), 1);
//printk("%s: channl 3 high\n", __func__);
}
spin_lock_irqsave(&port->lock, flags);
if (port->x_char) { //如果有待发送的字符,则发送
wr_regb(port, S3C2410_UTXH, port->x_char);
port->icount.tx++;
port->x_char = 0;
goto out;
}
/* if there isn't anything more to transmit, or the uart is now
* stopped, disable the uart and exit
*/
//如果没有更多的字符需要发送,或者uart的tx停止,则停止uart并退出
if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
s3c24xx_485_stop_tx(port);
goto out;
}
/* try and drain the buffer... */
//尝试把环形buffer中的数据发空
while (!uart_circ_empty(xmit) && count-- > 0) {
if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
break;
wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
//printk("head = %d, tail=%d\n", xmit->head, xmit->tail);
port->icount.tx++;
}
//如果环形缓存中剩余的字符少于WAKEUP_CHARS,唤醒上层
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) {
spin_unlock(&port->lock);
uart_write_wakeup(port);
spin_lock(&port->lock);
}
if (uart_circ_empty(xmit)) //如果发送环形buffer为空
s3c24xx_485_stop_tx(port); //停止发送
out:
spin_unlock_irqrestore(&port->lock, flags);
return IRQ_HANDLED;
}
5.定义s3c24xx_485_start_tx
static void s3c24xx_485_start_tx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
//add by whl 2014-8-26
if (port->line == 1)
{
s3c_gpio_cfgpin(S3C2410_GPH(9), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPH(9), 1);
}
if (port->line == 2)
{
s3c_gpio_cfgpin(S3C2410_GPH(8), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPH(8), 1);
}
if (port->line == 3)
{
s3c_gpio_cfgpin(S3C2410_GPE(5), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPE(5), 1);
//printk("%s: channl 3 high\n", __func__);
}
if (!tx_enabled(port)) {
if (port->flags & UPF_CONS_FLOW)
s3c24xx_serial_rx_disable(port);
if (s3c24xx_serial_has_interrupt_mask(port))
__clear_bit(S3C64XX_UINTM_TXD,
portaddrl(port, S3C64XX_UINTM));
else
enable_irq(ourport->tx_irq);
tx_enabled(port) = 1;
}
}
6.定义s3c24xx_485_stop_tx
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
if (tx_enabled(port)) {
if (s3c24xx_serial_has_interrupt_mask(port))
__set_bit(S3C64XX_UINTM_TXD,
portaddrl(port, S3C64XX_UINTM));
else
disable_irq_nosync(ourport->tx_irq);
tx_enabled(port) = 0;
if (port->flags & UPF_CONS_FLOW)
s3c24xx_serial_rx_enable(port);
}
//add by whl 2014-8-26
#if 0
printk("before===>\n");
printk("rd_regl(port, S3C2410_UFSTAT)=%x\n", rd_regl(port, S3C2410_UFSTAT)); //0x700
printk("rd_regl(port, S3C2410_UTRSTAT)=%x\n", rd_regl(port, S3C2410_UTRSTAT));//0x00
mdelay(10);
printk("after msleep(10)===>\n");
printk("rd_regl(port, S3C2410_UFSTAT)=%x\n", rd_regl(port, S3C2410_UFSTAT));//0x00
printk("rd_regl(port, S3C2410_UTRSTAT)=%x\n", rd_regl(port, S3C2410_UTRSTAT));//0x6
#endif
while( (rd_regl(port, S3C2410_UFSTAT))&(0x3F<<8) ||
(!((rd_regl(port, S3C2410_UTRSTAT))&0x06)) ){
mdelay(1);
}
mdelay(1);
if (port->line == 1)
{
s3c_gpio_cfgpin(S3C2410_GPH(9), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPH(9), 0);
}
if (port->line == 2)
{
s3c_gpio_cfgpin(S3C2410_GPH(8), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPH(8), 0);
}
if (port->line == 3)
{
s3c_gpio_cfgpin(S3C2410_GPE(5), S3C2410_GPIO_OUTPUT);
gpio_set_value(S3C2410_GPE(5), 0);
//printk("%s: channl 3 low\n", __func__);
}
}
7.最后编译内核,将内核烧写到板子