这周突然需要调试 RS485 方向控制的问题,最开始想想应该问题不大,就加个GPIO口控制就好了,或者用UART 的RTS来作方向控制。当然很多事情都是想得简单,做起来的时候各种意外。
MX28串口的RTS 管脚无法正常使用,打开流控,PIN脚配置OK ,寄存器配置OK,然RTS管脚一直岿然不动。无果,找FAE,反馈也等于没有反馈。
用GPIO 飞线进行测试。应用层操作串口发送前后,分别控制GPIO口高低。惊喜又来了,GPIO口操作有时候会慢好几十个毫秒。应用层也查到tcdrain 这个函数来进行确认是否发送完成,发现
这个函数最大也有可能需要20多个毫秒。
以上种种故事,说明了原来的想法不行。就想着GPIO口在串口驱动中去操作。正题也就开始了。首先声明一下,最终这种方法是实现了,但也不是想的那么简单,道路是曲折的。
1,首先参考了如下做法:
http://kuafu80.blog.163.com/blog/static/122647180201431625820150/
修改完成后测试,发现xxx_start_tx , xxx_stop_tx 这两个函数在我所用的ARM 上,当发送数据的时候,只调用了xxx_start_tx ,而xxx_stop_tx 是没有调用的。这样该方法也就寿终正寝了。
2,开始发送已经能实现提前使能了,但发送完不能及时关闭。在驱动中查找发送完时的地方,然后添加GPIO口操作。因为串口驱动采用中断方式。
-
static void mxs_auart_start_tx(struct uart_port *u)
-
{
-
struct mxs_auart_port *s = to_auart_port(u);
-
-
/* enable transmitter */
-
__raw_writel(BM_UARTAPP_CTRL2_TXE, u->membase + HW_UARTAPP_CTRL2_SET);
-
-
mxs_auart_tx_chars(s);
-
}
-
-
-
static irqreturn_t mxs_auart_irq_handle(int irq, void *context)
-
{
-
...................
-
//发送相关
-
if (istat & BM_UARTAPP_INTR_TXIS) {
-
mxs_auart_tx_chars(s);
-
istat &= ~BM_UARTAPP_INTR_TXIS;
-
}
-
......................
-
}
-
-
-
-
static inline void mxs_auart_tx_chars(struct mxs_auart_port *s)
-
{
-
struct circ_buf *xmit = &s->port.info->xmit;
-
..........................................
-
//中断方式发送相关
-
while (!(__raw_readl(s->port.membase + HW_UARTAPP_STAT) &
-
BM_UARTAPP_STAT_TXFF)) {
-
if (s->port.x_char) {
-
__raw_writel(s->port.x_char,
-
s->port.membase + HW_UARTAPP_DATA);
-
s->port.x_char = 0;
-
continue;
-
}
-
if (!uart_circ_empty(xmit) && !uart_tx_stopped(&s->port)) {
-
__raw_writel(xmit->buf[xmit->tail],
-
s->port.membase + HW_UARTAPP_DATA);
-
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
-
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
-
uart_write_wakeup(&s->port);
-
} else
-
break;
-
}
-
if (uart_circ_empty(&(s->port.info->xmit)))
-
{
-
__raw_writel(BM_UARTAPP_INTR_TXIEN,
-
s->port.membase + HW_UARTAPP_INTR_CLR);
-
tasklet_hi_schedule(&my_task);
-
-
}
-
else
-
__raw_writel(BM_UARTAPP_INTR_TXIEN,
-
s->port.membase + HW_UARTAPP_INTR_SET);
-
-
if (uart_tx_stopped(&s->port))
-
mxs_auart_stop_tx(&s->port);
-
}
到了这里,原本一切应该变得很简单,然而当然很多事情都是想得简单,做起来的时候各种意外。CPU 里面内部自带的串口功能没有FIFO为空或者TIMOUT 的中断。只有一个发送中断。
分析源码发现uart_circ_empty 是判断发送队列是否为空的宏。在tx_chars 中会调用 ,tx_chars 在 start_tx 中会调用,在irq_handle 会被调用。一切很明朗了,就在uart_circ_empty函数中调用 gpio_drection_output 就好 了。当然很多事情都是想得简单,做起来的时候各种意外。
uart_circ_empty函数中调用 gpio_drection_output 后,出现了一种情况,发现数据还没有发送完,GPIO口已经拉低了。具体原因是uart_circ_empty 中判断是不是已经加载完到了FIFO,实际并没有实时发送出去,晚了几 个毫秒。这样也满足不了要求。
怎么办啊,怎么办啊?无法知道串口什么时候才把数据完全丢出去,该CPU串口功能中有一个BUSY位是用于轮询模式查看是否发送完成的标志。天无绝人之路,在uart_circ_empty中一直查看BUSY,等待其发完。实验 测试结果,表示好坑爹啊。可能在中断中,轮询16+8=24 个字节的时间。这对于系统来说 ,要了其老命,没法用。这时候想到了中断的下半部操作。自然就是工作队列,任务队列,软中断等。这里就只能引出下一 篇了,linux 中断底半部机制的差异。
揭晓一下最终做的结果,是这样的。startup 相当于open 。打开的时候初始一个任务队列,然后uart_circ_empty中调用。工作队列还真不行,tasklet_hi_schedule 只有这个能达到所需要的实时性。但这里好像会损失CPU 的性能。
-
759 static int mxs_auart_startup(struct uart_port *u)
-
760 {
-
761 struct mxs_auart_port *s = to_auart_port(u); `
-
762 my_wq_arg = to_auart_port(u) ;
-
763
-
764 tasklet_init(&my_task,my_wq_func,(unsigned long) s);
-
765 ...........
-
xxx }
阅读(13043) | 评论(7) | 转发(0) |