|
本帖最后由 QianFan 于 2015-10-29 15:30 编辑 可能觉得使用串口很简单,无非就是初始化GPIO,初始化串口。接着发送---检测是否成功。表面上看来是很简单的问题。然而,我要说的并不是这些。我要说的是volatile和中断向量表的问题。在其中配合一点gdb调试。 使用ringbuffer, p+ C9 b! v2 t( E7 N4 ~ 这里的串口使用非缓冲发送,ringbuffer中断缓冲接收的方式。先来看看ringbuffer。ringbuffer是一个特殊的队列,FIFO。 struct ringbuffer { uint8_t *bf;/ Q3 [! k. \, s- ~8 I int len; : }& j/ ^5 J# t2 L: d9 N: ?: U int count;" m& V+ G6 H/ Z. J0 Y int putidx; /* read index and put index */ int getidx; };0 V$ j$ h- U% Y/ W1 ? struct ringbuffer对一个数组中的数据进行管理。数组的字节个数是len。更加详细的细节请大家尽情Google。写了一个宏,用来声明一个ringbuffer用来管理内存,准确的说叫做定义更好一些。 #define DECLARE_RB(name,len) \; Q" n2 k3 P) s* g5 Z5 `! ^ uint8_t name##_buff[len]; \ struct ringbuffer name = { \4 G- R* N* |; D0 z name##_buff,len,0,0,0 }! v) u( ~9 h# u: E4 J$ q6 _ 提供的接口函数: #define rbcount(prb) ((prb)->count) 3 G$ M7 l2 b, ^* \ g' Q #define rbfull(prb) ( (prb)->count==(rb)->len ) #define rbempty(prb) ( (prb)->count==0 )* l/ i! C% U6 L. I. w6 p" z /* if ringbuffer is full,return 0.else return 1 */ int rbput(struct ringbuffer *prb,uint8_t add); /* if ringbuffer is empty,return -1.else return bf[tail] */ int rbget(struct ringbuffer *prb);/ E5 h+ U! s" w6 O$ {7 f$ P5 P3 Q rbput是往ringbuffer中添加一个数据。rbget是从ringbuffer中读取一个数据。相关的源代码: g: `, K; B1 H7 i' x #include "ringbuffer.h"' y$ e" A! |# k. W* e int rbput(struct ringbuffer *rb,uint8_t add)( t: d+ I; E7 M# j- {9 v {0 K9 Z$ i+ P: d2 \! Y; w int curidx=rb->putidx; if(rbfull(rb)) return 0;$ T. h- G3 D; k' W rb->bf[curidx]=add;( K- D9 i! E |7 C" ]4 P rb->putidx=(curidx+1)%rb->len;6 V" Q/ L1 v6 r) d rb->count++; return 1; y; v& q0 C4 t- x' o9 s& S1 U: X }/ \, q6 R' }5 ?9 F5 ] int rbget(struct ringbuffer *rb)' Y0 x+ N M9 Q& } {5 \ F% b3 I& U2 i0 z int result;* G+ D) V' D1 \$ l. p; p int curidx=rb->getidx; if(rbempty(rb)) return -1; result=rb->bf[curidx]; rb->getidx=(curidx+1)%rb->len; rb->count--; return result; }% Z5 g7 h. x7 t* D% g 串口中断+ringbuffer w5 c/ E0 r! T 在串口的初始化的时候,打开串口中断以及RXNE中断。在中断代码中将接受到的字符rbput到ringbuffer中。& q j4 B$ U- X2 G' r5 b /* USART interrupt handler */ USART_Handler(); q5 o: U% a: [7 ]( t7 \2 ? {8 U/ k! D# p, `& o } if(USART_GetITStatus(USART,USART_IT_RXNE)==SET){ rbput(&rb_usart,USART_ReceiveData(USART));: t) V, b! [. l: d* a n } } 编写__io_getchar() __io_putchar()函数。用于发送和读取一个数据。1 A2 v6 m- d. p3 r DECLARE_RB(rb_usart,USART_RD_BUF_LEN); int __io_putchar(int ch) { while(USART_GetFlagStatus(USART,USART_FLAG_TXE)!=SET); USART_SendData(USART,ch); return ch;' h) i6 C( H6 \( S } int __io_getchar(void) {1 }, J% T2 R! G9 X signed char ch; while(rb_usart.count==0) continue; ch=rbget(&rb_usart);3 q6 m2 [3 `3 {- t# s3 e: C switch(ch), G' ~0 L- k+ A/ l: g. g: l. X) \- D$ o { case '\r':ch='\n';break; case 0x1b:ch=' ';break; default: break; }' r ^( J8 u. [! ?" \) g* i /* echo it *// u0 `3 a. }) ~+ _2 l9 ^4 y( s6 ]% j return __io_putchar(ch);% M' L6 V% H' u# o/ [% ~8 R& r7 T }; m" T/ W8 N$ A& T- E 当在使用minicom发送特殊的按键,比如Up,Down,Left,Right按键的时候,会发送以0x18开始的三个或者若干字符。对于这些,我们只显示0x18之后的字符。6 v+ l& H5 k: Y* R1 R/ _0 }, ^ 由于使用minicom,putty一类的串口,发送的数据并不能回显。因此,要想有回显,只能在读取的时候进行回显。在中断中回显并不是一个好的方法,会占用一定的中断时间。因此我将回显放在__io_getchar函数中。 在主函数中一直读取字符) f2 |9 p% q0 p/ _6 c. _ 我们的主函数中什么也不做,一直在读取字符。由于读取的时候会有回显,所以不论我们按下什么按键,都会实时回显。 int main(void) { const char *str="Try to enter something...\n";9 a) U- N8 m; i" `: k; I const char *tmp=str;( ^3 E5 x/ Q4 `; Z. L for(;*tmp!='\0';tmp++) __io_putchar(*tmp); while(1) __io_getchar();: ]8 I6 t. ?: g }1 H i+ t4 \' t. z( h: G * R% x, \$ S! I/ S+ V void _exit(int status)2 [" \4 c! w. E- Z# { {2 y) b* [9 ?! y9 N; t; c const char *exit_str="GoodBye!\n"; const char *tmp=exit_str; for(;*tmp!='\0';tmp++) __io_putchar(*tmp);" B0 m4 i7 Y9 I for(;;) continue;- r, V4 C* `9 r; V8 r/ e" H# b, q6 F% U } 下载到FLASH中运行- {5 ~& Z8 d3 ]. P$ a2 S 目前所有的代码都在serial_v1.zip中,大家可以自行下载测试。 试着使用make all,make burn将代码下载至FLASH中。发现Try to enter something...这几个字符确实能够输出,但是不管我们按下什么按键都不能回显。暂时先不要使用make sram, make burns ./ v! p( c' ]: b# f5 O* {; y 不管什么原因,至少能够显示也是好的。 3 E' q; h! x. Q& X" J' u 使用gdb调试代码, R1 ^2 J+ k. s2 q 新建一个控制台窗口,输入sudo st-util。连接st-link与stm32。st-util会监听4242端口。使用这个端口可以与arm-none-eabi-gdb进行通信。
在当前目录下启动控制台窗口,输入arm-none-eabi-gdb blink.elf。由于使用了上一个例子的Makefile,所以文件名字blink.elf并没有更改。 在gdb串口使用tar连接远程终端。如下图所示:! @0 k. O* z% k2 y8 Q% ^
tar连接完毕之后,使用load命令将代码下载至单片机内。 ; w2 e+ H$ e- E 至于没有回显字符,我首先想到的原因是串口RXNE中断没有进去。我可以在串口中断里面设置一个断点。当进入串口中断的时候自动停止。设置断点需要使用break命令+行号。为了查看usart.c的内容,可以使用list命令。list命令(可以简写成l,小写的L,不是数字1)有几种形式。: G5 _2 C9 C! E" {" S
在第33行,也就是进入RXNE中断处设置一个断点。如果想查看所有已经设置的断点,可以使用info break查看。想删除断点,先使用info break查看对应断点的序号,在使用delete删除即可。
在设置完断点之后,可以输入continue(或者c)。继续运行。直到遇到断点停止。
在continue之后,可以看见确实通过串口把数据输出了。这时候,可以试着在minicom中输入一个字符。我随便点了一个c。这时候,可以看到gdb中停在了中断的位置。
在试着随便输入几个字符。(没输入一个字符需要在gdb中输入continue。) 当gdb停在断点处的时候,使用print输出一下结构体rb_usart中的值。(或者使用print rb_usart.count查看结构体中的任意值) 2 F# `0 i* ~& L5 z0 C* N7 ~
我们的rb_usart结构体中确实存放了数值。但为什么不能读取呢?可以在gdb中按下Ctrl+c结束程序运行,使用frame查看当前程序死在什么地方。+ Z" Q+ |" V. L9 Z: A
使用frame之后,发现程序死在rb_usart.count==0这句。可是我们使用gdb查看,rb_usart.count明明是2,为什么这一句还过不去呢?初步感觉是volatile的事情。由于我们编译命令开启了-Os,使用了优化,可能这个rb_usart.count被优化到寄存器中去了。而while判断的时候,并没有实际读取内存,而是直接从寄存器中读取。所以造成数据不同步的原因。 5 S. C9 P. [) r 如果想退出gdb的时候,先按下Ctrl+c,让程序停止运行。在输入q,并按y就能够退出了。) w( r0 i2 f6 o/ Q# w$ S : R: \/ B1 V! k7 C7 D" s ; S6 [( B" w4 T bug1+ z5 u" F3 y$ r& ?8 o l" w 如果只是volatile的原因的话,那么改正很简单。只要在相关变量中添加volatile就行了。 重新make all,make burn测试。' \' |2 k1 }# u1 ^$ O
问题已经成功解决。更正之后的代码在serial_v2.zip bug2 bug描述请参考下面回复的置顶贴。关于ringbuffer无锁的实现请参考:http://www.cnblogs.com/l00l/p/4115001.html 网址。 一开始接触到ringbuffer的时候,是阅读Arduino源代码中Hardwareserial的实现。他最开始并没有将ringbuffer单独抽象出来。代码比较难阅读。后来,Arduino sam的源代码将ringbuffer抽象成一个类。: f; Z) G! S7 ]; g2 x8 V 在阅读完相关ringbuffer的代码之后,感觉ringbuffer如果只使用两个变量readidx,putidx来记录指针的变化,判断空和满的时候就会变得复杂。像这样:* i8 X$ f+ c) @ #define rbfull(prb) ( ((prb)->putidx+1)%(prb)->len == (prb)->getidx ) #define rbempty(prb) ( (prb)->getidx == (prb)->putidx )% D0 h- i) ~. u; k4 u 我很大意的给ringbuffer添加了一个count变量,用来记录存储的数据。但是这样的一个变量也破坏了ringbuffer的无锁结构。 更新之后的两个实现代码:( {4 P2 f* i4 m# Q, [7 f' R int rbput(struct ringbuffer *rb,uint8_t add) {2 l; K1 i0 M: K if(rbfull(rb)) return 0;0 E( d0 G6 r: |$ O9 }1 N; m rb->bf[rb->putidx]=add;( k( `: V( v4 d/ J rb->putidx=(rb->putidx+1)%rb->len;6 G: i& y8 C2 e8 }# v return 1; }9 w5 i/ l' m- ?' Z int rbget(struct ringbuffer *rb) { int result; if(rbempty(rb)) return -1;: u9 t/ ^$ H: t result=rb->bf[rb->getidx]; rb->getidx=(rb->getidx+1)%rb->len;4 `; u" s4 e9 D& x return result;# W+ v; [* U# \- B; J! L u2 L }! a. b# k9 n2 c _ 由于去掉了count这个变量,在__io_getchar的时候,就不能使用count元素进行判断了。可以使用rbempty这个方法来判断。 int __io_getchar(void)( [4 e$ D" m9 T p O { signed char ch; while(rbempty(&rb_usart)) continue;- E' y! y+ d# E' e0 @. ` ch=rbget(&rb_usart); switch(ch)$ r @0 ~/ `& h3 r, G }: j { case '\r':ch='\n';break;2 Y/ k' F9 \2 Z' x% \+ a case 0x1b:ch=' ';break;) l( t/ }0 m/ p" p Q0 R0 j# g default: break; }! H5 E; {7 J+ C4 R/ o 7 S- G( A2 I, x. Y3 l /* echo it */ return __io_putchar(ch); } 更新之后的代码在附件serial_v3.zip中。, s5 z8 M# G9 d* X 更多. N W- b5 W8 @/ Q. _ 在下一节的时候,我们将来解决在SRAM中运行,使用中断的问题。 & \2 |# n, y) t% ]% O! \1 L 2 ?7 ~$ W# U, _1 }3 M9 H3 t # V' y4 x; J6 V# P7 Y0 @% T& b5 z# c) ` |
微信公众号
手机版
考虑:! \8 j, E+ @0 ? |
rb->count--执行到一半,' o2 ^$ g% ~8 N- M
此时进入中断rbput里rb->count++1 d. ]* ~/ n: E4 `& r
可能会导致rb->count计算不对8 ^6 S$ j8 k4 v* O
确实是这样的。之前阅读Arduino的源代码的时候,发现如果只使用head,tail两个变量,在判断空或者满的时候稍微有点麻烦。我索性就加上了一个count。这样实现比较简单。没想到把ringbuffer无锁编程的特性给意外的去掉了。9 G" |# Z6 p1 h
还需要改改。
刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?
全局量要加互斥锁的吧
用过之后就好了。。。
一开始接触到ringbuffer的时候,也是看到了Arduino的代码才知道的。
只不过加了一个DECLARE_RB而已
把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。
http://www.cnblogs.com/l00l/p/4115001.html