OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 23104|回复: 15

【Alientek STM32 实验3】--串口实验

[复制链接]

98

主题

408

帖子

3

精华

金牌会员

Rank: 6Rank: 6

积分
1280
金钱
1280
注册时间
2010-12-14
在线时间
0 小时
发表于 2010-12-14 22:54:44 | 显示全部楼层 |阅读模式

3.3 串口实验

前面两节介绍了STM32IO口操作。这一节我们将学习STM32的串口。通过本节的学习,你将了解到STM32串口的基本使用方法。本节分为如下几个小节:

3.3.1 STM32串口简介

3.3.2 硬件设计

3.3.3 软件设计

3.3.4 仿真与下载

3.3.1 STM32串口简介

 

            前面两节介绍了STM32IO口操作。这一节我们将学习STM32的串口。作为软件开发重要的调试手段,串口的作用是很大的。在调试的时候可以用来查看和输入相关的信息。在使用的时候,串口也是一个和外设(比如GPSGPRS模块等)通信的重要渠道。

STM32的串口是相当丰富的,功能也很强劲。最多可提供5路串口(Mini STM32使用的是STM32F103RBT6,具有3个串口),有分数波特率发生器、支持单线光通信和半双工单线通讯、支持LIN、智能卡协议和IrDA SIR ENDEC规范(仅串口3支持)、具有DMA等。

上一章对串口有过简单的介绍,接下来我们将从寄存器层面,告诉你如何设置串口,以达到我们最基本的通信功能。这一节,我们将实现利用串口1不停的打印一个信息到电脑上,同时接收从串口发过来的数据,把发送过来的数据直接送回给电脑。

串口最基本的设置,就是波特率的设置。STM32的串口使用起来还是蛮简单的,只要你开启了串口时钟,并设置相应IO口的模式,然后配置一下波特率,数据位长度,奇偶校验位等信息,就可以使用了。下面,我们就简单介绍下这几个与串口基本配置直接相关的寄存器。

1,串口时钟使能。串口作为STM32的一个外设,其时钟由外设时钟使能寄存器控制,这里我们使用的串口1是在APB2ENR寄存器的第14位。APB2ENR寄存器在之前已经介绍过了,这里不再介绍。只是说明一点,就是除了串口1的时钟使能在APB2ENR寄存器,其他串口的时钟使能位都在APB1ENR

2,串口复位。当外设出现异常的时候可以通过复位寄存器里面的对应位设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。一般在系统刚开始配置外设的时候,都会先执行复位该外设的操作。串口1的复位是通过配置APB2RSTR寄存器的第14位来实现的。APB2RSTR寄存器的各位描述如下:


                      3.3.1.1寄存器APB2RSTR各位描述

从上图可知串口1的复位设置位在APB2RSTR的第14位。通过向该位写1复位串口1,写0结束复位。其他串口的复位位在APB1RSTR里面。

3,串口波特率设置。每个串口都有一个自己独立的波特率寄存器USART_BRR,通过设置该寄存器达到配置不同波特率的目的。该寄存器的各位描述如下:


                     3.3.1.2寄存器USART_BRR各位描述

前面提到STM32的分数波特率概念,其实就是在这个寄存器里面体现的。最低4位用来存放小数部分DIV_Fraction[15:4]12位用来存放整数部分DIV_Mantissa。高16位未使用。这里波特率的计算通过如下公式计算:

这里的x=12)是给外设的时钟(PCLK1用于串口2345PCLK2用于串口1),USARTDIV是一个无符号的定点数,它的值可以有串口的BRR寄存器值得到。而我们更关心的是如何从USARTDIV的值得到USART_BRR的值,因为一般我们知道的是波特率,和PCLKx的时钟,要求的就是USART_BRR的值。

下面我们来介绍如何通过USARTDIV得到串口USART_BRR寄存器的值,假设我们的串口1要设置为9600的波特率,而PCLK2的时钟为72M。这样,我们根据上面的公式有:

                                          USARTDIV=72000000/9600*16=468.75

那么得到:

DIV_Fraction=16*0.75=12=0X0C;

DIV_Mantissa= 468=0X1D4;

这样,我们就得到了USART1->BRR的值为0X1D4C。只要设置串口1BRR寄存器值为0X1D4C就可以得到9600的波特率。

4,串口控制。STM32的每个串口都有3个控制寄存器USART_CR1~3,串口的很多配置都是通过这3个寄存器来设置的。这里我们只要用到USART_CR1就可以实现我们的功能了,该寄存器的描述在《STM32参考手册》第496也有详细介绍,在这里我们就不列出来了。

5,数据发送与接收。STM32的发送与接收是通过数据寄存器USART_DR来实现的,这是一个双寄存器,包含了TDRRDR。当向该寄存器写数据的时候,串口就会自动发送,当收到收据的时候,也是存在该寄存器内。该寄存器的各位描述如下:

                    3.3.1.3寄存器USART_DR各位描述

可以看出,虽然是一个32位寄存器,但是只用了低9位(DR[8:0]),其他都是保留。

DR[8:0]为串口数据,包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给发送用(TDR),一个给接收用(RDR),该寄存器兼具读和写的功能。TDR寄存器提供了内部总线和输出移位寄存器之间的并行接口。RDR寄存器提供了输入移位寄存器和内部总线之间的并行接口。

当使能校验位(USART_CR1PCE位被置位)进行发送时,写到MSB的值(根据数据的长度不同,MSB是第7位或者第8)会被后来的校验位该取代。

当使能校验位进行接收时,读到的MSB位是接收到的校验位。

6,串口状态。串口的状态可以通过状态寄存器USART_SR读取。USART_SR的各位描述如下:


                    3.3.1.4寄存器USART_SR各位描述

这里我们关注一下两个位,第56RXNETC

RXNE(读数据寄存器非空),当该位被置1的时候,就是提示已经有数据被接收到了,并且可以读出来了。这时候我们要做的就是尽快去读取USART_DR,通过读USART_DR可以将该位清零,也可以向该位写0,直接清除。

TC(发送完成),当该位被职位的时候,表示USART_DR内的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式:1)读USART_SR,写USART_DR2)直接向该位写0

通过以上一些寄存器的操作外加一下IO口的配置,我们就可以达到串口最基本的配置了,关于串口更详细的介绍,请参考《STM32参考手册》第472页至502页,通用同步异步收发器一章。

 

3.3.2 硬件设计

 

该实验的硬件配置不同于前两个实验,串口1USB串口默认是分开的,并没有在PCB上连接在一起,需要通过跳线帽来连接一下。这里我们把P4RXDTXD用跳线帽与P3PA9PA10连接起来。如下图所示:

                                           3.3.2.1硬件连接图

连接上这里之后,我们在硬件上就设置完成了,可以开始软件设计了。

 

3.3.3 软件设计

 

这里的代码设计,比前两节简单很多,因为我们的串口初始化代码和接收代码就是用我们之前介绍的SYSTEM文件夹下的串口部分内容。这里我们对代码部分稍作讲解。

打开3.2节的TEST工程,然后在SYSTEM组下双击usart.c,我们就可以看到该文件里面的代码,先介绍uart_init函数,该函数代码如下:

//初始化IO 串口1

//pclk2CLK2时钟频率(Mhz)

//bound:波特率

void uart_init(u32 pclk2u32 bound)

{   

     float temp;

     u16 mantissa;

     u16 fraction;        

     temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV

     mantissa=temp;                                      //得到整数部分

     fraction=(temp-mantissa)*16; //得到小数部分     

   mantissa<<=4;

     mantissa+=fraction;

     RCC->APB2ENR|=1<<2;   //使能PORTA口时钟 

     RCC->APB2ENR|=1<<14;  //使能串口时钟

     GPIOA->CRH=0X444444B4;//IO状态设置

                  

     RCC->APB2RSTR|=1<<14;   //复位串口1

     RCC->APB2RSTR&=~(1<<14);//停止复位                        

     //波特率设置

     USART1->BRR=mantissa; // 波特率设置

     USART1->CR1|=0X200C; //1位停止,无校验位.

#ifdef EN_USART1_RX                    //如果使能了接收

     //使能接收中断

     USART1->CR1|=1<<8;    //PE中断使能

     USART1->CR1|=1<<5;    //接收缓冲区非空中断使能                 

     MY_NVIC_Init(33USART1_IRQChannel2);//2,最低优先级

#endif

}

从该代码可以看出,其初始化串口的过程,和我们前面介绍的一致先计算得到USART1->BRR的内容。然后开始初始化串口引脚,接着把USART1复位,然之后设置波特率和奇偶校验等。

这里需要注意一点,因为我们使用到了串口的中断接收,必须在usart.h里面定义 EN_USART1_RX 。该函数才会配置中断使能,以及开启串口1NVIC中断。这里我们把串口1中断放在组2,优先级设置为组2里面的最低。

再介绍一下串口1的中断服务函数USART1_IRQHandler,该函数的名字不能自己定义了,MDK已经给每个中断都分配了一个固定的函数名,我们直接用就可以了。具体这些函数的名字是什么,我们可以在MDK提供的例子里面,找到stm32f10x_it.c,该文件里面包含了STM32所有的中断服务函数。USART1_IRQHandler的代码如下:

void USART1_IRQHandler(void)

{

     u8 res;      

     if(USART1->SR&(1<<5))//接收到数据

     {         

                 res=USART1->DR;

                 if((USART_RX_STA&0x80)==0)//接收未完成

                 {

                             if(USART_RX_STA&0x40)//接收到了0x0d

                             {

                                         if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始

                                         else USART_RX_STA|=0x80;      //接收完成了

                             }else //还没收到0X0D

                             {         

                                         if(res==0x0d)USART_RX_STA|=0x40;

                                         else

                                         {

                                                     USART_RX_BUF[USART_RX_STA&0X3F]=res;

                                                     USART_RX_STA++;

                                                     if(USART_RX_STA>63)USART_RX_STA=0;//接收数据错误,重新开始接收        

                                         }                     

                             }

                 }                                                                                                                                       

     }                                                                                                                                   

}

该函数的重点就是判断接收是否完成,通过检测是否收到0X0D0X0A的连续2个字节(回车键)来检测是否结束。当检测到这个结束序列之后,就会置位USART_RX_STA的最高为来标记已经收到了一次数据。之后等待外部函数清空该位之后才开始第二次接收。所接收的数据全部存放在USART_RX_BUF里面,一次接收数据不能超过64个字节,否则被丢弃。

介绍完了这两个函数,我们回到test.c,在test.c里面编写如下代码:

#include <stm32f10x_lib.h>

#include "sys.h"

#include "usart.h"               

#include "delay.h"  

#include "led.h"

#include "key.h"                  

//Mini STM32开发板范例代码3

//串口实验

//正点原子@ALIENTEK

//2010.5.28  

int main(void)

{                          

     u8 t;

     u8 len; 

     u16 times=0; 

     Stm32_Clock_Init(9); //系统时钟设置

     delay_init(72);        //延时初始化

     uart_init(729600);  //串口初始化为9600

     LED_Init();                                //初始化与LED连接的硬件接口   

     while(1)

     {

                 if(USART_RX_STA&0x80)

                 {                                                            

                             len=USART_RX_STA&0x3f;//得到此次接收到的数据长度

                             printf("\n您发送的消息为:\n");

                             for(t=0;t<len;t++)

                             {

                                         USART1->DR=USART_RX_BUF[t];

                                         while((USART1->SR&0X40)==0);//等待发送结束

                             }

                             printf("\n\n");//插入换行

                             USART_RX_STA=0;

                 }else

                 {

                             times++;

                             if(times%5000==0)

                             {

                                         printf("\nMiniSTM32开发板 串口实验\n");

                                         printf("正点原子@ALIENTEK\n\n\n");

                             }

                             if(times%200==0)printf("请输入数据,以回车键结束\n"); 

                             if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.

                             delay_ms(10);  

                 }

     }         

}

这段代码比较简单,重点看下以下两句:

USART1->DR=USART_RX_BUF[t];           

while((USART1->SR&0X40)==0);//等待发送结束

第一句,其实就是发送一个字节到串口,通过直接操作寄存器来实现的。第二句呢,就是我们在写了一个字节在USART1->DR之后,要检测这个数据是否已经被发送完成了,通过检测USART1->SR的第6位,是否为1来决定是否可以开始第二个字节的发送。

其他的代码比较简单,我们执行编译之后看看有没有错误,没有错误就可以开始仿真与调试了。整个工程的编译结果如下:

                          3.3.3.1编译结果

可以看到,这里我们的代码比上一节的多了800多字节,其实这里主要是使用了printf函数的缘故,所以如果有时候你程序太大了,可以通过不使用printf函数,来给其他代码腾出一部分空间。

3.3.4 仿真与下载

 

前面2接已经重点介绍了仿真,仿真的基本技巧也差不多介绍完了,接下来我们将淡化这部分,因为代码都是经过作者实际检测,并且在开发板上验证了的,有兴趣的大家可以自己仿真看看。但是这里要说明几点:

对于串口仿真,MDK3.80的仿真串口窗口的显示貌似有BUG,因为实际硬件上是没问题,但是仿真的时候很少有机会能打印出来(有时候又可以有时候不行,希望MDK的后续版本能得到改正)。

IO口复用的,信号在逻辑分析窗口是不能显示出来的,这一点也请大家注意。比如串口的输出,SPIUSBCAN等。你在仿真的时候在该窗口看不到任何信息。遇到这样的情况,你就不得不准备一个逻辑分析仪,外加一个ULINK或者JTAG来做在线调试。但一般情况,这些都是有现成的例子,不用这几个东西一般也能编出来。

仿真并不能代表实际情况。只能从某些方面给你一些启示,告诉你大方向,不能尽信仿真,当然也不能完全没有仿真。比如上面IO口的输出,仿真的时候,其翻转速度可以达到很快,但是实际上STM32IO输出就达不到这个速度。

总之,我们要合理的利用仿真,也不能过于依赖仿真。当仿真解决不了了,可以试试在线调试,在线调试一般都可以知道问题在哪个地方,但是问题要怎么解决还是得各位自己动脑筋、找资料了。

我们把代码下载到MiniSTM32开发板,可以看到板子上的DS0开始闪烁,说明程序已经在跑了。接着我们打开串口调试助手,看到如下信息:

                       3.3.4.1串口调试助手收到的信息

证明串口数据发送没问题。接着,我们在发送区输入上面的文字,输入完后按回车键。然后单击发送,可以得到如下结果:

                          3.3.4.2发送数据后收到的数据

可以看到,我们发送的消息被发送回来了(图中红圈内)。各位可以试试,如果不输入回车键,直接按发送是什么结果。

ALIENTEK MINISTM32 实验3 串口实验.rar

645.61 KB, 下载次数: 3811

串口实验.pdf

394.07 KB, 下载次数: 1452

希望openedv能给大家提供一个友好的技术交流平台!
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

1

主题

7

帖子

0

精华

初级会员

Rank: 2

积分
74
金钱
74
注册时间
2011-4-10
在线时间
17 小时
发表于 2011-5-6 17:50:10 | 显示全部楼层
你好,这个串口实验的回车键结束符是怎么判断的,现在我不想用结束符,可以怎么修改谢谢
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2011-5-6 18:16:59 | 显示全部楼层
回车符=0X0D+0X0A.
不想用结束符,可以用超时法.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

1

主题

7

帖子

0

精华

初级会员

Rank: 2

积分
74
金钱
74
注册时间
2011-4-10
在线时间
17 小时
发表于 2011-5-7 09:35:58 | 显示全部楼层
谢谢原子,我现在改写串口实验的程序,让串口调试助手发过来的任意字符(任意长度)在开发板的屏幕上显示(既然带了屏幕就用一下呗)
回复 支持 反对

使用道具 举报

1

主题

7

帖子

0

精华

初级会员

Rank: 2

积分
74
金钱
74
注册时间
2011-4-10
在线时间
17 小时
发表于 2011-5-7 10:31:26 | 显示全部楼层
回复【3楼】正点原子:
-------------------------------
原子你好,又要问你一下,汉字显示中,text.c中Show_Str函数是否只能显示确定的汉字,也就是说是否只能以这种格式
Show_Str(60,130,"按KEY0,更新字库",16,0);调用函数,好像不能显示一个变量
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2011-5-7 12:11:33 | 显示全部楼层
回复【5楼】zhzp:
-------------------------------
变量不就是数字了?那你用现实数字的那个函数即可.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

3

主题

4

帖子

0

精华

初级会员

Rank: 2

积分
50
金钱
50
注册时间
2011-1-21
在线时间
5 小时
发表于 2011-6-20 19:17:25 | 显示全部楼层
谢谢了 学习了!
回复 支持 反对

使用道具 举报

1

主题

4

帖子

0

精华

新手入门

积分
28
金钱
28
注册时间
2012-4-19
在线时间
0 小时
发表于 2012-4-19 14:47:33 | 显示全部楼层
回复【3楼】正点原子:
---------------------------------
原子哥您好!若用串口2实现呢?帮忙看下:http://www.openedv.com/posts/list/4876.htm
回复 支持 反对

使用道具 举报

2

主题

4

帖子

0

精华

新手上路

积分
34
金钱
34
注册时间
2012-4-1
在线时间
0 小时
发表于 2012-10-12 16:38:53 | 显示全部楼层
原子哥,怎样实现串口2的LIN模式?
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2012-10-12 17:06:27 | 显示全部楼层
回复【9楼】zx4428:
---------------------------------
没整过。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

22

主题

436

帖子

0

精华

高级会员

Rank: 4

积分
541
金钱
541
注册时间
2012-8-6
在线时间
2 小时
发表于 2012-10-12 17:26:04 | 显示全部楼层
肿么可以这样
往事随风!
回复 支持 反对

使用道具 举报

1

主题

12

帖子

0

精华

新手上路

积分
37
金钱
37
注册时间
2013-10-5
在线时间
0 小时
发表于 2014-1-28 14:26:59 | 显示全部楼层
回复【10楼】正点原子:
---------------------------------
fraction=(temp-mantissa)*16; //得到小数部分 
请问原子哥这个*16是什么意思?
回复 支持 反对

使用道具 举报

11

主题

71

帖子

0

精华

初级会员

Rank: 2

积分
135
金钱
135
注册时间
2014-1-3
在线时间
0 小时
发表于 2014-2-14 17:42:42 | 显示全部楼层
void USART1_IRQHandler(void)              
{
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
Res =USART_ReceiveData(USART1);//(USART1->DR);

if((USART_RX_STA&0x8000)==0)
{
if(USART_RX_STA&0x4000)
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了   最高位置1 
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;//这句是干嘛?
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;//这句
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   
}  

}     
     } 


原子哥,关于这段程序,我有4个问题:
1  USART_RX_STA这个你定义的寄存器是怎样和接收到的数据联系起来的,接收到的数存在Res中(Res =USART_ReceiveData(USART1);),
USART_RX_STA是怎么知道接收到的最后两位为0x0d和0x0a的?并且低14位怎么知道是接收到的位数的?
2  既然Res(Res =USART_ReceiveData(USART1);)表示接收到的数,可这两句话句话if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始    if(Res==0x0d)    怎么能用Res来判断呢?
3  if(Res==0x0d) USART_RX_STA|=0x4000;给 USART_RX_STA重新赋值,这个有什么作用呢?
4 USART_RX_BUF[USART_RX_STA&0X3FFF]=Res 这句话也不太懂

这么多问题,麻烦原子哥了
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2014-2-14 22:34:09 | 显示全部楼层
回复【13楼】jidian0177:
---------------------------------
1,在串口中断服务函数里面设置的.
2,res就是收到的数据啊,就是判断收到的数据.
3,判断是不是收到0X0D.给USART_RX_STA的第6位赋值1,不是重新赋值!
4,还是根据USART_RX_STA的定义来的.

如果你还不懂,自己写一个出来
就懂了.
完全别看我的代码了,但是要实现我的功能.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

0

主题

22

帖子

0

精华

新手上路

积分
44
金钱
44
注册时间
2013-11-11
在线时间
0 小时
发表于 2014-9-12 17:35:54 | 显示全部楼层
请问原子哥,串口1使能时候,能够正常的收发数据了,那其他几个和串口1相关的口,比如USART1_CTS,USART1_RTS,等等还可以用作普通IO吗?比如初始化了串口1,我又写一段IO初始化代码,把其他几个引脚初始化为GPIO,可以吗?
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2014-9-12 22:40:53 | 显示全部楼层
回复【15楼】木君之上:
---------------------------------
可以的,你设置为推挽输出就可以了。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-7-1 13:25

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表