Openharmony南向研究—Linux驱动框架-串口( 三 )


Openharmony南向研究—Linux驱动框架-串口

文章插图
如果对比于STM32单片机实现的逻辑可能更易于理解 。
Openharmony南向研究—Linux驱动框架-串口

文章插图
中断处理函数的名称不同:Linux使用的是irq函数,而STM32使用的是HAL_UART_IRQHandler函数 。STM32的中断处理函数包含了发送中断和接收中断,需要在处理函数内部进行区分,而Linux中的发送和接收分别有对应的中断处理函数 。在Linux中,可以通过tty设备文件直接访问串口,而STM32需要使用串口API进行访问和操作 。STM32需要手动开启和关闭中断,而Linux的中断处理函数会在内核中自动启动和停止 。Linux中,数据的接收和发送是由tty设备驱动完成的,而STM32需要在中断处理函数内部实现数据的接收和发送 。两者关键差异是LINUX使用内核管理中断函数的启停 。
以下给出一种示例程序可以根据需要进行修改编译合入内核实现串口驱动 。
#include <linux/module.h>#include <linux/init.h>#include <linux/serial_core.h>#include <linux/serial.h>#include <linux/tty.h>#include <linux/tty_flip.h>#define DRIVER_NAME "my_serial_driver"static struct uart_driver my_uart_driver = {.owner = THIS_MODULE,.driver_name = DRIVER_NAME,.dev_name = "ttyMY",// 设备文件名,例如 /dev/ttyMY0.major = 0,// 自动分配主设备号.minor = 0,// 自动分配从设备号.nr = 1,// 支持的最大串口数量};// 串口 probe 函数,用于初始化串口参数和注册串口设备static int my_serial_probe(struct uart_port *port){// 设置串口参数port->ops = &my_uart_driver.ops;port->type = PORT_16550A;port->iotype = UPIO_MEM;port->ioport = 0x3f8;// 串口的 I/O 端口地址port->irq = 4;// 串口的中断号port->flags = UPF_BOOT_AUTOCONF;return uart_add_one_port(&my_uart_driver, port);// 注册串口设备}// 串口 remove 函数,用于注销串口设备static void my_serial_remove(struct uart_port *port){uart_remove_one_port(&my_uart_driver, port);// 注销串口设备}// 串口操作函数表,这里只需要实现 probe 和 remove 函数static struct uart_ops my_uart_ops = {.tx_empty = NULL,.set_mctrl = NULL,.get_mctrl = NULL,.stop_tx = NULL,.start_tx = NULL,.send_xchar = NULL,.stop_rx = NULL,.enable_ms = NULL,.break_ctl = NULL,.startup = NULL,.shutdown = NULL,.flush_buffer = NULL,.set_termIOS = NULL,.type = NULL,.release_port = NULL,.request_port = NULL,.config_port = NULL,.verify_port = NULL,.ioctl = NULL,.send_xchar_locked = NULL,};// 模块初始化函数,在这里注册串口驱动static int my_serial_init(void){int ret = 0;// 注册串口驱动ret = uart_register_driver(&my_uart_driver);if (ret) {printk(KERN_ERR "Failed to register UART drivern");return ret;}// 设置串口操作函数表中的 probe 和 remove 函数my_uart_ops.probe = my_serial_probe;my_uart_ops.remove = my_serial_remove;my_uart_driver.ops = my_uart_ops;return ret;}// 模块卸载函数,在这里注销串口驱动static void my_serial_exit(void){uart_unregister_driver(&my_uart_driver);}module_init(my_serial_init);module_exit(my_serial_exit);MODULE_LICENSE("GPL");驱动可以通过makefile编译为.ko文件后通过insmod合入内核 。
常规驱动的调用方式串口驱动程序在新的板卡上通常由厂家进行设备树适配和驱动开发,在实际使用案例当中需要熟练掌握通过文件描述符合tty层调用串口驱动即可 。以下展示串口驱动的调用方式
#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <unistd.h>#include <termios.h>#define DEVICE "/dev/ttyMY0"int main(){int fd = 0;struct termios tio;char buf[256];// 打开设备文件fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NDELAY);if (fd < 0) {perror("open");return -1;}// 设置串口参数tcgetattr(fd, &tio);tio.c_iflag = IGNBRK | IGNPAR;tio.c_oflag = 0;tio.c_cflag = CS8 | CREAD | CLOCAL;tio.c_lflag = 0;tio.c_cc[VTIME] = 0;tio.c_cc[VMIN] = 1;cfsetispeed(&tio, B9600);cfsetospeed(&tio, B9600);tcsetattr(fd, TCSANOW, &tio);// 读取串口数据printf("Reading from serial port...n");while (1) {int n = read(fd, buf, sizeof(buf));if (n > 0) {buf[n] = '';printf("Received: %s", buf);}}// 关闭设备文件close(fd);return 0;对于刚刚开发的驱动程序可以通过以上程序进行简单测试和验证 。
实战案例接下来展示一种通过UnionPi Tiger开发板进行串口数据收发的方案,基本思路是通过两个线程分别控制串口的收发任务,将收到的数据进行处理后再发送结果 。
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <sys/types.h>#include <fcntl.h>#include <unistd.h>#include <pthread.h>//宏定义#define OK 0#define ERR (-1)//静态变量static int fd1;// 串口设备文件描述符static int fd2;static int send_data;// 传输的数据// 从串口读的线程// 转换波特率speed_t conver_baudrate(int baudrate){switch (baudrate) {case 9600L:return B9600;case 19200L:return B19200;case 38400L:return B38400;case 115200L:return B115200;case 1152000L:return B1152000;default:return 1152000L;}}void set_baud(int fd, int baud){int ret = ERR;struct termios opt;tcgetattr(fd, &opt); // tcgetattr用来获取终端参数,将从终端获得的信息fd,保存到opt结构体中tcflush(fd, TCIOFLUSH); // 刷清缓冲区cfsetispeed(&opt, baud);cfsetospeed(&opt, baud);ret = tcsetattr(fd, TCSANOW, &opt); // 设置终端参数到opt中,使之立即生效if (ret == ERR) {perror("tcsetattr fd");exit(0);}tcflush(fd, TCIOFLUSH); // 刷清缓冲区}// 设置数据位int setup_data_bits(int setup_databits, struct termios *options_databits){if (options_databits == NULL) {perror("setup_data_bits error");return ERR;}switch (setup_databits) {case 5L:options_databits->c_cflag |= CS5;break;case 6L:options_databits->c_cflag |= CS6;break;case 7L:options_databits->c_cflag |= CS7;break;case 8L:options_databits->c_cflag |= CS8;break;default:return ERR;}return OK;}// 设置校验位int set_params_parity(int setup_parity, struct termios *options_parity){switch (setup_parity) {case 'n':case 'N':// 无奇偶校验位options_parity->c_cflag &= ~PARENB; // Clear parity enable/options_parity->c_iflag &= ~INPCK;// disable input parity checking/break;case 'o':case 'O':// 设置为奇校验options_parity->c_cflag |= (PARODD | PARENB); // odd parity checkingoptions_parity->c_iflag |= INPCK;// enable parity checkingbreak;case 'e':case 'E':// 设置为偶校验options_parity->c_cflag |= PARENB;// Enable parity /options_parity->c_cflag &= ~PARODD; // even parity/options_parity->c_iflag |= INPCK;// enable parity checking /break;case 'M':case 'm': // 标记奇偶校验options_parity->c_cflag |= PARENB | CMSPAR | PARODD;options_parity->c_iflag |= INPCK; // enable parity checking /break;case 'S':case 's': // 设置为空格options_parity->c_cflag |= PARENB | CMSPAR;options_parity->c_cflag &= ~PARODD;options_parity->c_iflag |= INPCK; // enable parity checking /break;default:return ERR;}return OK;}// 设置校验位int set_params(int fd, int databits, int stopbits, int parity){struct termios options;int ret = ERR;if (tcgetattr(fd, &options) != 0) {perror("tcgetattr failn");return ERR;}options.c_iflag = 0;options.c_oflag = 0;// setup data bitsoptions.c_cflag &= ~CSIZE;ret = setup_data_bits(databits, &options);if (ret == ERR) {return ERR;}// parityret = set_params_parity(parity, &options);if (ret == ERR) {return ERR;}// stop bits/switch (stopbits) {case 1:options.c_cflag &= ~CSTOPB;break;case 2L:options.c_cflag |= CSTOPB;break;default:return ERR;}// 请求发送和清除发送options.c_cflag &= ~CRTSCTS;options.c_lflag = 0;options.c_cc[VTIME] = 10L;options.c_cc[VMIN] = 1;tcflush(fd, TCIFLUSH);if (tcsetattr(fd, TCSANOW, &options) != 0) {return ERR;}return OK;}// 设置波特率int uart_init(int fd, int uartBaud){set_baud(fd, conver_baudrate(uartBaud));// uart param /if (set_params(fd, 8L, 1, 'n')) {perror("set uart parameters failn");return ERR;}return OK;}int data_proce(recv){if(recv=="hello_world"){send_data=https://www.isolves.com/it/cxkf/ydd/hms/2023-03-09/1;return 1;}else{send_data =0;return 0;}}void *_serial_output_task(void){pthread_detach(pthread_self());int ret;ret=write(fd2,(unsigned char *) send_data,1);if(ret>0)printf("send success");else {printf("send error");}usleep(10000);}void *_serial_input_task(void){int i = 0;int ret = ERR;// 函数返回值int buf = 0;// 用于保存读取到的字节int recv[FRAME_LEN] = {0};// 用于保存接收到的数据while (1) {// 读取一帧数据for (i = 0; i


推荐阅读