本帖最后由 QianFan 于 2015-10-29 15:30 编辑 可能觉得使用串口很简单,无非就是初始化GPIO,初始化串口。接着发送---检测是否成功。表面上看来是很简单的问题。然而,我要说的并不是这些。我要说的是volatile和中断向量表的问题。在其中配合一点gdb调试。( P( V3 l; |$ R. C2 p! E+ _ 使用ringbuffer" T- p; o) [* v1 q 这里的串口使用非缓冲发送,ringbuffer中断缓冲接收的方式。先来看看ringbuffer。ringbuffer是一个特殊的队列,FIFO。4 Q0 H0 ?/ a1 r% U! n struct ringbuffer { uint8_t *bf;# c; `+ Y' F T: U& a* o+ U$ N int len; 7 ^+ f/ n$ `3 U* N; U7 a9 {& ~! y int count;+ n) a4 M3 z8 m' D/ c int putidx; /* read index and put index */0 G1 `, t1 N! |* P, \2 ` int getidx;! K ~- I2 B# g( n }; struct ringbuffer对一个数组中的数据进行管理。数组的字节个数是len。更加详细的细节请大家尽情Google。写了一个宏,用来声明一个ringbuffer用来管理内存,准确的说叫做定义更好一些。 #define DECLARE_RB(name,len) \7 w8 ]/ w) X% y1 z* k: \' T uint8_t name##_buff[len]; \ struct ringbuffer name = { \( S$ z2 f3 S& e name##_buff,len,0,0,0 } 提供的接口函数:. D( M( D: |/ l$ Y( X4 p2 t7 ^3 y #define rbcount(prb) ((prb)->count) 2 `+ {& ?. m( p #define rbfull(prb) ( (prb)->count==(rb)->len )' X" O$ a# @# ^, n2 J #define rbempty(prb) ( (prb)->count==0 ) /* if ringbuffer is full,return 0.else return 1 */, L q; A, H( s+ H0 | int rbput(struct ringbuffer *prb,uint8_t add); /* if ringbuffer is empty,return -1.else return bf[tail] */ int rbget(struct ringbuffer *prb); rbput是往ringbuffer中添加一个数据。rbget是从ringbuffer中读取一个数据。相关的源代码: #include "ringbuffer.h"9 a9 n6 P3 h5 q3 ], S + U& F) A9 M. |/ r int rbput(struct ringbuffer *rb,uint8_t add) { int curidx=rb->putidx; if(rbfull(rb)) return 0; rb->bf[curidx]=add;/ s2 E$ s1 c/ M/ g% C+ U5 {- S5 v rb->putidx=(curidx+1)%rb->len;* M$ m0 ?/ s$ O, {+ Y rb->count++;$ {' m& N& G5 \: U* c" k+ l% E/ J6 @ return 1; } int rbget(struct ringbuffer *rb)6 Y5 I- e) F1 ]3 D- o0 c* Q {! h3 A; q+ V' S. r% u* w* P int result;9 P+ O, J/ n* \# @1 g int curidx=rb->getidx; if(rbempty(rb)) return -1;# v3 A, y: _% e; f8 G- z result=rb->bf[curidx];5 k. P' l) } d c. \ rb->getidx=(curidx+1)%rb->len; rb->count--; return result; } 5 F0 }6 m4 F g p/ j7 [4 m Z 串口中断+ringbuffer 在串口的初始化的时候,打开串口中断以及RXNE中断。在中断代码中将接受到的字符rbput到ringbuffer中。2 D+ ?+ @3 ?. A, r# Q, A+ m /* USART interrupt handler */. ?, z- Z. t$ m: k' f/ @4 O USART_Handler(): `9 f: p9 s# o3 m) u {5 @9 W2 S( K$ [ if(USART_GetITStatus(USART,USART_IT_RXNE)==SET){) W; o# [" \( s) W6 p" u8 f rbput(&rb_usart,USART_ReceiveData(USART)); } } 编写__io_getchar() __io_putchar()函数。用于发送和读取一个数据。 DECLARE_RB(rb_usart,USART_RD_BUF_LEN); int __io_putchar(int ch)8 y/ b9 y* f: G { while(USART_GetFlagStatus(USART,USART_FLAG_TXE)!=SET); USART_SendData(USART,ch);! f% U: n6 C' m" }: { return ch; } int __io_getchar(void) {, |( h9 x3 {- g9 p( m i signed char ch;' L$ T, b6 D" u" W5 @7 P. z while(rb_usart.count==0) continue; ch=rbget(&rb_usart);7 I- {+ y3 S! g, c: s) ~& e switch(ch) {" V+ V l: y4 r# n; A case '\r':ch='\n';break; case 0x1b:ch=' ';break; default: break; } /* echo it */% u- t( h, u' J8 A return __io_putchar(ch); }$ g# e9 t q* F% E- V1 [9 K' h 当在使用minicom发送特殊的按键,比如Up,Down,Left,Right按键的时候,会发送以0x18开始的三个或者若干字符。对于这些,我们只显示0x18之后的字符。5 J( V$ d% ^" Y" H 由于使用minicom,putty一类的串口,发送的数据并不能回显。因此,要想有回显,只能在读取的时候进行回显。在中断中回显并不是一个好的方法,会占用一定的中断时间。因此我将回显放在__io_getchar函数中。 K* e% s5 {" a3 ` 在主函数中一直读取字符 我们的主函数中什么也不做,一直在读取字符。由于读取的时候会有回显,所以不论我们按下什么按键,都会实时回显。$ v+ L( A* z* E$ o5 J int main(void)4 A5 b; M' y: g { const char *str="Try to enter something...\n"; const char *tmp=str;. S) }7 f" v4 }8 M/ F& _ for(;*tmp!='\0';tmp++)* Q; T; T3 i9 \1 I: p% i( d __io_putchar(*tmp); 7 w! L& c: ~' B while(1) __io_getchar();. i( c/ I' v% P' Q } void _exit(int status)$ p7 m2 y+ l# c7 o3 P- G) j6 a {* u6 x' _. r" q: L- C% Y/ O( k9 d! m const char *exit_str="GoodBye!\n";7 }; W1 F9 Z( M: e) b5 N2 x const char *tmp=exit_str; for(;*tmp!='\0';tmp++) __io_putchar(*tmp); for(;;) continue; }: u+ }- B# F3 S. m 下载到FLASH中运行 目前所有的代码都在serial_v1.zip中,大家可以自行下载测试。 试着使用make all,make burn将代码下载至FLASH中。发现Try to enter something...这几个字符确实能够输出,但是不管我们按下什么按键都不能回显。暂时先不要使用make sram, make burns . 不管什么原因,至少能够显示也是好的。, h9 b7 I( r# K% n+ K$ O! k* f 使用gdb调试代码 新建一个控制台窗口,输入sudo st-util。连接st-link与stm32。st-util会监听4242端口。使用这个端口可以与arm-none-eabi-gdb进行通信。' Y3 w# ?% I8 E% C' Z$ w 在当前目录下启动控制台窗口,输入arm-none-eabi-gdb blink.elf。由于使用了上一个例子的Makefile,所以文件名字blink.elf并没有更改。 在gdb串口使用tar连接远程终端。如下图所示: tar连接完毕之后,使用load命令将代码下载至单片机内。3 n7 D! ?7 ?$ ~( d 至于没有回显字符,我首先想到的原因是串口RXNE中断没有进去。我可以在串口中断里面设置一个断点。当进入串口中断的时候自动停止。设置断点需要使用break命令+行号。为了查看usart.c的内容,可以使用list命令。list命令(可以简写成l,小写的L,不是数字1)有几种形式。
在第33行,也就是进入RXNE中断处设置一个断点。如果想查看所有已经设置的断点,可以使用info break查看。想删除断点,先使用info break查看对应断点的序号,在使用delete删除即可。% z+ w% Z: D( e9 q4 N$ a 0 Y8 ? @. V) F: f; E, C7 l& t 在设置完断点之后,可以输入continue(或者c)。继续运行。直到遇到断点停止。 A/ m& F2 j+ c5 j 在continue之后,可以看见确实通过串口把数据输出了。这时候,可以试着在minicom中输入一个字符。我随便点了一个c。这时候,可以看到gdb中停在了中断的位置。 在试着随便输入几个字符。(没输入一个字符需要在gdb中输入continue。)( g& g1 q: K3 t# l N 当gdb停在断点处的时候,使用print输出一下结构体rb_usart中的值。(或者使用print rb_usart.count查看结构体中的任意值) 5 ` t* W0 u- L! @( S3 [ 我们的rb_usart结构体中确实存放了数值。但为什么不能读取呢?可以在gdb中按下Ctrl+c结束程序运行,使用frame查看当前程序死在什么地方。9 j0 v, f; w: ~3 v) ] 使用frame之后,发现程序死在rb_usart.count==0这句。可是我们使用gdb查看,rb_usart.count明明是2,为什么这一句还过不去呢?初步感觉是volatile的事情。由于我们编译命令开启了-Os,使用了优化,可能这个rb_usart.count被优化到寄存器中去了。而while判断的时候,并没有实际读取内存,而是直接从寄存器中读取。所以造成数据不同步的原因。 9 b z$ Q9 _2 u( W; U# [ 如果想退出gdb的时候,先按下Ctrl+c,让程序停止运行。在输入q,并按y就能够退出了。 9 v0 J. l k8 `$ l V; v7 i bug1& y8 J1 I+ B' }) d* D; H7 n3 y 如果只是volatile的原因的话,那么改正很简单。只要在相关变量中添加volatile就行了。. [; z/ F# k" V' ?2 N# K 重新make all,make burn测试。; ^! N. f6 B7 P3 ^ q2 y b3 S 问题已经成功解决。更正之后的代码在serial_v2.zip! t: Z( L0 e @2 ]2 Q- [ bug2" B% F, ^$ }! {9 Q! f' f bug描述请参考下面回复的置顶贴。关于ringbuffer无锁的实现请参考:http://www.cnblogs.com/l00l/p/4115001.html 网址。" y3 A5 p( c- A; F& ] 一开始接触到ringbuffer的时候,是阅读Arduino源代码中Hardwareserial的实现。他最开始并没有将ringbuffer单独抽象出来。代码比较难阅读。后来,Arduino sam的源代码将ringbuffer抽象成一个类。/ b5 e3 Z) P" Z0 Q 在阅读完相关ringbuffer的代码之后,感觉ringbuffer如果只使用两个变量readidx,putidx来记录指针的变化,判断空和满的时候就会变得复杂。像这样: #define rbfull(prb) ( ((prb)->putidx+1)%(prb)->len == (prb)->getidx ) #define rbempty(prb) ( (prb)->getidx == (prb)->putidx ) 我很大意的给ringbuffer添加了一个count变量,用来记录存储的数据。但是这样的一个变量也破坏了ringbuffer的无锁结构。( Y* v* ]) m3 N2 k! j# L- d6 _ 更新之后的两个实现代码:9 ~' {$ f8 G1 @$ ^+ A int rbput(struct ringbuffer *rb,uint8_t add) { if(rbfull(rb)) return 0; rb->bf[rb->putidx]=add;. l1 a& N. H Q( i9 q rb->putidx=(rb->putidx+1)%rb->len; return 1;9 k' G; r' f' Q } 9 i0 E6 F1 K0 V int rbget(struct ringbuffer *rb) { int result;( V# _6 [* L6 n if(rbempty(rb)) return -1;9 B9 S7 \& F8 J. G0 N7 V& Y result=rb->bf[rb->getidx]; rb->getidx=(rb->getidx+1)%rb->len; return result; } 由于去掉了count这个变量,在__io_getchar的时候,就不能使用count元素进行判断了。可以使用rbempty这个方法来判断。 int __io_getchar(void) {8 F: b; i0 W- y signed char ch;8 W: O1 _7 S5 [# G1 E) X while(rbempty(&rb_usart)) continue;: e1 d: I3 H! D8 p9 N* v ch=rbget(&rb_usart);! _0 L" L, M! N6 G3 V+ G( G3 o \ switch(ch) {$ [* l$ H C# D e case '\r':ch='\n';break;/ k1 J8 j& k7 m+ l case 0x1b:ch=' ';break; default: break; }2 ] {' e; [- g; M! K7 A / E/ D# `3 Q6 R3 y/ V /* echo it */. L, P; B6 {3 b: r% E c return __io_putchar(ch); }- |4 a/ E0 N/ S- B 更新之后的代码在附件serial_v3.zip中。 更多 在下一节的时候,我们将来解决在SRAM中运行,使用中断的问题。7 J+ u( U4 ` a; P# o2 F ) }) u0 W& L0 _( l7 n |
考虑:
rb->count--执行到一半,# E3 r0 d4 l7 R( U6 b: v3 Y7 c- ^
此时进入中断rbput里rb->count++
可能会导致rb->count计算不对5 w7 V! _) N Y- v$ f) u
确实是这样的。之前阅读Arduino的源代码的时候,发现如果只使用head,tail两个变量,在判断空或者满的时候稍微有点麻烦。我索性就加上了一个count。这样实现比较简单。没想到把ringbuffer无锁编程的特性给意外的去掉了。9 Y7 v$ E+ s1 c+ w3 [/ Q8 Q
还需要改改。
刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?
全局量要加互斥锁的吧
用过之后就好了。。。
一开始接触到ringbuffer的时候,也是看到了Arduino的代码才知道的。
只不过加了一个DECLARE_RB而已
把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。
http://www.cnblogs.com/l00l/p/4115001.html