、以 STM32最小系统核心板(STM32F103C8T6)+面板板+3只红绿蓝LED 搭建电路,使用GPIOB、GPIOC、GPIOD这3个端口控制LED灯,轮流闪烁,间隔时长1秒。 1)写出程序设计思路,包括GPIOx端口的各寄存器地址和详细参数; 2)分别用汇编语言,C语言编程实现。
一、工程的建立
1、新建相关文件
新建总文件夹,用来存放本次工程的所有程序,然后再建CORE、HARDWARE、OBJ、FWLIB、SYSTEM、USER这六个文件夹。其中,HARDWARE文件夹是用来存放外设硬件代码,OBJ用来存放生成调试代码,FWLIB是各种.c和.h文件
2、打开MDK,建立新工程,保存到USER下:
芯片型号选择STM32F103C8,然后会弹出run-time environment窗口,选择取消 USER文件夹会出现这两个我们需要的文件
3、 在MDK添加项目所需要的分组以及文件:
4、 配置options for target:
其中,select folder for objects是选择生成的hex存放的目录,这里选择存放在OBJ文件夹中,create HEX file是生成hex文件,用于下载到开发板的: 以上只是参照流程配置过程截图,并不完整。
具体流程
5、 添加LED驱动代码:
(一)配置GPIO端口
时钟配置: stm32提供了一个用c语言封装好的固件库调用相应的库函数。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //打开外设GPIOB的时钟
GPIO初始化结构体 配置GPIO端口的输入输出模式设置 , 最大速率设置等
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
配置为通用推挽输出、输出速度为2M
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //输出模式为通用推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1 ; //选定端口为GPIO_Pin_1
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz; //输出速度为2M
GPIO_Init(GPIOB,&GPIO_InitStruct);
我们需要的是输出高低电平,所以要设置为输出。输出模式又有好几种输出: 推挽输出:可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。 开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)。
开漏是需要外接上拉电阻才可以输出高电平的,这里并不适合。所以需要设置为推挽输出。
至此一个GPIOB_Pin_1配置完毕。 led.h
void LED_R_TOGGLE(void);
void LED_G_TOGGLE(void);
void LED_Y_TOGGLE(void);
void LED_Init(void);
led.c
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE); //打开外设GPIOA、GPIOB、GPIOC的时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //输出模式为通用推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_12 ; //选定端口为GPIOA_Pin_12
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz; //输出速度为2M
GPIO_Init(GPIOA,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //输出模式为通用推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1 ; //选定端口为GPIOB_Pin_1
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz; //输出速度为2M
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //输出模式为通用推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_14 ; //选定端口为GPIOC_Pin_14
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz; //输出速度为2M
GPIO_Init(GPIOC,&GPIO_InitStruct);
}
void LED_R_TOGGLE(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_12);
delay_ms(500);
GPIO_ResetBits(GPIOA,GPIO_Pin_12);
}
void LED_G_TOGGLE(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_1);
delay_ms(500);
GPIO_ResetBits(GPIOB,GPIO_Pin_1);
}
void LED_Y_TOGGLE(void)
{
GPIO_SetBits(GPIOC, GPIO_Pin_14);
delay_ms(500);
GPIO_ResetBits(GPIOC,GPIO_Pin_14);
}
(二)主函数
main.c
int main(void)
{
LED_Init(); //LED初始化
delay_init(); //延时初始化
while(1)
{
LED_R_TOGGLE(); //红灯闪烁
delay_ms(500);
LED_G_TOGGLE(); //绿灯闪烁
delay_ms(500);
LED_Y_TOGGLE(); //黄灯闪烁
delay_ms(500);
}
其他函数 GPIO端口配置完成之后,如果需要确定闪烁时间,我们就需要添加延时函数,这部分系统中断,这里不做详细介绍。
二、各寄存器地址和详细参数
提到单片机,就不得不提到寄存器。根据百度百科介绍,寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。
1、寄存器地址的查找
假如我们想读取PB3引脚的电平
第一步,找到GPIOB的基地址 所有GPIOB相关的寄存器,都住在0x4001 0C00到0x4001 0FFF范围
第二步,找到端口输入寄存器的地址偏移 找到存储数据的那个屋子,结论是0x4001 0C00+8 = 0x4001 0C08
第三步,找到知道数据的那个人 PB3的数据位于从右往左数第4个。 而这个寄存器的位数是32位(虽然高16位没有用到),这就是32位的单片机的意思。每个寄存器都占据4字节,32位。而CPU的总线一次可以操作32位。 结论:PB3的输入数据位于0x4001 0C08这个地址上,这个地址上存放数据的右起第4个位就是PB3引脚对应的高低电平。 直接访问这个地址:GPIOB端口的起始地址+偏移地址
unsigned int *pGPIOB_IDR = (unsigned int *)0x40010C08;
unsigned char PB3 = *pGPIOB_IDR & 0x8;//取出从右往左数的第4位
写入其他寄存器地址
#define RCC_BASE (unsigned int)(0x40021000)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
#define GPIOB_BASE (unsigned int)(0X40010C00)
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE+0x00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE+0x04)
#define GPIOB_IDR *(unsigned int*)(GPIOB_BASE+0x08)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE+0x10)
#define GPIOB_BRR *(unsigned int*)(GPIOB_BASE+0x14)
#define GPIOB_LCKR *(unsigned int*)(GPIOB_BASE+0x18)
#define GPIOC_BASE (unsigned int)(0x40011000)
#define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00)
#define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04)
#define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08)
#define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C)
#define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10)
#define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14)
#define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18)
#define GPIOD_BASE (unsigned int)(0x40011400)
#define GPIOD_CRL *(unsigned int*)(GPIOD_BASE+0x00)
#define GPIOD_CRH *(unsigned int*)(GPIOD_BASE+0x04)
#define GPIOD_IDR *(unsigned int*)(GPIOD_BASE+0x08)
#define GPIOD_ODR *(unsigned int*)(GPIOD_BASE+0x0C)
#define GPIOD_BSRR *(unsigned int*)(GPIOD_BASE+0x10)
#define GPIOD_BRR *(unsigned int*)(GPIOD_BASE+0x14)
#define GPIOD_LCKR *(unsigned int*)(GPIOD_BASE+0x18)
2、寄存器编程
这个寄存器功能控制输出的数据为0或者1 。 所以我们控制LED延时闪烁就是控制ODR寄存器先输出1,LED灯亮,延时一段时间,控制ODR寄存器先输出0,LED灯灭,一直循环,实现流水灯的效果。 控制GPIOB_Pin_1输出为1
GPIOB_ODR |= (1<<1);
控制GPIOB_Pin_1输出为0
GPIOB_ODR &= ~(1<<1); 1左移1位,取反,与GPIOB_ODR进行与运算,将其第二位变为0
实现延时:
void delay(unsigned int i);
int main(void)
{
RCC_APB2ENR |= (7<<2);
GPIOA_CRH &= ~( 0x0F<< (4*4));
GPIOA_CRH |= (2<<4*4);
GPIOB_CRL &= ~( 0x0F<< (4*1));
GPIOB_CRL |= (2<<4*1);
GPIOC_CRH &= ~( 0x0F<< (4*6));
GPIOC_CRH |= (2<<4*6);
while(1)
{
GPIOB_ODR |= (1<<1);
delay(100);
GPIOB_ODR &= ~(1<<1);
delay(100);
GPIOA_ODR |= (1<<12);
delay(100);
GPIOA_ODR &= ~(1<<12);
delay(100);
GPIOC_ODR |= (1<<14);
delay(100);
GPIOC_ODR &= ~(1<<14);
delay(100);
}
}
void delay(unsigned int i)
{
unsigned char j;
unsigned char k;
for(;i>0;i--)
for(j =500; j>0; j--)
for(k =200; k>0; k--);
}
三、汇编语言实现流水灯
构建一个纯汇编的工程文件,配置工程文件环境时,不要选择’’startup’‘和’’core’
RCC_APB2ENR EQU 0x40021018
GPIOA_CRH EQU 0x40010804
GPIOA_ODR EQU 0x4001080C
GPIOB_CRL EQU 0x40010C00 ;寄存器映射
GPIOB_ODR EQU 0x40010C0C
GPIOC_CRH EQU 0x40011004
GPIOC_ODR EQU 0x4001100C
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
AREA RESET, DATA, READONLY
__Vectors DCD __initial_sp
DCD Reset_Handler
AREA |.text|, CODE, READONLY
THUMB
REQUIRE8
PRESERVE8
ENTRY
Reset_Handler
MainLoop BL LED2_Init
BL LED2_ON
BL Delay ;LED2灯闪烁
BL LED2_OFF
BL Delay
BL LED1_Init
BL LED1_ON
BL Delay ;LED1灯闪烁
BL LED1_OFF
BL Delay
BL LED3_Init
BL LED3_ON
BL Delay ;LED3灯闪烁
BL LED3_OFF
BL Delay
B MainLoop
LED1_Init
PUSH {R0,R1, LR}
LDR R0,=RCC_APB2ENR
ORR R0,R0,#0x08 ;开启端口GPIOB的时钟
LDR R1,=RCC_APB2ENR
STR R0,[R1]
LDR R0,=GPIOB_CRL
ORR R0,R0,#0X00000020 ;GPIOB_Pin_1配置为通用推挽输出
LDR R1,=GPIOB_CRL
STR R0,[R1]
LDR R0,=GPIOB_ODR
BIC R0,R0,#0X00000002
LDR R1,=GPIOB_ODR ;GPIO_Pin_1输出为0
STR R0,[R1]
POP {R0,R1,PC}
LED1_OFF
PUSH {R0,R1, LR}
LDR R0,=GPIOB_ODR
BIC R0,R0,#0X00000002 ;GPIO_Pin_1输出为0,LED1熄灭
LDR R1,=GPIOB_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED1_ON
PUSH {R0,R1, LR}
LDR R0,=GPIOB_ODR
ORR R0,R0,#0X00000002 ;GPIO_Pin_1输出为1,LED1亮
LDR R1,=GPIOB_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED2_Init
PUSH {R0,R1, LR}
LDR R0,=RCC_APB2ENR
ORR R0,R0,#0x04 ;打开GPIOA的时钟
LDR R1,=RCC_APB2ENR
STR R0,[R1]
LDR R0,=GPIOA_CRH
ORR R0,R0,#0X00020000 ;GPIOA_Pin_12配置为通用推挽输出
LDR R1,=GPIOA_CRH
STR R0,[R1]
LDR R0,=GPIOA_ODR
BIC R0,R0,#0X00001000
LDR R1,=GPIOA_ODR ;GPIOA_Pin_12输出为0
STR R0,[R1]
POP {R0,R1,PC}
LED2_OFF
PUSH {R0,R1, LR}
LDR R0,=GPIOA_ODR
BIC R0,R0,#0X00001000 ;GPIOA_Pin_12输出为0,LED2熄灭
LDR R1,=GPIOA_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED2_ON
PUSH {R0,R1, LR}
LDR R0,=GPIOA_ODR
ORR R0,R0,#0X00001000 ;GPIOA_Pin_12输出为1,LED2亮
LDR R1,=GPIOA_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED3_Init
PUSH {R0,R1, LR}
LDR R0,=RCC_APB2ENR
ORR R0,R0,#0x10 ;打开GPIOC的时钟
LDR R1,=RCC_APB2ENR
STR R0,[R1]
LDR R0,=GPIOC_CRH
ORR R0,R0,#0X02000000 ;GPIOC_Pin_14配置为通用推挽输出
LDR R1,=GPIOC_CRH
STR R0,[R1]
LDR R0,=GPIOC_ODR
BIC R0,R0,#0X00004000 ;GPIOC_Pin_14输出为0
LDR R1,=GPIOC_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED3_OFF
PUSH {R0,R1, LR}
LDR R0,=GPIOC_ODR
BIC R0,R0,#0X00004000 ;GPIOC_Pin_14输出为0,LED3熄灭
LDR R1,=GPIOC_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED3_ON
PUSH {R0,R1, LR}
LDR R0,=GPIOC_ODR
ORR R0,R0,#0X00004000 ;GPIOC_Pin_14输出为1,LED3亮
LDR R1,=GPIOC_ODR
STR R0,[R1]
POP {R0,R1,PC}
Delay
PUSH {R0,R1, LR}
MOVS R0,#0
MOVS R1,#0
MOVS R2,#0
DelayLoop0
ADDS R0,R0,#1
CMP R0,#300
BCC DelayLoop0
MOVS R0,#0
ADDS R1,R1,#1
CMP R1,#300
BCC DelayLoop0
MOVS R0,#0
MOVS R1,#0
ADDS R2,R2,#1
CMP R2,#15
BCC DelayLoop0
POP {R0,R1,PC}
END
stm32启动文件
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
AREA RESET, DATA, READONLY
__Vectors DCD __initial_sp
DCD Reset_Handler
AREA |.text|, CODE, READONLY
THUMB
REQUIRE8
PRESERVE8
ENTRY
Reset_Handler
LED端口初始化
LED1_Init
PUSH {R0,R1, LR}
LDR R0,=RCC_APB2ENR
ORR R0,R0,#0x08 ;开启端口GPIOB的时钟
LDR R1,=RCC_APB2ENR
STR R0,[R1]
LDR R0,=GPIOB_CRL
ORR R0,R0,#0X00000020 ;GPIOB_Pin_1配置为通用推挽输出,输出速度为2M
LDR R1,=GPIOB_CRL
STR R0,[R1]
LDR R0,=GPIOB_ODR
BIC R0,R0,#0X00000002 ;配置GPIOB_Pin_1输出为低电平,LED灯灭
LDR R1,=GPIOB_ODR
STR R0,[R1]
POP {R0,R1,PC}
延时
Delay
PUSH {R0,R1, LR}
MOVS R0,#0
MOVS R1,#0
MOVS R2,#0
DelayLoop0
ADDS R0,R0,#1
CMP R0,#300
BCC DelayLoop0
MOVS R0,#0
ADDS R1,R1,#1
CMP R1,#300
BCC DelayLoop0
MOVS R0,#0
MOVS R1,#0
ADDS R2,R2,#1
CMP R2,#15
BCC DelayLoop0
POP {R0,R1,PC}
实现延时通常有两种方法:一种是硬件延时,要用到定时器/计数器,这种方法可以提高CPU的工作效率,也能做到精确延时;另一种是软件延时,这种方法主要采用循环体进行。
四、实际演示
(一)仪器选择 1、stm32核心板103f一块 2、usb转串口一块 3、面包板一块,导线若干 (二)操作连线 GND-G 3V3-3.3 RXD-A10 TXD-A9
五、总结
经过此次实验,加深了C语言,汇编语言的认识,对GPIOx端口的各寄存器地址的了解,我也意识到了自己操作能力的不足,仍需查漏补缺。 关于面包板 参考: 《STM32F10X参考手册》 STM32寄存器的简介、地址查找,与直接操作寄存器 STM32串口下载程序 STM32最小核心板F103串口通信USART
|