STM32F103C8T6 学习笔记摘要(三)

第一节 跑马灯实验

1. 了解电路

结构图

说明一下:

  • 那几个LED的引脚线和数码管的是一样的,如果不想让LED亮,就可以把J11的接线帽拔了
  • 这里的引脚是PA0-PA7

原理图

说明一下:

  • 当J11接线帽盖上时,VCC3.3_LED就会有一个正电压
  • 而我们最终要实现跑马灯效果时,就是指定对应PA0-PA7的引脚上输入低电平,就可以了

2. 初始化LED

Led.h

cpp 复制代码
#ifndef _H_LED
#define _H_LED
#include "stm32f10x.h"
#include "system.h"

// 初始化LED
void led_init(void);

#endif

Led.c

cpp 复制代码
#include "Led.h"

// 封装点亮LED的前2步
void led_init(void)
{
	//step1: 给APB GPIOA 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//step2:将引脚的工作模式 设置为推挽输出50MHZ
	GPIO_InitTypeDef initDef;
	initDef.GPIO_Speed = GPIO_Speed_50MHz;
	initDef.GPIO_Mode = GPIO_Mode_Out_PP;
	
	// PA0
	initDef.GPIO_Pin = GPIO_Pin_0;
	GPIO_Init(GPIOA,&initDef);
	GPIO_SetBits(GPIOA,GPIO_Pin_0);// 设置为高电平-对应LED不亮

	// PA1
	initDef.GPIO_Pin = GPIO_Pin_1;
	GPIO_Init(GPIOA,&initDef);
	GPIO_SetBits(GPIOA,GPIO_Pin_1);// 设置为高电平-对应LED不亮
	
	// PA2
	initDef.GPIO_Pin = GPIO_Pin_2;
	GPIO_Init(GPIOA,&initDef);
	GPIO_SetBits(GPIOA,GPIO_Pin_2);// 设置为高电平-对应LED不亮
	
	// PA3
	initDef.GPIO_Pin = GPIO_Pin_3;
	GPIO_Init(GPIOA,&initDef);
	GPIO_SetBits(GPIOA,GPIO_Pin_3);// 设置为高电平-对应LED不亮
	
	// PA4
	initDef.GPIO_Pin = GPIO_Pin_4;
	GPIO_Init(GPIOA,&initDef);
	GPIO_SetBits(GPIOA,GPIO_Pin_4);// 设置为高电平-对应LED不亮
	
	// PA5
	initDef.GPIO_Pin = GPIO_Pin_5;
	GPIO_Init(GPIOA,&initDef);
	GPIO_SetBits(GPIOA,GPIO_Pin_5);// 设置为高电平-对应LED不亮
	
	// PA6
	initDef.GPIO_Pin = GPIO_Pin_6;
	GPIO_Init(GPIOA,&initDef);
	GPIO_SetBits(GPIOA,GPIO_Pin_6);// 设置为高电平-对应LED不亮
	
	// PA7
	initDef.GPIO_Pin = GPIO_Pin_7;
	GPIO_Init(GPIOA,&initDef);
	GPIO_SetBits(GPIOA,GPIO_Pin_7);// 设置为高电平-对应LED不亮
}

3. ledWriteData函数

led.h

cpp 复制代码
#ifndef _H_LED
#define _H_LED
#include "stm32f10x.h"
#include "system.h"

// 初始化LED
void led_init(void);

// 对应PA0-7设置为低电平 其他引脚为高电平
void ledWriteData(u8 data);

#endif

led.c

cpp 复制代码
// 对应PA0-7设置为低电平 其他引脚为高电平
void ledWriteData(u8 data)
{
	// data=1111 1110(第1个led)
	for(u8 i = 0;i < 8;i++){
		if(data & 0x01){ // 
			GPIO_WriteBit(GPIOA,GPIO_Pin_0<<i,Bit_SET);// 设置高电平
		}
		else{
			GPIO_WriteBit(GPIOA,GPIO_Pin_0<<i,Bit_RESET);// 设置低电平
		}
		data = data >> 1;// data = 0111 111(第8个led)
	}
}

说明一下:

  • 首先GPIO_Pin_0 到GPIO_Pin_n的地址是有规律的,就是GPIO_Pin_n = GPIO_Pin_0 << n
    • GPIO_Pin_0<<i 表示GPIO_Pin_0 到GPIO_PIn_7

第一次循环: 将第1个led设置为低电平

  • data & 0x01 表示1111 1110 & 0000 0001 = 0
    则进入:GPIO_WriteBit(GPIOA,GPIO_Pin_0<<i,Bit_RESET);// 设置低电平
  • data = data >> 1;// data = 0111 111(第8个led)

第二次循环: 将第8个led设置为高电平

  • data & 0x01 表示0111 1111 & 0000 0001 = 1
    则进入:GPIO_WriteBit(GPIOA,GPIO_Pin_0<<i,Bit_SET);// 设置高电平
  • data = data >> 1;// data = 1011 1111(第7个led)

依次类推,第7个 第6个led都将设置为高电平

4. main函数

cpp 复制代码
#include "stm32f10x.h"
#include "Led.h"
#include "systick.h"
int main()
{
	SysTick_Init(72);
	//1.初始化led
	led_init();
	
	u8 i = 0;
	while(1){
		// 设置第1个为低电平 其他为高电平
		ledWriteData(~(0x01<<i));
		i++;
		if(i>7){
			i=0;
		}
		// 定时
		delay_ms(100);
	}
	//return 0;
}

第二节 蜂鸣器实验

1. 了解电路

结构图

原理图

2. beep.h

cpp 复制代码
#ifndef _H_BEEP
#define _H_BEEP
#include "stm32f10x.h"
#include "system.h"
#include "systick.h"

// 位带操作-PB0
#define BEEP PBout(0)

// 初始化LED
void beep_init(void);

// time持续时间 us延迟时间
void beep_active(u16 time,u8 us);

#endif

说明一下:

  • #define BEEP PBout(0),这里使用了位带操作,方便后面将其设置为低电平

3. beep.c

cpp 复制代码
#include "beep.h"


// 初始化LED
void beep_init(void)
{
	// PB0
	// 开启时钟始能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	// 设置工作模式
	GPIO_InitTypeDef initdef;
	initdef.GPIO_Speed = GPIO_Speed_50MHz;
	initdef.GPIO_Pin = GPIO_Pin_0;
	initdef.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB,&initdef);
	
	// 设置为高电平
	GPIO_SetBits(GPIOB,GPIO_Pin_0);

}

// time持续时间 us延迟时间
void beep_active(u16 time,u8 us)
{
	while(time--)
	{
		BEEP=!BEEP;
		delay_us(us);
	}
}

4. main.c

cpp 复制代码
#include "stm32f10x.h"
#include "beep.h"
#include "systick.h"

int main()
{
	SysTick_Init(72);
	// 1. 初始化蜂鸣器
	beep_init();
	
	while(1)
	{
		// 2. 活跃蜂鸣器
		beep_active(100,100);
		delay_ms(200);
	}
}

第三节 数码管实验

1. 数码管简介

数码管是一种半导体发光器件,其基本单元是发光二极管。数码管也称LED数码管,不同行业人士对数码管的称呼不一样,其实都是同样的产品。数码管按段数可分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管单元,也就是多一个小数点(DP),这个小数点可以更精确的表示数码管想要显示的内容;按能显示多少个(8)可分为 1 位、 2 位、3 位、4 位、5位、6 位、7 位等数码管。按发光二极管单元连接方式可分为共阳极数码管共阴极数码管

2. 数码管显示原理

共阴极时,高电平,亮,而共阳极反之

从上图可看出,一位数码管的引脚是 10 个,显示一个8 字需要7 个小段,另外还有一个小数点,所以其内部一共有8 个小的发光二极管,最后还有一个公共端,多数生产商为了封装统一,单位数码管都封装10 个引脚,其中第3和第 8 引脚是连接在一起的。而它们的公共端又可分为共阳极和共阴极,图中间为共阳极内部原理图,右图为共阴极内部原理图。

3. 了解电路

结构图

原理图

说明一下:

  • 对于PB3 PB4 PB5 来说需要关闭调试,才能当做基础的GPIO口使用

4. 关于74HC245芯片

芯片 74HC245 是一种三态输出、八路信号收发器,主要应用于大屏显示,以及其它的消费类电子产品中增加驱动。

5. 简单总结

  • 位选三条线(LSA、LSB、LSC)PB5、4、3
  • 段选8条线 PA0 - PA7

6. smg.h

cpp 复制代码
#ifndef _H_SMG
#define _H_SMG
#include "stm32f10x.h"
#include "system.h"
#define LSA PBout(5)
#define LSB PBout(4)
#define LSC PBout(3)

// 初始化smg
void smg_init(void);

// 设置低电平
void smgWriteData(u8 data);

#endif

7. smg.c

smg_init函数

cpp 复制代码
void smg_init(void)
{
	//1.给APB2 GPIOA GPIOB 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	// 关闭PB3 PB4 PB5调试,使其能当作正常GPIO口使用
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);
	
	//2. 设置工作模式
	GPIO_InitTypeDef initDef;
	initDef.GPIO_Speed= GPIO_Speed_50MHz;
	initDef.GPIO_Mode = GPIO_Mode_Out_PP;
	
	//PA0-PA7
	initDef.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5  | GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_Init(GPIOA,&initDef);	
	//PB3-PB5
	initDef.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_4 | GPIO_Pin_3;
	GPIO_Init(GPIOB,&initDef);
}	

smgWriteData函数

cpp 复制代码
void smgWriteData(u8 data)
{
	// data=1111 1110(第1个led)
	for(u8 i = 0;i < 8;i++){
		if(data & 0x01){ // 
			GPIO_WriteBit(GPIOA,GPIO_Pin_0<<i,Bit_SET);// 设置高电平
		}
		else{
			GPIO_WriteBit(GPIOA,GPIO_Pin_0<<i,Bit_RESET);// 设置低电平
		}
		data = data >> 1;// data = 0111 111(第8个led)
	}
}

8. main.c

cpp 复制代码
#include "stm32f10x.h"
#include "smg.h"
#include "systick.h"

u8 gsmg_code[17] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
			0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71
		};

int main()
{
	SysTick_Init(72);
	// 1. 初始化蜂鸣器
	smg_init();
	
	while(1)
	{
		// 在那个位置上显示 - 位选
		LSC = 0;LSB = 0;LSA = 1;
		
		// 在对应的位置上显示什么内容 - 段选
		for(u8 i = 0;i < 10;i++)
		{	
				smgWriteData(gsmg_code[i]);
				delay_ms(1000);
		}

	}
}

第四节 独立按键实验-默认是高电平

按键是一种电子开关,使用时轻轻按开关按钮就可使开关接通,当松开手时,开关断开。我们开发板上使用的按键及内部简易图如下图所示

按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态,如果按键按下,初始导通状态变为断开,初始断开状态变为导通。通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,电压信号如下图所示:

由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的,一般为 5ms 到 10ms。按键稳定闭合时间的长短则由操作人员的按键动作决定的,一般为零点几秒至数秒。按键抖动会引起按键被误读多次。为了确保 CPU 对按键的一次闭合仅作一次处理,必须进行消抖

按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。我们开发板也是采用软件消抖,一般来说一个简单的按键消抖就是先读取按键的状态如果得到按键按下之后,延时10ms,再次读取按键的状态, 如果按键还是按下状态,那么说明按键已经按下。**其中延时10ms 就是软件消抖处理,**至于硬件消抖,大家可以百度了解下,网上都有非常详细的介绍

1. 了解电路

结构图

原理图

说明一下:

  • 默认是高电平,按下按钮之后才能变成低电平

2. key初始化

这个板子上有4个按键,我想让它控制对应的led0 led1 led2 led3 led4

key.h

cpp 复制代码
#ifndef _H_KEY
#define _H_KEY
#include "stm32f10x.h"
#include "system.h"
#include "systick.h"

// 位带操作
#define KEY1 PAin(15)
#define KEY2 PAin(14)
#define KEY3 PAin(13)
#define KEY4 PAin(12)

// 初始化按键
void key_init(void);

// 检查按键状态
u8 keyScan(u8 mode);

#endif

key_init函数

cpp 复制代码
#include "key.h"

// 初始化按键
void key_init(void){
	
	// 1gpio时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能是GPIOA
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);	// 使能 AFIO 时钟
	// 这几根线上也有调试功能
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);// 关闭JTAG 和 SWD 功能
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);	// 关闭JTAG 和 SWD 功能

	// 设置工作模式
	GPIO_InitTypeDef initDef;
	initDef.GPIO_Mode = GPIO_Mode_IPU;// 上拉输入模式
	initDef.GPIO_Speed = GPIO_Speed_50MHz;
	
	initDef.GPIO_Pin = GPIO_Pin_12;
	GPIO_Init(GPIOA,&initDef);
	
	initDef.GPIO_Pin = GPIO_Pin_13;
	GPIO_Init(GPIOA,&initDef);
	
	initDef.GPIO_Pin = GPIO_Pin_14;
	GPIO_Init(GPIOA,&initDef);
	
	initDef.GPIO_Pin = GPIO_Pin_15;
	GPIO_Init(GPIOA,&initDef);
}

说明一下:

  • PA12-PA15这几根线上也有调式功能,初始化时需要关闭
  • 注意:它的工作模式应该设置为上拉输入

led.h

3. key.c

cpp 复制代码
#include "key.h"

// 初始化按键
void key_init(void){
	
	// 1gpio时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能是GPIOA
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);	// 使能 AFIO 时钟
	// 这几根线上也有调试功能
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);// 关闭JTAG 和 SWD 功能
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);	// 关闭JTAG 和 SWD 功能

	// 设置工作模式
	GPIO_InitTypeDef initDef;
	initDef.GPIO_Mode = GPIO_Mode_IPU;// 上拉输入模式
	initDef.GPIO_Speed = GPIO_Speed_50MHz;
	
	initDef.GPIO_Pin = GPIO_Pin_12;
	GPIO_Init(GPIOA,&initDef);
	
	initDef.GPIO_Pin = GPIO_Pin_13;
	GPIO_Init(GPIOA,&initDef);
	
	initDef.GPIO_Pin = GPIO_Pin_14;
	GPIO_Init(GPIOA,&initDef);
	
	initDef.GPIO_Pin = GPIO_Pin_15;
	GPIO_Init(GPIOA,&initDef);
}

// 检查按键状态
u8 keyScan(u8 mode)
{
	static u8 flag = 1;
	if(mode) flag = 1;// 持续检测-长按按键出现闪烁效果
	if(flag && (KEY1 == 0 ||KEY2 == 0 || KEY3 == 0 || KEY4 == 0))	{
		delay_ms(10);// 消抖
		flag = 0;
        // 返回值:表示确定是那个按键被按下,切换对应的LED状态
		if(KEY1 == 0) return 1;
		if(KEY2 == 0) return 2;
		if(KEY3 == 0) return 3;
		if(KEY4 == 0) return 4;
	}
	// 此时所有按键都没有被按下
	else if(KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1){
		flag = 1;
	}
	else{
		return 0;
	}
}

说明一下

  • mode = 1表示长按按键时出现闪烁效果
  • mode = 0表示长按按键出现常亮效果

4. main.c

cpp 复制代码
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "systick.h"


int main()
{
	// 滴答定时器
	SysTick_Init(72);
	
	// 初始化
	led_init();
	key_init();
	
	u8 i = 0;
	while(1)
	{
		i = keyScan(0);
		if(i == 1) LED1 = !LED1;
		else if(i == 2) LED2 = !LED2;
		else if(i == 3) LED3 = !LED3;
		else if(i == 4) LED4 = !LED4;
		
		delay_ms(150);
	}
}

第五节. 矩阵按键实验

1. 了解电路

结构图

说明一下:

  • 我们要做的是让前2排按键点亮led1-led8
  • 后两排要做的是点灭led1-led8

原理图

2. key.h

我们要做的是让前2排按键点亮led1-led8****, 后两排要做的是点灭led1-led8

Led.h

key.h

cpp 复制代码
#ifndef _H_KEY
#define _H_KEY
#include "stm32f10x.h"
#include "system.h"
#include "systick.h"

// 四根行线
#define KEY_H1_PIN GPIO_Pin_8
#define KEY_H2_PIN GPIO_Pin_9
#define KEY_H3_PIN GPIO_Pin_10
#define KEY_H4_PIN GPIO_Pin_11

// 四根列线
#define KEY_L1_PIN GPIO_Pin_12
#define KEY_L2_PIN GPIO_Pin_13
#define KEY_L3_PIN GPIO_Pin_14
#define KEY_L4_PIN GPIO_Pin_15


// 初始化按键
void key_init(void);

// 检查按键状态
u8 keyScan(void);

#endif

3. key_init函数

cpp 复制代码
#include "key.h"

// 初始化按键
void key_init(void){
	
	// 1gpio时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能是GPIOA
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);	// 使能 AFIO 时钟
	// 这几根线上也有调试功能
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);// 关闭JTAG 和 SWD 功能
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);	// 关闭JTAG 和 SWD 功能

	// 四根行线设置为下拉输入 - 设置工作模式
	GPIO_InitTypeDef initDef;
	initDef.GPIO_Speed = GPIO_Speed_50MHz;
	initDef.GPIO_Mode = GPIO_Mode_IPD;// 下拉输入模式
	initDef.GPIO_Pin = GPIO_Pin_12;
	GPIO_Init(GPIOB,&initDef);
	initDef.GPIO_Pin = GPIO_Pin_13;
	GPIO_Init(GPIOB,&initDef);
	initDef.GPIO_Pin = GPIO_Pin_14;
	GPIO_Init(GPIOB,&initDef);
	initDef.GPIO_Pin = GPIO_Pin_15;
	GPIO_Init(GPIOB,&initDef);
	
	// 四根列线设置为推挽输出 - 设置工作模式
	initDef.GPIO_Mode = GPIO_Mode_Out_PP;// 推挽输出模式
	initDef.GPIO_Pin = GPIO_Pin_8;
	GPIO_Init(GPIOB,&initDef);
	initDef.GPIO_Pin = GPIO_Pin_9;
	GPIO_Init(GPIOB,&initDef);
	initDef.GPIO_Pin = GPIO_Pin_10;
	GPIO_Init(GPIOB,&initDef);
	initDef.GPIO_Pin = GPIO_Pin_11;
	GPIO_Init(GPIOB,&initDef);
	
}

说明一下:

  • 四根行线设置为下拉输入 - 设置工作模式
  • 四根列线设置为推挽输出 - 设置工作模式

4. keyScan函数(有bug)

GPIO_ReadInputDataBit - 读取指定端口管脚的输入

keyScan函数

cpp 复制代码
// 检查按键状态
u8 keyScan()
{
	// 设置前四行为高电平
	GPIO_SetBits(GPIOB,KEY_H1_PIN);
	GPIO_SetBits(GPIOB,KEY_H2_PIN);
	GPIO_SetBits(GPIOB,KEY_H3_PIN);
	GPIO_SetBits(GPIOB,KEY_H4_PIN);
	
	// 读取指定端口管脚的输入
	if((GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN)) 
		| (GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN))
		| (GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN))
		| (GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN)) == 0){
			// 没有按键被按下
			return 0;
	}
	else{
		// 等待一会儿 再读取
		// 读取指定端口管脚的输入
		if((GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN)) 
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN)) == 0){
				// 没有按键被按下
				return 0;
		}
	}
	
}

keyScan函数

cpp 复制代码
// 检查按键状态
u8 keyScan()
{
	// 设置前四行为高电平
	GPIO_SetBits(GPIOB,KEY_H1_PIN);
	GPIO_SetBits(GPIOB,KEY_H2_PIN);
	GPIO_SetBits(GPIOB,KEY_H3_PIN);
	GPIO_SetBits(GPIOB,KEY_H4_PIN);
	
	// 读取指定端口管脚的输入
	if((GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN)) 
		| (GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN))
		| (GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN))
		| (GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN)) == 0){
			// 没有按键被按下
			return 0;
	}
	else{
		// 等待一会儿 再读取
		// 读取指定端口管脚的输入
		if((GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN)) 
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN)) == 0){
				// 没有按键被按下
				return 0;
		}
	}
	
	// 检测是否有按键被按下
	u8 col1,col2,col3,col4;
	// 第一行:设置第一行为低电平 其他为高电平
	GPIO_SetBits(GPIOB,KEY_H1_PIN);
	GPIO_ResetBits(GPIOB,KEY_H2_PIN);
	GPIO_ResetBits(GPIOB,KEY_H3_PIN);
	GPIO_ResetBits(GPIOB,KEY_H4_PIN);
	// 读取四列的状态	
	col1 = GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN);
	col2 = GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN);
	col3 = GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN);
	col4 = GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN);
	if(col1 == 1 && col2 == 0&& col3==0 && col4 == 0) return 1;
	if(col1 == 0 && col2 == 1&& col3==0 && col4 == 0) return 2;
	if(col1 == 0 && col2 == 0&& col3==1 && col4 == 0) return 3;
	if(col1 == 0 && col2 == 0&& col3==0 && col4 == 1) return 4;
	while((GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN)) 
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN)) > 0);
	
	// 第二行:设置第二行为低电平 其他为高电平
	GPIO_ResetBits(GPIOB,KEY_H1_PIN);
	GPIO_SetBits(GPIOB,KEY_H2_PIN);
	GPIO_ResetBits(GPIOB,KEY_H3_PIN);
	GPIO_ResetBits(GPIOB,KEY_H4_PIN);
	// 读取四列的状态	
	col1 = GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN);
	col2 = GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN);
	col3 = GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN);
	col4 = GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN);
	if(col1 == 1 && col2 == 0&& col3==0 && col4 == 0) return 5;
	if(col1 == 0 && col2 == 1&& col3==0 && col4 == 0) return 6;
	if(col1 == 0 && col2 == 0&& col3==1 && col4 == 0) return 7;
	if(col1 == 0 && col2 == 0&& col3==0 && col4 == 1) return 8;
	while((GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN)) 
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN)) > 0);
	
	
	// 第三行:设置第三行为低电平 其他为高电平
	GPIO_ResetBits(GPIOB,KEY_H1_PIN);
	GPIO_ResetBits(GPIOB,KEY_H2_PIN);
	GPIO_SetBits(GPIOB,KEY_H3_PIN);
	GPIO_ResetBits(GPIOB,KEY_H4_PIN);
	// 读取四列的状态	
	col1 = GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN);
	col2 = GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN);
	col3 = GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN);
	col4 = GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN);
	if(col1 == 1 && col2 == 0&& col3==0 && col4 == 0) return 9;
	if(col1 == 0 && col2 == 1&& col3==0 && col4 == 0) return 10;
	if(col1 == 0 && col2 == 0&& col3==1 && col4 == 0) return 11;
	if(col1 == 0 && col2 == 0&& col3==0 && col4 == 1) return 12;
	while((GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN)) 
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN)) > 0);
	
	
	// 第四行:设置第四行为低电平 其他为高电平
	GPIO_ResetBits(GPIOB,KEY_H1_PIN);
	GPIO_ResetBits(GPIOB,KEY_H2_PIN);
	GPIO_ResetBits(GPIOB,KEY_H3_PIN);
	GPIO_SetBits(GPIOB,KEY_H4_PIN);
	// 读取四列的状态	
	col1 = GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN);
	col2 = GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN);
	col3 = GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN);
	col4 = GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN);
	if(col1 == 1 && col2 == 0&& col3==0 && col4 == 0) return 13;
	if(col1 == 0 && col2 == 1&& col3==0 && col4 == 0) return 14;
	if(col1 == 0 && col2 == 0&& col3==1 && col4 == 0) return 15;
	if(col1 == 0 && col2 == 0&& col3==0 && col4 == 1) return 16;
	while((GPIO_ReadInputDataBit(GPIOB,KEY_L1_PIN)) 
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L2_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L3_PIN))
			| (GPIO_ReadInputDataBit(GPIOB,KEY_L4_PIN)) > 0);

    return 0;
	
}

5. main.c

cpp 复制代码
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "systick.h"


int main()
{
	// 滴答定时器
	SysTick_Init(72);
	
	// 初始化
	led_init();
	key_init();
	
	u8 i = 0;
	while(1)
	{
		i = keyScan();
		// 前2排按键按下 点亮LED
		if(i == 1) LED1 = 0;
		else if(i == 2) LED2 = 0;
		else if(i == 3) LED3 = 0;
		else if(i == 4) LED4 = 0;
		else if(i == 5) LED5 = 0;
		else if(i == 6) LED6 = 0;
		else if(i == 7) LED7 = 0;
		else if(i == 8) LED8 = 0;
		
		// 后2排按键按下 点灭LED
		else if(i == 9) LED1 = 1;
		else if(i == 10) LED2 = 1;
		else if(i == 11) LED3 = 1;
		else if(i == 12) LED4 = 1;
		else if(i == 13) LED5 = 1;
		else if(i == 14) LED6 = 1;
		else if(i == 15) LED7 = 1;
		else if(i == 16) LED8 = 1;
		delay_ms(150);
	}
}

第六节 点阵实验

1. 了解电路

结构图

原理图

说明一下:

  • 当需要串联其他点阵时,就需要使用VCC3.39号线

2. 74HC595芯片介绍

点阵 LED 基本结构

  • 右侧(ROW1-ROW8) 控制的是行(阳极/正极)。

  • 下侧(SMG A ~ SMG DP) 控制的是列(阴极/负极)

74HC595 是一个 8 位串行转并行移位寄存器,它的作用是:

  • 将你通过 **SER**引脚串行输入的 8 位数据,在 SRCLK(移位时钟)作用下依次移入。
  • 当你触发 RCLK(锁存时钟)的 上升沿 时,数据会被输出到 QA~QH(即点阵的行控制)。

点阵工作流程(动态扫描)

  • 只有设置为高电平有效 只有设置为低电平有效 则我们点亮第 1 行第 1 列的 LED(即 A1-K1 对应 LED):
  • 行为0x08 列为0x7F

关键信号控制如下:

工作原理

  • 采用高位的输入 数据到 移位寄存器中

3. dzled.h

cpp 复制代码
#ifndef _H_DZLED
#define _H_DZLED
#include "stm32f10x.h"
#include "system.h"
#include "systick.h"

// 三个总要信号 - 产生上升沿
#define SER PBout(4)
#define RCLK PBout(5)
#define SRCLK PBout(3)

// 初始化点阵
void dzled_init(void);

// 行输入
void writeRowData(u8 data);

// 列输入
void writeColData(u8 data);

#endif

4. dzled.c

dzled_init函数

cpp 复制代码
// 初始化点阵
void dzled_init(void)
{
	//1.给APB2 GPIOA GPIOB 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	// 关闭PB3 PB4 PB5调试,使其能当作正常GPIO口使用
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);
	
	//2. 设置工作模式
	GPIO_InitTypeDef initDef;
	initDef.GPIO_Speed= GPIO_Speed_50MHz;
	initDef.GPIO_Mode = GPIO_Mode_Out_PP;
	
	//PA0-PA7
	initDef.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5  | GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_Init(GPIOA,&initDef);	
	//PB3-PB5
	initDef.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_4 | GPIO_Pin_3;
	GPIO_Init(GPIOB,&initDef);

}

说明一下:

  • 由于数码管和点阵的使用的同一根线,则初始化时直接拷贝过来就行了

writeColData函数

cpp 复制代码
// 列输入
void writeColData(u8 data)
{
	// data=1111 1110(第1个led)
	for(u8 i = 0;i < 8;i++){
		if(data & 0x01){ // 
			GPIO_WriteBit(GPIOA,GPIO_Pin_0<<i,Bit_SET);// 设置高电平
		}
		else{
			GPIO_WriteBit(GPIOA,GPIO_Pin_0<<i,Bit_RESET);// 设置低电平
		}
		data = data >> 1;// data = 0111 111(第8个led)
	}
}

说明一下:

  • 点阵的列输入又和跑马灯的程序一致,则直接拷贝过来就行了

writeRowData函数

cpp 复制代码
// 行输入
void writeRowData(u8 data)
{
	for(u8 i = 0;i < 8;i++){
		// 1.数据从高位依次输入
		SER = data>>7;// 每一次得到数据的最高位
		// 移位寄存器的时钟线变化
		SRCLK = 0;
		delay_us(1);
		SRCLK = 1;//上升沿信号 移位
		data <<= 1;// 把已经输入的数据丢弃
	}
	// 把数据从移位寄存器 移动到 存储寄存器里面
	RCLK = 0;
	delay_us(1);
	RCLK = 1;// 给一个上升沿信号
}

5. main.c

cpp 复制代码
#include "stm32f10x.h"
#include "led.h"
#include "dzled.h"
#include "systick.h"


int main()
{
	// 滴答定时器
	SysTick_Init(72);
	
	// 初始化
	dzled_init();
	
	// 点亮最左上角的LED
	writeRowData(0x80);// 行只有设置高电平有效
	writeColData(0x7F);// 列只有设置低电平有效
	u8 i = 0;
	while(1)
	{
		
		delay_ms(150);
	}
}

说明一下:

  • 直接调用列输入 行输入就行了

第七节 动力模块->直流电机

直流电机是指能将直流电能转换成机械能(直流电动机)或将机械能转换成直流电能(直流发电机)的旋转电机

直流电机的结构应由定子转子两大部分组成,直流电机没有正负之分,在两端加上直流电就能工作

开发板配置的直流电机为5V 直流电机,其主要参数如下:轴长:8mm 轴径:2mm 电压:1-6V 参考电流:0.35-0.4A 3V 转速:17000-18000 转每分钟 外观实物图如下:

1. ULN2003 芯片

单片机主要是用来控制而非驱动,如果直接使用芯片的GPIO管脚去驱动大功率器件,要么将芯片烧坏,要么就驱动不起来。所以要驱动大功率器件,比如电机。就必须搭建驱动电路,开发板上板载的驱动芯片是ULN2003,该芯片是一个单片高电压、高电流的达林顿晶体管阵列集成电路。不仅可以用来驱动直流电机,还可用来驱动五线四相步进电机,比如28BYJ48 步进电机。

2. 了解电路

简单来说:使用PB8就行了

结构图

原理图

3. moto.h

cpp 复制代码
#ifndef _H_MOTO
#define _H_MOTO
#include "stm32f10x.h"
#include "system.h"
#include "systick.h"
// PB8的引脚 - 位带操作
#define MOTO PBout(8)

// 初始化电机
void moto_init(void);

#endif

4. moto.c

cpp 复制代码
#include "moto.h"

// 初始化电机
void moto_init(void)
{
    // 1. 时钟始能
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    // 2.关闭调试功能
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);// 关闭JTAG
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);
    // 3. 初始化
    GPIO_InitTypeDef initDef;// 结构体
    initDef.GPIO_Mode = GPIO_Mode_Out_PP;// 推挽输出
    initDef.GPIO_Speed = GPIO_Speed_50MHz;
    initDef.GPIO_Pin = GPIO_Pin_8;
    GPIO_Init(GPIOB,&initDef);
    GPIO_SetBits(GPIOB,GPIO_Pin_8);// 设置为高电平

}

5. main.c

cpp 复制代码
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "moto.h"
#include "systick.h"

int main()
{
	// 滴答定时器
	SysTick_Init(72);

	// 初始化
	led_init();
	moto_init();

	while(1)
	{
		// 检测矩阵按键中: 那个按键被按下
		u8 i = keyScan();
		if(i == 1){
			// 高低电平之间切换
			LED1 = !LED1;
			MOTO = !MOTO;
		}
		delay_ms(150);
	}
}

说明一下:

  • 最终效果: 点击矩阵按键上的第一个按键,就可控制第一个led的亮灭电机的启动

第八节 步进电机

步进电机是将电脉冲信号转变为角位移或线位移的开环控制元件

工作原理

单极性

双极性

2.1 28BYJ-48 步进电机简介

28BYJ48 步进电机自带减速器,为五线四相单极性步进电机,直径为28mm,

28BYJ48 电机内部结构等效图如下所示:

28BYJ48 步进电机主要参数如下所示:

在上图中 28BYJ48 步进电机主要参数中可以看到有一个减速比:1:64,步进角为 5.625/64 度,如果需要转动一圈,那么需要 360/5.625*64=4096 个脉冲信号。 减速比这个和之前介绍的直流减速电机有点类似,所以28BYJ48 步进电机实际上是:减速齿轮+步进电机组成,28BYJ48 步进电机减速齿轮实物图如下所示:

减速齿轮计算方法如下所示:

2.2 了解电路

1、MOTO IN1-4全部用到,所以端口是PB8-9PB12-13

2、根据原理了解,需要实现的是电位的切换驱动旋转

3、给一个高电平,输出低电平,形成电流环路,产生磁场

结构图

原理图

2.3 moto.h

cpp 复制代码
#ifndef _H_MOTO
#define _H_MOTO
#include "stm32f10x.h"
#include "system.h"
#include "systick.h"
// 位带操作
#define MOTOR_IN1 PBout(8)
#define MOTOR_IN2 PBout(9)
#define MOTOR_IN3 PBout(12)
#define MOTOR_IN4 PBout(13)

// 初始化步进电机
void moto_init(void);

// 运行步进电机
void motor_run(u8 step,u8 dir,u8 speed,u16 angle,u8 sta);
#endif

参数说明:

  • step表示指定步进控制节拍,可选值4或8
  • dir表示方向选择,其中1表示顺时针,0表示逆时针
  • speed表示速度,可选范围为1-5
  • angle表示角度,可选范围为0-360
  • sta表示运行状态,其中1启动,0停止

2.4 moto.c

cpp 复制代码
#include "moto.h"

// 初始化电机
void moto_init(void)
{
    // 1. 时钟始能
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    // 2.关闭调试功能
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);// 关闭JTAG
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);
    // 3. 初始化
    GPIO_InitTypeDef initDef;// 结构体
    initDef.GPIO_Mode = GPIO_Mode_Out_PP;// 推挽输出
    initDef.GPIO_Speed = GPIO_Speed_50MHz;

    initDef.GPIO_Pin = GPIO_Pin_8;
    GPIO_Init(GPIOB,&initDef);

    initDef.GPIO_Pin = GPIO_Pin_9;
    GPIO_Init(GPIOB,&initDef);

    initDef.GPIO_Pin = GPIO_Pin_12;
    GPIO_Init(GPIOB,&initDef);

    initDef.GPIO_Pin = GPIO_Pin_13;
    GPIO_Init(GPIOB,&initDef);
}

// 运行步进电机
void motor_run(u8 step,u8 dir,u8 speed,u16 angle,u8 sta)
{
    // 启动
    if(sta){
        // 顺
        if(dir){
            // 转动角度所对应的信号个数
            for(u8 j = 0;j < angle*64/45;j++){
                // 把整个一圈分为4 / 8节拍
                // 每个节拍应该怎么转动 电机应该是什么状态
                for(int i = 0;i < 8;i+=(8/step)){
                    switch (i)
                    {
                        case 0: MOTOR_IN1=1;MOTOR_IN2=0;MOTOR_IN3=0;MOTOR_IN4=0;break;
                        case 1: MOTOR_IN1=1;MOTOR_IN2=1;MOTOR_IN3=0;MOTOR_IN4=0;break;
                        case 2: MOTOR_IN1=0;MOTOR_IN2=1;MOTOR_IN3=0;MOTOR_IN4=0;break;
                        case 3: MOTOR_IN1=0;MOTOR_IN2=1;MOTOR_IN3=1;MOTOR_IN4=0;break;
                        case 4: MOTOR_IN1=0;MOTOR_IN2=0;MOTOR_IN3=1;MOTOR_IN4=0;break;
                        case 5: MOTOR_IN1=0;MOTOR_IN2=0;MOTOR_IN3=1;MOTOR_IN4=1;break;
                        case 6: MOTOR_IN1=0;MOTOR_IN2=0;MOTOR_IN3=0;MOTOR_IN4=1;break;
                        case 7: MOTOR_IN1=1;MOTOR_IN2=0;MOTOR_IN3=0;MOTOR_IN4=1;break;
                    }
                    delay_ms(speed);
                }
            }
        }
        else{// 逆时针
            // 转动角度所对应的信号个数
            for(u8 j = 0;j < angle*64/45;j++){
                // 把整个一圈分为4 / 8节拍
                // 每个节拍应该怎么转动 电机应该是什么状态
                for(int i = 0;i < 8;i+=(8/step)){
                    switch (i)
                    {
                        case 0: MOTOR_IN1=1;MOTOR_IN2=0;MOTOR_IN3=0;MOTOR_IN4=1;break;
                        case 1: MOTOR_IN1=0;MOTOR_IN2=0;MOTOR_IN3=0;MOTOR_IN4=1;break;
                        case 2: MOTOR_IN1=0;MOTOR_IN2=0;MOTOR_IN3=1;MOTOR_IN4=1;break;
                        case 3: MOTOR_IN1=0;MOTOR_IN2=0;MOTOR_IN3=1;MOTOR_IN4=0;break;
                        case 4: MOTOR_IN1=0;MOTOR_IN2=1;MOTOR_IN3=1;MOTOR_IN4=0;break;
                        case 5: MOTOR_IN1=0;MOTOR_IN2=1;MOTOR_IN3=0;MOTOR_IN4=0;break;
                        case 6: MOTOR_IN1=1;MOTOR_IN2=1;MOTOR_IN3=0;MOTOR_IN4=0;break;
                        case 7: MOTOR_IN1=1;MOTOR_IN2=0;MOTOR_IN3=0;MOTOR_IN4=0;break;
                    }
                    delay_ms(speed);
                }
            }
        }
    }
}

2.5 main.c

cpp 复制代码
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "moto.h"
#include "systick.h"

int main()
{
	// 滴答定时器
	SysTick_Init(72);

	// 初始化
	led_init();
	moto_init();
	u8 dir = 1;
	u8 sta = 0;
	u8 speed = 1;// 取值1-5
	while(1)
	{
		// 检测矩阵按键中: 那个按键被按下
		u8 i = keyScan();
		if(i == 1){
			sta = !sta;// 启动/停止
		}
		else if(i == 2){
			dir = !dir;// 正转/反转
		}
		else if(i == 3){
			// 加速度
			if(speed > 1) speed--;
		}
		else if(i == 4){
			// 减sudo
			if(speed<5) speed++;
		}
		else{
			delay_ms(10);
		}
		motor_run(8,dir,speed,1,sta);
		delay_ms(150);
	}
}
相关推荐
DevangLic几秒前
数据结构-单链表的头插法和尾插法-
数据结构·c++·学习
IMPYLH2 小时前
Python 的内置函数 hasattr
笔记·python
moxiaoran57532 小时前
uni-app项目实战笔记17--获取系统信息getSystemInfo状态栏和胶囊按钮
笔记·uni-app
智者知已应修善业2 小时前
【51单片机2位数码管100毫秒的9.9秒表】2022-5-16
c语言·经验分享·笔记·单片机·嵌入式硬件·51单片机
知青春之宝贵2 小时前
BEV感知-课程学习详细记录(自动驾驶之心课程)
学习
HeartException2 小时前
Spring Boot + MyBatis Plus + SpringAI + Vue 毕设项目开发全解析(源码)
人工智能·spring boot·学习
饕餮争锋3 小时前
设计模式笔记_创建型_单例模式
java·笔记·设计模式
teeeeeeemo3 小时前
Number.toFixed() 与 Math.round() 深度对比解析
开发语言·前端·javascript·笔记
蚊子不吸吸4 小时前
在Docker、KVM、K8S常见主要命令以及在Centos7.9中部署的关键步骤学习备存
linux·学习·docker·kubernetes·centos·k8s·kvm
2401_888859714 小时前
STM32 CAN简介及帧格式
stm32·单片机·嵌入式硬件