本帖最后由 QianFan 于 2015-10-29 15:30 编辑 $ u! J* E Q, K0 p$ I H 可能觉得使用串口很简单,无非就是初始化GPIO,初始化串口。接着发送---检测是否成功。表面上看来是很简单的问题。然而,我要说的并不是这些。我要说的是volatile和中断向量表的问题。在其中配合一点gdb调试。 $ I, ?6 I+ ?8 R% a 使用ringbuffer 这里的串口使用非缓冲发送,ringbuffer中断缓冲接收的方式。先来看看ringbuffer。ringbuffer是一个特殊的队列,FIFO。 struct ringbuffer { uint8_t *bf;, P! f# R% D+ F) G) S int len;/ ^5 C2 T' a0 w3 \0 k ( G# n6 e5 r- ]& p int count; int putidx; /* read index and put index */4 U$ b S3 e6 e int getidx; };$ u1 q3 ~3 F8 O, ], K G3 j struct ringbuffer对一个数组中的数据进行管理。数组的字节个数是len。更加详细的细节请大家尽情Google。写了一个宏,用来声明一个ringbuffer用来管理内存,准确的说叫做定义更好一些。 #define DECLARE_RB(name,len) \, N5 Q% N" L/ l- P8 a0 @ uint8_t name##_buff[len]; \ struct ringbuffer name = { \0 A. h: t' B8 I9 f name##_buff,len,0,0,0 }: O/ D& }- x) B4 E8 {5 I6 }! l 提供的接口函数: #define rbcount(prb) ((prb)->count) #define rbfull(prb) ( (prb)->count==(rb)->len ) #define rbempty(prb) ( (prb)->count==0 )1 J" s+ N* Z3 K8 f ) {6 I; D/ ]) G) @9 _ /* if ringbuffer is full,return 0.else return 1 */1 d; I! D9 G" G, Q int rbput(struct ringbuffer *prb,uint8_t add);0 a3 z& b/ a; w, T /* if ringbuffer is empty,return -1.else return bf[tail] */ int rbget(struct ringbuffer *prb);, f9 n. O5 d0 b, J rbput是往ringbuffer中添加一个数据。rbget是从ringbuffer中读取一个数据。相关的源代码: #include "ringbuffer.h" int rbput(struct ringbuffer *rb,uint8_t add)9 w- C3 U/ c6 m, J4 I { int curidx=rb->putidx;6 T& ], q/ m' E. } if(rbfull(rb)) return 0;5 L4 T1 S- O, Y& y& p rb->bf[curidx]=add;% E$ \4 Q1 I5 a% ?- `6 M" V a rb->putidx=(curidx+1)%rb->len;+ B/ W% {' {! a/ j* z rb->count++; return 1; }* `; r- E1 @3 q9 c , \ l5 X5 g# ~% C int rbget(struct ringbuffer *rb) { int result; int curidx=rb->getidx; if(rbempty(rb)) return -1; result=rb->bf[curidx]; rb->getidx=(curidx+1)%rb->len; rb->count--;$ X @5 p0 U0 g2 u2 c0 E return result; } 串口中断+ringbuffer 在串口的初始化的时候,打开串口中断以及RXNE中断。在中断代码中将接受到的字符rbput到ringbuffer中。# L% x" m% e+ X% o7 K" n /* USART interrupt handler */ USART_Handler(), h2 a* P: a/ ], B( e7 z* |8 R { if(USART_GetITStatus(USART,USART_IT_RXNE)==SET){ rbput(&rb_usart,USART_ReceiveData(USART)); }1 S, T% A& H) H4 o5 V% X7 K4 p }5 d, ?% K P- u E. J3 b6 ~ 编写__io_getchar() __io_putchar()函数。用于发送和读取一个数据。 DECLARE_RB(rb_usart,USART_RD_BUF_LEN); int __io_putchar(int ch)$ A* }2 B% J+ t9 \) Y( l. L { while(USART_GetFlagStatus(USART,USART_FLAG_TXE)!=SET); USART_SendData(USART,ch);- @ \$ l" K4 ]5 ~; P# z return ch; } - a$ P7 D; o4 W int __io_getchar(void)7 H: X: p. p8 E! x: L6 c { signed char ch;' P. r& ?6 s" x1 _# y! N7 t while(rb_usart.count==0) continue;7 A& j5 D" m1 d9 d7 s" S ch=rbget(&rb_usart); switch(ch) {/ A# T Q+ i9 l case '\r':ch='\n';break;+ ^( Y* w: m/ {1 k. E# ?; c case 0x1b:ch=' ';break; default: break; } /* echo it */+ A' u/ b% M8 P return __io_putchar(ch); }7 |9 K$ _$ Z7 g/ |0 N3 N' g 当在使用minicom发送特殊的按键,比如Up,Down,Left,Right按键的时候,会发送以0x18开始的三个或者若干字符。对于这些,我们只显示0x18之后的字符。& a, T& o! E9 g$ P 由于使用minicom,putty一类的串口,发送的数据并不能回显。因此,要想有回显,只能在读取的时候进行回显。在中断中回显并不是一个好的方法,会占用一定的中断时间。因此我将回显放在__io_getchar函数中。4 {# o- H1 l7 }! }8 Z, Y ) ^4 x5 H3 ~, b, _; Q: @8 j7 E 在主函数中一直读取字符 我们的主函数中什么也不做,一直在读取字符。由于读取的时候会有回显,所以不论我们按下什么按键,都会实时回显。" X* K- q( K5 d6 i* } int main(void) {. ?) `: H. W/ _+ k const char *str="Try to enter something...\n";* B/ k, L9 K2 r) k const char *tmp=str;; m$ e$ l8 Q! ?2 r( g for(;*tmp!='\0';tmp++)* H o$ e0 ]) S! H, G __io_putchar(*tmp);7 Q) C9 }- @+ p/ C7 [# i- Y' _ : t3 G4 C& H4 g- a3 J0 P6 g while(1) __io_getchar(); }; B8 Z, E* l, E7 D( Y" O' A 8 g! y7 u4 Z8 e) A void _exit(int status)) {- I0 P% L3 J* ^8 w { const char *exit_str="GoodBye!\n";+ R+ i' M* j$ w- W$ k4 J7 o const char *tmp=exit_str; for(;*tmp!='\0';tmp++)& t8 z+ o7 F% u F __io_putchar(*tmp);3 a8 V% a8 L/ J6 @ for(;;) continue; }. ]3 u5 ~9 x1 {& s& o 下载到FLASH中运行+ }4 U% c+ k, @ 目前所有的代码都在serial_v1.zip中,大家可以自行下载测试。 试着使用make all,make burn将代码下载至FLASH中。发现Try to enter something...这几个字符确实能够输出,但是不管我们按下什么按键都不能回显。暂时先不要使用make sram, make burns .. y" n/ T4 t( E8 P( T& I 不管什么原因,至少能够显示也是好的。6 \. ^# |" d8 C9 R: m5 W 使用gdb调试代码 新建一个控制台窗口,输入sudo st-util。连接st-link与stm32。st-util会监听4242端口。使用这个端口可以与arm-none-eabi-gdb进行通信。) h3 u9 a( ]+ o6 j) g5 P7 X 在当前目录下启动控制台窗口,输入arm-none-eabi-gdb blink.elf。由于使用了上一个例子的Makefile,所以文件名字blink.elf并没有更改。 在gdb串口使用tar连接远程终端。如下图所示: tar连接完毕之后,使用load命令将代码下载至单片机内。# Z% `- u; s: J: X) d9 @4 h, i4 ] 至于没有回显字符,我首先想到的原因是串口RXNE中断没有进去。我可以在串口中断里面设置一个断点。当进入串口中断的时候自动停止。设置断点需要使用break命令+行号。为了查看usart.c的内容,可以使用list命令。list命令(可以简写成l,小写的L,不是数字1)有几种形式。
在第33行,也就是进入RXNE中断处设置一个断点。如果想查看所有已经设置的断点,可以使用info break查看。想删除断点,先使用info break查看对应断点的序号,在使用delete删除即可。 ' B0 w" M6 y$ `* f: l9 z t 在设置完断点之后,可以输入continue(或者c)。继续运行。直到遇到断点停止。+ W# C4 p2 e) J% | 在continue之后,可以看见确实通过串口把数据输出了。这时候,可以试着在minicom中输入一个字符。我随便点了一个c。这时候,可以看到gdb中停在了中断的位置。. [- H4 { h+ R! r |! K% p7 U 在试着随便输入几个字符。(没输入一个字符需要在gdb中输入continue。) 当gdb停在断点处的时候,使用print输出一下结构体rb_usart中的值。(或者使用print rb_usart.count查看结构体中的任意值) 我们的rb_usart结构体中确实存放了数值。但为什么不能读取呢?可以在gdb中按下Ctrl+c结束程序运行,使用frame查看当前程序死在什么地方。% b6 f Y0 Z5 [1 b* D 使用frame之后,发现程序死在rb_usart.count==0这句。可是我们使用gdb查看,rb_usart.count明明是2,为什么这一句还过不去呢?初步感觉是volatile的事情。由于我们编译命令开启了-Os,使用了优化,可能这个rb_usart.count被优化到寄存器中去了。而while判断的时候,并没有实际读取内存,而是直接从寄存器中读取。所以造成数据不同步的原因。 如果想退出gdb的时候,先按下Ctrl+c,让程序停止运行。在输入q,并按y就能够退出了。 9 Y' O) q3 @6 v 6 L8 D: o% }+ s6 g# Q0 M4 z 0 g% V# A/ r& _0 R* R2 J* o: T bug1 如果只是volatile的原因的话,那么改正很简单。只要在相关变量中添加volatile就行了。 重新make all,make burn测试。 问题已经成功解决。更正之后的代码在serial_v2.zip ' Q$ P6 R$ k5 I* f* |7 N2 ^ bug22 d* z6 G6 j- f8 X2 Q3 D bug描述请参考下面回复的置顶贴。关于ringbuffer无锁的实现请参考:http://www.cnblogs.com/l00l/p/4115001.html 网址。 一开始接触到ringbuffer的时候,是阅读Arduino源代码中Hardwareserial的实现。他最开始并没有将ringbuffer单独抽象出来。代码比较难阅读。后来,Arduino sam的源代码将ringbuffer抽象成一个类。 在阅读完相关ringbuffer的代码之后,感觉ringbuffer如果只使用两个变量readidx,putidx来记录指针的变化,判断空和满的时候就会变得复杂。像这样:0 J" Q5 K! Y0 J8 G #define rbfull(prb) ( ((prb)->putidx+1)%(prb)->len == (prb)->getidx ) #define rbempty(prb) ( (prb)->getidx == (prb)->putidx )3 E/ e, p U0 H$ v# {0 D; b 我很大意的给ringbuffer添加了一个count变量,用来记录存储的数据。但是这样的一个变量也破坏了ringbuffer的无锁结构。4 F" `- s k* C' |* s% f 更新之后的两个实现代码:5 _/ l7 Z, A) q int rbput(struct ringbuffer *rb,uint8_t add). f) x$ V) g" q* m { if(rbfull(rb)) return 0; rb->bf[rb->putidx]=add; rb->putidx=(rb->putidx+1)%rb->len; return 1; }; _& A! H! b. J7 y7 @ int rbget(struct ringbuffer *rb) {! s+ ?! ~/ D9 h" g, c int result; if(rbempty(rb)) return -1;: P/ G9 U/ ^2 u' P! N( e result=rb->bf[rb->getidx];* x: z: B. g" j$ B* x6 E+ e rb->getidx=(rb->getidx+1)%rb->len; return result; } 由于去掉了count这个变量,在__io_getchar的时候,就不能使用count元素进行判断了。可以使用rbempty这个方法来判断。 int __io_getchar(void)4 T& R6 C3 N( v, L/ G {. i8 F9 |/ d* H5 A signed char ch; while(rbempty(&rb_usart)) continue; ch=rbget(&rb_usart); switch(ch) {# d+ K9 Y- {: U0 [/ e( W case '\r':ch='\n';break;" N5 C9 _. y# V% u, C" {0 K case 0x1b:ch=' ';break;' o9 G9 _% Z4 C7 q default: break;# P6 C* S7 p! `1 i } 8 w: G3 \! T' J2 Q. r$ }2 f /* echo it */9 b- n5 r* d0 O& s1 E return __io_putchar(ch); }: x2 H J& d5 `! p 更新之后的代码在附件serial_v3.zip中。" Y6 x0 q1 s/ c" i5 u# H. q# x 更多: ~3 U& Q( e3 M1 E# _, C 在下一节的时候,我们将来解决在SRAM中运行,使用中断的问题。% ]% d; ~7 C& |6 j2 M 5 q0 X4 v! M# G$ A1 h% ? - t- h6 j3 q$ }, f$ ?5 Q |
考虑:- t2 [( ~# f1 |7 {) S
rb->count--执行到一半,4 W4 Y! r, K0 v# A
此时进入中断rbput里rb->count++3 w6 ?+ n& [) b; ` Z
可能会导致rb->count计算不对0 L! Y* B. m3 ]; Q4 ]$ G
确实是这样的。之前阅读Arduino的源代码的时候,发现如果只使用head,tail两个变量,在判断空或者满的时候稍微有点麻烦。我索性就加上了一个count。这样实现比较简单。没想到把ringbuffer无锁编程的特性给意外的去掉了。
还需要改改。
刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?
全局量要加互斥锁的吧
用过之后就好了。。。
一开始接触到ringbuffer的时候,也是看到了Arduino的代码才知道的。
只不过加了一个DECLARE_RB而已
把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。
http://www.cnblogs.com/l00l/p/4115001.html