|
本帖最后由 QianFan 于 2015-10-29 15:30 编辑 0 _$ g/ z j! E' e6 Y5 s$ i 可能觉得使用串口很简单,无非就是初始化GPIO,初始化串口。接着发送---检测是否成功。表面上看来是很简单的问题。然而,我要说的并不是这些。我要说的是volatile和中断向量表的问题。在其中配合一点gdb调试。5 n" b1 x! }# o+ s% X! i 0 o* ~2 W4 ~7 ^& x. P 使用ringbuffer 这里的串口使用非缓冲发送,ringbuffer中断缓冲接收的方式。先来看看ringbuffer。ringbuffer是一个特殊的队列,FIFO。 struct ringbuffer {. @4 N: ^( i, S0 e& w0 H uint8_t *bf; int len;( d6 a1 s+ L, E6 Y1 }% l ; a: `3 c) g/ Y, @2 a T int count;- P9 n+ w& X( y3 K int putidx; /* read index and put index */ int getidx; };, `2 U0 |" ^/ X) Y3 ? struct ringbuffer对一个数组中的数据进行管理。数组的字节个数是len。更加详细的细节请大家尽情Google。写了一个宏,用来声明一个ringbuffer用来管理内存,准确的说叫做定义更好一些。 #define DECLARE_RB(name,len) \ uint8_t name##_buff[len]; \ struct ringbuffer name = { \ name##_buff,len,0,0,0 } 提供的接口函数: #define rbcount(prb) ((prb)->count) J$ F7 |- p5 [' U! e# V( s5 S, F6 b #define rbfull(prb) ( (prb)->count==(rb)->len ) #define rbempty(prb) ( (prb)->count==0 )" p7 _& D+ S6 B* K# t: C* K* W /* if ringbuffer is full,return 0.else return 1 *// @1 \: ?# t2 R. C. j- ?" K B# B int rbput(struct ringbuffer *prb,uint8_t add);/ t; }' x4 Z! g3 n% x H) f% p B /* if ringbuffer is empty,return -1.else return bf[tail] */! b& I/ E* x7 A9 O int rbget(struct ringbuffer *prb); 8 |8 Y2 v- ?) q# }( H rbput是往ringbuffer中添加一个数据。rbget是从ringbuffer中读取一个数据。相关的源代码: #include "ringbuffer.h" int rbput(struct ringbuffer *rb,uint8_t add)1 ?/ p5 B% D3 d$ L; s { int curidx=rb->putidx;6 b# O9 F& a! u if(rbfull(rb)) return 0; rb->bf[curidx]=add;& ^4 \% R' x' D: Y% z9 u rb->putidx=(curidx+1)%rb->len;! t7 j% H8 I3 u& t rb->count++; return 1;5 \9 U- l" l2 ?$ {7 w6 R/ y } int rbget(struct ringbuffer *rb) {3 A! r9 T c) W6 p" Z$ ^: m0 M int result; int curidx=rb->getidx;7 E* n, R/ Q, ?9 e% | if(rbempty(rb)) return -1;2 M# D { Q: Q result=rb->bf[curidx]; rb->getidx=(curidx+1)%rb->len; rb->count--; return result; } 串口中断+ringbuffer6 R3 p2 i' z G1 G) h 在串口的初始化的时候,打开串口中断以及RXNE中断。在中断代码中将接受到的字符rbput到ringbuffer中。 /* USART interrupt handler *// L: r" }6 B, A" s3 _4 A USART_Handler()7 S6 ?) Y7 C5 d q3 g1 \0 J {/ z3 `0 F% W6 {+ p* G6 T& x if(USART_GetITStatus(USART,USART_IT_RXNE)==SET){ rbput(&rb_usart,USART_ReceiveData(USART)); } } 编写__io_getchar() __io_putchar()函数。用于发送和读取一个数据。 DECLARE_RB(rb_usart,USART_RD_BUF_LEN); 5 N5 R( ?( E I8 Q: s: E2 v3 b int __io_putchar(int ch)$ D# L2 M# I8 v5 }" c2 c7 n& { {8 |: c0 y8 x, u1 m/ i while(USART_GetFlagStatus(USART,USART_FLAG_TXE)!=SET); USART_SendData(USART,ch); return ch; }% T0 S' H% D9 f $ i$ J$ r, X9 O/ A& e int __io_getchar(void)0 B( }2 q8 V1 W- q { signed char ch; while(rb_usart.count==0) continue;2 V; d: J% S5 T4 H7 v! k/ @ ch=rbget(&rb_usart);: F# R( r) A1 Q: ~" Y2 z switch(ch) { case '\r':ch='\n';break;0 h+ h4 m7 r0 \ case 0x1b:ch=' ';break;! J! _4 ?: {8 e9 j$ d- a default: break; } /* echo it *// A- J, w( |0 Q. F return __io_putchar(ch); }1 z9 T6 E0 N6 H4 s 当在使用minicom发送特殊的按键,比如Up,Down,Left,Right按键的时候,会发送以0x18开始的三个或者若干字符。对于这些,我们只显示0x18之后的字符。 由于使用minicom,putty一类的串口,发送的数据并不能回显。因此,要想有回显,只能在读取的时候进行回显。在中断中回显并不是一个好的方法,会占用一定的中断时间。因此我将回显放在__io_getchar函数中。5 u! c+ j5 q4 @* h, W6 p; H 在主函数中一直读取字符 我们的主函数中什么也不做,一直在读取字符。由于读取的时候会有回显,所以不论我们按下什么按键,都会实时回显。 int main(void)1 Y" }4 J) z& ~, N2 _0 F+ n, b# F {8 b0 s8 |, _3 q% }' Y5 [ const char *str="Try to enter something...\n"; 2 F: y: i$ W3 g( k const char *tmp=str; for(;*tmp!='\0';tmp++)* ~3 Y1 P9 E. x1 ? U __io_putchar(*tmp); # ?5 E9 ?- \. G3 G u. C while(1) __io_getchar(); } M; v& ^) P S, f void _exit(int status)# F4 Y; x1 s$ ?' I3 E- `% l { const char *exit_str="GoodBye!\n"; const char *tmp=exit_str;: l0 h0 U+ l4 w/ W0 s a" S. ^ for(;*tmp!='\0';tmp++)3 B) O7 f, `" ~6 M4 }- m __io_putchar(*tmp);9 D* E" W0 f* {0 w/ ?' I- X' Y ; Z9 D2 z' v5 m2 s5 I for(;;) continue; }0 {* n' f9 D& W8 Z2 E! a ' i$ w: {. S8 Z& X+ i 下载到FLASH中运行5 g" K( k9 o$ C' u. e 目前所有的代码都在serial_v1.zip中,大家可以自行下载测试。 I7 N. i4 B7 Q1 K+ C2 T2 o 试着使用make all,make burn将代码下载至FLASH中。发现Try to enter something...这几个字符确实能够输出,但是不管我们按下什么按键都不能回显。暂时先不要使用make sram, make burns . 不管什么原因,至少能够显示也是好的。 + H; _5 R0 O5 Q6 q/ W" U' ~ 使用gdb调试代码5 E6 k& M' M. m, b: j# y 新建一个控制台窗口,输入sudo st-util。连接st-link与stm32。st-util会监听4242端口。使用这个端口可以与arm-none-eabi-gdb进行通信。
在当前目录下启动控制台窗口,输入arm-none-eabi-gdb blink.elf。由于使用了上一个例子的Makefile,所以文件名字blink.elf并没有更改。 在gdb串口使用tar连接远程终端。如下图所示:: y! o r) K+ ?4 h! t
tar连接完毕之后,使用load命令将代码下载至单片机内。 / b# g# [1 H( b$ _6 _- O% Z 至于没有回显字符,我首先想到的原因是串口RXNE中断没有进去。我可以在串口中断里面设置一个断点。当进入串口中断的时候自动停止。设置断点需要使用break命令+行号。为了查看usart.c的内容,可以使用list命令。list命令(可以简写成l,小写的L,不是数字1)有几种形式。
在第33行,也就是进入RXNE中断处设置一个断点。如果想查看所有已经设置的断点,可以使用info break查看。想删除断点,先使用info break查看对应断点的序号,在使用delete删除即可。2 Y# d8 H' Z5 I" `( ^
在设置完断点之后,可以输入continue(或者c)。继续运行。直到遇到断点停止。, P2 E+ i3 q" D" C1 j1 b
在continue之后,可以看见确实通过串口把数据输出了。这时候,可以试着在minicom中输入一个字符。我随便点了一个c。这时候,可以看到gdb中停在了中断的位置。
在试着随便输入几个字符。(没输入一个字符需要在gdb中输入continue。)6 q# k9 |. W- v 当gdb停在断点处的时候,使用print输出一下结构体rb_usart中的值。(或者使用print rb_usart.count查看结构体中的任意值) 9 U. ^2 [& s' h) r$ O S1 U( K. ~
我们的rb_usart结构体中确实存放了数值。但为什么不能读取呢?可以在gdb中按下Ctrl+c结束程序运行,使用frame查看当前程序死在什么地方。& _2 S' t }3 ]: R q+ e V, ?
使用frame之后,发现程序死在rb_usart.count==0这句。可是我们使用gdb查看,rb_usart.count明明是2,为什么这一句还过不去呢?初步感觉是volatile的事情。由于我们编译命令开启了-Os,使用了优化,可能这个rb_usart.count被优化到寄存器中去了。而while判断的时候,并没有实际读取内存,而是直接从寄存器中读取。所以造成数据不同步的原因。. ^% S& p3 N1 q1 s7 Z' a& U5 e5 D . D4 `( B% S: M. y* J* I/ f, U1 X 如果想退出gdb的时候,先按下Ctrl+c,让程序停止运行。在输入q,并按y就能够退出了。, V0 `; d5 C) R2 S4 {& d+ h ) ^+ g/ {6 s0 |- Y8 m, Q bug1 如果只是volatile的原因的话,那么改正很简单。只要在相关变量中添加volatile就行了。8 n& ]+ w* o. `# [. { 重新make all,make burn测试。
问题已经成功解决。更正之后的代码在serial_v2.zip + U# X2 }& }/ } N- D bug2 bug描述请参考下面回复的置顶贴。关于ringbuffer无锁的实现请参考:http://www.cnblogs.com/l00l/p/4115001.html 网址。" B$ w! N6 c! p) y( ~9 A8 @ 一开始接触到ringbuffer的时候,是阅读Arduino源代码中Hardwareserial的实现。他最开始并没有将ringbuffer单独抽象出来。代码比较难阅读。后来,Arduino sam的源代码将ringbuffer抽象成一个类。 在阅读完相关ringbuffer的代码之后,感觉ringbuffer如果只使用两个变量readidx,putidx来记录指针的变化,判断空和满的时候就会变得复杂。像这样:; }" T& T1 M$ l #define rbfull(prb) ( ((prb)->putidx+1)%(prb)->len == (prb)->getidx ). v0 K/ ~4 J5 C* x2 H #define rbempty(prb) ( (prb)->getidx == (prb)->putidx ) 我很大意的给ringbuffer添加了一个count变量,用来记录存储的数据。但是这样的一个变量也破坏了ringbuffer的无锁结构。 更新之后的两个实现代码:& I) ?; C: l$ A. s @8 R; S" i int rbput(struct ringbuffer *rb,uint8_t add) { if(rbfull(rb)) return 0; rb->bf[rb->putidx]=add;& m. `; r5 v) K0 N0 X$ r* y1 ` rb->putidx=(rb->putidx+1)%rb->len; return 1;) E: M5 B0 T( E W }! d# D4 ?- `+ I- K4 l5 H int rbget(struct ringbuffer *rb) { j9 `. [" A# @& @: j2 I int result; if(rbempty(rb)) return -1; result=rb->bf[rb->getidx]; rb->getidx=(rb->getidx+1)%rb->len;6 P C& t( l3 r) T6 m9 H return result;1 z8 q* A4 _9 a/ ]! e2 X, Z } 由于去掉了count这个变量,在__io_getchar的时候,就不能使用count元素进行判断了。可以使用rbempty这个方法来判断。 {( E: q% P3 F6 u* c0 G4 f | int __io_getchar(void) {- k+ p' t0 z& N- Z) y) U signed char ch;. r" k- L! K1 T( O6 L7 q while(rbempty(&rb_usart)) continue;, `1 I/ h8 y" h" [ ch=rbget(&rb_usart); switch(ch) { case '\r':ch='\n';break;1 B9 c, C N& O' Y+ a# X% N' w# H case 0x1b:ch=' ';break; default: break; }' w2 f- {7 A* [2 H /* echo it */( y) W; s- h# k" \" j return __io_putchar(ch); }7 }2 j: H+ j& d3 {* _% t 更新之后的代码在附件serial_v3.zip中。; I, }( ^ t: O' N, P 8 r2 v( u$ `8 ^0 C# a0 _ 更多& l% H% o; i+ Z: f 在下一节的时候,我们将来解决在SRAM中运行,使用中断的问题。+ m5 @1 F! B' L5 j7 O & x5 H4 U! x0 |) r |
微信公众号
手机版
考虑:" |) ~5 t3 @7 N* k8 \
rb->count--执行到一半,
此时进入中断rbput里rb->count++
可能会导致rb->count计算不对. S0 X) F/ c% h u9 }4 F$ I
确实是这样的。之前阅读Arduino的源代码的时候,发现如果只使用head,tail两个变量,在判断空或者满的时候稍微有点麻烦。我索性就加上了一个count。这样实现比较简单。没想到把ringbuffer无锁编程的特性给意外的去掉了。
还需要改改。
刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?
全局量要加互斥锁的吧
用过之后就好了。。。
一开始接触到ringbuffer的时候,也是看到了Arduino的代码才知道的。
只不过加了一个DECLARE_RB而已
把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。
http://www.cnblogs.com/l00l/p/4115001.html