寄存器与固件库的编程差异&STM32的USART窗口通讯
文章目录
前言
1.基于寄存器与基于固件库的stm32 LED流水灯例子的编程方式的差异;
2. 学习和阅读“零死角玩转STM32F103–指南者”文档中的第20、21章内容,完成STM32的USART窗口通讯程序,要求:
1)设置波特率为115200,1位停止位,无校验位。
2)STM32系统给上位机(win10)连续发送“hello windows!”,上位机接收程序可以使用“串口调试助手“,也可自己编程。
3)当上位机给stm32发送“Stop,stm32”后,stm32停止发送。
一、寄存器与固件库的编程差异
使用固件库,目前比较多的例程是使用固件库编写的。官方的例子也都采用固件库方式。特点就是简单,易于理解,资料多。如果你没有 CortexM 系列内核的开发基础,建议从固件库开始玩起。等有一定基础,或是特别需要时再用寄存器,本篇文章采用的就是固件库。
使用寄存器,想要深入理解 CortexM3 内核或是需要为了获得更好的可移植性,学习寄存器编程会比较有帮助。但是从专业的角度上看,寄存器更贴近底层,对外设的工作原理和运行机理会有更深的理解。
二、串口通讯的认识
1.了解通讯
串行通讯与并行通讯
串行通讯:设备之间通过少量数据信号线,地线以及控制信号线,按数据形式一位一位地传输数据。
并行通讯:设备之间通过信号线,同时传输多个数据位的数据。
串行通讯的通讯距离和抗干扰能力要优于并行通讯,并且成本更低,而并行通讯的传输速率要优于串行通讯
全双工,半双工和单工通讯
全双工通讯:设备之间可以同时收发数据。
半双工通讯:设备之间可以收发数据,但是不能够同时进行。
单工通讯:单方向的进行数据的发送和接收,即一个设备要么作为发送设备,要么作为接收设备。
同步通讯和异步通讯
同步通讯:收发双方使用同一个信号线作为时钟信号,在时钟信号的驱动下双方进行协调,同步数据。
异步通讯:不采用时钟信号进行数据同步,而是在数据信号中穿插一些同步用的信号位来实现同步。
2.相关介绍
两种电平标准
TTL标准:当电平处于2.4~5V之间时,表示逻辑1;当电平处于 0 ~0.5V时,表示逻辑0。
RS-232标准:当电平处于-15~-3V之间时,表示逻辑1;当电平处于3 ~15V时,表示逻辑0。
USB转串口通讯
USB转串口主要是设备跟电脑通信,该过程需要电平转换芯片来实现,常用的芯片有CH340,PL2303,CP2102,FT232。使用的时候需要安装电平转换芯片的驱动。
原生的串口到串口
主要是控制器跟串口设备或者传感器通信,不需要电平转换芯片来转换电平,直接使用TTL电平通信。例如GPS模块。
波特率与比特率
波特率即每秒钟传输的码元个数,便于对信号进行解码。常用的波特率4800,9600,115200。比特率即每秒钟传输的二进制位数。
通讯的起始和停止信号
起始信号由逻辑0的数据位表示,停止信号可由0.5,1.5,1或2个1的数据位来表示。双方自行约定。
校验
通过校验码来避免数据在传输过程中,受到外部干扰而发生偏差。常采用奇偶校验,只能检测出发生偏差位的1位。
3.通讯过程
串口设备间常见的通讯结构,如下图所示
两个通讯设备的 “ DB9 接口 ” 之间通过串口信号线建立起连接,串口信号线中使用 “ RS-232 标准 ” 传输数据信号。由于 RS-232 电频标准的信号不能直接被控制器直接识别,所以这些信号会经过一个 “ 电平转换芯片 ” 转换成控制器能识别的 “ TTL 标准 ” 的电平信号,才能实现通讯。
三、USART窗口通讯
①将使用已经建好的一个文件(使用固件库),将其备份成一个新文件
②在新文件中的User文件下,新建一个bsp_uart.h,bsp_uart.c,main.c文件
③使用Keil打开文件,将新建文件添加进来
④查看相关文件
相关数据的介绍
①USART初始化结构体
typedef struct
{
uint32_t USART_BaudRate;//波特率BRR
uint16_t USART_WordLength;//字长CR1_M
uint16_t USART_StopBits;//停止位CR2_STOP
uint16_t USART_Parity;//校验控制CR1_PCE,CR1_PS
uint16_t USART_Mode;//模式选择CR1_TE,CR1_RE(发送和接收)
uint16_t USART_HardwareFlowControl;//硬件流选择CR3_CTSE,CR3_RTSE
} USART_InitTypeDef;
②同步时钟初始化结构体
typedef struct
{
uint16_t USART_Clock;//同步时钟CR2_CLKEN(时钟使能)
uint16_t USART_CPOL;//极性CR2_CPOL
uint16_t USART_CPHA;//相位CR2_CPRA
uint16_t USART_LastBit;//最后一个位的时钟脉冲CR2_LBC
} USART_ClockInitTypeDef;
相关固件函数介绍
①串口初始化函数
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
②中断配置函数
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
③串口使能函数
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
④数据发送函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
⑤数据接收函数
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
⑥中断状态位获取函数
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
代码编写
①一个简单的串口发送数据
bsp_uart.h
#ifndef _BSP_UART_H
#define _BSP_UART_H
#include "stm32f10x.h"
//串口1-USART1
#define DEBUG_UARTx USART1;
#define DEBUG_UART_CLK RCC_APB2Periph_USART1
#define DEBUG_UART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_UART_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 DEBUG_UART_Config(void);
#endif /*_BSP_UART_H*/
bsp_uart.c
#include "./uart/bsp_uart.h"
void DEBUG_UART_Config(void)
{
/*第一步:初始化GPIO*/
//定义GPIO对象
GPIO_InitTypeDef GPIO_InitStructure;
//定义串口对象
USART_InitTypeDef USART_InitStructure;
//打开串口GPIO的时钟
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK,ENABLE);
//将USART Tx(发送数据)的GPIO的配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin=DEBUG_USART_TX_GPIO_PIN;
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=DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT,&GPIO_InitStructure);
/*第二步:配置串口的初始化结构体*/
//打开串口外设的时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK,ENABLE);
//配置波特率
USART_InitStructure.USART_BaudRate=DEBUG_USART_BAUDRATE;
//配置数据字长
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(DEBUG_USARTx,&USART_InitStructure);
/*第三步:使能串口*/
USART_Cmd(DEBUG_USARTx,ENABLE);
}
main.c
#include "stm32f10x.h"
#include "./uart/bsp_uart.h"
int main(void)
{
DEBUG_UART_Config();
USART_SendData(DEBUG_USARTx,0xaa);
while(1);
}
编译
烧录程序的设置
选择对应的下载器
端口选择SW
将图中内容打上勾
编译程序,再次烧录
安装CH34驱动程序
链接: http://www.wch.cn/search?t=all&q=CH341A.
结果显示
串口的复杂通信
bsp_uart.h增添如下代码
void Usart_SendByte(USART_TypeDef * pUSARTx,uint8_t ch);
void USART_SendString(USART_TypeDef * pUSARTx,char *str);
void delay_ms(uint16_t delay_ms);
bsp_uart.c增添如下代码
//发送一个字节
void Usart_SendByte(USART_TypeDef * pUSARTx,uint8_t ch)
{
USART_SendData(pUSARTx,ch);// 发送一个字节数据到USART
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==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 delay_us(uint32_t delay_s)
{
volatile unsigned int i;
volatile unsigned int t;
for (i = 0; i < delay_s; i++)
{
t = 11;
while (t != 0)
{
t--;
}
}
}
//毫秒级的延时函数
void delay_ms(uint16_t delay_ms)
{
volatile unsigned int num;
for (num = 0; num < delay_ms; num++)
{
delay_us(1000);
}
}
//配置嵌套向量中断控制器NVIC
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 嵌套向量中断控制器组选择
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 配置USART为中断源
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
// 抢断优先级
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 子优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
// 使能中断
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// 初始化配置NVIC
NVIC_Init(&NVIC_InitStructure);
}
//并在DEBUG_USART_Config函数第三步前面添加
//串口中断优先级配置
NVIC_Configuration();
//使能串口接收中断
USART_ITConfig(DEBUG_USARTx,USART_IT_RXNE,ENABLE);
main.c
#include "stm32f10x.h"
#include "./uart/bsp_usart.h"
// 接收缓冲,最大100个字节
uint8_t USART_RX_BUF[100];
// 接收状态标记位
uint16_t USART_RX_FLAG=0;
//串口中断服务函数
void DEBUG_USART_IRQHandler(void)
{
uint8_t temp;
//接收中断
if(USART_GetFlagStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET)
{
// 读取接收的数据
temp = USART_ReceiveData(DEBUG_USARTx);
//接收未完成
if((USART_RX_FLAG & 0x8000)==0)
{
//接收到了0x0d
if(USART_RX_FLAG & 0x4000)
{
// 接收错误,重新开始
if(temp != 0x0a) USART_RX_FLAG=0;
// 接收完成
else USART_RX_FLAG |= 0x8000;
}
// 还未接收到0x0d
else
{
if(temp == 0x0d)
{
USART_RX_FLAG |= 0x4000;
}
else
{
USART_RX_BUF[USART_RX_FLAG & 0x3FFF]=temp;
USART_RX_FLAG++;
//接收数据错误,重新开始接收
if(USART_RX_FLAG > 99) USART_RX_FLAG=0;
}
}
}
}
}
int main(void)
{
uint8_t len=0;
uint8_t i=0;
// USART初始化
USART_Config();
while(1)
{
if(USART_RX_FLAG & 0x8000)
{
// 获取接收到的数据长度
len = USART_RX_FLAG & 0x3FFF;
USART_SendString(DEBUG_USARTx, "发送消息:\n");
for(i=0; i<len;i++)
{
// 向串口发送数据
USART_SendData(DEBUG_USARTx, USART_RX_BUF[i]);
//等待发送结束
while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TC)!=SET);
}
USART_SendString(DEBUG_USARTx, "\n\n");
if(strcmp((char *)USART_RX_BUF,"Stop,stm32!")==0)
{
USART_SendString(DEBUG_USARTx, "stm32已停止发送!");
break;
}
USART_RX_FLAG=0;
memset(USART_RX_BUF,0,sizeof(USART_RX_BUF));
}
else
{
USART_SendString(DEBUG_USARTx, "hello windows!\n");
delay_ms(800);
}
}
}
烧录编译,结果显示;
四、总结与参考资料
1.总结
对串口通讯有个简单了解,理解相关代码。不懂得需要多查阅资料。
2.参考资料
1.野火F103-MINI视频学习.
2. ST-LINK连接方式.