我们阅读了数据手册2:STM32上的SPI; STM8上的PWM定时器和中断


第一部分中,我试图告诉那些从Arduino裤子成长出来的电子爱好者,以及为什么他们应该阅读数据表和其他微控制器文档。 结果文本很大,所以我答应在另一篇文章中显示实际示例。 好吧,他称自己为货物...


今天,我将展示如何在STM32(蓝色药丸)和STM8控制器上使用数据表来解决相当简单的任务,但对于许多项目而言却是必需的。 所有演示项目都专用于我最喜欢的LED,我们将大量点亮它们,为此,我们将不得不使用各种有趣的外围设备。


文字再次变大了,为方便起见,我在做内容:


STM32蓝色药丸:16个带DM634驱动器的LED
STM8:配置六个PWM引脚
STM8:三个引脚上的8个RGB LED,中断


免责声明:我不是工程师,我不假装对电子产品有深入的了解,因此该文章适用于像我这样的恋人。 实际上,两年前,我以自己为目标受众。 如果有人告诉我,在不熟悉的芯片上阅读数据表并不令人害怕,我将不会花费很多时间在Internet上搜索一些代码,并用剪刀和创可贴发明拐杖。


本文的重点是数据表,而不是项目,因此代码可能不会过于梳理,而且经常受到限制。 尽管适合初次接触新芯片,但项目本身非常简单。


我希望我的文章可以帮助处于类似阶段的人进行业余爱好潜水。


STM32


16个带DM634和SPI的LED


一个使用Blue Pill(STM32F103C8T6)和DM634 LED驱动器的小项目。 借助数据表,我们将处理驱动程序,IO端口STM和配置SPI。


DM634


台湾芯片具有16个16位PWM输出,可以成串连接。 最年轻的12位模型以国内项目Lightpack闻名 。 一次,我在DM63x和著名的TLC5940之间进行选择,我选择了DM,原因有以下几个:1)Aliexpress上的TLC绝对是假的,但事实并非如此; 2)DM具有一个带有自己的频率发生器的自治PWM; 3)它可以在莫斯科便宜地购买,而不必等待与阿里的包裹。 好吧,当然,有趣的是学习如何自己管理芯片,而不是使用现成的库。 现在,这些芯片主要采用SSOP24封装,易于焊接至适配器。


由于制造商是台湾人,因此该芯片的数据表用中文英文书写,这很有趣。 首先,查看引脚连接Pin Connection )以了解要连接到的支脚以及引脚说明Pin Description )。 16结论:



流入直流电源(漏极开路)


水槽 / 漏极开路输出 -漏极; 流入电流源; 有源输出接地-LED通过阴极连接到驱动器。 从电气上来说,这当然不是“开漏”,但在数据手册中,通常以漏电模式来指定输出。



REXT和GND之间的外部电阻可设置输出电流值


在REXT引脚和地之间安装了一个基准电阻,该电阻控制输出的内部电阻,请参见数据表第9页的图表。 在DM634中,也可以通过设置全局亮度来以编程方式控制该电阻; 我不会在本文中详细介绍,我只是在此处放置了一个2.2-3 kOhm的电阻器。


要了解如何控制芯片,请查看设备接口的说明:



是的,这就是中国英语的全部荣耀。 翻译是有问题的,您可以根据需要理解,但是还有另一种方法-查看数据表中与功能紧密的TLC5940的连接描述方式:



...仅需三个引脚即可将数据输入设备。 SCLK信号的上升沿将数据从SIN引脚移至内部寄存器。 下载完所有数据后,短高XLAT信号将捕获内部寄存器中的串行数据。 内部寄存器-XLAT触发的闸阀。 所有数据都以最高有效位发送。


闩锁 -闩锁/闩锁/夹具。
上升沿 -脉冲的前沿
MSB优先 - 最高有效 (最左边)位向前。
时钟数据 -顺序(按位)传输数据。


闩锁一词经常在芯片的文档中找到,并以各种方式进行翻译,因此,我将让我自己理解

小型教育计划
LED驱动器本质上是一个移位寄存器。 名称中的“移位”是设备内部数据的按位移动:向内推入的每个新位都将整个链推向自己的前面。 由于没有人希望观察移位期间LED的混乱闪烁,因此该过程在通过锁存器与工作寄存器分开的缓冲寄存器中进行-这是一种等待室,其中按所需顺序排列位。 一切准备就绪后,百叶窗将打开,并且将位发送至工作位置,以替换前一批。 无论使用哪种组合,微电路文档中的“ 闩锁 ”一词几乎总是暗示着这种阻尼器。

因此,向DM634的数据传输如下:将DAI输入设置为远端LED的高位,上下拉DCK; 将DAI输入设置为下一位,拉DCK。 依此类推,直到所有位都发送完( 送入 ),然后拉LAT。 这可以手动完成( bit-bang ),但最好为此使用经过改进的SPI接口,因为它在我们的STM32中有两个副本。


蓝色平板电脑STM32F103


简介:STM32控制器比Atmega328复杂得多,令人恐惧。 同时,出于节能的考虑,几乎所有外围设备在启动时都被禁用,并且时钟频率从内部来源为8 MHz。 幸运的是,STM程序员编写了将芯片提高到“计算出的” 72 MHz的代码,并且我所知道的所有IDE的作者都将其包括在初始化过程中,因此我们不需要计时(但是您可以,如果您确实愿意的话 )。 但是您必须打开外围设备。


文档:Blue Pill上安装了流行的STM32F103C8T6芯片,有两个有用的文档:



在数据表中,我们可能感兴趣:


  • 引脚排列-芯片引脚排列-如果我们决定自己制作电路板;
  • 内存映射-特定芯片的存储卡。 在《参考手册》中,有一张整行的卡,其中列出了我们不在的寄存器。
  • 表引脚定义-列出引脚的主要功能和替代功能; 对于Internet上的“蓝色药丸”,您可以找到带有引脚列表及其功能的更方便的图片。 因此,请立即在“蓝色药丸”引脚图上用Google搜索,并保留此图片:


注意:在互联网上的图片中,评论中发现了一个错误,对此表示感谢。 图片已被替换,但这是一个教训-最好检查非数据表中的信息。


我们删除数据表,打开参考手册,现在我们只使用它。
程序:我们处理标准输入/输出,配置SPI,打开所需的外设。


输入输出


Atmega328 I / O非常简单,这就是为什么大量STM32选项会造成混淆的原因。 现在我们只需要得出结论,但是甚至有四个选择:



开漏输出,推挽输出,备用推挽输出,备用开漏


推挽 ”( push-pull )-Arduina的常见结论是,引脚可以为HIGH或LOW。 但是,尽管存在“开漏”的问题,但实际上仍然很简单:




输出配置/当端口分配给输出时:/输出缓冲器打开:/-开漏模式:“ 0”激活输出寄存器中的N-MOS,“ 1”使端口保留在输出寄存器中的Hi-Z模式(未激活P-MOS )/-“推-推”模式:输出寄存器中的“ 0”激活N-MOS,输出寄存器中的“ 1”激活P-MOS。


开漏和推挽之间的所有区别是,在第一个引脚中它不能接受高电平状态:当将一个单元写入输出寄存器时,它将切换到高阻抗 -Z )。 记录零时,两种模式下的引脚在逻辑上和电气上的行为均相同。


在正常输出模式下,该引脚仅转换输出寄存器的内容。 在“替代”中,它由相应的外围设备控制(请参见9.1.4):



如果端口位配置为备用功能输出,则禁用输出寄存器,并且该引脚连接到外设输出信号


引脚定义数据表中描述了每个引脚的替代功能,该功能位于下载的图片中。 当被问及如果引脚具有几种替代功能该怎么办时,答案在数据表中给出了脚注:



如果多个外设单元使用同一引脚,为了避免其他功能之间的冲突,一次只能使用一个外设单元,并使用外设时钟激活位(在相应的RCC寄存器中)进行切换。


最后,处于输出模式的引脚也具有时钟速度。 这是另一个节能功能,在我们的案例中,我们只需将其设置为最大值,然后将其忘记即可。


因此:我们使用SPI,因此两个引脚(带有数据和时钟信号)应为“另一种推挽功能”,而另一个(LAT)应为“正常推挽”功能。 但是在分配它们之前,我们将处理SPI。


SPI


另一个小型教育计划

SPI或串行外围设备接口(串行外围设备接口)-一种简单且非常有效的接口,用于与其他MK和通常与外界的通信。 上面已经描述了其工作原理,其中涉及中文LED驱动器(在参考手册中,请参见第25节)。 SPI可以在主机(“主机”)和从机(“从机”)模式下运行。 SPI具有四个基本通道,但可能不涉及所有这些通道:


  • MOSI,主机输出/从机输入:该引脚在主机模式下发送,但在从机模式下接收数据。
  • MISO,主机输入/从机输出:相反,在主机中接受,在从机中-给出;
  • SCK,串行时钟:设置主机中数据传输的频率或从机中接收时钟信号。 本质上是跳动;
  • SS,从机选择:通过该通道,从机得知他们希望从中获得某些东西。 在STM32上,它称为NSS,其中N =负,即 如果该通道接地,则控制器将成为从属。 与漏极开路输出模式很好地结合在一起,但这是另一回事。

像其他所有内容一样,STM32上的SPI也具有丰富的功能,这使其在某种程度上难以理解。 例如,它不仅可以与SPI一起使用,而且还可以与I2S接口一起使用,并且在文档中它们的描述混杂在一起,您需要及时消除多余的内容。 我们的任务非常简单:您只需要使用MOSI和SCK发送数据。 我们转到第25.3.4节(半双工通信),在其中找到1个时钟和1个单向数据线 (1个时钟信号和1个单向数据流):



在这种模式下,应用程序在仅发送或仅接收模式下使用SPI。 /仅发送模式类似于双工模式:数据在发送引脚上发送(主模式下为MOSI,从模式下为MISO),接收引脚(分别为MISO或MOSI)可以用作常规输入输出引脚。 在这种情况下,应用程序忽略Rx缓冲区就足够了(如果读取它,将不会传输任何数据)。


好吧,MISO引脚是免费的,让我们将LAT信号连接到它。 我们将讨论Slave Select,它可以在STM32上以编程方式进行控制,这非常方便。 我们在SPI一般说明的第25.3.1节中阅读了同名的段落:



NSS程序控制(SSM = 1)/有关选择从站的信息包含在寄存器SPI_CR1的SSI位中。 外部NSS引脚保持空闲状态以用于其他应用需求。


现在该写寄存器了。 我决定使用SPI2,我们正在数据表中寻找基地址-在3.3内存映射部分:



好,我们开始:


#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset))) 

我们打开第25.3.3节,上面写着“在主模式下配置SPI”:



1.通过SPI_CR1寄存器中的BR [2:0]位设置串行时钟速度。


寄存器收集在同名的参考手册部分。 CR1的地址偏移量Address offset )为0x00,默认情况下,所有位都将重置( 重置值 0x0000):



BR位设置控制器时钟分频器,从而确定SPI工作的频率。 STM32的频率为72 MHz,根据其数据表,LED驱动器的最高频率为25 MHz,因此您需要将其分为4个(BR [2:0] = 001)。


 #define _SPI_CR1 0x00 #define BR_0 0x0008 #define BR_1 0x0010 #define BR_2 0x0020 _SPI2_ (_SPI_CR1) |= BR_0;// pclk/4 

2.将CPOL和CPHA位置1,以确定数据传输和串行接口时钟之间的关系(请参见第240页的图)


由于我们在这里阅读数据表,而不考虑电路,因此,让我们更好地研究第704页上的CPOL和CPHA位的文字说明(SPI通用说明):



时钟相位和极性
使用SPI_CR1寄存器的CPOL和CPHA位,可以通过编程选择四个时序关系选项。 当没有数据传输时,CPOL(时钟极性)位控制时钟的状态。 该位控制主模式和从模式。 如果CPOL复位,则空闲模式下SCK引脚为低电平。 如果CPOL位置1,则SCK引脚在空闲模式下为高电平。
如果将CPHA位(时钟相位)置1,则SCK信号的第二个边沿充当高位的陷阱门(如果CPOL被清除,则下降,如果CPOL被置位,则上升)。 时钟信号的第二次变化捕获数据。 如果CPHA位被清零,则SCK信号的上升沿用作高位的陷阱门(如果置位CPOL,则下降;如果清零CPOL,则上升。 时钟信号的第一次变化会捕获数据。


掌握了这些知识之后,我们得出结论,两个位都必须保持为零,因为 我们需要SCK信号在不使用时保持低电平,并沿脉冲的前沿传输数据(请参见DM634数据表中的上升沿)。


顺便说一句,在这里我们首先遇到了ST数据表中的词汇功能:例如,在Atmega中,短语“将位重置为零”被写入来重置位而不清除位


3.将DFF位置1以定义8位或16位数据块格式。


我专门采用了16位DM634,以免像DM633那样困扰12位PWM数据的传输。 DFF放入一个单元很有意义:


 #define DFF 0x0800 _SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode 

4.配置寄存器SPI_CR1中的LSBFIRST位,以确定块格式


顾名思义,LSBFIRST将低位设置为正向。 但是DM634希望从高位开始接收数据。 因此,我们将其丢弃。


5.在硬件模式下,如果需要从NSS引脚输入,则在整个字节传输序列期间向NSS引脚发送高电平信号。 在NSS编程模式下,将寄存器SPI_CR1中的SSM和SSI位置1。 如果NSS引脚应在输出端工作,则仅必须将SSOE位置1。


安装SSM和SSI可以忽略NSS硬件模式:


 #define SSI 0x0100 #define SSM 0x0200 _SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high 

6.必须将MSTR和SPE位置1(仅当NSS施加高电平信号时,它们才保持设置状态)


实际上,通过这些位,我们将SPI分配为主设备并将其打开:


 #define MSTR 0x0004 #define SPE 0x0040 _SPI2_ (_SPI_CR1) |= MSTR; //SPI master //  ,  SPI _SPI2_ (_SPI_CR1) |= SPE; 

配置了SPI,让我们编写一些函数,这些函数立即将字节发送到驱动程序。 我们继续阅读25.3.3“在主模式下配置SPI”:



数据传输程序
当一个字节被写入发送缓冲器时,传输开始。
在发送第一个位期间,数据字节以并行模式(从内部总线)加载到移位寄存器中,然后以串行模式发送到MOSI引脚,第一个或最后一位向前,具体取决于寄存器CPI_CR1中LSBFIRST位的设置。 数据从Tx缓冲区传输到移位寄存器后,TXE标志被置1,如果CPI_CR1寄存器中的TXEIE位置1,也会产生中断。


我在翻译中突出了几个单词,以引起人们对STM控制器中SPI实现的一项功能的关注。 在Atmega上,仅在整个字节都耗尽后才设置TXE标志( Tx空 ,Tx空,准备接收数据)。 在此字节被推入内部移位寄存器后,此标志置位。 由于所有位同时(并行)被推送到那里,然后依次发送数据,因此在完全发送字节之前设置TXE。 这很重要,因为 对于我们的LED驱动器,我们需要在发送所有数据后拉LAT引脚,即 仅TXE标志对我们来说还不够。


这意味着我们需要其他标志。 让我们在25.3.7中看到“状态标志”:



<...>

忙标志
BSY标志由硬件设置和重置(对其进行写入不会产生任何影响)。 BSY标志指示SPI通信层的状态。
重置:
传输完成时(主模式除外,如果传输是连续的)
禁用SPI时
发生向导模式错误时(MODF = 1)
如果传输不连续,则在每次数据传输之间清除BSY标志。


好吧,派上用场。 我们找出发送缓冲区的位置。 为此,请阅读“ SPI数据寄存器”:



位15:0 DR [15:0]数据寄存器
接收到的数据或要传输的数据。
数据寄存器分为两个缓冲区-一个用于写(发送缓冲区),另一个用于读(接收缓冲区)。 写入数据寄存器将写入Tx缓冲区,而从数据寄存器读取将返回Rx缓冲区中包含的值。


好,状态寄存器中有TXE和BSY标志:



我们写:


 #define _SPI_DR 0x0C #define _SPI_SR 0x08 #define BSY 0x0080 #define TXE 0x0002 void dm_shift16(uint16_t value) { _SPI2_(_SPI_DR) = value; //send 2 bytes while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent } 

好吧,由于我们需要两次发送16个字节,这取决于LED驱动器的输出数量,如下所示:


 void sendLEDdata() { LAT_low(); uint8_t k = 16; do { k--; dm_shift16(leds[k]); } while (k); while (_SPI2_(_SPI_SR) & BSY); // finish transmission LAT_pulse(); } 

但是我们仍然不知道如何拉LAT引脚,因此我们将返回I / O。


分配针脚


在STM32F1中,负责引脚状态的寄存器非常少见。 显然,它们比Atmega还要多,但它们也不同于其他STM芯片。 第9.1节GPIO概述:



每个通用输入/输出端口(GPIO)都有两个32位配置寄存器(GPIOx_CRL和GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),一个32位设置/复位寄存器(GPIOx_BSRR),16位复位寄存器(GPIOx_BRR)和32位块寄存器(GPIOx_LCKR)。


前两个寄存器不寻常且不方便,因为16个端口引脚散布在它们上面,格式为“每位兄弟四位”。 即 从零到第七的引脚在CRL中,其余的引脚在CRH中。 同时,其余寄存器成功地放入了端口所有引脚的位中-通常只剩下一半“保留”。


为简单起见,请从列表末尾开始。


我们不需要块寄存器。


设置和复位寄存器很有趣,因为它们彼此部分复制:您只能在BSRR中写入所有内容,其中高16位会将引脚复位为零,而低位-设置为1,或者也使用BRR,低16位只能复位引脚。 。 我喜欢第二种选择。 这些寄存器很重要,因为它们提供了对引脚的原子访问:




原子安装或重置
在位级别上对GPIOx_ODR进行编程时,您无需禁用中断:您可以通过一个原子写操作APB2来更改一个或多个位。 通过向设置/复位寄存器(GPIOx_BSRR或仅用于复位的GPIOx_BRR)中写入要更改的位来实现“ 1”。 其他位将保持不变。


数据寄存器的名称非常明确-IDR = 输入方向寄存器,输入寄存器; ODR = 输出方向寄存器,输出寄存器。 在当前项目中,我们将不需要它们。


最后,控制寄存器。 由于我们对第二个SPI的引脚PB13,PB14和PB15感兴趣,因此我们立即查看CRH:



而且我们看到有必要从20号到31号逐位写一些东西。


我们已经弄清楚了想要从引脚获得什么,因此在这里我可以不用截图就可以说MODE设置了方向(输入,如果两个位都设置为0)和引脚速度(我们需要50MHz,即都引脚设置为“ 1”),CNF设置模式:普通的“推-推”-00,“替代”-10。默认情况下,如我们上面所见,所有引脚的底部都是第三个位(CNF0),它将其设置为浮动输入模式。


由于我计划对该芯片做其他事情,为简单起见,我为上下控制寄存器定义了所有可能的MODE和CNF值。


好吧,像这样
 #define CNF0_0 0x00000004 #define CNF0_1 0x00000008 #define CNF1_0 0x00000040 #define CNF1_1 0x00000080 #define CNF2_0 0x00000400 #define CNF2_1 0x00000800 #define CNF3_0 0x00004000 #define CNF3_1 0x00008000 #define CNF4_0 0x00040000 #define CNF4_1 0x00080000 #define CNF5_0 0x00400000 #define CNF5_1 0x00800000 #define CNF6_0 0x04000000 #define CNF6_1 0x08000000 #define CNF7_0 0x40000000 #define CNF7_1 0x80000000 #define CNF8_0 0x00000004 #define CNF8_1 0x00000008 #define CNF9_0 0x00000040 #define CNF9_1 0x00000080 #define CNF10_0 0x00000400 #define CNF10_1 0x00000800 #define CNF11_0 0x00004000 #define CNF11_1 0x00008000 #define CNF12_0 0x00040000 #define CNF12_1 0x00080000 #define CNF13_0 0x00400000 #define CNF13_1 0x00800000 #define CNF14_0 0x04000000 #define CNF14_1 0x08000000 #define CNF15_0 0x40000000 #define CNF15_1 0x80000000 #define MODE0_0 0x00000001 #define MODE0_1 0x00000002 #define MODE1_0 0x00000010 #define MODE1_1 0x00000020 #define MODE2_0 0x00000100 #define MODE2_1 0x00000200 #define MODE3_0 0x00001000 #define MODE3_1 0x00002000 #define MODE4_0 0x00010000 #define MODE4_1 0x00020000 #define MODE5_0 0x00100000 #define MODE5_1 0x00200000 #define MODE6_0 0x01000000 #define MODE6_1 0x02000000 #define MODE7_0 0x10000000 #define MODE7_1 0x20000000 #define MODE8_0 0x00000001 #define MODE8_1 0x00000002 #define MODE9_0 0x00000010 #define MODE9_1 0x00000020 #define MODE10_0 0x00000100 #define MODE10_1 0x00000200 #define MODE11_0 0x00001000 #define MODE11_1 0x00002000 #define MODE12_0 0x00010000 #define MODE12_1 0x00020000 #define MODE13_0 0x00100000 #define MODE13_1 0x00200000 #define MODE14_0 0x01000000 #define MODE14_1 0x02000000 #define MODE15_0 0x10000000 #define MODE15_1 0x20000000 

我们的引脚位于端口B(基本地址为0x40010C00)上,代码:


 #define _PORTB_(mem_offset) (*(volatile uint32_t *)(0x40010C00 + (mem_offset))) #define _BRR 0x14 #define _BSRR 0x10 #define _CRL 0x00 #define _CRH 0x04 //  SPI2: MOSI  B15, CLK  B13 //LAT     MISO – B14 //  ,      _PORTB_ (_CRH) &= ~(CNF15_0 | CNF14_0 | CNF13_0 | CNF12_0); //   MOSI  SCK _PORTB_ (_CRH) |= CNF15_1 | CNF13_1; //50 , MODE = 11 _PORTB_ (_CRH) |= MODE15_1 | MODE15_0 | MODE14_1 | MODE14_0 | MODE13_1 | MODE13_0; 

并且,因此,您可以为LAT编写定义,这将使寄存器BRR和BSRR跳动:


 /*** LAT pulse – high, then low */ #define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14) #define LAT_low() _PORTB_(_BRR) = (1<<14) 

(LAT_low只是由于惯性而已,某种程度上一直如此,让自己留下来)


现在一切都已经很好,只是无法正常工作。 因为它是STM32,所以可以节省电力,这意味着您需要为必需的外围设备启用时钟。


开启计时


时钟负责计时,它们也是时钟。 我们已经可以看到缩写RCC。 我们正在文档中寻找它:这是“复位和时钟控制”。


如上文所述,幸运的是,STM的人员为我们完成了时间安排主题中最困难的部分,对此他们表示非常感谢(再次,我将提供一个指向Di Halt网站的链接,以明确这是多么混乱 )。 我们只需要负责启用外设时钟的寄存器(Peripheral Clock Enable Registers)。 首先,找到RCC的基址,它位于“存储卡”的开头:



 #define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset))) 

然后,单击链接以尝试在板上找到某些东西,或者,更好的是,从使能寄存器的各部分中对包含寄存器的描述进行遍历。 我们在哪里找到RCC_APB1ENR和RCC_APB2ENR:




其中的位分别包括时钟SPI2,IOPB(I / O端口B)和替代功能(AFIO)。


 #define _APB2ENR 0x18 #define _APB1ENR 0x1C #define IOPBEN 0x0008 #define SPI2EN 0x4000 #define AFIOEN 0x0001 //   B  .  _RCC_(_APB2ENR) |= IOPBEN | AFIOEN; //  SPI2 _RCC_(_APB1ENR) |= SPI2EN; 

最终代码可以在这里找到。


如果有机会并希望进行测试,则可以将DM634这样连接:DAI到PB15,DCK到PB13,LAT到PB14。 我们从5伏特给驱动器供电,别忘了将地结合起来。



STM8 PWM


STM8上的PWM


例如,当我计划本文时,我决定尝试仅使用数据表来学习不熟悉的芯片的某些功能,以便在没有引导程序的情况下无法获得引导程序。 STM8非常适合这个角色:首先,我有几块带有STM8S103的中文主板,其次,它不是很流行,因此,在互联网上阅读和查找解决方案的诱惑在于缺乏这些解决方案。


该芯片还有一个数据表参考手册RM0016 ,第一个是引脚排列,第二个是寄存器地址-其他。 STM8在难看的IDE ST Visual Develop中用C 编程


时钟和I / O


默认情况下,STM8的工作频率为2 MHz,必须立即将其固定。



HSI时钟(内部速度)
HSI时钟从内部带有可编程分频器(1至8)的16 MHz RC振荡器获得。 它在时钟分频器的寄存器(CLK_CKDIVR)中设置。
注意:首先,选择带分频器8的HSI RC振荡器作为主要时钟源。


我们在数据表中找到了寄存器地址,在refman中进行了描述,并看到需要清除寄存器:


 #define CLK_CKDIVR *(volatile uint8_t *)0x0050C6 CLK_CKDIVR &= ~(0x18); 

因为我们要启动PWM并连接LED,所以我们看一下引脚:



该芯片很小,许多功能都挂在同一引脚上。 方括号中的内容是“替代功能”,它由“ 选项字节 ”切换-类似于Atmega的保险丝。 您可以通过编程方式更改其值,但这不是必需的,因为 仅在重新启动后才能激活新功能。 使用ST Visual Programmer(与Visual Develop一起下载)可以更轻松地更改这些字节。 引脚排列显示,第一个计时器的CH1和CH2的结论隐藏在方括号中; 必须将STFR中的AFR1和AFR0位置1,第二个定时器还将第二个定时器的CH1的输出从PD4传输到PC5。


因此,有6个引脚将控制LED:第一个计时器为PC6,PC7和PC3,第二个为PC5,PD3和PA3。


与STM32相比,在STM8上配置I / O引脚本身更简单,更合理:


  • 熟悉Atmega 数据方向寄存器 :1 =输出;
  • 输出端的第一控制寄存器CR1设置推挽模式(1)或开漏(0); 由于我将LED与带有阴极的芯片相连,因此我在此处保留零。
  • 输出处的第二个控制寄存器CR2设置时钟速度:1 = 10 MHz

 #define PA_DDR *(volatile uint8_t *)0x005002 #define PA_CR2 *(volatile uint8_t *)0x005004 #define PD_DDR *(volatile uint8_t *)0x005011 #define PD_CR2 *(volatile uint8_t *)0x005013 #define PC_DDR *(volatile uint8_t *)0x00500C #define PC_CR2 *(volatile uint8_t *)0x00500E PA_DDR = (1<<3); //output PA_CR2 |= (1<<3); //fast PD_DDR = (1<<3); //output PD_CR2 |= (1<<3); //fast PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fast 

PWM设置


首先,让我们定义术语:


  • PWM频率 -计时器滴答的频率;
  • 自动重载,AR-计时器将计数到的自动重载值(脉冲周期);
  • UEV更新事件 -计时器计入AR 时发生的事件;
  • PWM占空比-PWM占空比,通常称为“占空比”;
  • 捕获/比较值 - 捕获/比较的值 ,已经计数到定时器将要执行的操作 (对于PWM,它将反转输出信号);
  • 预加载值 -预加载值。 计时器计时时, 比较值不能更改,否则PWM周期将中断。 因此,当定时器到达计数结束并复位时,新的发送值将被放入缓冲区并从缓冲区中拉出。
  • 边缘对齐中心对齐模式 -边界和中心对齐 ,与Atmelovskie 快速PWM相位校正PWM相同
  • OCiREF,输出比较参考信号 -实际上,PWM模式下的参考输出信号位于相应的引脚上。

从引脚上已经可以清楚地看到,两个定时器具有PWM功能-第一个和第二个。 两者均为16位,第一个具有许多附加功能(尤其是它可以向上和向下计数)。 我们两个都需要以相同的方式工作,所以我决定从明显较差的第二个开始,以免意外使用其中没有的东西。 一个问题是,参考手册中所有计时器的PWM功能的描述都在第一个计时器(17.5.7 PWM模式)的章节中,因此您必须在本文档中来回跳转。


STM8上的PWM与PWM Atmega相比具有重要优势:



具有边缘对齐功能的PWM
帐户配置从下到上
如果TIM_CR1寄存器中的DIR位被清除,则自下而上的计数有效
例子
该示例使用第一个PWM模式。 TIM1_CNT <TIM1_CCRi时,OCiREF PWM参考信号保持高电平。 否则,它会处于较低水平。 如果TIM1_CCRi寄存器中的比较值大于启动值(TIM1_ARR寄存器),则OCiREF信号保持为1。 如果比较值为0,则OCiREF保持为零。 ...


更新事件期间的STM8计时器首先检查比较值 ,然后才提供参考信号。 Atmega的计时器先洗牌,然后进行比较,结果,当compare value == 0输出结果导致指针需要进行某种方式的争夺(例如,通过编程求逆逻辑)。


因此,我们要做的是:8位PWM( AR == 255 ),我们从下到上考虑沿边界对齐。 由于灯泡是通过阴极连接到芯片的,因此PWM必须在比较值之前输出0(LED点亮),之后再输出1。


我们已经阅读了一些PWM模式 ,因此可以通过在参考手册中搜索以下短语(18.6.8-TIMx_CCMR1)来找到第二个定时器所需的寄存器:



110:第一PWM模式-从下至上计数时,当TIMx_CNT <TIMx_CCR1时,第一通道有效。 否则,第一个通道将处于非活动状态。 [进一步从计时器1复制粘贴文件时出现错误]
111:第二个PWM模式-当从下往上计数时,当TIMx_CNT <TIMx_CCR1时,第一个通道无效。 否则,第一个通道是活动的。


由于LED连接到MK阴极,因此第二种模式适合我们(第一种模式也适用,但我们还不知道)。



Bit 3 OC1PE:使能输出1预载
0:TIMx_CCR1关闭时的预加载寄存器。 您可以随时写入TIMx_CCR1。 新值立即生效。
1:使能TIMx_CCR1的预加载寄存器。 读/写操作访问预加载寄存器。 在每次更新事件期间,预加载的值TIMx_CCR1被加载到影子寄存器中。
*注意:为使PWM模式正常工作,必须打开预加载寄存器。 在单信号模式下(在TIMx_CR1寄存器中将OPM位置1),这不是必需的。

好的,打开第二个计时器的三个通道所需的一切:


 #define TIM2_CCMR1 *(volatile uint8_t *)0x005307 #define TIM2_CCMR2 *(volatile uint8_t *)0x005308 #define TIM2_CCMR3 *(volatile uint8_t *)0x005309 #define PWM_MODE2 0x70 //PWM mode 2, 0b01110000 #define OCxPE 0x08 //preload enable TIM2_CCMR1 = (PWM_MODE2 | OCxPE); TIM2_CCMR2 = (PWM_MODE2 | OCxPE); TIM2_CCMR3 = (PWM_MODE2 | OCxPE); 

AR由两个八位寄存器组成,一切都很简单:


 #define TIM2_ARRH *(volatile uint8_t *)0x00530F #define TIM2_ARRL *(volatile uint8_t *)0x005310 TIM2_ARRH = 0; TIM2_ARRL = 255; 

第二个计时器只能从下往上计数,沿边框对齐,无需更改任何内容。将分频器设置为例如256。对于第二个计时器,分频器在TIM2_PSCR寄存器中设置,代表2的幂:


 #define TIM2_PSCR *(volatile uint8_t *)0x00530E TIM2_PSCR = 8; 

剩下的就是结论和第二个计时器本身。第一个任务由捕获/比较使能寄存器解决:沿它们有两个,三个通道不对称分散。在这里我们还可以发现您可以更改信号的极性,即 原则上,您可以使用PWM模式1。


 #define TIM2_CCER1 *(volatile uint8_t *)0x00530A #define TIM2_CCER2 *(volatile uint8_t *)0x00530B #define CC1E (1<<0) // CCER1 #define CC2E (1<<4) // CCER1 #define CC3E (1<<0) // CCER2 TIM2_CCER1 = (CC1E | CC2E); TIM2_CCER2 = CC3E; 

, , TIMx_CR1:



 #define TIM2_CR1 *(volatile uint8_t *)0x005300 TIM2_CR1 |= 1; 

AnalogWrite(), . Capture/Compare registers , : 8 TIM2_CCRxL TIM2_CCRxH. 8- , :


 #define TIM2_CCR1L *(volatile uint8_t *)0x005312 #define TIM2_CCR2L *(volatile uint8_t *)0x005314 #define TIM2_CCR3L *(volatile uint8_t *)0x005316 void setRGBled(uint8_t r, uint8_t g, uint8_t b) { TIM2_CCR1L = r; TIM2_CCR2L = g; TIM2_CCR3L = b; } 

, , 100% ( 255 ). , , .


, .


( , «» , ). . , .. , 16- Prescaler High Low . … . ?


1, , . 17.7.30 Break register (TIM1_BKR) , :




 #define TIM1_BKR *(volatile uint8_t *)0x00526D TIM1_BKR = (1<<7); 

, .



STM8 Multiplex


STM8


- , RGB- . – LED-, , - , , ( persistence of vision , ). - - .


:


  • RGB LED;
  • , ;
  • ;
  • RGB LED;
  • ...

.. , , «» . . , , , UEV RGB-.


LED , «», . :


 uint8_t colors[8][3]; 

, , .


 uint8_t cnt; 


, , CD74HC238. – , << . ( 0, 1 2) X, ( 1<<X ). . , – , , . , .


CD74HC238 , . P-MOSFET, , .. 20 , absolute maximum ratings . CD74HC238 :



H = , L = , X –


E2 E1 , E3, A0, A1 A3 PD5, PC3, PC4 PC5 STM8. , , push-pull .



, , :


-, Update Event (UEV), , LED. Update Interrupt Enable




 #define TIM2_IER *(volatile uint8_t *)0x005303 //enable interrupt TIM2_IER = 1; 

, ghosting – . - , , UEV, , LED - . (0 = , 255 = ) . 即 , UEV .


:


 //set polarity TIM2_CCER1 |= (CC1P | CC2P); TIM2_CCER2 |= CC3P; 

r, g b 255 .



, - . - , .


ST Visual Develop, main.c stm8_interrupt_vector.c , . NonHandledInterrupt . .


, :



13 TIM2 /
14 TIM2 /


LED UEV, №13.


, -, stm8_interrupt_vector.c , №13 (IRQ13) :


 {0x82, TIM2_Overflow}, /* irq13 */ 

-, main.h :


 #ifndef __MAIN_H #define __MAIN_H @far @interrupt void TIM2_Overflow (void); #endif 

, , main.c :


 @far @interrupt void TIM2_Overflow (void) { PD_ODR &= ~(1<<5); //   PC_ODR = (cnt<<3); //      PD_ODR |= (1<<5); //   TIM2_SR1 = 0; //   Update Interrupt Pending cnt++; cnt &= 7; //   LED TIM2_CCR1L = ~colors[cnt][0]; //      TIM2_CCR2L = ~colors[cnt][1]; //     TIM2_CCR3L = ~colors[cnt][2]; // return; } 

. rimProgramming Manual :


 //enable interrupts _asm("rim"); 

sim – . «», .


.



- , , . , .

Source: https://habr.com/ru/post/zh-CN456094/


All Articles