通常我们认为以计算机cpu为核心,其外部的所有的设备都可以称为是外部输入输出设备。例如计算机中的显示器就是一个输出设备,它的作用是将一些数字信号转化为图形信号显示在电子屏幕上,其数据是由内向外流动,因此我们称显示器为输出设备。
又如键盘和鼠标是输入设备,它们的作用是将使用者的命令信号转化为数字信号传递给计算机的cpu,其数据是由外向内流动,因此我们称键盘和鼠标为输入设备。又如,打印机是一个输出设备,游戏手柄和摄像头是输入设备。如下图:
通常的,我们将键盘称为计算机的标准输入设备,将显示器称为计算机的标准输出设备。这也是unix和类unix系统中一直延用的名称。我们在做虚拟文件系统时将每一个进程中都分配了一个文件描述结构体数组:
//进程控制块process control blocktypedef struct pcb_s{ //进程栈顶地址 void *p_stack; //栈内存地址,释放、统计内存时使用 void *p_stack_mem; //栈内在大小 uint32_t stack_size; //优先级由高0到低32 uint8_t prio; //任务状态 uint8_t status; //任务休眠ticks uint32_t sleep_tick; //任务入口函数 void (*task_entry)(void *); //任务函数参数 void *task_arg; //进程的文件描位图,1表示空闲,0表示使用 uint32_t f_use_map; //进程的文件描述结构体数组 vfs_node_s *fnodes[fnode_size];} pcb_s;这个fnodes[fnode_size]文件描述数组记录了进程所打开每一个设备文件的地址。这个数组的下标就是我们使用open()函数所返回的值,也就是我们通常所说的文件描述符。文件描述符为int类型,范围通常是0~fnode_size。当文件描述符小于0时表示打开设备文件失败。几乎所有的类unix系统中都使用了标准输入、标准输出和标准错误这3个io设备。
所以,操作系统为每一个进程分配文件描述符时,会默认将0、1、2分别用于表示标准输入、标准输出和标准错误这3个设备。所以通常情况下我们使用open()函数来打开一个设备文件时, 返回的文件描述大多数是以3开始的,在同一个进程中同时打开多个设备文件,其文件描述符通常会是3、4、5、6、7、8……等。
实际上,我们在嵌入式领域里的处理器性能有限,外设的各类和功能多种多样,并非像个人电脑或是网络服务器一样通用和统一。在单片机领域中我们所使用的操作系统并非一定要遵循unix标准或习惯,但为了学习其优秀的设计理念,我们可以将我们的嵌入式操作系统中实现标准输入、标准输出和标准错误这3个设备。
在cortex-m3处理器中我们可以使用串口设备作为操作系统中的标准输入和标准输出,而从本质上讲标准错误这个设备的功能跟标准输出是一样的,只不过其显示的内容都是程序错误,我们不单独来实现标准错误设备,而只来完成标准输入和标准输出这两个设备。例如串口1的初始化、读、写程序如下:
void serial1_init(void){ gpio_inittypedef gpio_initstructure; usart_inittypedef usart_initstructure; nvic_inittypedef nvic_initstructure; nvic_prioritygroupconfig(nvic_prioritygroup_4); nvic_initstructure.nvic_irqchannel = usart1_irqn; nvic_initstructure.nvic_irqchannelpreemptionpriority = 2; nvic_initstructure.nvic_irqchannelsubpriority = 0; nvic_initstructure.nvic_irqchannelcmd = enable; nvic_init(&nvic_initstructure); rcc_apb2periphclockcmd(rcc_apb2periph_usart1 | rcc_apb2periph_gpioa | rcc_apb2periph_afio, enable); gpio_initstructure.gpio_pin = gpio_pin_9; gpio_initstructure.gpio_mode = gpio_mode_af_pp; gpio_initstructure.gpio_speed = gpio_speed_50mhz; gpio_init(gpioa, &gpio_initstructure); gpio_initstructure.gpio_pin = gpio_pin_10; gpio_initstructure.gpio_mode = gpio_mode_in_floating; gpio_init(gpioa, &gpio_initstructure); usart_initstructure.usart_baudrate = serial_bautrate; usart_initstructure.usart_wordlength = usart_wordlength_8b; 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; usart1- >cr1 |= (usart_cr1_re | usart_cr1_te); usart_init(usart1, &usart_initstructure); usart_itconfig(usart1, usart_it_rxne, enable); usart_cmd(usart1, enable);}void serial1_write(uint8_t data){ uint8_t next_head = serial1_tx_buffer_head + 1; if (next_head >= tx_ring_buffer1) { next_head = 0; } while (next_head == serial1_tx_buffer_tail); serial1_tx_buffer[serial1_tx_buffer_head] = data; serial1_tx_buffer_head = next_head; usart1- >cr1 |= usart_flag_txe;}int serial1_read(uint8_t *ch){ uint8_t tail = serial1_rx_buffer_tail; if (serial1_rx_buffer_head == tail) { return 0; } else { uint8_t data = serial1_rx_buffer[tail]; tail++; if (tail >= rx_ring_buffer1) { tail = 0; } serial1_rx_buffer_tail = tail; *ch = data; return 1; }}void storehandledatain(uint8_t data){ uint8_t next_head; next_head = serial1_rx_buffer_head + 1; if (next_head >= rx_ring_buffer1) { next_head = 0; } if (next_head != serial1_rx_buffer_tail) { serial1_rx_buffer[serial1_rx_buffer_head] = data; serial1_rx_buffer_head = next_head; } else { next_head++; next_head--; }}之后我们就可以编写一个/dev/ttys1设备文件用于串口1的驱动:
int ttys1_open(struct file *fs){ return 0;}int ttys1_close(struct file *fs){ return 0;}size_t ttys1_read(struct file *fs, void *buff, size_t size){ uint8_t *p = (uint8_t *)buff; size_t read_len = 0; for (int i = 0; i < size; i++) { if (!serial1_read(&p[read_len])) { return read_len; } read_len++; } return size;}size_t ttys1_write(struct file *fs, const void *buff, size_t size){ uint8_t *p = (uint8_t *)buff; for (int i = 0; i < size; i++) { serial1_write(p[i]); } return size;}void ttys1_init(void){ file_operations_s ops = {0}; ops.open = ttys1_open; ops.close = ttys1_close; ops.write = ttys1_write; ops.read = ttys1_read; ops.ioctl = null; fs_register_dev(/dev/ttys1, ops);}
健身房智能镜子成为了一种发展趋势,它的特色是什么
360儿童七款新品齐发 “少年新装备”安全更兼关怀
万兴PDF密码如何查看列出的权限
了解FPGA比特流结构
IC芯片的晶圆级射频(RF)测试
实战篇:IO设备
助力高校射频教学,RIGOL推出经济型频谱分析仪
【Renesas RA6M4开发板之DHT11温湿度读取】
iPhoneSE可能无缘中国市场,不如期待iPhone S的到来
这几款旧的旗舰价格跌至冰点,要入手的朋友们一定要抓紧了!
太阳能发电原理是什么
电机正反转电路图讲解
新一代信息技术产业发展高峰论坛在深圳举办
宝莱高科:Q4营收同比有望实现增长,环比增长存在不确定性
真无线蓝牙耳机哪个好,2020真无线蓝牙耳机推荐
什么是粗抛、中抛和精抛?等离子抛光属于哪种?
基于FPGA的温度模糊自适应PID控制器的设计
“粽”享好礼!探索CodeArts产品,赢取丰厚奖品!
下一个风口——光伏电站运维+指南
余承东称将发布捅破天的新技术 华为回应将发天”技术