最近一直在肝STM32系列芯片,这里我想要检验一下自己的初步学习成果,简单的运用一下IO口的配置,代码使用正点的库函数模板写的,仿真软件是Proteus 8.13,代码编写编译软件是Keil MDK5,仿真芯片是STM32F103R6小容量芯片。
目录
一、硬件设计
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个停止位。
这样大差不差就可以成功运行了!!!
四、结尾
新手一枚,文章有些冗杂,但是还是希望本文能够对大家有所帮助,加油兄弟们,总有一天会头秃的!