文章目录
- 一、基础知识
- 1、什么是STM32
- 2、STM32诞生背景
- 3、STM32分类
- 4、STM32F1X系列命名规则
- 5、STM32F103C8T6最小系统
- 二、STM32固件库
- 1、初始固件库
- (1)51单片机的寄存器
- (2)STC8A通过库函数方式实现LED闪烁
- (3)寄存器开发与库函数开发
- (4)STM32F1固件库开发参考资料
- (5)标准库和HAL库的区别
- (6)STM32标准库说明及新建工程模板(略)
- 2、GPIO输入输出与定时器延时函数
- (1)STM32 GPIO的8种模式
- (2)源码详解
- 3、串口通信
- (1)原理
- (2)源码详解
- 未完待续...
说明:本文所有例程使用STM32F103C8T6单片机运行无误
一、基础知识
1、什么是STM32
ST是意法半导体,M是Microelectronics(微控制器),32表示32位。合起来理解是:STM32就是ST公司开发的32位微控制器。
微控制器和微处理器区别:
最主要的区别在硬件结构上,微处理器就是一个单芯片的CPU,而微控制器则在集成了CPU和其他电路,构成了一个完整的微型计算机系统。微控制器除了CPU还包含RAM、ROM、串行接口、计时器、中断。
2、STM32诞生背景
51 是嵌入式学习中一款入门级的精典 MCU,因其结构简单,易于教学,且可以通过串口编程而不需要额外的仿真器,所以在教学时被大量采用,至今很多大学在嵌入式教学中用的还是 51。 51 诞生于 70 年代,属于传统的 8 位单片机,如今,久经岁月的洗礼,既有其辉煌又有其不足。现在的市场产品竞争越来越激烈,对成本极其敏感,相应地对 MCU的 性能要求也更苛刻: 更多功能, 更低功耗,易用界面和多任务。面对这些要求, 51 现有的 资源就显得得抓襟见肘。所以无论是高校教学还是市场需求,都急需一款新的 MCU 来为这个领域注入新的活力。
基于这样的市场需求, ARM 公司推出了其全新的基于 ARMv7 架构的 32 位 CortexM3 微控制器内核。紧随其后, ST(意法半导体)公司就推出了基于 Cortex-M3 内核的MCU—STM32。 STM32 凭借其产品线的多样化、极高的性价比、简单易用的库开发方 式,迅速在众多 Cortex-M3 MCU 中脱颖而出,成为最闪亮的一颗新星。 STM32 一上市就 迅速
占领了中低端 MCU 市场,受到了市场和工程师的无比青睐,颇有星火燎原之势。
作为一名合格的嵌入式工程师,面对新出现的技术,我们不是充耳不闻,而是要尽快吻合市场的需要,跟上技术的潮流。如今 STM32 的出现就是一种趋势,一种潮流,我们要做的就是搭上这趟快车,让自己的技术更有竞争力。
——参考野火原文《零死角玩转STM32—F429挑战者》
3、STM32分类
STM32 有很多系列,可以满足市场的各种需求,从内核上分有 Cortex-M0、 M3、 M4
和 M7 这几种
——参考野火原文《零死角玩转STM32—F429挑战者》
4、STM32F1X系列命名规则
5、STM32F103C8T6最小系统
分享一个我之前学习STM32绘制的一个最小系统
二、STM32固件库
1、初始固件库
(1)51单片机的寄存器
之前写过51单片机的博客,对单片机的寄存器一些简要的说明,见下图
回顾之前学习51单片机串口向PC机发送“0x01”的程序,如下:
#include <reg51.h>
/*向串口发送一字节数据*/
void SendOneByte(unsigned char c)
{
SBUF = c;
while(!TI);
TI = 0;
}
void InitUART(void)
{
TMOD = 0x20;//设置定时器1,工作方式2,8位自动重载
SCON = 0x40;//设置串口工作方式1
TH1 = 0xFD;//波特率设置为9600
TL1 = TH1;
PCON = 0x00;//波特率不加倍
EA = 1;//开总中断
ES = 1;//允许串联1中断
TR1 = 1;//启动定时器T1
}
void main(void)
{
InitUART();
SendOneByte(0x01);
while(1);
}
void UARTInterrupt(void) interrupt 4
{
if(RI)
{
RI = 0;
//add your code here!
}
else
TI = 0;
}
通过这个程序我们可以理解到,CPU只有读和写这个操作,而我们要通过读和写寄存器变量的方式来实现来实现单片机控制外设的目的。
在程序中,我们通过给TMOD、SCON、TH1、TL1、PCON、EA、ES、TR1寄存器写数据来达到初始化串口的目的,而在串口中断中,我们又通过读RI寄存器来判断一帧数据发送完毕。
(2)STC8A通过库函数方式实现LED闪烁
学习过STC8单片机的应该知道,STC8是51单片机的一个升级版本,它完全兼容8051,可以使用直接操作寄存器的方式编程,也可以使用STC8A系列库函数进行开发,如下例程是使用“STC8A-SOFTWARE-LIB”实现LED闪烁的程序。
#include "config.h"
#include "GPIO.h"
#include "delay.h"
void GPIO_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //结构定义
GPIO_InitStructure.Pin = GPIO_Pin_0 | GPIO_Pin_1; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P5,&GPIO_InitStructure); //初始化
}
void main(void)
{
GPIO_config();
while(1)
{
P50 = ~P50;
delay_ms(250);
P51 = ~P51;
delay_ms(250);
}
}
通过这个程序,我们我们初始化STC8A的GPIO的时候,我们不需要直接写寄存器,而是先使用GPIO_InitTypeDef定义GPIO初始化结构体变量,然后调用GPIO_Inilize()函数,直接初始化GPIO。这便是一个经典的使用库函数编程。
那么,GPIO_InitTypeDef和GPIO_Inilize()函数里都有什么呢?我们看看
//GPIO_InitTypeDef
typedef struct
{
u8 Mode; //IO模式, GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
u8 Pin; //要设置的端口
} GPIO_InitTypeDef;
//========================================================================
// 函数: u8 GPIO_Inilize(u8 GPIO, GPIO_InitTypeDef *GPIOx)
// 描述: 初始化IO口.
// 参数: GPIOx: 结构参数,请参考timer.h里的定义.
// 返回: 成功返回0, 空操作返回1,错误返回2.
// 版本: V1.0, 2012-10-22
//========================================================================
u8 GPIO_Inilize(u8 GPIO, GPIO_InitTypeDef *GPIOx)
{
if(GPIO > GPIO_P7) return 1; //空操作
if(GPIOx->Mode > GPIO_OUT_PP) return 2; //错误
if(GPIO == GPIO_P0)
{
if(GPIOx->Mode == GPIO_PullUp) P0M1 &= ~GPIOx->Pin, P0M0 &= ~GPIOx->Pin; //上拉准双向口
if(GPIOx->Mode == GPIO_HighZ) P0M1 |= GPIOx->Pin, P0M0 &= ~GPIOx->Pin; //浮空输入
if(GPIOx->Mode == GPIO_OUT_OD) P0M1 |= GPIOx->Pin, P0M0 |= GPIOx->Pin; //开漏输出
if(GPIOx->Mode == GPIO_OUT_PP) P0M1 &= ~GPIOx->Pin, P0M0 |= GPIOx->Pin; //推挽输出
}
if(GPIO == GPIO_P1)
{
if(GPIOx->Mode == GPIO_PullUp) P1M1 &= ~GPIOx->Pin, P1M0 &= ~GPIOx->Pin; //上拉准双向口
if(GPIOx->Mode == GPIO_HighZ) P1M1 |= GPIOx->Pin, P1M0 &= ~GPIOx->Pin; //浮空输入
if(GPIOx->Mode == GPIO_OUT_OD) P1M1 |= GPIOx->Pin, P1M0 |= GPIOx->Pin; //开漏输出
if(GPIOx->Mode == GPIO_OUT_PP) P1M1 &= ~GPIOx->Pin, P1M0 |= GPIOx->Pin; //推挽输出
}
if(GPIO == GPIO_P2)
{
if(GPIOx->Mode == GPIO_PullUp) P2M1 &= ~GPIOx->Pin, P2M0 &= ~GPIOx->Pin; //上拉准双向口
if(GPIOx->Mode == GPIO_HighZ) P2M1 |= GPIOx->Pin, P2M0 &= ~GPIOx->Pin; //浮空输入
if(GPIOx->Mode == GPIO_OUT_OD) P2M1 |= GPIOx->Pin, P2M0 |= GPIOx->Pin; //开漏输出
if(GPIOx->Mode == GPIO_OUT_PP) P2M1 &= ~GPIOx->Pin, P2M0 |= GPIOx->Pin; //推挽输出
}
if(GPIO == GPIO_P3)
{
if(GPIOx->Mode == GPIO_PullUp) P3M1 &= ~GPIOx->Pin, P3M0 &= ~GPIOx->Pin; //上拉准双向口
if(GPIOx->Mode == GPIO_HighZ) P3M1 |= GPIOx->Pin, P3M0 &= ~GPIOx->Pin; //浮空输入
if(GPIOx->Mode == GPIO_OUT_OD) P3M1 |= GPIOx->Pin, P3M0 |= GPIOx->Pin; //开漏输出
if(GPIOx->Mode == GPIO_OUT_PP) P3M1 &= ~GPIOx->Pin, P3M0 |= GPIOx->Pin; //推挽输出
}
if(GPIO == GPIO_P4)
{
if(GPIOx->Mode == GPIO_PullUp) P4M1 &= ~GPIOx->Pin, P4M0 &= ~GPIOx->Pin; //上拉准双向口
if(GPIOx->Mode == GPIO_HighZ) P4M1 |= GPIOx->Pin, P4M0 &= ~GPIOx->Pin; //浮空输入
if(GPIOx->Mode == GPIO_OUT_OD) P4M1 |= GPIOx->Pin, P4M0 |= GPIOx->Pin; //开漏输出
if(GPIOx->Mode == GPIO_OUT_PP) P4M1 &= ~GPIOx->Pin, P4M0 |= GPIOx->Pin; //推挽输出
}
if(GPIO == GPIO_P5)
{
if(GPIOx->Mode == GPIO_PullUp) P5M1 &= ~GPIOx->Pin, P5M0 &= ~GPIOx->Pin; //上拉准双向口
if(GPIOx->Mode == GPIO_HighZ) P5M1 |= GPIOx->Pin, P5M0 &= ~GPIOx->Pin; //浮空输入
if(GPIOx->Mode == GPIO_OUT_OD) P5M1 |= GPIOx->Pin, P5M0 |= GPIOx->Pin; //开漏输出
if(GPIOx->Mode == GPIO_OUT_PP) P5M1 &= ~GPIOx->Pin, P5M0 |= GPIOx->Pin; //推挽输出
}
if(GPIO == GPIO_P6)
{
if(GPIOx->Mode == GPIO_PullUp) P6M1 &= ~GPIOx->Pin, P6M0 &= ~GPIOx->Pin; //上拉准双向口
if(GPIOx->Mode == GPIO_HighZ) P6M1 |= GPIOx->Pin, P6M0 &= ~GPIOx->Pin; //浮空输入
if(GPIOx->Mode == GPIO_OUT_OD) P6M1 |= GPIOx->Pin, P6M0 |= GPIOx->Pin; //开漏输出
if(GPIOx->Mode == GPIO_OUT_PP) P6M1 &= ~GPIOx->Pin, P6M0 |= GPIOx->Pin; //推挽输出
}
if(GPIO == GPIO_P7)
{
if(GPIOx->Mode == GPIO_PullUp) P7M1 &= ~GPIOx->Pin, P7M0 &= ~GPIOx->Pin; //上拉准双向口
if(GPIOx->Mode == GPIO_HighZ) P7M1 |= GPIOx->Pin, P7M0 &= ~GPIOx->Pin; //浮空输入
if(GPIOx->Mode == GPIO_OUT_OD) P7M1 |= GPIOx->Pin, P7M0 |= GPIOx->Pin; //开漏输出
if(GPIOx->Mode == GPIO_OUT_PP) P7M1 &= ~GPIOx->Pin, P7M0 |= GPIOx->Pin; //推挽输出
}
return 0; //成功
}
我们查看GPIO_InitTypeDef结构体时发现mode可以直接赋值GPIO_PullUp, GPIO_HighZ, GPIO_OUT_OD, GPIO_OUT_PP,那么这些又是什么呢?
我们查看GPIO.h文件可以得知如下:
#define GPIO_PullUp 0 //上拉准双向口
#define GPIO_HighZ 1 //浮空输入
#define GPIO_OUT_OD 2 //开漏输出
#define GPIO_OUT_PP 3 //推挽输出
我们还在GPIO.h程序中看到其他已经宏定义好的字符串
#define GPIO_Pin_0 0x01 //IO引脚 Px.0
#define GPIO_Pin_1 0x02 //IO引脚 Px.1
#define GPIO_Pin_2 0x04 //IO引脚 Px.2
#define GPIO_Pin_3 0x08 //IO引脚 Px.3
#define GPIO_Pin_4 0x10 //IO引脚 Px.4
#define GPIO_Pin_5 0x20 //IO引脚 Px.5
#define GPIO_Pin_6 0x40 //IO引脚 Px.6
#define GPIO_Pin_7 0x80 //IO引脚 Px.7
#define GPIO_Pin_All 0xFF //IO所有引脚
#define GPIO_P0 0
#define GPIO_P1 1
#define GPIO_P2 2
#define GPIO_P3 3
#define GPIO_P4 4
#define GPIO_P5 5
#define GPIO_P6 6
#define GPIO_P7 7
(3)寄存器开发与库函数开发
我通过51单片机初始化串口和STC8A库函数方式实现LED灯闪烁,两个简单的例子,简单说明了,直接操作寄存器开发与调用库函数开发。
在51单片机开发过程中,因为51单片机相对简单,所以都是直接使用操作寄存器开发方式,这些工作很繁琐、机械。但是到了STM32开发时,外设资源非常丰富,必然寄存器和和复杂度增加了,直接配置寄存器方式就会造成开发速度慢、程序可读性差、维护复杂等缺点。而调用库函数开发正好可以弥补这些缺点。
但我们通过上面STC8A使用库函数方式使LED闪烁可以看到,只是一个简单的GPIO初始化源码代码量居然就这么大。这也是库函数开发的缺点,使用库函数开发会使代码量增大,库函数会把所有情况考虑到库函数里,势必会造成代码冗余。
所以直接操作寄存器开发不可否认还是具备程序运行占用资源少、代码简单明了、具体参数更直观的优点。
在摩尔定律预言中,每隔18个月,计算机的芯片集成度会提高一倍,功能提高一倍,而价格则下降为一半。现如今,STM32有足够的资源,权衡寄存器和库函数开发的优缺点,我们大多数的时候都会牺牲一些CPU资源来使用库函数开发。只有在一些对代码要求极为苛刻的地方,才会使用直接操作寄存器开发。调用库函数开发说得更加直接了当一点就是:我想偷点懒,然后就直接白嫖官方写好的程序,直接拿来用。而官方的程序帮我们把寄存器的工作全部都已经配置完成了,我不需要了解人家是怎么配置的,我知道的是白嫖过来后,程序下到板子里居然能跑。
(4)STM32F1固件库开发参考资料
1、STM32F1X固件函数库:用于查阅STM32F1标准库,有英文的帮助文档,也要翻译好的中文pdf文档。
2、STM32F103X数据手册:介绍芯片的一些基本参数、功能一栏、电气特性、封装参数等。
3、STM32F10X参考手册:此手册包含各个模块的内部结构、所有可能的功能描述、各个工作模式的使用寄存器配置等详细信息,值得注意的是参考手册不包含有关产品的计数特征说明,这些在芯片手册中。
4、Cortex-M3权威指南:本书详细概述了ARM Cortex-M3内核的架构和特性。
(5)标准库和HAL库的区别
标准库是直接把寄存器封装好的库,而HAL库可以直接使用ST的CUBEMX软件生成,相比于HAL库可移植性更强。在推行时间上,HAL库是最近才推出的库,STM32官方也主推,而标准库是STM32最早推出的库,应用非常广泛,在市面上的模块都能找得到例程,但是比较新的F7和H7等系列已经不支持了。
如果在时间紧迫的情况下,只需要掌握其中一种库就行了,没必要两种都掌握。
如果以后时间充裕的话,我写一份HAL库的博客…
(6)STM32标准库说明及新建工程模板(略)
这段内容网上有太多的资料,此处直接略过。
关于STM32标准库的说明和新建一个STM32工程模板可以多到B站上看看视频。可以到B站找一下野火的视频,不是打广告,野火也没有给我任何好处,也不是我的什么亲戚,我自己学习STM32的时候也是看野火的视频,讲得还不错。看完视频,然后再找一本参考书《STM32库开发指南(基于STM32F103)》,把视频的知识点和书中的结合,多做笔记,多动手实践。
最后,如果之前学习51单片机安装了keil c51的话,现在安装keil arm是可以安装在一起的,多去网上找找资料,或者安装两个不同的软件,分别安装在不同的目录。
2、GPIO输入输出与定时器延时函数
(1)STM32 GPIO的8种模式
通过《STM32F10X-参考手册中文版》可以得知,每个GPIO管脚都可以由软件设置成输出(推挽或开漏)、输入(带或者不带上下拉)或其他外设功能接口,多数GPIO管脚数字或模拟的外设共用,具体GPIO模式如下8种:
- 输入浮空
- 输入上拉
- 输入下拉
- 模拟输入
- 开漏输出
- 推挽输出
- 推挽复用功能
- 开漏复用功能
(2)源码详解
1)首先,我们将系统时钟设置成72MHZ
//将系统时钟配置为72MHZ
void RCC_Configuration(void)
{
SystemInit();
}
2)封装GPIO推挽式输出、按键输入、按键检测函数
/**
******************************************************************************
* @file gpio.c
* @author 小途
* @version V1.0
* @date 2022-2-21
* @brief STM32F1的GPIO初始化设置
******************************************************************************
* @attention
*
*
******************************************************************************
*/
#include "gpio.h"
/***************************************************
*函数名称:void gpio_OutputConfig(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx)
*函数输入:RCC_APB2Periph: 初始化GPIO总线的外设时钟
* pin: 引脚数
* GPIOx: where x can be (A..G) to select the GPIO peripheral.
*函数返回:无
*函数说明:GPIO推挽式输出初始化
***************************************************/
void gpio_OutputConfig(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx)
{
//定义一个GPIO_InitTypeDef类型结构体
GPIO_InitTypeDef GPIO_InitStructure;
//开启要初始化GPIO总线的外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph, ENABLE);
//选择要控制的GPIO引脚
GPIO_InitStructure.GPIO_Pin = pin;
//设置引脚模式为通用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
//设置引脚速率为50MHZ
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//初始化GPIO
GPIO_Init(GPIOx, &GPIO_InitStructure);
}
/**
* @brief 配置按键用到的I/O口
* @param 无
* @retval 无
*/
void Key_GPIO_Config(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*开启按键端口时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph, ENABLE);
//选择按键的引脚
GPIO_InitStructure.GPIO_Pin = pin;
// 设置按键的引脚为浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//使用结构体初始化按键
GPIO_Init(GPIOx, &GPIO_InitStructure);
}
/*
* 函数名:Key_Scan
* 描述 :检测是否有按键按下
* 输入 :GPIOx:x 可以是 A,B,C,D或者 E
* GPIO_Pin:待读取的端口位
* 输出 :KEY_OFF(没按下按键)、KEY_ON(按下按键)
*/
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{
/*检测是否有按键按下 */
if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )
{
/*等待按键释放 */
while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);
return KEY_ON;
}
else
return KEY_OFF;
}
//gpio.h
#ifndef __GPIO_H
#define __GPIO_H
#include "stm32f10x.h"
/** 按键按下标置宏
* 按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0
* 若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1 即可
*/
#define KEY_ON 1
#define KEY_OFF 0
void gpio_OutputConfig(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx);
void Key_GPIO_Config(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx);
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
#endif
3)配置延时函数定时器
51单片机开发中,常使用软件执行空语句来达到延时一段时间的目的。这种延时会导致CPU资源被占用无法执行其他程序,导致系统效率降低。在STM32开发中,可以使用定时器来达到延时效果。
/**
******************************************************************************
* @file delay.c
* @author 小途
* @version V1.0
* @date 2022-2-21
* @brief STM32F1的延时函数
******************************************************************************
* @attention
*
* 使用定时器4(通用定时器),备用2个基本定时器TIM6、TIM7
*
******************************************************************************
*/
#include "delay.h"
//延时函数定时器初始,初始化定时器4
void Delay_Timer_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Down;
TIM_TimeBaseInitStruct.TIM_Period = 100-1;
TIM_TimeBaseInitStruct.TIM_Prescaler = (84-1);
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStruct);
while((TIM4->SR & TIM_FLAG_Update)!=SET);
TIM4->SR = (uint16_t)~TIM_FLAG_Update;
}
void Delay_us(uint32_t us_cnt)
{
TIM4->CNT = us_cnt-1;
TIM4->CR1 |= TIM_CR1_CEN;
while((TIM4->SR & TIM_FLAG_Update)!=SET);
TIM4->SR = (uint16_t)~TIM_FLAG_Update;
TIM4->CR1 &= ~TIM_CR1_CEN;
}
void Delay_ms(uint32_t ms_cnt)
{
int i;
for(i=0; i<ms_cnt; i++)
{
Delay_us(1000);
}
}
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
/********************基本定时器TIM参数定义,只限TIM6、7************/
#define BASIC_TIM6 // 如果使用TIM7,注释掉这个宏即可
#ifdef BASIC_TIM6 // 使用基本定时器TIM6
#define BASIC_TIM TIM6
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK RCC_APB1Periph_TIM6
#define BASIC_TIM_Period (1000-1)
#define BASIC_TIM_Prescaler 71
#define BASIC_TIM_IRQ TIM6_IRQn
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
#else // 使用基本定时器TIM7
#define BASIC_TIM TIM7
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK RCC_APB1Periph_TIM7
#define BASIC_TIM_Period 1000-1
#define BASIC_TIM_Prescaler 71
#define BASIC_TIM_IRQ TIM7_IRQn
#define BASIC_TIM_IRQHandler TIM7_IRQHandler
#endif
/**************************函数声明********************************/
void Delay_Timer_Init(void);
void Delay_us(uint32_t us_cnt);
void Delay_ms(uint32_t ms_cnt);
#endif
4)对GPIO输出端口进行宏定义
在编写程序过程中,还应该考虑硬件修改的情况。例如输出端口改变,这时我们更希望修改一小部分代码就可以在新的硬件上运行。我们可以把硬件的相关部分使用宏来进行封装,若更改了硬件环境,只需要修改相关硬件的宏就可以了。
#define LED5_ON GPIO_SetBits(GPIOC, GPIO_Pin_13)
#define LED5_OFF GPIO_ResetBits(GPIOC, GPIO_Pin_13)
5)主函数
int main(void)
{
RCC_Configuration();
Delay_Timer_Init();
gpio_OutputConfig(RCC_APB2Periph_GPIOC, GPIO_Pin_13, GPIOC);
while(1)
{
LED5_ON;
Delay_ms(500);
LED5_OFF;
Delay_ms(500);
}
}
3、串口通信
(1)原理
这个段落主要讲解标准库编程源码,关于串口通信的基础,可以查看这一篇文章
【通信基础】TTL、RS232、RS485
通过查看《STM32F103数据手册中文版》STM32F103C8T6共有3个串口,各个默认串口引脚分布如下:
串口1:TX——PA9,RX——PA10
串口2:TX——PA2,RX——PA3
串口3:TX——PB10,TX——PB11
继续查看发现STM32F103C8T6光串口一就有5个功能管脚,如下图所示:
那么,USARTx_CK、USARTx_CTS、USARTx_RTS分别是什么呢?
首先,USARTx_CK很好理解,CK表示的是SCLK,中文意思是时钟。该管脚是一个时钟输出引脚,用于同步模式下使用。而关于CTS与RTS接口,我们是否还记得之前学习232通信的DB9接口,如下图所示:
我们看到DB9接口8和7管脚正好是CTS和RTS接口,这两个管脚的功能也如上图中所示,分别是清除发生和发生数据请求。
我们玩232通信都接触过DB9接口,但是我们使用最多的也就是TX、RX、GND这三个接口。但是,在STM32已经具备硬件流控的功能。这里只做了解,我平时开发的时候很少会用到。
这时我们还是编程第一步,编写usart.c和usart.h文件
(2)源码详解
/**
******************************************************************************
* @file usart.c
* @author 小途
* @version V1.0
* @date 2022-2-21
* @brief STM32F1的串口初始化设置
******************************************************************************
* @attention
*
*
******************************************************************************
*/
#include "usart.h"
/**
* @brief 配置嵌套向量中断控制器NVIC
* @param 无
* @retval 无
*/
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);
}
/**
* @brief USART GPIO 配置,工作参数配置
* @param 无
* @retval 无
*/
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 打开串口外设的时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_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);
// 配置串口的工作参数
// 配置波特率
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);
// 串口中断优先级配置
NVIC_Configuration();
// 使能串口接收中断
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
/**
* @brief USART2 GPIO 配置,工作参数配置
* @param 无
* @retval 无
*/
void USART2_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
DEBUG_USART2_GPIO_APBxClkCmd(DEBUG_USART2_GPIO_CLK, ENABLE);
// 打开串口外设的时钟
DEBUG_USART2_APBxClkCmd(DEBUG_USART2_CLK, ENABLE);
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART2_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART2_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART2_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART2_RX_GPIO_PORT, &GPIO_InitStructure);
// 配置串口的工作参数
// 配置波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART2_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_USART2, &USART_InitStructure);
// 使能串口
USART_Cmd(DEBUG_USART2, ENABLE);
}
/***************** 发送一个字符 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,ch);
/* 等待发送数据寄存器为空 */
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);
}
//重定向c库函数printf到串口,重定向后可使用printf函数,默认使用串口1
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(DEBUG_USART1, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
//重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数,默认使用串口1
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(DEBUG_USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USART1);
}
#ifndef __USART_H
#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
//------------------------------------------------------------------------
// 串口1-USART1
#define DEBUG_USART1 USART1
#define DEBUG_USART1_CLK RCC_APB2Periph_USART1
#define DEBUG_USART1_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART1_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART1_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART1_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART1_TX_GPIO_PORT GPIOA
#define DEBUG_USART1_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART1_RX_GPIO_PORT GPIOA
#define DEBUG_USART1_RX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART1_IRQ USART1_IRQn
#define DEBUG_USART1_IRQHandler USART1_IRQHandler
//------------------------------------------------------------------------
// 串口2-USART2
#define DEBUG_USART2 USART2
#define DEBUG_USART2_CLK RCC_APB1Periph_USART2
#define DEBUG_USART2_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART2_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART2_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART2_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART2_TX_GPIO_PORT GPIOA
#define DEBUG_USART2_TX_GPIO_PIN GPIO_Pin_2
#define DEBUG_USART2_RX_GPIO_PORT GPIOA
#define DEBUG_USART2_RX_GPIO_PIN GPIO_Pin_3
#define DEBUG_USART2_IRQ USART2_IRQn
#define DEBUG_USART2_IRQHandler USART2_IRQHandler
void NVIC_Configuration(void);
void USART_Config(void);
//void USART1_Config(void);
void USART2_Config(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);
#endif
并在stm32f10x_it.c里编写串口中断服务函数
#include "stm32f10x_it.h"
#include "usart.h"
#include "delay.h"
// 串口中断服务函数
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucTemp;
if(USART_GetITStatus(DEBUG_USART1,USART_IT_RXNE)!=RESET)
{
ucTemp = USART_ReceiveData(DEBUG_USART1);
USART_SendData(DEBUG_USART1,ucTemp);
}
}
最后就是主函数调用
int main(void)
{
RCC_Configuration();
Delay_Timer_Init();
USART_Config();
while(1)
{
Delay_ms(1000);
printf("HelloWorld!\n");
}
}
烧录验证效果