一 通讯基本概念;
1 串行通讯与并行通讯:
按数据传送的方式,通讯可分为串行通讯与并行通讯,串行通讯是指设备之间通过少
量数据信号线
(
一般是
8
根以下
)
,地线以及控制信号线,按数据位形式一位一位地传输数
据的通讯方式。而并行通讯一般是指使用
8
、
16
、
32
及
64
根或更多的数据线进行传输的通
讯方式,它们的通讯传输对比说明见图
20-1
,并行通讯就像多个车道的公路,可以同时传
输多个数据位的数据,而串行通讯,而串行通讯就像单个车道的公路,同一时刻只能传输
一个数据位的数据。
2 全双工、半双工及单工通讯
3 同步通讯与异步通讯:不是同时收发而是根据相同的时钟同步采集数据线的数据
根据通讯的数据同步方式,又分为同步和异步两种,可以根据通讯过程中是否有使用
到时钟信号进行简单的区分。
在同步通讯中,收发设备双方会使用一根信号线表示时钟信号,在时钟信号的驱动下
双方进行协调,同步数据。通讯中通常双方会统一规定在时钟信号的上升沿或
下降沿对数据线进行采样
在异步通讯中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用
的信号位,或者把主体数据进行打包,以数据帧的格式传输数据
,某些通讯中
还需要双方约定数据的传输速率,以便更好地同步
在同步通讯中,数据信号所传输的内容绝大部分就是有效数据,而异步通讯中会包含
有帧的各种标识符,所以同步通讯的效率更高,但是同步通讯双方的时钟允许误差较小,
而异步通讯双方的时钟允许误差较大
4 通讯速率
衡量通讯性能的一个非常重要的参数就是通讯速率,通常以比特率
(Bitrate)
来表示,即
每秒钟传输的二进制位数,单位为比特每秒
(bit/s)
。容易与比特率混淆的概念是“波特率”
(Baudrate)
,它表示每秒钟传输了多少个码元。而码元是通讯信号调制的概念,通讯中常用
时间间隔相同的符号来表示一个二进制数字,这样的信号称为码元。如常见的通讯传输中,
用
0V
表示数字
0
,
5V
表示数字
1
,那么一个码元可以表示两种状态
0
和
1
,所以一个码元
等于一个二进制比特位,此时波特率的大小与比特率一致;如果在通讯传输中,有
0V
、
2V
、
4V
以及
6V
分别表示二进制数
00
、
01
、
10
、
11
,那么每个码元可以表示四种状态,
即两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率
的一半。因为很多常见的通讯中一个码元都是表示两种状态,人们常常直接以波特率来表
示比特率,虽然严格来说没什么错误,但希望您能了解它们的区别
二 串口通信简介
1.串口通信物理层
a 电平标准
根据通讯使用的电平标准不同,串口通讯可分为
TTL
标准及
RS-232
标准
我们知道常见的电子电路中常使用
TTL
的电平标准,理想状态下,使用
5V
表示二进
制逻辑
1
,使用
0V
表示逻辑
0
;而为了增加串口通讯的远距离传输及抗干扰能力,它使用
-
15V
表示逻辑
1
,
+15V
表示逻辑 0。
在上面的通讯方式中,两个通讯设备的“
DB9
接口”之间通过串口信号线建立起连接,
串口信号线中使用“
RS-232
标准”传输数据信号。由于
RS-232
电平标准的信号不能直接
被控制器直接识别,所以这些信号会经过一个“电平转换芯片MA3232”转换成控制器能识别的
“
TTL
标准”的电平信号,才能实现通讯。
b
RS-232
信号线
在旧式的台式计算机中一般会有
RS-232
标准的
COM
口
(
也称
DB9
接口),
其中接线口以针式引出信号线的称为公头,以孔式引出信号线的称为母头。在计算机
中一般引出公头接口,而在调制调解器设备中引出的一般为母头,使用上图中的串口线即
可把它与计算机连接起来。通讯时,串口线中传输的信号就是使用前面讲解的
RS-232
标准
调制的。
上表中的是计算机端的
DB9
公头标准接法,由于两个通讯设备之间的收发信号
(RXD
与
TXD)
应交叉相连,所以调制调解器端的
DB9
母头的收发信号接法一般与公头的相反,
两个设备之间连接时,只要使用“直通型”的串口线连接起来即可,见图
在目前的其它工业控制使用的串口通讯中,一般只使用
RXD
、
TXD
以及
GND
三条信
号线,直接传输数据信号,而
RTS
、
CTS
、
DSR
、
DTR
及
DCD
信号都被裁剪掉了。
c usb转串口通信
2 通信接口协议层面
串口通讯的数据包由发送设备通过自身的
TXD
接口传输到接收设备的
RXD
接口。在
串口通讯的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位
组成,通讯双方的数据包格式要约定一致才能正常收发数据。
通讯的起始和停止信号
串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一
个逻辑
0
的数据位表示,而数据包的停止信号可由
0.5
、
1
、
1.5
或
2
个逻辑
1
的数据位表示,
只要双方约定一致即可。
有效数据
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效
数据的长度常被约定为
5
、
6
、
7
或
8
位长。
数据校验
在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰
导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验
(odd)
、偶校验
(even)
、
0
校验
(space)
、
1
校验
(mark)
以及无校验
(noparity)
。
奇校验要求有效数据和校验位中“
1
”的个数为奇数,比如一个
8
位长的有效数据为:
01101001
,此时总共有
4
个“
1
”,为达到奇校验效果,校验位为“
1
”,最后传输的数据
将是
8
位的有效数据加上
1
位的校验位总共
9
位。
偶校验与奇校验要求刚好相反,要求帧数据和校验位中“
1
”的个数为偶数,比如数据
帧:
11001010
,此时数据帧“
1
”的个数为
4
个,所以偶校验位为“
0
”。
0
校验是不管有效数据中的内容是什么,校验位总为“
0
”,
1
校验是校验位总为“
1
三 STM32
的
USART
1.功能框图
有别于
USART
还有一个
UART(Universal Asynchronous Receiver and Transmitter)
,它是在
USART
基础上裁剪掉了同
步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输
出,我们平时用的串口通信基本都是
UART
。
.
①功能引脚
TX
:发送数据输出引脚。
RX
:接收数据输入引脚。
SW_RX
:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引
脚。
nRTS
:请求以发送
(Request To Send)
,
n
表示低电平有效。如果使能
RTS
流控制,当
USART
接收器准备好接收新数据时就会将
nRTS
变成低电平;当接收寄存器已满时,
nRTS
将被设置为高电平。该引脚只适用于硬件流控制。
nCTS
:清除以发送
(Clear To Send)
,
n
表示低电平有效。如果使能
CTS
流控制,发送
器在发送下一帧数据之前会检测
nCTS
引脚,如果为低电平,表示可以发送数据,如果为
高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。
SCLK
:发送器时钟输出引脚。这个引脚仅适用于同步模式。
USART
引脚在
STM32F103ZET6
芯片具体分布见表
②数据寄存器
USART
数据寄存器
(USART_DR)
只有低
9
位有效,并且第
9
位数据是否有效要取决于
USART
控制寄存器
1(USART_CR1)
的
M
位设置,当
M
位为
0
时表示
8
位数据字长,当
M
位为
1
表示
9
位数据字长,我们一般使用
8
位数据字长。
USART_DR
包含了已发送的数据或者接收到的数据。
USART_DR
实际是包含了两个
寄存器,一个专门用于发送的可写
TDR
,一个专门用于接收的可读
RDR
。当进行发送操
作时,往
USART_DR
写入数据会自动存储在
TDR
内;当进行读取操作时,向
USART_DR
读取数据会自动提取
RDR
数据。
TDR
和
RDR
都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,
发送时把
TDR
内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收
时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到
RDR
。
USART
支持
DMA
传输,可以实现高速数据传输,具体
DMA
使用将在
DMA
章节讲
解。
③控制器
USART
有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。
使用
USART
之前需要向
USART_CR1
寄存器的
UE
位置
1
使能
USART
,
UE
位用来开启
供给给串口的时钟。发送或者接收数据字长可选
8
位或
9
位,由
USART_CR1
的
M
位控制。
发送器
当
USART_CR1
寄存器的发送使能位
TE
置
1
时,启动数据发送,发送移位寄存器的
数据会在
TX
引脚输出,低位在前,高位在后。如果是同步模式
SCLK 也输出时钟信号。
一个字符帧发送需要三个部分:起始位
+
数据帧
+
停止位。起始位是一个位周期的低电
平,位周期就是每一位占用的时间;数据帧就是我们要发送的
8
位或
9
位数据,数据是从
最低位开始传输的;停止位是一定时间周期的高电平。
停止位时间长短是可以通过
USART
控制寄存器
2(USART_CR2)
的
STOP[1:0]
位控制,
可选
0.5
个、
1
个、
1.5
个和
2
个停止位。默认使用
1
个停止位。
2
个停止位适用于正常
USART
模式、单线模式和调制解调器模式。
0.5
个和
1.5
个停止位用于智能卡模式。
当选择
8
位字长,使用 1 个停止位时,具体发送字符时序图见图
.
④小数波特率生成
波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示,
单位为波特。比特率指单位时间内传输的比特数,单位
bit/s(bps)
。对于
USART
波特率与
比特率相等,以后不区分这两个概念。波特率越大,传输速率越快。
USART 的发送器和接收器使用相同的波特率。计算公式如下:
其中,
f
PLCK
为
USART
时钟,
USARTDIV
是一个存放在波特率寄存器
(USART_BRR)
的 一个 无符 号定 点数。 其中
DIV_Mantissa[11:0]
位 定义
USARTDIV
的 整数 部分 ,
DIV_Fraction[3:0]
位定义
USARTDIV
的小数部分。
例如:
DIV_Mantissa=24(0x18)
,
DIV_Fraction=10(0x0A)
,此时
USART_BRR
值为
0x18A
;那么
USARTDIV
的小数位
10/16=0.625
;整数位
24
,最终
USARTDIV
的值为
24.625
。
如果知道
USARTDIV
值为
27.68
,那么
DIV_Fraction=16*0.68=10.88
,最接近的正整数
为
11
,所以
DIV_Fraction[3:0]
为
0xB
;
DIV_Mantissa=
整数
(27.68)=27
,即为
0x1B
。
波特率的常用值有
2400
、
9600
、
19200
、
115200
。下面以实例讲解如何设定寄存器值
得到波特率的值。
我们知道
USART1
使用
APB2
总线时钟,最高可达
72MHz
,其他
USART
的最高频率
为
36MHz
。我们选取
USART1
作为实例讲解,即
f
PLCK
=72MHz
。为得到
115200bps
的波特
率,此时:
校验控制
STM32F103
系列控制器
USART
支持奇偶校验。当使用校验位时,串口传输的长度将
是
8
位的数据帧加上
1
位的校验位总共
9
位,此时
USART_CR1
寄存器的
M
位需要设置为
1
,即
9
数据位。将
USART_CR1
寄存器的
PCE
位置
1
就可以启动奇偶校验控制,奇偶校
验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收
数据时自动验证校验位。接收数据时如果出现奇偶校验位验证失败,会见
USART_SR
寄存
器的
PE
位置 1,并可以产生奇偶校验中断
2. USART 初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体,比如
USART_InitTypeDef
,结构
体成员用于设置外设工作参数,并由外设初始化配置函数,比如
USART_Init()
调用,这些
设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。
初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个
成员意义基本上就可以对该外设运用自如了。初始化结构体定义在
stm32f10x_usart.h
文件
中,初始化库函数定义在
stm32f10x_usart.c
文件中,编程时我们可以结合这两个文件内注
释使用。
USART
初始化结构体
1 typedef
struct
{
2
uint32_t
USART_BaudRate;
//
波特率
3
uint16_t
USART_WordLength;
//
字长
4
uint16_t
USART_StopBits;
//
停止位
5
uint16_t
USART_Parity;
//
校验位
6
uint16_t
USART_Mode;
// USART
模式
7
uint16_t
USART_HardwareFlowControl;
//
硬件流控制
8
} USART_InitTypeDef;
5) USART_BaudRate
:波特率设置。一般设置为
2400
、
9600
、
19200
、
115200
。标准
库函数会根据设定值计算得到
USARTDIV
值,从而设置
USART_BRR
寄存器值。
6) USART_WordLength
:数据帧字长,可选
8
位或
9
位。它设定
USART_CR1
寄存
器的
M
位的值。如果没有使能奇偶校验控制,一般使用
8
数据位;如果使能了奇
偶校验则一般设置为
9
数据位。
7) USART_StopBits
:停止位设置,可选
0.5
个、
1
个、
1.5
个和
2
个停止位,它设定
USART_CR2
寄存器的
STOP[1:0]
位的值,一般我们选择
1
个停止位。
8) USART_Parity
: 奇 偶 校 验 控 制 选 择 , 可 选
USART_Parity_No(
无校验
)
、
USART_Parity_Even(
偶校验
)
以 及
USART_Parity_Odd(
奇 校 验
)
, 它 设 定
USART_CR1
寄存器的
PCE
位和
PS
位的值。
9) USART_Mode
:
USART
模式选择,有
USART_Mode_Rx
和
USART_Mode_Tx
,
允许使用逻辑或运算选择两个,它设定
USART_CR1
寄存器的
RE
位和
TE
位。
10) USART_HardwareFlowControl
:硬件流控制选择,只有在硬件流控制模式才有效,
可选有⑴使能
RTS
、⑵使能
CTS
、⑶同时使能
RTS
和
CTS
、⑷不使能硬件流。
当使用同步模式时需要配置
SCLK
引脚输出脉冲的属性,标准库使用一个时钟初始化
结构体
USART_ClockInitTypeDef
来设置,该结构体内容也只有在同步模式才需要设置。
USART
时钟初始化结构体
1 typedef
struct
{
2
uint16_t
USART_Clock;
//
时钟使能控制
3
uint16_t
USART_CPOL;
//
时钟极性
4
uint16_t
USART_CPHA;
//
时钟相位
5
uint16_t
USART_LastBit;
//
最尾位时钟脉冲
6
} USART_ClockInitTypeDef;
1) USART_Clock
:同步模式下
SCLK
引脚上时钟输出使能控制,可选禁止时钟输出
(USART_Clock_Disable)
或开启时钟输出
(USART_Clock_Enable)
;如果使用同步模
式发送,一般都需要开启时钟。它设定
USART_CR2
寄存器的
CLKEN
位的值。
2) USART_CPOL
:同步模式下
SCLK
引脚上输出时钟极性设置,可设置在空闲时
SCLK
引脚为低电平
(USART_CPOL_Low)
或高电平
(USART_CPOL_High)
。它设
定
USART_CR2
寄存器的
CPOL
位的值。
3) USART_CPHA
:同步模式下
SCLK
引脚上输出时钟相位设置,可设置在时钟第一
个变化沿捕获数据
(USART_CPHA_1Edge)
或在时钟第二个变化沿捕获数据。它设
定
USART_CR2
寄存器的
CPHA
位的值。
USART_CPHA
与
USART_CPOL
配合
使用可以获得多种模式时钟关系。
4) USART_LastBit
:选择在发送最后一个数据位的时候时钟脉冲是否在
SCLK
引脚
输 出 , 可 以 是 不 输 出 脉 冲
(USART_LastBit_Disable)
、 输 出 脉 冲
(USART_LastBit_Enable)
。它设定
USART_CR2
寄存器的
LBCL
位的值。
四 USART1 接发通信实验
USART
只需两根信号线即可完成双向通信,对硬件要求低,使得很多模块都预留
USART
接口来实现与其他模块或者控制器进行数据传输,比如
GSM
模块,
WIFI
模块、蓝
牙模块等等。在硬件设计时,注意还需要一根“共地线”。
我们经常使用
USART
来实现控制器与电脑之间的数据传输。这使得我们调试程序非
常方便,比如我们可以把一些变量的值、函数的返回值、寄存器标志位等等通过
USART
发送到串口调试助手,这样我们可以非常清楚程序的运行状态,当我们正式发布程序时再
把这些调试信息去除即可。
我们不仅仅可以将数据发送到串口调试助手,我们还可以在串口调试助手发送数据给
控制器,控制器程序根据接收到的数据进行下一步工作。
首先,我们来编写一个程序实现开发板与电脑通信,在开发板上电时通过
USART
发
送一串字符串给电脑,然后开发板进入中断接收等待状态,如果电脑有发送数据过来,开
发板就会产生中断,我们在中断服务函数接收数据,并马上把数据返回发送给电脑。
编程要点
5)
使能
RX
和
TX
引脚
GPIO
时钟和
USART
时钟;
6)
初始化
GPIO
,并将
GPIO
复用到
USART
上;
7)
配置
USART
参数;
8)
配置中断控制器并使能
USART
接收中断;
9)
使能
USART
;
10)
在
USART
接收中断服务函数实现数据接收和发送。
#ifndef __USART_H//C文件需要add,头文件在软件设置好路径就不要自己add
#define __USART_H
#include "stm32f10x.h"
#include <stdio.h>
// 串口1-USART1
#define DEBUG_USARTx USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler
void USART_Config(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch);
#endif
#include "usart.h"
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);/* 嵌套向量中断控制器组选择:随便来 */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; /* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;/* 抢断优先级*/
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; /* 子优先级 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; /* 使能中断大门 */
NVIC_Init(&NVIC_InitStructure); /* 初始化配置NVIC */
}
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//1 GPIO初始化配置与时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 打开串口GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);// 打开串口外设的时钟
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式:外部电平状态决定你这个引脚什么电平状态
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
//2 配置串口初始化结构体:要与电脑上位机软件配置一样
USART_InitStructure.USART_BaudRate = 115200;// 配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 配置 针数据字长
USART_InitStructure.USART_StopBits = USART_StopBits_1;// 配置停止位
USART_InitStructure.USART_Parity = USART_Parity_No ;// 配置校验位为无
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 配置硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 配置工作模式,收发一起
USART_Init( USART1, &USART_InitStructure);// 完成串口的初始化配置
//3 串口中断配置:在中断服务函数把接收到数据发送给电脑
NVIC_Configuration();//接收数据寄存器非空标志 USART_IT_RXNE
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 使能串口接收完成中断:中断小门
//4 使能串口
USART_Cmd(USART1, ENABLE);
}
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)//发送一个字节
{
USART_SendData(pUSARTx,ch);/* 发送一个字节数据到USART */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); /* 等待发送数据寄存器为空 */
}
void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num)//发送8位的数组
{
uint8_t i;
for(i=0; i<num; i++)
{
Usart_SendByte(pUSARTx,array[i]); /* 发送一个字节数据到USART */
}
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);/* 等待发送完成 */
}
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)//发送字符串
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET); /* 等待发送完成 */
}
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)//发送一个16位数
{
uint8_t temp_h, temp_l;
temp_h = (ch&0XFF00)>>8;/* 取出高八位 */
temp_l = ch&0XFF;/* 取出低八位 */
USART_SendData(pUSARTx,temp_h);/* 发送高八位 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
USART_SendData(pUSARTx,temp_l); /* 发送低八位 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
USART_SendData(DEBUG_USARTx, (uint8_t) ch);/* 发送一个字节数据到串口 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET); /* 等待发送完毕 */
return (ch);
}
//重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);/* 等待串口输入数据 */
return (int)USART_ReceiveData(DEBUG_USARTx);
}
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "rcc.h"
#include "exti.h"
#include "systick.h"
#include "usart.h"
#include <stdio.h>
/*如果想要验证其他串口的功能功能,修改相关宏定义即可,实验以串口1为例
单片机发送数据到电脑上位机显示:几个发送函数完成,发送数据均是8位一字节为单位
或者利用单片机发送print的数据,让单片机串口重定向输出到上位机
单片机接收数据到电脑上位机显示:单片机接收到数据后通过中断函数将接收的数据发送给电脑上位机
里面通过中断服务函数完成。
*/
int main(void)
{
USART_Config();
//Usart_SendByte(USART1,'a');发送8位字符
//Usart_SendHalfWord(USART1, 0xffff);发送16位数
Usart_SendString(USART1, "abcdefg\n");//发送字符串
printf("用串口重定向输出到上位机\n");
while (1){
}
}
五 USART1 指令控制 灯实验
.
编程要点
1)
初始化配置
RGB
彩色灯
GPIO
;
2)
使能
RX
和
TX
引脚
GPIO
时钟和
USART
时钟;
3)
初始化
GPIO
,并将
GPIO
复用到
USART
上;
4)
配置
USART
参数;
5)
使能
USART
;
6)
获取指令输入,根据指令控制
RGB
彩色灯。
与上一个实验不同的是我们这里不使用接收中断,而是靠查询标志位的方式来实现接收。
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "rcc.h"
#include "exti.h"
#include "systick.h"
#include "usart.h"
#include <stdio.h>
/*
给单片机发送命令,单片机解析命令,做出一些控制LED反应
上位机发送一个字符1 LED0就反转一次
*/
int main(void)
{
uint8_t ch;
USART_Config();
LED0_GPIO_Config();
while (1){
ch = getchar();//上位机发送ch到单片机
printf("ch = %c\n",ch);
if (ch == '1')
LED0_TOGGLE;
}
}
#include "usart.h"
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);/* 嵌套向量中断控制器组选择:随便来 */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; /* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;/* 抢断优先级*/
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; /* 子优先级 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; /* 使能中断 */
NVIC_Init(&NVIC_InitStructure); /* 初始化配置NVIC */
}
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//1 GPIO初始化配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 打开串口GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);// 打开串口外设的时钟
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式:外部电平状态决定你这个引脚什么电平状态
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
//2 配置串口初始化结构体:要与电脑上位机软件配置一样
USART_InitStructure.USART_BaudRate = 115200;// 配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 配置 针数据字长
USART_InitStructure.USART_StopBits = USART_StopBits_1;// 配置停止位
USART_InitStructure.USART_Parity = USART_Parity_No ;// 配置校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 配置硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 配置工作模式,收发一起
USART_Init( USART1, &USART_InitStructure);// 完成串口的初始化配置
//3 串口中断优先级配置:在中断服务函数把接收到数据发送给电脑
// NVIC_Configuration();
//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 使能串口接收中断
//4 使能串口
USART_Cmd(USART1, ENABLE);
}
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)//发送一个字节
{
USART_SendData(pUSARTx,ch);/* 发送一个字节数据到USART */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); /* 等待发送数据寄存器为空 */
}
void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num)//发送8位的数组
{
uint8_t i;
for(i=0; i<num; i++)
{
Usart_SendByte(pUSARTx,array[i]); /* 发送一个字节数据到USART */
}
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);/* 等待发送完成 */
}
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)//发送字符串
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET); /* 等待发送完成 */
}
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)//发送一个16位数
{
uint8_t temp_h, temp_l;
temp_h = (ch&0XFF00)>>8;/* 取出高八位 */
temp_l = ch&0XFF;/* 取出低八位 */
USART_SendData(pUSARTx,temp_h);/* 发送高八位 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
USART_SendData(pUSARTx,temp_l); /* 发送低八位 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
USART_SendData(DEBUG_USARTx, (uint8_t) ch);/* 发送一个字节数据到串口 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET); /* 等待发送完毕 */
return (ch);
}
//重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);/* 等待串口输入数据 */
return (int)USART_ReceiveData(DEBUG_USARTx);
}