本人【Luckfox幸狐 RV1106 Linux 开发板测评】帖子链接:
一、开箱及测试
二、SDK获取与编译镜像
三、GPIO点灯
四、通过PC机共享网络接入Internet和Ubuntu下Python点灯
五、编译Buildroot系统并测试摄像头
六、PWM控制——命令和C方式
七、PWM控制——Python和设备树方式
本篇继续测试Luckfox Pro Max板子的外围接口:ADC和UART——只测试了UART3。前面已经依照文档测评了GPIO和PWM输出,证明文档没有什么坑,所以本篇直接将ADC和UART3的控制合并到一起,编写一个“UART3接收命令,然后根据命令开启一次ADC转换”的实验程序。
图8-1 UART3连接和ADC管脚
板子串口启用UART3,19脚Tx,20脚Rx,有两个ADC(0/1)为31,32脚。串口通过USB-TTL连接到PC机,串口调试工具使用XCOM。
开发板的/sys/bus/iio/devices/iio\:device0/目录下是ADC设备的属性文件,主要操作三个:in_voltage_scale用于获取ADC读数和实际电压的转换因子,in_voltage0_raw用于获取ADC通道0读数,in_voltage1_raw用于获取ADC通道1读数。
开发板的/dev目录下包含UART相关的设备文件,UART3对应文件/dev/ttyS3。
1、Python方式
Python控制程序中,ADC没有使用库,直接进行文件操作,UART使用了serial库,需要注意的串口的读写函数对应参数都是bytes类型,尤其是调用serial.write()进行串口输出时,输出内容是字符串需要通过encode()函数转为bytes。具体代码如下:
import serial # 导入串行通信库
import time # 导入时间库,用于延时
# 初始化串行端口uart3,配置其参数
uart3 = serial.Serial(
"/dev/ttyS3", # 串行端口的设备文件路径
baudrate=115200, # 波特率,即每秒传输的位数
bytesize=serial.EIGHTBITS, # 数据位大小,这里是8位
stopbits=serial.STOPBITS_ONE, # 停止位数量,这里是1位
parity=serial.PARITY_NONE, # 校验位,这里不使用校验
timeout=1, # 读取超时时间,单位是秒
)
# ADC设备的路径
ADC_DIR = "/sys/bus/iio/devices/iio:device0"
# 定义用于从指定文件路径读取值的函数
def read_value(file_path):
with open(file_path, "r") as file: # 打开文件
return file.read().strip() # 读取文件内容并去除前后的空白字符
# 主函数
def main():
print("Press Ctrl+C to quit") # 提示用户如何退出程序
while True: # 无限循环
buf = uart3.read(128) # 从串行端口读取最多128个字节的数据
if b"ADC0" in buf: # 如果接收到的数据中包含"ADC0"
# 从ADC设备读取相关的值
scale_value = float(read_value(f"{ADC_DIR}/in_voltage_scale")) # 读取电压比例因子
IN0_raw_value = float(read_value(f"{ADC_DIR}/in_voltage0_raw")) # 读取ADC0的原始值
# 计算ADC0的电压值并格式化输出
IN0_voltage = f"{IN0_raw_value * scale_value / 1000:.2f}"
print(f"IN0_Voltage: {IN0_voltage} V") # 打印到控制台
uart3.write(f"IN0_Voltage: {IN0_voltage} V\n".encode('utf-8')) # 通过串行端口发送数据
elif b"ADC1" in buf: # 如果接收到的数据中包含"ADC1"
# 类似地,处理ADC1的数据
scale_value = float(read_value(f"{ADC_DIR}/in_voltage_scale"))
IN1_raw_value = float(read_value(f"{ADC_DIR}/in_voltage1_raw"))
IN1_voltage = f"{IN1_raw_value * scale_value / 1000:.2f}"
print(f"IN1_Voltage: {IN1_voltage} V")
uart3.write(f"IN1_Voltage: {IN1_voltage} V\n".encode('utf-8')) # encode转bytes
time.sleep(2) # 等待2秒
if __name__ == "__main__":
try:
main() # 调用主函数
except KeyboardInterrupt: # 如果用户按下Ctrl+C
uart3.close() # 关闭串行端口
pass # 什么都不做,继续退出程序
图8-2 adc.py执行效果
程序执行效果如上图所示,XCOM连接板子UART3,发送“ADC0”过去则控制开发板对通道0进行一次ADC读值,发送“ADC1”则控制通道1。这里为了省事,两个ADC管脚都是悬空的,看输出应该是悬空为“高”——看文档ADC管脚参考电压就是1.8V。
2、C方式
C语音程序也是以操作设备文件的方式进行ADC和UART控制,其中需要用到struct termios结构体,这个结构体是在 POSIX 规范中定义的一个标准接口,它类似于系统 V 中的 termio 接口。这个结构体用于控制非同步通信端口,提供了一个常规的终端接口。通过设置 termios 类型的数据结构中的值和使用一组函数调用,你可以对终端接口进行控制。而在 Linux 系统中,struct termios 结构体常用于串口编程,用于设置串口参数、读取和写入串口数据等操作。通过调整这个结构体的各个成员,你可以控制串口的各种属性,如波特率、数据位、停止位、校验位等,以及终端的输入输出行为。相关代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>
// 定义一个串口发送函数,增加发送出错的判断
int writeBuf(int fd, char *buffer, int size) {
ssize_t bytes_written = write(fd, buffer, size);
if (bytes_written < 0) {
perror("Error writing to serial port");
close(fd);
return 1;
}
return 0;
}
int main() {
printf("Press Ctrl+C to quit\n");
// ADC设备文件的目录
const char *adc_dir = "/sys/bus/iio/devices/iio:device0";
// 定义三个字符数组,分别用于存储三个属性文件的路径
char in_voltage0_raw_path[256];
char in_voltage1_raw_path[256];
char in_voltage_scale_path[256];
// 字符数组赋值对应ADC的三个属性文件路径
sprintf(in_voltage0_raw_path, "%s/in_voltage0_raw", adc_dir);
sprintf(in_voltage1_raw_path, "%s/in_voltage1_raw", adc_dir);
sprintf(in_voltage_scale_path, "%s/in_voltage_scale", adc_dir);
// 打开三个ADC属性文件
FILE *scale_file = fopen(in_voltage_scale_path, "r");
FILE *in0_raw_file = fopen(in_voltage0_raw_path, "r");
FILE *in1_raw_file = fopen(in_voltage1_raw_path, "r");
// UART3设备文件路径
char serial_port[] = "/dev/ttyS3";
// 定义变量用于存储打开UART3后的文件符
int serial_fd;
// 打开UART3设备文件
serial_fd = open(serial_port, O_RDWR | O_NOCTTY);
if (serial_fd == -1) {
perror("Failed to open serial port");
return 1;
}
// 定义struct termios结构体,用于设置UART口参数
struct termios tty;
memset(&tty, 0, sizeof(tty));
// 从 serial_fd 所引用的设备中获取当前的终端属性
if (tcgetattr(serial_fd, &tty) != 0) {
perror("Error from tcgetattr");
return 1;
}
// 设置输入和输出波特率为115200
cfsetospeed(&tty, B115200);
cfsetispeed(&tty, B115200);
// 无奇偶校验、一个停止位、8 个数据位
tty.c_cflag &= ~PARENB;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
// 将串口属性值最终设置回文件
if (tcsetattr(serial_fd, TCSANOW, &tty) != 0) {
perror("Error from tcsetattr");
return 1;
}
// 串口收、发缓存和ADC读数缓存
char rx_buffer[64];
char tx_buffer[64];
char buffer[32];
float scale = 1.7578125;
while (1) {
int bytes_read = read(serial_fd, rx_buffer, sizeof(rx_buffer));
// 如果串口有接收
if (bytes_read > 0) {
rx_buffer[bytes_read] = '\0';
// 则先读取ADC的转换因子
fseek(scale_file, 0, SEEK_SET);
if (scale_file) {
fgets(buffer, sizeof(buffer), scale_file);
scale = strtof(buffer, NULL);
}
if (strstr(rx_buffer, "ADC0") != NULL) {
// 如果接收到“ADC0”则读取ADC通道0,并串3输出
fseek(in0_raw_file, 0, SEEK_SET);
if (in0_raw_file) {
fgets(buffer, sizeof(buffer), in0_raw_file);
int in0_raw_value = atoi(buffer);
float in0_voltage = (in0_raw_value * scale) / 1000.0;
printf("in ADC0 value: %d, volt: %.6f\n", in0_raw_value, in0_voltage);
memset(tx_buffer, 0, 64);
sprintf(tx_buffer, "IN0 Voltage: %.6f V\n", in0_voltage);
if (writeBuf(serial_fd, tx_buffer, strlen(tx_buffer)) != 0) return 1;
}
} else if (strstr(rx_buffer, "ADC1") != NULL) {
// 如果接收到“ADC1”则读取ADC通道1,并串3输出
fseek(in1_raw_file, 0, SEEK_SET);
if (in1_raw_file) {
fgets(buffer, sizeof(buffer), in1_raw_file);
int in1_raw_value = atoi(buffer);
float in1_voltage = (in1_raw_value * scale) / 1000.0;
printf("in ADC1 value: %d, volt:%.6f\n", in1_raw_value, in1_voltage);
memset(tx_buffer, 0, 64);
sprintf(tx_buffer, "IN1 Voltage: %.6f V\n", in1_voltage);
if (writeBuf(serial_fd, tx_buffer, strlen(tx_buffer)) != 0) return 1;
}
}
} else {
printf("No data received.\n");
}
sleep(1);
}
fclose(scale_file);
fclose(in0_raw_file);
fclose(in1_raw_file);
close(serial_fd);
return 0;
}