|
本帖最后由 QianFan 于 2015-10-29 15:30 编辑 可能觉得使用串口很简单,无非就是初始化GPIO,初始化串口。接着发送---检测是否成功。表面上看来是很简单的问题。然而,我要说的并不是这些。我要说的是volatile和中断向量表的问题。在其中配合一点gdb调试。 使用ringbuffer6 u- U0 w) a, A+ o1 C4 }8 A' ~ 这里的串口使用非缓冲发送,ringbuffer中断缓冲接收的方式。先来看看ringbuffer。ringbuffer是一个特殊的队列,FIFO。) `" r6 A2 F# g4 D0 f0 R- P( F struct ringbuffer { uint8_t *bf; int len; ' Y6 L4 y& e% ^9 r int count;; R- l1 S) H9 a5 C% p1 c int putidx; /* read index and put index */ int getidx; }; struct ringbuffer对一个数组中的数据进行管理。数组的字节个数是len。更加详细的细节请大家尽情Google。写了一个宏,用来声明一个ringbuffer用来管理内存,准确的说叫做定义更好一些。9 W: {/ q% [: O #define DECLARE_RB(name,len) \: Y Z/ l+ y% O ]( Q# i uint8_t name##_buff[len]; \ struct ringbuffer name = { \3 V5 o, J8 _2 ~# R6 { name##_buff,len,0,0,0 }% O# V+ n( B& N, W, J/ V/ J 提供的接口函数:, R7 k: g- B8 b! h) D& ? #define rbcount(prb) ((prb)->count) 3 |* s w: v( k7 y! p* O% b0 S S #define rbfull(prb) ( (prb)->count==(rb)->len )- p& I: U; F# |: T1 m #define rbempty(prb) ( (prb)->count==0 ) /* 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);% c0 B- Y: l" ]/ T rbput是往ringbuffer中添加一个数据。rbget是从ringbuffer中读取一个数据。相关的源代码: #include "ringbuffer.h" B# {. N1 s- O' G4 ^1 ? int rbput(struct ringbuffer *rb,uint8_t add) { int curidx=rb->putidx; if(rbfull(rb)) return 0;$ c3 l& ^+ X' F: Z& I rb->bf[curidx]=add;& o; C, |+ J4 A rb->putidx=(curidx+1)%rb->len; rb->count++; return 1;3 \" s7 H# @) u4 |3 b0 B) R }( x1 P5 o7 L; K. Z/ e% W0 e : U! O! X% [9 R4 A- S) F int rbget(struct ringbuffer *rb)" D7 b. J0 D4 h9 I! R- ] { int result;2 d/ M. K1 z& A$ o. b/ Q int curidx=rb->getidx; if(rbempty(rb)) return -1;! {* h. L1 u- b- r result=rb->bf[curidx]; rb->getidx=(curidx+1)%rb->len; rb->count--; return result; } 串口中断+ringbuffer) j2 i& ]% b5 A* v2 U! `: t8 S- W 在串口的初始化的时候,打开串口中断以及RXNE中断。在中断代码中将接受到的字符rbput到ringbuffer中。& J0 I6 i4 y% T( u /* USART interrupt handler */ USART_Handler() { if(USART_GetITStatus(USART,USART_IT_RXNE)==SET){* F, t/ t4 b; v. C O: Z rbput(&rb_usart,USART_ReceiveData(USART));& i% H: e |" Y' | } }. v& G3 v" l! e( M 编写__io_getchar() __io_putchar()函数。用于发送和读取一个数据。 DECLARE_RB(rb_usart,USART_RD_BUF_LEN); int __io_putchar(int ch) {% z8 C) P+ N, N: J, Y* v- B$ w while(USART_GetFlagStatus(USART,USART_FLAG_TXE)!=SET); USART_SendData(USART,ch);# w$ c d6 v% Y! @ return ch; }7 l. t4 x; ]0 h) c a % w: I6 L) ]0 {" d! K int __io_getchar(void) {" W9 M) Q1 [$ ]9 A* g9 D( o signed char ch;/ l- ?# k( O. V" }6 [ while(rb_usart.count==0) continue; ch=rbget(&rb_usart);: l) l4 v# f+ I9 Q# D6 ] switch(ch)" `1 G( v! Y1 c { case '\r':ch='\n';break; case 0x1b:ch=' ';break;3 C# ~% z0 l# f8 g% y. J9 ~ z default: break;5 D; c' o# G: Q: g( J( S: @ } /* echo it */6 I! O% t' V1 X- P- q return __io_putchar(ch);# F" i) r! g# R6 y) r0 y }9 D; u% j; |' S& ?9 b 当在使用minicom发送特殊的按键,比如Up,Down,Left,Right按键的时候,会发送以0x18开始的三个或者若干字符。对于这些,我们只显示0x18之后的字符。 由于使用minicom,putty一类的串口,发送的数据并不能回显。因此,要想有回显,只能在读取的时候进行回显。在中断中回显并不是一个好的方法,会占用一定的中断时间。因此我将回显放在__io_getchar函数中。6 h* ~& c1 I; B: [+ S: @! |0 D! B 1 j4 Y8 {6 |$ d+ K 在主函数中一直读取字符 我们的主函数中什么也不做,一直在读取字符。由于读取的时候会有回显,所以不论我们按下什么按键,都会实时回显。4 N- f1 u. o4 C. N int main(void): b% U* U! c9 ]+ c& v, ^: G$ G {# x% I9 o; v' ~- b7 f const char *str="Try to enter something...\n";/ j- L9 M( r! {% f( r const char *tmp=str;. M+ ^% @- F O; }9 a; n for(;*tmp!='\0';tmp++) __io_putchar(*tmp);9 y2 _2 ^ A2 a- B7 N) D/ x , e. f$ J) z; U) T2 [# S) h1 w while(1) __io_getchar();* S0 [( u" }" L( S5 l3 t }/ x5 Y/ Y1 Y' J5 q4 ?3 @5 ?' D 8 H( L- H. a2 \6 ~& c: J2 D H void _exit(int status) {" s+ x% H0 H# ~& ]) j const char *exit_str="GoodBye!\n";" }$ Q9 b3 f8 L7 {2 K const char *tmp=exit_str; for(;*tmp!='\0';tmp++) __io_putchar(*tmp);5 ^: c( G' T' }( _: N for(;;) continue; } * x+ s$ x6 @- D7 d" J% h7 } 下载到FLASH中运行/ V: O) h0 t/ N% M% H7 A6 U4 ?- w, K6 ?- U 目前所有的代码都在serial_v1.zip中,大家可以自行下载测试。 试着使用make all,make burn将代码下载至FLASH中。发现Try to enter something...这几个字符确实能够输出,但是不管我们按下什么按键都不能回显。暂时先不要使用make sram, make burns . 不管什么原因,至少能够显示也是好的。9 Q- [" K, U. X1 D7 ? ~( v3 d6 b 使用gdb调试代码, D6 B* Q( N4 A6 V) P5 t. y- |: u. N 新建一个控制台窗口,输入sudo st-util。连接st-link与stm32。st-util会监听4242端口。使用这个端口可以与arm-none-eabi-gdb进行通信。" E: e) H9 B- A H3 C' Y
在当前目录下启动控制台窗口,输入arm-none-eabi-gdb blink.elf。由于使用了上一个例子的Makefile,所以文件名字blink.elf并没有更改。 在gdb串口使用tar连接远程终端。如下图所示:6 a2 x; h5 w8 N$ B. u
tar连接完毕之后,使用load命令将代码下载至单片机内。, o/ h8 Q+ j: z 至于没有回显字符,我首先想到的原因是串口RXNE中断没有进去。我可以在串口中断里面设置一个断点。当进入串口中断的时候自动停止。设置断点需要使用break命令+行号。为了查看usart.c的内容,可以使用list命令。list命令(可以简写成l,小写的L,不是数字1)有几种形式。
在第33行,也就是进入RXNE中断处设置一个断点。如果想查看所有已经设置的断点,可以使用info break查看。想删除断点,先使用info break查看对应断点的序号,在使用delete删除即可。
在设置完断点之后,可以输入continue(或者c)。继续运行。直到遇到断点停止。
在continue之后,可以看见确实通过串口把数据输出了。这时候,可以试着在minicom中输入一个字符。我随便点了一个c。这时候,可以看到gdb中停在了中断的位置。
在试着随便输入几个字符。(没输入一个字符需要在gdb中输入continue。)1 P2 e7 A9 M0 F; p n 当gdb停在断点处的时候,使用print输出一下结构体rb_usart中的值。(或者使用print rb_usart.count查看结构体中的任意值)8 r0 M. {5 X6 p % u( X* v* v* d! Y' {
我们的rb_usart结构体中确实存放了数值。但为什么不能读取呢?可以在gdb中按下Ctrl+c结束程序运行,使用frame查看当前程序死在什么地方。; ?( y( j8 H9 E: ~
使用frame之后,发现程序死在rb_usart.count==0这句。可是我们使用gdb查看,rb_usart.count明明是2,为什么这一句还过不去呢?初步感觉是volatile的事情。由于我们编译命令开启了-Os,使用了优化,可能这个rb_usart.count被优化到寄存器中去了。而while判断的时候,并没有实际读取内存,而是直接从寄存器中读取。所以造成数据不同步的原因。 如果想退出gdb的时候,先按下Ctrl+c,让程序停止运行。在输入q,并按y就能够退出了。 X4 c% R& O, j) a A! a ( K" L7 f; t, O o$ E# g bug1) B. _4 |* x7 c$ e! ~ 如果只是volatile的原因的话,那么改正很简单。只要在相关变量中添加volatile就行了。8 h8 A3 R0 n8 y! \$ Q 重新make all,make burn测试。
问题已经成功解决。更正之后的代码在serial_v2.zip! o9 ~- i0 f9 ^/ A ( y& Z% e! D& s5 x5 N bug2 bug描述请参考下面回复的置顶贴。关于ringbuffer无锁的实现请参考:http://www.cnblogs.com/l00l/p/4115001.html 网址。* Z/ n$ |9 k* g0 b, M 一开始接触到ringbuffer的时候,是阅读Arduino源代码中Hardwareserial的实现。他最开始并没有将ringbuffer单独抽象出来。代码比较难阅读。后来,Arduino sam的源代码将ringbuffer抽象成一个类。 在阅读完相关ringbuffer的代码之后,感觉ringbuffer如果只使用两个变量readidx,putidx来记录指针的变化,判断空和满的时候就会变得复杂。像这样: #define rbfull(prb) ( ((prb)->putidx+1)%(prb)->len == (prb)->getidx )$ t, x" r3 W3 Z: a- e9 M #define rbempty(prb) ( (prb)->getidx == (prb)->putidx ) 我很大意的给ringbuffer添加了一个count变量,用来记录存储的数据。但是这样的一个变量也破坏了ringbuffer的无锁结构。 更新之后的两个实现代码: int rbput(struct ringbuffer *rb,uint8_t add) { if(rbfull(rb)) return 0; rb->bf[rb->putidx]=add;) [, s6 ~ W# p' w- r! ~- B) j rb->putidx=(rb->putidx+1)%rb->len; return 1; }( I' Q$ j) Y, S- Q C2 I int rbget(struct ringbuffer *rb)5 ~ E( E4 |4 b7 h L0 H { int result; if(rbempty(rb)) return -1;6 n# q' l9 S0 s H1 I result=rb->bf[rb->getidx]; rb->getidx=(rb->getidx+1)%rb->len;( X& _( O# K% v% U4 X N return result; } 由于去掉了count这个变量,在__io_getchar的时候,就不能使用count元素进行判断了。可以使用rbempty这个方法来判断。9 L3 a5 z! \' U4 { int __io_getchar(void) { signed char ch;( Y) ~ l6 b/ m( u while(rbempty(&rb_usart)) continue; ch=rbget(&rb_usart); switch(ch)* L m% A! ?" h {- W: M; [$ Z, V- b$ h case '\r':ch='\n';break; case 0x1b:ch=' ';break;4 i' H7 J2 m0 M, \$ M default: break;( M B C5 K* z3 n } - A& Z4 O `5 s9 y /* echo it */% z' c( _' [, T$ L8 d7 Z3 U return __io_putchar(ch); } 更新之后的代码在附件serial_v3.zip中。 更多 在下一节的时候,我们将来解决在SRAM中运行,使用中断的问题。 2 h: L4 S G$ P* h/ k ; I" k9 `! a2 X- P- S |
微信公众号
手机版
考虑:
rb->count--执行到一半,
此时进入中断rbput里rb->count++1 d& s$ O, v* @- ^7 `
可能会导致rb->count计算不对
确实是这样的。之前阅读Arduino的源代码的时候,发现如果只使用head,tail两个变量,在判断空或者满的时候稍微有点麻烦。我索性就加上了一个count。这样实现比较简单。没想到把ringbuffer无锁编程的特性给意外的去掉了。0 R" w/ j7 G5 A1 `
还需要改改。
刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?
全局量要加互斥锁的吧
用过之后就好了。。。
一开始接触到ringbuffer的时候,也是看到了Arduino的代码才知道的。
只不过加了一个DECLARE_RB而已
把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。
http://www.cnblogs.com/l00l/p/4115001.html