XM1601 是一款集成度高、性能优良的影像式二维扫描模块。
以下是他的引脚介绍
这里比较关键的是PIN10和PIN12引脚。PIN10用于判断二维码是否解码成功,PIN12由于使能XM1601模组开始进入识别状态。居然是通过串口通信,那么我们程序只要根据商家发的手册定义的数据报文协议来写就可以。
接下来我也不将废话,代码直接发出来copy。因为这个STM32F103C8T6我移植了RTThread None的操作系统。所以大伙copy的时候注意细改一点东西就可以,由于这个是我临时根据文档来写的所以肯定不会太全面,不过也能用,后面要补充的大伙根据自己情况来补充就可以。
#include "pin.h"
#include "stdlib.h"
#include "string.h"
//初始化
int pin_dev_init(pin_dev_t *dev, const char *port, pin_mode_t mode)
{
/*获取引脚号*/
dev->pin = atoi(&port[2]);
/*初始化RCC时钟*/
switch (port[1])
{
case 'A': RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); dev->gpio = GPIOA; dev->portsouce = GPIO_PortSourceGPIOA; break;
case 'B': RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); dev->gpio = GPIOB; dev->portsouce = GPIO_PortSourceGPIOB; break;
case 'C': RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); dev->gpio = GPIOC; dev->portsouce = GPIO_PortSourceGPIOC; break;
case 'D': RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); dev->gpio = GPIOD; dev->portsouce = GPIO_PortSourceGPIOD; break;
case 'E': RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); dev->gpio = GPIOE; dev->portsouce = GPIO_PortSourceGPIOE; break;
case 'F': RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF, ENABLE); dev->gpio = GPIOF; dev->portsouce = GPIO_PortSourceGPIOF; break;
case 'G': RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE); dev->gpio = GPIOG; dev->portsouce = GPIO_PortSourceGPIOG; break;
default: return -1;
}
/**
JTMS/SWDIO PA13
JTCK/SWCLK PA14
JTDI PA15
JTDO/TRACESWO PB3
JNTRST PB4
******************************/
/*关闭JTAG引脚*/
if (strcmp(port,"PB3") == 0 || \
strcmp(port,"PB4") == 0 || \
strcmp(port,"PA15") == 0)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
}
else if (strcmp(port,"PA13") == 0 || strcmp(port,"PA14") == 0)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);
}
/*初始化GPIO*/
dev->inittype.GPIO_Pin = 0x01 << dev->pin;
dev->inittype.GPIO_Mode = (GPIOMode_TypeDef)mode;
dev->inittype.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(dev->gpio, &dev->inittype);
return 0;
}
//输出模式
void pin_out_mode(pin_dev_t *dev, pin_mode_t mode)
{
dev->inittype.GPIO_Mode = (GPIOMode_TypeDef)mode;
GPIO_Init(dev->gpio, &dev->inittype);
}
//写
void pin_write(pin_dev_t dev, uint8_t n)
{
if (n)
{
pin_set(dev);
}
else
{
pin_reset(dev);
}
}
//配置中断
void pin_set_exit(pin_dev_t *dev, int8_t trigger, uint8_t pre, uint8_t sub)
{
int nvic;
NVIC_InitTypeDef exit_nvicinittype;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//初始化GPIO
dev->inittype.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(dev->gpio, &dev->inittype);
//配置GPIO源
GPIO_EXTILineConfig(dev->portsouce,dev->pin);
//配置中断
EXTI_InitTypeDef exit_inittype;
exit_inittype.EXTI_LineCmd = ENABLE;
exit_inittype.EXTI_Mode = EXTI_Mode_Interrupt;
exit_inittype.EXTI_Line = 0x01 << dev->pin;
//选择极性
if (trigger > 0)
{
exit_inittype.EXTI_Trigger = EXTI_Trigger_Rising;
}
else if (trigger < 0)
{
exit_inittype.EXTI_Trigger = EXTI_Trigger_Falling;
}
else
{
exit_inittype.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
}
EXTI_Init(&exit_inittype);
//配置NVIC
switch (dev->pin)
{
case 0: nvic = EXTI0_IRQn; break;
case 1: nvic = EXTI1_IRQn; break;
case 2: nvic = EXTI2_IRQn; break;
case 3: nvic = EXTI3_IRQn; break;
case 4: nvic = EXTI4_IRQn; break;
default:
if (dev->pin >= 5 && dev->pin < 10)
{
nvic = EXTI9_5_IRQn;
}
else
{
nvic = EXTI15_10_IRQn;
}
}
exit_nvicinittype.NVIC_IRQChannel = nvic;
exit_nvicinittype.NVIC_IRQChannelCmd = ENABLE;
exit_nvicinittype.NVIC_IRQChannelPreemptionPriority = pre;
exit_nvicinittype.NVIC_IRQChannelSubPriority = sub;
NVIC_Init(&exit_nvicinittype);
}
#ifndef __PIN_H__
#define __PIN_H__
#include "stm32f10x.h"
typedef enum
{
PIN_OUTPUT_PP = GPIO_Mode_Out_PP,
PIN_OUTPUT_OD = GPIO_Mode_Out_OD,
PIN_INPUT_U = GPIO_Mode_IPU,
PIN_INPUT_D = GPIO_Mode_IPD,
PIN_INPUT_IN = GPIO_Mode_IN_FLOATING,
} pin_mode_t;
typedef struct
{
GPIO_InitTypeDef inittype;
GPIO_TypeDef *gpio;
uint16_t pin;
uint8_t portsouce;
} pin_dev_t;
#define pin_set(dev) (dev.gpio->BSRR = (0x01 << dev.pin))
#define pin_reset(dev) (dev.gpio->BRR = (0x01 << dev.pin))
#define pin_read(dev) ((dev.gpio->IDR & (0x0001 << dev.pin)) != 0x00)
int pin_dev_init(pin_dev_t *dev, const char *port, pin_mode_t mode);
void pin_out_mode(pin_dev_t *dev, pin_mode_t mode);
void pin_set_exit(pin_dev_t *dev, int8_t trigger, uint8_t pre, uint8_t sub);
void pin_write(pin_dev_t dev, uint8_t n);
#endif /*__PIN_H__*/
#include "xm1601.h"
#include <rtthread.h>
#include <string.h>
#include "usart1.h"
#include "pin.h"
#define XM_WAITACK_MAX 400 //ms
#define xm_malloc rt_malloc
#define xm_free rt_free
#define xm_delay rt_thread_mdelay
#define xm_get_tick rt_tick_get
rt_mailbox_t qr_rx_mb = RT_NULL; //二维码数据接收邮箱
#define XM_RBUF_LEN 300 //接收缓冲区长度
uint8_t XM_RBUF[XM_RBUF_LEN];
int XM_RBUF_CNT = 0;
static uint8_t __qr_get_flag = 0; //二维码读取标志
pin_dev_t xm_beep; //无源蜂鸣器输出信号,空闲时为低电平
pin_dev_t xm_dled; //解码成功提示灯,空闲时为低电平
//当解码成功后,BEEP 与 DLED 引脚会给出一个高电平脉冲。
//注:BEEP 脉冲持续时长 60ms,可进行设置。
// DLED 脉冲持续时长 100ms。
pin_dev_t xm_tring; //扫码触发信号,低电平有效
//不触发扫码
void xm1601_stop(void)
{
pin_set(xm_tring);
}
//触发扫码
void xm1601_start(void)
{
pin_reset(xm_tring);
}
//初始化
int xm1601_init(void)
{
int ret = 0;
uint8_t param;
uart1_init(9600);
if (qr_rx_mb == RT_NULL)
qr_rx_mb = rt_mb_create("qrget",4,RT_IPC_FLAG_PRIO);
//初始化引脚
pin_dev_init(&xm_tring,"PA4", PIN_OUTPUT_PP);
pin_dev_init(&xm_dled, "PA5", PIN_INPUT_U);
pin_dev_init(&xm_beep, "PA6", PIN_INPUT_U);
//配置中断
pin_set_exit(&xm_dled,1,1,1);
pin_set_exit(&xm_beep,1,1,1);
xm1601_stop();
xm1601_restore();
//开启解码成功 LED 提示,补光灯-拍照时点亮,定位灯-拍照时点亮
ret |= xm1601_read_flagbit(0x0000,1,¶m);
param &= 0x3C;
param |= 0x94;
ret |= xm1601_write_flagbit(0x0000,1,¶m);
//边沿触发
ret |= xm1601_read_flagbit(0x0001,1,¶m);
param |= 0x20;
ret |= xm1601_write_flagbit(0x0001,1,¶m);
//允许图像镜像翻转
ret |= xm1601_read_flagbit(0x0009,1,¶m);
param |= 0x01;
ret |= xm1601_write_flagbit(0x0009,1,¶m);
//输出数据屏蔽中文开关不屏蔽
ret |= xm1601_read_flagbit(0x000A,1,¶m);
param |= 0x80;
ret |= xm1601_write_flagbit(0x000A,1,¶m);
//输出数据编码格式GBK,输出端口模式串口输出
ret |= xm1601_read_flagbit(0x000D,1,¶m);
param &= 0xF0;
ret |= xm1601_write_flagbit(0x000D,1,¶m);
//识读能力强化开关
ret |= xm1601_read_flagbit(0x0016,1,¶m);
param |= 0x01;
ret |= xm1601_write_flagbit(0x0016,1,¶m);
//允许识读所有条码
param = 0x02;
ret |= xm1601_write_flagbit(0x002C,1,¶m);
//QR模式1开,QR 码识读开关
ret |= xm1601_read_flagbit(0x003F,1,¶m);
param |= 0x21;
ret |= xm1601_write_flagbit(0x003F,1,¶m);
//串口/虚拟串口输出带协议,结束符为TAB(0x09)
ret |= xm1601_read_flagbit(0x0060,1,¶m);
param |= 0x80;
param &= ~0x60;
param |= 0x40;
ret |= xm1601_write_flagbit(0x0060,1,¶m);
xm_delay(100);
xm1601_clear();
return ret;
}
//写入字节
void xm1601_byte(uint8_t byte)
{
uart1_sendbyte(byte);
}
//计算CRC
//注:当用户不需要 CRC 校验功能时,可在 CRC 字节处填写 0xAB 0xCD,免校验。
uint16_t xm1601_crc_bybit(uint8_t *pdat, int len)
{
uint32_t crc = 0;
while (len-- != 0)
{
for (uint8_t i = 0x80; i != 0; i /=2 )
{
crc *= 2;
//上一位CRC*2后,如果首位为1,则除以0x11021
if ((crc&0x10000) != 0)
{
crc ^= 0x1021;
}
//如果本位是1,CRC=上一位的CRC+本位/CRC_CCITT
if (*pdat&i)
{
crc ^= 0x1021;
}
}
pdat++;
}
return (uint16_t)crc;
}
//xm601写
uint16_t xm1601_write(uint8_t type, uint8_t lens, uint16_t addr, uint8_t *pdat, int dat_len)
{
//命令格式: {Head1} {Types} {Lens} {Address} {Datas} {CRC}
uint32_t crc = 0;
int i;
//发送Head
xm1601_byte(0x7E);
xm1601_byte(0x00);
//发送Types
xm1601_byte(type);
//发送Lens
xm1601_byte(lens);
//发送Address
xm1601_byte((addr&0xFF00) >> 8);
xm1601_byte(addr&0xFF);
//发送Datas
for (i = 0; i < dat_len; i++)
{
xm1601_byte(pdat[i]);
}
/*
计算的范围:Types、Lens、Address、Datas 计算的方法为 CRC_CCITT,特征多项式:X16+X12+X5+1,即
多项式系数为 0x1021,初始值为全 0,对于单个字节来说最高位先计算,不需要取反直接输出。
*/
//计算CRC
uint8_t *crc_data = xm_malloc(4+dat_len);
crc_data[0] = type;
crc_data[1] = lens;
crc_data[2] = (addr&0xFF00)>>8;
crc_data[3] = addr&0xFF;
for (i = 0; i < dat_len; i++)
{
crc_data[4+i] = pdat[i];
}
crc = xm1601_crc_bybit(crc_data,4+dat_len);
//发送CRC
xm1601_byte((crc&0xFF00)>>8);
xm1601_byte(crc&0xFF);
xm_free(crc_data);
return crc;
}
//1.1 读标志位操作
int xm1601_read_flagbit(uint16_t addr/*表示要读取的标志位的起始地址。*/,
uint8_t rlen/*表示要连续读取的标志位的字节数,0x00 表示 256 个字节。*/,
uint8_t *fgdat)
{
int sta = 0;
int tick;
xm1601_clear();
//写入读标志位操作命令
xm1601_write(0x07, 0x01, addr, &rlen, 1);
//等待响应数据
memset(XM_RBUF,0,sizeof(XM_RBUF));
tick = xm_get_tick();
while (1)
{
if (xm_get_tick() - tick > XM_WAITACK_MAX)
{
sta = -1; //超时
break;
}
else if (XM_RBUF[0] == 0x02 && XM_RBUF[1] == 0x00 && XM_RBUF[2] == 0x00 && XM_RBUF[3] == rlen && XM_RBUF_CNT >= 6+XM_RBUF[3])
{
for (int i = 0; i < XM_RBUF[3]; i++)
{
fgdat[i] = XM_RBUF[4+i];
}
//读取成功
break;
}
xm_delay(1);
}
return sta;
}
//1.2 写标志位操作
int xm1601_write_flagbit(uint16_t addr/*表示要写入的标志位的起始地址*/,
uint8_t wlen /*表示要进行连续写操作的次数*/,
uint8_t *fgdat)
{
int sta = 0;
int tick;
xm1601_clear();
//写入写标志位操作命令
xm1601_write(0x08, wlen, addr, fgdat, wlen);
//等待响应数据
memset(XM_RBUF,0,sizeof(XM_RBUF));
tick = xm_get_tick();
while (1)
{
if (xm_get_tick() - tick > XM_WAITACK_MAX)
{
sta = -1; //超时
break;
}
else if ( XM_RBUF[0] == 0x02 && \
XM_RBUF[1] == 0x00 && \
XM_RBUF[2] == 0x00 && \
XM_RBUF[3] == 0x01 && \
XM_RBUF[4] == 0x00 && \
XM_RBUF[5] == 0x33 && \
XM_RBUF[6] == 0x31 )
{
//写入成功
break;
}
xm_delay(1);
}
return sta;
}
//1.3 标志位保存到内部 Flash 指令
int xm1601_save_flagbit(void)
{
int sta = 0;
int tick;
uint8_t data = 0x00;
xm1601_clear();
//写入标志位保存操作命令
xm1601_write(0x09, 1, 0x00, &data, 1);
//等待响应数据
tick = xm_get_tick();
while (1)
{
if (xm_get_tick() - tick > XM_WAITACK_MAX)
{
sta = -1; //超时
break;
}
else if ( XM_RBUF[0] == 0x02 && \
XM_RBUF[1] == 0x00 && \
XM_RBUF[2] == 0x00 && \
XM_RBUF[3] == 0x01 && \
XM_RBUF[4] == 0x00 && \
XM_RBUF[5] == 0x33 && \
XM_RBUF[6] == 0x31 )
{
//保存成功
break;
}
xm_delay(1);
}
return sta;
}
//1.4 标志位恢复到出厂设置
int xm1601_restore(void)
{
int sta = 0;
int tick;
uint8_t data = 0xFF;
xm1601_clear();
//写入标志位保存操作命令
xm1601_write(0x09, 1, 0x00, &data, 1);
//等待响应数据
tick = xm_get_tick();
while (1)
{
if (xm_get_tick() - tick > XM_WAITACK_MAX)
{
sta = -1; //超时
break;
}
else if ( XM_RBUF[0] == 0x02 && \
XM_RBUF[1] == 0x00 && \
XM_RBUF[2] == 0x00 && \
XM_RBUF[3] == 0x01 && \
XM_RBUF[4] == 0x00 && \
XM_RBUF[5] == 0x33 && \
XM_RBUF[6] == 0x31 )
{
//保存成功
break;
}
xm_delay(1);
}
return sta;
}
//清理
void xm1601_clear(void)
{
memset(XM_RBUF,0,sizeof(XM_RBUF));
XM_RBUF_CNT = 0;
}
//接收数据回调处理,需要放到串口中断里
void xm1601_irq_callback(uint8_t param)
{
if (__qr_get_flag == 0)
{
XM_RBUF[XM_RBUF_CNT++] = param;
}
else
{
XM_RBUF[XM_RBUF_CNT] = param;
if (XM_RBUF[XM_RBUF_CNT] == 0x09) //当接收到结尾段
{
__qr_get_flag = 0;
XM_RBUF_CNT = 0;
if (qr_rx_mb != RT_NULL)
{
//发送接收邮箱
rt_mb_send(qr_rx_mb,(rt_ubase_t)XM_RBUF);
}
//关闭识别
xm1601_stop();
}
else
{
XM_RBUF_CNT++;
}
}
}
void EXTI9_5_IRQHandler(void)
{
//LED
if (EXTI_GetITStatus(EXTI_Line5) == SET)
{
__qr_get_flag = 1; //解码成功,开始读取
EXTI_ClearITPendingBit(EXTI_Line5);
}
//BEEP
if (EXTI_GetITStatus(EXTI_Line6) == SET)
{
EXTI_ClearITPendingBit(EXTI_Line6);
}
}
void USART1_IRQHandler(void)
{
uint8_t ret;
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
ret = USART_ReceiveData(USART1);
xm1601_irq_callback(ret);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
#ifndef __XM1601_H__
#define __XM1601_H__
#include "stm32f10x.h"
#include "rtthread.h"
int xm1601_init(void);
void xm1601_clear(void);
void xm1601_start(void);
void xm1601_stop(void);
void xm1601_irq_callback(uint8_t param);
//1.1 读标志位操作
int xm1601_read_flagbit(uint16_t addr/*表示要读取的标志位的起始地址。*/,
uint8_t rlen/*表示要连续读取的标志位的字节数,0x00 表示 256 个字节。*/,
uint8_t *fgdat);
//1.2 写标志位操作
int xm1601_write_flagbit(uint16_t addr/*表示要写入的标志位的起始地址*/,
uint8_t wlen /*表示要进行连续写操作的次数*/,
uint8_t *fgdat);
//1.3 标志位保存到内部 Flash 指令
int xm1601_save_flagbit(void);
//1.4 标志位恢复到出厂设置
int xm1601_restore(void);
extern rt_mailbox_t qr_rx_mb;
#endif /*__XM1601_H__*/
串口代码我就不必放了,只要不是傻子都知道怎么去搞,这个代码使用的方法就是首先调用xm1601_start()启动xm1601模组进入识别状态,然后模组识别成功会返回二维码数据,我这里在xm1601.c的void USART1_IRQHandler(void)里是使用了rtthread的邮箱来将数据发出去的,如果是裸机你们可以改成标志位。不要忘记调用xm1601_init();初始化寄存器。下面这个是接收数据的任务线程实例。
手册的话一般可以直接找淘宝商家要就行。