热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

LinuxSPI应用编程

LinuxSPI应用编程设备文件devspidevx.yx是SPI总线号,即一组SCLK、MOSI、MISOy是SPI设备号,同一条总线上用不同的片
Linux SPI 应用编程

设备文件

/dev/spidevx.y
x是SPI总线号,即一组SCLK、MOSI、MISO
y是SPI设备号,同一条总线上用不同的片选信号区分:CE0、CE1等
对于树莓派,启用SPI功能后,有一条总线,两个设备:
/dev/spidev0.0
/dev/spidev0.1
树莓派上还可以通过dtoverlay使能第二条SPI总线,支持三个设备:
在config.txt中加入
dtoverlay=spi1-3cs
树莓派SPI相关PIN定义参考https://pinout.xyz/pinout/spi#

用户空间设备操作ioctl

相关头文件

#include #include #include #include #include

SPI设置:

  1. 时钟电平(CPOL)和采用阶段(CPHA)
  2. 时钟频率
  3. 片选电平
  4. 是否有片选信号
  5. 数据位数(通常为8bit)
  6. 数据位传输顺序(MSB或LSB)
  7. 3线或4线

功能cmdarg参数说明
读SPI工作模式SPI_IOC_RD_MODEu8*包括1,3,4,6,7
读SPI工作模式SPI_IOC_RD_MODE32u32*包括1,3,4,6,7
设置SPI工作模式SPI_IOC_WR_MODEu8*包括1,3,4,6,7
设置SPI工作模式SPI_IOC_WR_MODE32u32*包括1,3,4,6,7
读是否LSBSPI_IOC_RD_LSB_FIRSTu8*返回0或1
设置是否LSBSPI_IOC_WR_LSB_FIRSTu8*0或1
读字位数SPI_IOC_RD_BITS_PER_WORDu8*
设置字位数SPI_IOC_WR_BITS_PER_WORDu8*
读时钟频率SPI_IOC_RD_MAX_SPEED_HZu32*
设置时钟频率SPI_IOC_WR_MAX_SPEED_HZu32*

如果CPOL=0,串行同步时钟的空闲状态为低电平;
如果CPOL=1,串行同步时钟的空闲状态为高电平;
时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。
如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;
如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样;

工作模式由位组合而成:
SPI_CPHA
SPI_CPOL
SPI_CS_HIGH
SPI_LSB_FIRST
SPI_3WIRE
SPI_NO_CS
更多内容参考spidev.h

数据传输

可以调用read/write进行读写,也可以使用ioctl同时写并且读。
无论使用哪种方式,一次读写的最大字节数不能超过spi底层bufsiz,bufsiz是SPI驱动内核模块参数(TODO 如何修改此参数???)
使用ioctl方式可以一次读写多块数据,多块数据通过spi_ioc_transfer结构数组传递

ioctl参数说明:

  • cmd SPI_IOC_MESSAGE(N) 其中N是spi_ioc_transfer结构数组元素个数
  • arg 是spi_ioc_transfer结构数组地址

注意:每个数据发送和接收的数据长度是相同的,可以通过将tx_buf置NULL只读或将rx_buf置NULL只写
delay_usecs,speed_hz,bits_per_word等参数可以设置为0,使用全局或默认设置

由于有发送长度和接收长度一致的限制,并且通常先发送命令再接收应答而不会同时进行,所以一般使用read/write就可以了。确实需要同时收发的可以使用两个spi_ioc_transfer结构,一发一收。

应用举例

以树莓派操作PN532 NFC模块为例:
PN532采用微雪的PN532 NFC HAT模块,这个模块有个问题是40PIN的PI接口里片选信号没有接到PI的CE0或CE1,而是接到了GPIO4,所以,我们使用模块上的树莓派插针通过杜邦线与树莓派进行连接,将D4(GPIO4)接到树莓派的CE0,其他插针依次对应连接即可。
注意:电源需要使用模块上的树莓派5V插针连接,如需使用3.3V,可以将电源连接到模块控制接口的3.3V和GND。

使用HAT方式连接:
模块在SPI通信模式下可以使用控制接口的RX作为片选输入,将模块上的控制接口RX与模块上的树莓派插针CE0或CE1短接,将拨码开关的NSS设置为OFF,断开GPIO4与PN532的连接,这样GPIO4还可以做其他用途。

从树莓派的PIN脚定义上看,树莓派并没有接收IRQ,也没有发送H_REQ

PN532的通信参数:
PN532 SPI接口* The PN532 is configured as slave and is able to communicate with a host controller with a clock (SCK) up to 5MHz.

以下程序读取NFC tag的UID:

使用libnfc

// To compile this simple example:
// $ gcc -o quick_start_example1 quick_start_example1.c -lnfc
#include
#include
static void
print_hex(const uint8_t *pbtData, const size_t szBytes)
{size_t szPos;for (szPos = 0; szPos }
int
main(int argc, const char *argv[])
{nfc_device *pnd;nfc_target nt;// Allocate only a pointer to nfc_contextnfc_context *context;// Initialize libnfc and set the nfc_contextnfc_init(&context);if (context &#61;&#61; NULL) {printf("Unable to init libnfc (malloc)\n");exit(EXIT_FAILURE);}// Display libnfc versionconst char *acLibnfcVersion &#61; nfc_version();(void)argc;printf("%s uses libnfc %s\n", argv[0], acLibnfcVersion);// Open, using the first available NFC device which can be in order of selection:// - default device specified using environment variable or// - first specified device in libnfc.conf (/etc/nfc) or// - first specified device in device-configuration directory (/etc/nfc/devices.d) or// - first auto-detected (if feature is not disabled in libnfc.conf) devicepnd &#61; nfc_open(context, NULL);if (pnd &#61;&#61; NULL) {printf("ERROR: %s\n", "Unable to open NFC device.");exit(EXIT_FAILURE);}// Set opened NFC device to initiator modeif (nfc_initiator_init(pnd) <0) {nfc_perror(pnd, "nfc_initiator_init");exit(EXIT_FAILURE);}printf("NFC reader: %s opened\n", nfc_device_get_name(pnd));// Poll for a ISO14443A (MIFARE) tagconst nfc_modulation nmMifare &#61; {.nmt &#61; NMT_ISO14443A,.nbr &#61; NBR_106,};if (nfc_initiator_select_passive_target(pnd, nmMifare, NULL, 0, &nt) > 0) {printf("The following (NFC) ISO14443A tag was found:\n");printf(" ATQA (SENS_RES): ");print_hex(nt.nti.nai.abtAtqa, 2);printf(" UID (NFCID%c): ", (nt.nti.nai.abtUid[0] &#61;&#61; 0x08 ? &#39;3&#39; : &#39;1&#39;));print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen);printf(" SAK (SEL_RES): ");print_hex(&nt.nti.nai.btSak, 1);if (nt.nti.nai.szAtsLen) {printf(" ATS (ATR): ");print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen);}}// Close NFC devicenfc_close(pnd);// Release the contextnfc_exit(context);exit(EXIT_SUCCESS);
}

使用ioctl/read/write
pn532.h

#pragma once#include namespace pn532
{const uint8_t TFI_H2P &#61; 0xD4;
const uint8_t TFI_P2H &#61; 0xD5;#pragma pack(1)
struct ni_frame_header
{uint8_t preamble;uint8_t start_code[2];uint8_t len;uint8_t lcs;uint8_t tfi;
};struct frame_tailer
{uint8_t dcs;uint8_t postamble;
};
#pragma pack()const uint8_t ACK_FRAME[6] &#61; {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00};
const uint8_t NACK_FRAME[6] &#61; {0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00};
const uint8_t ERROR_FRAME[8] &#61; {0x00, 0x00, 0xFF, 0x01, 0xFF, 0x7F, 0x81, 0x00};typedef std::shared_ptr byte_ptr;
typedef byte_ptr data_ptr;
typedef byte_ptr frame_ptr;uint8_t checksum(const uint8_t * data, int data_len);
uint8_t checksum(data_ptr data, int data_len);
bool validate_checksum(const uint8_t * data, int data_len);
bool validate_checksum(data_ptr data, int data_len);
int cal_frame_len(int data_len);
frame_ptr new_frame(int frame_len);
int make_frame(uint8_t* buf, int buf_len, uint8_t tfi, const uint8_t* data, int data_len);
int make_frame(frame_ptr& frame, uint8_t tfi, data_ptr data, int data_len);const uint8_t SPI_DW &#61; 0x01;
const uint8_t SPI_DR &#61; 0x03;
const uint8_t SPI_SR &#61; 0x02;int cal_spi_frame_len(int data_len);
int make_spi_frame(uint8_t* buf, int buflen, uint8_t spi_tfi, uint8_t tfi, const uint8_t* data, int data_len);
int make_spi_frame(frame_ptr& frame, uint8_t spi_tfi, uint8_t tfi, const uint8_t* data, int data_len);
int make_spi_frame(frame_ptr& frame, uint8_t spi_tfi, uint8_t tfi, data_ptr data, int data_len);}

pn532.cpp

#include "pn532.h"
#include namespace pn532
{uint8_t checksum(const uint8_t * data, int data_len)
{assert(data_len >&#61; 0);uint8_t cs &#61; 0;const uint8_t* p &#61; data;for (int i &#61; 0; i }uint8_t checksum(data_ptr data, int data_len)
{return checksum(data.get(), data_len);
}bool validate_checksum(const uint8_t* data, int data_len)
{assert(data_len >&#61; 0);uint8_t cs &#61; 0;const uint8_t* p &#61; data;for (int i &#61; 0; i }bool validate_checksum(data_ptr data, int data_len)
{return validate_checksum(data.get(), data_len);
}int cal_frame_len(int data_len)
{assert(data_len >&#61; 0);return sizeof(ni_frame_header) &#43; data_len &#43; sizeof(frame_tailer);
}frame_ptr new_frame(int frame_len)
{assert(frame_len >&#61; 0);frame_ptr frame(new uint8_t[frame_len]);return frame;
}int make_frame(uint8_t* buf, int buf_len, uint8_t tfi, const uint8_t* data, int data_len)
{assert(data_len >&#61; 0);int frame_len &#61; cal_frame_len(data_len);if (buf &#61;&#61; NULL || buf_len &#61;&#61; 0){return frame_len;}if (buf_len preamble &#61; 0x00;h->start_code[0] &#61; 0x00;h->start_code[1] &#61; 0xFF;h->len &#61; data_len &#43; 1;h->lcs &#61; checksum(&h->len, 1);h->tfi &#61; tfi;uint8_t dcs &#61; tfi;const uint8_t* dd &#61; data;for (int i &#61; 0; i dcs &#61; dcs;t->postamble &#61; 0x00;return frame_len;
}int make_frame(frame_ptr & frame, uint8_t tfi, data_ptr data, int data_len)
{assert(data_len >&#61; 0);int frame_len &#61; cal_frame_len(data_len);frame &#61; new_frame(frame_len);return make_frame(frame.get(), frame_len, tfi, data.get(), data_len);
}int cal_spi_frame_len(int data_len)
{return cal_frame_len(data_len) &#43; 1;
}int make_spi_frame(uint8_t* buf, int buf_len, uint8_t spi_tfi, uint8_t tfi, const uint8_t* data, int data_len)
{int frame_len &#61; cal_spi_frame_len(data_len);if (buf &#61;&#61; NULL || buf_len &#61;&#61; 0){return frame_len;}if (buf_len }int make_spi_frame(frame_ptr& frame, uint8_t spi_tfi, uint8_t tfi, const uint8_t* data, int data_len)
{assert(data_len >&#61; 0);int frame_len &#61; cal_spi_frame_len(data_len);frame &#61; new_frame(frame_len);return make_spi_frame(frame.get(), frame_len, spi_tfi, tfi, data, data_len);
}int make_spi_frame(frame_ptr& frame, uint8_t spi_tfi, uint8_t tfi, data_ptr data, int data_len)
{assert(data_len >&#61; 0);int frame_len &#61; cal_spi_frame_len(data_len);frame &#61; new_frame(frame_len);return make_spi_frame(frame.get(), frame_len, spi_tfi, tfi, data.get(), data_len);
}}

get_uid.cpp

#include
#include
#include
#include
#include
#include
#include "pn532.h"using namespace pn532;#define IOCTL(fd, cmd, arg) \do\{\if (ioctl(fd, cmd, arg) &#61;&#61; -1)\{\perror(#cmd);\close(fd);\return -1;\}\}\while(0)void reverse_byte(uint8_t & b)
{uint8_t res &#61; 0;uint8_t ori &#61; b;for (int i &#61; 0; i <8; &#43;&#43;i){res <<&#61; 1;res |&#61; ori & 0x01;ori >>&#61; 1;}b &#61; res;
}void reverse_bits(uint8_t * buf, int len)
{uint8_t* p &#61; buf;for (int i &#61; 0; i }int init_spi(const char * dev)
{static const uint8_t spi_mode &#61; 0;static const uint8_t spi_lsb &#61; 1;static const uint8_t spi_bpw &#61; 8;//static const uint32_t spi_speed &#61; 5000000;static const uint32_t spi_speed &#61; 1000000;int fd &#61; open(dev, O_RDWR);if (fd &#61;&#61; -1){perror("open spi device:");return -1;}IOCTL(fd, SPI_IOC_WR_MODE, &spi_mode);//IOCTL(fd, SPI_IOC_WR_LSB_FIRST, &spi_lsb);IOCTL(fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bpw);IOCTL(fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);return fd;
}int sr_spi(int spi, const uint8_t* sendbuf, int sendlen, uint8_t* recvbuf, int recvlen)
{int trnum &#61; 0;struct spi_ioc_transfer tr[2];memset(tr, 0, sizeof(tr));frame_ptr lsbbuf;if (sendlen > 0){lsbbuf &#61; new_frame(sendlen);memcpy(lsbbuf.get(), sendbuf, sendlen);reverse_bits(lsbbuf.get(), sendlen);tr[trnum].tx_buf &#61; (uint64_t)lsbbuf.get();tr[trnum].rx_buf &#61; 0;tr[trnum].len &#61; sendlen;&#43;&#43;trnum;}if (recvlen > 0){tr[trnum].tx_buf &#61; 0;tr[trnum].rx_buf &#61; (uint64_t)recvbuf;tr[trnum].len &#61; recvlen;&#43;&#43;trnum;}if (trnum &#61;&#61; 0){return -1;}if (ioctl(spi, SPI_IOC_MESSAGE(trnum), tr) !&#61; (sendlen &#43; recvlen)){perror("spi send recv error:");return -1;}if (recvlen > 0){reverse_bits(recvbuf, recvlen);}return sendlen&#43;recvlen;
}int send_spi(int spi, const uint8_t* sendbuf, int sendlen)
{return sr_spi(spi, sendbuf, sendlen, NULL, 0);
}int recv_spi(int spi, uint8_t* recvbuf, int recvlen)
{return sr_spi(spi, NULL, 0, recvbuf, recvlen);
}void print_hex(const uint8_t * buf, int len)
{const uint8_t * p &#61; buf;for (int i &#61; 0; i }int wait_for_ready(int spi)
{static const uint8_t sr[] &#61; {pn532::SPI_SR};uint8_t buf[1];while (1){usleep(10000);if (sr_spi(spi, sr, 1, buf, 1) !&#61; 2){printf("spi send recv error!\n");return -1;}if (buf[0] &#61;&#61; 0x01)return 0;}return -1;
}#define BUF_SIZE 264int do_cmd(int spi, const uint8_t* cmd, int cmdlen, const char* cmdname, int answerlen)
{static const uint8_t dr[] &#61; {pn532::SPI_DR};if (cmdname !&#61; NULL){printf("do command: %s\n", cmdname);}if (answerlen <0){answerlen &#61; BUF_SIZE;}uint8_t buf[BUF_SIZE];frame_ptr cmd_frame;int cmd_frame_len &#61; make_spi_frame(cmd_frame, SPI_DW, TFI_H2P, cmd, cmdlen);print_hex(cmd_frame.get(), cmd_frame_len);if (send_spi(spi, cmd_frame.get(), cmd_frame_len) !&#61; cmd_frame_len){perror("write cmd");return -1;}if (wait_for_ready(spi) !&#61; 0){return -1;}printf("ready for ack!\n");if (sr_spi(spi, dr, 1, buf, 6) <0){perror("read ack error");return -1;}printf("read ack: ");print_hex(buf, 6);if (wait_for_ready(spi) !&#61; 0){return -1;}printf("ready for answer!\n");if (sr_spi(spi, dr, 1, buf, answerlen) <0){perror("read answer error");return -1;}printf("read answer: ");print_hex(buf, answerlen);return 0;
}int main(int argc, char *argv[])
{int spi &#61; init_spi("/dev/spidev0.0");if (spi <0){fprintf(stderr, "init spi device error!\n");return -1;}#if 0uint8_t gfv_data[] &#61; {0x02};if (do_cmd(spi, gfv_data, sizeof(gfv_data), "get fireware version", 13) <0){return -1;}
#endif#if 1uint8_t set_normal_mode_data[] &#61; {0x14, 0x01, 0x00, 0x00};if (do_cmd(spi, set_normal_mode_data, sizeof(set_normal_mode_data), "set normal mode", 9) <0){return -1;}
#endif#if 1uint8_t autopoll_data[] &#61; {0x60, 0xff, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, 0x10, 0x11, 0x12, 0x20, 0x23};if (do_cmd(spi, autopoll_data, sizeof(autopoll_data), "auto poll", 32) <0){return -1;}
#endif#if 0uint8_t lpt_data[] &#61; {0x4A, 0x01, 0x00};if (do_cmd(spi, lpt_data, sizeof(lpt_data), "list passive target", 32) <0){return -1;}
#endifclose(spi);return 0;
}

注意&#xff1a;在使用树莓派测试过程中发现以下问题&#xff1a;

  1. 树莓派的SPI驱动不支持设置LSB传输&#xff0c;只能以MSB传输&#xff0c;而PN532模块要求以LSB传输&#xff0c;所以应用程序只能在发送之前反转位序&#xff0c;然后发送&#xff0c;在接收之后反转位序&#xff0c;然后再处理数据&#xff1b;

  2. PN532用户手册中的以下消息描述不是很清楚&#xff0c;其中DW&#xff0c;SR&#xff0c;DR都应该是主控发给PN532的&#xff0c;从PN532读到的数据中不包含这些内容。在这里插入图片描述

  3. 如果以write写入SR&#xff0c;以read读取Status&#xff0c;则大概率会丢失数据导致检测不到ready状态&#xff0c;所以必须使用ioctl来一次完成写入SR&#xff0c;读入status&#xff1b;

  4. 如果检测到ready&#xff0c;但是不读取ack&#xff0c;程序退出了&#xff0c;那么下一次运行发送命令后就检测不到ready&#xff0c;推测PN532按错误处理丢弃了上一次的ACK和本次命令数据&#xff0c;所以再次运行发送命令后又可以检测到ready&#xff1b;因此建议在每次程序启动时复位一下PN532模块&#xff0c;避免之前程序退出遗留了ACK等数据导致程序逻辑错误。


参考

https://blog.csdn.net/TAlice/article/details/83868713
https://www.cnblogs.com/subo_peng/p/4848260.html
https://www.waveshare.net/wiki/PN532_NFC_HAT
https://pinout.xyz/pinout/spi#
https://github.com/nfc-tools/libnfc


推荐阅读
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
author-avatar
我想去海边6_414
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有