• 从零开始 DIY 智能家居 - AC791N通过单线SPI驱动WS2812



    前言

    今天被疫情管控在家,因为太过突然,完全没准备,也没办法远程办公访问,正好闲下来一天整理一下前段时间弄的一个小玩意。
    一个智能灯带,这玩意看起来简单,但是坑蛮多的,特别是我选择了一个比较少见的模块。那坑就更多了。
    在这里插入图片描述
    因为这次是被管控在家,我设备和代码啥都没有,只能通过之前的文档和笔记给大家分享一下这次开发遇到的坑,所以没办法像之前那样直接将代码分享给大家,但是我还是会尽量讲清楚WS2812要如何使用的。


    一、硬件选择

    首先,就是翼辉的 边缘计算机 Spirit 1 边缘计算机,这玩意给开发者用确实好使,我已经习惯用这个玩意协助开发调试设备了。

    至于开发板,这次终于不是祖传的ESP32了,是杰里科技的AC791N开发板!当然,本穷逼只买得起最便宜的单独的开发板模块,这玩意单板子就特么100块啊!

    灯带我选择的是WS2812B一种很常见,资料很多的灯带,但是实际上网上的资料很多都不对。我查着资料用示波器抓了半天波形才搞定。

    因为设备不在家,这里没办法给大家配图了QWQ

    驱动WS2812一般就三种方法
    直接控制IO口拉高拉低来产生波形,但是WS2812对电平翻转速度要求有点高,很难达到这么高的速度,而且直接这么控制误差比较大,所以我没采用。

    我在网上看见用得比较多的是 PWM+DMA的方式,然后蛋疼的地方来了,AC791N开发板开发板的资料比较少,只有官方的一个文档,官方文档只简单的说了一下PWM的使用,有很多的功能和宏并没有介绍,我折腾了半天,实在是调不通,去找他们的官方支持才知道,他们的PWM是直连的,没有DMA。

    最后在官方人员的帮助下,我才终于成功使用SPI单线模式+DMA的方式驱动WS2812,官方文档上没有SPI单线模式的介绍,就很蛋疼

    接线:
    5V->5V
    PH1(SPI DO)->DO
    GND->GND

    二、代码讲解

    1.SPI配置

    因为这次代码不在身边,我只能找我笔记里的关键部分来讲了

    采用SPI单线模式+DMA,主要是SPI_UNIDIR_MODE这个宏将SPI配置为单线模式
    使用SPI2 portA
    SPI速率为8M
    输出引脚为 :IO_PORTH_01

    SPI配置代码如下(示例):
    在这里插入图片描述
    port A 的引脚定义
    在这里插入图片描述

    /************************** SPI config ****************************/
    SPI2_PLATFORM_DATA_BEGIN(spi2_data)
        .clk    = 8000000,
        .mode   = SPI_1WIRE_MODE,
        .port   = 'A',
        .attr   = SPI_SCLK_L_UPH_SMPH | SPI_UNIDIR_MODE,
    SPI2_PLATFORM_DATA_END()
    
    注册SPI设备:
    REGISTER_DEVICES(device_table) = {
    	{"uart2", &uart_dev_ops, (void *)&uart2_data },
    	{"rtc", &rtc_dev_ops, NULL},
    	{"spi2", &spi_dev_ops, (void *)&spi2_data },
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.灯带代码

    WS2812的逻辑0和逻辑1的定义
    代码如下(示例):

    //需要设定灯带长度和ws2812的逻辑0和逻辑1
    #define LED_NUM  100	                // LED灯珠个数
    
    #define EIGHTBIT_0CODE	0xc0            // 逻辑0
    #define EIGHTBIT_1CODE	0xf8            // 逻辑1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这是将数据结构中传入的颜色和亮度数组转换成SPI发送数据的函数

    
    /**
     * 将传入数据转换成ws2812缓存
     * 	8个SPI bit 表示一个ws2812bit,要求SPI发送速率为8Mhz,ws2812信号频率为1M
     * 	经实测,还是8bit/1M 的模式比较准确,灯带不会误识别造成乱码,SPI为6.4M时会出现乱码
     *  */
    int convert2ws2812(struct frame_buf* fbuf, uint8_t *ws_buf, uint16_t buf_len)
    {
    	union ws2812_pixel pcolor;
    	uint8_t *subpixel = NULL;
        ws_buf[0] = 0;
        for (uint16_t pos = 0; pos < LED_NUM; pos++) {
            // 处理当前像素点颜色
    		pcolor.color.r = fbuf->color.r * fbuf->pixel_brightness[pos] / UINT8_MAX;
    		pcolor.color.g = fbuf->color.g * fbuf->pixel_brightness[pos] / UINT8_MAX;
    		pcolor.color.b = fbuf->color.b * fbuf->pixel_brightness[pos] / UINT8_MAX;
    		// 转换每个颜色通道
    		memset(ws_buf + pos * 24, 0, 24);
    		for(uint16_t i = 0; i < 3; i++) {
    			subpixel = ws_buf + pos * 24 + i * 8 + 0;
    			subpixel[0] |= ((pcolor.data[i] & 0x80) ? EIGHTBIT_1CODE : EIGHTBIT_0CODE);
    			subpixel[1] |= ((pcolor.data[i] & 0x40) ? EIGHTBIT_1CODE : EIGHTBIT_0CODE);
    			subpixel[2] |= ((pcolor.data[i] & 0x20) ? EIGHTBIT_1CODE : EIGHTBIT_0CODE);
    			subpixel[3] |= ((pcolor.data[i] & 0x10) ? EIGHTBIT_1CODE : EIGHTBIT_0CODE);
    			subpixel[4] |= ((pcolor.data[i] & 0x08) ? EIGHTBIT_1CODE : EIGHTBIT_0CODE);
    			subpixel[5] |= ((pcolor.data[i] & 0x04) ? EIGHTBIT_1CODE : EIGHTBIT_0CODE);
    			subpixel[6] |= ((pcolor.data[i] & 0x02) ? EIGHTBIT_1CODE : EIGHTBIT_0CODE);
    			subpixel[7] |= ((pcolor.data[i] & 0x01) ? EIGHTBIT_1CODE : EIGHTBIT_0CODE);
    		}
    	}
    	return 0;
    }
    
    
    • 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

    而在主业务循环函数中有个需要注意的是SPI do 端口默认为高电平,我尝试在发送前将GPIO端口拉低也没用,这会导致数据的第一个信号被吞掉,所以要在所有数据前填充一段0数据将电平拉低


    总结

    这次东西,不难,主要是的坑有点多:
    1、AC97的PWM是不支持DMA的,以及SPI的单线模式。
    2、WS2812并不需像网上资料写的理论上将SPI 的CLK设置为6.4M,亲测,6.4M情况下会有错乱的情况。8.0M才能稳定。
    3、SPI默认高电平会吃掉第一个数据,导致会有一个灯颜色异常,所有要手动在所有发送前往数据前塞0,拉低电平,通过GPIO控制函数拉低没用,我抓过波形看过了。
    4、还是AC97的问题,因为我这是两线程在跑,一个线程接收数据,一个线程发送数据,发送线程不知道为什么在有的情况下会调度得慢,几秒甚至几十秒才跑一次,我已经把发送线程的优先级拉到很高了,理论上他调度的机会应该比接收线程多,但是接收线程一切正常,发送线程响应就很慢,而我什么都没改,有时候重新上电就正常了,有时候重新烧录一下代码就好,什么都没改。

  • 相关阅读:
    算法通关村-----寻找祖先问题
    3DEXPERIENCE® DRAFTSIGHT® 新功能
    等保三级密码复杂度是多少?多久更换一次?
    springboot医美容院预约管理系统java ssm
    android 编译make指令
    GoLang协程与通道---下
    653. 两数之和 IV - 输入二叉搜索树
    docker全家桶(基本命令、dockerhub、docker-compose)
    Gin路由高级
    谈谈 MySQL 事务隔离级别
  • 原文地址:https://blog.csdn.net/lixiaocheng1983/article/details/127977898