本帖最后由 yangjiaxu 于 2022-3-31 14:10 编辑
首先感谢21ic和小管家举办的这个[活动] 【第1期】你选开发板,二姨家买单;很荣幸因为脸熟(其实是混子)得到了这次活动的中奖名额,说实话这板子到手挺长时间了,这么晚才发属实是我个人原因,还有就是疫情肆虐,真的让我板子与人相隔两地。哈哈哈,好了闲话少说,还是开启这次主题任务吧。本次豆包选择的开发板是沁恒的开发板“CH32V103R8”为什么选择这款开发板呢,其实原因有三。第一,因为没怎么接触过沁恒RISC-V内核的开发板,工作中一直都在使用Arm内核的单片机在玩耍,可能接触和体验新鲜事物是做工程师的一种本能吧,所以让我选择了沁恒。第二,因为朋友推荐我玩沁恒的MCU是因为沁恒的MCU较容易好上手,而且这个32位单片机在国产32位的MCU中是比较能打的(主要是有性价比)。第三,主要是我缺了个link调试器,我是一个对调试工具比较有感觉的一个野生工程师,手里的调试器也算是多种多样,所以一看沁恒这个买开发板送调试器的活动,我岂有不buy的道理。因此,众多理由让我选择了沁恒这款开发板。到手之后,果然没让我失望。接下来就来赏析这款开发板的样子吧。 开发板包装
开发板和调试器
其实这次购买还赠送了2片MCU,在这个芯片紧缺,在朋友圈盛行的一句 “芯片和白菜一个价的时代”,送我两片芯片无疑是地主行为。
接下来就是搭环境了,其实Risc-V的环境怎么搭我是真不是很清楚,但是沁恒这个CH32V103这个搭建环境是真的很容易,首先下载个开发环境,下载MounRiver_Studio_Setup_V170.zip即可, 下载链接。接下来就是安装,然后找沁恒关于CH32V103的资料例程即可, 资料链接。通过官网也可以看到CH32V103的开发资源和外设情况。
产品特点 - 青稞V3A处理器,最高80MHz系统主频;
- 支持单周期乘法和硬件除法;
- 20KB SRAM,64KB CodeFlash;
- 供电范围:2.7V ~ 5.5V,GPIO同步供电电压;
- 多种低功耗模式:睡眠/停止/待机;
- 上电/断电复位(POR/PDR);
- 可编程电压监测器(PVD);
- 7通道DMA控制器;
- 16路TouchKey通道监测;
- 16路12位ADC转换通道;
- 7个定时器;
- 1个USB2.0主机/设备接口(全速和低速);
- 2个IIC接口(支持SMBus/PMBus);
- 3个USART接口;
- 2个SPI接口(支持Master和Slave模式);
- 51个I/O口,所有的I/O口都可以映射到16个外部中断;
- CRC计算单元,96位芯片唯一ID;
- 串行单线调试(SWD)接口;
- 封装形式:LQFP64M、LQFP48、QFN48。
介绍完硬件,其实就可以写代码来玩玩这款得意的MCU了。 打开一个官方例程,我选择的是GPIO的例程,然后改了个名字,因为我想用这个MCU驱动一下FM17522射频阅读器,所以索性改了工程名字。先看看代码风格之类的,emmm,真香,这不是很熟悉的库函数形式么。
接下来添加FM17522的驱动代码,然后就是移植程序了。 以前换一款MCU来说,移植程序是非常费劲儿的,不过使用沁恒这款MCU的话就不用担心这个问题,基本上一看就会,一用就OK,所以我移植这个工程代码也是非常容易的。因为还需要修改一些变量和参数之类的。所以,大概用了20分钟吧。 分享一下基础代码: /**********************************以下为用户程序************************************/
void FM17522_IOinit(void)
{
u8 loop = 0;
GPIO_InitTypeDef GPIO_InitStructure;
// GPIO_InitTypeDef GPIO_InitStructure = {0};
//不要忘记开启AFIO时钟(JTAG复用时RCC_APB2Periph_AFIO)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
FM17522_Reset();
no_fm_flag = 1;
while(loop++ < 6)
{
if(FM17522_TEST()) // 测试读卡芯片FM17522是否在位
{
no_fm_flag = 0;
break;
}
}
}
/*********************************************************************************************************
** 函数名称:FM17522_TEST
** 函数描述:MCU与读卡芯片通信测试函数
** 输入参数:无
** 返回值 :TRUE---测试通过 FALSE----测试失败
** 说明:不同的读卡芯片的选取用于测试的寄存器不一样,需要更具具体芯片而确定目标寄存器
*********************************************************************************************************/
u8 FM17522_TEST(void)
{
volatile u8 ucRegVal;
FM17522_Reset(); // 硬件复位
ucRegVal = FM17522_Read_Reg(ControlReg);
FM17522_Write_Reg(ControlReg, 0x10); // 启动读写器模式
ucRegVal = FM17522_Read_Reg(ControlReg);
FM17522_Write_Reg(GsNReg, 0xF0 | 0x04); // CWGsN = 0xF; ModGsN = 0x4
ucRegVal = FM17522_Read_Reg(GsNReg);
if(ucRegVal != 0xF4) // 验证接口正确
return FALSE;
return TRUE;
}
void FM17522_Reset(void)
{
SPI_SCK_SET;
Delay_Us(300); // 延时300us
SPI_RST_CLR;
Delay_Us(500); // 延时500us
SPI_RST_SET;
Delay_Us(1000); // 延时1000us
}
/********************************************
函数名: Clear_BitMask
功能: 清除位寄存器操作
输入参数: reg_add,寄存器地址;mask,寄存器清除位
********************************************/
void Clear_BitMask(u8 reg_add, u8 mask)
{
FM17522_Write_Reg(reg_add, FM17522_Read_Reg(reg_add) & ~mask); // clear bit mask
}
/*************************************************************/
/*函数名: Set_BitMask */
/*功能: 置位寄存器操作 */
/*输入参数: reg_add,寄存器地址;mask,寄存器置位 */
/*返回值: TRUE */
/* FALSE */
/*************************************************************/
void Set_BitMask(u8 reg_add, u8 mask)
{
FM17522_Write_Reg(reg_add, FM17522_Read_Reg(reg_add) | mask); // set bit mask
}
/**********************************************/
/** 函数名: Set_RF **/
/** 功能: 设置射频输出 **/
/** 输入参数: mode,射频输出模式 **/
/** 0,关闭输出 **/
/** 1,仅打开TX1输出 **/
/** 2,仅打开TX2输出 **/
/** 3,TX1,TX2打开输出,TX2为反向输出 **/
/**********************************************/
void Set_Rf(u8 mode)
{
if((FM17522_Read_Reg(TxControlReg) & 0x03) == mode)
return;
if (mode == 0)
{
Clear_BitMask(TxControlReg, 0x03); //关闭TX1,TX2输出
}
else if (mode == 1)
{
Clear_BitMask(TxControlReg, 0x01); //仅打开TX1输出
}
else if (mode == 2)
{
Clear_BitMask(TxControlReg, 0x02); //仅打开TX2输出
}
else if (mode == 3)
{
Set_BitMask(TxControlReg, 0x03); //打开TX1,TX2输出
}
Delay_Ms(100);
}
void Fm17522_ConfigISOType(u8 type)
{
if (type == 0) //ISO14443_A
{
Set_BitMask(ControlReg, 0x10); //ControlReg 0x0C 设置reader模式
Set_BitMask(TxAutoReg, 0x40); //TxASKReg 0x15 设置100%ASK有效
FM17522_Write_Reg(TxModeReg, 0x00); //TxModeReg 0x12 设置TX CRC无效,TX FRAMING =TYPE A
FM17522_Write_Reg(RxModeReg, 0x00); //RxModeReg 0x13 设置RX CRC无效,RX FRAMING =TYPE A
FM17522_Write_Reg(RFCfgReg, 0x48); //增益调节至48DB 2021.01.16
}
else if (type == 1) //ISO14443_B
{
FM17522_Write_Reg(ControlReg, 0x10);
FM17522_Write_Reg(TxModeReg, 0x83); //BIT1~0 = 2'b11:ISO/IEC 14443B
FM17522_Write_Reg(RxModeReg, 0x83); //BIT1~0 = 2'b11:ISO/IEC 14443B
FM17522_Write_Reg(TxAutoReg, 0x00);
FM17522_Write_Reg(RxThresholdReg, 0x55);
FM17522_Write_Reg(RFCfgReg, 0x48); //?????????????
FM17522_Write_Reg(TxBitPhaseReg, 0x87); //默认值
FM17522_Write_Reg(GsNReg, 0x83);
FM17522_Write_Reg(CWGsPReg, 0x10);
FM17522_Write_Reg(GsNOffReg, 0x38);
FM17522_Write_Reg(ModGsPReg, 0x10);
}
Delay_Us(1000);
}
/********************************************
函数名: Fm17522_SetTimer
功能: 设置接收延时
输入参数: delaytime,延时时间(单位为毫秒)
返回值: TRUE
********************************************/
u8 Fm17522_SetTimer(unsigned long delaytime)//设定超时时间(ms)
{
unsigned long TimeReload;
unsigned int Prescaler;
Prescaler = 0;
TimeReload = 0;
while(Prescaler < 0xfff)
{
TimeReload = ((delaytime * (long)13560) - 1) / (Prescaler * 2 + 1);
if( TimeReload < 0xffff)
break;
Prescaler++;
}
TimeReload = TimeReload & 0xFFFF;
Set_BitMask(TModeReg, Prescaler >> 8);
FM17522_Write_Reg(TPrescalerReg, Prescaler & 0xFF);
FM17522_Write_Reg(TReloadMSBReg, TimeReload >> 8);
FM17522_Write_Reg(TReloadLSBReg, TimeReload & 0xFF);
return TRUE;
}
/*************************************************************
函数名: Clear_FIFO
功能: 清空FIFO
输入参数: 无
输出参数:
返回值: TRUE
FALSE
************************************************************/
u8 Clear_FIFO(void)
{
Set_BitMask(FIFOLevelReg, 0x80); //清除FIFO缓冲
if (FM17522_Read_Reg(FIFOLevelReg) == 0)
return TRUE;
else
return FALSE;
}
//ATQA是只有两个字节,UID却是4个字节加一个BCC校验值所以针对7字节UID防碰撞时就需要最多15字节的UID来存放
//SAK是一个字节的SAK+CRC_A(2字节)
/****************************************************************************************/
/*名称:TypeA_Request */
/*功能:TypeA_Request卡片寻卡 */
/*输入: */
/* */
/* */
/*输出: */
/* pTagType[0],pTagType[1] =ATQA */
/* TRUE: 应答正确 */
/* FALSE: 应答错误 */
/****************************************************************************************/
u8 Fm17522_TypeA_Request(void)
{
unsigned char result, send_buff[1], rece_buff[2];
unsigned int rece_bitlen;
Clear_BitMask(TxModeReg, 0x80); //关闭TX CRC
Clear_BitMask(RxModeReg, 0x80); //关闭RX CRC
Set_BitMask(RxModeReg, 0x08);//关闭位接收
Clear_BitMask(Status2Reg, 0x08);
FM17522_Write_Reg(BitFramingReg, 0x07);
send_buff[0] = 0x26;
Fm17522_SetTimer(1);
Clear_FIFO();
result = Fm17522_Comm(Transceive, send_buff, 1, rece_buff, &rece_bitlen);
if ((result == TRUE) && (rece_bitlen == 2 * 8))
{
memcpy(atqa, rece_buff, 2);
}
return result;
}
/*********************************************
函数名: Fm17522_Comm (不利用IRQ管脚的情况)
功能: 读卡器通信
输入参数: Command,通信操作命令;
pInData,发送数据数组;
InLenByte,发送数据数组字节长度;
pOutData,接收数据数组;
pOutLenBit,接收数据的位长度
返回值: TRUE
FALSE
********************************************/
u8 Fm17522_Comm(u8 Command, u8 *pInData, u8 InLenByte, u8 *pOutData, uint *pOutLenBit)
{
uint8_t status = FALSE;
uint8_t irqEn = 0x00;
uint8_t waitFor = 0x00;
uint8_t lastBits;
uint8_t n;
uint32_t i;
FM17522_Write_Reg(ComIrqReg, 0x7F); //清楚IRQ标记
FM17522_Write_Reg(TModeReg, 0x80); //设置TIMER自动启动
switch (Command)
{
case MFAuthent: /* Mifare认证 */
irqEn = 0x12;
waitFor = 0x10;
break;
case Transceive: /* 发送FIFO中的数据到天线,传输后激活接收电路*/
irqEn = 0x77;
waitFor = 0x30;
break;
default:
break;
}
FM17522_Write_Reg(ComIEnReg, irqEn | 0x80);
FM17522_Write_Reg(CommandReg, Idle);
Set_BitMask(FIFOLevelReg, 0x80);
for (i = 0; i < InLenByte; i++)
{
FM17522_Write_Reg(FIFODataReg, pInData[i]);
}
FM17522_Write_Reg(CommandReg, Command);
if (Command == Transceive)
{
Set_BitMask(BitFramingReg, 0x80);
}
i = 20000; /* 根据时钟频率调整,操作M1卡最大等待时间25ms*/
do
{
n = FM17522_Read_Reg(ComIrqReg);
i--;
}
while ((i != 0) && !(n & 0x01) && !(n & waitFor)); //i==0表示延时到了,n&0x01!=1表示PCDsettimer时间未到
Clear_BitMask(BitFramingReg, 0x80);
if (i != 0)
{
if(!(FM17522_Read_Reg(ErrorReg) & 0x1B))
{
status = TRUE;
if (n & irqEn & 0x01)
{
status = 0xEE;
}
if (Command == Transceive)
{
n = FM17522_Read_Reg(FIFOLevelReg);
lastBits = FM17522_Read_Reg(ControlReg) & 0x07;
if (lastBits)
{
*pOutLenBit = (n - 1) * 8 + lastBits;
}
else
{
*pOutLenBit = n * 8;
}
if (n == 0)
{
n = 1;
}
if (n > 64)
{
n = 64;
}
for (i = 0; i < n; i++)
{
pOutData[i] = FM17522_Read_Reg(FIFODataReg);
}
}
}
else
{
status = FALSE;
}
}
Clear_BitMask(BitFramingReg, 0x80); //关闭发送
return status;
}
/****************************************************************************************/
/*名称:Fm17522_TypeA_Anticollision */
/*功能:卡片防冲突 */
/*输入:selcode =0x93,0x95,0x97 */
/* */
/* */
/*输出: */
/* TRUE: 应答正确 */
/* FALSE: 应答错误 */
/****************************************************************************************/
u8 Fm17522_TypeA_Anticollision(unsigned char selcode)
{
unsigned char result, i, send_buff[2], rece_buff[5];
unsigned int rece_bitlen;
Clear_BitMask(TxModeReg, 0x80);
Clear_BitMask(RxModeReg, 0x80);
Clear_BitMask(Status2Reg, 0x08);
FM17522_Write_Reg(BitFramingReg, 0x00);
Clear_BitMask(CollReg, 0x80);
send_buff[0] = selcode;
send_buff[1] = 0x20; //NVB
Fm17522_SetTimer(1);
Clear_FIFO();
result = Fm17522_Comm(Transceive, send_buff, 2, rece_buff, &rece_bitlen);
i = ((selcode - 0x93) / 2) * 5;
if (result == TRUE)
{
// memcpy(uid[i], rece_buff, 5);
for (i = 0; i < 5; ++i) {
uid[i] = rece_buff[i];
}
if (uid[4 + i] != (uid[i]^uid[1 + i]^uid[2 + i]^uid[3 + i])) //UID0、UID1、UID2、UID3、BCC(校验字节)
result = FALSE;
}
return result;
}
/****************************************************************************************/
/*名称:TypeA_Select */
/*功能:TypeA_Select卡片选卡 */
/*输入:selcode =0x93,0x95,0x97 */
/* pSnr[0],pSnr[1],pSnr[2],pSnr[3]pSnr[4] =UID */
/* */
/*输出: */
/* pSak[0],pSak[1],pSak[2] =SAK */
/* TRUE: 应答正确 */
/* FALSE: 应答错误 */
/****************************************************************************************/
u8 Fm17522_TypeA_Select(unsigned char selcode)
{
unsigned char result, i, send_buff[7], rece_buff[5];
unsigned int rece_bitlen;
FM17522_Write_Reg(BitFramingReg, 0x00);
Set_BitMask(TxModeReg, 0x80); //打开TX CRC
Set_BitMask(RxModeReg, 0x80); //打开接收RX 的CRC校验
Clear_BitMask(Status2Reg, 0x08);
send_buff[0] = selcode; //SEL
send_buff[1] = 0x70; //NVB
i = ((selcode - 0x93) / 2) * 5;
// memcpy(&send_buff[2],uid[i], 5);
for (i = 0; i < 5; ++i) {
send_buff[i + 2] = uid[i];
}
Fm17522_SetTimer(1);
Clear_FIFO();
result = Fm17522_Comm(Transceive, send_buff, 7, rece_buff, &rece_bitlen);
i = (selcode - 0x93) / 2;
if (result == TRUE)
{
sak[i] = rece_buff[0];
}
return result;
}
/****************************************************************************************/
/*名称:Fm17522_TypeA_CardActive */
/*功能:TypeA_CardActive卡片激活 */
/*输入: */
/*输出: pTagType[0],pTagType[1] =ATQA */
/* pSnr[0],pSnr[1],pSnr[2],pSnr[3]pSnr[4] =UID */
/* pSak[0],pSak[1],pSak[2] =SAK */
/****************************************************************************************/
u8 Fm17522_TypeA_CardActive(void)
{
u8 result, i;
Set_Rf(3); //turn on radio
Fm17522_ConfigISOType(0);
for(i = 0; i < 2; i++)
{
result = Fm17522_TypeA_Request(); //寻卡 Standard send request command Standard mode
if(result == 1) break;
}
if(i == 2)
return FALSE;
if((atqa[0] & 0xC0) == 0x00) //M1卡,ID号只有4位
{
result = Fm17522_TypeA_Anticollision(0x93);
if (result == FALSE)
{
return FALSE;
}
result = Fm17522_TypeA_Select(0x93);
if (result == FALSE)
{
return FALSE;
}
}
else if((atqa[0] & 0xC0) == 0x40) //ID号有7位
{
result = Fm17522_TypeA_Anticollision(0x93);
if (result == FALSE)
{
return FALSE;
}
result = Fm17522_TypeA_Select(0x93);
if (result == FALSE)
{
return FALSE;
}
result = Fm17522_TypeA_Anticollision(0x95);
if (result == FALSE)
{
return FALSE;
}
result = Fm17522_TypeA_Select(0x95);
if (result == FALSE)
{
return FALSE;
}
}
else if((atqa[0] & 0xC0) == 0x80) //ID号有10位
{
result = Fm17522_TypeA_Anticollision(0x93);
if (result == FALSE)
{
return FALSE;
}
result = Fm17522_TypeA_Select(0x93);
if (result == FALSE)
{
return FALSE;
}
result = Fm17522_TypeA_Anticollision(0x95);
if (result == FALSE)
{
return FALSE;
}
result = Fm17522_TypeA_Select(0x95);
if (result == FALSE)
{
return FALSE;
}
result = Fm17522_TypeA_Anticollision(0x97);
if (result == FALSE)
{
return FALSE;
}
result = Fm17522_TypeA_Select(0x97);
if (result == FALSE)
{
return FALSE;
}
}
return result;
}
/*****************************************************************************************/
/*名称:Fm17522_Mifare_Auth */
/*功能:Mifare_Auth卡片认证 */
/*输入:mode,认证模式(0:key A认证,1:key B认证);sector,认证的扇区号(0~15) */
/* *mifare_key,6字节认证密钥数组;*card_uid,4字节卡片UID数组 */
/*输出: */
/* TRUE :认证成功 */
/* FALSE :认证失败 */
/*****************************************************************************************/
u8 Fm17522_Mifare_Auth(u8 mode, u8 sector, u8 *mifare_key, u8 *card_uid)
{
unsigned char send_buff[12], rece_buff[1], result;
unsigned int rece_bitlen;
if(mode == 0x0)
send_buff[0] = 0x60; //keyA认证
if(mode == 0x1)
send_buff[0] = 0x61; //keyB认证
send_buff[1] = sector * 4 + 3;
send_buff[2] = mifare_key[0];
send_buff[3] = mifare_key[1];
send_buff[4] = mifare_key[2];
send_buff[5] = mifare_key[3];
send_buff[6] = mifare_key[4];
send_buff[7] = mifare_key[5];
send_buff[8] = card_uid[0];
send_buff[9] = card_uid[1];
send_buff[10] = card_uid[2];
send_buff[11] = card_uid[3];
Fm17522_SetTimer(1);
Clear_FIFO();
result = Fm17522_Comm(MFAuthent, send_buff, 12, rece_buff, &rece_bitlen); //Authent认证
if (result == TRUE)
{
if(FM17522_Read_Reg(Status2Reg) & 0x08) //判断加密标志位,确认认证结果
return TRUE;
else
return FALSE;
}
return FALSE;
}
其实用这个软件也就大概用这几个地方,编译,调试和下载,调试的话用官方例程的话几乎不需要修改,所以非常容易就可以上手。 在写代码的时候用memcpy会出现问题,有一些警告,这个可以通过添加#include <string.h>来解决。还有个问题就是我用这个语句赋值的时候,会有警告。memcpy(uid,rece_buff, 5);,警告内容为:warning: passing argument 1 of'memcpy' makes pointer from integer without a cast [-Wint-conversion]。也不知道为什么,最后换了一种方式,采用for循环的方式赋值解决了。
最后分享一下效果吧,小密说如果LED是全亮或者全灭这样效果更加明显,可是我懒了,哈哈哈,就没改。 最后感谢小管家这个给力的活动,还让我学会了新技能,奈斯。 |