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

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, 下载次数: 53

serial _v2.zip

下载

395.62 KB, 下载次数: 48

serial _v3.zip

下载

395.61 KB, 下载次数: 72

评分

参与人数 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计算不对
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 09:12:20
学习了。。。 blank.png blank1.png blank2.png blank3.png blank4.png
dsjsjf 回答时间:2015-10-29 10:20:44
顶顶顶顶
卡德加 回答时间: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的代码才知道的。
123下一页

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

相似技术帖

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