C语言 IO 部分整理

文件IO
流的概念:
        计算机有大量不同设备,很多都与IO操作有关,如CD-ROM驱动器、显示器、键盘、鼠标网卡、通信端口等
    操作系统为程序员提供简单统一的接口来实现对这些设备的IO操作。就C程序而言,所有的IO只是简单地从
    程序移入移出字节的事情,这种操作的对象就是字节流,简称为流,程序员只需要正确地读写数据,其实现
    细节是不可见的。
缓冲区:
        1、    绝大多数流是完全缓冲的,所谓读写操作实际上是从一块被称为缓冲区的内存区域来回复制数据。
        
        2、    用于输出流的缓冲区只有当它被写满时才会被刷新到设备或文件中。
            一次性把被写满的缓冲区写入与逐片把程序产生的输出分别写入相比效率更高。
            比如相同的工作量,一次性做完总是比每次做一点需要多次才能完成的效率更高。
            
        3、    同样的道理,输入缓冲区当它为空时通过从设备或文件读取一块较大的输入来填充缓冲区。
        
        4、    使用标准输入和标准输出时,这种缓冲可能会引起混淆,只有当操作系统断定他们与交互设备并无联系
        时才会进行完全缓冲。
            一个常见的解决办法就是把标准输入和标准输出联系到一起,就是当请求输入时刷新输出缓冲区。一个
        很普遍的例子就是在用户必须进行输入之前,提示用户进行输入的信息和以前写入到输出缓冲区的内容会
        显示到屏幕上。
        
        5、    有一点需要注意的是许多人调试程序的时候会将printf散布到错误可能出现的位置进行调试。事实上,程
        序如果失败,输出缓冲可能不会被立即刷新,结果可能不会立即显示于屏幕上。一个可行的办法就是在printf
        后面加上fflush来刷新缓冲区。
文本流和二进制流:
        流分为文本流和二进制流。
        
        文本流的有些特性在不同系统中可能不同
            其中之一就是文本的最大长度,标准规定最少是254个字符,但还是要视具体系统而定。
            另一个就是文本行的结束方式,在MS-DOS系统中,文本文件以/r/n结尾,在Linux系统中为/n结尾,在Linux系统中为/n
            
        二进制流完全根据程序编写的形式写入到文件或设备中,不做任何改变,比较适合非文本数据。    
    
关于EOF*******************************
        
    
一个程序最多能打开多少文件?
        这和编译器有关,FOPENMAX常量包括了三个标准流,值至少是8
        
流IO:
        在C程序中,为了执行与文件相关的IO任务方便,规定了文件IO的一般概况
        1、程序为必须同时处于活动状态的每个文件声明一个指针变量,其类型为FILE*。这个指针指向FILE这个结构
        当它处于活动状态是由流使用。
            FILE* fopen(char const* name, char const* mode);
            FILE* freopen(const char* filename, const char* mode, FILE* stream);
            //最后一个参数是需要大开的流,可能是一个先前从fopen函数返回的流,也可能是三个标准,这个函数试图先关闭这个流,然
            //后用这顶的文件和模式重新打开这个流,失败返回NULL,成功返回第三个参数值。
            name为文件名
            mode为权限    文本:    r(读)    w(写)    a(追加)
                        二进制:rb        wb        ab
            如果打开一个流是用于读的,那么对应的文件必须是已经存在的;如果是用于写的,如果已经存在就覆盖,否则创建;
            追加表示如果在源文件内容尾部进行写,文件不存在时创建。
            
            fseek:
            fsetpos:
            rewind:
            fflush:
            
        2、流通过调用fopen而被打开,为了打开一个流必须指出需要访问的文件以及访问方式。(不一定成功)
        3、然后才能对该文件进行读取或写入。
        4、关闭流保证与其相关联的文件被再次访问,保证任何存储与缓冲区的数据被正确地写入到文件中,
           同时释放FILE结构
           int fclose(FILE* f);        对于输出流,fclose在关闭文件之前会刷新缓冲区。成功返回0, 否则返回EOF
           更严格地说,关闭流也要检查返回值
           
IO函数以三种基本形式处理数据:单个字符,问本行,二进制数据
        区别在于获得输入来源或输入写入的地方不同。
        
                    输入                        输出                        描述
        字符:        getchar(fgetc/getc)            putchar(fputc/putc)            读写单个字符
        文本行:    gets(fgets)/scanf(fscanf)    puts(fputs)/printf(fprintf)    文本行的未格式化读写/格式化读写
        二进制数据:fread                        fwrite                        读写二进制数据
        注:括号中的函数适用所有输出输出流,其余只适用于标准输入输出流
        
字符IO
    读:从流中读取数据
        int fgetc(FILE* stream);
        int getc(FILE* stream);
        int getchar(void);
        将需要操作的流作为参数传给getc和fgetc,getchar始终从标准输入读取。这三个函数从六中读取下一个字符,作为函数
        的返回值返回,如果字符不存在更多的字符,函数就返回常量值EOF。之所以读取字符而返回int型值是为了允许函数报告文件的结尾
        EOF的值在任何可能出现的字符范围之外。
        
    写:把单个字符写入到流中, 写的时候函数会将整型参数裁剪为一个无符号字符型值
        int fputc(int character, FILE* stream);
        int putc(int character, FILE* stream);
        int putchar(int charater);
        
    其实fgetc和fputc都是真正的函数,而getc, putc, getchar和putchar都是宏。
    
        ungetc(int charater, FILE* stream);         //拿出来看一下
        
格式化的行IO
    int scanf(char const* format, ...);
    int fscanf(FILE* stream, char const* format, ...);        //从stream中读取format格式的字符
    int sscanf(char const* string, char const* format, ...);    //从string字符串中读取format格式的字符
    
    int printf(FILR* format, ...);
    int fprintf(FILE* stream, const char* format, ...);
    int sprintf(char* buffer, const char* format, ...);        //同上
    
二进制IO
    size_t fread(void* buff, size_t size, size_t count, FILE* stream);
    size_t fwrite(void* buff, size_t size, size_t count, FILE* stream);
    //size是缓冲区中每个元素的字节数,count是读取或写入的元素数, stream是流
    
定位函数:
    long ftell(FILE* stream);        //返回流的当前位置,也就是下一个要读写的位置距离起始位置的偏移量
    int fseek(FILE* stream, long offset, int from);        //改变下一次读写的位置,stream是需要操作的流,后两个分别为偏移量和从offset到哪里
        副作用:1、行末知识字符被清除
                2、如果在fseek之前使用ungetc把一个字符返回到流中,那么这个返回的字符会被丢弃,因为在定位操作之后它不再是下一个字符
    void rewind(FILE* stream);        //将读写指针返回起始位置
    from的值可以是:
        SEEK_SET:从留的起始位置起offset个字节,offset必须非负
        SEEK_CUR:从当前位置其offset个字节,offset可正可负
        SEEK_END:从流的尾部起offset个字节,offset如果为正值将定位到文件尾的后面
        
        

<think>好的,我现在需要帮用户找到一个用C语言编写的STM32 IO口模拟SPI通信的示例代码。首先,我得先回顾一下用户提供的引用内容,看看里面有没有相关的信息。 引用[2]提到,使用4根SPI线连接W25Q64STM32,通过操作引脚电平实现SPI时序,这样就能读写存储器芯片。这说明用户可能是在使用SPI接口的Flash存储器,比如W25Q64,所以示例代码可能需要包含读写这类存储器的部分。 引用[3]则详细说明了软件模拟SPI的注意事项使用流程,比如GPIO配置要正确、延时调整、多从机的NSS控制,以及初始化、启动、传输、停止的流程。用户需要的是具体的示例代码,所以需要将这些步骤转化为实际的函数代码结构。 接下来,我需要整理一个基本的软件模拟SPI框架。通常,SPI有四根线:SCK、MOSI、MISO、NSS。用户可能需要定义这些引脚,并实现时钟的拉高拉低,数据的发送接收。 根据引用[3]中的函数,比如SIM_SPI_Init、SIM_SPI_Start、SIM_SPI_TransmitReceive、SIM_SPI_Stop,这些应该是示例代码中的关键函数。需要将它们具体实现出来。例如,初始化函数会配置GPIO为输出模式,设置初始电平;传输函数则按照SPI的模式(比如模式0,CPOL=0,CPHA=0)来产生时钟信号,并在适当的时机读写数据位。 另外,用户提到的代码可能需要处理具体的SPI设备,比如W25Q64,所以可能需要包含具体的读写命令,比如读取设备ID或者读写数据。这部分可能需要参考W25Q64的数据手册,使用正确的指令码地址格式。 需要注意的是,用户希望用C语言实现,所以代码应该使用STM32的标准库或者HAL库来操作GPIO。例如,使用GPIO_WritePin来设置引脚电平,GPIO_ReadPin来读取MISO的状态。 还要考虑延时的问题,因为不同的SPI设备可能有不同的时序要求。引用[3]提到延时需根据外设调整,所以在代码中可能需要插入适当的延时函数,比如__nop()或者自定义的延时函数。 最后,示例代码应该包含一个完整的流程,从初始化到发送命令,再到读取数据,最后停止通信。可能还需要处理NSS引脚,即在传输开始时拉低,传输结束后拉高,以选中从设备。 总结一下,示例代码的结构大致如下: 1. 定义SPI相关的GPIO引脚端口。 2. 实现初始化函数,配置引脚为输出或输入。 3. 实现启动停止函数,控制NSS引脚。 4. 实现数据传输函数,按位发送接收数据。 5. 提供读写W25Q64的示例,比如读取设备ID。 需要检查代码是否符合STM32的库函数使用方式,并确保语法正确。同时,要添加注释,让用户理解每个步骤的作用。可能还需要提醒用户根据实际硬件连接修改引脚定义,以及调整延时时间。</think>以下是基于STM32的GPIO模拟SPI通信的C语言示例代码,结合W25Q64 Flash存储器的读写实现: ```c #include "stm32f1xx_hal.h" /* 引脚定义 */ #define SPI_CS_PIN GPIO_PIN_4 #define SPI_CS_PORT GPIOA #define SPI_SCK_PIN GPIO_PIN_5 #define SPI_SCK_PORT GPIOA #define SPI_MOSI_PIN GPIO_PIN_6 #define SPI_MOSI_PORT GPIOA #define SPI_MISO_PIN GPIO_PIN_7 #define SPI_MISO_PORT GPIOA /* SPI模式配置(模式0)*/ void SIM_SPI_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // SCK/MOSI/CS配置为推挽输出 GPIO_InitStruct.Pin = SPI_SCK_PIN | SPI_MOSI_PIN | SPI_CS_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(SPI_SCK_PORT, &GPIO_InitStruct); // MISO配置为输入 GPIO_InitStruct.Pin = SPI_MISO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(SPI_MISO_PORT, &GPIO_InitStruct); // 初始电平设置 HAL_GPIO_WritePin(SPI_SCK_PORT, SPI_SCK_PIN, GPIO_PIN_RESET); // CPOL=0 HAL_GPIO_WritePin(SPI_CS_PORT, SPI_CS_PIN, GPIO_PIN_SET); // 初始不选中 } /* 启动传输(拉低CS)*/ void SIM_SPI_Start(void) { HAL_GPIO_WritePin(SPI_CS_PORT, SPI_CS_PIN, GPIO_PIN_RESET); } /* 停止传输(拉高CS)*/ void SIM_SPI_Stop(void) { HAL_GPIO_WritePin(SPI_CS_PORT, SPI_CS_PIN, GPIO_PIN_SET); } /* 单字节传输函数 */ uint8_t SIM_SPI_TransmitReceive(uint8_t data) { uint8_t receive = 0; for(int i=0; i<8; i++) { // 设置MOSI(MSB First) HAL_GPIO_WritePin(SPI_MOSI_PORT, SPI_MOSI_PIN, (data & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET); data <<= 1; // 产生上升沿(CPHA=0时数据在第一个边沿采样) HAL_GPIO_WritePin(SPI_SCK_PORT, SPI_SCK_PIN, GPIO_PIN_SET); __nop(); __nop(); // 延时保证建立时间 // 读取MISO receive <<= 1; if(HAL_GPIO_ReadPin(SPI_MISO_PORT, SPI_MISO_PIN)) receive |= 0x01; // 产生下降沿 HAL_GPIO_WritePin(SPI_SCK_PORT, SPI_SCK_PIN, GPIO_PIN_RESET); __nop(); __nop(); // 延时保证保持时间 } return receive; } /* 示例:读取W25Q64设备ID */ uint16_t W25Q_ReadID(void) { uint16_t id = 0; SIM_SPI_Start(); SIM_SPI_TransmitReceive(0x90); // 发送读ID命令 SIM_SPI_TransmitReceive(0x00); // 发送3字节地址 SIM_SPI_TransmitReceive(0x00); SIM_SPI_TransmitReceive(0x00); id = SIM_SPI_TransmitReceive(0xFF) << 8; id |= SIM_SPI_TransmitReceive(0xFF); SIM_SPI_Stop(); return id; } ``` 使用说明: 1. 需根据实际硬件连接修改引脚定义 2. 延时时间`__nop()`需要根据主频调整 3. 典型应用流程: ```c SIM_SPI_Init(); uint16_t flash_id = W25Q_ReadID(); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值