在Proteus软件仿真STM32F103寄存器玩俄罗斯方块之第二篇
这篇文章主要是代码分享,以及简单解析。
对代码哪里不理解的,欢迎找我讨论哈。
和上篇代码结构不一样的地方是多文件和分模块,使得工程更加有条理。
这篇文章主要是单片机输入和输出典型代表,这个在单片机初学中是很常见的。
这次的代码是直接按照文件抛出来的,不像上次解释的那么细。
目录
- 独立按钮的处理
- LCD12864的基础功能实现
独立按钮的处理
独立按钮在单片机初学阶段一定是绕不开的。
计算机系统的组成里边包括输入和输出两个部分。
我们的单片机也是一个计算机系统,区别是单片机是专用的,高度可裁剪的计算机系统。
独立按键就是一个典型的输入部分。
在这里不给你讲按键过于细节的部分。
在PID电机调速那篇文章中也就简单的独立按钮处理。这个工程里就是更规范化了,适合在实际工程中使用。同样这个工程中也没有做消抖处理。
下边的代码是按键配置头文件,也就是h文件。
#ifndef __BUTTON_CFG_H_
#define __BUTTON_CFG_H_
#include "Reg.h"
typedef enum
{
Btn_Up,
Btn_Down,
Btn_Left,
Btn_Right,
Btn_TotalNum
}Button;
typedef enum
{
Btn_Sta_Press,
Btn_Sta_Release,
Btn_Sta_PressLong,
}Button_State;
typedef struct
{
Button BtnID;
GPIO_TypeDef *GPIOx;
uint8_t Pinx;
uint8_t FreeLevel;
}ButtonCfg_Def;
#endif
下边的代码是按键配置C文件。
实际上配置文件里的GPIOB这个参数没有使用,原因就是在写GPIO这个初始化文件的时候是直接针对B端口的函数,没有写成通用的。
#include "button_cfg.h"
const ButtonCfg_Def BtnCfg[Btn_TotalNum] = {
{Btn_Up, GPIOB, 5, 1},
{Btn_Down, GPIOB, 6, 1},
{Btn_Left, GPIOB, 7, 1},
{Btn_Right, GPIOB, 8, 1},
};
下边的代码是按键对外开放的接口文件,也就是h文件。
一般情况下这种接口文件尽可能要简短。
#ifndef __BUTTON_H
#define __BUTTON_H
#include "button_cfg.h"
void Button_Init(void);
void Button_Step(void);
uint8_t Button_Read(Button *btn,Button_State *sta);
#endif
下边的代码是按键功能C语言实现的代码文件,里边实现了简单的环形队列,用于存放按键触发的状态。
按键功能的实现,很大程度取决你习惯用什么技术,有的人喜欢用回调去做,这种方式在图形界面中比较常见。
这种方式在之前H7 串口的那几篇文章中有实现。
#include "button.h"
#include "GPIO.h"
#define BtnQueueDeep 16ul
typedef struct{
Button btn;
Button_State sta;
uint8_t reset;
}BtnQueue_Def;
static BtnQueue_Def BtnQueue[BtnQueueDeep];
struct BtnRW_CB
{
uint8_t ReadNum;
uint8_t WriteNum;
}BtnRW;
static uint8_t Btn_PressCount[Btn_TotalNum];
static Button_State Btn_LastSta[Btn_TotalNum];
extern ButtonCfg_Def BtnCfg[Btn_TotalNum];
static void BtnQueueWrite(Button btn,Button_State sta)
{
Btn_LastSta[btn] = sta;
BtnQueue[BtnRW.WriteNum].btn = btn;
BtnQueue[BtnRW.WriteNum].sta = sta;
BtnQueue[BtnRW.WriteNum ++].reset = 0;
BtnRW.WriteNum &= 0x0F;
}
static BtnQueue_Def* BtnQueueRead(void)
{
if(BtnQueue[BtnRW.ReadNum].reset == 0)
{
uint8_t num = BtnRW.ReadNum ++;
BtnRW.ReadNum &= 0x0F;
BtnQueue[num].reset = 1;
return &BtnQueue[num];
}
else
return 0;
}
static void Btn_Init(Button btn)
{
if(BtnCfg[btn].BtnID == btn)
{
PB_In_Init(BtnCfg[btn].Pinx);
PB_Out(BtnCfg[btn].Pinx,BtnCfg[btn].FreeLevel);
}
}
static void Btn_Value_Read(Button btn)
{
if(BtnCfg[btn].BtnID == btn)
{
uint8_t pinvalue = PB_In(BtnCfg[btn].Pinx);
if(pinvalue != BtnCfg[btn].FreeLevel)
{
switch(Btn_LastSta[btn])
{
case Btn_Sta_Press:
{
Btn_PressCount[btn] ++;
if(Btn_PressCount[btn] > 50)
{
BtnQueueWrite(btn,Btn_Sta_PressLong);
}
}
break;
case Btn_Sta_Release:
{
BtnQueueWrite(btn,Btn_Sta_Press);
}
break;
default:break;
}
}
else
{
if(Btn_LastSta[btn] == Btn_Sta_Release)return;
Btn_PressCount[btn] = 0;
BtnQueueWrite(btn,Btn_Sta_Release);
}
}
}
void Button_Init(void)
{
for(uint32_t i = 0;i < Btn_TotalNum;i ++)
{
Btn_Init((Button)i);
Btn_LastSta[i] = Btn_Sta_Release;
}
for(uint32_t i = 0;i < BtnQueueDeep;i ++)
{
BtnQueue[i].reset = 1;
}
BtnRW.ReadNum = 0;
BtnRW.WriteNum = 0;
}
void Button_Step(void)//rate 10ms
{
for(uint32_t i = 0;i < Btn_TotalNum;i ++)
{
Btn_Value_Read((Button)i);
}
}
uint8_t Button_Read(Button *btn,Button_State *sta)
{
uint8_t ret = 0;
BtnQueue_Def *p = BtnQueueRead();
if(p)
{
*btn = p->btn;
*sta = p->sta;
ret = 0x01;
}
return ret;
}
按钮相关的代码到这里就结束了。
LCD12864的基础功能实现
这一次用的LCD屏幕显示画面。这个也算是个典型的输出系统吧。
总之在51中这个显示屏还是用的挺多的,但是需要注意的是,你一定要看清楚型号哦,每个型号他们的时序和寄存器什么的可能都不一样,在proteus这个软中中我选择的这个型号就和你在某宝买到的常见的那个就不是同一个,也就是说时序是不一样的,所以你首先要找到他的数据手册(数据手册点我),里边会包含时序和寄存功能。
阅读这个时序和寄存器你就需要一定的数电功底了,可以联系我,带你入门,高深的东西教不了你,但是简单的东西还是没有问题的,默念三遍,要谦虚不能托大,要谦虚不能托大,要谦虚不能托大。
首先还是配置文件,这次是12864的配置头文件。
嗯,还是非常的简短。实在没啥可配置的。
#ifndef __LCD12864_CFG_H_
#define __LCD12864_CFG_H_
#define LCD_Width 128ul
#define LCD_Hight 64ul
#define LCD_Pixel_Bit_Width 8ul
#endif
然后是LCD的对外接口头文件了。
这里对外的接口主要是每一个像素点的读写,屏幕方向的设置和屏幕大小的读取接口。
#ifndef __LCD12864_H_
#define __LCD12864_H_
#include "stdint.h"
void LCD_Init(void);
void LCD_Step(void);
uint8_t LCD_Width_Get(void);
uint8_t LCD_Hight_Get(void);
uint8_t LCD_Direction_Get(void);
void LCD_Direction_Set(uint8_t dir);
void LCD_Pixel_Write(uint8_t X,uint8_t Y,uint8_t value);
uint8_t LCD_Pixel_Read(uint8_t X,uint8_t Y);
#endif
最后是LCD接口的实现部分了。
这里LCD用的是串行接口,不支持读操作,所以直接在RAM中建一个对应的显示缓存数组,定时刷新到lcd就行了,所有数据都在内存里边,比较简单。
#include "GPIO.h"
#include "LCD12864.h"
#include "LCD12864_cfg.h"
static uint8_t LCD_memory[LCD_Hight / LCD_Pixel_Bit_Width][LCD_Width];
static volatile uint8_t Direction;
void LCD_Pin_Init(void)
{
PB_Out_Init(0);
PB_Out_Init(1);
PB_Out_Init(2);
PB_Out_Init(3);
PB_Out_Init(4);
PB_Out(0,1);
PB_Out(1,1);
PB_Out(2,1);
PB_Out(3,1);
PB_Out(4,1);
}
void LCD_Write_Byte(uint8_t byte)
{
for(uint32_t i = 0;i < 8;i ++)
{
PB_Out(3,0);//SCK
PB_Out(4,byte>>7);//SI
PB_Out(3,1);
byte <<= 1;
}
}
void LCD_Write_CMD(uint8_t cmd)
{
PB_Out(2,0);//cs
PB_Out(0,0);//RS
LCD_Write_Byte(cmd);
PB_Out(2,1);//cs
}
void LCD_Write_Data(uint8_t data)
{
PB_Out(2,0);//cs
PB_Out(0,1);//RS
LCD_Write_Byte(data);
PB_Out(2,1);//cs
}
void LCD_Init(void)
{
LCD_Pin_Init();
//LCD_Write_CMD(0x2C);//升压1
//LCD_Write_CMD(0x2e);//升压2
//LCD_Write_CMD(0x2f);//升压3
//LCD_Write_CMD(0x23);//粗调对比度
//LCD_Write_CMD(0x81);//微调对比度
//LCD_Write_CMD(0x1f);//微调对比度
//LCD_Write_CMD(0xa2);//偏压比
LCD_Write_CMD(0xC0);//行扫描顺序:从上到下 0上下 8下上
LCD_Write_CMD(0xa1);//列扫描顺序:从坐到右 0左右 1右左
LCD_Write_CMD(0xa6);//正反显示 7高亮显示 6暗显示
//LCD_Write_CMD(0x60);//起始行 从第一行显示
LCD_Write_CMD(0xaf);//开显示
}
void LCD_Write_line(uint8_t line)
{
line &= 0x07;
LCD_Write_CMD(0xb0 + line);
LCD_Write_CMD(0x10);
LCD_Write_CMD(0x00);
for(uint32_t i = 0;i < LCD_Width;i ++)
{
LCD_Write_Data(LCD_memory[line][i]);
}
}
void LCD_Step(void)
{
for(uint32_t i = 0;i < (LCD_Hight / LCD_Pixel_Bit_Width);i ++)
{
LCD_Write_line(i);
}
}
uint8_t LCD_Width_Get(void)
{
return LCD_Width;
}
uint8_t LCD_Hight_Get(void)
{
return LCD_Hight;
}
uint8_t LCD_Direction_Get(void)
{
return Direction;
}
void LCD_Direction_Set(uint8_t dir)
{
Direction = dir & 0x01;
}
void LCD_Pixel_Write(uint8_t X,uint8_t Y,uint8_t value)
{
uint8_t x = Direction ? Y : X;
uint8_t y = Direction ? LCD_Hight - 1 - X : Y;
if(x >= LCD_Width) return;
if(y >= LCD_Hight) return;
value &= 0x01;
LCD_memory[y >> 3][x] &= ~(0x01 << (y & 0x07));
LCD_memory[y >> 3][x] |= value << (y & 0x07);
}
uint8_t LCD_Pixel_Read(uint8_t X,uint8_t Y)
{
uint8_t x = Direction ? Y : X;
uint8_t y = Direction ? LCD_Hight - 1 - X : Y;
if(x >= LCD_Width) return 0xFF;
if(y >= LCD_Hight) return 0xFF;
return (LCD_memory[y >> 3][x] >> (y & 0x07)) & 0x01;
}
LCD12864基础部分到这里就结束了。足够发挥你的创意了。