你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

Ubuntu下开发STM32---5.使用串口Part1 精华

[复制链接]
qianfan 发布时间:2015-10-28 21:36
本帖最后由 QianFan 于 2015-10-29 15:30 编辑

  可能觉得使用串口很简单,无非就是初始化GPIO,初始化串口。接着发送---检测是否成功。表面上看来是很简单的问题。然而,我要说的并不是这些。我要说的是volatile和中断向量表的问题。在其中配合一点gdb调试。

使用ringbuffer
这里的串口使用非缓冲发送,ringbuffer中断缓冲接收的方式。先来看看ringbuffer。ringbuffer是一个特殊的队列,FIFO。
struct ringbuffer
{
    uint8_t *bf;
    int len;
   
    int count;
    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 = { \
        name##_buff,len,0,0,0 }

提供的接口函数:
#define rbcount(prb) ((prb)->count)
#define rbfull(prb) ( (prb)->count==(rb)->len )
#define rbempty(prb) ( (prb)->count==0 )

/* 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);


rbput是往ringbuffer中添加一个数据。rbget是从ringbuffer中读取一个数据。相关的源代码:
#include "ringbuffer.h"

int rbput(struct ringbuffer *rb,uint8_t add)
{
    int curidx=rb->putidx;
    if(rbfull(rb)) return 0;
    rb->bf[curidx]=add;
    rb->putidx=(curidx+1)%rb->len;
    rb->count++;
    return 1;
}

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--;
    return result;
}

串口中断+ringbuffer
在串口的初始化的时候,打开串口中断以及RXNE中断。在中断代码中将接受到的字符rbput到ringbuffer中。
/* USART interrupt handler */
USART_Handler()
{
    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);

int __io_putchar(int ch)
{
    while(USART_GetFlagStatus(USART,USART_FLAG_TXE)!=SET);
    USART_SendData(USART,ch);
    return ch;
}

int __io_getchar(void)
{
    signed char ch;
    while(rb_usart.count==0) continue;
    ch=rbget(&rb_usart);
    switch(ch)
    {
        case '\r':ch='\n';break;
        case 0x1b:ch=' ';break;
        default: break;
    }
   
    /* echo it */
    return __io_putchar(ch);
}

当在使用minicom发送特殊的按键,比如Up,Down,Left,Right按键的时候,会发送以0x18开始的三个或者若干字符。对于这些,我们只显示0x18之后的字符。
由于使用minicom,putty一类的串口,发送的数据并不能回显。因此,要想有回显,只能在读取的时候进行回显。在中断中回显并不是一个好的方法,会占用一定的中断时间。因此我将回显放在__io_getchar函数中。

在主函数中一直读取字符
我们的主函数中什么也不做,一直在读取字符。由于读取的时候会有回显,所以不论我们按下什么按键,都会实时回显。
int main(void)
{
    const char *str="Try to enter something...\n";
   
    const char *tmp=str;
    for(;*tmp!='\0';tmp++)
        __io_putchar(*tmp);
        
    while(1) __io_getchar();
}

void _exit(int status)
{
    const char *exit_str="GoodBye!\n";
    const char *tmp=exit_str;
    for(;*tmp!='\0';tmp++)
        __io_putchar(*tmp);
   
    for(;;) continue;
}


下载到FLASH中运行
目前所有的代码都在serial_v1.zip中,大家可以自行下载测试。
试着使用make all,make burn将代码下载至FLASH中。发现Try to enter something...这几个字符确实能够输出,但是不管我们按下什么按键都不能回显。暂时先不要使用make sram, make burns .
不管什么原因,至少能够显示也是好的。

使用gdb调试代码
新建一个控制台窗口,输入sudo st-util。连接st-link与stm32。st-util会监听4242端口。使用这个端口可以与arm-none-eabi-gdb进行通信。
2015-10-28 21:47:50屏幕截图.png
在当前目录下启动控制台窗口,输入arm-none-eabi-gdb blink.elf。由于使用了上一个例子的Makefile,所以文件名字blink.elf并没有更改。
在gdb串口使用tar连接远程终端。如下图所示:
2015-10-28 21:03:36屏幕截图.png
tar连接完毕之后,使用load命令将代码下载至单片机内。

至于没有回显字符,我首先想到的原因是串口RXNE中断没有进去。我可以在串口中断里面设置一个断点。当进入串口中断的时候自动停止。设置断点需要使用break命令+行号。为了查看usart.c的内容,可以使用list命令。list命令(可以简写成l,小写的L,不是数字1)有几种形式。
  • list function_name 用于显示一个函数附近的代码。如list main.
  • list file_name.c:line_number 用于显示一个源文件的第line_number行。
  • list 在只输入list的情况下,可以从当前代码的位置继续往下显示代码。
使用list usart.c:1显示usart.c文件的代码。继续输入list显示其他代码。找到串口中断的代码。
2015-10-28 21:11:53屏幕截图.png
在第33行,也就是进入RXNE中断处设置一个断点。如果想查看所有已经设置的断点,可以使用info break查看。想删除断点,先使用info break查看对应断点的序号,在使用delete删除即可。

2015-10-28 21:15:25屏幕截图.png
在设置完断点之后,可以输入continue(或者c)。继续运行。直到遇到断点停止。
2015-10-28 21:16:33屏幕截图.png
2015-10-28 21:17:02屏幕截图.png
在continue之后,可以看见确实通过串口把数据输出了。这时候,可以试着在minicom中输入一个字符。我随便点了一个c。这时候,可以看到gdb中停在了中断的位置。
2015-10-28 21:18:21屏幕截图.png
在试着随便输入几个字符。(没输入一个字符需要在gdb中输入continue。)
当gdb停在断点处的时候,使用print输出一下结构体rb_usart中的值。(或者使用print rb_usart.count查看结构体中的任意值)

2015-10-28 21:22:46屏幕截图.png
我们的rb_usart结构体中确实存放了数值。但为什么不能读取呢?可以在gdb中按下Ctrl+c结束程序运行,使用frame查看当前程序死在什么地方。
2015-10-28 21:26:09屏幕截图.png
使用frame之后,发现程序死在rb_usart.count==0这句。可是我们使用gdb查看,rb_usart.count明明是2,为什么这一句还过不去呢?初步感觉是volatile的事情。由于我们编译命令开启了-Os,使用了优化,可能这个rb_usart.count被优化到寄存器中去了。而while判断的时候,并没有实际读取内存,而是直接从寄存器中读取。所以造成数据不同步的原因。

如果想退出gdb的时候,先按下Ctrl+c,让程序停止运行。在输入q,并按y就能够退出了。



bug1
如果只是volatile的原因的话,那么改正很简单。只要在相关变量中添加volatile就行了。
重新make all,make burn测试。
2015-10-28 21:32:23屏幕截图.png
问题已经成功解决。更正之后的代码在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来记录指针的变化,判断空和满的时候就会变得复杂。像这样:
#define rbfull(prb) ( ((prb)->putidx+1)%(prb)->len == (prb)->getidx )
#define rbempty(prb) ( (prb)->getidx == (prb)->putidx )

我很大意的给ringbuffer添加了一个count变量,用来记录存储的数据。但是这样的一个变量也破坏了ringbuffer的无锁结构。
更新之后的两个实现代码:
int rbput(struct ringbuffer *rb,uint8_t add)
{
    if(rbfull(rb)) return 0;
    rb->bf[rb->putidx]=add;
    rb->putidx=(rb->putidx+1)%rb->len;
    return 1;
}

int rbget(struct ringbuffer *rb)
{
    int result;
    if(rbempty(rb)) return -1;
    result=rb->bf[rb->getidx];
    rb->getidx=(rb->getidx+1)%rb->len;
    return result;
}
由于去掉了count这个变量,在__io_getchar的时候,就不能使用count元素进行判断了。可以使用rbempty这个方法来判断
int __io_getchar(void)
{
    signed char ch;
    while(rbempty(&rb_usart)) continue;
    ch=rbget(&rb_usart);
    switch(ch)
    {
        case '\r':ch='\n';break;
        case 0x1b:ch=' ';break;
        default: break;
    }
   
    /* echo it */
    return __io_putchar(ch);
}
更新之后的代码在附件serial_v3.zip中。


更多
在下一节的时候,我们将来解决在SRAM中运行,使用中断的问题




serial _v1.zip

下载

395.61 KB, 下载次数: 55

serial _v2.zip

下载

395.62 KB, 下载次数: 50

serial _v3.zip

下载

395.61 KB, 下载次数: 75

评分

参与人数 1 ST金币 +30 收起 理由
zero99 + 30

查看全部评分

收藏 3 评论20 发布时间:2015-10-28 21:36

举报

20个回答
Mandelbrot_Set 最优答案 回答时间:2015-10-29 09:08:51
感觉楼主的 rb->count 可能不是安全的.
考虑:
rb->count--执行到一半,
此时进入中断rbput里rb->count++
可能会导致rb->count计算不对
qianfan 回答时间:2015-10-29 13:45:32
Mandelbrot_Set 发表于 2015-10-29 09:08
感觉楼主的 rb->count 可能不是安全的.
考虑:
rb->count--执行到一半,

确实是这样的。之前阅读Arduino的源代码的时候,发现如果只使用head,tail两个变量,在判断空或者满的时候稍微有点麻烦。我索性就加上了一个count。这样实现比较简单。没想到把ringbuffer无锁编程的特性给意外的去掉了。
还需要改改。
党国特派员 回答时间:2015-10-29 09:12:20
学习了。。。 blank.png blank1.png blank2.png blank3.png blank4.png
khadgar 回答时间:2015-10-29 14:32:30
QianFan 发表于 2015-10-29 13:46
把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。

刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?
Paderboy 回答时间:2015-10-28 22:58:39
沙发啊。。。这个很有arduino的味道啊。。。
yanhaijian 回答时间:2015-10-29 08:36:02
环形队列很有用。
aabird 回答时间:2015-10-29 08:40:17
哇,真的是大神呀,这篇帖子,说实话90%没看懂呀
埃斯提爱慕 回答时间:2015-10-29 10:20:44
提示: 作者被禁止或删除 内容自动屏蔽
khadgar 回答时间:2015-10-29 10:49:01
Mandelbrot_Set 发表于 2015-10-29 09:08
感觉楼主的 rb->count 可能不是安全的.
考虑:
rb->count--执行到一半,

全局量要加互斥锁的吧
qianfan 回答时间:2015-10-29 11:59:54
aabird 发表于 2015-10-29 08:40
哇,真的是大神呀,这篇帖子,说实话90%没看懂呀

用过之后就好了。。。
qianfan 回答时间:2015-10-29 12:01:05
Paderboy 发表于 2015-10-28 22:58
沙发啊。。。这个很有arduino的味道啊。。。

一开始接触到ringbuffer的时候,也是看到了Arduino的代码才知道的。
qianfan 回答时间:2015-10-29 12:01:44
Paderboy 发表于 2015-10-28 22:58
沙发啊。。。这个很有arduino的味道啊。。。

只不过加了一个DECLARE_RB而已
qianfan 回答时间:2015-10-29 13:46:16
卡德加 发表于 2015-10-29 10:49
全局量要加互斥锁的吧

把count变量去掉。更改full和empty的实现方法,还是能实现无锁编程的。
qianfan 回答时间:2015-10-29 14:35:31
卡德加 发表于 2015-10-29 14:32
刚开始接触linux,能详细说说怎么实现吗?无锁比有锁有优点吗?

http://www.cnblogs.com/l00l/p/4115001.html
12下一页

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版