访问手机版页面
你的位置:老古开发网 > STM32单片机的PWM > 正文  
工程师STM32单片机学习基础手记(4):用PWM实现荧火虫灯
内容导读:


  用PWM的方法实现荧火虫灯

  上次提到要用TImer的PWM功能来实现荧火虫灯。当然还是找一个现成的例子来作个修改,这回要用到的例子在这里。

  


  复制一份到自己练习用的文件夹中,建立工程。

  


  先阅读readme.txt及源程序,了解一些基本信息。

  从程序中可以知道:

  (1) 使用TIM3

  (2) 定时器的时钟频率是36MHz.

  (3) PWM信号的频率是36KHz,这是通过TIM3的ARR来设置的。ARR的值是999,因此PWM的频率是36MHz/(999+1)=36KHz。

  (4) 四个通道的占空比分别由TIM3_CCR1~TIM3_CCR4来确定,算式是:

  (TIM3_CCR1/ TIM3_ARR)* 100

  由此,当PWM的频率是36K时,占空比分辨率接近0.1%。降低频率,可以获得更高的分辨率。

  要完成灯的渐亮和渐灭控制,只要定时改变TIM3_CCR1的值就行了。

  如何改变呢?这里用到STM32提供的系统定时器(SysTick)

  数据手册中关于这个定时器的描述如下:

  -------------------------------------------------------------

  系统时基定时器

  这个定时器是专用于实时操作系统,也可当成一个标准的递减计数器。它具有下述特性:

  ● 24位的递减计数器

  ● 自动重加载功能

  ● 当计数器为0时能产生一个可屏蔽系统中断

  ● 可编程时钟源

  而它的使用方法可以在库提供的例子中找到。

  有一个初始化函数:

  void SysTick_Configuration(void)

  {

  if (SysTick_Config((SystemFrequency) / 10)) //经实际测试发现,除以10是100ms,除以100是10ms,依此类推

  {

  /* Capture error */

  while (1);

  }

  NVIC_SetPriority(SysTick_IRQn, 0x0);

  }

  这里将其初始化为每100ms产生一次中断。

  将这个函数放在main.c中,在main函数中调用它,即完成初始化工作。在system32_it.c中有中断处理函数。

  void SysTick_Handler(void)

  {}

  原例子中这里没有写代码,可以根据需要自行增加相关代码来处理每100ms时间到的事件。

  代码如下:

  extern uint16_t dutyRatio;

  extern uint8_t ChangDuty;

  void SysTick_Handler(void)

  { static uint8_t Counter;

  if(Counter》16)

  dutyRatio-=62;

  else

  { dutyRatio+=62;

  if(dutyRatio》999)

  dutyRatio=999;

  }

  if(++Counter》=32)

  Counter=0;

  ChangDuty=1;

  }

  这里定义了两个变量,一个是dutyRatio,用来控制占空比的变化。它在main.c中定义,并初始化为6。初始化TIM3_CH1通道时使用该变量。

  


  每次中断则视情况增加或者减少,每次变化的量是62。在SysTick_Handler函数中,定义了一个static型的变量Counter,它的值在 0~31之间变化。当其值在0~15之间时,dutyRatio每次加1,这样一共是加16次,即其最终的值是:6+16*62=998,正好比ARR的值小1。当Counter的值在16~31之间变化时,dutyRatio每次减62。这样,dutyRatio的值始终在6~998之间变化,对应的是占空比在:

  6/999*100%=0.6% ~ 998/999*100%=99.89% 之间变化。

  ChangDuty是一个标志,用途是通知main函数,占空比已发生变化,要求更新CCR1。Mina函数的处理如下:

  while (1)

  { if(ChangDuty==1)

  {

  TIM3-》CCR1=dutyRatio;

  ChangDuty=0;

  }

  }

  在用软件仿真时,执行到TIM3-》CCR1=dutyRatio;时,外围部件中的相应值并没有立即变化。目前还没有弄清楚是调试器的问题还是确实不立即发生变化。

  


  使用硬件来测试,由于我手边的板子TIM3_CH1上没有接LED,所以就看不出灯亮的效果了,不过,不要紧,还有示波器。将程序下载入FLASH后运行,观察GPIOA.6,可以看到非常漂亮的波形。用万用表电压档测该引脚的电压,可以看到电压平稳地上升和下降。所以,我有些怀疑上面提到的那个CCR1没有立即变化仅仅只是调试器的问题。//蓝色的字这个不对,下面有说明。

     用PWM生成正弦波

  有了PWM,自然就可以用PWM的方法生成正弦波了。下面生成500Hz正弦波的方法参考自张明峰的《PIC单片机入门与实践》

  每个正弦波分成四个像限,每个像限16点,共64点,每点出现2个PWM周期,故PWM的周期为:2ms/128=156.25us,频率为64KHz。

  TIM3 Frequency = TIM3 counter clock/(ARR + 1)

  倒过来:

  ARR=TIM3 Counter Clock/TIM3 Frequenc - 1 =562.5-1 =561

  如果取ARR的值是561的话,那么实际的频率是64.056KHz,即最终生成为的正弦波频率是:500.4Hz

  有了ARR,占空比就取决于CCR1的值了,使用EXCEL可以方便地计算出第一象限的16个点的数据:

  280,307,335,361,387,412,436,458,478,496,513,527,539,548,555,559

  有了第一象限,其他象限都可以镜像生成了。具体方法请看源程序。

  要用上面的例子修改,还需要做一些工作。

  前面是在SysTick中做出标志,然后在主程序中修改CCR1的值,现在不行了,肯定会有时间的误差,不能这做么,要在PWM输出后修正,这样就要在PWM波形输出时产生中断。因此,需要在main函数中增加以下这个函数。

  


  这个函数哪里来的呢,很简单,从timebase工程中中抄来的然后将TIM2改成TIM3就行了^_^。然后在main函数中调用它。

  注意,还需要打开stm32f10x_conf.h文件,将下面:

  


  蓝色框里面的包含文件给“解放”出来。当然,同时要把库中的misc.c源程序文件加入工程中来。否则,编译是通不过的。

  为了让通道1可以产生中断,还需要做一件事,就是下面蓝色的部分。

  /* TIM IT enable */

  TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);

  //也是从TIMEBASE工程中抄来,再将TIM2改成TIM3的。

  /* TIM3 enable counter */

  TIM_Cmd(TIM3, ENABLE);

  现在该到stm32f10x_it.c中去了,增加一个中断处理函数:

  uint16_t sinTab[]={280,307,335,361,387,412,436,458,478,496,513,527,539,548,555,559};

  uint8_t Count1,Count2; //1.像限计数器,其值在0~3之间变化 2.其值在0~31之间变化

  void TIM3_IRQHandler(void)

  {

  if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)

  {

  TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);

  if(Count2%2==0) //准备更新,新的值会在下一次更新

  { switch(Count1)

  { case 0: //象限1

  {

  TIM3-》CCR1= sinTab[Count2/2];

  break;

  }

  case 1: //象限2

  { TIM3-》CCR1=sinTab[15-Count2/2];

  break;

  }

  case 2: //象限3

  { TIM3-》CCR1=560-sinTab[Count2/2];

  break;

  }

  case 3: //象限4

  { TIM3-》CCR1=560-sinTab[15-Count2/2];

  break;

  }

  default:break;

  }

  }

  }

  if(++Count2==32)

  { Count2=0;

  if(++Count1==4)

  Count1=0;

  }

  }

  也就是在这里,搞清楚了,所谓的“我有些怀疑上面提到的那个CCR1没有立即变化仅仅只是调试器的问题”不对,这是CCR1更新方法的问题,

  


  注意上图中红色框中的描述。

  这里就是不用立即更新的方法。因为每个点的PWM波形会出现2次,因此,用

  if(Count2%2==0) 来判断是第一次产生PWM波形,更新CCR1,但是这个CCR1不会立即更新,而会在下一次产生PWM事件时更新。

  STM32的SDIO控制SD卡

  一、STM32的SDIO适配器的结构

  STM32的SDIO适配器包括与AHB总线接口和SD卡接口两个大块儿。如下面图中两个灰色阴影区域。

  左侧的阴影部分又细分两个子单无,适配器寄存器组和FIFO。适配器寄存器组包含了所有SDIO所有的寄存器,这些寄存器用于配置一些参数,以实现一些SD协议中的时序,最终于实现SD卡的命令传输。FIFO则是为了实现数据的传输,这两者部分分别代表者对SD卡的两种传输操作,即命令的传输和数据的传输。

  右侧的阴影部分,即SDIO适配器的SD卡接口大块儿,又细分为三个子单元。控制单元(Control Unit),命令通道和数据通道(Command Path and Data Path)。控制单元主要实现电源和时钟的控制。它根据适配器寄存器组中寄存器的配置,开启和关闭SDIO适配器模块的电源,改变工作的时钟源(直接使用 SDIO_CLK还是其分频后的时钟等)。命令通道连接CMD线,控制命令的传输。数据通道连接数据线(DAT0~DAT7),控制数据的传输。

  


  二、分单元详述

  下面按照上面一章提到的五个子单元的划分,对各子单元进行详细的描述。

  1、适配器寄存器组

  The adapter register block contains all system registers. This block also generates the signals that clear the static flags in the multimedia card. The clear signals are generated when 1 is written into the corresponding bit location in the SDIO Clear register.

  这个STM32数据手册上关于这一部分的全部描述,大体上就是说:适配器寄存器组包含了所有的系统寄存器。它还产生用于清除MMC卡的静态标志位的信号。当向SDIO Clear register(清除寄存器)的对应位写1,即产生这个信号。

  2、控制单元

  这一单元又在内部分为电源管理和时钟管理两个子单元,他们都受控制适配器寄存器组。时钟管理子单元产生和控制时钟信号SDIO_CK,也就是SD卡最络接收到的SCK。时钟管理子单元工作于两种模式时钟分频模式和时钟直通模式(Bypass,标准的翻译不知是什么,似乎可以是“旁路”,但“直通”更容易理解些)。当工作在直通模式进,SDIO_CK==SDIO_CLK.工作于分频模块时,SDIO_CK==SDIO_CLK/div.

  在如下情形下,时钟是不输出时钟信号的:

  复位后

  上电和掉电期间

  省电模式下总线处于空闲模式时

  电源管理子单元在上电和掉电时关继时适配器的输出信号。

  

  3、命令通道

  命令通道向卡发送命令和接收回应。

  


  如图所示,图上左侧阴影部分是属于适配器寄存器组子单元里的两个寄存器,分别为SDIO_ARG和SDIO_CMD,后者用于添加想要发送的命令,前者用于添加所要发送的命令的参数,将两个添好之后使能命令发送,命令就会自动发送出去。适配器上述两个寄存器的内容进行组合,并最终形成48位长的命令,这48 位首先进入移位寄存器,即图中的Shift register,在这里由并转串一位一位的发送,由图可见,这些位要经过CRC后,才发送出去。实际上,前面讲的总位数并非48,在这里经过CRC,生成那些位的CRC较验值,并追加到其尾部,最络才是48位。命令分为有回应的和没有回应的两种。如果发送的是没有回应的命令,发送之后会对标志位中的相送位置位,通知系统,命令发送正常,然后进入空闭状态。如果发送的命令是有回应的命令,则要等待回应。接收到回应之后,会对回应进行CRC校验,并设定相关位。下面是命令通道的状态机:

  


  进入等待状态后,命令时钟(command timer)开始计时,如果到达超时时间CPSM状态机还没移动到接收状态,则置位超时标志并进入空闲状态。

  注意:命令超时时间是固定值,为64个SDIO_CK。

  如果在命令寄存器中设置了中断位(interrupt bit),就不会启用上面讲到的超时时钟,CPSM状态机会一直等待来自卡的中断请求。如果在命令寄存器中置位了悬停位(pending bit),CPSM状态机会进入悬停状态(所谓的挂起状态),并等待来自数据通道子单元的CmdPend信号。检测到CmdPend位以为,CPSM状态机会移动到发送状态(Send state),这将使数据计数器停止命令的传输。

  注意:CPSM会在空闲模式停留至少8个SDIO_CK时间,以满足Ncc和Nrc的时间要求。Ncc时两次主机命令传输的最小延迟,而Nrc时主机命令与卡的回应之间的最小延迟。如下图:

  


  命令的格式:

  命令即是开始传输的一个标记。命令由主机发送给单个卡(寻址性命令)或是所有的卡(广播性命令,MMC V3.31及更早的MMC卡标准中支持)。命令通过CMD信号线串行传输,所有的命令都有一个固定的长度,即48位。命令的格式如下图:

  


  命令通道工作于半双工模式,所以可以发送,也可以接收命令或回应。如果CPSM状态机不在发送状态(Send State),SDIO_CMD为高阻状态(Hi-Z state),如下图:

  

  SDIO_CMD在SDIO_CK的上升沿进行同步。

  回应:

  回应是由被寻址的卡发出的一个标记(或是在MMC V3.31及以前标准中,所有连接在适配器上的卡同步发送),此标记由卡发给主机,是对刚刚接收到的命令的回答。回应是在CMD信号线上串行传输的。

  SDIO支持两种回应类型,都是进行CRC校验的:

  48位的短回应(short response)

  136位的长回应(long response)

  注意:如果回应不包含CRC校验信息(如CMD1的回应),设备驱动就必须忽略CRC错误的状态。

  下面两张表是两种回应的格式:

  


  前面讲到,SDIO适配器包含两个大块儿,详见本帖开头,这里只拿出图来:

  


 其中,与AHB接口相连的有两个块儿,就是上图中左侧阴影部分,Adapter registers 和FIFO,即适配器寄存器组和数据FIFO。前者包含了适配器所有的寄存器,用于配置相应时序,产生相应的信号。

  这里面,用于控制命令通道产生命令时序的就有两个寄存器,名为SDIO_ARG和SDIO_CMD,SDIO_ARG的三十二位全部用来存储命令参数,也就没什么好讲的了。SDIO_CMD则不同,它有六个位,用来识别不同的命令,总共可以区别64个,但实际上SD卡的命令集没有那么多。 SDIO_CMD还有一些位,用来表示些命令时否有回应,是长回应还是短回应,命令的类型是什么等等。适配器最终根据这些,加上CRC组合成一个48位的命令。

  另外,我们还提到过命令发送之后,如果这是一个没有回应的命令,那么就很简单,命令通道直接置位CMDSENT标志,或进入空闲状态。如果是有回应的,则要等待回应,并设定相关的标志位。命令通道的相关标志位如下:

  


  CRC 产生器计算的是CRC码前面的所有位的校验和。这包括开始位,传输位,命令索引(command index)和命令参数(和卡状态)。对长回应格式来说,CRC校验和计算的是CID或CSD的前120位。这里不包括开始位,传输位和六个保留位。 CRC是一个7位的值,其计算方法如下:

  




 

  补充一些硬件知识

  

  SEGGER 给出的Jlink引脚图

  

  开发板上的连接图

  

  标准的JTAG连接图,供对照参考。

  调试方式既可以用JTAG,也可以用SW。

  以下是转载:

  SWD 仿真模式概念简述

  先所说 SWD 和传统的调试方式有什么不一样:

  首先给大家介绍下经验之谈:

  (一): SWD 模式比 JTAG 在高速模式下面更加可靠。 在大数据量的情况下面 JTAG 下载程序会失败, 但是 SWD 发生的几率会小很多。 基本使用 JTAG 仿真模式的情况下是可以直接使用 SWD 模式的, 只要你的仿真器支持。 所以推荐大家使用这个模式。

  (二): 在大家 GPIO 刚好缺一个的时候, 可以使用 SWD 仿真, 这种模式支持更少的引脚。

  (三): 在大家板子的体积有限的时候推荐使用 SWD 模式, 他需要的引脚少, 当然需要的 PCB 空间就小啦。 比如: 你可以选择一个很小的 2.54 间距的 5 芯端子做仿真接口。

  (2) 仿真器对 SWD 模式支持情况

  再说说市面上的常用仿真器对 SWD 仿真的支持情况。

  (1) JLINKV6 支持 SWD 仿真模式。 速度较慢。

  (2) JLINKV7 比较好的支持 SWD 仿真模式, 速度有了明显的提高。 速度是 JLINKV6 的 6 倍。

  (3) JLINKV8 非常好的支持 SWD 仿真模式, 速度可以到 10M.

  (4) ULINK1 不支持 SWD 模式

  (5) 盗版 ULINK2 非常好的支持 SWD 模式。 速度可以达到 10M.

  (6) 正版 ULINK2 非常好的支持 SWD 模式。 速度可以达到 10M.

  再所说硬件上的不同:

  (1) JLINKV6 需要的硬件接口为: GND, RST, SWDIO, SWDCLK

  (2) JLINKV7 需要的硬件接口为: GND, RST, SWDIO, SWDCLK

  (3) JLINKV8 需要的硬件接口为: VCC, GND, RST, SWDIO, SWDCLK

  注:下面有我自己实验的结果

  (4) ULINK1 不支持 SWD 模式

  (5) 盗版 ULINK2 需要的硬件接口为: GND, RST, SWDIO, SWDCLK

  (6) 正版 ULINK2 需要的硬件接口为: GND, RST, SWDIO, SWDCLK

  由此可以看到只有 JLINKV8 需要 5 个引脚。 那么给大家介绍下为什么有了 VCC 这个引脚时候有好处, 我的个人理解: 我认为有这个引脚是最合适的, 仿真器对目标板子的仿真需要用到 RST 引脚, 其实使用仿真器内部的 VCC 做这个功能其实并不是非常美妙。 因此 JLINKV8 选择了只和目标板共 GND, 但是不共 VCC. 因此我觉得这种模式最合理, 当然通常情况下仿真器和目标板共 GND 和 VCC 是没有错的。

  (3) 在 MDK 中SWD 模式设置

  接下来告诉大家怎么使用 SWD 设置:

  打开工程 Option 设置:

  

  

  在设置中按照上图设置成 SWD 模式, 速度你可以按照你的实际需求来设置, 如果你的板子供电系统不是特别稳定, 纹波比较大或者仿真线比较长可以设置成 500K 或者 1M , 如果环境很好当然可以选择 10M , 当然速度会飞起来。 记得不要忽略了左下方的那个USB 还是 TCP 模式, 当然我们是 USB 模式, 因为有的时候默认是 TCP 模式, 这个时候我们忽略这个设置后会仿真常常连接不上的。

  JTAG引脚可以被复用为IO口,但是这样一来,JLINK就不能够连上芯片了。解决的方法有两种:

  (2) 将BOOT0/BOOT1设置成为内部RAM启动,那么上电后就不会执行FLASH中的程序,这样JLINK就能顺利“接管”JTAG引脚。

  按SW方式来调试

  

  1脚不接时出现的画面

  

  DMA初步

  DMA有什么用?

  直接存储器存取用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。

  有多少个DMA资源

  有两个DMA控制器,DMA1有7个通道,DMA2有5个通道。

  数据从什么地方送到什么地方?

  外设到SRAM(I2C/UART等获取数据并送入SRAM);

  SRAM的两个区域之间;

  外设到外设(ADC读取数据后送到TIM1控制其产生不同的PWM占空比);

  SRAM到外设(SRAM中预先保存的数据送入DAC产生各种波形);

  ……还有一些目前还搞不清楚的。

  DMA可以传递多少数据?

  传统的DMA的概念是用于大批量数据的传输,但是我理解,在STM32中,它的概念被扩展了,也许更多的时候快速是其应用的重点。数据可以从1~65535个。

  通道是如何分配的?

  见下面的这个表:

  

  

  如何来用DMA?

  确定数据来源,确定数据目的地,选择使用哪个通道,设定传输多少个数据,设定数据传递模式等等就可以了。且读一下STM32提供给我们的例子。

  //////////////////////////////////////////

  ……

  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)TIM1_CCR3_Address;

  //设定外围设备的地址

  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SRC_Buffer;

  //设定内存地址,SRC_Buffer是前面定义的一个数组

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //方向控制

  DMA_InitStructure.DMA_BufferSize = 3; //缓冲区大小

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外围地址增量控制

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址增量控制

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

  //DMA_PeripheralDataSize_HalfWord的值为0x100,后一个为0x400,在在stm32f10x_dma.h中定义,用于决定存储器数据宽度

  */

  



  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

  DMA_InitStructure.DMA_Priority = DMA_Priority_High;

  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

  以下是stm32f10x_dma.c中有关DMA的初始化设置代码

  tmpreg |= DMA_InitStruct-》DMA_DIR | DMA_InitStruct-》DMA_Mode |

  DMA_InitStruct-》DMA_PeripheralInc | DMA_InitStruct-》DMA_MemoryInc |

  DMA_InitStruct-》DMA_PeripheralDataSize | DMA_InitStruct-》DMA_MemoryDataSize |

  DMA_InitStruct-》DMA_Priority | DMA_InitStruct-》DMA_M2M;

  /* Write to DMAy Channelx CCR */

  DMAy_Channelx-》CCR = tmpreg;

  /*--------------------------- DMAy Channelx CNDTR Configuration ---------------*/

  /* Write to DMAy Channelx CNDTR */

  DMAy_Channelx-》CNDTR = DMA_InitStruct-》DMA_BufferSize;

  


  /*--------------------------- DMAy Channelx CPAR Configuration ----------------*/

  /* Write to DMAy Channelx CPAR */

  DMAy_Channelx-》CPAR = DMA_InitStruct-》DMA_PeripheralBaseAddr;

  

  /*--------------------------- DMAy Channelx CMAR Configuration ----------------*/

  /* Write to DMAy Channelx CMAR */

  DMAy_Channelx-》CMAR = DMA_InitStruct-》DMA_MemoryBaseAddr;

  //内存地址送入CMAR寄存器

  


  说明:这个图从PDF截下来,图中那个DMA_CPARx写错了,应该是DMA_CMARx

  ------------------------------------------------------------------------------------------

  再来看一个DMA的例子

  /* DMA1 Channel5 configuration ----------------------------------------------*/

  DMA_DeInit(DMA1_Channel5);

  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)TIM1_CCR1_Address;

  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC1_DR_Address;

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;

  DMA_InitStructure.DMA_BufferSize = 1;

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

  DMA_InitStructure.DMA_Priority = DMA_Priority_High;

  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

  DMA_Init(DMA1_Channel5, &DMA_InitStructure);

  /* Enable DMA1 Channel5 */

  DMA_Cmd(DMA1_Channel5, ENABLE);

  还有一些目前暂时还没有去搞清楚的,比如中断处理等,等到需要时再看吧。


——下次将讨论学习用认识ADC兼进一步看懂STM的库,敬请继续关注。
 

标签:32位单片机,STM32单片机,STM32,PWM,工程师,
来源:elecfans 作者:电子大兵 时间:2012/11/7 15:24:00
相关阅读
推荐阅读
阅读排行
最近更新
商品推荐