基于STM32设计的校园一卡通项目

描述

1. 项目简介

信息时代的校园, 离不开信息化的管理, 数字化"校园一卡通"建设是校园信息化建设的重要组成部分, 是为信息化校园提供信息采集的基础工程也是获取学校信息化服务的主要方式之一。

校园一卡通将只能 IC 卡的强大功能与计算机网络的数字化理念融入校园, 将学校各个系统连为一体, 动态掌握每一持卡人情况, 极大提高学校的管理水平和服务质量。

本文介绍通过STM32 微控制器+RFID RC522设计的一个校园一卡通消费充值机的项目,可以模拟实现充值、消费、修改密码、挂失、登录、查询.......等操作。

硬件介绍:

MCU:STM32F103ZE6

刷卡模块: RFID-RC522

LCD屏: 正点原子的3.5寸LCD屏+触摸屏

完整项目下载地址: https://download.csdn.net/download/xiaolong1126626497/63983899

视频演示地址: https://live.csdn.net/v/182606

运行效果:

STM32

 

STM32STM32

2. 项目实现

2.1 RFID-RC522模块

RFID-RC522模块直接淘宝购买的现成模块,模块实物图如下:

STM32

RC522是NXP公司设计的13.56MHz非接触式读写卡芯片,可以读写IC卡,具备低电压、低成本、体积小的特点,本身支持SPI接口通信,任何单片机都与通信,SPI时序模拟也非常简单。

现在地铁卡、校园卡、公交卡都是属于M1(S50)卡,M1卡内部有16个扇区,每个扇区分为4个块,每个块的容量是16个字节,每个扇区里的最后一个块是存放密码,每次对块里的数据读写都需要验证IC卡的密码,只有具备写权限才可以对块进行读写,密码验证通过之后可以直接利用修改密码、读写扇区等等,读取卡号是不需要验证密码的。

关于IC卡的详细介绍请看这里: https://blog.csdn.net/xiaolong1126626497/article/details/117075834

 

本项目里STM32与RCC522通信使用的SPI是模拟时序,可以很方便的移植到其他的单片机。

SPI模拟时序代如下:

/*
函数功能:移植接口--SPI时序读写一个字节
函数参数:data:要写入的数据
返 回 值:读到的数据
*/
u8 RC522_SPI_ReadWriteOneByte(u8 tx_data)
{			  	 
  u8 rx_data=0;				 
  u8 i;
  for(i=0;i<8;i++)
	{
		RC522_SCLK=0;  
		if(tx_data&0x80){RC522_OUTPUT=1;}
		else {RC522_OUTPUT=0;}
		tx_data<<=1;	
		RC522_SCLK=1;
		rx_data<<=1;
		if(RC522_INPUT)rx_data|=0x01;
	}
	return rx_data; 
}


/*
函数功能:初始化RC522的IO口	 
*/
void RC522_IO_Init(void)
{
	RCC->APB2ENR |= 0x01 << 0;
	AFIO->MAPR |= 0x01 << 26;

	RCC->APB2ENR |= 0x01 << 2;     //PA时钟使能 
	
//#define RC522_CS	PAout(10)
//#define RC522_SCLK	PAout(13)
//#define RC522_OUTPUT	PAout(14)
//#define RC522_INPUT	PAin(15)
//#define RC522_RST	PAout(0)

	GPIOA->CRL &= 0xFFFFFFF0;
	GPIOA->CRL |= 0x00000003;

	GPIOA->CRH &= 0x000FF0FF;
	GPIOA->CRH |= 0x43330300;

	RC522_CS = 1;
	RC522_SCLK = 1;
}

2.2 LCD屏

LCD使用的是正点原子3.5寸屏,驱动芯片是NT35310,支持8080时序,本身STM32大容量芯片具备FSMC接口的,可以直接使用FSMC接口操作LCD屏完成操作,这里考虑到程序的移植性,因为小容量,中容量的比如STM32F103C8T6就没有FSMC接口,为了方便程序可以移植到这些开发板正常运行,当前项目采用的是模拟8080时序方式,直接使用GPIO口模拟时序操作LCD屏;虽然刷屏效率比FSMC慢不少,但是本项目的界面也不需要很高的刷新率,没有图频繁的切换效果,所以整体效果还是不错的。

模拟时序代码如下: 如果要移植到其他单片机上,只需要修改GPIO口即可。
 

void lcd_write_cmd(u8 reg)
{
	LCD_CS = 0;	//拉低片选脚,选中 LCD
	LCD_RS = 0;	//拉低数据/命令控制线,选择要操作命令

	LCD_RD = 1;	//禁止读
	LCD_WR = 0;	//拉低 WR,准备写操作
	
	//数据总线输出命令, 把要发送的命令放到数据总线上
	GPIOB->ODR = (u16)reg;
	
	LCD_WR = 1;	//拉高 WR 写使能
	LCD_CS = 1;	//拉高片选,结束操作
}

void lcd_write_data(u16 data)
{
	LCD_CS = 0;	//拉低片选脚,选中 LCD
	LCD_RS = 1;	//拉高数据/命令控制线,选择要操作数据

	LCD_RD = 1;	//禁止读
	LCD_WR = 0;	//拉低 WR,准备写操作
	
	//数据总线输出数据, 把要发送的数据放到数据总线上
	GPIOB->ODR = data;
	
	LCD_WR = 1;	//拉高 WR 写使能
	LCD_CS = 1;	//拉高片选,结束操作
}

void lcd_set_cursor(u16 x, u16 y)
{
	lcd_write_cmd(SET_X_ADDR);
	lcd_write_data(x>>8);
	lcd_write_data(x&0xff);

	lcd_write_cmd(SET_Y_ADDR);
	lcd_write_data(y>>8);
	lcd_write_data(y&0xff);
}

void lcd_write_reg(u16 cmd, u16 parameter)
{
	lcd_write_cmd(cmd);
	lcd_write_data(parameter);
}

void lcd_draw_dot(u16 x, u16 y, u16 color)
{
	lcd_set_cursor(x, y);
	lcd_write_cmd(WRITE_MEMORY_START);
	lcd_write_data(color);	// [15:0] --> [R4-R0:G5-G0:B4-B0]
}

void lcd_show_screen(const u8 * image, u32 size, u16 x, u16 y)
{
	u32 i = 0;

	lcd_set_cursor(x, y);	//设置光标位置
	lcd_write_cmd(WRITE_MEMORY_START);	 //开始写入GRAM

	while( i < size ){
		lcd_write_data( *image<<8 | *(image+1) ); 
		image += 2;
		++i;
	}
}

//画矩形	  
//(x1,y1),(x2,y2):矩形的对角坐标
void lcd_draw_rectblock(u16 y1, u16 y2, u16 color)
{
	u16 i;
	for( ; y1<=y2; ++y1){
		lcd_set_cursor(0,y1);		  //设置光标位置
		lcd_write_cmd(WRITE_MEMORY_START);		  //开始写入GRAM
		for(i=0; i<320; ++i){
			lcd_write_data( color );    //写数据
		}
	}
}

void lcd_show_image(const u8 * image, u16 width, u16 high, u16 x, u16 y)
{	
	u32 i,j;

	for(i=0; i0)incx=1; //设置单步方向 
	else if(delta_x==0)incx=0;//垂直线 
	else {incx=-1;delta_x=-delta_x;} 
	if(delta_y>0)incy=1; 
	else if(delta_y==0)incy=0;//水平线 
	else{incy=-1;delta_y=-delta_y;} 
	if( delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 
	else distance=delta_y; 
	for(t=0;t<=distance+1;t++ )//画线输出 
	{  
		lcd_draw_dot(uRow,uCol, WHITE);//画点 
		xerr+=delta_x ; 
		yerr+=delta_y ; 
		if(xerr>distance) 
		{ 
			xerr-=distance; 
			uRow+=incx; 
		} 
		if(yerr>distance) 
		{ 
			yerr-=distance; 
			uCol+=incy; 
		} 
	} 
}

void lcd_draw_circle(u16 x,u16 y,u8 r, u16 color)
{
	int a, b, di;

	a = 0;
	b = r;	  
	di = 3 - (r << 1);	//判断下个点位置的标志

	while(a<=b){
		lcd_draw_dot(x+a,y-b, color);	//5
 		lcd_draw_dot(x+b,y-a, color);	//0           
		lcd_draw_dot(x+b,y+a, color);	//4               
		lcd_draw_dot(x+a,y+b, color);	//6 
		lcd_draw_dot(x-a,y+b, color);	//1       
 		lcd_draw_dot(x-b,y+a, color);             
		lcd_draw_dot(x-a,y-b, color);	//2             
  		lcd_draw_dot(x-b,y-a, color);	//7     	         
		++a;
		//使用Bresenham算法画圆     
		if(di < 0)
			di += 4*a + 6;	  
		else{
			di+=10+4*(a-b);   
			--b;
		}						    
	}
}

void lcd_clear(u16 color)
{
	u32 index;      
	u32 point;
	point = 480*320; 		//得到总点数
	lcd_set_cursor(0x00,0x00);	//设置光标位置 
	lcd_write_cmd(WRITE_MEMORY_START);  	//开始写入GRAM	  	  
	for(index=0; index;>;>

3.3 触摸屏

触摸屏是LCD屏本身自带的,触摸芯片是XPT2046,是一个12位的ADC芯片,通信协议是SPI时序。

项目里采用模拟SPI时序进行与触摸屏芯片通信,因为XPT2046本身是ADC芯片,所以在屏幕上按下后读取出来的数据是模拟数据值—物理坐标值,我们还需要将它转为屏幕坐标与LCD屏的像素点对应起来,这样使用起来才比较方便。

XPT2046核心代码如下:
 

#include "stm32f10x.h"
#include "xpt2046.h"
#include "delay.h"

/*
#define T_SCK	PAout(12)
#define T_MI	PAin(6)
#define T_MO	PAout(11)
#define T_PEN	PAin(7)
#define T_CS	PAout(8)
*/
void xpt2046_init(void)
{
	RCC->APB2ENR |= 0x01 << 2;	// ENABLE port a clock

	GPIOA->CRL &= 0x00FFFFFF;	// 浮空输入
	GPIOA->CRL |= 0x44000000;	// 推挽输出

	GPIOA->CRH &= 0xFFF00FF0;
	GPIOA->CRH |= 0x00033003;

	T_CS = 1;
	T_SCK = 0;
}

/*
cmd format 1: 10010000	0x90	Y-POSITION Measure
cmd format 2: 11010000	0xd0	X-POSITION Measure
*/
u16 xpt2046_read(u8 cmd)
{
	T_SCK = 0;
	T_MO = 0;
	T_CS = 0;
	
	for(u8 i=0; i<8; ++i){
		T_SCK = 0;
		if( cmd & 0x80 )
			T_MO = 1;
		else
			T_MO = 0;
		cmd <<= 1;
		T_SCK = 1;
	}
	// 15时钟周期转换
	T_SCK = 0;
	T_MO = 0;
	T_SCK = 1;
	u16 data = 0;
	for(u8 i=0; i<12; ++i){
		T_SCK = 0;
		data <<= 1;
		T_SCK = 1;
		if( T_MI )
			data |= 0x01;
	}
	T_CS = 1;

	return (data);
}

u8 xpt2046_position(TOUCH * xpt2046_pos)
{
	if( !T_PEN ){
		u8 i, j;
		u16 tmp;
		u16 x[16], y[16];
		
		for(i=0; i<16; ++i){
			x[i] = xpt2046_read(XPOS);
			y[i] = xpt2046_read(YPOS);
		}
		for(i=0; i<16; ++i){
			for(j=0; j<16-i; ++j){
				if(x[j]>x[j+1]){
					tmp = x[j];
					x[j] = x[j+1];
					x[j+1] = tmp;
				}
				if(y[j]>y[j+1]){
					tmp = y[j];
					y[j] = y[j+1];
					y[j+1] = tmp;
				}
			}
		}

		u32 sum_x, sum_y;

		sum_x = sum_y =0;
		for(i=3; i<13; i++){
			sum_x += x[i];
			sum_y += y[i];
		}
		xpt2046_pos->x = sum_x / 10;
		xpt2046_pos->y = sum_y / 10;

		return 0;
	}
	else
		return 1;
}

u8 touch_position(TOUCH * touch_pos)
{
	TOUCH xpt2046_pos;

	if( !xpt2046_position(&xpt2046_pos) ){
		touch_pos->x = 320 - (xpt2046_pos.x - 300) / 11.25;
		touch_pos->y = 480 - (xpt2046_pos.y - 200) / 7.7;
		return 0;
	}
	else{
		touch_pos->x = 0xffff;
		touch_pos->y = 0xffff;
		return 1;
	}
}

 

 审核编辑:汤梓红

 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分