欢迎来到SFWR的博客

招新题流程简介(WS2812)

22物电科协软件招新题学习流程

有错误或者不当的地方请在评论区指出😘

题目简介

使用stm32驱动单一ws2812b灯珠实现呼吸灯效果,驱动及实现方法不限

演示效果

image

快速入门,在stm32核心板上点灯

单片机介绍

采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统
入门阶段可以粗略的将其认为一个可以控制电路的小计算机
招新题所用的单片机型号为stm32f103c6t6,开发语言为c语言

前置软件

  • 组合一:stm32cubeide
  • 组合二:stm32cubemx+keil5
  • 可选软件:flymcu

软件介绍

  • 32单片机程序编写分为配置和写程序,组合二中配置过程我们选择stm32cubemx,这是一个可以图形化配置软件,比较直观,不需要自己写配置代码,软件会根据你的图形配置自己生成,写程序采用keil5,这是一个面向单片机C语言软件开发系统,集成了完善的开发环境,使用时需要破解。
  • 组合一中的cubeide则集成了两个功能,具有图形化配置界面,配置生成的代码可以直接开始编写,较为方便,缺点是使用非官方的stlink烧写程序较为麻烦,下面的教程的环境均为stm32cubeide。
  • flymcu为一款串口烧录软件,如果同学们选择串口烧录则可以选择下载这个软件。
  • cubeide官网 https://www.st.com/zh/development-tools/stm32cubeide.html
    cubemx官网 https://www.st.com/en/development-tools/stm32cubemx.html
    flymcu与keil5群文件

程序烧录

这里介绍的是编写好程序后的烧写方式,可以先学习完程序的编写再学习如何烧写
烧写/烧录是指将我们在电脑上写的程序写入我们的单片机,从而让单片机可以执行我们的程序

烧写工具

  • USB转TTL
    image
  • 正版stlink
    image
  • 盗版stlink
    image
    淘宝购买即可

烧写方法

  • 使用正版stlink下载,写完程序并连接好单片机后点击run即可
  • 使用盗版stlink下载(在keil5上可以直接使用(应该),在cubeide上如果点击run后显示需要更新固件,要先把stlink与单片机断开,然后open upgrade mode 进行更新,如果报错就refresh一下再次更新,直到下方出现进度条开始更新,更新完毕即可,然后再连接单片机,此时就可以正常下载程序了)
  • 使用串口下载,首先确定代码生成的hex文件,cubeide可以点击工程属性>c/c++ build>setting>MCU Post build outputs>打钩生成hex,然后单片机需要接usb转TTL,RX接PA9,TX接PA10,5V,gnd分别与单片机5v,gnd烧录前将boot0接为1,然后使用flymcu下载,flymcu下方编程到flash时写入选项字节取消打钩,然后开始编程,烧录成功后将boot0改为0即可

开始点灯

配置程序

这一部分网上也有很多教程,这里提供的是我的配置过程,更详细的也可以参考其他教程,这里提供一个博客

  1. 打开软件,建立一个新的工程
    选择Flie》new》stm32 project (qq截图不了这个地方,自己找一下这几个的位置)
  2. 选择我们的单片机型号
    image
    然后点击next
  3. 填入我们工程的名字
    建议用英文,防止可能出现的路径错误,工程地址可以自己修改,也可以使用默认
    image
    然后直接点击finish即可
  4. 配置图形化页面
    image
    这个就是我们的单片机芯片了,是一个很直观的图形页面,可以自由拖动和配置,我们首先来配置单片机的时钟
    image
    将RCC配置为如图所示,这里是使用的外部晶振,更加稳定
    image
    将SYS配置为如图所示,便于串口调试
    下面来配置IO口
    image
    将PC13 设置为GPIO_Output,即输出电平,这里就设计到我们点灯的原理了
点灯原理

image
在我们单片机上的led连接方法是这样的,我们知道二极管具有单向导通性,而led也就是发光二极管也具有这样的性质,因此,当led2为0v也就是接地时,led导通,灯亮,当led2为3.3V时,led两端电压一样,不会导通,灯灭。而我们的GPIO_output正是可以控制输出电压,控制的方法就是控制高低电平,我们已经学过的C/C++,二进制是由0,1构成,而在32单片机中,0就代表0v,1就代表3.3V(有误差),当我们把这个引脚设置为0时,led便会亮起。
5. 生成代码
直接点击保存或者ctrl+s即可,弹出窗口点解generation code
image
这里便是我们的主程序了
下面我们来尝试按照我们上面的讲解点亮这个led
6. 点灯
在main内找到while(1)循环,在前面写上
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET)
这个函数使将我们的GPIO设置为某一电平,前两个参数较容易理解,我们选的的是PC13,所以就是C13,第三个参数其实就是低电平
image

注意,程序一定要写在注释的begin与end之间,在这之外的都会被再次配置后的代码覆盖
下面我们烧写程序观察我们的核心板。
烧写方法在上面
image
可以看到灯已经亮起
7. 让灯闪烁起来
在我们的主程序中,有一个while(1){},我们学习c语言时,都是不会出现死循环的,但是在单片机中死循环是一个非常正常的事情,我们为了让程序一直循环一直我们设定的东西。
于是我们在主程序中添加如下代码
image
HAL_Delay();是延时程序,单位为ms,即让程序延时1s,如此我们便知道,这个程序的含义是让灯亮一秒灭一秒,并不停循环。
下面我们烧写程序观察我们的核心板
image

进阶——产生一个pwm波

波/信号

下面讨论的波/信号可以粗略的认为是电压的变化,这种变化可以承载着信息的传递,比如我们上面提到的二进制,比如我们想用波传递一个数字六,六在二进制下是110,那我们可以让波在两个时间单位下为高电平,一个时间单位下为低电平,这样就把信息传递了出去

pwm波的定义

PWM就是脉冲宽度调制,也就是占空比可变的脉冲波形。脉冲宽度调制是一种对模拟信号电平进行数字编码的方法。
可以简单理解为一个只由高低电平组成的周期波形,高低电平的持续时间可调

pwm波图示

image
根据上一节的知识,我们很容易知道ton指的就是高电平,toff就是指的低电平,而这样一个pwm波有两个参数是我们需要关注的,第一个是周期/频率,高中知识我们可以知道周期就是频率的倒数,即\(T={{1}\over{f}}\),而在图示中,我们可以知道\(T=ton+toff\),而另一个参数叫做占空比,其定义是一个周期中高电平占整个周期的时间,即\(D={{ton}\over{ton+toff}}={ton \over T}\)

尝试产生一个pwm波

在上一节的学习中,我们其实已经产生过一个pwm波,即让灯闪烁的波,根据延时的时间,我们很容易知道这个pwm波的占空比为\(50\%\),周期为\(2s\),频率为\(0.5HZ\)
下面我们将尝试产生两个不同方法下的pwm波,第一个是基于上一种方法使led有呼吸的效果,第二个是使用定时器产生更为精准的pwm波,并为招新题做好基础。

呼吸灯的定义与实现方法

原理

呼吸灯是指灯光在微电脑的控制之下完成由亮到暗的逐渐变化,感觉好像是人在呼吸。
举个不太恰当的例子,在生活中有一种可以发光的荧光棒,快速挥舞便可以看出图案,这个的原理是利用人的视觉暂留,基于人眼的分辨率不超过\(60HZ\)。而呼吸灯的实现也有类似的地方,如果我们以比较高频率的pwm波驱动led,那么led的闪烁频率就会高于我们人眼的分辨率,这样人眼看起来led就不会闪烁,是一直亮的,但是亮与亮之间也有不同。很显然,占空比90%和占空比10%的灯肯定是前者更亮一点,因为亮的时间更长一点,于是我们就可以利用这个实现呼吸灯——改变pwm的占空比。

实现

将主函数中的while(1)循环修改为
image
此流程介绍中均不会涉及c语言语法讲解,若对循环判断函数等有疑惑可以自行查阅课本及线上教程,这里提供一个较为全面的线上教程https://www.runoob.com/
根据上面的学习与上一节led原理,我们知道当引脚输出为低电平时led才会亮,因此占空比低时led亮,占空比高是led暗,因此循环中的两个for循环可以很容易知道分别是从暗到亮与从亮到暗,因为HAL_Delay中的参数单位是ms,因此这个pwm的周期为20ms,即50hz,这里没有超过60hz是因为HAL_Delay的分辨率不足的原因,无法实现ms级以下延时,在下面的讨论中我们会学习更为精准且分辨率更高的延时。下面是延时效果(手机拍摄效果不佳)
image

使用定时器产生一个pwm波

定时器介绍

定时器最基本的功能就是定时处理事情。比如定时发送USART数据、定时采集AD数据、定时检测IO口电位、还可以通过IO口输出波形等。可以实现非常丰富的功能。定时器是一个很强大的外设,不同行业使用的方式不同,知识面很广。
可以简单理解为秒表,闹钟,倒计时等等等。

利用stm32自带的pwm功能产生一个波形

定时器的使用这里暂时不会用到,只讲如何使用定时器的pwm输出产生波形,想学习的同学可以自行查阅资料配置学习,这里提供一个讲解定时器的博客

  1. 首先打开我们的图形化配置界面
    image
  2. 修改单片机时钟频率
    image
    圈出地方本来值应该为8,直接修改为72然后点回车确定即可,这个页面配置的是单片机各个模块的工作频率,这里修改的原因是提高定时器的能力,下面也会讲这个值对定时器的影响
  3. 选择定时器的输出
    image
    注意选择PWM Generation,不要选择错了
  4. 修改定时器的预分频系数与计数周期
    image
    有关于这里数值选择与计算,这里提供一个讲解的博客
    这里也简单讲一下pwm频率的计算,在第二步的时钟树频率修改,我们可以看到修改后
    APB1 Timer的值为72M,这里就是我们定时器的总频率
    image
    而参数一Prescaler便是对这个值的预分频,如果将这个设为71,那么定时器的频率就会变成\(72M/(71+1)=1M\),即1MHZ,参数二Counter Period是自动重装值,即计数到多少时自动重新计数,也可以理解为计数周期,将这个地方设置为999,则定时器的频率就会变成\(1M/(999+1)=1K\),即1KHZ,这里两个值都加一是因为单片机定时器计数的方式决定的,类似于for循环中的开区间,对这里理解有困难的同学可以参考上面的博客,也可以先暂且记住,在以后的学习中逐渐理解。
  5. ctrl+s保存配置并更新代码
  6. 修改主函数
    image
    这里的函数HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);是打开pwm的输出,将Start改为Stop即为关闭,函数内的参数与之前的配置相对应。
    这里的函数__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 500);是修改pwm的占空比,初始值为0,所以这里要修改,也可以在之前配置的下方的pusle处修改,注意第三个参数是高电平的持续时间,是与计数周期相对比,因此不能大于1000,此时占空比易知\(D={500 \over 1000}*100\%=50\%\)
    修改占空比的函数可以在程序内任意地方执行,因此同学们可以尝试利用这个函数实现呼吸灯
  7. 烧写程序,使用示波器观察
    这里我们的pwm输出口在图形化配置界面中有,是PABimage
    有关于示波器的使用这里不会涉及,如果在宿舍想测试可以买一个小蜂鸣器或者喇叭,在下面的内容会简单说一下去年招新题的内容,就是用pwm驱动喇叭
    image
    可以看到这个pwm的占空比为50%,左上角频率为1KHZ

利用pwm波产生七个音符

我们知道声音是有频率的,而我们使用的发声器件喇叭或者无源蜂鸣器便是使用信号驱动的。如果我们用不同频率的pwm波驱动喇叭或者蜂鸣器就会发出不同的声音,下面是C调的七音符对应的频率
image
下面是有关这个题的一些点

  • 这里占空比对声音的音调没有影响,因此默认为50%。
  • 去年的招新题是利用七个按键控制喇叭发出七种音符,如何用按键控制这里暂且不会涉及,可以自行查阅GPIO_Input的使用。
  • 需要注意的是,核心板带载能力有限,喇叭建议一边接电源一边接pwm输出口,或者连接功放或者三极管,或者使用带有供电端的无源蜂鸣器。
  • 因为这个是去年的招新题,因此此处不做演示,有兴趣的同学可以自行制作。
  • 下面提供的是小星星的简谱
    image

进进阶,点亮N个WS2812灯珠

WS2812灯珠介绍

WS2812B是一款智能控制LED光源,控制电路和RGB芯片集成在一个5050个组件的封装中。

原理

ws2812有四个脚,一对供电脚与数据输入DIN与数据输出DOUT
image
控制方式为总线控制。
我们知道,颜色的表示方法有很多种,我们这里需要用到的有两种,第一种是RGB表示,第二种是HSV表示,这里先介绍RGB表示
RGB分别表示RED,GREEN,BLUE,三个的值都在0-255之间,不同的取值对应不同的颜色,类似于美术中的三原色混合,如果我们想表示一个红色,那么rgb的值就是255,0,0。
而ws2812的控制就是基于rgb值的控制,每一次传输24位数据,从前向后每8位分别是G,R,B,8位也正好是rgb的范围,因此我们想传输红色的值就是00000000 11111111 00000000.
如果我们想要驱动多个ws2812产生不同的颜色,就需要让其级联,将第一个的DOUT连接第二个的DIN,因为ws2812的数据传输原理,就像切蛋糕一样,每次经过一个就会切去一片,我们传输一个48位的数据,前24位进入第一个灯珠然后切去,后24位就会进入第二个灯珠

在单片机上的实现

原理

这里使用的方法是PWM+DMA传输,也有SPI和直接延时等方法,方法不唯一,有兴趣的同学可以自行查阅资料
首先我们要确定的是,WS2812的0与1实现方式,与之前点灯时的01不同,灯珠的01码是根据占空比来判断的image
可以看到,1码的占空比高,0码的占空比低,下面是具体的参数范围
image
正是因为这种特性,我们才选用pwm的传输方式,下面我们来进行一个简单的计算

如果我们将预分频设为0,自动重载值设为89,那么pwm的频率就是\({72M \over {(0+1)*(89+1)}}=800K\),当我们把高电平比较值设置为61时,高电平持续时间就为\({61 \over (89+1)}*{1 \over 800K}=847us\),当我们把高电平比较值设置为28时,高电平持续时间就为\({28 \over (89+1)}*{1 \over 800K}=388us\),两个高电平持续时间正好满足1码与0码的高电平的范围,同理,可以计算出低电平的值也符合,因此,我们按照之前学习的方法控制单片机产生这两种占空比不同的pwm波,就可以传输数据

但是,这样是比较麻烦的实现,并且会有一定的延时,针对这种问题,32单片机提供了一种数据搬运的方法,DMA传输

DMA传输

介绍

DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。DMA 传输将数据从一个地址空间复制到另外一个地址空间。当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能 嵌入式系统算法和网络是很重要的。
这里我们只要了解如何使用与配置方法即可

配置与程序

  1. 首先按照上面所说配置预分频与重载值
    image
  2. 按照下图配置DMA
    image
    点击ADD添加DMA,然后选择通道并如图配置即可(注意dma的方向是内存到外设,需要更改)
    有关如此配置的含义,有兴趣的同学可以自行查找资料,这里提供了一个博客
  3. 配置工程生成单独.c/.h文件
    image
    这样配置的目的是为了我们下面代码的结构
  4. ctrl+s保存并更新代码
  5. 在工程的文件结构中添加两个文件
    image
    添加方式为右键创建一个文本文档,并将原本的文件名XX.TXT改为ws2812.c/ws2812.h(不显示后缀自行搜索如何显示后缀)
  6. 在我们的cubeide中打开这两个文件,并写入如下代码(已经掌握原理的同学可以根据下面的讲解自行写,这里仅作为参考)
//ws2812.c
#include "ws2812.h"
unsigned char color_data[24*led_num+4];//实际GRB数据
void show()
{
	HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,(uint32_t *)(&color_data),sizeof(color_data));
}
void color_set(unsigned short int index,unsigned char r,unsigned char g,unsigned char b)
{
	unsigned char j;
	if(index >led_num)
		return;
	for(j = 0; j < 8; j++)
	{
		color_data [24 * index + j+3]        = (g & (0x80 >> j)) ? bit1 : bit0;  //G 将高位先发
		color_data [24 * index + j + 8+3]    = (r & (0x80 >> j)) ? bit1 : bit0;  //R将高位先发
		color_data [24 * index + j + 16+3]   = (b & (0x80 >> j)) ? bit1 : bit0;  //B将高位先发
	}
}
void hsv_to_rgb(int h,int s,int v,float *R,float *G,float *B)
{
    float C = 0,X = 0,Y = 0,Z = 0;
    int i=0;
    float H=(float)(h),S=(float)(s)/100.0,V=(float)(v)/100.0;
    if(S == 0)
        *R = *G = *B = V;
    else
    {
        H = H/60;
        i = (int)H;
        C = H - i;
        X = V * (1 - S);
        Y = V * (1 - S*C);
        Z = V * (1 - S*(1-C));
        switch(i){
            case 0 : *R = V; *G = Z; *B = X; break;
            case 1 : *R = Y; *G = V; *B = X; break;
            case 2 : *R = X; *G = V; *B = Z; break;
            case 3 : *R = X; *G = Y; *B = V; break;
            case 4 : *R = Z; *G = X; *B = V; break;
            case 5 : *R = V; *G = X; *B = Y; break;
        }
    }
    *R = *R *255;
    *G = *G *255;
    *B = *B *255;
}

//ws2812.h
#ifndef WS2812
#define WS2812
#include "tim.h"
#define bit1               61            //1码比较值为61-->850us
#define bit0               28            //0码比较值为28-->400us
#define led_num			    6             //灯的数量
void color_set(unsigned short int index,unsigned char r,unsigned char g,unsigned char b);
void show(void);
void hsv_to_rgb(int h,int s,int v,float *R,float *G,float *B);
#endif

下面来解释代码内容

  • 首先,创建这两个文件的目的是为了将对颜色的控制封装在一起,然后在主函数中调用,从而让整个代码结构更加简洁明了
  • 然后来看.h文件,头文件tim.h是因为这里要用定时器,所以要引用这个,前两行的作用是为了重复引用。然后是宏定义部分,比较明了,是前面讲的部分。然后是一个数组,这个数组就是数据传输的数组,我们知道每个灯有24位,所以是led_num*24,而这里加4是为了让程序运行更加稳定,开始三位为0,代表reset码,清除之前的颜色,最后一位为0,代表控制颜色结束,所以是3+1=4,多了四位。下面的是函数的定义,这里的hsv_to_rgb暂且不讲,是呼吸灯用到的内容
  • 最后来看.c文件,show()函数便是传输数据,用DMA的方式传输,数组中的值代表的就是pwm的比较值,比如数组中是0,0,0,61,28,61,61,28……,那么就是传输的10110……。下面的color_set()便是对颜色的修改,这里三目运算符与二进制运算的使用留给同学们自己研究,可以检验一下自己c语言学习是否清除扎实
  1. 修改主函数的内容
    image
    调用我们之前写的库,注意写在注释的begin与end之间
    image
    控制第一个灯为红色,注意这里传入的值0为第一个,以此类推,同时,color_set并不会使灯的颜色修改,必须要在后面加上show()才能传输
  2. 烧写程序并观察
    这里效果可以参考文章最开始的效果,这里也附上文章开始的gif程序实现
	  for(int i=1;i<=6;i++){
		  color_set(i-1,i*50,0,255);
		  show ();
		  HAL_Delay (500);
		}
	  for(int i=1;i<=6;i++)
		  color_set(i-1,255,0,255);
	   show ();
	   HAL_Delay (500);
	   for(int i=1;i<=6;i++)
		color_set(i-1,0,255,255);
	   show ();
	   HAL_Delay (500);
	   for(int i=1;i<=6;i++)
		color_set(i-1,255,255,0);
	   show ();
	   HAL_Delay (500);

呼吸灯的实现

原理

根据之前的学习,我们有一种方式使灯珠产生呼吸的效果,即不停地对灯珠进行点亮与reset,控制明暗产生呼吸的效果,同学们可以自行尝试这种方法。下面介绍的是另一个较为简单的方法,利用颜色的HSV值

HSV

HSV(Hue, Saturation, Value)是根据颜色的直观特性由 A. R. Smith 在 1978 年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。这个模型中颜色的参数分别是色调(H)、饱和度(S)和明度(V)。
因此,我们可以通过控制V的值来实现颜色的明暗变化,这里也很好理解,颜色本来就有明暗之分,比如亮红与暗红。

HSV与RGB的转换

下面提供的是HSV向RGB的转换公式
image
根据转换公式,我们便得到了之前提到的hsv_to_rgb函数
参数的范围h(0~360),s(0~100),v(0~100)

修改主函数实现呼吸灯

image
这里HAL_Delay的目的是降低呼吸频率,可以计算得到这个程序的呼吸时间为1.2s

烧写程序观察

image
这里也提供了一个常见颜色对照表,同学们可以选择喜欢的颜色实现呼吸灯的效果

结语

本次招新题的个人建议流程到这里也就结束了,主要是讲了stm32的配置与pwm波的产生,具体实现细节还需要同学们实践学习,有疑问的地方也欢迎提问。对于本题的发挥部分这里不做讲解,同学们可以自行查阅资料学习,培养好的自学能力是大学生活很重要的一环,这里也提供几个我大一学习时参考的博客。
串口收发
OLED配置
ADC采集

posted @ 2022-09-16 16:09  SFWR  Views(1749)  Comments(0Edit  收藏  举报