linux之多线程

发布于:2025-02-10 ⋅ 阅读:(45) ⋅ 点赞:(0)

Linux多线程间通信和多进程间通信的方式_多进程、多线程同步(通讯)的方法-CSDN博客

别再被多线程搞晕了!一篇文章轻松搞懂 Linux 多线程同步! - 江小康 - 博客园

1 创建一个串口线程

/*
 ============================================================================
 Name        : uart.c
 Author      : xx
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style, FMQL
				
 ============================================================================
 */

#include "uartps.h"

int uart_set(int fd, int speed, int flow_ctrl, int data_bits, int stop_bits, char parity)
{
	int i;
	int status;
	speed_t baud;
	int speed_get = 0;
	struct termios newtio, oldtio;
	
	/* 配置串口 */
    // ... 
	
	printf("Uart set done\n");
	
	return 0;
}

int uart_send(int fd, char *snd_buf, int snd_len)
{
	int len = 0, fs_sel;
	fd_set fs_write;
	struct timeval time;
	
	FD_ZERO(&fs_write);
	FD_SET(fd, &fs_write);
	
	time.tv_sec = 10;
	time.tv_usec = 0;
	
	fs_sel = select(fd+1, NULL, &fs_write, NULL, &time);
	if(fs_sel)
	{
		len = write(fd, snd_buf, snd_len);
		//printf("len = %d fs_sel=%d\n", len, fs_sel);
		return len;	
	}
	
}

int uart_rec(int fd, char *rcv_buf, int rcv_len)
{
	int len, fs_sel;
	fd_set fs_read;
	struct timeval time;
	
	FD_ZERO(&fs_read);
	FD_SET(fd, &fs_read);
	
	time.tv_sec =10;
	time.tv_usec = 0;
	
	fs_sel = select(fd+1, &fs_read, NULL, NULL, &time);
	if(fs_sel)
	{
		len = read(fd, rcv_buf, rcv_len);
		printf("len = %d fs_sel=%d\n", len, fs_sel);
		return len;	
	}
	
}

int open_uart(const char *device_name)
{
	int fd;
	const char *val="send_data";
	char read_back[128], show_read_back[128];
	int i=0;
	int ret = 0;
	int num = 0;
	

	/* 1. open the uart */
	fd = open(device_name,O_RDWR| O_NOCTTY | O_NDELAY);
	if(fd < 0)
	{
		printf("can't open device %s\n", device_name);
		return -1;	
	}

	/* 2. 对fd进行操作 */
	ret = fcntl(fd, F_SETFL, 0);
	if(ret < 0)
	{
		printf("fcntl failed!\n");
		return -2;
	}
	else
		printf("fcntl =%d\n", ret);
		
	uart_set(fd, 115200, -1, 8, 1, 'n');

	uart_send(fd, (char *)val, strlen(val));
	while(i<2)
	{
		// uart_send(fd, (char *)val, strlen(val));
		//write(fd,val, strlen(val));
		//i++;
		sleep(3);
		
		memset(show_read_back, 0x0, sizeof(show_read_back));
		num = read(fd,read_back, sizeof(read_back));
		printf("num = %d\n", num);
		if(num > 0 )
		{
			memcpy(show_read_back, read_back, num);
			printf("recved: %s", show_read_back);
		}
	}
	i=0;

	close(fd);

	return 0;
}
/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : main.c
 作者      : Skylar
 版本      : V1.0
 描述      : FMQL uart + 异步通信
 其他      : success
			实现串口收发(单次发送,多次接收)
 论坛      : www.openedv.com
 日志      : 初版V1.0 2024/12/10 创建
 ***************************************************************/

#include "main.h"

int main() {
	const char *device_name = "/dev/ttyPS1";  
	int fd = open_uart(device_name);

	printf("main end\n");
	return 0;
}

成功。

2 创建2个串口线程

共用串口线程函数

修改uartps.c 的代码,创建2个线程。

/*
 ============================================================================
 Name        : uart.c
 Author      : xx
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style, FMQL
				官方程序
 ============================================================================
 */
// 异步处理 & 回调函数
#include "uartps.h"

#define UART_BUF_SIZE 256

int uart_set(int fd, int speed, int flow_ctrl, int data_bits, int stop_bits, char parity)
{
	speed_t baud;
	int speed_get = 0;
	struct termios newtio, oldtio;
	
	
	
	printf("Uart set done\n");
	
	return 0;
}

int uart_send(int fd, char *snd_buf, int snd_len)
{
	int len = 0, fs_sel;
	fd_set fs_write;
	struct timeval time;
	
	FD_ZERO(&fs_write);
	FD_SET(fd, &fs_write);
	
	time.tv_sec = 10;
	time.tv_usec = 0;
	
	fs_sel = select(fd+1, NULL, &fs_write, NULL, &time);
	if(fs_sel)
	{
		len = write(fd, snd_buf, snd_len);
		//printf("len = %d fs_sel=%d\n", len, fs_sel);
		return len;	
	}
}

int uart_rec(int fd, char *rcv_buf, int rcv_len)
{
	int len, fs_sel;
	fd_set fs_read;
	struct timeval time;
	
	FD_ZERO(&fs_read);
	FD_SET(fd, &fs_read);
	
	time.tv_sec =10;
	time.tv_usec = 0;
	
	fs_sel = select(fd+1, &fs_read, NULL, NULL, &time);
	if(fs_sel)
	{
		len = read(fd, rcv_buf, rcv_len);
		printf("len = %d fs_sel=%d\n", len, fs_sel);
		return len;	
	}
	
}

int open_uart(const char *device_name[])
{
	// int fd;
	const char *val[2]={"ttyPS1:send_data!!!","ttyPS2:send_data!!!"};
	// char read_back[128], show_read_back[128];
	int ret = 0;

	// printf("串口dev0: %s, 串口dev1: %s\r\n", device_name[0], device_name[1]);

	// 初始化互斥锁
	pthread_mutex_init(&mutex, NULL);

	for (int i = 0; i < 2; i++){
		// 初始化串口线程,指定串口设备名
		uart_threads[i].uart_device_name = device_name[i];
		printf("串口dev0: %s \r\n", uart_threads[i].uart_device_name);
		/* 1. open the uart */
		uart_threads[i].uart_fd = open(uart_threads[i].uart_device_name,O_RDWR| O_NOCTTY | O_NDELAY);;
		if (uart_threads[i].uart_fd == -1) {
			printf("串口 %d 初始化失败\r\n", 1);
			return -1;
		}

	/* 2. 对fd进行操作 */
		ret = fcntl(uart_threads[i].uart_fd, F_SETFL, 0);
		if(ret < 0)
		{
			printf("fcntl failed!\n");
			return -2;
		}
		// else
		// 	printf("fcntl =%d\n", ret);		
		uart_set(uart_threads[i].uart_fd, 115200, -1, 8, 1, 'n');

	/* 3. 创建线程 */
		uart_send(uart_threads[i].uart_fd, val[i], strlen(val[i]));
		if (uart_create_thread(&uart_threads[i])!= 0) {
			perror("创建串口 线程失败");
			return 1;
		}
	}

	// 等待所有线程执行完毕(这里只是简单示例,实际可能不需要等待或者有其他退出条件)
	for (int i = 0; i < 2; i++) {
		pthread_join(uart_threads[i].thread_id, NULL);
		printf("waiting\r\n");
	}

	// 关闭所有设备并释放相关资源
	close_all_devices(uart_threads, 2);
	
	// 销毁互斥锁
	pthread_mutex_destroy(&mutex);

	return 0;
}

/****************** 创建线程 ************************/
// 串口线程函数
void* uart_thread_function(void* arg) {
	UartThread* uart_thread = (UartThread*)arg;

	// 调用串口的初始化函数,获取文件描述符
	// uart_thread->uart_fd = fd;

	// 循环处理串口的数据收发等操作
	while (1) {
		int ret = pthread_mutex_lock(&mutex);
		if (ret!= 0) {
			perror("线程获取互斥锁失败\r");
			// 可以在这里进行适当的错误处理,如退出线程或者尝试重新获取锁等操作
		}
		// char send_buf[UART_BUF_SIZE] = "send_data";
		// 这里可以根据实际需求填充发送缓冲区 send_buf
		// uart_send(uart_thread->uart_fd, send_buf,strlen(send_buf));

		char receive_buf[UART_BUF_SIZE];
		uart_rec(uart_thread->uart_fd, receive_buf, UART_BUF_SIZE);
		pthread_mutex_unlock(&mutex);
		// 可以在这里添加适当的延时,避免过于频繁地操作串口,根据实际情况调整
		usleep(100);
	}
	pthread_exit(NULL);
}

// 创建串口线程函数实现
int uart_create_thread(UartThread* uart_thread) {
	return pthread_create(&uart_thread->thread_id, NULL, uart_thread_function, uart_thread);
}

// 关闭单个串口设备及释放相关线程资源的函数实现,传入线程结构体指针
void close_uart_device(UartThread* uart_thread) {
	if (uart_thread->uart_fd!= -1) {
		close(uart_thread->uart_fd);
		uart_thread->uart_fd = -1;
	}
	pthread_cancel(uart_thread->thread_id);  // 取消线程执行
	pthread_join(uart_thread->thread_id, NULL);  // 等待线程真正结束,释放线程资源
}

// 关闭所有串口设备及释放相关线程资源的函数实现,传入线程结构体数组和数组大小
void close_all_devices(UartThread* uart_threads, int num_threads) {
	for (int i = 0; i < num_threads; i++) {
		close_uart_device(&uart_threads[i]);
	}
}

运行结果:

  • 串口1:正常接收;
  • 串口2:接收延迟

 串口1/2分开串口线程函数

void* uart_thread_function1(void* arg);
void* uart_thread_function2(void* arg)

分开接收缓冲区;

和uart_rev函数的编写有关:(猜测)FD_ZERO、FD_SET、select、tcflush函数的使用影响进程的时间。

3 不影响主线程的方式

4 创建tcp线程

lwip.c

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : lwip.c
 作者      : Skylar
 版本      : V1.0
 描述      : FMQL lwip + pthread
 其他      : 实现创建线程,轮询接收数据
			
 论坛      : www.openedv.com
 日志      : 初版V1.0 2024/12/19 创建
 ***************************************************************/
#include "lwip.h"

int init_tcp(TcpDevice* tcp_device){
	if((tcp_device->socket_fd = socket(AF_INET,SOCK_STREAM,0)) < 0 )
	{
		printf("fd = %d\n",tcp_device->socket_fd);
		perror("socket error\r");
		return -1;
	}
	printf("socket\r\n");

	bzero(&tcp_device->server_addr,sizeof(tcp_device->server_addr));
	// IP & Port
	tcp_device->server_addr.sin_family = AF_INET;
	tcp_device->server_addr.sin_port = htons(tcp_device->port);
	tcp_device->server_addr.sin_addr.s_addr = INADDR_ANY;

	//绑定
	if(bind(tcp_device->socket_fd, (struct sockaddr *)&tcp_device->server_addr,sizeof(tcp_device->server_addr)) < 0)
	{
		perror("bind error\r");
		return -1;
	}
	printf("bind\r\n");

	//调用listen把主动套接字变成被动套接字
	if(listen(tcp_device->socket_fd,5) < 0)	// 监听连接
	{
		perror("listen error\r");
		return -1;
	}
	printf("listen\r\n");

	socklen_t len;
	struct sockaddr_in *cilt;

	len = sizeof(struct sockaddr_in);		//sockaddr
	cilt =(struct sockaddr_in *) malloc(sizeof(struct sockaddr_in));
	bzero(cilt,sizeof(struct sockaddr));

	//阻塞等待客户端链接请求	// 如果连接失败,需要重新accept
	tcp_device->newfd = -1;
	tcp_device->newfd = accept(tcp_device->socket_fd,(struct sockaddr *)cilt,&len);
	if(tcp_device->newfd < 0)
	{
		perror("accept error\r");
		return -1;
		// continue;
		// 再次连接
	}
	printf("accept\r\n");

	free(cilt);
	return tcp_device->newfd;
}

int tcp_send(int socketfd, const char *send_buf){
	

	int ret = send(socketfd, send_buf, strlen(send_buf), 0);
	if(ret < 0){
		perror("tcp send error\t");
		return -1;
	}else{
		printf("tcp send message succeed\r\n");
	}
	return 0;
}

void* tcp_thread_function(void* arg){
	TcpDevice* tcp_device = (TcpDevice*)arg;

	int ret = 0;
	char p[INET_ADDRSTRLEN];

	socklen_t len;
	struct sockaddr_in *cilt;
	unsigned short int  du;

	len = sizeof(struct sockaddr_in);
	cilt =(struct sockaddr_in *) malloc(sizeof(struct sockaddr_in));
	// bzero(cilt,sizeof(struct sockaddr));
	if (getpeername(tcp_device->newfd, (struct sockaddr *)cilt, &len) == 0){
		du = ntohs(cilt->sin_port);
		inet_ntop(AF_INET,(void *)&(cilt->sin_addr),p,len);
		printf("tcp recv ready\n");
	}else {
        perror("getpeername error");
    }

	while(1)
	{
		// 清除缓冲区数据
		bzero(tcp_device->buf,sizeof(tcp_device->buf));

		do{
			ret = read(tcp_device->newfd, tcp_device->buf, BUF_SIZ - 1);
		} while(ret < 0 && EINTR == errno );
		if(ret < 0)
		{
			perror("tcp read\r");
			exit(1);
		}
		if(!ret)
			break;
		printf("tcp recv[%u]:[%s] data: %s\n",du,p,tcp_device->buf);

		/* 数据处理 */
		if(!strncasecmp(tcp_device->buf,"quit",strlen("quit")))
		{
			printf("tcp rev exit \n");
			break;	
		}
	}
	free(cilt);
	pthread_exit(NULL);
}

// 创建串口线程函数实现
int tcp_create_thread(TcpDevice* tcp_thread) {
	int ret = pthread_create(&tcp_thread->thread_id, NULL, tcp_thread_function, tcp_thread);
	// pthread_t tid;
	if (ret != 0) {
		perror("tcp pthread_create err\r");
		exit(1);
	}
	printf("tcp pthread_create\n");
	pthread_detach(tcp_thread->thread_id);	//要不要 &
	return ret;
}
#ifndef LWIP_H
#define LWIP_H

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <errno.h>
#include <arpa/inet.h>
#include <pthread.h>

#define TCP_PORT	5001
#define IP_ADDR		"192.168.1.138"

#define BUF_SIZ		128

// 定义TCP设备结构体
typedef struct {
	int socket_fd;
	int port;
	struct sockaddr_in server_addr;
	int newfd;

	char buf[BUF_SIZ];  // rev_buf

	pthread_t thread_id;
} TcpDevice;
TcpDevice* tcp_dev;


// TCP初始化函数声明,传入TCP设备结构体指针,返回初始化结果(成功为0,失败为-1)
int init_tcp(TcpDevice* tcp_device);

int tcp_send(int socketfd, const char *send_buf);
// TCP线程函数声明,传入包含TCP设备信息的结构体指针
void* tcp_thread_function(void* arg);
int tcp_create_thread(TcpDevice* tcp_thread);

#endif

main.c

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : main.c
 作者      : Skylar
 版本      : V1.0
 描述      : FMQL lwip + pthread
 其他      : 实现创建线程,轮询接收数据
			问题:无法一次接收全部字符(分多次接收)
			优化TcpDevice结构体
 论坛      : www.openedv.com
 日志      : 初版V1.0 2024/12/19 创建
 ***************************************************************/

#include "main.h"

int main() {
	const char *hello = "Hello from TCP Server";
	printf("main start\r\n");
	// 初始化
	tcp_dev = (TcpDevice *)malloc(sizeof(TcpDevice));
	tcp_dev->port = TCP_PORT;
	tcp_dev->newfd = -1;
    bzero(tcp_dev->buf, sizeof(tcp_dev->buf));
    tcp_dev->thread_id = 0;

	int fd = init_tcp(tcp_dev);
	if(fd == -1){
		free(tcp_dev);
		return -1;
	}

	usleep(100);
	tcp_send(fd, hello);
	usleep(100);

	tcp_create_thread(tcp_dev);

	printf("back to main\r");
	usleep(100);
	tcp_send(fd, hello);
	usleep(100);
	while(1);
	return 0;
}
#ifndef MAIN_H
#define MAIN_H

#include "rtc_ds3231.h"
#include <stdio.h>
#include <pthread.h>

#endif

改进

 tcp接收字符串数据部分还需要修改。

5 创建gpio线程

了解linux下gpio相关的文件。

fmql gpio

  • 确定IO管脚对应的gpio编号。
  • 先 export, 再 set direction, set value,不使用的话就unexport。
  • 所有的gpio,只创建一个线程。

代码

找不到gpiochip文件夹

  •  app运行结果:

Device or resource busy

led不亮 - gpio引脚弄错

  • 根据gpiochip基地址,确定gpio控制器(gpio0,gpio1,gpio2,gpio3)

修改之后,led亮了。

gpio.c

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : gpio.c
 作者      : Skylar
 版本      : V1.0
 描述      : FMQL gpio + pthread
 其他      : 实现创建线程,
			
 论坛      : www.openedv.com
 日志      : 初版V1.0 2024/12/20 创建
 ***************************************************************/
#include "gpio.h"

// GPIO引脚编号数组
gpio_pins[] = {59};    //led


// 根据GPIO编号获取对应的gpiochip控制器编号
int get_gpiochip_num(int gpio_pin) {
	DIR *dir;
	struct dirent *entry;
	// char gpiochip_path[100];
	int base_gpiochip_num = 0;
	int found = 0;

	dir = opendir(GPIO_BASE_DIR);
	if (dir == NULL) {
		perror("打开GPIO基础目录失败");
		return -1;
	}

	if(gpio_pin >= 0 && gpio_pin < 32){				// gpiochip480
		base_gpiochip_num = 480;
		gpio_pin += 480;
	} else if(gpio_pin >= 32 && gpio_pin < 54){		// gpiochip458
		base_gpiochip_num = 458;
		gpio_pin += 458 - 32;
	} else if(gpio_pin >= 54 && gpio_pin < 86){		// gpiochip426
		base_gpiochip_num = 426;
		gpio_pin += 426 - 54;
	} else if(gpio_pin >= 86 && gpio_pin < 118){	// gpiochip394
		base_gpiochip_num = 394;
		gpio_pin += 394 - 86;
	} else {
		perror("gpio引脚%d不是PS端io.\r\n",gpio_pin);
	}
/*
// 思路是对的,但是失败了
	while ((entry = readdir(dir))!= NULL) {				// 遍历
	printf("entry->d_name: %s\n", entry->d_name);		// 只找到了gpiochip458
	
		if (strncmp(entry->d_name, "gpiochip", 8) == 0)		// 字符串比较函数
		{
			printf("找到符合条件的目录项: %s\n", entry->d_name);
			sscanf(entry->d_name, "gpiochip%d", &base_gpiochip_num);
			// printf("base_gpiochip_num = %d\n", base_gpiochip_num);

			if (gpio_pin >= base_gpiochip_num &&
				gpio_pin < base_gpiochip_num + 32) {  // gpiochip480控制32个GPIO引脚
				found = 480;
				break;
			} else if(gpio_pin >= base_gpiochip_num + 32 &&
				gpio_pin < base_gpiochip_num + 54){
				found = 458;
				break;
			} else if(gpio_pin >= base_gpiochip_num + 54 &&
				gpio_pin < base_gpiochip_num + 86){
				found = 426;
				break;
			} else if(gpio_pin >= base_gpiochip_num + 86 &&
				gpio_pin < base_gpiochip_num + 118){
				found = 394;
				break;
			} else {
				found = -1;
				break;
			}
		} else {
			printf("找到不符合条件的目录项: %s\n", entry->d_name);
		}
	}

	closedir(dir);
	if (found == -1) {
		printf("未找到对应gpio%d的gpiochip控制器,found = %d\n", gpio_pin, found);
		return -1;
	}
*/
	return gpio_pin;
}

// 配置GPIO引脚的函数,传入引脚编号,将其配置为输出模式
int gpio_config(int gpio_pin) {
	char export_path[100];
	char direction_path[100];
	int fd_export, fd_direction;
	ssize_t bytes_written;

	// 先将GPIO引脚导出到用户空间
	snprintf(export_path, sizeof(export_path), GPIO_EXPORT_PATH);
	fd_export = open(export_path, O_WRONLY);
	if (fd_export < 0) {
		perror("打开GPIO导出文件失败");
		return -1;
	}
	printf("1-export GPIO%d 成功.\r\n",gpio_pin);

	bytes_written = dprintf(fd_export, "%d", gpio_pin + 394);
	if (bytes_written < 0) {
		perror("写入GPIO引脚编号到导出文件失败");
		close(fd_export);
		return -1;
	}
	printf("2-写入GPIO引脚编号到导出文件%d成功.\r\n",gpio_pin);
	close(fd_export);

	// 设置GPIO引脚为输出方向
	snprintf(direction_path, sizeof(direction_path), GPIO_DIRECTION_PATH, gpio_pin + 394);
	fd_direction = open(direction_path, O_WRONLY);
	if (fd_direction < 0) {
		perror("打开GPIO方向设置文件失败");
		return -1;
	}
	printf("3-打开GPIO%d方向设置文件成功.\r\n",gpio_pin);

	bytes_written = write(fd_direction, "out", 3);
	if (bytes_written < 0) {
		perror("设置GPIO方向为输出失败");
		close(fd_direction);
		return -1;
	}
	printf("4-设置GPIO%d方向为输出成功.\r\n",gpio_pin);
	close(fd_direction);

	return 0;
}

// 设置GPIO引脚电平的函数,传入引脚编号和期望的电平值(0或1)
int gpio_set_level(int gpio_pin, int level) {
	char value_path[100];
	int fd_value;
	ssize_t bytes_written;

	snprintf(value_path, sizeof(value_path), GPIO_VALUE_PATH, gpio_pin + 394);
	fd_value = open(value_path, O_WRONLY);
	if (fd_value < 0) {
		perror("打开GPIO值设置文件失败");
		return -1;
	}
	printf("1-打开GPIO%d值设置文件成功.\r\n",gpio_pin);

	bytes_written = dprintf(fd_value, "%d", level);
	if (bytes_written < 0) {
		perror("写入GPIO电平值失败");
		close(fd_value);
		return -1;
	}
	printf("2-写入GPIO%d电平值成功.\r\n",gpio_pin);

	close(fd_value);

	return 0;
}

// 线程函数,用于在一个线程中配置并设置多个GPIO引脚的电平
void *gpio_thread_function(void *arg) {	
	int num_pins = sizeof(gpio_pins) / sizeof(gpio_pins[0]);
	int i;

	for (i = 0; i < num_pins; i++) {
		// 先获取对应gpio引脚的gpiochip编号并进行导出操作
		int gpiochip_num[i] = get_gpiochip_num(gpio_pins[i]);
		printf("GPIO%d对应的gpio_num = %d.\r\n",gpio_pins[i],gpiochip_num[i]);
		if (gpiochip_num[i] < 0) {
			printf("处理GPIO引脚 %d 失败,无法找到对应的gpiochip\n", gpio_pins[i]);
			continue;
		}

		// 配置每个GPIO引脚为输出模式
		if (gpio_config(gpiochip_num[i]) < 0) {
			printf("配置GPIO引脚 %d 失败\n", gpio_pins[i]);
			continue;
		}
	}

	while (1) {
		// 循环设置GPIO引脚电平,这里简单示例交替设置高低电平,实际可按具体逻辑调整
		for (i = 0; i < num_pins; i++) {
			if (gpio_set_level(gpiochip_num[i], 1) < 0) {
				printf("设置GPIO引脚 %d 电平为高失败\n", gpiochip_num[i]);
				continue;
			}
		}
		usleep(5000000);  // 睡眠5秒,可根据实际需求调整

		for (i = 0; i < num_pins; i++) {
			if (gpio_set_level(gpiochip_num[i], 0) < 0) {
				printf("设置GPIO引脚 %d 电平为低失败\n", gpiochip_num[i]);
				continue;
			}
		}
		usleep(5000000);  // 睡眠5秒,可根据实际需求调整
	}

	pthread_exit(NULL);
}

int gpio_create_thread(GpioThread *gpio_thread){
	// 创建线程用于操作GPIO引脚
	if (pthread_create(&gpio_thread, NULL, gpio_thread_function, NULL)!= 0) {
		perror("创建GPIO线程失败\r");
		return 1;
	}

	// 主线程可以继续执行其他操作,这里简单示例等待线程结束(实际可能不需要等待,根据需求调整)
	pthread_join(gpio_thread, NULL);
	return 0;
}
#ifndef GPIO_H
#define GPIO_H

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <pthread.h>
#include <dirent.h>
#include <poll.h>


/* GPIO 控制器				MIO			Pin Num			Pin
 * gpiochip480			0 - 31			32			480 - 511
 * gpiochip458			32 - 53			22			458 - 479
 * gpiochip426			0 - 31			32
 * gpiochip394			32 - 63			32
 */


// 定义GPIO的相关路径宏(基于设备树中相关节点的名称等信息,这里示例基于给定的设备树结构)
#define GPIO_BASE_DIR "/sys/class/gpio"     // 与GPIO 相关操作的路径
#define GPIO_EXPORT_PATH GPIO_BASE_DIR "/export"    // 导出GPIO引脚到用户空间
#define GPIO_UNEXPORT_PATH GPIO_BASE_DIR "/unexport"// 还原GPIO引脚回用户空间
#define GPIO_DIRECTION_PATH GPIO_BASE_DIR "/gpio%d/direction"   // 设置GPIO引脚方向
#define GPIO_VALUE_PATH GPIO_BASE_DIR "/gpio%d/value"   // 设置GPIO电平

// 定义TCP设备结构体
typedef struct {
	pthread_t gpio_thread;
} GpioThread;
GpioThread *gpio_thread;

int gpio_pins[];

int get_gpiochip_num(int gpio_pin);
int gpio_config(int gpio_pin);
int gpio_set_level(int gpio_pin, int level);
void *gpio_thread_function(void *arg);
int gpio_create_thread(GpioThread* gpio_thread);
#endif

 main.c

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : main.c
 作者      : Skylar
 版本      : V1.0
 描述      : FMQL gpio + pthread
 其他      : 实现创建线程,set gpio direction/value
			
 论坛      : www.openedv.com
 日志      : 初版V1.0 2024/12/20 创建
 ***************************************************************/

#include "main.h"

int main() {
	printf("main start\r\n");
	// 初始化
	gpio_create_thread(gpio_thread);
	usleep(100);


	printf("back to main\r");
	usleep(100);
	while(1);
	return 0;
}

/*****************************官方例程 - set gpio500 value
int main()
	{
		struct pollfd fds[1];
		char buffer[16];
		int len;
		int fd=open("/sys/class/gpio/gpio500/value",O_RDONLY);
		if(fd<0)
		{
			perror("open '/sys/class/gpio/gpio500/value' failed!\n"); 
			return -1;
		}
		fds[0].fd=fd;
		fds[0].events=POLLPRI;
		while(1)
		{
			if(poll(fds,1,0)==-1)
			{
				perror("poll failed!\n");
				return -1;
			}
			if(fds[0].revents&POLLPRI)
			{
				if(lseek(fd,0,SEEK_SET)==-1)
				{
					perror("lseek failed!\n");
					return -1;
				}
				if((len=read(fd,buffer,sizeof(buffer)))==-1)
				{
					perror("read failed!\n");
					return -1;
				}
				buffer[len]=0;
				printf("%s",buffer);
			}
		}
		return 0;
	}
******************************/
#ifndef MAIN_H
#define MAIN_H

#include "gpio.h"
#include <stdio.h>
#include <pthread.h>

#endif

6 创建can线程

代码

can.c

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : can.c
 作者      : Skylar
 版本      : V1.0
 描述      : FMQL can + pthread
 其他      : 实现创建线程,can接收
			
 论坛      : www.openedv.com
 日志      : 初版V1.0 2025/01/07 创建
 ***************************************************************/

#include "can.h"

int init_can(CanDevice *can_dev)
{
	struct ifreq ifr = {0};
	memset(&can_dev->can_addr, 0, sizeof(can_dev->can_addr));
	can_dev->sockfd = -1;
	int ret;
	can_dev->bitrate = 1000000; // 设置波特率为100M
/* test code */
	printf("start init can0 succeed\r\n");

	// 使用system函数执行设置波特率的命令(这里以ip命令为例设置波特率,不同系统设置方式可能有差异)
	// char set_baudrate_cmd[100];
	sprintf(can_dev->set_baudrate_cmd, "ip link set %s type can bitrate %d", CAN_DEV, can_dev->bitrate);
	if (system(can_dev->set_baudrate_cmd) == -1) {
		perror("system set baudrate command failed\r\n");
		return -1;
	}
	printf("system set bitrate succeed\r\n");
	// 使用system函数执行启用CAN接口的命令
	// char up_cmd[50];
	sprintf(can_dev->up_cmd, "ifconfig %s up", CAN_DEV);
	if (system(can_dev->up_cmd) == -1) {
		perror("system up command failed\r\n");
		return -1;
	}
	printf("set can0 up succeed\r\n");

	/* 打开套接字 */
	can_dev->sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
	printf("sockfd = %d\r\n",can_dev->sockfd);
	if(0 > can_dev->sockfd) {
		perror("socket error\r\n");
		return -1;
	}
 
	/* 指定can0设备 */
	strcpy(ifr.ifr_name, CAN_DEV);							// 设置接口名称
	if(ioctl(can_dev->sockfd, SIOCGIFINDEX, &ifr) < 0){		// 获取接口索引
		perror("ioctl error\n");
		return -1;
	}
	printf("ioctl succeed\r\n");

	/* 配置套接字地址结构 */
	can_dev->can_addr.can_family = AF_CAN;
	can_dev->can_addr.can_ifindex = ifr.ifr_ifindex;
 
	/* 将CAN_DEV与套接字进行绑定 */
	ret = bind(can_dev->sockfd, (struct sockaddr *)&can_dev->can_addr, sizeof(can_dev->can_addr));
	if (0 > ret) {
		perror("bind error\r\n");
		close(can_dev->sockfd);
		return -1;// exit(EXIT_FAILURE);
	}
	printf("bind = %d\r\n",ret);

	/* 设置过滤规则:不接受任何报文、仅发送数据 */
	// setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);	// NULL 换成 &rfilter, 0 换成sizeof(rfilter)

	return 0;
}

int can_send(CanDevice *can_dev)
{
	int ret = -1;
	struct can_filter rfilter[2];  //设置过滤规则
	printf("write start\r\n");

	/* 设置过滤原则*/
	// rfilter[0].can_id = 0x123;  // 目标帧 ID
	// 匹配标准帧,屏蔽扩展帧、远程帧
	// rfilter[0].can_mask = CAN_SFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG; 

	ret = write(can_dev->sockfd, &can_dev->frame, sizeof(can_dev->frame)); //发送数据
	// printf("ret = %d,size of frame = %d\r\n", ret, sizeof(can_dev->frame));
	if(sizeof(can_dev->frame) != ret) { //如果ret不等于帧长度,就说明发送失败
		perror("write error\r\n");
		return -1;
	}
	printf("write end\r\n");

	sleep(1);
	return 0;

// out:
// 	/* 关闭套接字 */
// 	close(can_dev->sockfd);
}

int cand_rcv(CanDevice *can_dev)
{
	if (0 > read(can_dev->sockfd, &can_dev->frame, sizeof(struct can_frame))) {
		perror("read error");
		return -1;
	}	// error
	printf("read end\r\n");

	/* 校验是否接收到错误帧 */
	if (can_dev->frame.can_id & CAN_ERR_FLAG) {
		printf("Error frame!\n");
		return -1;
	}

	/* 校验帧格式 */
	if (can_dev->frame.can_id & CAN_EFF_FLAG)	//扩展帧
		printf("扩展帧 <0x%08x> ", can_dev->frame.can_id & CAN_EFF_MASK);
	else		//标准帧
		printf("标准帧 <0x%03x> ", can_dev->frame.can_id & CAN_SFF_MASK);

	/* 校验帧类型:数据帧还是远程帧 */
	if (can_dev->frame.can_id & CAN_RTR_FLAG) {
		printf("remote request\n");
		// continue;
	}

	/* 打印数据长度 */
	printf("rev[%d] ", can_dev->frame.can_dlc);

	/* 打印数据 */
	for (int i = 0; i < can_dev->frame.can_dlc; i++)
		printf("%02x ", can_dev->frame.data[i]);
	printf("\n");

	return 0;
}

// 线程函数,用于在一个线程中配置CAN
void *can_thread_function(CanDevice *can_dev)
{
	int ret = -1;
	// CanDevice *can_dev = (CanDevice*)arg;
	ret = init_can(can_dev);
	printf("ret = %d\r\n",ret);
	if(ret != 0){
		perror("init CAN failed\r\n");
		// exit(EXIT_FAILURE);
	}

/* test code */
	// printf("init can0 succeed\r\n");
	// printf("can_dev: sockfd = %d, bitrate = %d\r\n", can_dev->sockfd, can_dev->bitrate);
	// printf("can_dev address: %p\n", can_dev);
	// printf("can_dev->frame address: %p\n", &can_dev->frame);
	// printf("sizeof frame: %d\n", sizeof(can_dev->frame));

	// 使用memset初始化 can_dev->frame
	memset(&can_dev->frame, 0, sizeof(can_dev->frame));
	printf("clear frame succeed\r\n");

	can_dev->frame.data[0] = 0xA0;
	can_dev->frame.data[1] = 0xB0;
	can_dev->frame.data[2] = 0xC0;
	can_dev->frame.data[3] = 0xD0;
	can_dev->frame.data[4] = 0xE0;
	can_dev->frame.data[5] = 0xF0;
	can_dev->frame.can_dlc = 6;			//一次发送6个字节数据
	can_dev->frame.can_id = 0x123;		//帧ID为0x123,标准帧

	printf("start send data\r\n");
	ret = can_send(can_dev);
	if(ret != 0){
		perror("send CAN失败\r\n");
		return -1;
	}
	printf("send can0 data succeed\r\n");

	while(1){
		ret = cand_rcv(can_dev);
		if(!ret){
			perror("rev CAN失败\r");
			// return -1;
		}
		usleep(1000);
	}
}

void *can_create_thread(CanDevice *can_dev)
{
	// 创建线程用于CAN通信		can_thread, NULL, thread_func, can_dev
	if (pthread_create(&can_dev->can_thread, NULL, can_thread_function, can_dev)!= 0) {
		perror("创建CAN线程失败\r");
		exit(1);
	}
	printf("can pthread_create\n");

	// 主线程可以继续执行其他操作,这里简单示例等待线程结束(实际可能不需要等待,根据需求调整)
	// pthread_join(can_dev, NULL);
	pthread_detach(can_dev->can_thread);	//多线程分离
	return 0;
}

#ifndef CAN_H
#define CAN_H

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <pthread.h>
#include <dirent.h>
#include <poll.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>

#define CAN_DEV     "can0"
#define BUF_SIZ		128

// 定义CAN设备结构体
typedef struct {
	pthread_t can_thread;
	struct can_frame frame;
	int sockfd;
	// struct can_bittiming bt;
	int bitrate;
	struct sockaddr_can can_addr;
	char set_baudrate_cmd[100];
	char up_cmd[50];
} CanDevice;
CanDevice *can_dev;

int init_can(CanDevice *can_dev);
int can_send(CanDevice *can_dev);
int cand_rcv(CanDevice *can_dev);
void *can_thread_function(CanDevice *can_dev);
void *can_create_thread(CanDevice *can_dev);

#endif

 main.c

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : main.c
 作者      : Skylar
 版本      : V1.0
 描述      : FMQL gpio + pthread
 其他      : 实现创建线程,
			
 论坛      : www.openedv.com
 日志      : 初版V1.0 2024/12/20 创建
 ***************************************************************/

#include "main.h"

int main() {
	printf("main start\r\n");

	// 为 can_dev 分配内存
	CanDevice *can_dev = (CanDevice *)malloc(sizeof(CanDevice));
	if (can_dev == NULL) {
		perror("Memory allocation failed");
		return 1;
	}

	// 初始化
	can_create_thread(&can_dev);
	usleep(100);

	printf("back to main\r");
	usleep(100);
	while(1);
	return 0;
}


#ifndef MAIN_H
#define MAIN_H

#include "can.h"
#include <stdio.h>
#include <pthread.h>

#endif

运行结果

7 创建rtc线程

set rtc

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名                 : rtcAPP.c
 作者                   : Skylar
 版本                   : V1.0
 描述                   : rtc测试 + pthread
 其他                   : /dev/rtc0
 使用方法                : 
 论坛                   : www.openedv.com
 日志                   : 初版V1.0 2024/12/30 创建
 ***************************************************************/

#include "rtc_ds3231.h"

int rtc_open(const char *device_name){
	int fd = open(device_name,O_RDWR);
	if(fd < 0)
	{
		printf("%s 设备文件打开失败.\n",device_name);
		return 0;
	}
	printf("%s 设备文件打开成功.\n",device_name);
	return fd;
}

void *rtc_thread_function(RtcDevice* rtc_dev)
{
	// int fd = *(int *)arg;
	// rtc_read(rtc_dev->fd);
	rtc_dev->fd = rtc_open(RTC_DEV);

	// 读取RTC驱动的时间
	struct rtc_time local_time;
	if (ioctl(rtc_dev->fd, RTC_RD_TIME, &local_time) < 0) {
		perror("读取RTC时间失败");
		// 这里可以根据实际情况决定是否要退出线程或者进行其他处理
		// continue;
	}

	printf("应用层读取的时间: %d-%d-%d %d:%d:%d\n",
			local_time.tm_year + 1900,
			local_time.tm_mon + 1,
			local_time.tm_mday,
			local_time.tm_hour,
			local_time.tm_min,
			local_time.tm_sec);

	// 线程休眠一段时间,比如2秒,再去读取时间打印
	sleep(2);

	// 循环获取并打印时间
	while (1) {
		// 初始化互斥锁
		pthread_mutex_init(&mutex, NULL);
		// set time
		local_time.tm_year	= 2020 - 1900;
		local_time.tm_mon	= 5 - 1;
		local_time.tm_mday	= 16;
		local_time.tm_hour	= 6;
		local_time.tm_min	= 46;
		local_time.tm_sec	= 0;

		// char *time_str = rtc_set(22,5,13,6,34,28);
		if (ioctl(rtc_dev->fd, RTC_SET_TIME, &local_time) < 0) {
			perror("设置RTC时间失败");
			// 这里可以根据实际情况决定是否要退出线程或者进行其他处理
			continue;
		}
		// printf("Current time in thread: %s\n", time_str);
		printf("应用层set的时间: %d-%d-%d %d:%d:%d\n",
			   local_time.tm_year + 1900,
			   local_time.tm_mon + 1,
			   local_time.tm_mday,
			   local_time.tm_hour,
			   local_time.tm_min,
			   local_time.tm_sec);

		// 休眠一段时间,比如1秒
		sleep(1);

		// 销毁互斥锁
		pthread_mutex_destroy(&mutex);
	}
	return NULL;
}

int rtc_thread_create(RtcDevice* rtc_dev)
{
	// 创建线程
	if (pthread_create(&rtc_dev->thread_id, NULL, rtc_thread_function, rtc_dev)!= 0) {
		perror("Error creating thread");
		return 1;
	}
	printf("rtc pthread_create\n");
	pthread_detach(rtc_dev->thread_id);

	return 0;
}

#ifndef RTC_DS3231_H
#define RTC_DS3231_H

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

#define RTC_DEV     "/dev/rtc0"
pthread_mutex_t mutex;
struct rtc_time local_time;

typedef struct{
	pthread_t thread_id;
	int fd;
} RtcDevice;
RtcDevice* rtc_dev;

int rtc_open(const char *device_name);
void *rtc_thread_function(RtcDevice* rtc_dev);
int rtc_thread_create(RtcDevice* rtc_dev);

#endif

 

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : main.c
 作者      : Skylar
 版本      : V1.0
 描述      : FMQL gpio + pthread
 其他      : 实现创建线程,read & set rtc time
			succeed
 论坛      : www.openedv.com
 日志      : 初版V1.0 2024/12/20 创建
 ***************************************************************/

#include "rtc_ds3231.h"
#include <stdio.h>
#include <pthread.h>

int main() {
	printf("main start\r\n");

	rtc_thread_create(&rtc_dev);
	usleep(100);

	printf("back to main\r");
	usleep(100);

	while(1);
	return 0;
}

8 创建timer线程

  • 使用timer定时,有不同的方法:
    • 模拟定时器,usleep
    • 硬件定时器/sys/class/timer
    • 硬件定时器/sys/class/timer + /dev/mem

尝试写代码,但是失败。待续。。。

fmql-linux程序生成步骤

  • 设置环境变量

petalinux安装目录下;

SDK安装包目录下。

  •  回到程序的路径

Makefile文件,用来生成驱动程序 .ko 文件(也可修改Makefile,生成app)

  • 生成可执行文件(此处为生成app)

生成app需要main.c main.h **.c **.h等多个关联的文件,所以用Makefile生成,只需输入make即可:

  •  应用程序拷贝至windows

  • app传至开发板(开发板连接路由器,电脑连接路由器网关)

fmql官方提供的ubuntu:用户名和密码都是fmsh

  • 把app拷贝至: /lib/modules/.../... 路径下
  • sudo chmod 777 app     设置app权限
  • sudo ./app   运行程序

文件夹内容(记录一下自己的学习过程)

 

上图:按照正点原子教程学的。

测试添加线程的外设(已测试uart、tcp)


网站公告

今日签到

点亮在社区的每一天
去签到