智能教室控制系统
引言
概述
该系统通过上位机C#控制下位机STM32,实现教室打卡考勤功能,通过打卡对数据库SQLserver进行增删改查,同时能将数据库的数据导出表格,下位机用到RFID的RC522模块,舵机、风扇,ws2812灯环。风扇通过调温度阈值,自动开关。上位机和下位机通过串口进行通信。
运用技术:C#桌面级winform程序、SQL server、stm32f103zet6单片机
技术路线:软件部分:C#:三方API接入 json解析、串口通信、调用数据库
硬件部分:Rfid射频刷卡,舵机,风扇
舵机模块
橙色线接脉冲线,红色线接+5V,黑色接GND;
RC522模块
上位机共有5个界面,分别是注册,登录,忘记密码,主界面,导出表单。
代码部分
下位机代码
主函数
#include "bsp.h" /* 底层硬件驱动 */
#include "pwm.h"
/*
STM32 每个系列都会有唯一的一个芯片序列号(96 位 bit):
STM32F10X 的地址是 0x1FFFF7E8
STM32F20X 的地址是 0x1FFF7A10
STM32F30X 的地址是 0x1FFFF7AC
STM32F40X 的地址是 0x1FFF7A10
STM32L1XX 的地址是 0x1FF80050
*/
/* SN 起始地址 */
#define STM32F10X_SN_ADDR 0x1FFFF7E8
/* 定义例程名和例程发布日期 */
#define EXAMPLE_NAME "WSNEP_V01-051_SPI 实验( RC522) "
#define EXAMPLE_DATE "2021-06-01"
#define DEMO_VER "1.0"
/* 定义 LCD 显示相关公共信息 */
#define DEV_NAME "Wisdomclassroom"
#define LCD_EXAMPLE_NAME "RFID"
const unsigned char str1[] = {0x08,0x00,0x29,0x00,0xCA,0x00,0x4C,0xFF,0x78,0x92,0x4C,0x92,0x4A,0x92,0x08,0x92,0x00,0x92,0x7E,0x92,0x42,0x92,0x42,0xFF,0x42,0x00,0x7E,0x00,0x00,0x00,0x00,0x00};/*"智",0*/
const unsigned char str2[] = {0x44,0x01,0x54,0x06,0x55,0x10,0xFF,0x50,0x55,0x56,0x55,0x51,0x45,0x51,0x01,0x59,0x45,0x55,0x55,0x51,0x55,0x51,0xFF,0x53,0x55,0xF0,0x54,0x04,0x44,0x03,0x00,0x00};/*"慧",1*/
const unsigned char str3[] = {0x04,0x20,0x25,0x50,0x25,0x92,0x25,0x11,0xFF,0x7E,0x25,0xA0,0x2D,0x20,0x14,0x21,0x21,0x02,0x0E,0x04,0xF1,0xC8,0x10,0x30,0x10,0xCC,0x1F,0x02,0x10,0x01,0x00,0x00};/*"教",2*/
const unsigned char str4[] = {0x08,0x02,0x30,0x02,0x24,0x12,0x24,0x92,0x25,0x92,0x26,0x92,0xA4,0x92,0x64,0xFE,0x24,0x92,0x24,0x92,0x25,0x92,0x24,0xD2,0x24,0x12,0x28,0x02,0x30,0x02,0x00,0x00};/*"室",3*/
const unsigned char str5[] = {0x02,0x00,0x42,0x00,0x33,0xFE,0x00,0x04,0x00,0x08,0x22,0x00,0x2A,0xFF,0x2A,0xA8,0x2A,0xA8,0xFE,0xA8,0x2A,0xAA,0x2A,0xA9,0x2A,0xFE,0x22,0x00,0x02,0x00,0x00,0x00};/*"请",4*/
const unsigned char str6[] = {0x00,0x08,0x00,0x70,0x7F,0x80,0x48,0xFC,0x48,0x80,0x48,0x80,0x4F,0xFF,0x48,0x88,0x48,0x84,0x78,0xF8,0x00,0x00,0x0F,0xF0,0x00,0x02,0x00,0x01,0xFF,0xFE,0x00,0x00};/*"刷",5*/
const unsigned char str7[] = {0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0xFF,0xFF,0x22,0x00,0x22,0x00,0x22,0x40,0x22,0x20,0x22,0x10,0x22,0x08,0x02,0x00,0x02,0x00,0x00,0x00};/*"卡",6*/
void IC_test( void )
{
char cStr [ 30 ];
char EptStr[30];
uint8_t ucArray_ID [ 4 ]; /*先后存放 IC 卡的类型和 UID(IC 卡序列号)*/
uint8_t ucStatusReturn; /*返回状态 */
static u16 ucLineCount = 170; /* LCD 起始行为 170 */
sprintf ( EptStr, " "); /* LCD 显示空白,为清空 LCD 显示做准备 */
/*寻卡*/
if ( ( ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID ) ) != MI_OK )
{
/*若失败再次寻卡*/
ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID );
}
if ( ucStatusReturn == MI_OK )
{
// printf("----------------------------\r\n");
// printf("0x%02X%02X\r\n",ucArray_ID [ 0 ],ucArray_ID [ 1 ]);
/*防冲撞(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作) */
if ( PcdAnticoll ( ucArray_ID ) == MI_OK )
{
sprintf ( cStr, "%02X%02X%02X%02X",ucArray_ID [ 0 ],ucArray_ID [ 1 ],ucArray_ID [ 2 ],ucArray_ID [ 3 ]);
//printf("%s",ucArray_ID);
// USART1_Send_Data((uint8_t*)ucArray_ID,4);
printf ( "%s",cStr );
if ( ucLineCount == 290 ) /* 最多同时显示 6 行 */
{
for(ucLineCount=170;ucLineCount<=290;ucLineCount+=20)
{
LCD_ShowString(30,ucLineCount,200,16,16,(u8*)EptStr); //循环清空 6 行 LCD 显示
}
ucLineCount=170; /* 设置 LCD 起始行为 170 */
}
LCD_ShowString(30,ucLineCount,200,16,16,(u8*)cStr); //显示读取到的卡号
ucLineCount += 20; /* 设置下一次 LCD 显示行,自增 20 */
}
}
}
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: c程序入口
* 形 参:无
* 返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
uint8_t lcd_id[12]; /* 存放 LCD ID 字符串 */
// uint8_t a0;
/*
ST 固件库中的启动文件已经执行了 SystemInit() 函数,该函数在 system_stm32f10x.c 文件,主要功能是
配置 CPU 系统的时钟,内部 Flash 访问时序,配置 FSMC 用于外部 SRAM
*/
bsp_Init(); /* 硬件初始化 */
TIM_Init();
// SMBus_Init();//人体红外初始化
// bsp_InitUART5(9600);/*注意,串口 4 初始化放在 LCD 之前*/
TFTLCD_Init(); /* TFTLCD 初始化*/
bsp_InitWS2812B(); /* 初始化 WS2812B */
POINT_COLOR=BLUE;
sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将 LCD ID 打印到 lcd_id 数组。
LCD_ShowString(30,40,200,24,24,(uint8_t*)DEV_NAME);
LCD_ShowString(30,70,200,16,16,(uint8_t*)LCD_EXAMPLE_NAME);
LCD_ShowString(30,90,200,16,16,(uint8_t*)DEMO_VER);
LCD_ShowString(30,110,200,16,16,lcd_id); //显示 LCD ID
LCD_ShowString(30,130,200,12,12,(uint8_t*)EXAMPLE_DATE);
//汉字部分
Show_Graph(30,150,(u8*)str1,16,0);
Show_Graph(45,150,(u8*)str2,16,0);
Show_Graph(60,150,(u8*)str3,16,0);
Show_Graph(75,150,(u8*)str4,16,0);
Show_Graph(105,150,(u8*)str5,16,0);
Show_Graph(120,150,(u8*)str6,16,0);
Show_Graph(135,150,(u8*)str7,16,0);
bsp_InitRc522(); /* 初始化 RC522 的 SPI 端口 */
PcdReset(); /* 复位 RC522 */
M500PcdConfigISOType('A'); /* 设置工作方式 */
bsp_StartAutoTimer(0, 300); /* 定时器 0 周期 300 毫秒 */
bsp_StartAutoTimer(1, 500); /* 定时器 1 周期 500 毫秒 */
while(1)
{
bsp_Idle(); /* CPU 空闲时执行的函数,在 bsp.c */
if (bsp_CheckTimer(0)) /* 定时到 */
{
bsp_LedToggle(2);
}
if (bsp_CheckTimer(1)) /* 定时到 */
{
IC_test();
}
}
}
串口通信模块
#include "bsp.h"
#include "bsp_fan.h"
#include "bsp_uart5.h"
static const uint8_t temp[16] = {0XFD,0X00,0X0D,0X01,0X01,0XBB,0XD4,0XB8,0XE7,0XB4,0XF8,0XB4,0XF8,0XCE,0XD2,0XDC};
static const uint8_t temp1[16] = {0XFD,0X00,0X0D,0X01,0X01,0XC0,0XA4,0XB8,0XE7,0XB4,0XF8,0XB4,0XF8,0XCE,0XD2,0XD7};
#if EN_USART1_RX
uint8_t USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节,末字节为换行符
uint8_t ReceiveState=0; //接收状态标记
uint16_t RxCounter=0;
u16 USART_RX_STA=0; //接收状态标记
#endif
/*
*********************************************************************************************************
* 函 数 名:bsp_InitUart
* 功能说明:初始化 CPU 的 USART1 串口硬件设备
* 形 参:无
* 返 回 值:无
*********************************************************************************************************
*/
/*
********************************************************************************************************
*/
//加入以下代码,支持 printf 函数,而不需要选择 use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
/* 标准库需要的支持函数 */
struct __FILE
{
int handle;
};
FILE __stdout;
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
/*
*********************************************************************************************************
* 函 数 名: fputc
* 功能说明: 重定义 putc 函数,这样可以使用 printf 函数从串口 1 打印输出
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0){};//;//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
void bsp_InitUart1(uint32_t baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
#if EN_USART1_RX //如果使能了接收
NVIC_InitTypeDef NVIC_InitStructure;
#endif
/* 串口 1 TX = PA9 RX = PA10 */
/* 第 1 步: 配置 GPIO */
/* 打开 GPIO 时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/* 打开 UART 时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/* 配置 USART Tx 为复用功能 */ //USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 GPIOA.9
/* 配置 USART Rx 为复用功能 */ //USART1_RX GPIOA.10 初始化
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10; //PA.10
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 GPIOA.10
/* 第 2 步: 配置串口硬件参数 */
USART_InitStructure.USART_BaudRate=baud; //波特率
USART_InitStructure.USART_WordLength=USART_WordLength_8b; //字长为8位数据格式
USART_InitStructure.USART_StopBits=USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity=USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //无硬件数据流控制
USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; //收发模式
USART_Init(USART1,&USART_InitStructure); //初始化串口
#if EN_USART1_RX //如果使能了接收
/* 第 3 步: Usart1 NVIC 配置 */
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); /* 开启串口接受中断 */
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); /* 开启串口空闲中断 */
#endif
/* 第 4 步: 使能串口 1 */
USART_Cmd(USART1,ENABLE); //使能串口
}
/*
*********************************************************************************************************
* 函 数 名:USART1_IRQHandler
* 功能说明:USART1 中断,主要是接收中断和空闲中断
* 形 参:无
* 返 回 值:无
*********************************************************************************************************
*/
void USART1_IRQHandler(void) //串口 1 中断服务程序
{
u8 Res;
bsp_InitUART5(9600);/*注意,串口 4 初始化放在 LCD 之前*/
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
Res=USART_ReceiveData(USART1); //读取接收到的数据
bsp_InitFan(); /* 初始化风扇 */
if(Res=='1' || Res==0x01) //接收到1或0x01,正转舵机3s,停止
{
TIM_SetCompare1(TIM1,10);
delay_ms(3000);
TIM_SetCompare1(TIM1,15);
}
if(Res=='2' || Res==0x02) //接收到2或0x02,反转转舵机3s,停止
{
TIM_SetCompare1(TIM1,20);
delay_ms(3000);
TIM_SetCompare1(TIM1,15);
}
if(Res=='3'||Res==0x03) //开灯环
{
WS2812_effect(1);
}
if(Res=='4'||Res==0x04) //关灯环
{
WS2812_effect(0);
}
if(Res=='5'||Res==0x05)
{
bsp_FanOn(1);
bsp_FanOn(2);
}
if(Res=='6'||Res==0x06)
{
bsp_FanOff(1);
bsp_FanOff(2);
}
if(Res=='7'||Res==0x07)
{
bsp_BeepToggle();
delay_ms(200);
bsp_BeepToggle();
}
if(Res=='8'||Res==0x08)
{
bsp_BeepOff;
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
USART_RX_STA=0;
}
/*
*********************************************************************************************************
* 函 数 名: USART1_Send_Data
* 功能说明: USART1 发送 len 个字。
* 形 参: buf:发送区首地址
* len:发送的字节数 0~255
* 返 回 值: 无
*********************************************************************************************************
*/
void USART1_Send_Data(uint8_t *buf,uint8_t len)
{
uint8_t t;
for(t=0;t<len;t++) //循环发送数据
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_SendData(USART1,buf[t]);
}
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
uart.h
#ifndef _BSP_UART_H_
#define _BSP_UART_H_
#include "sys.h"
#define USART_REC_LEN 1024 //定义最大接收字节数1024
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern uint8_t USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节,末字节为换行符
extern uint8_t ReceiveState; //接收状态标记
extern uint16_t RxCounter;
void bsp_InitUart1(uint32_t baud);
#if EN_USART1_RX //如果使能了接收
void Uart0_STA_Clr(void);
void USART1_Send_Data(uint8_t *buf,uint8_t len);
#endif
#endif
/***************************** 德致伦电子 DeZLinc (END OF FILE) *********************************/
PWM模块部分
#include "pwm.h"
/*
*********************************************************************************************************
* 函 数 名: bsp_GetRCCofGPIO
* 功能说明: 根据 GPIO 得到 RCC 寄存器
* 形 参:无
* 返 回 值: GPIO 外设时钟名
*********************************************************************************************************
*/
static void TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 输出比较通道 GPIO 初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// BKIN引脚默认先输出低电平
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}
static void Advance_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 开启定时器时钟,即内部时钟CK_INT=72M
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
//TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period= (200-1);
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler= (7200-1);
// 时钟分频因子 ,用于配置死区时间,没用到,随意
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,没用到,可以随意设置
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
/*--------------------输出比较结构体初始化-------------------*/
// 配置为PWM模式2
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
// 互补输出使能
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
// 设置占空比大小
TIM_OCInitStructure.TIM_Pulse = 0;
// 输出通道电平极性配置
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 输出通道空闲电平极性配置
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
// 使能计数器
TIM_Cmd(TIM1, ENABLE);
// 主输出使能,当使用的是通用定时器时,这句不需要
TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
void TIM_Init(void)
{
TIM_GPIO_Config();
Advance_TIM_Config();
}
ws2812.c
#include "bsp.h"
static void send_Data_rgb(uint8_t r,uint8_t g, uint8_t b);
uint16_t LED_BYTE_Buffer[LED_BYTE_Buffer_Size]; //发送数组缓存
/*
*********************************************************************************************************
* 函 数 名: bsp_InitWS2812B
* 功能说明: 配置 WS2812 灯相关的 GPIO 和 TIM PWM 功能
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
extern const uint8_t test_color[9][3] = {
{0, 0, 0 },//黑色(不亮)
{32, 32, 32},//白色
{64, 0, 0 },//红色
{0, 64, 0 },//绿色
{0, 0, 64},//蓝色
{64, 64, 0 },//黄色
{64, 32, 0 },//橙色
{64, 0, 64},//紫红色
{0, 64, 64},//青色
};
void bsp_InitWS2812B(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(WS2812_GPIO_RCC, ENABLE); //使能 GPIO 时钟
GPIO_InitStructure.GPIO_Pin = WS2812_GPIO_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(WS2812_GPIO_Port, &GPIO_InitStructure);
RCC_APB1PeriphClockCmd(WS2812_TIM_RCC, ENABLE); //使能定时器 x 时钟
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 89; // 800kHz = 72M/(89+1)/(0+1)=800KHz
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(WS2812_TIM, &TIM_TimeBaseStructure); //根据 TIM_TimeBaseInitStruct 中指定的参数初始化TIMx 的时间基数单位
/* PWM1 Mode configuration: Channelx */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM 脉冲宽度调制模式 1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM 输出比较极性高
WS2812_TIM_OCxInit(WS2812_TIM, &TIM_OCInitStructure); //配置输出设置函数
/* 配置 DMA */
/* 使能 DMA 时钟 */
RCC_AHBPeriphClockCmd(WS2812_DMA_RCC, ENABLE);
/* 配置 WS2812 所用 TIM 的 DMA 通道信息 */
DMA_DeInit(WS2812_DMA_CH);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&WS2812_DMA_ADDR; //DMA 外设基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LED_BYTE_Buffer; //DMA 内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = LED_BYTE_Buffer_Size; //DMA 通道的 DMA 缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为 16 位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为 16 位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//工作在正常模式 // stop DMA feed after buffer size is reached
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA 通道 x 拥有高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA 通道 x 没有设置为内存到内存传输
DMA_Init(WS2812_DMA_CH, &DMA_InitStructure); //根据 DMA_InitStruct 中指定的参数初始化 DMA 的通道
/* 只能使用通道 x TIMx_UP */
TIM_DMACmd(WS2812_TIM, TIM_DMA_Update, ENABLE);
}
/*
*********************************************************************************************************
* 函 数 名: send_Data_rgb
* 功能说明: 发送一次数据
* 形 参: red 红色数值, green 绿色数值, blue 蓝色数值
* 返 回 值: 无
*********************************************************************************************************
*/
static void send_Data_rgb(uint8_t red,uint8_t green, uint8_t blue)
{
uint8_t i=0;
//注意需要发送的数据顺序高位->低位的顺序 是 绿->红->蓝
uint32_t rgb_value = ( ( (uint32_t)green ) << 16 ) | ( ( (uint32_t)red ) << 8 ) | ( (uint32_t)blue );
for(i=0;i<24;i++)
{
//判断最高位是否为 1,而赋值
LED_BYTE_Buffer[i] = ( ( rgb_value << i ) & 0x800000 ) > 0 ? TIMING_ONE : TIMING_ZERO;
}
DMA_SetCurrDataCounter(WS2812_DMA_CH, LED_BYTE_Buffer_Size); // 装载需要发送的数据长度到 DMA 通道
DMA_Cmd(WS2812_DMA_CH, ENABLE); // 使能对应的 DMA 通道
TIM_Cmd(WS2812_TIM, ENABLE); // 使能对应的 TIMx
while(!DMA_GetFlagStatus(WS2812_DMA_TC)); // 等待传输完毕
TIM_Cmd(WS2812_TIM, DISABLE); // 失能对应的 TIMx
DMA_Cmd(WS2812_DMA_CH, DISABLE); // 失能对应的 DMA 通道
DMA_ClearFlag(WS2812_DMA_TC); // 清除对应的 DMA 传输完成标志位
}
/*
*********************************************************************************************************
* 函 数 名: ws2812Send
* 功能说明: 发送一次灯带数据
* 形 参: uint8_t (*color)[3] 是二维数组(按照[0]对应红色数值,[1]对应绿色数值,[2]对应蓝色数值)
* uint16_t len 需要同时操作的灯的数量(注意,不要超过 NBR_LEDS)
* 返 回 值: 无
*********************************************************************************************************
*/
void ws2812Send(uint8_t (*color)[3], uint16_t len)
{
uint16_t i = 0;
if(len>NBR_LEDS)
{
printf("操作的灯数量超出定义\r\n");
return;
}
for(i = 0;i < NBR_LEDS; i++)
{
if(i<len)
{
send_Data_rgb(color[i][0],color[i][1],color[i][2]); //调换rgb 灯环的颜色顺序导致不同
}
else
{
send_Data_rgb(0,0,0);//灭剩余的灯 ,注意,黑色的 rgb 为 0,0,0
}
}
}
/*
*********************************************************************************************************
* 函 数 名: WS2812_effect
* 功能说明: WS2812 效果
* 形 参: uint8_t effect 效果参数
* 返 回 值: 无
*********************************************************************************************************
*/
void WS2812_effect(uint8_t effect)
{
uint8_t i=0;
uint8_t buf[NBR_LEDS][3] = {0};
for(i=0;i<NBR_LEDS;i++) //填充数值
{
buf[i][0] = test_color[effect][0];
buf[i][1] = test_color[effect][1];
buf[i][2] = test_color[effect][2];
}
ws2812Send(buf,NBR_LEDS); //显示
}
ws2812.h
#ifndef __BSP_WS2812_H
#define __BSP_WS2812_H
#include "sys.h"
/
移植修改区
按照实际端口修改
较前版本更加容易移植(注意,端口仅仅适用于非 JTAG/SWD 引脚,如果是 JTAG 引脚,需要打开 AFIO 时钟,并失能JTAG)
//注意,配合程序仅适用于 TIM2~TIM5 通用定时器
#define WS2812_GPIO_RCC RCC_APB2Periph_GPIOB //端口时钟
#define WS2812_GPIO_Pin GPIO_Pin_6 //引脚号
#define WS2812_GPIO_Port GPIOB //端口号
#define WS2812_TIM_RCC RCC_APB1Periph_TIM4 //定时器时钟
#define WS2812_TIM TIM4 //定时器
#define WS2812_TIM_OCxInit TIM_OC1Init //定时器比较函数
#define WS2812_DMA_RCC RCC_AHBPeriph_DMA1 //DMA 时钟
#define WS2812_DMA_ADDR TIM4->CCR1 //连接的外设基地址
#define WS2812_DMA_CH DMA1_Channel7 //DMAx 通道
#define WS2812_DMA_TC DMA1_FLAG_TC7 //DMAx 传输完成标志
/
#define TIMING_ONE 61 //T1H 1 码 68%占空比 68%*89=61
#define TIMING_ZERO 28 //T0L 0 码 32%占空比 32%*89=28
#define LED_BYTE_Buffer_Size 24 //RGB 共 24
#define NBR_LEDS 12 //WS2812 RGB 灯个数
/* 供外部调用的函数声明 */
void bsp_InitWS2812B(void);
void ws2812Send(uint8_t (*color)[3], uint16_t len);
void WS2812_effect(uint8_t effect);
#endif
RC522.c
#include "bsp.h"
/*
*********************************************************************************************************
* 函 数 名: bsp_InitRc522
* 功能说明: 初始化 RC522 的 IO 口和 SPI 功能配置
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitRc522(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_ALL_RC522, ENABLE ); //RC522 时钟使能
GPIO_InitStructure.GPIO_Pin = RC522_CS_GPIO_PIN; // RC522_CS 推挽
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(RC522_CS_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = RC522_RST_GPIO_PIN; // RC522_RST 推挽
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(RC522_RST_GPIO_PORT, &GPIO_InitStructure);
RC522_CS = 1; //RC522 不选中
RC522_RST = 1; //RC522 不复位
bsp_InitSPIx(RC522_SPIx); //初始化 CLK,MISO,MOSI 三个端口和 SPI 时钟
/* SPI 模式配置 */
// FLASH 芯片 支持 SPI 模式 0,据此设置 CPOL CPHA
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
//SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(RC522_SPIx , &SPI_InitStructure);
/* 使能 SPI */
SPI_Cmd(RC522_SPIx , ENABLE);
}
/*
*********************************************************************************************************
* 函 数 名: SPI_RC522_SendByte
* 功能说明: 从 SPI 总线写入一个字节
* 形 参: byte:要写入的字节
* 返 回 值: 返回接收到的数据
*********************************************************************************************************
*/
uint8_t SPI_RC522_SendByte(uint8_t byte)
{
return SPIx_ReadWriteByte(RC522_SPIx,byte);
}
/*
*********************************************************************************************************
* 函 数 名: SPI_RC522_ReadByte
* 功能说明: 从 SPI 总线读取一个字节
* 形 参: 无
* 返 回 值: 读取到的字节
*********************************************************************************************************
*/
uint8_t SPI_RC522_ReadByte(void)
{
return (SPI_RC522_SendByte(RC522_Dummy_Byte));
}
/*
*********************************************************************************************************
* 函 数 名: ReadRawRC
* 功能说明: 读 RC522 寄存器
* 形 参: Address:寄存器地址
* 返 回 值: 读出的值
*********************************************************************************************************
*/
uint8_t ReadRawRC(uint8_t Address)
{
uint8_t ucAddr;
uint8_t ucResult=0;
ucAddr = ( ( ( Address <<1 ) & 0x7E ) | 0x80 );
/*选择 RC522: CS 低 */
/* 通讯开始: CS 低 */
RC522_CS = 0;
SPI_RC522_SendByte( ucAddr );
ucResult = SPI_RC522_ReadByte();
/* 停止信号 RC522: CS 高 */
/* 通讯开始: CS 低 */
RC522_CS = 1;
return ucResult;
}
/*
*********************************************************************************************************
* 函 数 名: ReadRawRC
* 功能说明: 写 RC522 寄存器
* 形 参: uint8_t Address:寄存器地址
* uint8_t value:写入的值
* 返 回 值: 无
*********************************************************************************************************
*/
void WriteRawRC(uint8_t Address, uint8_t value)
{
uint8_t ucAddr;
ucAddr = ( ( Address << 1 ) & 0x7E );
/*选择 RC522: CS 低 */
/* 通讯开始: CS 低 */
RC522_CS = 0;
SPI_RC522_SendByte( ucAddr );
SPI_RC522_SendByte( value );
/* 停止信号 RC522: CS 高 */
/* 通讯开始: CS 低 */
RC522_CS = 1;
}
/*
*********************************************************************************************************
* 函 数 名: SetBitMask
* 功能说明: 对 RC522 寄存器置位
* 形 参: uint8_t ucReg,寄存器地址
* uint8_t ucMask,置位值
* 返 回 值: 无
*********************************************************************************************************
*/
void SetBitMask ( uint8_t ucReg, uint8_t ucMask )
{
uint8_t ucTemp = 0;
ucTemp = ReadRawRC ( ucReg );
WriteRawRC ( ucReg, ucTemp | ucMask ); // set bit mask
}
/*
*********************************************************************************************************
* 函 数 名: ClearBitMask
* 功能说明: 对 RC522 寄存器清位
* 形 参: uint8_t ucReg,寄存器地址
* uint8_t ucMask,清位值
* 返 回 值: 无
*********************************************************************************************************
*/
void ClearBitMask ( uint8_t ucReg, uint8_t ucMask )
{
uint8_t ucTemp = 0;
ucTemp = ReadRawRC ( ucReg );
WriteRawRC ( ucReg, ucTemp & ( ~ ucMask) ); // clear bit mask
}
/*
*********************************************************************************************************
* 函 数 名: PcdAntennaOn
* 功能说明: 开启天线
* 形 参: 无
* 返 回 值: 无
* 注 意:每次启动或关闭天险发射之间应至少有 1ms 的间隔
*********************************************************************************************************
*/
void PcdAntennaOn ( void )
{
uint8_t uc;
uc = ReadRawRC ( TxControlReg );
if ( ! ( uc & 0x03 ) )
{
SetBitMask( TxControlReg, 0x03 );
}
}
/*
*********************************************************************************************************
* 函 数 名: PcdAntennaOff
* 功能说明: 关闭天线
* 形 参: 无
* 返 回 值: 无
* 注 意:每次启动或关闭天险发射之间应至少有 1ms 的间隔
*********************************************************************************************************
*/
void PcdAntennaOff ( void )
{
ClearBitMask ( TxControlReg, 0x03 );
}
/*
*********************************************************************************************************
* 函 数 名: PcdReset
* 功能说明: 复位 RC522
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void PcdReset ( void )
{
/* 复位 RC522: RST 高 */
RC522_RST = 1;
delay_us(10); //_NOP();
/* 取消复位 RC522: RST 低 */
RC522_RST = 0;
delay_us(10); //_NOP();
/* 复位 RC522: RST 高 */
RC522_RST = 1;
delay_us(10); //_NOP();
WriteRawRC ( CommandReg, 0x0f );
while ( ReadRawRC ( CommandReg ) & 0x10 );
delay_us(10); //_NOP();
WriteRawRC ( ModeReg, 0x3D ); //定义发送和接收常用模式 和 Mifare 卡通讯, CRC 初始值 0x6363
WriteRawRC ( TReloadRegL, 30 ); //16 位定时器低位
WriteRawRC ( TReloadRegH, 0 ); //16 位定时器高位
WriteRawRC ( TModeReg, 0x8D ); //定义内部定时器的设置
WriteRawRC ( TPrescalerReg, 0x3E ); //设置定时器分频系数
WriteRawRC ( TxAutoReg, 0x40 ); //调制发送信号为 100%ASK
}
/*
*********************************************************************************************************
* 函 数 名: PcdComMF522
* 功能说明: 通过 RC522 和 ISO14443 卡通讯
* 形 参: ucCommand, RC522 命令字
* pInData,通过 RC522 发送到卡片的数据
* ucInLenByte,发送数据的字节长度
* pOutData,接收到的卡片返回数据
* pOutLenBit,返回数据的位长度
* 返 回 值: 状态值= MI_OK,成功
*********************************************************************************************************
*/
char PcdComMF522 ( uint8_t ucCommand, uint8_t * pInData, uint8_t ucInLenByte, uint8_t * pOutData, uint32_t * pOutLenBit )
{
char cStatus = MI_ERR;
uint8_t ucIrqEn = 0x00;
uint8_t ucWaitFor = 0x00;
uint8_t ucLastBits;
uint8_t ucN;
uint32_t ul;
switch ( ucCommand )
{
case PCD_AUTHENT: //Mifare 认证
{
ucIrqEn = 0x12; //允许错误中断请求 ErrIEn 允许空闲中断 IdleIEn
ucWaitFor = 0x10; //认证寻卡等待时候 查询空闲中断标志位
}
break;
case PCD_TRANSCEIVE: //接收发送 发送接收
{
ucIrqEn = 0x77; //允许 TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEn
ucWaitFor = 0x30; //寻卡等待时候 查询接收中断标志位与 空闲中断标志位
}
break;
default:break;
}
WriteRawRC ( ComIEnReg, ucIrqEn | 0x80 ); //IRqInv 置位管脚 IRQ 与 Status1Reg 的 IRq 位的值相反
ClearBitMask ( ComIrqReg, 0x80 ); //Set1 该位清零时, CommIRqReg 的屏蔽位清零
WriteRawRC ( CommandReg, PCD_IDLE ); //写空闲命令
SetBitMask ( FIFOLevelReg, 0x80 ); //置位 FlushBuffer 清除内部 FIFO 的读和写指针以及 ErrReg 的BufferOvfl 标志位被清除
for ( ul = 0; ul < ucInLenByte; ul ++ )
{
WriteRawRC ( FIFODataReg, pInData [ ul ] ); //写数据进 FIFOdata
}
WriteRawRC ( CommandReg, ucCommand ); //写命令
if ( ucCommand == PCD_TRANSCEIVE )
{
SetBitMask(BitFramingReg,0x80); //StartSend 置位启动数据发送 该位与收发命令使用时才有效
}
ul = 1000;//根据时钟频率调整,操作 M1 卡最大等待时间 25ms
do //认证 与寻卡等待时间
{
ucN = ReadRawRC ( ComIrqReg ); //查询事件中断
ul --;
}
while ( ( ul != 0 ) && ( ! ( ucN & 0x01 ) ) && ( ! ( ucN & ucWaitFor ) ) ); //退出条件 i=0,定时器中断,与写空闲命令
ClearBitMask ( BitFramingReg, 0x80 ); //清理允许 StartSend 位
if ( ul != 0 )
{
if ( ! ( ReadRawRC ( ErrorReg ) & 0x1B ) ) // 读 错 误 标 志 寄 存 器 BufferOfI CollErr ParityErrProtocolErr
{
cStatus = MI_OK;
if ( ucN & ucIrqEn & 0x01 ) //是否发生定时器中断
{
cStatus = MI_NOTAGERR;
}
if ( ucCommand == PCD_TRANSCEIVE )
{
ucN = ReadRawRC ( FIFOLevelReg ); //读 FIFO 中保存的字节数
ucLastBits = ReadRawRC ( ControlReg ) & 0x07; //最后接收到得字节的有效位数
if ( ucLastBits )
{
* pOutLenBit = ( ucN - 1 ) * 8 + ucLastBits; //N 个字节数减去 1(最后一个字节) +最后一位的位数 读取到的数据总位数
}
else
{
* pOutLenBit = ucN * 8; //最后接收到的字节整个字节有效
}
if ( ucN == 0 )
{
ucN = 1;
}
if ( ucN > MAXRLEN )
{
ucN = MAXRLEN;
}
for ( ul = 0; ul < ucN; ul ++ )
{
pOutData [ ul ] = ReadRawRC ( FIFODataReg );
}
}
}
else
{
cStatus = MI_ERR;
}
}
SetBitMask ( ControlReg, 0x80 ); // stop timer now
WriteRawRC ( CommandReg, PCD_IDLE );
return cStatus;
}
/*
*********************************************************************************************************
* 函 数 名: M500PcdConfigISOType
* 功能说明: 设置 RC522 的工作方式
* 形 参: ucType,工作方式
* 返 回 值: 无
*********************************************************************************************************
*/
void M500PcdConfigISOType ( uint8_t ucType )
{
if ( ucType == 'A') //ISO14443_A
{
ClearBitMask ( Status2Reg, 0x08 );
WriteRawRC ( ModeReg, 0x3D ); //3F
WriteRawRC ( RxSelReg, 0x86 ); //84
WriteRawRC( RFCfgReg, 0x7F ); //4F
WriteRawRC( TReloadRegL, 30 );//tmoLength);// TReloadVal = 'h6a =tmoLength(dec)
WriteRawRC ( TReloadRegH, 0 );
WriteRawRC ( TModeReg, 0x8D );
WriteRawRC ( TPrescalerReg, 0x3E );
delay_us ( 2 );
PcdAntennaOn ();//开天线
}
}
/*
*********************************************************************************************************
* 函 数 名: PcdRequest
* 功能说明: 寻卡
* 形 参: ucReq_code,寻卡方式
* = 0x52,寻感应区内所有符合 14443A 标准的卡
* = 0x26,寻未进入休眠状态的卡
* pTagType,卡片类型代码
* = 0x4400, Mifare_UltraLight
* = 0x0400, Mifare_One(S50)
* = 0x0200, Mifare_One(S70)
* = 0x0800, Mifare_Pro(X))
* = 0x4403, Mifare_DESFire
* 返 回 值: 状态值= MI_OK,成功
*********************************************************************************************************
*/
char PcdRequest ( uint8_t ucReq_code, uint8_t * pTagType )
{
char cStatus;
uint8_t ucComMF522Buf [ MAXRLEN ];
uint32_t ulLen;
ClearBitMask ( Status2Reg, 0x08 ); //清理指示 MIFARECyptol 单元接通以及所有卡的数据通信被加密的情况
WriteRawRC ( BitFramingReg, 0x07 ); // 发送的最后一个字节的 七位
SetBitMask ( TxControlReg, 0x03 ); //TX1,TX2 管脚的输出信号传递经发送调制的 13.56 的能量载波信号
ucComMF522Buf [ 0 ] = ucReq_code; //存入 卡片命令字
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, & ulLen ); //寻卡
if ( ( cStatus == MI_OK ) && ( ulLen == 0x10 ) ) //寻卡成功返回卡类型
{
*pTagType = ucComMF522Buf [ 0 ];
*( pTagType + 1 ) = ucComMF522Buf [ 1 ];
}
else
{
cStatus = MI_ERR;
}
return cStatus;
}
/*
*********************************************************************************************************
* 函 数 名: PcdAnticoll
* 功能说明: 防冲撞
* 形 参: pSnr,卡片序列号, 4 字节
* 返 回 值: 状态值= MI_OK,成功
*********************************************************************************************************
*/
char PcdAnticoll ( uint8_t * pSnr )
{
char cStatus;
uint8_t uc, ucSnr_check = 0;
uint8_t ucComMF522Buf [ MAXRLEN ];
uint32_t ulLen;
ClearBitMask ( Status2Reg, 0x08 ); //清 MFCryptol On 位 只有成功执行 MFAuthent 命令后,该位才能置位
WriteRawRC ( BitFramingReg, 0x00); //清理寄存器 停止收发
ClearBitMask ( CollReg, 0x80 ); //清 ValuesAfterColl 所有接收的位在冲突后被清除
ucComMF522Buf [ 0 ] = 0x93; //卡片防冲突命令
ucComMF522Buf [ 1 ] = 0x20;
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, & ulLen);//与卡片通信
if ( cStatus == MI_OK) //通信成功
{
for ( uc = 0; uc < 4; uc ++ )
{
*( pSnr + uc ) = ucComMF522Buf [ uc ]; //读出 UID
ucSnr_check ^= ucComMF522Buf [ uc ];
}
if ( ucSnr_check != ucComMF522Buf [ uc ] )
{
cStatus = MI_ERR;
}
}
SetBitMask ( CollReg, 0x80 );
return cStatus;
}
/*
*********************************************************************************************************
* 函 数 名: CalulateCRC
* 功能说明: 用 RC522 计算 CRC16
* 形 参:
* pIndata,计算 CRC16 的数组
* ucLen,计算 CRC16 的数组字节长度
* pOutData,存放计算结果存放的首地址
* 返 回 值: 无
*********************************************************************************************************
*/
void CalulateCRC ( uint8_t * pIndata, uint8_t ucLen, uint8_t * pOutData )
{
uint8_t uc, ucN;
ClearBitMask(DivIrqReg,0x04);
WriteRawRC(CommandReg,PCD_IDLE);
SetBitMask(FIFOLevelReg,0x80);
for ( uc = 0; uc < ucLen; uc ++)
{
WriteRawRC ( FIFODataReg, * ( pIndata + uc ) );
}
WriteRawRC ( CommandReg, PCD_CALCCRC );
uc = 0xFF;
do
{
ucN = ReadRawRC ( DivIrqReg );
uc --;
}
while ( ( uc != 0 ) && ! ( ucN & 0x04 ) );
pOutData [ 0 ] = ReadRawRC ( CRCResultRegL );
pOutData [ 1 ] = ReadRawRC ( CRCResultRegM );
}
/*
*********************************************************************************************************
* 函 数 名: PcdSelect
* 功能说明: 选定卡片
* 形 参: pSnr,卡片序列号, 4 字节
* 返 回 值: 状态值= MI_OK,成功
*********************************************************************************************************
*/
char PcdSelect ( uint8_t * pSnr )
{
char ucN;
uint8_t uc;
uint8_t ucComMF522Buf [ MAXRLEN ];
uint32_t ulLen;
ucComMF522Buf [ 0 ] = PICC_ANTICOLL1;
ucComMF522Buf [ 1 ] = 0x70;
ucComMF522Buf [ 6 ] = 0;
for ( uc = 0; uc < 4; uc ++ )
{
ucComMF522Buf [ uc + 2 ] = * ( pSnr + uc );
ucComMF522Buf [ 6 ] ^= * ( pSnr + uc );
}
CalulateCRC ( ucComMF522Buf, 7, & ucComMF522Buf [ 7 ] );
ClearBitMask ( Status2Reg, 0x08 );
ucN = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, & ulLen );
if ( ( ucN == MI_OK ) && ( ulLen == 0x18 ) )
{
ucN = MI_OK;
}
else
{
ucN = MI_ERR;
}
return ucN;
}
/*
*********************************************************************************************************
* 函 数 名: PcdSelect
* 功能说明: 验证卡片密码
* 形 参:
* ucAuth_mode,密码验证模式
* = 0x60,验证 A 密钥
* = 0x61,验证 B 密钥
* ucAddr,块地址
* pKey,密码
* pSnr,卡片序列号, 4 字节
* 返 回 值: 状态值= MI_OK,成功
*********************************************************************************************************
*/
char PcdAuthState ( uint8_t ucAuth_mode, uint8_t ucAddr, uint8_t * pKey, uint8_t * pSnr )
{
char cStatus;
uint8_t uc, ucComMF522Buf [ MAXRLEN ];
uint32_t ulLen;
ucComMF522Buf [ 0 ] = ucAuth_mode;
ucComMF522Buf [ 1 ] = ucAddr;
for ( uc = 0; uc < 6; uc ++ )
{
ucComMF522Buf [ uc + 2 ] = * ( pKey + uc );
}
for ( uc = 0; uc < 6; uc ++ )
{
ucComMF522Buf [ uc + 8 ] = * ( pSnr + uc );
}
cStatus = PcdComMF522 ( PCD_AUTHENT, ucComMF522Buf, 12, ucComMF522Buf, & ulLen );
if ( ( cStatus != MI_OK ) || ( ! ( ReadRawRC ( Status2Reg ) & 0x08 ) ) )
{
cStatus = MI_ERR;
}
return cStatus;
}
/*
*********************************************************************************************************
* 函 数 名: PcdWrite
* 功能说明: 写数据到 M1 卡一块
* 形 参:
* ucAddr,块地址
* pData,写入的数据, 16 字节
* 返 回 值: 状态值= MI_OK,成功
*********************************************************************************************************
*/
char PcdWrite ( uint8_t ucAddr, uint8_t * pData )
{
char cStatus;
uint8_t uc, ucComMF522Buf [ MAXRLEN ];
uint32_t ulLen;
ucComMF522Buf [ 0 ] = PICC_WRITE;
ucComMF522Buf [ 1 ] = ucAddr;
CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
if ( ( cStatus != MI_OK ) || ( ulLen != 4 ) || ( ( ucComMF522Buf [ 0 ] & 0x0F ) != 0x0A ) )
{
cStatus = MI_ERR;
}
if ( cStatus == MI_OK )
{
//memcpy(ucComMF522Buf, pData, 16);
for ( uc = 0; uc < 16; uc ++ )
{
ucComMF522Buf [ uc ] = * ( pData + uc );
}
CalulateCRC ( ucComMF522Buf, 16, & ucComMF522Buf [ 16 ] );
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 18, ucComMF522Buf, & ulLen );
if ( ( cStatus != MI_OK ) || ( ulLen != 4 ) || ( ( ucComMF522Buf [ 0 ] & 0x0F ) != 0x0A ) )
{
cStatus = MI_ERR;
}
}
return cStatus;
}
/*
*********************************************************************************************************
* 函 数 名: PcdRead
* 功能说明: 读取 M1 卡一块数据
* 形 参:
* ucAddr,块地址
* pData,读出的数据, 16 字节
* 返 回 值: 状态值= MI_OK,成功
*********************************************************************************************************
*/
char PcdRead ( uint8_t ucAddr, uint8_t * pData )
{
char cStatus;
uint8_t uc, ucComMF522Buf [ MAXRLEN ];
uint32_t ulLen;
ucComMF522Buf [ 0 ] = PICC_READ;
ucComMF522Buf [ 1 ] = ucAddr;
CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
if ( ( cStatus == MI_OK ) && ( ulLen == 0x90 ) )
{
for ( uc = 0; uc < 16; uc ++ )
*( pData + uc ) = ucComMF522Buf [ uc ];
}
else
{
cStatus = MI_ERR;
}
return cStatus;
}
/*
*********************************************************************************************************
* 函 数 名: PcdHalt
* 功能说明: 命令卡片进入休眠状态
* 形 参: 无
* 返 回 值: 状态值= MI_OK,成功
*********************************************************************************************************
*/
char PcdHalt( void )
{
uint8_t ucComMF522Buf [ MAXRLEN ];
uint32_t ulLen;
ucComMF522Buf [ 0 ] = PICC_HALT;
ucComMF522Buf [ 1 ] = 0;
CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );
PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
return MI_OK;
}
/***************************** 德致伦电子 DeZLinc (END OF FILE) *********************************/
RC522.h
#ifndef __BSP_RC522_H
#define __BSP_RC522_H
#include "sys.h"
/****************************************************************************/
#define RCC_ALL_RC522 ( RC522_CS_GPIO_CLK | RC522_RST_GPIO_CLK )
#define RC522_CS_GPIO_PIN GPIO_Pin_6 //RC522_CS 引脚号
#define RC522_CS_PIN_ID 6 //RC522_CS 引脚序号
#define RC522_CS_GPIO_PORT GPIOF //RC522_CS 端口号
#define RC522_CS_GPIO_CLK RCC_APB2Periph_GPIOF //RC522_CS 时钟
#define RC522_CS_FUN_OUT PFout //RC522_CS 输出端口配置函数
//#define RC522_CS_FUN_IN PFin //RC522_CS 输入端口配置函数
#define RC522_RST_GPIO_PIN GPIO_Pin_7 //RC522_RST 引脚号
#define RC522_RST_PIN_ID 7 //RC522_RST 引脚序号
#define RC522_RST_GPIO_PORT GPIOB //RC522_RST 端口号
#define RC522_RST_GPIO_CLK RCC_APB2Periph_GPIOB //RC522_RST 时钟
#define RC522_RST_FUN_OUT PBout //RC522_RST 输出端口配置函数
//#define RC522_CS_FUN_IN PBin //RC522_RST 输入端口配置函数
#define RC522_SPIx SPI1 //RC522 使用 SPI1 端口
/****************************************************************************/
//IO 操作函数
#define RC522_CS RC522_CS_FUN_OUT(RC522_CS_PIN_ID) //RC522_CS RC522 的片选信号
#define RC522_RST RC522_RST_FUN_OUT(RC522_RST_PIN_ID) //RC522_RST RC522 的复位信号
/
//MF522 命令字
/
#define PCD_IDLE 0x00 //取消当前命令
#define PCD_AUTHENT 0x0E //验证密钥
#define PCD_RECEIVE 0x08 //接收数据
#define PCD_TRANSMIT 0x04 //发送数据
#define PCD_TRANSCEIVE 0x0C //发送并接收数据
#define PCD_RESETPHASE 0x0F //复位
#define PCD_CALCCRC 0x03 //CRC 计算
/
//Mifare_One 卡片命令字
/
#define PICC_REQIDL 0x26 //寻天线区内未进入休眠状态
#define PICC_REQALL 0x52 //寻天线区内全部卡
#define PICC_ANTICOLL1 0x93 //防冲撞
#define PICC_ANTICOLL2 0x95 //防冲撞
#define PICC_AUTHENT1A 0x60 //验证 A 密钥
#define PICC_AUTHENT1B 0x61 //验证 B 密钥
#define PICC_READ 0x30 //读块
#define PICC_WRITE 0xA0 //写块
#define PICC_DECREMENT 0xC0 //扣款
#define PICC_INCREMENT 0xC1 //充值
#define PICC_RESTORE 0xC2 //调块数据到缓冲区
#define PICC_TRANSFER 0xB0 //保存缓冲区中数据
#define PICC_HALT 0x50 //休眠
/
//MF522 FIFO 长度定义
/
#define DEF_FIFO_LENGTH 64 //FIFO size=64byte
#define MAXRLEN 18
/
//MF522 寄存器定义
/
// PAGE 0
#define RFU00 0x00
#define CommandReg 0x01
#define ComIEnReg 0x02
#define DivlEnReg 0x03
#define ComIrqReg 0x04
#define DivIrqReg 0x05
#define ErrorReg 0x06
#define Status1Reg 0x07
#define Status2Reg 0x08
#define FIFODataReg 0x09
#define FIFOLevelReg 0x0A
#define WaterLevelReg 0x0B
#define ControlReg 0x0C
#define BitFramingReg 0x0D
#define CollReg 0x0E
#define RFU0F 0x0F
// PAGE 1
#define RFU10 0x10
#define ModeReg 0x11
#define TxModeReg 0x12
#define RxModeReg 0x13
#define TxControlReg 0x14
#define TxAutoReg 0x15
#define TxSelReg 0x16
#define RxSelReg 0x17
#define RxThresholdReg 0x18
#define DemodReg 0x19
#define RFU1A 0x1A
#define RFU1B 0x1B
#define MifareReg 0x1C
#define RFU1D 0x1D
#define RFU1E 0x1E
#define SerialSpeedReg 0x1F
// PAGE 2
#define RFU20 0x20
#define CRCResultRegM 0x21
#define CRCResultRegL 0x22
#define RFU23 0x23
#define ModWidthReg 0x24
#define RFU25 0x25
#define RFCfgReg 0x26
#define GsNReg 0x27
#define CWGsCfgReg 0x28
#define ModGsCfgReg 0x29
#define TModeReg 0x2A
#define TPrescalerReg 0x2B
#define TReloadRegH 0x2C
#define TReloadRegL 0x2D
#define TCounterValueRegH 0x2E
#define TCounterValueRegL 0x2F
// PAGE 3
#define RFU30 0x30
#define TestSel1Reg 0x31
#define TestSel2Reg 0x32
#define TestPinEnReg 0x33
#define TestPinValueReg 0x34
#define TestBusReg 0x35
#define AutoTestReg 0x36
#define VersionReg 0x37
#define AnalogTestReg 0x38
#define TestDAC1Reg 0x39
#define TestDAC2Reg 0x3A
#define TestADCReg 0x3B
#define RFU3C 0x3C
#define RFU3D 0x3D
#define RFU3E 0x3E
#define RFU3F 0x3F
/
//和 MF522 通讯时返回的错误代码
/
#define MI_OK 0x26
#define MI_NOTAGERR 0xcc
#define MI_ERR 0xbb
#define RC522_Dummy_Byte 0xFF
/* 供外部调用的函数声明 */
void bsp_InitRc522(void);
void PcdReset ( void ); //复位
void M500PcdConfigISOType ( u8 type ); //工作方式
char PcdRequest ( u8 req_code, u8 * pTagType ); //寻卡
char PcdAnticoll ( u8 * pSnr); //读卡号
char PcdAuthState ( u8 ucAuth_mode, u8 ucAddr, u8 * pKey, u8 * pSnr ); //验证卡片密码
char PcdRead ( u8 ucAddr, u8 * pData ); //读卡
char PcdSelect ( u8 * pSnr ); //选卡
char PcdHalt( void );
#endif
上位机部分
主界面
using System;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Wisdomclassroom
{
public partial class Main : Form
{
SerialClass sc;
SQL sql;
String wendu;
public Main()
{
this.StartPosition = FormStartPosition.CenterScreen;
InitializeComponent();
}
private void Main_Load(object sender, EventArgs e)
{
/*串口部分 */
sc = new SerialClass("COM10", 9600);
sc.openPort();
sc.ReadData = Read1;
/*连接数据库*/
sql = new SQL("11.111.11.11", "xxxx", "xxx", "xxxxx");
sql.Connect();
/* 天气 API外接 json解析*/
string str = API.PostWebRequest("http://apis.juhe.cn/simpleWeather/query", "city=%E5%A4%A9%E6%B4%A5&key=xxxxx");
JObject json1 = JObject.Parse(str);//解析json Newtonsoft.Json.Linq.JObject class.
string jieguo = json1["reason"].ToString();
wendu = json1["result"]["realtime"]["temperature"].ToString();
string info = json1["result"]["realtime"]["info"].ToString();
string humidity = json1["result"]["realtime"]["humidity"].ToString();
temp .Text = wendu;
weather.Text = info;
hum.Text = humidity;
int a = Convert.ToInt32(wendu);
if (numericUpDown1.Value < a)
{
bo = true;
byte[] data = { 0x05 };
sc.SendData(data);
}
else
{
bo = false;
byte[] data = { 0x06 };
sc.SendData(data);
}
/*启动time控件 */
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
/*显示系统时间、日期 */
calendar.Text = DateTime.Now.ToString("yyy年MM月dd日");
time.Text = DateTime.Now.ToString("HH时mm分ss秒");
}
public void Read(string readstr, byte[] temp)
{
//byte[] data = { 0x05 };
//sc.SendData(data);
/* 下位机体温部分 */
Invoke(new MethodInvoker(delegate ()
{
//label1.Text = readstr+"°C";
// MessageBox.Show(byteToHexStr(temp));
//do something...
}));
}
public void Read1(string readstr, byte[] card)
{
/*读取数据库的数据 ,刷卡显示信息*/
string xingming = sql.GetString(string.Format("select xingming from [login] where ID='{0}'", card));
string arr = byteToHexStr(card);
string sr = sql.GetString("select ID from [login] where ID='" + arr + "'");
if (sr != "")
{
string id = sql.GetString("select ID from [login] where ID='" + arr + "'");
string xm = sql.GetString("select xingming from [login] where ID='" + arr + "'");
string xb = sql.GetString("select xingbie from [login] where ID='" + arr + "'");
string xj = sql.GetString("select xueji from [login] where ID='" + arr + "'");
MessageBox.Show("姓名:" + xm + '\n' + "性别:" + xb + '\n' + "学籍校:" + xj + '\n' + "ID:" + id+'\n'+"已打卡");
}
else
{
MessageBox.Show("卡号不存在");
}
}
public string byteToHexStr(byte[] bytes)
{
//字节数组转16进制字符串
string returnStr = "";
if (bytes != null)
{
for (int i = 0; i < bytes.Length; i++)
{
returnStr += bytes[i].ToString("X2");
}
}
return returnStr;
}
private void Main_Activated(object sender, EventArgs e)
{
/*RFID刷卡、打开串口*/
sc = new SerialClass("COM10", 9600);
sc.openPort();
sc.ReadData = Read1;
}
private void openled_Click(object sender, EventArgs e)
{
/* 开下位机灯 */
// byte[] data = { 0xfd, 0x03, 0x07, 0x04, 0x01, 0x60, 0xdf };
byte[] data = { 0x03 };
sc.SendData(data);
MessageBox.Show("教室灯光已开");
}
private void panel3_Click(object sender, EventArgs e)
{
/* 关下位机灯 */
// byte[] data = { 0xfd, 0x03, 0x07, 0x04, 0x02, 0x60, 0xdf };
byte[] data = { 0x04};
sc.SendData(data);
MessageBox.Show("教室灯光已关");
}
private void panel2_MouseUp(object sender, MouseEventArgs e)
{
//效果:按下颜色变暗
}
private void panel4_Click(object sender, EventArgs e)
{
/*温度阈值判断,温度大于阈值开下位机风扇,温度小于阈值关风扇*/
int a = Convert.ToInt32(wendu);
if (numericUpDown1.Value < a)
{
byte[] data = { 0x05 };
sc.SendData(data);
MessageBox.Show("风扇已开");
}
else
{
byte[] data = { 0x06 };
sc.SendData(data);
MessageBox.Show("温度低于阈值");
}
}
private void Media_off_Click(object sender, EventArgs e)
{
//关下位机风扇
byte[] data = { 0x06 };
sc.SendData(data);
MessageBox.Show("风扇已关");
}
private void panel5_Click(object sender, EventArgs e)
{
//开下位机蜂鸣器
byte[] data = { 0x07 };
sc.SendData(data);
MessageBox.Show("音箱已开");
}
private void panel6_Click(object sender, EventArgs e)
{
//开下位机舵机
byte[] data = { 0x01};
sc.SendData(data);
MessageBox.Show("教室窗户已开");
}
private void win_off_Click(object sender, EventArgs e)
{
//关下位机舵机
byte[] data = { 0x02 };
sc.SendData(data);
MessageBox.Show("教室窗户已关");
}
private void beep_off_Click(object sender, EventArgs e)
{
//关下位机蜂鸣器
byte[] data = {0x08};
sc.SendData(data);
}
private void panel1_Click(object sender, EventArgs e)
{
/* 跳转表单 退出线程 */
Form f1 = new DataSheet();
this.Hide();
f1.ShowDialog();
Application.ExitThread();
}
/**/
private void panel5_Click_1(object sender, EventArgs e)
{
/*跳转登录 退出线程*/
Form f = new denglu();
this.Hide();
f.ShowDialog();
Application.ExitThread();
}
private void Main_Deactivate(object sender, EventArgs e)
{
sc.closePort();
}
private void Main_Activated_1(object sender, EventArgs e)
{
sc.openPort();
}
bool bo;
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
/*温度阈值判断,温度大于阈值开下位机风扇,温度小于阈值关风扇*/
int a = Convert.ToInt32(wendu);
if (numericUpDown1.Value < a)
{
byte[] data = { 0x05 };
sc.SendData(data);
if (bo==false)
{
MessageBox.Show("温度高于阈值,风扇已开");
bo = true;
}
}
else
{
byte[] data = { 0x06 };
sc.SendData(data);
if (bo == true)
{
MessageBox.Show("温度低于阈值,风扇已关闭");
bo = false;
byte[] data1 = { 0x07 };
sc.SendData(data1);
}
}
}
}
}
表单部分
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.IO;
namespace Wisdomclassroom
{
public partial class DataSheet : Form
{
SQL sql;
public DataSheet()
{
this.StartPosition = FormStartPosition.CenterScreen;
InitializeComponent();
}
private void DataSheet_Load(object sender, EventArgs e)
{
sql = new SQL("11.111.11.11", "xxxxxx", "xxx", "xxxxx");
sql.Connect();
}
private void button1_Click(object sender, EventArgs e)
{
string connstr = @"Data Source=11.111.11.11;Initial Catalog=xxxxxx;Uid=xxx;Pwd=xxxxx";
saveFileDialog1.Filter = "文本文件|*.txt";
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
string path = saveFileDialog1.FileName;
using (SqlConnection conn = new SqlConnection(connstr))
{
string sql1 = "select xingming,ID,xueji from [login]";
using (SqlCommand cmd = new SqlCommand(sql1, conn))
{
conn.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
{
List<string> student = new List<string>();
while (dr.Read())
{
string num = dr["ID"].ToString();
string name = dr["xingming"].ToString();
string school = dr["xueji"].ToString();
student.Add( "姓名:"+name+ " "+"学号:"+num+" "+"学籍校:"+ school) ;
}
File.WriteAllLines(path, student.ToArray());
MessageBox.Show("导出成功");
}
}
}
}
}
private denglu anotherForm;
private void 退出ToolStripMenuItem_Click(object sender, EventArgs e)
{
anotherForm = new denglu();
this.Hide();
anotherForm.ShowDialog();
Application.ExitThread();
}
private zhuce anotherForm1;
private void 个人信息录入ToolStripMenuItem_Click(object sender, EventArgs e)
{
anotherForm1 = new zhuce();
this.Hide();
anotherForm1.ShowDialog();
Application.ExitThread();
}
private wangjimima anotherForm2;
private void 修改密码ToolStripMenuItem_Click(object sender, EventArgs e)
{
anotherForm2 = new wangjimima();
this.Hide();
anotherForm2.ShowDialog();
Application.ExitThread();
}
private void 清空数据库ToolStripMenuItem_Click(object sender, EventArgs e)
{
String str = sql.GetString("delete from [login]");
MessageBox.Show("数据库已清空");
}
}
}
总结:
由于本项目应用到了硬件,主要通过串口将上位机C#和下位机stm32进行通信,上位机通过发送协议数据给下位机,数据传输完成,下位机判断不同的数据,响应执行串口中断函数,实现不同的功能。而串口通信在传输协议里并不是最优的选择,所以我想在以后项目用到Wi-Fi或Zigbee协议等,来提升自己的水平。
技术问题:
硬件层面上:
项目初期,在实现单独人体红外测温模块,下位机能够正常响应,能够接收到正常温度的发送。
项目中期阶段,其他外设的加入,导致硬件引脚的冲突,例如下位机板载的灯环用到了PB6引脚及RFID模块的PB7引脚,在板载外设不能更改的情况下,先是将红外测温模块底层代码的数据线和时钟线引脚进行修改,但还是读不到正确的温度。
项目后期阶段,在保证核心功能RFID的实现下,权衡将红外模块去除,加入执行器风扇,通过和阈值温度的判断,实现自动开关,其也是较为创新的地方。
软件层面上:
C#核心问题:
在上位机C#在项目快结束时一直存在的问题,其下位机RFID,通过串口将电子标签的ID发送给上位机,上位机里的串口类,当接收到一个字节时,也会触发DataReceived事件,同时调用Invoke函数,将卡号显示的textbox里,其只有第一次刷的卡号才能显示,再刷传输来的数据就会溢出或不正确。
经过老师指导,在每次的刷卡之后接收到数据,可以用clear将上次传过来的数据清除,保证每次的数据帧的正确,但这个串口类调用不了clear,所以转换思路,通过设置缓存区的方式,将传过来的数据的帧头和结束符做一个判断,将其放入线程里,将其实现数据的传输的正确性。
问题解决思路的不同,也导致项目后期工作量不同、业务逻辑不同。后来在下位机中,将电子标签一靠近RFID,就会一直读改为读一次。最后也是通过简单的方法解决复杂的问题。
可加本人Q1429536866,欢迎大家指导讨论。