新年将至,一个漂亮的雪花灯可以增添新的气氛,带来冬日的氛围。本次分享将以这个基于STC公司的51单片机控制的雪花灯为例,讲一讲如何零基础入门嵌入式,搭建51单片机开发环境,新建工程并点亮这个雪花灯。
开发环境的搭建
安装Keil
首先安装Keil软件,一个广泛使用的嵌入式开发工具,其中整合了代码编辑器,C51编译器及调试器为一体。下载该软件时,注意选择C51版本。不同的版本包含了不同的交叉编译工具,比如ARM版可以用来编译ARM架构的一些芯片,比如STM32,而我们的51单片机需要用C51编译器来编译。官网下载地址如下:
https://www.keil.com/download/product/
安装完成后,需要购买软件的许可或通过其它的手段来激活软件,否则试用版只能编译较小的程序。
下载STC-ISP下载编程烧录软件
说是烧录软件,实际上说它是STC单片机的万能工具包也不为过。下面的很多步骤都会用到它。下载地址在STC公司它魔性的官网上:
打开程序,可以看到从芯片选型,头文件,程序样例,到下载工具,各种常用功能整合到一起,给开发者节省了很多资料的时间。
向Keil中导入STC芯片的型号库和头文件库
为了能使用keil新建一个STC单片机的工程,需要选择单片机的型号来新建。而Keil软件内自带的芯片中并没有STC芯片的型号库。下面的操作将同时导入STC所有单片机芯片的型号库和头文件库,方便从keil新建工程。
打开STC-ISP软件,找到“Keil仿真设置”一栏,点击图中的按钮并根据提示选择Keil对应的安装路径即可。
新建工程
从原理图可知,我们的单片机具体型号为STC15W204S。它只有8个引脚,256字节的内存,不过控制我们的雪花灯还是基本够用。在Keil的新建工程(New Project)对话框中,选择我们的芯片型号。
注意在选择芯片前,需要先在下拉框中切换到STC MCU Database。之后去找具体的芯片型号即可。
选择好后,Keil会询问是否要将STARTUP.A51这个启动文件添加到工程中,回答是即可。
此时我们的工程已经新建完成,并且包含项目的第一个代码文件,也就是51单片机通用的启动文件STARTUP.A51。这是一个汇编代码,当程序编译时工程中所有的C和汇编代码都会被编译为机器指令。
添加代码文件
在Keil左侧的项目管理器中添加代码文件。在这里可以新建代码分组,在每个分组上右击可以选择新建文件,双击可以添加源代码文件。此处仅添加源文件即可,添加到此处的源代码才会被编译到生成文件中。头文件无需在此添加。
根据项目的需要,我们可以添加以下三个源文件以及对应的头文件。新建的文件为空文件,接下来需要我们来编写内容。
main.c
delay.c/delay.h
ws2812.c/ws2812.h
实现WS2812 LED驱动芯片发送功能
RGB颜色表示方法
雪花板上面的LED灯可以发出的颜色,可以用三个参数来表示:R, G, B,范围为0-255。分别对应WS2812模块中内置的红色、绿色、蓝色三颗发光二极管的亮度。红,绿,蓝为显示的三原色,将三种颜色的光的亮度按照不同的大小叠加起来,就可以得到五彩斑斓的颜色。例如(255,255,255)为白色,(100,100,100)就是暗一些的白色,(255,255,0)则为橙色,因为它是红色和绿色的叠加。
WS2812的单总线数据传输协议
每个WS2812上面有红绿蓝三种颜色的LED灯,需要按照绿-红-蓝的顺序接受到24字节的数据才能正常工作。其中每个颜色的数据长度为8位,因此数据范围为0-255.
WS2812的妙处在于,用单片机向WS2812发送数据,只需要一根线,而不像SPI等协议需要很多根线。而且当有多个WS2812时,不需要从单片机向所有的模块都连一根线,而是可以将前一个WS2812模块的发送端连接到下一个的接收端,这样串起来连接。
https://cdn-shop.adafruit.com/datasheets/WS2812.pdf
通过查阅WS2812的数据手册,可以了解到该模块的通信方式。该芯片可以自动转发,也就是说从单片机可以一次发送多个灯的数据,第一个灯可以吃掉前24位的数据,并将剩余的数据发送给后面的灯。对于后面的灯,它们同样是吃掉它们得到的数据的前24位,用于控制自己的亮度,之后把后面的数据丢到后面的灯。按照这样的协议,单片机可以按照灯的总线连接顺序一次按顺序发送所有WS2812的数据。
数据手册还告诉我们,对于每24bit的数据,绿色在前,接着是红色,蓝色。对于每个颜色,先发高位,后发低位。
那么如何发送每一位的数据是1还是0呢?查阅数据手册,总线上的电平每跳变一次,表示发送了一位数据。而按照规定的时间保持高电平和低电平,就可以表示要发送的是0还是1。保持较长时间的低电平,表示发送的是RESET Code。
实现WS2812发送一位数据
回到我们的STC15W单片机。这个单片机的速率并不快,内部时钟最高也通常只能达到33MHz左右。如何用这个单片机实现数百微秒的延时呢?__nop_()语句可以实现让单片机的CPU执行一个空指令,什么也不做但是可以延时一个时钟周期。通过使用逻辑分析仪,我看可以测试出大约多少个nop()可以达到数据手册规定的延时时长。
我们把单片机的内部时钟调节为33MHz。之后可以编写这样的代码,来实现WS2812发送低位或发送高位。
#define WS2812_Send_Bit_High {\
DI=1;\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
DI=0;\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
}
#define WS2812_Send_Bit_Low {\
DI=1;\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
DI=0;\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
_nop_();\
}
实现WS2812发送所有数据
下面的函数用来发送多个字节的WS2812数据,例如发送三个字节可以控制第一个WS2812模块的颜色,发送n*3个字节则可以控制单片机后面的n个灯。
buf为要发送的数据的指针,byte_length为数据长度,单位为字节。
void WS2812_Send(uint8_t *buf, uint8_t byte_length){
uint8_t i, j;
for(i = 0; i < byte_length; ++i){
for(j = 0; j < 8; ++j){
if(buf[i] & (0x80 >> j)){
WS2812_Send_Bit_High;
} else{
WS2812_Send_Bit_Low;
}
}
}
}
编写一个测试程序
接下来,将使用上面写好的WS2812发送功能,来编写主函数main.c文件。
LED待发送数据结构
定义下面的联合体,使其既可以按照要发送的数据的顺序存储,又可以按照每个灯的r,g,b颜色的方式来访问。
union{
uint8_t buf[LED_NUM*3];
struct{
uint8_t g, r, b;
}u[LED_NUM];
}LED;
延时函数
粗略的延时可以通过固定次数的循环来实现。STC-ISP中的软件延时计算器可以为我们生成一个粗略的延时函数:
完整的main.c
最后,可以编写如下代码来测试我们的程序是否正常工作。雪花灯将像封面效果一样点亮,并一秒钟交换一次颜色。
#include "ws2812.h"
#include "delay.h"
union{
uint8_t buf[LED_NUM*3];
struct{
uint8_t g, r, b;
}u[LED_NUM];
}LED;
void main()
{
LED.u[36].r = LED.u[36].g = LED.u[36].b = 30;
while(1){
LED.u[15].r = LED.u[16].g = LED.u[17].b = 0;
LED.u[16].r = LED.u[17].g = LED.u[15].b = 60;
WS2812_Send(LED.buf, LED_NUM*3);
Delay100Ms(10);
LED.u[16].r = LED.u[17].g = LED.u[15].b = 0;
LED.u[15].r = LED.u[16].g = LED.u[17].b = 60;
WS2812_Send(LED.buf, LED_NUM*3);
Delay100Ms(10);
}
}