一、通信。
通信就是指微处理机与外设交换数据的过程。
1.1 通信基本方式:
方式一:并行通信(多车道,多窗口)。
数据的各数位同时传输。
方式二:串行通信(单车道,单窗口)。
数据按位一位位的传输。
串行通信种类:
种类一:单工。
只允许一方发给另一方。
种类二:半双工。(对讲机)
双方可以相互通信,但是不能同时发送、同时接收。
种类三:全双工。
双方可以同时相互通信。
1.2 串行通信的标准:
1.3 同步通信与异步通信。
同步通信需要相同频率的时钟,逐字符发送接收,发一个收一个,收一个发一个,不能有间隙。
异步通信可以任意间隙,接受端随时准备,发送端任意时刻发送 ,需要加停止位和开始位。
UART异步通信 全双工 2线 RX TX
I2C 同步通信 半双工 2线 SDA SCL
SPI 同步通信 全双工 3线或四线 MOSI MISO SCLK SS(片选)
二、UART 异步通信
2.1 异步通信协议:
异步通信协议需要定义5个方面的内容:
1、起始位。
2、数据位。
3、奇偶校验位。
4、停止位。
5、波特率(通信速度,因为通信双方没有时钟)。
2.2 异步通信应用场合:
-
芯片间的近距离通信。
-
与PC机的通信。
-
模块之间的远距离通信。
用较高的电压差表示逻辑“0”、“1”
2.3 stm32f10x USART框图、引脚
2.4 stm32f10x波特率的计算
设波特率位9600,根据公式
9600 = 7210001000/(16*U)
分频值U = 72 * 1000 * 1000/(9600 * 16) = 468.75
DIV_Mantissa = 468 = 0x1d4;
DIV_Fraction = 16 * 0.75 = 12 = 0x0C
2.5 相关寄存器(了解)
- USART_SR状态寄存器
位7 TXE:发送数据寄存器空 (Transmit data register empty)
当TDR寄存器中的数据被硬件转移到移位寄存器的时候,该位被硬件置位。 - USART_DR数据寄存器
- USART_BRR波特率寄存器
波特率的计算方法由此得出 - USART_CR1 控制寄存器
主要用来使能
2.6 USART库函数:
USART_init配置串口:
USART_Cmd使能串口:
USART_GetFlagStatus判断标志位:
RESET 和 SET 的值:
三、stm32串口编程
3.1 串口配置一般步骤
- 使能时钟
RCC_APB2PeriphClockCmd(); - 串口复位(不是必须的)
USART_DeInit(); - GPIO口初始化
GPIO_Init(GPIOx,&gpiost),GPIO模式设置为GIPO_Mode_AF_PP - 串口初始化
USART_Init(USARTx,&usartst); - 串口 开启
USART_Cmd(USART1,ENABLE); - 中断初始化
NVIC_Init(&nvicst); - 中断服务函数
USARTx_IRQHandler() - 接收,发送数据函数
3.2 串口发送
串口发送使用到来printf(),重定向。重定向链接usart1-printf()重定向
仿真代码
晶振调为8Mhz
usart1.h
#ifndef USART1_H
#define USART1_H
#include "stm32f10x.h"
#include <stdio.h>
void usart1_init(u32 baundRate);
void usart1_it_init(void);
#endif
usart1.c 寄存器代码
#include "usart1.h"
/*****************************************************************************************
* Function Name : fputc
* Descrption : 重定向这个C库(stdio) printf函数 文件流->串口USART1
* Input : ch ,*f
* Output : None
* Return : None
*****************************************************************************************/
int fputc(int ch,FILE *f)
{
//将ch送给USART1
USART_SendData(USART1,ch);
//等待发送完毕
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
//返回ch
return ch;
}
void usart1_gpio_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
}
void usart1_init(u32 baundRate)
{
usart1_gpio_init();
USART_InitTypeDef usartst;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
usartst.USART_BaudRate = baundRate;
usartst.USART_WordLength = USART_WordLength_9b;
usartst.USART_StopBits = USART_StopBits_1;
usartst.USART_Parity = USART_Parity_No;
usartst.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usartst.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_Init(USART1,&usartst);
USART_Cmd(USART1,ENABLE);
}
main.c 库函数代码
#include "stm32f10x.h"
#include "bsp_systick.h"
#include "led.h"
#include "exti.h"
#include "bitBandTool.h"
#include "usart1.h"
void Stm32_Clock_Init(void)
{
/*----------使用外部RC晶振----------*/
RCC_DeInit() ;
//初始化为缺省值
RCC_HSEConfig(RCC_HSE_ON); //使 能外部的高速时钟
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); //等 待外部高速时钟使能就绪
//Flash 2 wait state
RCC_HCLKConfig (RCC_SYSCLK_Div1) ;
//HCLK = SYSCLK
RCC_PCLK2Config(RCC_HCLK_Div1) ;
//PCLK2 =HCLK
RCC_PCLK1Config(RCC_HCLK_Div2) ;
//PCLK1 = HCLR/2
RCC_PLLConfig (RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); //PLLCLK = 8MHZ * 9 =72MHZ
RCC_PLLCmd(ENABLE) ;
//Enable PLLCLK
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //Wait till PLLCLK is ready
RCC_SYSCLKConfig (RCC_SYSCLKSource_PLLCLK) ;
//Select PLL as system clock
while (RCC_GetSYSCLKSource () !=0x08) ;
//wait till PLL is used as system clock source
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);
}
int main(void)
{
Stm32_Clock_Init();
usart1_init(9600);
printf("system start!\n");//这里可以在电脑的串口助手看到数据
}
3.3 串口接收
串口接收可以以轮询的方式接收,也可以以中断的方式接受。以轮询的方式接收可能会由于cpu正处理其他程序,导致接收失败。故串口接收使用中断的方式。
代码
usart1.h
#ifndef USART1_H
#define USART1_H
#include "stm32f10x.h"
#include <stdio.h>
void usart1_init(u32 baundRate);
void usart1_it_init(void);
#endif
usart1.c
#include "usart1.h"
/*****************************************************************************************
* Function Name : fputc
* Descrption : 重定向这个C库(stdio) printf函数 文件流->串口USART1
* Input : ch ,*f
* Output : None
* Return : None
*****************************************************************************************/
int fputc(int ch,FILE *f)
{
//将ch送给USART1
USART_SendData(USART1,ch);
//等待发送完毕
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
//返回ch
return ch;
}
void usart1_gpio_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
}
void usart1_init(u32 baundRate)
{
usart1_gpio_init();
USART_InitTypeDef usartst;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
usartst.USART_BaudRate = baundRate;
usartst.USART_WordLength = USART_WordLength_8b;
usartst.USART_StopBits = USART_StopBits_1;
usartst.USART_Parity = USART_Parity_No;
usartst.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usartst.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_Init(USART1,&usartst);
USART_Cmd(USART1,ENABLE);
}
void usart1_it_init(void)
{
NVIC_InitTypeDef nvicst;
nvicst.NVIC_IRQChannel = USART1_IRQn;
nvicst.NVIC_IRQChannelPreemptionPriority = 3;
nvicst.NVIC_IRQChannelSubPriority = 1;
nvicst.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&nvicst); //根据指定的参数初始化VIC寄存器
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
}
//USART1中断服务程序
void USART1_IRQHandler(void)
{
u8 Res;
//USART1中断服务程序只有一个,得判断是串口的什么中断
//指令判断是否是接收中断
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
printf("%c",Res);
}
}
main.c
#include "bitBandTool.h"
#include "usart1.h"
void Stm32_Clock_Init(void)
{
/*----------使用外部RC晶振----------*/
RCC_DeInit() ;
//初始化为缺省值
RCC_HSEConfig(RCC_HSE_ON); //使 能外部的高速时钟
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); //等 待外部高速时钟使能就绪
//Flash 2 wait state
RCC_HCLKConfig (RCC_SYSCLK_Div1) ;
//HCLK = SYSCLK
RCC_PCLK2Config(RCC_HCLK_Div1) ;
//PCLK2 =HCLK
RCC_PCLK1Config(RCC_HCLK_Div2) ;
//PCLK1 = HCLR/2
RCC_PLLConfig (RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); //PLLCLK = 8MHZ * 9 =72MHZ
RCC_PLLCmd(ENABLE) ;
//Enable PLLCLK
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //Wait till PLLCLK is ready
RCC_SYSCLKConfig (RCC_SYSCLKSource_PLLCLK) ;
//Select PLL as system clock
while (RCC_GetSYSCLKSource () !=0x08) ;
//wait till PLL is used as system clock source
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);
}
int main(void)
{
Stm32_Clock_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组
usart1_init(9600);
usart1_it_init();
printf("system start!\n");
while(1)
{
}
}
调试结果:
3.4、串口批量输出
串口接收中断批量接收数据中断服务程序:
//首先在usart.h中事先定义全局变量,USART1_RX_STA、USART1_RX_BUF
#define USART1_REC_LEN 200
extern u16 USART1_RX_STA;
extern u8 USART1_RX_BUF[USART1_REC_LEN];
//在usart.c中初始化全局变量,并且编写中断服务程序
u16 USART1_RX_STA = 0;
u8 USART1_RX_BUF[USART1_REC_LEN];
//USART1中断服务程序
void USART1_IRQHandler(void)
{
u8 Res;
//USART1中断服务程序只有一个,得判断是串口的什么中断
//指令判断是否是接收中断
if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
{
Res = USART_ReceiveData(USART1);
//接收数据,判断数据
//末尾数据是0x0d,回车表示数据接收完毕,STA高位置1表示接受完,
//回车后会有换行,接到换行 0x0a,初始化数组指针
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART1_RX_STA&0x8000) == 0)
{
//判断接收了\r后,是否接收到了\n
if(USART1_RX_STA&0x4000)
{
//接受到了\n ,接结束结束,置位结束接收标志
if(Res == 0x0a)
USART1_RX_STA |= 0x8000;
//没有接收到\n,表示数据错误
else
{
USART1_RX_STA = 0;//接收标志全清除
memset(USART1_RX_BUF,0x00,USART1_REC_LEN);//接收buf清0
}
}
//没有接收到\n
else
{
//判断是否是回车的第一个字符 \n,是的话置位,否则接收数据入buf
if(Res == 0x0d)
USART1_RX_STA |= 0x4000;
else
{
USART1_RX_BUF[USART1_RX_STA&0x3fff] = Res;
USART1_RX_STA++;
//接收数据过大
if(USART1_RX_STA > (USART1_REC_LEN-1))
{
USART1_RX_STA = 0;//数据清0
memset(USART1_RX_BUF,0x00,USART1_REC_LEN);//接收buf清0
}
}
}
}
}
}
USART1_IRQHandler解析:
- 利用一个变量USART1_RX_STA来表示串口接收数据的情况。
- 我们以回车作为数据结束符,回车符就是 \r\n 。串口接收到 \r\n 就表示接收数据完毕。
- USART1_RX_STA次高位置1表示接收到了\r,这时我们要判断下一个字符是否时 \n 。
- USART1_RX_STA的最高位置1表示接收到了\n,即数据接收完毕。
- USART1_RX_STA的除最高位、最低位 的 低位 表示的是接收数据的长度。
主函数:
int main(void)
{
u8 i;
Stm32_Clock_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
usart1_init(9600);
usart1_it_init();
printf("system start!\n");
while(1)
{
if(USART1_RX_STA&0x8000)
{
for(i=0;i<(USART1_RX_STA&0x3fff);i++)
printf("%c",USART1_RX_BUF[i]);
USART1_RX_STA = 0;
}
}
}
四、利用虚拟串口对软件仿真USART进行调试。
由于MDK 的UART没有输入功能,不方便调试,我们使虚拟串口软件VSPD来虚拟串口,实现MDK的仿真调试的串口输入。
-
首先下载虚拟串口软件VSPD
-
建立虚拟串口对
-
在MDK中j进入仿真调试,在command输入如下指令:
mode com2 9600,0,8,1
assign com2 <s1in> s1out
可以看到com2已经配置到mdk
-
打开串口助手。某人使用的是 野人4.04 版本的串口助手。
可以看到虚拟串口已经被串口助手占用。
-
现在就可以全速运行仿真,使用串口调试啦!
程序是接收到什么数据(以回车为结束符的数据),发送相应的数据回去。