单片机论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2711|回复: 0
收起左侧

KT0810SG调试过程与程序(FM收音芯片)

[复制链接]
liuyuxi 发表于 2015-1-10 20:08 | 显示全部楼层 |阅读模式
全志A20的板子,记录下来,方便自己随时复习。
自己按照以前熟悉的驱动框架模型来写,发现测试时 echo 1 > /dev/KT0810SG  write()函数会一直被调用。

测试I2C读写时,发现读芯片ID的数值总是 0xFF 经过一上午的折腾,确定是硬件问题,I2C的上拉电压只有2.5V,改成3V 成功读取ID值 == 0xb002

下午的时候,在执行初始化 KT0810SG 函数时,需要检测晶振和System PLL 时钟是否准备好,但总是发现 System PLL 未准备好,

原因为 A20的提供的 32.768KHz 时钟没有送到 KT0810SG 芯片里,是硬件焊接错误。 已经解决。

现在 遇到的问题是,芯片没有声音输出。
昨晚回去之后怀疑I2C写寄存器 的函数有问题,没有把数据写进去。

早上调试了很多次,发现有一个奇怪的现象,那就是写的时候 高低字节不需要调换,但是读取出来的时候需要调换高低字节。
中午,设置了8980频率,居然有声音了。后来发现有些频率是没有声音的,所以最好多试试几个频率。
下午遇到的问题是,收到的台数很少且噪音很大。怀疑是硬件问题,因为目前只是调试芯片,所以芯片都是直接在板子上飞线到芯片上的。干扰性会很大。

总结:
        完全可以按照自己以前的驱动框架写fops方式:
        一开始 write()会一直被调用可能是因为 echo命令会判断 如果写入失败就会一直写,而write()刚好被我写成返回0。
        1、先拿到原厂代码,最好要原厂提供完整的 demo 源码工程。
        2、大概了解原厂代码,分析 demo 源码的执行流程以及各个函数的功能
        3、修改sys_config.fex 启用使能I2C通道2
        4、编写I2C驱动框架,修改 原厂代码的I2C读写函数(移植过程)
        5、在驱动中根据 demo 源码分析出来的流程去调用原厂代码 若不知道流程,最好询问原厂。
        6、预留接口给上层。如设置收音频率等 可以用 echo 123 >设备节点文件 来调试
       
        问题点:
                1、KT0810SG 是采用I2C协议通讯,在测试I2C读写时,发现读取芯片ID总是0xFF,读取其他的寄存器的值也不对。
                        硬件问题:I2C的SDA、SCL线电压只有2.5V,需要提升至3V,MCU的高电平在3.3V左右
                       
                2、KT0810SG 在 KT_FMInit(void) 初始化总是失败。
                        uchar KT_FMInit(void)                                        //0->Fail 1->Success
                        {
                                ...
                                ...
                                for (i=0;i<INIT_FAIL_TH;i++)
                                {
                                        Delay_ms(500);
                                        regx=KT_Bus_Read(0x12);                                               
                                        if ((regx&0x8800)!=0x8800)                // 查看芯片手册,这里检测的是晶振和System PLL 是否准备好
                                                continue;
                                        break;
                                }
                                if (i==INIT_FAIL_TH)
                                        return(0);
                                ...
                                ...
                        }
                        KT0810SG 的时钟是由 A20 提供,32.768KHz 。
                        硬件问题:时钟线接错位置,导致 A20 提供的时钟没法送到 KT0810SG 。
                        检测方法:将时钟线断开,示波器去测 A20 引脚看是否有频率,现场是有的,但是接回芯片频率就不正常。
                       
                3、根据原厂流程以及写好驱动,但是没有杂音输出。
                        可能的原因:I2C 写寄存器不成功,导致初始化失败。 还有一个可能性就是 设置的频率刚刚好没声音输出,可以多设置几个频率试试。
                       
        注意点:
                在修改原厂驱动的I2C读写函数时,因为 KT0810SG芯片的寄存器是16位 ,I2C读函数读出的数据是两个字节
                需要将两个字节的数据调换即高字节和低字节互换 0x1234 => 0x3412 而I2C写函数则不需要交换
       
修改增加裸板程序的I2C 读写函数:(奇怪的是读的时候高八位和低八位互换才能用。而写的时候则不需要调换)
        static unsigned char i2c_write_reg(unsigned char reg,unsigned short data)                // 可用
        {
                unsigned char buf[3] = {0};
                unsigned short a, b;
                printk("%s - %s  reg = 0x%X  data = 0x%X o(∩_∩)o~~!\n", FMMSG, __func__, reg, data);
                /*
                // 高低八位互换
                buf[0]        = reg;
                buf[1] = ((data << 8) & 0xFF00) >> 8;        // 因为data是两个字节 而buf[] 是一个字节
                buf[2] = (data >> 8) & 0x00FF;
                */
                // 高低不换  这里不能对换 否则无法收到台。
                buf[0]        = reg;                                                        // 寄存器
                buf[2] = ((data << 8) & 0xFF00) >> 8;
                buf[1] = (data >> 8) & 0x00FF;
                printk("+++write true buf[0]=0x%X buf[1]=0x%X buf[2]=0x%X\n", buf[0], buf[1], buf[2]);

                i2c_master_send(FM_dev->FM_client, buf, sizeof(buf));
               
                return 0;
        }       
        static unsigned int i2c_read_reg(int reg)                        // 可用
        {
                int ret;
                unsigned short  data = 0, a = 0, b = 0;

                struct i2c_msg msgs[] = {
                        {
                                .addr        = FM_dev->FM_client->addr,
                                .flags        = 0,
                                .len        = 1,                                                        // 1个字节
                                .buf        = &reg,                                                        // 寄存器
                        },
                        {
                                .addr        = FM_dev->FM_client->addr,
                                .flags         = I2C_M_RD,
                                .len        = 2,                                                        // 两个字节的空间
                                .buf        = &data,                                                // 用于存放读取出来的数据
                        }
                };
               
                ret = i2c_transfer(FM_dev->FM_client->adapter, msgs, 2);
                if(ret < 0)
                        printk("read error~~~~~~~~~~~~~~~");
                else
                {
                        printk("read ok reg = 0x%x data = 0x%x!!!\n", reg,  data);
                        a = (data << 8) & 0xFF00;                                        // 有些奇怪,需要互换高低八位
                        b = (data >> 8) & 0x00FF;
                        data = a | b;  
                        printk("read ok reg = 0x%x data = %d | %d = 0x%x!!!\n", reg,  a, b , data);
                }
                return data;
        }
       
       
       
意外收获:
        FM5807驱动流程分析(这种方式并没有实现fops结构体来给上层提供接口,有些地方不是很明白,也算是一种有趣的实现方式):
        原来的驱动写的有些乱,我简化了一些。
       
        #define FM_CHRDEV_NAME "KT0810SG"
        module_init(FM_init);                        // 定义驱动入口方式
        module_exit(FM_exit);                        // 定义驱动出口方式
       
        //函数入口
        static int __init FM_init(void)
        {
                // 读取配置文件
                // 保存从配置文件里面选择的I2C通道
                // 设置输出32.768KHz时钟
                twi_id = 2;                                // 会用在FM_detect()
                // 加载I2C驱动
                i2c_add_driver(&FM_drv);
                return 0;
        }
        // 全局变量
        static __u32 twi_id = 0;
       
        // 定义FM I2C驱动结构体
        struct i2c_driver FM_drv = {
                .class = I2C_CLASS_HWMON,                // 不能忽略,表示去哪些适配器上找设备
                .detect =         FM_detect,                // 该函数确定能否找到address_list里的设备
                .probe =        FM_probe,                // 如果匹配就调用probe
                .remove =        __devexit_p(FM_remove),
                .driver = {
                        .name = "FM_drivce",
                        .owner = THIS_MODULE,
                },
                .id_table = FM_id,                        //支持的设备列表
                .address_list = normal_i2c,                // I2C设备地址列表
        };
        // FM芯片的设备地址
        static const unsigned short normal_i2c[2] = {0x37, I2C_CLIENT_END};
       
        // 实现FM_detect()
        static int FM_detect(struct i2c_client *client, struct i2c_board_info *info)
        {
                struct i2c_adapter *adapter = client->adapter;
                int ret = 0, i =0;
               
                printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
                // 检查功能
                if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
                        return -ENODEV;               
                       
                if(twi_id != adapter->nr)        // 匹配FM芯片所在的I2C通道 匹配成功才能回调Proc()
                        return -ENODEV;
                else
                        strlcpy(info->type, FM_CHRDEV_NAME, I2C_NAME_SIZE);// 若匹配则拷贝名字 成功匹配会调用probe()函数
                        return 0;
        }       
       
        // 实现FM_probe()
        int FM_probe(struct i2c_client *client, const struct i2c_device_id *device_id)
        {
                int ret;

                if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
                {
                        printk("%s_check_functionality_failed!\n", __func__);
                        return -ENODEV;
                }
                       
                FM_dev = kmalloc(sizeof(struct FM_device*), GFP_KERNEL);
                if( NULL == FM_dev)
                {
                        printk("%s - %s no memory for kmalloc!\n", FMMSG, __func__);
                        return -ENOMEM;
                }
               
                // 获取Client 保存到全局变量中
                FM_dev->FM_client = client;
                FM_dev->FM_client->driver = &FM_drv;
               
                class_register(&FM_attrs_class);      // 重点,这里会注册在/sys/class/设备名 具体看 FM_attrs_class
                       
                INIT_DELAYED_WORK(&open_pa, open_pa_func);        // 这里是工作队列 初始化
                __cancel_delayed_work(&open_pa.work);                // 取消调度
                schedule_delayed_work(&open_pa.work, 100);        // 100ms后调用 open_pa_func()

                return 0;
        }
       
        struct delayed_work open_pa;
        // 定义
        struct FM_device{
                struct i2c_client *FM_client;
                struct device *FM_device;
                struct class *FM_class;
        };

        struct FM_device *FM_dev;

        // echo 10 >/sys/class/kt0810sg/cmd                命令会将参数传递到  FM_cmd_store()
        // 这里不太明白 FM_status_sho()、FM_cmd_show() 作用
        static struct class_attribute        FM_attrs[] = {
                __ATTR(status, 0777, FM_status_show, FM_status_store),           // 构成 /sys/class/kt0810sg/status 节点
                __ATTR(cmd, 0777, FM_cmd_show, FM_cmd_store),                // 构成        /sys/class/kt0810sg/cmd        节点
                __ATTR_NULL
        };
        static struct class FM_attrs_class = {
                .name = "kt0810sg",                                        // 构成/sys/class/kt0810sg 目录
                .class_attrs = FM_attrs,
        };
       
        // 工作队列
        static void open_pa_func(struct work_struct *work)
        {
                printk("-------------------open pa \n");
                // 这里用于关pa  于框架没有实际意义。
        }
       
        // 实现上面需的回调函数 echo 10 >/sys/class/kt0810sg/cmd 会传进来
        static ssize_t  FM_cmd_store(struct device *dev, struct device_attribute *attr, const char *buf, ssize_t count)
        {
                unsigned long data = 0;
                int command = 0;
               
                printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
                strict_strtoul(buf, 10,  &data);
                command = data;
                printk("%s_Revc = %d\n", __func__, command);
               
                // 在 0-15 之间则表示直接设定音量
                if(command >= FM_CMD_SETVOLUMEMIN && command <= FM_CMD_SETVOLUMEMAX )
                {
                        printk("Cmd is Set Volume %d \n", command);
                        KT_FMVolumeSet(command);
                        goto retu;
                }
               
                // 在 8700-10800 之间则表示直接设定频率
                if(command >= FM_CMD_FREQMIN && command <= FM_CMD_FREQMAX )
                {
                        printk("Cmd is Set Freq %d \n", command);
                        KT_User_FMTune(command);
                        KT_FMUnMute();
                        goto retu;
                }

                // 剩余的表示各种命令
                switch(command)
                {
                        case FM_CMD_AUTOSCAN:                                                                // 自动搜台
                                printk("cmd = FM_CMD_AUTOSCAN ---> OK\n");
                                ScanFM();
                                break;
                        case FM_CMD_SELECTUP:                                                                // 上一个台
                                printk("cmd = FM_CMD_SELECTUP ---> OK\n");
                                break;
                        case FM_CMD_SELECTDOWN:                                                                // 下一个台
                                printk("cmd = FM_CMD_SELECTDOWN ---> OK\n");
                                break;
                }
        retu:
                return count; // 返回值必须指定成功接收了多少个字节,若返回0 那么此函数会一直被回调
        }

        static ssize_t FM_cmd_show(struct device *dev, struct device_attribute *attr, char *buf)
        {
                printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
                return 1;
        }

        static ssize_t FM_status_store(struct device *dev, struct device_attribute *attr, const char *buf, ssize_t count)
        {
                printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
                return count;
        }

        static ssize_t FM_status_show(struct device *dev, struct device_attribute *attr, char *buf)
        {
                printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
                return 1;
        }
        // 实现 退出函数
        int FM_remove(struct i2c_client *client)
        {
                printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
                return 0;
        }
        // 这很关键,是否能重复加载 该ko文件取决于它。
        static void __exit FM_exit(void)
        {
                printk("%s - %s Test11 o(∩_∩)o~~~ !\n", FMMSG, __func__);

                __cancel_delayed_work(&open_pa.work);
                class_unregister(&FM_attrs_class);// 必须卸载  会删除 /sys/class/kt0810sg 目录 不然下次加载就无法创建
                i2c_del_driver(&FM_drv);                // 删除I2C驱动
                kfree(FM_dev);                                // 释放。                               
        }
               




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|单片机论坛 |51黑电子论坛技术交流 QQ 管理员QQ:125739409;技术交流QQ群636986012

Powered by 单片机教程网

快速回复 返回顶部 返回列表