
本帖最后由 QianFan 于 2015-10-29 15:30 编辑 5 a# y3 P1 j' _7 W T( e / k/ ^5 V0 Z9 V 可能觉得使用串口很简单,无非就是初始化GPIO,初始化串口。接着发送---检测是否成功。表面上看来是很简单的问题。然而,我要说的并不是这些。我要说的是volatile和中断向量表的问题。在其中配合一点gdb调试。% E8 \" N3 W8 t 2 v1 W: i, q* ^# T4 s 使用ringbuffer& g1 O3 I5 u5 b5 Z0 {$ H: Z 这里的串口使用非缓冲发送,ringbuffer中断缓冲接收的方式。先来看看ringbuffer。ringbuffer是一个特殊的队列,FIFO。 struct ringbuffer& |( N- A" o4 Q4 }3 C) M6 s {6 q1 E3 E; c/ {! d uint8_t *bf; S0 x- z% m$ Z int len; / |1 W8 u5 s- ]) ^; F int count; int putidx; /* read index and put index */ int getidx; };! d4 k' `4 i. \% u/ x& i4 g% M struct ringbuffer对一个数组中的数据进行管理。数组的字节个数是len。更加详细的细节请大家尽情Google。写了一个宏,用来声明一个ringbuffer用来管理内存,准确的说叫做定义更好一些。" a, e' J) o; l5 C( E/ { #define DECLARE_RB(name,len) \2 |' r. j0 b) o0 r/ [ uint8_t name##_buff[len]; \ struct ringbuffer name = { \( U; }. n4 e" S name##_buff,len,0,0,0 }' q- ~$ t; j( Z5 v: O 提供的接口函数: #define rbcount(prb) ((prb)->count) #define rbfull(prb) ( (prb)->count==(rb)->len )# F$ y$ y1 E6 [- ?& L8 F #define rbempty(prb) ( (prb)->count==0 ) 9 v9 M) u' @. L7 B X /* 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); , w- _- ], y; M& W# P5 n rbput是往ringbuffer中添加一个数据。rbget是从ringbuffer中读取一个数据。相关的源代码: #include "ringbuffer.h"4 U0 c% R5 X& b$ _# M int rbput(struct ringbuffer *rb,uint8_t add)1 D+ u( q) ]% G. i5 P { int curidx=rb->putidx; if(rbfull(rb)) return 0; rb->bf[curidx]=add; rb->putidx=(curidx+1)%rb->len;' g6 |$ A) s% g rb->count++;* A. V9 k+ O2 Q4 ?$ r return 1;1 B" [6 m6 h% P n% a% h }6 J6 {7 J7 U! G# d5 a3 z int rbget(struct ringbuffer *rb)5 L6 ` _- f. U2 d( Z+ t# @7 H* B { int result; int curidx=rb->getidx;8 k8 W$ [ m- |4 M* S6 z if(rbempty(rb)) return -1; result=rb->bf[curidx];$ l2 ^# V5 f# T$ o7 z rb->getidx=(curidx+1)%rb->len; rb->count--;% ?/ {# r) V$ k, ] _: ]* Y7 | return result; } 串口中断+ringbuffer 在串口的初始化的时候,打开串口中断以及RXNE中断。在中断代码中将接受到的字符rbput到ringbuffer中。* ?$ U# k7 f- ? /* USART interrupt handler */ USART_Handler()5 B S3 P! k& o4 Y: \ { if(USART_GetITStatus(USART,USART_IT_RXNE)==SET){ rbput(&rb_usart,USART_ReceiveData(USART)); } }" d; C5 K9 O- z+ @ @ 编写__io_getchar() __io_putchar()函数。用于发送和读取一个数据。 `- e% V- e" F1 n6 b( l% Y0 G7 i DECLARE_RB(rb_usart,USART_RD_BUF_LEN);+ Q. n" s3 q$ Z- X4 l* b7 y int __io_putchar(int ch) H* K, |3 Y, X7 C% }- t5 h3 U {# A d! k! d; a' a2 g while(USART_GetFlagStatus(USART,USART_FLAG_TXE)!=SET); USART_SendData(USART,ch);( h" j- r' G0 v( S" U return ch; }) F. ?. l; ?: { J+ p) u0 y8 Z4 J( L int __io_getchar(void)/ z: b+ _0 a) r0 N4 h( y {1 ^' a# l- J }5 t6 M: F signed char ch;6 q$ k7 I5 a8 M% t' v while(rb_usart.count==0) continue;: K5 \0 M- I+ y3 s+ C4 Q ch=rbget(&rb_usart);/ F1 P9 }5 j- U8 W2 q# w switch(ch)% x! o8 e% u4 j; _9 r { case '\r':ch='\n';break; case 0x1b:ch=' ';break; default: break; }# w8 V1 Z. ]; N, g2 f# B /* echo it */4 P" s# e) L/ g/ o3 T- K8 | return __io_putchar(ch);0 |- E2 c5 Z8 |, K B1 D+ Q } 当在使用minicom发送特殊的按键,比如Up,Down,Left,Right按键的时候,会发送以0x18开始的三个或者若干字符。对于这些,我们只显示0x18之后的字符。4 f$ M* Y2 a% H5 y3 u: A j6 D) ~ 由于使用minicom,putty一类的串口,发送的数据并不能回显。因此,要想有回显,只能在读取的时候进行回显。在中断中回显并不是一个好的方法,会占用一定的中断时间。因此我将回显放在__io_getchar函数中。. y' f% N! s4 y- h1 c. N + I2 q# ?4 e: ?4 C$ Q7 N! A 在主函数中一直读取字符 我们的主函数中什么也不做,一直在读取字符。由于读取的时候会有回显,所以不论我们按下什么按键,都会实时回显。 int main(void) { const char *str="Try to enter something...\n"; const char *tmp=str;/ y& { O9 }) F8 O; J for(;*tmp!='\0';tmp++)& j, d% S& d* n, F9 V __io_putchar(*tmp); while(1) __io_getchar(); }- M' q+ z/ K9 J. Q: t 9 R# h+ B. e% K void _exit(int status)( {* M( d7 V; T* m8 z/ |% D {2 r2 W+ s+ e+ b const char *exit_str="GoodBye!\n"; const char *tmp=exit_str; for(;*tmp!='\0';tmp++) __io_putchar(*tmp); % L& E+ w0 L) V1 @ for(;;) continue; }% L9 X3 h8 z' E3 a 9 n! K$ C) @! d0 s 下载到FLASH中运行 目前所有的代码都在serial_v1.zip中,大家可以自行下载测试。. f. d; ?* O1 g4 d+ O0 Q 试着使用make all,make burn将代码下载至FLASH中。发现Try to enter something...这几个字符确实能够输出,但是不管我们按下什么按键都不能回显。暂时先不要使用make sram, make burns .7 P. h; \. Y" P 不管什么原因,至少能够显示也是好的。 5 a M4 b$ r1 ?9 ~ _2 w 使用gdb调试代码1 r p" H8 {$ {1 S5 g" ^3 r 新建一个控制台窗口,输入sudo st-util。连接st-link与stm32。st-util会监听4242端口。使用这个端口可以与arm-none-eabi-gdb进行通信。 ![]() 在当前目录下启动控制台窗口,输入arm-none-eabi-gdb blink.elf。由于使用了上一个例子的Makefile,所以文件名字blink.elf并没有更改。 在gdb串口使用tar连接远程终端。如下图所示: ![]() tar连接完毕之后,使用load命令将代码下载至单片机内。4 H. c/ e- `5 p' G# Z" O' I : i) Y+ v2 S1 O9 C) O ?3 y 至于没有回显字符,我首先想到的原因是串口RXNE中断没有进去。我可以在串口中断里面设置一个断点。当进入串口中断的时候自动停止。设置断点需要使用break命令+行号。为了查看usart.c的内容,可以使用list命令。list命令(可以简写成l,小写的L,不是数字1)有几种形式。 \7 @" l. e$ D
![]() 在第33行,也就是进入RXNE中断处设置一个断点。如果想查看所有已经设置的断点,可以使用info break查看。想删除断点,先使用info break查看对应断点的序号,在使用delete删除即可。4 T+ M. y2 \& p, V+ i ![]() 在设置完断点之后,可以输入continue(或者c)。继续运行。直到遇到断点停止。 ![]() ![]() 在continue之后,可以看见确实通过串口把数据输出了。这时候,可以试着在minicom中输入一个字符。我随便点了一个c。这时候,可以看到gdb中停在了中断的位置。# z/ n# N* b; o' g, Q ![]() 在试着随便输入几个字符。(没输入一个字符需要在gdb中输入continue。) 当gdb停在断点处的时候,使用print输出一下结构体rb_usart中的值。(或者使用print rb_usart.count查看结构体中的任意值) C5 r! q7 E; ? 9 g3 e: z% x6 _- q; M9 f ![]() 我们的rb_usart结构体中确实存放了数值。但为什么不能读取呢?可以在gdb中按下Ctrl+c结束程序运行,使用frame查看当前程序死在什么地方。# Z% j2 \4 Q t* i7 u& B2 D ![]() 使用frame之后,发现程序死在rb_usart.count==0这句。可是我们使用gdb查看,rb_usart.count明明是2,为什么这一句还过不去呢?初步感觉是volatile的事情。由于我们编译命令开启了-Os,使用了优化,可能这个rb_usart.count被优化到寄存器中去了。而while判断的时候,并没有实际读取内存,而是直接从寄存器中读取。所以造成数据不同步的原因。' a. b0 R7 K3 O. C 4 s1 o9 d+ i I8 X M1 e, ] 如果想退出gdb的时候,先按下Ctrl+c,让程序停止运行。在输入q,并按y就能够退出了。 0 e; l% L, c+ M( i9 ~' N bug1 如果只是volatile的原因的话,那么改正很简单。只要在相关变量中添加volatile就行了。* ~+ G! f) K; w4 l8 x& B 重新make all,make burn测试。 ![]() 问题已经成功解决。更正之后的代码在serial_v2.zip bug2 bug描述请参考下面回复的置顶贴。关于ringbuffer无锁的实现请参考:http://www.cnblogs.com/l00l/p/4115001.html 网址。 一开始接触到ringbuffer的时候,是阅读Arduino源代码中Hardwareserial的实现。他最开始并没有将ringbuffer单独抽象出来。代码比较难阅读。后来,Arduino sam的源代码将ringbuffer抽象成一个类。 在阅读完相关ringbuffer的代码之后,感觉ringbuffer如果只使用两个变量readidx,putidx来记录指针的变化,判断空和满的时候就会变得复杂。像这样:& W- b4 d- {4 c T1 {! k #define rbfull(prb) ( ((prb)->putidx+1)%(prb)->len == (prb)->getidx ) #define rbempty(prb) ( (prb)->getidx == (prb)->putidx )8 N; p1 o) G8 Z; {( P' U" X+ C0 ] 我很大意的给ringbuffer添加了一个count变量,用来记录存储的数据。但是这样的一个变量也破坏了ringbuffer的无锁结构。 更新之后的两个实现代码:, s$ h% s( V% ?' u int rbput(struct ringbuffer *rb,uint8_t add)9 |6 t0 E% K' \/ Z- |7 e0 J {' r8 z6 K8 t& k1 g/ ?% j! W if(rbfull(rb)) return 0;: ~: S- G7 c7 D% w rb->bf[rb->putidx]=add;. r3 l5 |' ^7 P& O$ J* U8 } rb->putidx=(rb->putidx+1)%rb->len; return 1;+ B% N1 j- ^! j4 x; w9 C } ( T. U9 E5 ?* x" b# Y- W- r. Q int rbget(struct ringbuffer *rb)7 P: m; ^' Q, w* B& i {' g* ^0 d; \- }: r" A/ @& ~8 z9 g int result;0 w3 W7 G# I t) j2 k; J6 f$ v if(rbempty(rb)) return -1;8 `. s6 ^0 `: u# `: n+ x( A$ I' ~ result=rb->bf[rb->getidx]; rb->getidx=(rb->getidx+1)%rb->len; return result;' m3 W) u) U1 T, I/ }) \2 y }- ~% G$ ?2 [' q v 由于去掉了count这个变量,在__io_getchar的时候,就不能使用count元素进行判断了。可以使用rbempty这个方法来判断。8 N5 Z" ~+ J2 _ int __io_getchar(void)0 w5 K ?0 l9 [, N; N, b" [ { signed char ch;6 c9 f% q4 c3 N: x( N while(rbempty(&rb_usart)) continue;& L+ ]- n R1 h7 J4 f0 B ch=rbget(&rb_usart); switch(ch) { case '\r':ch='\n';break; case 0x1b:ch=' ';break;7 K9 t2 E8 a4 T0 R default: break; }4 h, {* q9 S9 q' X0 v' A ; O4 N5 X1 E/ p /* echo it */ return __io_putchar(ch);7 e7 B$ s5 X2 X0 }' _6 D7 @ }3 r$ P( G/ i: ]3 P% G: |( _ N/ B; c) E6 { 更新之后的代码在附件serial_v3.zip中。 # w- A3 ?- i1 _ 更多% [& P' ~! E7 B9 i& w: y$ V 在下一节的时候,我们将来解决在SRAM中运行,使用中断的问题。' a2 T s3 s4 f ( h2 v J$ U7 l : @: `) F7 g0 ^# B$ g |
考虑:
rb->count--执行到一半,
此时进入中断rbput里rb->count++' j z9 K3 w' |0 N; F, U* Z
可能会导致rb->count计算不对, q' s0 D9 S; H" d! ]
确实是这样的。之前阅读Arduino的源代码的时候,发现如果只使用head,tail两个变量,在判断空或者满的时候稍微有点麻烦。我索性就加上了一个count。这样实现比较简单。没想到把ringbuffer无锁编程的特性给意外的去掉了。1 C4 h% T: v1 i8 N
还需要改改。
刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?
全局量要加互斥锁的吧
用过之后就好了。。。
一开始接触到ringbuffer的时候,也是看到了Arduino的代码才知道的。
只不过加了一个DECLARE_RB而已
把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。
http://www.cnblogs.com/l00l/p/4115001.html