TLC5615算是能找到的最简单的一块SPI设备了. 顺带KGDB调试配置记录.
模块基础
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <linux/kernel.h> #include <linux/errno.h> #include <linux/uaccess.h> #include <linux/slab.h> #include <linux/of.h> #include <linux/device.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/list.h> #include <linux/mutex.h> #include <linux/spi/spi.h> #include <linux/module.h>
MODULE_LICENSE("GPL");
|
OF匹配表
1 2 3 4 5 6 7 8 9 10
| static const struct of_device_id tlc5615_ids[] = { { .compatible = "ti,tlc5615" }, {}, };
static const struct spi_device_id tlc5615_id[] = { {"tlc5615", 0}, {}, }; MODULE_DEVICE_TABLE(spi, tlc5615_id);
|
SPI驱动
1 2 3 4 5 6 7 8 9 10 11 12 13
| static int tlc5615_probe(struct spi_device *spi); static int tlc5615_remove(struct spi_device *spi); static struct spi_driver tlc5615_driver = { .driver = { .owner = THIS_MODULE, .name = "tlc5615", .of_match_table = of_match_ptr(tlc5615_ids), }, .probe = tlc5615_probe, .remove = tlc5615_remove, .id_table = tlc5615_id }; module_spi_driver(tlc5615_driver);
|
字符设备
1 2 3 4 5 6 7 8 9 10 11 12
| #define TLC_DEVICE_NAME "tlc5615" static uint tlc_major = 0; static struct class *tlc_class = NULL;
int tlc5615_open(struct inode *inode, struct file *filp); ssize_t tlc5615_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops);
struct file_operations tlc5615_fops = { .owner = THIS_MODULE, .write = tlc5615_write, .open = tlc5615_open, };
|
数据结构体
1 2 3 4 5 6
| struct tlc5615 { struct mutex lock; struct spi_device *spi; struct cdev cdev; struct device* dev; };
|
实体
probe
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| struct spi_message msg = {0, }; struct spi_transfer xfer = {0, }; struct tlc5615 *dac; uint16_t tx_data = 0x0000; int err; int devno;
spi->mode = SPI_MODE_0; spi->max_speed_hz = 10000000; spi->bits_per_word = 8; err = spi_setup(spi); if(err<0) return err;
xfer.tx_nbits = 1; xfer.tx_buf = &tx_data; xfer.bits_per_word = 8; xfer.len = 2; xfer.speed_hz = 10000000; spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); if((err = spi_sync(spi, &msg))<0) return -EFAULT;
dac = devm_kzalloc(&spi->dev, sizeof(struct tlc5615), GFP_KERNEL); if(dac == NULL)return -ENOMEM; dac->spi = spi;
err = alloc_chrdev_region(&devno, 0, 1, TLC_DEVICE_NAME); if(err<0)return err; tlc_major = MAJOR(devno);
tlc_class = class_create(THIS_MODULE, TLC_DEVICE_NAME); if(IS_ERR(tlc_class)) return PTR_ERR(tlc_class);
cdev_init(&dac->cdev, &tlc5615_fops); dac->cdev.owner = THIS_MODULE; err = cdev_add(&dac->cdev, devno, 1); if(err){ class_destroy(tlc_class); return err; }
dac->dev = device_create(tlc_class, NULL, devno, NULL, "tlc5615"); if(IS_ERR(dac->dev)){ cdev_del(&dac->cdev); class_destroy(tlc_class); return PTR_ERR(dac->dev); }
spi_set_drvdata(spi, dac); return 0;
|
remove
probe申请了一个device, 一个cdev, 一个class和一个region.
设备数据中还有一个互斥锁.
1 2 3 4 5 6 7
| struct tlc5615 *dac = spi_get_drvdata(spi); device_del(dac->dev); cdev_del(&dac->cdev); class_destroy(tlc_class); unregister_chrdev_region(MKDEV(tlc_major, 0), 1); mutex_destroy(&dac->lock); return 0;
|
open
没什么要做的, 就是把设备数据放到file里面.
1 2
| filp->private_data = container_of(inode->i_cdev, struct tlc5615, cdev); return 0;
|
write
写数据.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| struct tlc5615 *dac = filp->private_data; struct spi_transfer xfer = {0, }; struct spi_message msg = {0, }; uint16_t* buffer; int i;
if(count % 2)return -EINVAL;
buffer = kmalloc(count, GFP_KERNEL); if(buffer == NULL)return -ENOMEM;
if(copy_from_user(buffer, buf, count)!=0){ kfree(buffer); return -EFAULT; }
for(i = 0; i < (count << 1); i++){ buffer[i] = __builtin_bswap16((buffer[i] & 0x3ff) << 2); }
xfer.tx_buf = buffer; xfer.len = count; xfer.bits_per_word = 8; xfer.speed_hz = 10000000; spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); i = spi_sync(dac->spi, &msg);
kfree(buffer); if(i < 0)return i; return count;
|
Debug
算是一点额外的内容.
因为最开始没有初始化spi_transfer结构体, 里面的delay完全就是随机数. 在传输检查的时候delay的mode找不到返了一个-EINVAL回来. 没上调试器之前完全不知道是哪里返回来的. (上了调试器也有够麻烦
V3s的JTAG和SDC0只能二选一(显然用SD卡启动的没得选), 只能使用KDB和KGDB进行调试.
准备带kgdb/kdb的内核
到Linux内核进行menuconfig, 搜索kdb以后打开就行了. 另外一个FRAME_POINTER看起来ARM是没法用的. 另外还有一个调试符号也需要设为y. 重新编译以后把zImage扔回第一分区.
设备树
调试的时候WIFI不稳定模块时不时往串口打印一堆奇怪东西, 没办法再开了一个串口. V3s还好把有一个UART2没人用, 抄UART0的配置再在别名里面加一个serial1=&uart2, 把kgdboc调到ttyS1,115200做调试口. UART0上次扔到i2c1去了这回还需要禁用i2c1并重新启用uart0. alias也要加回去.
重编译dtbs扔回第一分区.
启动参数
修改boot.cmd在setenv那里加一个kgdboc=ttyS1,115200, 用mkimage重新打包boot.scr扔回第一分区.(到这里mmcblk0p1已经没有一个文件是没换过的了.
调试准备
pacman -S gdb-multiarch, 这个包是archlinuxcn里面的, 需要添加archlinuxcn的源.
gdb-multiarch vmlinux按照提示准备好.gdbinit, 还可以在.gdbinit里面加一条set serial baud 115200.
使用Windows的话还是好好用COM转TCP, 自己找了一个好用的软件commfront Serial-TCP, 非商用免费.
进入调试
开机以后直接echo g > /proc/sysrq-trigger, 宿主机gdb vmlinux以后载入远程调试目标target remote /dev/ttyUSB0, 用telnet的把/dev/ttyUSB0换成对应ip:port就行.
这种方法还有一个好处就是可以在linux虚拟机下面执行调试不需要把COM分配到虚拟机.
远程调试载入完了执行lx-symbols自动载入模块符号. 不使用lx-symbols要一个个从/proc/modules里面把base address找出来, 完了还要扣ko里面的段地址一个个添加到gdb有够麻烦的.
开始调试
抓probe函数需要在载入模块前添加断点, lx-symbols以后允许预注册断点, gdb命令b tlc5615_probe可以在符号载入后产生断点. 调write函数可以在载入以后再添加断点. 放置断点后c继续运行.
载入完了等进入断点就跟平常使用gdb一摸一样了. 至于怎么触发断点也蛮麻烦的. 只有一个串口可以与目标及通信的目前没发现什么好办法. 像我这种不仅开俩串口还有WIFI和EMAC的倒是可以继续走串口或者ssh载入模块. 注册一个IO口挂shell做while(1)循环检测也可以. (要是没有其他的通信方法还不给jtag那不调了. 老老实实pr_info.
实际上kgdb还是有点不稳定的. 能用jtag还是jtag好. 调试过程中跑飞,, 段错误, 取不到值经常有出现. 前一句还被optimized out后一句就取到值了. 前一句i=-1152921504606后一句i<0就为假. 传个东西突然就没了, 只能断电再来. 顺便这个时候中个中断串口打点奇怪的东西调试直接结束. (pr_info走串口的也算.
调半天找到问题是没有初始化结构体变量导致delay这一条检查过不去. 初始化以后就没问题了. (为什么我看人家都不带初始化的淦
时序测试
python没办法给一个文件写16位数据, 所以用nodejs的writeUInt16LE函数好一点.
放while(1)里面给劲传. 逻辑分析仪开400M采样采. (实际10MHz用10M采样率完全变形, 用25M采有一段不对, 气到开400M采(实际50M够了
调到大致和目标一样了以后拆掉逻辑分析仪上DAC.
在0x00f0这个值的时候, 输入Vref=3.3V, 输出电压0.77V, 刚好发光二极管有点亮. 万用表测到0.763V. (实际上不可以这么玩. 输入Vin=5V, Vref_max=Vin-2V=3V, 没炸掉算我运气好. 找不到电阻还不想用2.5V LDO的情况下就随便拿3.3V来玩玩.