STM32F103系列:使用Proteus仿真实现4*4矩阵键盘与LCD1602显示的连接

        最近一直在肝STM32系列芯片,这里我想要检验一下自己的初步学习成果,简单的运用一下IO口的配置,代码使用正点的库函数模板写的,仿真软件是Proteus 8.13,代码编写编译软件是Keil MDK5,仿真芯片是STM32F103R6小容量芯片。


目录

一、硬件设计

 二、软件设计

1、LCD1602的编写       

2、4*4 矩阵键盘的编写

 3、串口通信的编写

 4、主函数

三、仿真

 四、结尾


一、硬件设计

        stm32f103R6芯片的IO口中以下为硬件的具体配置

        PA9、PA10为串口通讯的发送和接收引脚

        PA15我用到了其他的功能(在本文中并没有用到,也懒得删除)

        PB0-PB3是矩阵键盘的列输入(需要设置为输入,至于是下拉输入还是上拉输入,需要看你代码怎么写,我用的是下拉输入)

        PB4-PB7是矩阵键盘的行输出(需要设置为推挽输出,因为我PB0-PB3设置的是下拉输入,所以输出的初始化应该设置为低电平)

        PB13-PB15是LCD1602的功能端,分别为E端、RW端、RS端

        PC0-PC7为LCD1602的数据输入端

        以下为Proteus中的连接图。

     

 二、软件设计

         本程序主要记录4*4矩阵键盘的逻辑和LCD1602的驱动方法,次要编写串口通信的程序。在矩阵键盘中,这里用的是按行扫描的方法,每次只返回一个值,不会产生重复的按键响应,并且每按下一次按键只会给串口发送一次数据,不会重复发送,松开按键后才会等待下一次按键响应;LCD1602的驱动方法可以去查看数据手册,这里只列出几个重要的配置数据,本程序中有显示单个字节,显示一行字符等;另外例如在严谨的程序中,防止程序跑飞,还应加入看门狗之类的,本文并没有列出,有需要的请自行在主函数中加入喂狗就可以。

1、LCD1602的编写       

        lcd1602指令介绍:

        指令1:清显示,指令码01H,光标复位到地址00H位置

        指令2:光标复位,光标返回到地址00H

        指令3:光标和显示位置设置I/D,光标移动方向,高电平右移,低电平左移,S:屏幕上所有文字是否左移或右移,高电平表示有效,低电平表示无效。

        指令4:显示开关控制。D:控制整体的显示开与关,高电平表示开显示,低电平表示关显示。C:控制光标的开与关,高电平表示有光标,低电平表示无光标B:控制光标是否闪烁,高电平闪烁,低电平不闪烁。

        指令5:光标或显示移位 S/C :高电平时显示移动的文字,低电平时移动光标

        指令6:功能设置命令 DL:高电平时为4位总线,低电平时为8位总线N:低电平时为单行显示,高电平时为双行显示,F:低电平时显示5X7的点阵字符,高电平时显示5X10的显示字符。

        指令7:字符发生器RAM地址设置。

        指令8:DDRAM地址设置。

        指令9:读忙信号和光标地址 BF:忙标志位,高电平表示忙,此时模块不能接收命令或数据,如果为低电平表示不忙。

        本程序中的初始化函数中用到的有指令1、指令4、指令6、指令7。

        下图为写指令和写数据所用图。

         当写指令时,需要先将RS和RL全部置低电平,然后再写指令;当写数据时,需要先将RS置为高电平,将RW置为低电平,然后再写入数据;当使能端口E有上升沿时便会将执行指令或者执行数据。

 lcd1602.h文件

#ifndef __LCD1602_H
#define __LCD1602_H
#include "stm32f10x.h"

//PC引脚定义为 lcd的数据输入端
#define LCD_DATA GPIOC

//lcd的RS端定义 PB15
#define LCD_RS_EN GPIO_SetBits(GPIOB,GPIO_Pin_15)
#define LCD_RS_CLEAR GPIO_ResetBits(GPIOB,GPIO_Pin_15)

//lcd的RW端定义 PB14
#define LCD_RW_EN GPIO_SetBits(GPIOB,GPIO_Pin_14)
#define LCD_RW_CLEAR GPIO_ResetBits(GPIOB,GPIO_Pin_14)

//lcd的E端定义 PB13
#define LCD_E_EN GPIO_SetBits(GPIOB,GPIO_Pin_13)
#define LCD_E_CLEAR GPIO_ResetBits(GPIOB,GPIO_Pin_13)

//lcd输入数据
#define DATAOUT(x) GPIO_Write(LCD_DATA,x)

void LCD1602_Init(void);
void LCD1602_gpio_Init(void);
void LCD1602_CMD(u8 cmd);
void LCD1602_DATA(u8 data);
void LCD1602_Busy(void);
void LCD1602_Clear(void);
void LCD1602_Show_Str(u8 rol,u8 line,u8 *str);
void LCD1602_Show_Bit(u8 rol,u8 line,u8 showdata);

#endif

 lcd1602.c文件

#include "lcd1602.h"
#include "delay.h"

void LCD1602_Init(void)
{
	LCD1602_gpio_Init();     //GPIO初始化
	
	delay_ms(15);
	LCD1602_CMD(0x38);       //16*2显示 8位数据线  5*7点阵
	delay_ms(5);
	LCD1602_CMD(0x0c);       //开显示 无光标
	LCD1602_CMD(0x06);			 //文字不动 地址自动+1
	LCD1602_CMD(0x01);       //清屏
}

//写指令
void LCD1602_CMD(u8 cmd)
{
	LCD1602_Busy();  //检测忙不忙
	LCD_RS_CLEAR;    //RS 0
	LCD_RW_CLEAR;    //RW 0
	LCD_E_CLEAR;
	DATAOUT(cmd);
	delay_ms(1);
	LCD_E_EN;
	delay_ms(5);
	LCD_E_CLEAR;
}

//写数据
void LCD1602_DATA(u8 data)
{
	LCD1602_Busy();   //检测忙不忙
	LCD_RS_EN;        //RS 1
	LCD_RW_CLEAR;     //RW 0
	LCD_E_CLEAR;
	DATAOUT(data);
	delay_ms(1);
	LCD_E_EN;
	delay_ms(5);
	LCD_E_CLEAR;
}

//清屏
void LCD1602_Clear(void)
{
	LCD1602_CMD(0x01);
}

//字符串显示 rol 行 line 列
void LCD1602_Show_Str(u8 rol,u8 line,u8 *str)
{
	if(rol == 1)
		LCD1602_CMD(0x00+line+0x80);
	else if(rol == 2)
		LCD1602_CMD(0x40+line+0x80);
	while(*str != '\0')
	{
		LCD1602_DATA(*str);
		str++;
	}
}

//字符显示
void LCD1602_Show_Bit(u8 rol,u8 line,u8 showdata)
{
	if(rol == 1)
		LCD1602_CMD(0x00+line+0x80);
	else if(rol == 2)
		LCD1602_CMD(0x40+line+0x80);
	LCD1602_DATA(showdata);
}

//检测忙函数
void LCD1602_Busy(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;   //浮空输入
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;        //PC7
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStruct);
	
	LCD_RS_CLEAR;   //RS 0
	LCD_RW_EN;      //RW 1
	
	while(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_7));
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;   //推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;        //PC7
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStruct);
}

//gpio初始化
void LCD1602_gpio_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;   //推挽输出
	GPIO_InitStruct.GPIO_Pin=0X00FF;        //PC0-PC7
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStruct);
	GPIO_ResetBits(GPIOC,0x00ff);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;   //推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;        //PB13-PB15
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	GPIO_ResetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
}

2、4*4 矩阵键盘的编写

        本程序逻辑比较简单,采用的是行扫描方法,程序中有一点需要注意就是一定要搞清楚两个for循环的逻辑开始和结束,否则很容易导致虽然设置了按键按下标志位,但是还是会出现重复按键的问题,另外就是对gpio口的配置,到底是应该设置为输出还是输入,其他的emmmm我还没想到,这两点注意到应该就没问题了。虽然写的程序有点啰嗦,但是简单易懂,还有很多可以优化的方法,我就简单说一个简化方法,可以将列扫描函数在头文件里面直接define也是可以的,大家可以自行简化和优化。

 key.h

#ifndef __KEY_H
#define __KEY_H

#include "sys.h"
#include "stm32f10x.h"

//这里将按键重定义 方便后期修改IO口

#define Key_Gpio GPIOB

#define line1 GPIO_Pin_4
#define line2 GPIO_Pin_5
#define line3 GPIO_Pin_6
#define line4 GPIO_Pin_7

#define rol1 GPIO_Pin_0
#define rol2 GPIO_Pin_1
#define rol3 GPIO_Pin_2
#define rol4 GPIO_Pin_3


void Key_Init(void);
u8 Key_Scan(void);
void Key_gpio_Init(void);
void Key_set(u8 key,u8 mode);  
u8 Rol_state(u8 rol);


#endif

key.c

#include "key.h"
#include "stm32f10x.h"
#include "delay.h"
#include "uart.h"

void Key_Init(void)
{
	Key_gpio_Init();
}

u8 Key_Scan(void)
{
	u8 i,j;
	u8 Key_Value = '.';
	static u8 Key_up_flag = 1;            //不支持连按
	
	for(i=1;i<=4;i++)       //一共是四行,i的值为行数
	{
		Key_set(i,1);		      //将改行置为高电平,其余行置为低电平,保证只有一行是高电平

		for(j=1;j<=4;j++)           // 一共四列 四列分别检测  另一种方法可以不去单个检测,去做一个统一的判断
		{
			if(Rol_state(j) == 1)
			{
				delay_ms(5);           //按键消抖
				
				if(Rol_state(j) == 1 && Key_up_flag == 1)      
				{
					Key_up_flag = 0;
					
					switch(j)
					{
						case 1:Key_Value = (4*(i-1))+1;break;
						case 2:Key_Value = (4*(i-1))+2;break;
						case 3:Key_Value = (4*(i-1))+3;break;
						case 4:Key_Value = (4*(i-1))+4;break;
						default:break;
					}
					
					//USART_SendData(USART1,Key_Value);      //此条注释可以验证是否是每次只触发一次按键响应,即不支持连按
				} 
			}
		}		
	}
	
	//在for循环结束之后 将行全部置为高电平  检测列是否有高电平 如果没有 即没有按键按下 此时就将行全部置为低电平 标志位置为1 等待下一次按键按下
	Key_set(5,1);            
	if(Rol_state(1) == 0 && Rol_state(2) == 0 && Rol_state(3) == 0 && Rol_state(4) == 0 && Key_up_flag == 0) 
	{
		Key_up_flag = 1;
		Key_Value = '.';
		Key_set(5,0);
	}	
	
	
	//进行按键的重定义
	switch(Key_Value)
	{
		case 1:Key_Value = '1';break;
		case 2:Key_Value = '2';break;
		case 3:Key_Value = '3';break;
		case 4:Key_Value = 'A';break;
		case 5:Key_Value = '4';break;
		case 6:Key_Value = '5';break;
		case 7:Key_Value = '6';break;
		case 8:Key_Value = 'B';break;
		case 9:Key_Value = '7';break;
		case 10:Key_Value = '8';break;
		case 11:Key_Value = '9';break;
		case 12:Key_Value = 'C';break;
		case 13:Key_Value = '*';break;
		case 14:Key_Value = '0';break;
		case 15:Key_Value = '#';break;
		case 16:Key_Value = 'D';break;
		default:Key_Value = '.';break;
	}
		
	return Key_Value;
}


//gpio的初始化 
void Key_gpio_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPD;            //下拉输入
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;            //推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	GPIO_ResetBits(GPIOB,GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);
}


//行的置电平函数
void Key_set(u8 key,u8 mode)   //key: 哪一行  mode: 高电平还是低电平
{
	switch(key)
	{
		case 1: 
			if(mode == 1)
			{
				GPIO_SetBits(Key_Gpio,line1);
				GPIO_ResetBits(Key_Gpio,line2);
				GPIO_ResetBits(Key_Gpio,line3);
				GPIO_ResetBits(Key_Gpio,line4);
			}
			else if(mode == 0)
				GPIO_ResetBits(Key_Gpio,line1);
			break;
		case 2: 
			if(mode == 1)
			{
				GPIO_SetBits(Key_Gpio,line2);
				GPIO_ResetBits(Key_Gpio,line1);
				GPIO_ResetBits(Key_Gpio,line3);
				GPIO_ResetBits(Key_Gpio,line4);
			}
			else if(mode == 0)
				GPIO_ResetBits(Key_Gpio,line2);
			break;
		case 3: 
			if(mode == 1)
			{
				GPIO_SetBits(Key_Gpio,line3);
				GPIO_ResetBits(Key_Gpio,line2);
				GPIO_ResetBits(Key_Gpio,line1);
				GPIO_ResetBits(Key_Gpio,line4);
			}
			else if(mode == 0)
				GPIO_ResetBits(Key_Gpio,line3);
			break;
		case 4: 
			if(mode == 1)
			{
				GPIO_SetBits(Key_Gpio,line4);
				GPIO_ResetBits(Key_Gpio,line2);
				GPIO_ResetBits(Key_Gpio,line3);
				GPIO_ResetBits(Key_Gpio,line1);
			}
			else if(mode == 0)
				GPIO_ResetBits(Key_Gpio,line4);
			break;
		case 5: 
			if(mode == 1)
				GPIO_SetBits(Key_Gpio,line4|line3|line2|line1);
			else if(mode == 0)
				GPIO_ResetBits(Key_Gpio,line4|line3|line2|line1);
			break;
		default:break;
	}
}

//列的检测函数
u8 Rol_state(u8 rol)
{
	u8 Rol_state;
	switch(rol)
	{
		case 1: rol = rol1;break;
		case 2: rol = rol2;break;
		case 3: rol = rol3;break;
		case 4: rol = rol4;break;
	}
	Rol_state = GPIO_ReadInputDataBit(Key_Gpio,rol);
	return Rol_state;
}

 3、串口通信的编写

        由于本文重点不是在串口通信,不在过多具体讲解,需要注意的是在串口程序里面出现的PA15引脚和motor字眼,这是我做的电机验证程序,自行忽略和删除即可。

uart.h

#ifndef __UART_H
#define __UART_H
#include "sys.h"

void Uart_Init(u32 bound);
void Uart_gpio_Init(void);
void Uart_NVIC_Init(void);
void USART1_IRQHandler(void);

#endif

 uart.c

#include "uart.h"
#include "stm32f10x.h"
#include "motor.h"

void Uart_Init(u32 bound)
{
	USART_InitTypeDef USART_InitStruct;
	
	Uart_gpio_Init();
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	USART_DeInit(USART1);
	
	USART_InitStruct.USART_BaudRate=bound;
	USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	USART_InitStruct.USART_Parity=USART_Parity_No;
	USART_InitStruct.USART_StopBits=USART_StopBits_1;
	USART_InitStruct.USART_WordLength=USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStruct);
	
	Uart_NVIC_Init();
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);	
	
	USART_Cmd(USART1,ENABLE);
}

void Uart_gpio_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
}

void Uart_NVIC_Init(void)
{
	NVIC_InitTypeDef NVIC_InitStruct;
	
	NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStruct);
}

void USART1_IRQHandler(void)
{
	u8 res;
	if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
	{
		res = USART_ReceiveData(USART1);
		USART_SendData(USART1,res);
		while(USART_GetFlagStatus(USART1,USART_IT_TC));
		GPIO_SetBits(GPIOA,GPIO_Pin_15);
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

 4、主函数

main.c

#include "stm32f10x.h"
#include "lcd1602.h"
#include "delay.h"
#include "motor.h"
#include "uart.h"
#include "key.h"

int main(void)
{
	u8 str[] = "I LOVE YOU"; 
	u8 Key_Val;
	static int Key_val_Flag = 1;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	delay_init();
	Uart_Init(9600);
	LCD1602_Init();
	Motor_Init();
	Key_Init();
	
	while(1)
	{
		Key_Val = Key_Scan();
		if(Key_Val != '.'&& Key_val_Flag == 1)     
		{	
			Key_val_Flag = 0;
			LCD1602_Show_Bit(2,7,Key_Val);
			USART_SendData(USART1,Key_Val);
		}
		else if(Key_Val == '.' && Key_val_Flag == 0)
		{
			Key_val_Flag = 1;
		}
		LCD1602_Show_Str(1,3,str);
	}
}

三、仿真

         在仿真上的话,因为采用了串口,所以就需要用到虚拟串口和串口助手,串口助手我直接用的是在微软商店的串口调试助手,虚拟串口采用的是虚拟串口助手VSPD。

        Proteus也可能存在一些对stm32系列仿真的bug,我就把我遇到的bug列在这里,可能大家可以用的到。

        1、芯片频率

        建议设置为48MHz,这样就差不多可以和实际时间一样,不然仿真会很慢。

        2、 LCD1602

        把我圈中的值设大一点,可能是刷新频率,不然屏幕上显示不出来或者就是有问题。

         3、COMPIM

        这两个波特率设置为9600和57600,另外没有奇偶校验,8位数据,1个停止位,相应的串口助手设置为9600波特率,其他一样。

        4、 VIRTUAL TERMIN

        波特率设置成57600,8位数据,没有奇偶校验,1个停止位。

         这样大差不差就可以成功运行了!!!

 四、结尾

        新手一枚,文章有些冗杂,但是还是希望本文能够对大家有所帮助,加油兄弟们,总有一天会头秃的!

物联沃分享整理
物联沃-IOTWORD物联网 » STM32F103系列:使用Proteus仿真实现4*4矩阵键盘与LCD1602显示的连接

发表评论