|
本帖最后由 QianFan 于 2015-10-29 15:30 编辑 9 E2 u J8 N7 w" s% C+ i4 y 2 E9 c$ i0 ~! S* d. z6 z 可能觉得使用串口很简单,无非就是初始化GPIO,初始化串口。接着发送---检测是否成功。表面上看来是很简单的问题。然而,我要说的并不是这些。我要说的是volatile和中断向量表的问题。在其中配合一点gdb调试。7 a5 R! Y* k* w9 h# o E& ] 使用ringbuffer 这里的串口使用非缓冲发送,ringbuffer中断缓冲接收的方式。先来看看ringbuffer。ringbuffer是一个特殊的队列,FIFO。. I- y% j0 d" t K+ L) U; X2 u$ d struct ringbuffer( t, W9 N% n9 Q1 @2 F8 P& g( P5 s {" |3 g0 j& V* l& x+ g0 x, ~7 v uint8_t *bf; int len;4 _8 g2 u% B- l0 |4 q' O! i : o4 M) d" b J0 p int count;* a' G, i4 V w; {! r) r int putidx; /* read index and put index */ int getidx; }; struct ringbuffer对一个数组中的数据进行管理。数组的字节个数是len。更加详细的细节请大家尽情Google。写了一个宏,用来声明一个ringbuffer用来管理内存,准确的说叫做定义更好一些。 #define DECLARE_RB(name,len) \ uint8_t name##_buff[len]; \ struct ringbuffer name = { \( L! y8 M' H1 D% w, V3 { name##_buff,len,0,0,0 } 提供的接口函数:9 W- Q. o5 h0 C( Y: @ #define rbcount(prb) ((prb)->count) #define rbfull(prb) ( (prb)->count==(rb)->len ) #define rbempty(prb) ( (prb)->count==0 ) & ]# @7 A5 L5 o4 v7 z /* if ringbuffer is full,return 0.else return 1 */9 I7 O9 F' N! d- |8 s4 }- f int rbput(struct ringbuffer *prb,uint8_t add);6 d$ p1 W2 ^9 f& v4 O /* if ringbuffer is empty,return -1.else return bf[tail] */ int rbget(struct ringbuffer *prb);" `/ I! d* G6 `, F" h c8 C rbput是往ringbuffer中添加一个数据。rbget是从ringbuffer中读取一个数据。相关的源代码: #include "ringbuffer.h". H! g% H N! g/ M& ~; J int rbput(struct ringbuffer *rb,uint8_t add) { int curidx=rb->putidx; if(rbfull(rb)) return 0;7 e0 l% g% a7 n9 O! D! ] rb->bf[curidx]=add;* q: A; f! z' J2 a rb->putidx=(curidx+1)%rb->len;2 h# G% |$ ^, `; P$ ~3 w rb->count++;/ X7 {. Q' I4 `/ a" W, x% ?. k return 1;; a* u" s) i, A$ K/ p0 m } 8 F+ Q, q5 @+ Y) ^4 D. n int rbget(struct ringbuffer *rb)' Q& a& I" P2 U- e { int result; int curidx=rb->getidx;7 w/ R: g) o) b0 o( o4 R# J if(rbempty(rb)) return -1; result=rb->bf[curidx]; rb->getidx=(curidx+1)%rb->len; rb->count--; return result; }: u1 `( h* E' {& Q + T% _( O6 q& W: o$ E/ D 串口中断+ringbuffer' j0 C+ d- n& e0 d 在串口的初始化的时候,打开串口中断以及RXNE中断。在中断代码中将接受到的字符rbput到ringbuffer中。 /* USART interrupt handler */! M/ O$ I; Q" R& B2 d- h9 [ USART_Handler()* W2 g* q' j, ^; D { if(USART_GetITStatus(USART,USART_IT_RXNE)==SET){ rbput(&rb_usart,USART_ReceiveData(USART)); }0 K9 j& b" Y/ t) S1 t3 @ s } 编写__io_getchar() __io_putchar()函数。用于发送和读取一个数据。 DECLARE_RB(rb_usart,USART_RD_BUF_LEN);& z8 |5 I' G" N + u0 P/ B- H" D# I* Q6 V+ A/ F int __io_putchar(int ch) {, z0 x7 {% G! e! p3 M7 ~5 E$ F# O while(USART_GetFlagStatus(USART,USART_FLAG_TXE)!=SET); USART_SendData(USART,ch);& B0 O. Z" Q4 b( l0 ]2 i1 ]" n return ch;1 K1 \7 C* O6 E% w3 e } int __io_getchar(void)& @( H/ i. o( B3 z/ h { signed char ch;6 J7 r8 n& u1 T7 i7 p while(rb_usart.count==0) continue; ch=rbget(&rb_usart); switch(ch)% K& f0 \5 d: A) i& K- o+ H { case '\r':ch='\n';break; case 0x1b:ch=' ';break;( N$ z. k4 h+ g9 A& [% P$ a: g! m default: break; } /* echo it */% v9 e; ^# @2 o return __io_putchar(ch);) O7 P" _- ~% x }4 }8 \8 d+ D2 j; ^7 Q5 Y 当在使用minicom发送特殊的按键,比如Up,Down,Left,Right按键的时候,会发送以0x18开始的三个或者若干字符。对于这些,我们只显示0x18之后的字符。' {+ M2 a+ l0 f" P4 q 由于使用minicom,putty一类的串口,发送的数据并不能回显。因此,要想有回显,只能在读取的时候进行回显。在中断中回显并不是一个好的方法,会占用一定的中断时间。因此我将回显放在__io_getchar函数中。 在主函数中一直读取字符: d1 Q: N" M+ |8 D3 Z: [- k; S) b 我们的主函数中什么也不做,一直在读取字符。由于读取的时候会有回显,所以不论我们按下什么按键,都会实时回显。1 z- h4 M$ W7 a/ i) Z int main(void) {; ]6 m0 T5 f5 L I# ]8 E+ Q% ~ const char *str="Try to enter something...\n";* N- V, C: `5 t5 [ const char *tmp=str;2 v: m6 H7 _$ O- ^ e- m% f$ J for(;*tmp!='\0';tmp++) __io_putchar(*tmp);( X' @5 o9 `* R0 I 8 |* t3 u( P* X2 ]! U8 C while(1) __io_getchar(); } 8 o c5 M. p4 v( Q: H! k void _exit(int status) { const char *exit_str="GoodBye!\n"; const char *tmp=exit_str;* l3 n# l5 F' }& @" A# W for(;*tmp!='\0';tmp++), n3 G0 P% D" F4 m __io_putchar(*tmp); - T: F8 Y9 N) D for(;;) continue;: N) F. u& X; x$ m }' T r& n" M* ~5 L x # d2 }( W" k4 X* r7 Q 下载到FLASH中运行 目前所有的代码都在serial_v1.zip中,大家可以自行下载测试。 试着使用make all,make burn将代码下载至FLASH中。发现Try to enter something...这几个字符确实能够输出,但是不管我们按下什么按键都不能回显。暂时先不要使用make sram, make burns . 不管什么原因,至少能够显示也是好的。+ E1 e f' g- M N } 使用gdb调试代码8 _) R0 h; C$ H 新建一个控制台窗口,输入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命令将代码下载至单片机内。, \# z& R% I/ u1 _3 K5 m4 m2 D 8 @. z+ [8 A# Y% u4 P- m/ X 至于没有回显字符,我首先想到的原因是串口RXNE中断没有进去。我可以在串口中断里面设置一个断点。当进入串口中断的时候自动停止。设置断点需要使用break命令+行号。为了查看usart.c的内容,可以使用list命令。list命令(可以简写成l,小写的L,不是数字1)有几种形式。
在第33行,也就是进入RXNE中断处设置一个断点。如果想查看所有已经设置的断点,可以使用info break查看。想删除断点,先使用info break查看对应断点的序号,在使用delete删除即可。& ~. s! l* a: N3 {# U3 N
在设置完断点之后,可以输入continue(或者c)。继续运行。直到遇到断点停止。. a0 |9 u& l: p$ b% q% \5 y; H
在continue之后,可以看见确实通过串口把数据输出了。这时候,可以试着在minicom中输入一个字符。我随便点了一个c。这时候,可以看到gdb中停在了中断的位置。
在试着随便输入几个字符。(没输入一个字符需要在gdb中输入continue。)- X3 e) d0 n- K, z1 u; o 当gdb停在断点处的时候,使用print输出一下结构体rb_usart中的值。(或者使用print rb_usart.count查看结构体中的任意值) $ `) t' f4 R/ p$ ]+ r9 R
我们的rb_usart结构体中确实存放了数值。但为什么不能读取呢?可以在gdb中按下Ctrl+c结束程序运行,使用frame查看当前程序死在什么地方。
使用frame之后,发现程序死在rb_usart.count==0这句。可是我们使用gdb查看,rb_usart.count明明是2,为什么这一句还过不去呢?初步感觉是volatile的事情。由于我们编译命令开启了-Os,使用了优化,可能这个rb_usart.count被优化到寄存器中去了。而while判断的时候,并没有实际读取内存,而是直接从寄存器中读取。所以造成数据不同步的原因。1 r0 x$ P7 x' d2 j }8 @; u: ?, `4 ]+ j* x4 L* r 如果想退出gdb的时候,先按下Ctrl+c,让程序停止运行。在输入q,并按y就能够退出了。 * Y* N: } e: E+ L6 A/ p ; I5 t: {: W. `: H Z2 L/ k bug12 Y# ?) B, t( N r' S+ g 如果只是volatile的原因的话,那么改正很简单。只要在相关变量中添加volatile就行了。6 c- i B. s0 n, f* {9 E0 B5 m7 { 重新make all,make burn测试。
问题已经成功解决。更正之后的代码在serial_v2.zip ' a7 z: n! E5 d bug24 j- {& N6 ]# o4 L bug描述请参考下面回复的置顶贴。关于ringbuffer无锁的实现请参考:http://www.cnblogs.com/l00l/p/4115001.html 网址。 一开始接触到ringbuffer的时候,是阅读Arduino源代码中Hardwareserial的实现。他最开始并没有将ringbuffer单独抽象出来。代码比较难阅读。后来,Arduino sam的源代码将ringbuffer抽象成一个类。 在阅读完相关ringbuffer的代码之后,感觉ringbuffer如果只使用两个变量readidx,putidx来记录指针的变化,判断空和满的时候就会变得复杂。像这样: #define rbfull(prb) ( ((prb)->putidx+1)%(prb)->len == (prb)->getidx ) #define rbempty(prb) ( (prb)->getidx == (prb)->putidx )$ X" O+ \3 i& y1 O( F 我很大意的给ringbuffer添加了一个count变量,用来记录存储的数据。但是这样的一个变量也破坏了ringbuffer的无锁结构。4 k) L. J2 r- L% j 更新之后的两个实现代码:! {1 O9 a9 o* n int rbput(struct ringbuffer *rb,uint8_t add) {' }) v( j% B1 h if(rbfull(rb)) return 0;; E6 ~8 _! j( f9 J) Q' K rb->bf[rb->putidx]=add;0 Q1 ~! k7 B1 z7 X) s$ n) h& K rb->putidx=(rb->putidx+1)%rb->len; return 1; }5 S0 D/ X% H( Y' |8 y) y2 N ) ]0 Q9 e8 m) V7 k d5 A int rbget(struct ringbuffer *rb), K5 S3 W3 x9 `$ n) U, } \ { int result; if(rbempty(rb)) return -1; result=rb->bf[rb->getidx]; rb->getidx=(rb->getidx+1)%rb->len;2 s" Z5 o% a8 q! K& E7 C return result;. z5 z! o3 V, P, V9 w% y7 s }3 Z) E5 w2 P5 }% D: Q2 H' z 由于去掉了count这个变量,在__io_getchar的时候,就不能使用count元素进行判断了。可以使用rbempty这个方法来判断。- y! }# ?: n1 Q8 h6 k5 F int __io_getchar(void)0 y. x4 W6 ~$ f8 l7 `+ h f- |# ] {# x1 T. B$ `/ c2 u7 m signed char ch; while(rbempty(&rb_usart)) continue; ch=rbget(&rb_usart); switch(ch)1 S1 u- h( B8 [9 d% Q* f9 X {; N' d, f% X( U0 E4 ? case '\r':ch='\n';break;8 J; w1 \; I8 F7 h2 d5 Q case 0x1b:ch=' ';break;- t0 k$ S6 l6 X* t0 A default: break;8 S8 E1 H3 p- @' ~ }- ]. P7 V: g" V 7 r- Y/ R$ N! R2 \. H! { /* echo it */6 k" L8 a: t0 ?5 B7 W7 d5 a( e return __io_putchar(ch); }* N1 ^( j9 C2 ` 更新之后的代码在附件serial_v3.zip中。 更多 在下一节的时候,我们将来解决在SRAM中运行,使用中断的问题。5 F* Y; e P$ R* X( v/ C 1 d9 F" e3 H7 Q% e$ | 5 }+ o& E/ t) ^5 O |
微信公众号
手机版
考虑:
rb->count--执行到一半,
此时进入中断rbput里rb->count++
可能会导致rb->count计算不对/ J9 u2 W! G. j { P) W1 o ~
确实是这样的。之前阅读Arduino的源代码的时候,发现如果只使用head,tail两个变量,在判断空或者满的时候稍微有点麻烦。我索性就加上了一个count。这样实现比较简单。没想到把ringbuffer无锁编程的特性给意外的去掉了。
还需要改改。
刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?
全局量要加互斥锁的吧
用过之后就好了。。。
一开始接触到ringbuffer的时候,也是看到了Arduino的代码才知道的。
只不过加了一个DECLARE_RB而已
把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。
http://www.cnblogs.com/l00l/p/4115001.html