一、前期主备
(一)STM32F103C8T6基础版介绍
随着嵌入式的开发与不断钻研,开发板种类也在不断变多,今天我们在这里使用的是最基础的STM32F103C8T6基础板,虽然是基础板,但是它的功能还是挺齐全的很适合新手使用。
下图是STM32F103C8T6的引脚分布图介绍:
引脚配置说明如下:
(二)GPIO介绍
熟悉完开发板后,我们现在来学习GPIO端口i的定义。
——GPIO(General Purpose Input Output)通用输入输出口
——可配置为8种输入输出模式
——引脚电平:OV-3.3V.部分引脚可容忍5V
——输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂呜器、
模拟通信协议输出时序等
——输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等
具体的可以参考江协科技的视频。
这里我截取了江协老师的PPT内容,让大家更好理解。
GPIO外设配置
端口配置低寄存器
端口配置高寄存器
具体的GPIO端口配置可以参考STM32中文参考手册
(三)LED灯
——LED:发光二极管,正向通电点亮。
二、工程代码
(一)标准库实现
这里我参考的是江协科技的教学。具体步骤如下:
1、新建工程
点击peoject,再点击new project。
在文件夹里创建文件
再在刚刚创建的文件夹里新建三个文件,分别叫Start,Library,User。
加入启动文件。(这里的启动文件我找的是江协科技的)
添加三个工程项目。
2、编写时延函数
Delay.c:
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
Delay.h:
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
编写主程序代码main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
//使用各个外设前必须开启时钟,否则对外设的操作无效
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; //GPIO引脚,赋值为所有引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
//实现GPIOA的初始化
/*主循环,循环体内的代码会一直循环执行*/
while (1)
{
/*使用GPIO_Write,同时设置GPIOA所有引脚的高低电平,实现LED流水灯*/
GPIO_Write(GPIOA, ~0x0001); //0000 0000 0000 0001,PA0引脚为低电平,其他引脚均为高电平,注意数据有按位取反
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0002); //0000 0000 0000 0010,PA1引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0004); //0000 0000 0000 0100,PA2引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0008); //0000 0000 0000 1000,PA3引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0010); //0000 0000 0001 0000,PA4引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0020); //0000 0000 0010 0000,PA5引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0040); //0000 0000 0100 0000,PA6引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0080); //0000 0000 1000 0000,PA7引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
}
}
3、程序的烧录与实现
在编写完程序后,就开始进行程序的烧录,我这里采用的是Stlink烧录,方便快捷。
用杜邦线将Stlink烧录器与stm32开发板接连。然后依次进行编译和烧录。
4、LED流水灯接线图
我采用的是面包板与杜邦线连接,平面接线图如下图所示:
(二)寄存器实现
设计寄存器步骤:
以GPIOB和0号引脚(B0)为例,将其设置为低电平:
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
GPIOB_ODR &= ~(1<<0); // 最后一位变0
将相应的bit位设置为高电位则灯不亮,GPIO的A0、B0、C15代码如下:
//给GPIOA、GPIOB、GPIOC配置输入寄存器
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
// 3个LED初始化为不亮(即高点位)
GPIOB_ODR |= (1<<0); // 最后一位设置为1
GPIOC_ODR |= (1<<15); // 倒数第15位设置为1
GPIOA_ODR |= (1<<0); // 最后一位设置为1
main.c:
#define GPIOB_BASE 0x40010C00
#define GPIOC_BASE 0x40011000
#define GPIOA_BASE 0x40010800
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
#define GPIOB_CRH (*(unsigned int *)0x40010C04)
#define GPIOC_CRH (*(unsigned int *)0x40011004)
#define GPIOA_CRL (*(unsigned int *)0x40010800)
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
void SystemInit(void);
void Delay_ms(volatile unsigned int);
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
void A_LED_LIGHT(){
GPIOA_ODR=0x0<<4; //PA4低电平
GPIOB_ODR=0x1<<9; //PB9高电平
GPIOC_ODR=0x1<<15; //PC15高电平
}
void B_LED_LIGHT(){
GPIOA_ODR=0x1<<4; //PA4高电平
GPIOB_ODR=0x0<<9; //PB9低电平
GPIOC_ODR=0x1<<15; //PC15高电平
}
void C_LED_LIGHT(){
GPIOA_ODR=0x1<<4; //PA4高电平
GPIOB_ODR=0x1<<9; //PB9高电平
GPIOC_ODR=0x0<<15; //PC15低电平
}
int main(){
int j=100;
// 开启时钟
RCC_APB2ENR |= (1<<3); // 开启 GPIOB 时钟
RCC_APB2ENR |= (1<<4); // 开启 GPIOC 时钟
RCC_APB2ENR |= (1<<2); // 开启 GPIOA 时钟
// 设置 GPIO 为推挽输出
GPIOB_CRH&= 0xffffff0f; //设置位 清零
GPIOB_CRH|=0x00000020; //PB9推挽输出
GPIOC_CRH &= 0x0fffffff; //设置位 清零
GPIOC_CRH|=0x30000000; //PC15推挽输出
GPIOA_CRL &= 0xfff0ffff; //设置位 清零
GPIOA_CRL|=0x00010000; //PA4推挽输出
// 3个LED初始化为不亮(即高点位)
GPIOB_ODR |= (1<<9);
GPIOC_ODR |= (1<<15);
GPIOA_ODR |= (1<<4);
while(j){
A_LED_LIGHT();
Delay_ms(1000000);
B_LED_LIGHT();
Delay_ms(1000000);
C_LED_LIGHT();
Delay_ms(1000000);
}
}
void SystemInit(){
}
三、成果展示:
VID_20241113_201050
四、心得体会与总结
1. Keil工程创建:
- 首先要新建工程,选择对应的STM32芯片型号,这确保了后续编译等操作能基于正确的芯片资源进行。
- 添加必要的启动文件以及分组来管理代码,比如创建源文件和头文件分组等,方便代码的组织与维护。
2. GPIO输入输出:
- 明确要使用的GPIO引脚,通过配置相应的寄存器来设置其为输入或输出模式。例如,要实现流水灯,一般将引脚设置为推挽输出模式。
- 对于输出,可通过向对应的输出数据寄存器写入高低电平来控制引脚的输出状态,从而实现LED的亮灭控制。
3. 寄存器知识:
- STM32的寄存器是对芯片内部各种硬件资源进行控制和配置的关键。每个寄存器都有其特定的地址和功能。
- 比如GPIO的端口配置寄存器(CRL、CRH等)用于设置引脚的模式(输入、输出、复用等),输出数据寄存器(ODR)用于控制引脚的输出电平。直接操作寄存器能更深入地理解芯片的工作原理,但也需要对寄存器的每一位含义有清晰的了解,编写代码时要注意按位操作的准确性。
4. 标准固件库知识:
- 标准固件库提供了一系列的函数来方便对STM32芯片进行开发,它对底层寄存器操作进行了封装。
- 例如,对于GPIO的配置,有诸如GPIO_Init()等函数,通过设置结构体成员的值来完成对GPIO引脚模式、速度等的配置,相较于直接操作寄存器,使用固件库函数可以提高开发效率,代码也更具可读性和可维护性,但在一些对资源占用和执行速度有严格要求的场景下,可能需要结合寄存器操作来优化。
在整个流水灯实验过程中,通过以上各方面知识的综合运用,成功实现了LED按照一定顺序依次点亮熄灭,呈现出流水灯的效果。
五、心得体会
通过完成用STM32实现流水灯实验,收获颇丰。
在工程创建方面,体会到了合理组织工程结构的重要性,清晰的分组和文件管理能让后续代码的查找、修改和扩展都更加便捷,避免了代码混乱带来的困扰。
对于GPIO输入输出的配置,深入理解了如何通过软件来控制硬件引脚的状态,这是实现与外部设备交互的基础,也感受到了硬件和软件紧密结合的魅力。
在学习寄存器知识时,虽然一开始觉得寄存器的位操作比较复杂且容易出错,但当真正掌握后,能深切感受到直接操作寄存器带来的对芯片底层控制的精准性,也更能明白芯片内部资源是如何被一步步调配使用的。
而标准固件库则大大提高了开发效率,让我可以更快地实现功能,不用过多纠结于底层寄存器的细节。但同时也明白在一些特殊场景下,还是需要深入到寄存器层面去优化代码。
总的来说,这个实验是一个很好的入门实践,让我对STM32的开发流程、硬件控制以及相关知识有了较为全面的认识,为后续更复杂的项目开发打下了坚实的基础。