本帖最后由 QianFan 于 2015-10-29 15:30 编辑 7 E* q6 G$ h. H3 d 可能觉得使用串口很简单,无非就是初始化GPIO,初始化串口。接着发送---检测是否成功。表面上看来是很简单的问题。然而,我要说的并不是这些。我要说的是volatile和中断向量表的问题。在其中配合一点gdb调试。 8 P3 d0 P4 Q# q/ @0 W; V 使用ringbuffer 这里的串口使用非缓冲发送,ringbuffer中断缓冲接收的方式。先来看看ringbuffer。ringbuffer是一个特殊的队列,FIFO。 struct ringbuffer { uint8_t *bf; int len;+ m: n; |. r8 z% x6 Y$ k) N: s int count; int putidx; /* read index and put index */4 P1 F/ ~& @9 f, i0 g int getidx; };7 M" `, t7 R5 q* n struct ringbuffer对一个数组中的数据进行管理。数组的字节个数是len。更加详细的细节请大家尽情Google。写了一个宏,用来声明一个ringbuffer用来管理内存,准确的说叫做定义更好一些。/ w! J# P1 D: E5 T- e( g #define DECLARE_RB(name,len) \6 B) {' P- ?3 S! U uint8_t name##_buff[len]; \ struct ringbuffer name = { \ o6 V9 e- x6 v& @) M$ n name##_buff,len,0,0,0 }# i# e! w7 E1 {9 q+ C 提供的接口函数: #define rbcount(prb) ((prb)->count) ' _2 g; H3 q4 @0 j #define rbfull(prb) ( (prb)->count==(rb)->len ) #define rbempty(prb) ( (prb)->count==0 ) / t; t# \" M( }9 j/ Y /* if ringbuffer is full,return 0.else return 1 */1 f' C- t, a+ a int rbput(struct ringbuffer *prb,uint8_t add);& ]( M' X/ x. E5 b4 H /* if ringbuffer is empty,return -1.else return bf[tail] */ int rbget(struct ringbuffer *prb); rbput是往ringbuffer中添加一个数据。rbget是从ringbuffer中读取一个数据。相关的源代码: #include "ringbuffer.h" int rbput(struct ringbuffer *rb,uint8_t add)9 f% {: B/ ^" |; y# M6 `- \! x { int curidx=rb->putidx;0 f; J1 h- B; s8 X if(rbfull(rb)) return 0; rb->bf[curidx]=add;) A# q( O; w M" T/ ]+ o0 ` rb->putidx=(curidx+1)%rb->len;) S( X+ J0 s7 m. F6 C' F8 h6 n rb->count++; return 1; } W' N' x- Z5 d4 M6 h# g 7 P3 |3 m. O% Y$ W( c# [/ w% n* L int rbget(struct ringbuffer *rb)( c, q3 [% Q* o" e { int result; int curidx=rb->getidx;* C4 l/ t+ i% _# _# D/ E if(rbempty(rb)) return -1; result=rb->bf[curidx];) o/ M" N" k1 ?& |! D4 G/ A rb->getidx=(curidx+1)%rb->len;6 u# X( T1 n8 {9 t) d rb->count--;+ H7 S! g& A1 x1 z' `' S( e return result; }: E! G+ X4 k( _2 J3 L 6 f6 q) L/ w, j+ X7 @; ^: Z4 p4 t3 z 串口中断+ringbuffer+ I5 h! B( c: b: _% P2 c 在串口的初始化的时候,打开串口中断以及RXNE中断。在中断代码中将接受到的字符rbput到ringbuffer中。 /* USART interrupt handler */ USART_Handler()& S! `6 G: R ]& ? {- ~0 `. |) |: @* e/ G5 y if(USART_GetITStatus(USART,USART_IT_RXNE)==SET){ rbput(&rb_usart,USART_ReceiveData(USART)); g/ Y2 K4 s, V5 }0 m, s } }3 u& l* [8 l6 c, c& R! d( @! F 编写__io_getchar() __io_putchar()函数。用于发送和读取一个数据。 DECLARE_RB(rb_usart,USART_RD_BUF_LEN);% l4 ~, n9 Z* x8 j4 A & Y5 m6 a6 ?5 N2 j& i$ C4 k int __io_putchar(int ch)* q2 b* p5 ~. k, d: U5 p {+ C8 {' H' O& d0 j while(USART_GetFlagStatus(USART,USART_FLAG_TXE)!=SET); USART_SendData(USART,ch); return ch;4 U1 E- q3 v; i6 I$ b } int __io_getchar(void)9 c* i0 R2 E7 \8 o0 x0 \. h {. ]7 z: E) S: x* x+ V signed char ch;& u- e' g6 I& n, B5 x9 | while(rb_usart.count==0) continue;% g1 L3 ?! P9 W( ]6 ~, o ch=rbget(&rb_usart);4 p/ d" s. [) F2 K. }9 ^ switch(ch) p9 B$ m, A) _ { case '\r':ch='\n';break;3 \( {1 F) t. y( p p( c case 0x1b:ch=' ';break;, m* t7 {/ H7 B; z# t default: break;' }2 ^& E9 G/ ~, R6 ]5 ^7 C- ^' G } % Y5 G* R4 R0 h /* echo it */$ U. K, Y+ H9 d3 i \ r: Q return __io_putchar(ch);' a: @5 I$ D1 @* ]9 T }4 x, N3 X5 j4 `, N 当在使用minicom发送特殊的按键,比如Up,Down,Left,Right按键的时候,会发送以0x18开始的三个或者若干字符。对于这些,我们只显示0x18之后的字符。 由于使用minicom,putty一类的串口,发送的数据并不能回显。因此,要想有回显,只能在读取的时候进行回显。在中断中回显并不是一个好的方法,会占用一定的中断时间。因此我将回显放在__io_getchar函数中。 在主函数中一直读取字符; T6 T! ]2 z% r# L 我们的主函数中什么也不做,一直在读取字符。由于读取的时候会有回显,所以不论我们按下什么按键,都会实时回显。. O: a& E0 H' ?3 t4 |5 J% y' }6 N int main(void) {% D, X1 N0 e, t# u+ T1 i/ w const char *str="Try to enter something...\n";; u0 } Y$ C+ o- r # d3 K! s0 O) o. I4 s3 t* ] const char *tmp=str; for(;*tmp!='\0';tmp++) __io_putchar(*tmp); # B! G# n q4 v while(1) __io_getchar(); } ( \# O# ?: G/ [( ~% j! N y. v void _exit(int status) {/ \" m. \3 K! F( Z1 Y" ^ const char *exit_str="GoodBye!\n"; const char *tmp=exit_str; for(;*tmp!='\0';tmp++) __io_putchar(*tmp);( M) n$ r8 x0 Y# d2 @& A + a5 h/ C. Z2 M: F1 ?. A for(;;) continue; }0 k% N2 B1 k5 V0 S ) h( e$ L5 ]* O- s( Y6 J, O 下载到FLASH中运行2 N, |. w" ~( u! {3 A 目前所有的代码都在serial_v1.zip中,大家可以自行下载测试。: x' k, I5 l$ K 试着使用make all,make burn将代码下载至FLASH中。发现Try to enter something...这几个字符确实能够输出,但是不管我们按下什么按键都不能回显。暂时先不要使用make sram, make burns . 不管什么原因,至少能够显示也是好的。$ ]% Z6 o2 Z6 \8 e+ `* w- r 使用gdb调试代码 C+ V6 X9 @5 ^ 新建一个控制台窗口,输入sudo st-util。连接st-link与stm32。st-util会监听4242端口。使用这个端口可以与arm-none-eabi-gdb进行通信。 在当前目录下启动控制台窗口,输入arm-none-eabi-gdb blink.elf。由于使用了上一个例子的Makefile,所以文件名字blink.elf并没有更改。4 V. n/ D) K2 ^" e' }; z2 [% a 在gdb串口使用tar连接远程终端。如下图所示:' C- C% Q, \; w, ]9 Y tar连接完毕之后,使用load命令将代码下载至单片机内。. _6 d9 ~3 S1 P9 W) ^ 至于没有回显字符,我首先想到的原因是串口RXNE中断没有进去。我可以在串口中断里面设置一个断点。当进入串口中断的时候自动停止。设置断点需要使用break命令+行号。为了查看usart.c的内容,可以使用list命令。list命令(可以简写成l,小写的L,不是数字1)有几种形式。0 {9 F m. x# `6 o5 S
在第33行,也就是进入RXNE中断处设置一个断点。如果想查看所有已经设置的断点,可以使用info break查看。想删除断点,先使用info break查看对应断点的序号,在使用delete删除即可。: |# J; s% b* G5 d# { 在设置完断点之后,可以输入continue(或者c)。继续运行。直到遇到断点停止。 在continue之后,可以看见确实通过串口把数据输出了。这时候,可以试着在minicom中输入一个字符。我随便点了一个c。这时候,可以看到gdb中停在了中断的位置。8 H: P5 {' d0 {( P- K, D$ b 在试着随便输入几个字符。(没输入一个字符需要在gdb中输入continue。)5 f: q H2 \ E' u% k 当gdb停在断点处的时候,使用print输出一下结构体rb_usart中的值。(或者使用print rb_usart.count查看结构体中的任意值) - h' Y- v, g: O0 }8 Q# [" H8 t 我们的rb_usart结构体中确实存放了数值。但为什么不能读取呢?可以在gdb中按下Ctrl+c结束程序运行,使用frame查看当前程序死在什么地方。) f* y3 ]0 K* v9 N2 D 使用frame之后,发现程序死在rb_usart.count==0这句。可是我们使用gdb查看,rb_usart.count明明是2,为什么这一句还过不去呢?初步感觉是volatile的事情。由于我们编译命令开启了-Os,使用了优化,可能这个rb_usart.count被优化到寄存器中去了。而while判断的时候,并没有实际读取内存,而是直接从寄存器中读取。所以造成数据不同步的原因。 如果想退出gdb的时候,先按下Ctrl+c,让程序停止运行。在输入q,并按y就能够退出了。- L$ J/ g' h/ V/ h bug19 q+ ?$ m5 @! T 如果只是volatile的原因的话,那么改正很简单。只要在相关变量中添加volatile就行了。 重新make all,make burn测试。 问题已经成功解决。更正之后的代码在serial_v2.zip bug2: }/ }( n, M+ }9 k5 X e" X bug描述请参考下面回复的置顶贴。关于ringbuffer无锁的实现请参考:http://www.cnblogs.com/l00l/p/4115001.html 网址。 一开始接触到ringbuffer的时候,是阅读Arduino源代码中Hardwareserial的实现。他最开始并没有将ringbuffer单独抽象出来。代码比较难阅读。后来,Arduino sam的源代码将ringbuffer抽象成一个类。 在阅读完相关ringbuffer的代码之后,感觉ringbuffer如果只使用两个变量readidx,putidx来记录指针的变化,判断空和满的时候就会变得复杂。像这样: #define rbfull(prb) ( ((prb)->putidx+1)%(prb)->len == (prb)->getidx ) #define rbempty(prb) ( (prb)->getidx == (prb)->putidx )( ?7 y9 H# s B: N5 R! \1 w 我很大意的给ringbuffer添加了一个count变量,用来记录存储的数据。但是这样的一个变量也破坏了ringbuffer的无锁结构。 更新之后的两个实现代码: int rbput(struct ringbuffer *rb,uint8_t add)- \" y- X' l6 z# B7 B4 u { t4 ], s' D1 N2 A& v. \ if(rbfull(rb)) return 0;) g+ K/ c3 a) u- b/ f& V. T* W rb->bf[rb->putidx]=add; rb->putidx=(rb->putidx+1)%rb->len;! g9 a" f9 {% `0 R: ]1 M% R) q return 1; } 9 O ]" |( U( C8 j; K int rbget(struct ringbuffer *rb) { int result; if(rbempty(rb)) return -1; result=rb->bf[rb->getidx]; rb->getidx=(rb->getidx+1)%rb->len; return result; }) S% F9 w* _+ S5 _1 }2 u& l 由于去掉了count这个变量,在__io_getchar的时候,就不能使用count元素进行判断了。可以使用rbempty这个方法来判断。 int __io_getchar(void)# i8 j5 Y) j( H4 m( e& _ {( E8 T: }& d- c. q; t signed char ch; while(rbempty(&rb_usart)) continue; ch=rbget(&rb_usart);; @- k7 k/ R! e, W switch(ch) {6 h7 d5 M# q8 [3 J case '\r':ch='\n';break;: r( E9 h+ F3 X O case 0x1b:ch=' ';break;$ u$ R( K) M4 H9 d" s default: break; } 0 D" x7 e d" O6 T, T/ t /* echo it */4 |8 R3 b+ u/ e return __io_putchar(ch);6 k' W5 K) D9 w* L. O } 更新之后的代码在附件serial_v3.zip中。 & S- b% u1 ~$ |, \* @ 更多6 y) B1 A5 ~5 t! K0 h. R 在下一节的时候,我们将来解决在SRAM中运行,使用中断的问题。 G2 K1 _8 K+ {& [ |
考虑:1 g1 {- c$ ?- `$ S
rb->count--执行到一半,4 G& ~" L4 n, |8 {: Z- _) b6 s
此时进入中断rbput里rb->count++
可能会导致rb->count计算不对
确实是这样的。之前阅读Arduino的源代码的时候,发现如果只使用head,tail两个变量,在判断空或者满的时候稍微有点麻烦。我索性就加上了一个count。这样实现比较简单。没想到把ringbuffer无锁编程的特性给意外的去掉了。
还需要改改。
刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?
全局量要加互斥锁的吧
用过之后就好了。。。
一开始接触到ringbuffer的时候,也是看到了Arduino的代码才知道的。
只不过加了一个DECLARE_RB而已
把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。
http://www.cnblogs.com/l00l/p/4115001.html