原创 转载网络

2009-12-25 13:39 3148 3 3 分类: MCU/ 嵌入式

电源控制:


先看一下这2张截自STM32参考手册的图片:
attachimg.gif 点击看大图


下载 (23.24 KB)

2009-9-4 11:41


attachimg.gif 点击看大图


下载 (22.88 KB)

2009-9-4 11:41


下面这张表截自STM32F103x8/B的数据手册,对上图的参数给出了具体数值:
attachimg.gif 点击看大图


下载 (28.64 KB)

2009-9-4 11:42



下面对上面2张图和表格中的数据做一个简要的解释:

1)PVD = Programmable Votage Detector 可编程电压监测器
它的作用是监视供电电压,在供电电压下降到给定的阀值以下时,产生一个中断,通知软件做紧急处理。在给出表格的上半部分就是可编程的监视阀值数据。当供电电压又恢复到给定的阀值以上时,也会产生一个中断,通知软件供电恢复。供电下降的阀值与供电上升的PVD阀值有一个固定的差值,这就是表中的VPVDhyst(PVD迟滞)这个参数,通过列出的PVD阀值数据可以看到这个差别。引入这个差值的目的是为了防止电压在阀值上下小幅抖动,而频繁地产生中断。

2)POR = Power On Reset 上电复位;PDR = Power Down Reset 掉电复位。
POR的功能是在VDD电压由低向高上升越过规定的阀值之前,保持芯片复位,当越过这个阀值后的一小段时间后(图中的"滞后时间"或表中的"复位迟滞"),结束复位并取复位向量,开始执行指令。这个阀值就是表中倒数第4行(min=1.8V,typ=1.88V,max=1.96V)。
POR的功能是在VDD电压由高向低下降越过规定的阀值后,将在芯片内部产生复位,这个阀值就是表中倒数第3行(min=1.84V,typ=1.92V,max=2.0V)。

3)可以看到POR比PDR大了0.04V,这就是表中倒数第2行,VPDRhyst(PDR迟滞)=40mV。

4)从上面的第2张图可以看到,当VDD上升越过POR阀值时,内部并不马上结束复位,而是等待一小段时间(Reset temporization),这就是表中的最后一行TRSTTEMPO,它的典型数值是2.5ms。

这个滞后时间是为了等待供电电压能够升高到最低可靠工作电压以上,我们看到POR阀值最小只有1.8V,最大也只有1.96V,都低于数据手册中给出的最低可靠工作电压2.0V,所以这个滞后时间是十分必要的,如果供电电压上升缓慢,尤其是从1.8V升到2.0V以上超过1~2.5ms,则很可能造成上电复位后MCU不能正常工作的情况。


STM32--低功耗模式 收藏
STM32F10xxx有三中低功耗模式:
●睡眠模式(Cortex?-M3内核停止,外设仍在运行)
●停止模式(所有的时钟都以停止)
●待机模式(1.8V电源关闭)


待机模式可实现系统的最低功耗。 可将电流消耗降至两微安。


在待机模式下,所有的I/O引脚处于高阻态,除了以下的引脚:
●复位引脚(始终有效)
●当被设置为防侵入或校准输出时的TAMPER引脚
●被使能的唤醒引脚


 



时钟频率72MHz时,从闪存执行代码,STM32功耗36mA,是32位市场上功耗最低的产品,相当于0.5mA/MHz。


/*按钮GPIOB9进入睡眠,WKUP pin(GPIOA0)唤醒,GPIOD3-LED 200ms闪烁*/
int main(void)
{
  /* System Clocks Configuration **********************************************/
  RCC_Configuration();
 


  GPIO_Configuration();


   /* Enable PWR and BKP clock */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);


  /* Enable WKUP pin */
  PWR_WakeUpPinCmd(ENABLE);


  /* Allow access to BKP Domain */
  PWR_BackupAccessCmd(ENABLE);


  //RTC_Configuration();


  EXTI_Configuration();


  NVIC_Configuration();


  SysTick_Config(SystemFrequency / 1000 *200 );    //200ms


  while (1)
  {
 
    Delay(0xAFFFF);


  }
}


 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/10/16/4681682.aspx


 STM32启动代码分析问题 收藏
能否讲解一下startup_stm32f10x_cl.s启动代码含义,谢谢!


我现在看反汇编如下
0x08000000 0678      LSLS     r0,r7,#25
0x08000002 2000      MOVS     r0,#0x00
0x08000004 1105      ASRS     r5,r0,#408
0x08000006 0800      LSRS     r0,r0,#00A
。。。。。。。。。。。。。。。。。。。。。。


上面应该对应
__Vectors       DCD     __initial_sp              ; Top of Stack
                DCD     Reset_Handler             ; Reset Handler
                DCD     NMI_Handler               ; NMI Handler
                DCD     HardFault_Handler         ; Hard Fault Handler
                DCD     MemManage_Handler         ; MPU Fault Handler
                DCD     BusFault_Handler          ; Bus Fault Handler
                DCD     UsageFault_Handler        ; Usage Fault Handler


 


0x08001104 4808      LDR      r0,[pc,#32]  ;  程序一运行跳到这里,why?
0x08001106 4700      BX       r0,r0,#0


上面对应
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  __main
                 LDR     R0, =__main
                 BX      R0
                 ENDP


那位能说一下为什么跳到0x08001104,即PC =0x08001104, 我想应该PC应该先跳到0x08000000?


解答:


cortex-M3和ARM9的架构有很大区别,ARM7、ARM9在复位后是从地址0处开始执行指令,也就是说地址0x00000000的内容是指令。而cortex-M3的异常向量表中的内容并不是指令,0x00000000处(当然也可能映射到别的范围)是主堆栈指针的数值,0x00000004的内容是复位后需要跳转到的地址,是一个地址而不是一条指令。


stm32选择flash启动方式,中断向量表映射到0x08000000,由楼主给出的反汇编可知,复位后主堆栈指针的位置是0x20000678,0x08000004位置的数值是0x08001105,由于cortex-M3只能运行在thumb2状态,所以要保证向PC(R15)写入的数值的bit0必须是1(如果向PC写入的数值的bit0是0,则处理器认为试图切入ARM状态,会产生fault),而实际上stm32的指令是半字对齐的,所以复位后会跳转到0x08001104.



本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/10/15/4674008.aspx


STM32开发板基础教程(十) - RTC初探(转帖) 收藏
豆皮 - STM32开发板基础教程(十) - RTC初探(原创)


版权所有 STMFANS 原创,转载请保留出处



http://www.stmfans.com/bbs/viewthread.php?tid=1147&extra=page%3D1
 



STM32的RTC实际是一个独立的定时器。
下面将介绍如何使用RTC。
我们将头一次牵扯到振源的问题。


首先介绍一下STM32使用的各种振源。
有三种
HSE:  外置晶振
HSI:  内置RC振荡
LSE:  外置RTC振荡(32768居多)


APB1 和 APB2 是经过PLL以后的振荡源。


STM32启动,首先使用的HSI振荡,在确认HSE振荡可用的情况下,才可以转而使用HSE,
当HSE出现问题,STM32可自动切换回HSI振荡,维持工作。
LSE振荡则是专门供RTC使用。


LSE晶振需要特别注意。
STM32非常奇怪,要求使用 6p负载的晶振,
市面买到的时钟晶振,绝大多是是12.5pF的
算是一个不小的bug,
大家做相关开发的时候,要留神。
要买6pF的晶振,配10pF的谐振电容。


由此,马七怀念一下AVR单片机。不用谐振电容都跑的飞,当然是Mega系列。


下面介绍RTC驱动过程。


第一件事情,喂时钟。


RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP|RCC_APB1Periph_PWR, ENABLE);


注意,喂的是什么?不是RTC,是电源管理和BKP备份器的时钟。用于备份模式下。
即系统掉电了,BKP和RTC还能继续工作,RTC继续计时。
那么RTC的时钟呢?前面提到,RTC的时钟,一般用LSE。


第二件事情,初始化RTC


// RTC config
void RTC_configuration()
{
        //Open the BKP
        PWR_BackupAccessCmd(ENABLE);


        BKP_DeInit();


        //RTC use the LSE Clock
        RCC_LSEConfig(RCC_LSE_ON);  //RCC打开了LSE时钟


        //Wait LSE Ready
        while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET);  //等待LSE就绪,一般来说,如果谐振不对,就会死在这里。实


际代码请慎重


        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //RTC使用时钟,可以使用LSE,也可以使用HSI,也可以使用HSE/128


        RCC_RTCCLKCmd(ENABLE); //RTC的时钟开启


        RTC_WaitForSynchro();  //RTC等待同步,


        RTC_WaitForLastTask();  //这个代码在RTC中常常出现,类似于等待就绪的含义


        // Interrupt Each Second
        RTC_ITConfig(RTC_IT_SEC, ENABLE);  //RTC开中断,RTC中断有三种,秒中断,闹钟中断,溢出中断,很明显他们的作用。秒中断用于即时操作,闹钟中断用于关闭或者唤醒,溢出中断的话,用于复位RTC


        RTC_WaitForLastTask();//


        RTC_SetPrescaler(32767);  //RTC预分频,32768HZ,分为一秒一个振荡,RTC period = RTCCLK/RTC_PR = (32.768


KHz)/(32767+1)


        RTC_WaitForLastTask(); //等待同步
       
}


这样,RTC就启动了。
通过 RTC_GetCounter() 这个函数。读到计数器的值。
既然 一秒增一个。
很容易就可以从 计数器的值,算出确切的时间值。
对于这种时间分量复杂的,我习惯用结构体定义


typedef struct
{
        unsigned char Sec;
        unsigned char Min;
        unsigned char Hour;
        unsigned char Day;
        unsigned char Month;
        unsigned char Year;
}Time_Struct;


// translate seconds to YY::MM:D::HH::MM::SS
Time_Struct read_RTC_time()
{
        unsigned long Time_Value;
       
        Time_Struct TimeStruct;
       
        Time_Value = RTC_GetCounter();


        TimeStruct.Year = Time_Value/(12*30*24*3600);


        TimeStruct.Month = Time_Value/(30*24*3600) - TimeStruct.Year*12;


        TimeStruct.Day = Time_Value/(24*3600) - TimeStruct.Year*12*30 - TimeStruct.Month*30;


        TimeStruct.Hour = Time_Value/3600 - TimeStruct.Year*12*30*24 - TimeStruct.Month*30*24 - TimeStruct.Day*24;


        TimeStruct.Min = Time_Value/60 - TimeStruct.Year*12*30*24*60 - TimeStruct.Month*30*24*60 -


TimeStruct.Day*24*60 - TimeStruct.Hour*60;


        TimeStruct.Sec = Time_Value - TimeStruct.Year*12*30*24*60*60 - TimeStruct.Month*30*24*60*60 -


TimeStruct.Day*24*60*60 - TimeStruct.Hour*60*60 -TimeStruct.Min*60;


        return TimeStruct;
}


当然,也可以在任意时候设置这个时间,手工修改Counter即可。相关函数在工程文件rtc.c当中。



本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/09/26/4598602.aspx


STM32多通道ADC规则转换实现了(转)! 收藏
vu16 ADC_RCVTab[160] ;   //自己添加


/*******************************************************************************
* Function Name  : main
* Description    : Main program
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
int main(void)
{
#ifdef DEBUG
  debug();
#endif


  /* System clocks configuration ---------------------------------------------*/
  RCC_Configuration();


  /* NVIC configuration ------------------------------------------------------*/
  NVIC_Configuration();


  /* GPIO configuration ------------------------------------------------------*/
  GPIO_Configuration();


  LcdShow_Init();


  /* DMA1 channel1 configuration ----------------------------------------------*/


  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA时钟



  DMA_DeInit(DMA1_Channel1);
  DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//外设地址
  DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_RCVTab;//内存地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//dma传输方向单向
  DMA_InitStructure.DMA_BufferSize = 160;//设置DMA在传输时缓冲区的长度 word
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//设置DMA的外设递增模式,一个外设
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//设置DMA的内存递增模式,
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据字长
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存数据字长
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//设置DMA的传输模式:连续不断的循环模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//设置DMA的优先级别
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//设置DMA的2个memory中的变量互相访问
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);
 
  /* Enable DMA1 channel1 */
  DMA_Cmd(DMA1_Channel1, ENABLE);


  /* ADC1 configuration ------------------------------------------------------*/
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//独立工作模式
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描方式
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//外部触发禁止
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
  ADC_InitStructure.ADC_NbrOfChannel = 8;//用于转换的通道数
  ADC_Init(ADC1, &ADC_InitStructure);


  /* ADC1 regular channels configuration [规则模式通道配置]*/
  ADC_RegularChannelConfig(ADC1, ADC_Channel_8 , 1, ADC_SampleTime_239Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_9 , 2, ADC_SampleTime_239Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 3, ADC_SampleTime_239Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 4, ADC_SampleTime_239Cycles5); 
  ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 5, ADC_SampleTime_239Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 6, ADC_SampleTime_239Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 7, ADC_SampleTime_239Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 8, ADC_SampleTime_239Cycles5); 


  /* Enable ADC1 DMA [使能ADC1 DMA]*/
  ADC_DMACmd(ADC1, ENABLE);
 
  /* Enable ADC1 [使能ADC1]*/
  ADC_Cmd(ADC1, ENABLE); 


  /* Enable ADC1 reset calibaration register */  
  ADC_ResetCalibration(ADC1);
  /* Check the end of ADC1 reset calibration register */
  while(ADC_GetResetCalibrationStatus(ADC1));


  /* Start ADC1 calibaration */
  ADC_StartCalibration(ADC1);
  /* Check the end of ADC1 calibration */
  while(ADC_GetCalibrationStatus(ADC1));
 
  /* Start ADC1 Software Conversion */
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);


  while(RESET==DMA_GetFlagStatus(DMA1_FLAG_TC1));   //自己添加
 
    while(1)
  {
    vu16 value1 = 0;
    vu16 value2 = 0;
    vu16 value3 = 0;
    vu16 value4 = 0;
    vu16 value5 = 0;
    vu16 value6 = 0;
    vu16 value7 = 0;
    vu16 value8 = 0;
 value1 = average(ADC_RCVTab,0);
 value2 = average(ADC_RCVTab,1);
 value3 = average(ADC_RCVTab,2);
 value4 = average(ADC_RCVTab,3);
 value5 = average(ADC_RCVTab,4);
 value6 = average(ADC_RCVTab,5);
 value7 = average(ADC_RCVTab,6);
 value8 = average(ADC_RCVTab,7);
 
    u8 num1 = value3 % 10;
    u8 num2 = (value3 / 10) % 10;
    u8 num3= (value3 / 100) % 10;
    u8 num4 = value3 / 1000;
    if (num1 > 9)
      display[3] = num1 + (65 - 10);
    else
      display[3] = num1 + (48-0);


    if (num2 > 9)
      display[2] = num2 +(65 - 10);
    else
      display[2] = num2 + (48 - 0);


    if (num3>9)
      display[1]=num3+(65-10);
    else
      display[1]=num3+(48-0);


    if (num4>9)
      display[0]=num4+(65-10);
    else
      display[0]=num4+(48-0);


    write_string(display);
    delay();
  }
}


u16 average(vu16 ADCDataTab[], u16 nChannel)     //自己添加
{
   u16 averagevalue="0", maxvalue="0", minvalue="0xFFFF", i;


   for (i=0;i<20;i++)
   {
     averagevalue += *(ADCDataTab+nChannel+i*8);


  if(*(ADCDataTab+nChannel+i*8)>maxvalue)  
  maxvalue=*(ADCDataTab+nChannel+i*8);
   
  if(*(ADCDataTab+nChannel+i*8)<minvalue)
  minvalue=*(ADCDataTab+nChannel+i*8);
 }


 return ((averagevalue-maxvalue-minvalue)/18);   //这样会耗时不可取 最好用 >>
 
}


 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/09/24/4589395.aspx


【转帖】STM32开发板入门教程(六) - I2C--24Cxx 收藏
我们所用来示范的24Cxx系列是最常用的EEPROM芯片。
前面提到了一个地址码,
24Cxx的地址码是固定的,
8位如下:
1  0  1  0  A2 A1 A0  0
A2 A1 A0分别是它三个管脚的电平


24Cxx 理解起来有一个特别之处。
24Cxx 包括 01/02/04/08/16 四种,容量关系刚好和数字一样。1K 2K 4K 8K 16K
24C02 最为常见, 它的三个地址管脚A2 A1 A0都是可用的,
A2 A1 A0 有8中电平组合,也就是说,可以有8个 24C02 挂载同一个I2C总线上。
24C04呢, A0管脚就失效了,只有A2 和 A1 有用,四种组合,最多有4个24C04在总线上,
以此类推。24C16只能有一个在总线上。
这里就不好理解了,为什么要这样呢。
事实是一片 24C16 == 8片24C02 总线挂到一起。A2 A1 A0虽然起不到设置作用了,但你使用地址码还是会访问到特定的区域。
明白了吧。所以其实24C系列的代码是通用的。
地址码也是固定的。就是 0xA0 0xA2 0xA4 0xA6 0xA8 0xAA 0xAC 0xAE
好,我们以24C16为范例吧。


IO设置在I2C1上,无Remap,复用开漏输出。I2C 总线是挂4.7k电阻上拉到高电平的。
        //-----------------------I2C--------------------------------------------
            /* Configure I2C1 pins: SCL and SDA */
          GPIO_InitStruct.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
          GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
          GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;   //复用开漏输出
          GPIO_Init(GPIOB, &GPIO_InitStruct);


I2C init函数:


void i2c_24c_init(I2C_TypeDef *I2Cx)
{
        I2C_InitTypeDef I2C_InitStruct;


        I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;    // I2C模式
        I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;    // ACK 在通讯中常见,握手包,即发送到了一个数据,接收方回一句,我收到鸟。
        I2C_InitStruct.I2C_ClockSpeed = I2C_Speed;  // I2C 速度设置,一般是40KHZ,400KHZ是极限,一般到不了那么高
        I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;  //快速模式下的选项,这里先不讲,100KHZ以上才有用
        I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;  //应答地址码长度,7位或者10位,24C是7位
      
// EEprom Block Select;
        I2C_InitStruct.I2C_OwnAddress1 = I2C_Slave_Adress7;   //第一个设备自身地址


        I2C_Cmd(I2Cx,ENABLE);   //开启I2C


        I2C_Init(I2Cx,&I2C_InitStruct);  //将刚刚的设置送进去
}


注意:现在的片子一般都不止一个I2C。所以用了上述模式,请详细看注释。


写一个字节进EEPROM:
参数解释:Byte待写的字节,WriteAddr预计写入的地址,ByteToWrite写多少给字节,EE24cBlockSelect选择EEPROM相应的区域(I2C地址),*I2Cx,I2C设备指针
void i2c_24c_byte_write(unsigned char Byte, unsigned char WriteAddr, unsigned int ByteToWrite, unsigned char EE24cBlockSelect,I2C_TypeDef *I2Cx)
{
        // Start the I2C
        I2C_GenerateSTART(I2Cx,ENABLE); //打开I2C,开始发送过程


      //not recommanded, stupid way
        while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));  //设置主机模式


        I2C_Send7bitAddress(I2Cx,EE24cBlockSelect,I2C_Direction_Transmitter); //发送片选,选择哪一片区域写。i2C地址区分


        // when get ACK, means Set Success
        while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //等待这次选择过程完成


        I2C_SendData(I2Cx, WriteAddr);  //发送要写入的地址码


        while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待字节发送完成


        I2C_SendData(I2Cx, Byte); //发送要写的字节


        while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待直到字节发送完成
       
        I2C_GenerateSTOP(I2Cx, ENABLE); //发送过程结束。
}


读EEPROM函数类似,却稍微复杂。
参数说明:pBuffer接收I2C数据的缓冲区,Addr读的地址,NumToRead读多少个字节,ee24cblockselect读哪个区域,I2Cx i2c设备指针
void i2c_24c_buffer_read(unsigned char *pBuffer, unsigned char Addr,unsigned char NumToRead,unsigned char EE24cBlockSelect, I2C_TypeDef *I2Cx)
{
        //open I2C
        I2C_GenerateSTART(I2Cx, ENABLE);  //开始发送


        while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))); //设置自己为主机


        I2C_Send7bitAddress(I2Cx,EE24cBlockSelect,I2C_Direction_Transmitter);  //设置自己为发送


        while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))); //等待主机发送模式设置成功


        I2C_Cmd(I2Cx,ENABLE);  //使能I2C


        I2C_SendData(I2Cx, Addr); //发送地址码,即要读的地址


        while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)));  //等待主机发送过程完成


        I2C_GenerateSTART(I2Cx, ENABLE); //I2C开始发送


        while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)));  //设置主机模式


        I2C_Send7bitAddress(I2Cx,EE24cBlockSelect,I2C_Direction_Receiver);   //设置从机地址,并设置主机为接收模式


        while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))); //确认该过程完成


        while(NumToRead)
        {
                if(NumToRead==1)
                        {
                                I2C_AcknowledgeConfig(I2Cx, DISABLE);  //关闭I2C的应答功能


                                I2C_GenerateSTOP(I2Cx, ENABLE); //发送结束信息
                        }


                if((I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)))  //如果接收到信息了
                        {
                                *pBuffer = I2C_ReceiveData(I2Cx);  //把接收到的数据 填进缓冲区当中


                                pBuffer++;


                                NumToRead--;
                        }
        }


        I2C_AcknowledgeConfig(I2Cx, ENABLE);  //开启主机I2C的应答功能
       
}


在写i2C和读i2C之间要插入下面函数等待,否则会有问题


I2C_EE_WaitEepromStandbyState();


 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/09/11/4542049.aspx


STM32笔记(三)ADC、DMA、USART的综合练习 收藏
/******************************************************************************
* 本文件实现ADC模块的基本功能
* 设置ADC1的常规转换序列包含CH10和CH16(片内温度传感器)
* 设置了连续转换模式,并使用DMA传输
* AD转换值被放在了AD_Value[2]数组内,[0]保存CH0结果,[1]保存CH16结果
* GetVolt函数计算[0]的值对应的电压值(放大100倍,保留2位小数)
* GetTemp函数计算[1]的值对应的温度值,计算公式在相应函数内有说明
*******************************************************************************/


/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_lib.h"
#include "stdio.h"


/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define ADC1_DR_Address    ((u32)0x4001244C)
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
vu16 AD_Value[2];
vu16 i="0";
s16  Temp;
u16  Volt;


/* Private function prototypes -----------------------------------------------*/
void RCC_Configuration(void);
void GPIO_Configuration(void);
void NVIC_Configuration(void);
void USART1_Configuration(void);
void ADC1_Configuration(void);
void DMA_Configuration(void);


int fputc(int ch, FILE *f);
void Delay(void);
u16 GetTemp(u16 advalue);
u16 GetVolt(u16 advalue);
/* Private functions ---------------------------------------------------------*/
/*******************************************************************************
* Function Name  : main
* Description    : Main program.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
int main(void)
{
    RCC_Configuration();
    GPIO_Configuration();
    NVIC_Configuration();
    USART1_Configuration();
    DMA_Configuration();
    ADC1_Configuration();
   
    //启动第一次AD转换
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    //因为已经配置好了DMA,接下来AD自动连续转换,结果自动保存在AD_Value处  
   
    while (1)
    {
        Delay();
        Temp = GetTemp(AD_Value[1]);
        Volt = GetVolt(AD_Value[0]);
        USART_SendData(USART1, 0x0c);       //清屏
        //注意,USART_SendData函数不检查是否发送完成
        //等待发送完成
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);


        printf("电压:%d.%d\t温度:%d.%d℃\r\n", \
            Volt/100, Volt%100, Temp/100, Temp%100);
       
    }
}


/*******************************************************************************
* Function Name  : 重定义系统putchar函数int fputc(int ch, FILE *f)
* Description    : 串口发一个字节
* Input          : int ch, FILE *f
* Output         :
* Return         : int ch
*******************************************************************************/
int fputc(int ch, FILE *f)
{
    //USART_SendData(USART1, (u8) ch);
    USART1->DR = (u8) ch;
   
    /* Loop until the end of transmission */
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
    {
    }


    return ch;
}


/*******************************************************************************
* Function Name  : Delay
* Description    : 延时函数
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void Delay(void)
{
    u32 i;
    for(i=0;i<0x4f0000;i++);
    return;
}                           


/*******************************************************************************
* Function Name  : GetTemp
* Description    : 根据ADC结果计算温度
* Input          : u16 advalue
* Output         :
* Return         : u16 temp
*******************************************************************************/
u16 GetTemp(u16 advalue)
{
    u32 Vtemp_sensor;
    s32 Current_Temp;
   
//    ADC转换结束以后,读取ADC_DR寄存器中的结果,转换温度值计算公式如下:
//          V25 - VSENSE
//  T(℃) = ------------  + 25
//           Avg_Slope
//   V25:  温度传感器在25℃时 的输出电压,典型值1.43 V。
//  VSENSE:温度传感器的当前输出电压,与ADC_DR 寄存器中的结果ADC_ConvertedValue之间的转换关系为:
//            ADC_ConvertedValue * Vdd
//  VSENSE = --------------------------
//            Vdd_convert_value(0xFFF)
//  Avg_Slope:温度传感器输出电压和温度的关联参数,典型值4.3 mV/℃。


    Vtemp_sensor = advalue * 330 / 4096;
    Current_Temp = (s32)(143 - Vtemp_sensor)*1000/34 + 2500;
    return (s16)Current_Temp;



/*******************************************************************************
* Function Name  : GetVolt
* Description    : 根据ADC结果计算电压
* Input          : u16 advalue
* Output         :
* Return         : u16 temp
*******************************************************************************/
u16 GetVolt(u16 advalue)
{
    return (u16)(advalue * 330 / 4096);
}
                         


/*******************************************************************************
* Function Name  : RCC_Configuration
* Description    : 系统时钟设置
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void RCC_Configuration(void)
{
    ErrorStatus HSEStartUpStatus;


    //使能外部晶振
    RCC_HSEConfig(RCC_HSE_ON);
    //等待外部晶振稳定
    HSEStartUpStatus = RCC_WaitForHSEStartUp();
    //如果外部晶振启动成功,则进行下一步操作
    if(HSEStartUpStatus==SUCCESS)
    {
        //设置HCLK(AHB时钟)=SYSCLK
        RCC_HCLKConfig(RCC_SYSCLK_Div1);


        //PCLK1(APB1) = HCLK/2
        RCC_PCLK1Config(RCC_HCLK_Div2);


        //PCLK2(APB2) = HCLK
        RCC_PCLK2Config(RCC_HCLK_Div1);
       
        //设置ADC时钟频率
        RCC_ADCCLKConfig(RCC_PCLK2_Div2);


        //FLASH时序控制
        //推荐值:SYSCLK = 0~24MHz   Latency="0"
        //        SYSCLK = 24~48MHz  Latency="1"
        //        SYSCLK = 48~72MHz  Latency="2"
        FLASH_SetLatency(FLASH_Latency_2);
        //开启FLASH预取指功能
        FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);


        //PLL设置 SYSCLK/1 * 9 = 8*1*9 = 72MHz
        RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
        //启动PLL
        RCC_PLLCmd(ENABLE);
        //等待PLL稳定
        while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
        //系统时钟SYSCLK来自PLL输出
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
        //切换时钟后等待系统时钟稳定
        while(RCC_GetSYSCLKSource()!=0x08);


       
    }


    //下面是给各模块开启时钟
    //启动GPIO
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | \
                           RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD,\
                           ENABLE);
    //启动AFIO
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    //启动USART1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    //启动DMA时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    //启动ADC1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);


}



/*******************************************************************************
* Function Name  : GPIO_Configuration
* Description    : GPIO设置
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;


    //PC口4567脚设置GPIO输出,推挽 2M
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);


    //KEY2 KEY3 JOYKEY
    //位于PD口的3 4 11-15脚,使能设置为输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_11 | GPIO_Pin_12 |\
        GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOD, &GPIO_InitStructure);


    //USART1_TX
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
   
    //USART1_RX
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
   
    //ADC_CH10--> PC0
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOC, &GPIO_InitStructure);


}


 


/*******************************************************************************
* Function Name  : NVIC_Configuration
* Description    : NVIC设置
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;


#ifdef  VECT_TAB_RAM
    // Set the Vector Table base location at 0x20000000
    NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else  /* VECT_TAB_FLASH  */
    // Set the Vector Table base location at 0x08000000
    NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
#endif


    //设置NVIC优先级分组为Group2:0-3抢占式优先级,0-3的响应式优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    //串口中断打开
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}



/*******************************************************************************
* Function Name  : USART1_Configuration
* Description    : NUSART1设置
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void USART1_Configuration(void)
{
    USART_InitTypeDef USART_InitStructure;
   
    USART_InitStructure.USART_BaudRate = 19200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(USART1, &USART_InitStructure);
   
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
   
    USART_Cmd(USART1, ENABLE);
}


/*******************************************************************************
* Function Name  : ADC1_Configuration
* Description    : ADC1设置(包括ADC模块配置和自校准)
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void ADC1_Configuration(void)
{
    ADC_InitTypeDef ADC_InitStructure;


    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  //连续转换开启
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 2;     //设置转换序列长度为2
    ADC_Init(ADC1, &ADC_InitStructure);
   
    //ADC内置温度传感器使能(要使用片内温度传感器,切忌要开启它)
    ADC_TempSensorVrefintCmd(ENABLE);
   
    //常规转换序列1:通道10
    ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_13Cycles5);
    //常规转换序列2:通道16(内部温度传感器),采样时间>2.2us,(239cycles)
    ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_239Cycles5);
   
    // Enable ADC1
    ADC_Cmd(ADC1, ENABLE);
    // 开启ADC的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
    ADC_DMACmd(ADC1, ENABLE);
   
    // 下面是ADC自动校准,开机后需执行一次,保证精度
    // Enable ADC1 reset calibaration register
    ADC_ResetCalibration(ADC1);
    // Check the end of ADC1 reset calibration register
    while(ADC_GetResetCalibrationStatus(ADC1));


    // Start ADC1 calibaration
    ADC_StartCalibration(ADC1);
    // Check the end of ADC1 calibration
    while(ADC_GetCalibrationStatus(ADC1));
    // ADC自动校准结束---------------
   
}


/*******************************************************************************
* Function Name  : DMA_Configuration
* Description    : DMA设置:从ADC模块自动读转换结果至内存
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void DMA_Configuration(void)
{
    DMA_InitTypeDef DMA_InitStructure;
   
    DMA_DeInit(DMA1_Channel1);
    DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AD_Value;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    //BufferSize=2,因为ADC转换序列有2个通道
    //如此设置,使序列1结果放在AD_Value[0],序列2结果放在AD_Value[1]
    DMA_InitStructure.DMA_BufferSize = 2;
    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;
    //循环模式开启,Buffer写满后,自动回到初始地址开始传输
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    //配置完成后,启动DMA通道
    DMA_Cmd(DMA1_Channel1, ENABLE);
}


 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/08/24/4480497.aspx


STM32开发板入门教程 - 内部温度传感器 收藏
 
废话少说 先看看他的参数
1.  STM32内部温度传感器与ADC的通道16相连,与ADC配合使用实现温度测量;
2.  测量范围–40~125℃,精度±1.5℃。
3.  温度传感器产生一个随温度线性变化的电压,转换范围在2V < VDDA < 3.6V之间。


转换公式如下图所示:


 


 
呵呵 其实 写代码的时候 公式直接简化就得啦  如果测量要求不怎么高的话 呵呵(其实高也高不了 呵呵)
我们都喜欢简单 简单明了  嘿嘿
简化的公式:  vu16 Temperature= (1.42 - ADC_Value*3.3/4096)*1000/4.35 + 25;
 


呵呵  重新说一下 过程:
1. 初始化ADC  初始化DMA (大家可以参考马七的ADC教程 点击这里)
2.  ADC_TempSensorVrefintCmd(ENABLE);   这个要开启哦  使能温度传感器和内部参考电压通道
3. 简单的数字滤波一下检测到的ADC的值
4. 按照刚才列出的公式计算  就OK啦 呵呵



第二步是做什么的呢?  看这个图就晓得啦
 
贴一下初始化的函数


/*******************************************************************************
* Function Name  : ADC_Configuration
* Description    : ADC_Configuration
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void ADC_Configuration(void)
{
  /* DMA1 channel1 configuration ----------------------------------------------*/
  DMA_DeInit(DMA1_Channel1);
  DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
  DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADCConvertedValue;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  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_Channel1, &DMA_InitStructure);
 
  /* Enable DMA1 channel1 */
  DMA_Cmd(DMA1_Channel1, ENABLE);
    
  /* ADC1 configuration ------------------------------------------------------*/
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_NbrOfChannel = 1;
  ADC_Init(ADC1, &ADC_InitStructure);
  /* ADC1 regular channel14 configuration */
  ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_55Cycles5);
  /* Enable the temperature sensor and vref internal channel */
  ADC_TempSensorVrefintCmd(ENABLE);
  /* Enable ADC1 DMA */
  ADC_DMACmd(ADC1, ENABLE);
 
  /* Enable ADC1 */
  ADC_Cmd(ADC1, ENABLE);
  /* Enable ADC1 reset calibaration register */  
  ADC_ResetCalibration(ADC1);
  /* Check the end of ADC1 reset calibration register */
  while(ADC_GetResetCalibrationStatus(ADC1));
  /* Start ADC1 calibaration */
  ADC_StartCalibration(ADC1);
  /* Check the end of ADC1 calibration */
  while(ADC_GetCalibrationStatus(ADC1));
    
  /* Start ADC1 Software Conversion */
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}



 



这个是抄袭马七的均值数字滤波函数 呵呵


/*******************************************************************************
* Function Name  : ADC_Filter
* Description    : ADC_Filter
* Input          : None
* Output         : None
* Return         : ADC Converted Value
*******************************************************************************/
u16 ADC_Filter(void)
{
    u16 result="0";
    u8 i;
    for(i=16;i>0;i--)
    {
        Delay_Ms(1);
        result += ADCConvertedValue;
    }
    return result/16;
}



 



转换结果 往串口发送显示 (写的很烂哈)


   ADC_Value = ADC_filter();
   
    vu16 Temperature= (1.42 - ADC_Value*3.3/4096)*1000/4.35 + 25;
    ADC_Value = Temperature;
                  
    a = ADC_Value/1000;
    b = (ADC_Value - a*1000)/100;
    c = (ADC_Value - a*1000 - b*100)/10;
    d = ADC_Value - a*1000 - b*100 - c*10;
                      
    Uart1_PutString("STM32 Chip Temperature = ",strlen("STM32 Chip Temperature = "));
    Uart1_PutChar(a+'0');
    Uart1_PutChar(b+'0');
    Uart1_PutChar(c+'0');
    Uart1_PutChar(d+'0');
    Uart1_PutString(" C\n",strlen(" C\n"));
 



本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/08/19/4462989.aspx


STM32开发板入门教程 - 串口通讯 UART 收藏
三种方式:查询,中断,DMA


通用同步异步收发器(USART)提供了一种灵活的方法来与使用工业标准NR 异步串行数据格式的外部设备之间进行全双工数据交换。 USART利用分数波特率发生器提供宽范围的波特率选择。
它支持同步单向通信和半双工单线通信。它也支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。用于多缓冲器配置的DMA方式,可以实现高速数据通信。


主要特性:
全双工的,异步通信
NR 标准格式
分数波特率发生器系统
       -发送和接收共用的可编程波特率,最高到4.5Mbits/s
可编程数据字长度(8位或9位)
可配置的停止位        -支持1或2个停止位
LIN主发送同步断开符的能力以及LIN从检测断开符的能力
       -   当USART硬件配置成LIN时,生成13位断开符;检测10/11位断开符
发送方为同步传输提供时钟
IRDA SIR 编码器解码器
       -   在正常模式下支持3/16位的持续时间
智能卡模拟功能
       -   智能卡接口支持ISO7816        -3标准里定义的异步协议智能卡
       -   智能卡用到的0.5和1.5个停止位
单线半双工通信
使用DMA的可配置的多缓冲器通信
       -   在保留的SRAM里利用集中式DMA缓冲接收/发送字节
单独的发送器和接收器使能位
检测标志
       -   接收缓冲器满
       -   发送缓冲器空
       -   传输结束标志
校验控制
       -   发送校验位
       -   对接收数据进行校验
四个错误检测标志
       -   溢出错误
       -   噪音错误
       -   帧错误
       -   校验错误
   10个带标志的中断源
       -   CTS改变
       -   LIN断开符检测
       -   发送数据寄存器
       -   发送完成
       -   接收数据寄存器
       -   检测到总线为空
       -   溢出错误
       -   帧错误
       -   噪音错误
       -   校验错误
多处理器通信       -        - 如果地址不匹配,则进入静默模式
从静默模式中唤醒(通过空闲总线检测或地址标志检测)
两种唤醒接收器的方式
       -   地址位(MSB)
       -   空闲总线


 



 



STM32的串口配置 也挺方便的


首先是配置UART的GPIO口
/*******************************************************************************
* Function Name   : UART1_GPIO_Configuration
* Description        : Configures the uart1 GPIO ports.
* Input                    : None
* Output                 : None
* Return                 : None
*******************************************************************************/
void UART1_GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// Configure USART1_Tx as alternate function push-pull
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);


// Configure USART1_Rx as input floating
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure); 
}


然后是配置串口参数



/* 如果使用查询的方式发送和接收数据 则不需要使用串口的中断 
   如果需要使用中断的方式发送和接收数据 则需要使能串口中断
    函数原形 void USART_ITConfig(USART_TypeDef* USARTx, u16 USART_IT, FunctionalState NewState)
    功能描述 使能或者失能指定的 USART 中断
  
       USART_IT                   描述
       USART_IT_PE       奇偶错误中断
       USART_IT_TXE    发送中断
       USART_IT_TC       传输完成中断
       USART_IT_RXNE 接收中断
       USART_IT_IDLE    空闲总线中断
       USART_IT_LBD    LIN中断检测中断
       USART_IT_CTS    CTS中断
       USART_IT_ERR    错误中断


*/



/*******************************************************************************
* Function Name   : UART1_Configuration
* Description        : Configures the uart1
* Input                    : None
* Output                 : None
* Return                 : None
*******************************************************************************/
void UART1_Configuration(void)
{


   USART_InitTypeDef USART_InitStructure;
   /* USART1 configured as follow:
       - BaudRate = 9600 baud 
       - Word Length = 8 Bits
       - One Stop Bit
       - No parity
       - Hardware flow control disabled (RTS and CTS signals)
       - Receive and transmit enabled
   */
   USART_InitStructure.USART_BaudRate = 9600;
   USART_InitStructure.USART_WordLength = USART_WordLength_8b;
   USART_InitStructure.USART_StopBits = USART_StopBits_1;
   USART_InitStructure.USART_Parity = USART_Parity_No ;
   USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
   USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 
   /* Configure the USART1*/
   USART_Init(USART1, &USART_InitStructure);


   /* Enable USART1 Receive and Transmit interrupts */
   USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);



   /* Enable the USART1 */
   USART_Cmd(USART1, ENABLE); 
}


 


发送一个字符
/*******************************************************************************
* Function Name   : Uart1_PutChar
* Description        : printf a char to the uart.
* Input                    : None
* Output                 : None
* Return                 : None
*******************************************************************************/
u8 Uart1_PutChar(u8 ch)
{
/* Write a character to the USART */
USART_SendData(USART1, (u8) ch);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
{
}
return ch;
}
 



发送一个字符串
/*******************************************************************************
* Function Name   : Uart1_PutString
* Description        : print a string to the uart1
* Input                    : buf为发送数据的地址 , len为发送字符的个数
* Output                 : None
* Return                 : None
*******************************************************************************/
void Uart1_PutString(u8* buf , u8 len)
{
for(u8 i="0";i<len;i++)
{
       Uart1_PutChar(*buf++);
}
}


 


如果UART使用中断发送数据 则需要修改stm32f10x_it.c 中的串口中断函数 并且需要修改void NVIC_Configuration(void)函数


在中断里面的处理 原则上是需要简短和高效 下面的流程是 如果接收到255个字符或者接收到回车符 则关闭中断 并且把标志位UartHaveData 置1


/*******************************************************************************
* Function Name   : USART1_IRQHandler
* Description        : This function handles USART1 global interrupt request.
* Input                    : None
* Output                 : None
* Return                 : None
*******************************************************************************/
void USART1_IRQHandler(void)
{
   if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
   {
/* Read one byte from the receive data register */
RxBuffer[ RxCounter ] = USART_ReceiveData(USART1);   
if( RxCounter == 0xfe || '\r' == RxBuffer[ RxCounter ] )
{
   /* Disable the USART1 Receive interrupt */
   USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
   RxBuffer[ RxCounter ] = '\0';
   UartHaveData = 1;
}


RxCounter++;
   }
}



修改NVIC_Configuration函数


/*******************************************************************************
* Function Name   : NVIC_Configuration
* Description        : Configures NVIC and Vector Table base location.
* Input                    : None
* Output                 : None
* Return                 : None
*******************************************************************************/
void NVIC_Configuration(void)
{
   NVIC_InitTypeDef NVIC_InitStructure;
 
#ifdef   VECT_TAB_RAM
/* Set the Vector Table base location at 0x20000000 */
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else   /* VECT_TAB_FLASH   */
/* Set the Vector Table base location at 0x08000000 */
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
#endif
 
   /* Configure the NVIC Preemption Priority Bits */ 
   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
 
   /* Enable the USART1 Interrupt */
   NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure);
 
}
 



至此 串口就可以工作起来了 附件中的程序功能是 开机后 从串口中输出2行信息 然后就等待接收串口数据 并且把接收到的数据发回到PC机上来 附件有2个 一个是查询方式的 一个是中断方式的


采用DMA方式进行串口通信


使用了DMA功能以后,用户程序中只需配置好DMA,开启传输后,再也不需要操心,10K数据完成后会有标志位或中断产生,期间可以做任何想做的事,非常方便。


 



本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/08/19/4462339.aspx


STM32学习笔记---SysTick定时器 收藏
Q:什么是SYSTick定时器?


SysTick 是一个24 位的倒计数定时器,当计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息。


Q:为什么要设置SysTick定时器?


(1)产生操作系统的时钟节拍


SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。在以前,大多操作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作为整个系统的时基。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。


(2)便于不同处理器之间程序移植。


Cortex‐M3处理器内部包含了一个简单的定时器。因为所有的CM3芯片都带有这个定时器,软件在不同 CM3器件间的移植工作得以化简。该定时器的时钟源可以是内部时钟(FCLK,CM3上的自由运行时钟),或者是外部时钟( CM3处理器上的STCLK信号)。


不过,STCLK的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同,你需要检视芯片的器件手册来决定选择什么作为时钟源。SysTick定时器能产生中断,CM3为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在CM3器件间的移植变得简单多了,因为在所有CM3产品间对其处理都是相同的。


(3)作为一个闹铃测量时间。


SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。要注意的是,当处理器在调试期间被喊停(halt)时,则SysTick定时器亦将暂停运作。


Q:Systick如何运行?


首先设置计数器时钟源,CTRL->CLKSOURCE(控制寄存器)。设置重载值(RELOAD寄存器),清空计数寄存器VAL(就是下图的CURRENT)。置CTRL->ENABLE位 开始计时。


如果是中断则允许Systick中断,在中断例程中处理。如采用查询模式则不断读取控制寄存器的COUNTFLAG标志位,判断是否计时至零。或者采取下列一种方法


当SysTick 定时器从1 计到0 时,它将把COUNTFLAG 位置位;而下述方法可以清零之:


1. 读取SysTick 控制及状态寄存器(STCSR)


2. 往SysTick 当前值寄存器(STCVR)中写任何数据


只有当VAL值为0时,计数器自动重载RELOAD。


Q:如何使用SysTicks作为系统时钟?


SysTick 的最大使命,就是定期地产生异常请求,作为系统的时基。OS 都需要这种“滴答”来推动任务和时间的管理。如欲使能SysTick 异常,则把STCSR.TICKINT 置位。另外,如果向量表被重定位到SRAM 中,还需要为SysTick 异常建立向量,提供其服务例程的入口地址。


Q:如何使用SysTick完成一段延时?


查询方式 参考:http://blog.ednchina.com/atom6037/188271/message.aspx


中断方式 参考:


初始化函数SysTick_Configuration(void)放在while()循环外,执行一次:


view plaincopy to clipboardprint?
void SysTick_Configuration(void)   
{   
  /* Select AHB clock(HCLK) as SysTick clock source 设置AHB时钟为SysTick时钟*/  
  SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);   
  
  /* Set SysTick Priority to 3 设置SysTicks中断抢占优先级 3, 从优先级0*/  
  NVIC_SystemHandlerPriorityConfig(SystemHandler_SysTick, 3, 0);   
      
  /* SysTick interrupt each 1ms with HCLK equal to 72MHz 每1ms发生一次SysTick中断*/  
  SysTick_SetReload(72000);   
  
  /* Enable the SysTick Interrupt */  
  SysTick_ITConfig(ENABLE);   
}  
void SysTick_Configuration(void)
{
  /* Select AHB clock(HCLK) as SysTick clock source 设置AHB时钟为SysTick时钟*/
  SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);


  /* Set SysTick Priority to 3 设置SysTicks中断抢占优先级 3, 从优先级0*/
  NVIC_SystemHandlerPriorityConfig(SystemHandler_SysTick, 3, 0);
  
  /* SysTick interrupt each 1ms with HCLK equal to 72MHz 每1ms发生一次SysTick中断*/
  SysTick_SetReload(72000);


  /* Enable the SysTick Interrupt */
  SysTick_ITConfig(ENABLE);
}
延时函数,需要延时处调用:


view plaincopy to clipboardprint?
void Delay(u32 nTime)   
{   
  /* Enable the SysTick Counter 允许SysTick计数器*/  
  SysTick_CounterCmd(SysTick_Counter_Enable);   
     
  TimingDelay = nTime;   
  
  while(TimingDelay != 0)   
    ;  //等待计数至0   
  
  /* Disable the SysTick Counter 禁止SysTick计数器*/  
  SysTick_CounterCmd(SysTick_Counter_Disable);   
  /* Clear the SysTick Counter 清零SysTick计数器*/  
  SysTick_CounterCmd(SysTick_Counter_Clear);   
}  
void Delay(u32 nTime)
{
  /* Enable the SysTick Counter 允许SysTick计数器*/
  SysTick_CounterCmd(SysTick_Counter_Enable);
 
  TimingDelay = nTime;


  while(TimingDelay != 0)
    ;  //等待计数至0


  /* Disable the SysTick Counter 禁止SysTick计数器*/
  SysTick_CounterCmd(SysTick_Counter_Disable);
  /* Clear the SysTick Counter 清零SysTick计数器*/
  SysTick_CounterCmd(SysTick_Counter_Clear);
}
中断函数,定时器减至零时调用,放在stm32f10x_it.c文件中


view plaincopy to clipboardprint?
void SysTickHandler(void)   
{   
  TimingDelay--;   
}  



本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/08/19/4462035.aspx


对于STM32别名区的理解——转帖+优化 收藏
1. 什么是位段、位带别名区?
2. 它有什么好处?


答1: 是这样的,记得MCS51吗? MCS51就是有位操作,以一位(BIT)为数据对象的操作,
      MCS51可以简单的将P1口的第2位独立操作: P1.2=0;P1.2=1 ; 就是这样把P1口的第三个脚(BIT2)置0置。
      而现在STM32的位段、位带别名区就为了实现这样的功能。      
      对象可以是SRAM,I/O外设空间。实现对这些地方的某一位的操作。
      它是这样的。在寻址空间(32位地址是 4GB )另一地方,取个别名区空间,从这地址开始处,每一个字(32BIT)
      就对应SRAM或I/O的一位。
      
      这样呢,1MB SRAM就 可以有32MB的对应别名区空间,就是1位膨胀到32位(1BIT 变为1个字)
      我们对这个别名区空间开始的某一字操作,置0或置1,就等于它映射的SRAM或I/O相应的某地址的某一位的操作。
      
答2:  简单来说,可以把代码缩小, 速度更快,效率更高,更安全。
      一般操作要6条指令,而使用 位带别名区只要4条指令。   
      一般操作是  读-改-写  的方式, 而位带别名区是 写 操作。防止中断对读-改-写  的方式的影响。


//  STM32支持了位带操作(bit_band),有两个区中实现了位带。其中一个是SRAM 区的最低1MB 范围,第二个则是片内外设
//  区的最低1MB 范围。这两个区中的地址除了可以像普通的RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区
//  把每个比特膨胀成一个32 位的字。
//
//  每个比特膨胀成一个32 位的字,就是把  1M  扩展为 32M ,
//
//  于是;RAM地址 0X200000000(一个字节)扩展到8个32 位的字,它们是:(STM32中的SRAM依然是8位的,所以RAM中任一地址对应一个字节内容)
//   0X220000000 ,0X220000004,0X220000008,0X22000000C,0X220000010,0X220000014, 0X220000018,0X22000001C



// 支持位带操作的两个内存区的范围是:
// 0x2000_0000‐0x200F_FFFF(SRAM 区中的最低1MB)
// 0x4000_0000‐0x400F_FFFF(片上外设区中的最低1MB)


/*



对SRAM 位带区的某个比特,记它所在字节地址为A,位序号
在别名区的地址为:
AliasAddr= 0x22000000 +((A‐0x20000000)*8+n)*4 =0x22000000+ (A‐0x20000000)*32 + n*4
对于片上外设位带区的某个比特,记它所在字节的地址为A,位序号为n(0<=n<=7),则该比特
在别名区的地址为:
AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4
上式中,“*4”表示一个字为4 个字节,“*8”表示一个字节中有8 个比特。


// 把“位带地址+位序号”转换别名地址宏
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
//把该地址转换成一个指针
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
// MEM_ADDR(BITBAND( (u32)&CRCValue,1)) = 0x1;


 


例如点亮LED


// 使用STM32库
   GPIO_ResetBits(GPIOC, GPIO_Pin_4); //关LED5
   GPIO_SetBits(GPIOC, GPIO_Pin_7);   //开LED2


// 一般读操作
    STM32_Gpioc_Regs->bsrr.bit.BR4 =1;// 1:清除对应的ODRy位为0
    STM32_Gpioc_Regs->bsrr.bit.BS7 =1;// 1:设置对应的ODRy位为1


//如果使用 位带别名区操作
  STM32_BB_Gpioc_Regs->BSRR.BR[4] =1;// 1:清除对应的ODRy位为0
  STM32_BB_Gpioc_Regs->BSRR.BS[7] =1;// 1:设置对应的ODRy位为1


代码比STM32库 高效 十倍 !


对内存变量的位操作。


   1. // SRAM  变量
   2.


   3. long CRCValue;
   4.


   5. // 把“位带地址+位序号”转换别名地址宏
   6. #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
   7. //把该地址转换成一个指针
   8. #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
   9.


  10. // 对32位变量 的BIT1 置 1 :
  11.


  12. MEM_ADDR(BITBAND( (u32)&CRCValue,1)) = 0x1;
  13.


  14. //对任意一位( 第23位 ) 判断:
  15.


  16. if(MEM_ADDR(BITBAND( (u32)&CRCValue,23))==1)
  17. {
  18.


  19. }


STM32启动模式 收藏
我们玩ARM9,一般都是在内存里调试程序,速度飞快。STM32下也可以这样,虽说现在的flash寿命已经很长了,但flash中调试烧录程序还是一个很慢的过程,有时候程序上一个小小的改动要花上几倍的时间下载代码,这确实是不能忍受的。
  我们也可以在开发STM32时,在内存中调试程序。
  {
STM32这颗Cortex-M3控制器,与其他许多ARM一样,提供了BOOT0和BOOT1两个管脚用于启动选择。
BOOT1=x  BOOT0=0  从用户闪存启动,这是正常的工作模式。
BOOT1=0  BOOT0=1  从系统存储器启动,这种模式启动的程序功能由厂家设置。(用于串口ISP)
BOOT1=1  BOOT0=1  从内置SRAM启动,这种模式可以用于调试。
  在芯片上电复位时,BOOT0和BOOT1两个管脚的状态将决定芯片从何处启动。


1.当BOOT0和BOOT1均设置为逻辑1时,系统将从内置SRAM中启动,这是代码内存调试的第一个条件。
}
经多次确认,BOOT0和BOOT1状态与内存中调试无直接联系


2.然后,我们需要在代码中设置正确的中断向量表位置。中断向量表通常被放置在用户程序的开始,所以flash中运行时,向量表位于0x08000000处,而当代码被放置在SRAM中运行时,他的位置就成了0x20000000。在初始化NVIC时,我们可以放置如下代码,定义向量表的位置


    NVIC_SetVectorTable(0x20000000 , 0x0);

    NVIC_SetVectorTable(0x08000000 , 0x0);


3. 在编译器中,要进行正确的设置。这里以IAR 5.4为例说明。
    3.1.  工程选项中Linker项,Config选项卡中,指定Linker Configuration File为stm32f10x_ram.icf, 该文件在FWLIB安装包中可以获得,IAR4和5分别有不同的配置文件。
    3.2.  同样是工程选项中Debug项,Download选项卡中,去掉所有钩子,不下载代码到flash


  经过以上步骤,代码就可以在内存中调试,下载速度飞快,调试速度也比flash中快了不少!


STM32中用到的Cortex-M3寄存器说明 收藏
在STM32的固件库中定义了三个结构体与这三个寄存器组相对应,这三个结构体与ARM手册中寄存器的对应关系如下:


 


一、NVIC寄存器组


STM32的固件库中有如下定义:


typedef struct
{
  vu32 ISER[2];
  u32 RESERVED0[30];
  vu32 ICER[2];
  u32 RSERVED1[30];
  vu32 ISPR[2];
  u32 RESERVED2[30];
  vu32 ICPR[2];
  u32 RESERVED3[30];
  vu32 IABR[2];
  u32 RESERVED4[62];
  vu32 IPR[11];
} NVIC_TypeDef;


它们对应ARM手册中的名称为
ISER = Interrupt Set-Enable Registers
ICER = Interrupt Clear-Enable Registers
ISPR = Interrupt Set-Pending Register
ICPR = Interrupt Clear-Pending Register
IABR = Active Bit Register
IPR = Interrupt Priority Registers


每个寄存器有240位,以Interrupt Set-Enable Registers说明,ISER[0]对应中断源0~31,ISER[1]对应中断源32~63,STM32只有60个中断源,所以没有ISER[2:7]。


参考STM32技术参考手册中的中断向量表,中断源的位置为:
位置0 - WWDG = Window Watchdog interrupt
位置1 - PVD = PVD through EXTI Line detection interrupt
位置2 - TAMPER = Tamper interrupt
......
位置58 - DMA2_Channel3 = DMA2 Channel3 global interrupt
位置59 - DMA2_Channel4_5 = DMA2 Channel4 and DMA2 Channel5 global interrupts


 


二、系统控制寄存器组


STM32的固件库中有如下定义:


typedef struct
{
  vuc32 CPUID;
  vu32 ICSR;
  vu32 VTOR;
  vu32 AIRCR;
  vu32 SCR;
  vu32 CCR;
  vu32 SHPR[3];
  vu32 SHCSR;
  vu32 CFSR;
  vu32 HFSR;
  vu32 DFSR;
  vu32 MMFAR;
  vu32 BFAR;
  vu32 AFSR;
} SCB_TypeDef; /* System Control Block Structure */


它们对应ARM手册中的名称为


CPUID = CPUID Base Register
ICSR = Interrupt Control State Register
VTOR = Vector Table Offset Register
AIRCR = Application Interrupt/Reset Control Register
SCR = System Control Register
CCR = Configuration Control Register
SHPR = System Handlers Priority Register
SHCSR = System Handler Control and State Register
CFSR = Configurable Fault Status Registers
HFSR = Hard Fault Status Register
DFSR = Debug Fault Status Register
MMFAR = Mem Manage Address Register
BFAR = Bus Fault Address Register
AFSR = Auxiliary Fault Status Register


 


三、系统时钟寄存器组


STM32的固件库中有如下定义:


typedef struct
{
  vu32 CTRL;
  vu32 LOAD;
  vu32 VAL;
  vuc32 CALIB;
} SysTick_TypeDef;



它们对应ARM手册中的名称为


CTRL = SysTick Control and Status Register
LOAD = SysTick Reload Value Register
VAL = SysTick Current Value Register


STM32学习----时钟 收藏
在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。
①、HSI是高速内部时钟,RC振荡器,频率为8MHz。
②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
③、LSI是低速内部时钟,RC振荡器,频率为40kHz。
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。
⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。


其中40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外,实时时钟RTC的时钟源还可以选择LSE,或者是HSE的128分频。RTC的时钟源通过RTCSEL[1:0]来选择。


STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。


另外,STM32还可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。


系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可选择为PLL输出、HSI或者HSE。系统时钟最大频率为72MHz,它通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。其中AHB分频器输出的时钟送给5大模块使用:
①、送给AHB总线、内核、内存和DMA使用的HCLK时钟。
②、通过8分频后送给Cortex的系统定时器时钟。
③、直接送给Cortex的空闲运行时钟FCLK。
④、送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。
⑤、送给APB2分频器。APB2分频器可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer)1倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器1使用。另外,APB2分频器还有一路输出供ADC分频器使用,分频后送给ADC模块使用。ADC
分频器可选择为2、4、6、8分频。


在以上的时钟输出中,有很多是带使能控制的,例如AHB总线时钟、内核时钟、各种APB1外设、APB2外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。


需要注意的是定时器的倍频器,当APB的分频为1时,它的倍频值为1,否则它的倍频值就为2。


连接在APB1(低速外设)上的设备有:电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3、SPI2、窗口看门狗、Timer2、Timer3、Timer4。注意USB模块虽然需要一个单独的48MHz时钟信号,但它应该不是供USB模块工作的时钟,而只是提供给串行接口引擎(SIE)使用的时钟。USB模块工作的时
钟应该是由APB1提供的。


连接在APB2(高速外设)上的设备有:UART1、SPI1、Timer1、ADC1、ADC2、所有普通IO口(PA~PE)、第二功能IO口。


下图为STM32芯片的时钟结构图。从图中可以直观的看出STM32的时钟封装。



本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zyboy2000/archive/2009/08/09/4428808.aspx


基于STM32的PWM输出【转】

2009年08月28日 星期五 16:54






c) 初始化函数定义:
void TIM_Configuration(void);   //定义TIM初始化函数
d) 初始化函数调用:
TIM_Configuration();   //TIM初始化函数调用
e) 初始化函数,不同于前面模块,TIM的初始化分为两部分——基本初始化和通道初始化:
void TIM_Configuration(void)//TIM初始化函数
{
   TIM_TimeBaseInitTypeDef   TIM_TimeBaseStructure;//定时器初始化结构
   TIM_OCInitTypeDef   TIM_OCInitStructure;//通道输出初始化结构

//TIM3初始化
   TIM_TimeBaseStructure.TIM_Period = 0xFFFF;        //周期0~FFFF
   TIM_TimeBaseStructure.TIM_Prescaler = 5;       //时钟分频
   TIM_TimeBaseStructure.TIM_ClockDivision = 0;    //时钟分割
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//模式
   TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //基本初始化
   TIM_ITConfig(TIM3, TIM_IT_CC4, ENABLE);//打开中断,中断需要这行代码
  
//TIM3通道初始化
   TIM_OCStructInit(& TIM_OCInitStructure);                                              //默认参数
   TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                    //工作状态
   TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;       //设定为输出,需要PWM输出才需要这行代码
   TIM_OCInitStructure.TIM_Pulse = 0x2000;                   //占空长度
   TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;                 //高电平
   TIM_OC4Init(TIM3, &TIM_OCInitStructure);                   //通道初始化

   TIM_Cmd(TIM3, ENABLE);                                                                      //启动TIM3
}

f) RCC初始化函数中加入TIM时钟开启:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM3, ENABLE);
g) GPIO里面将输入和输出管脚模式进行设置。信号:AF_PP,50MHz。
h) 使用中断的话在NVIC里添加如下代码:

//打开TIM2中断
   NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel;   //通道
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;//占先级
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;           //响应级
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;       //启动
   NVIC_Init(&NVIC_InitStructure);                                            //初始化

中断代码:
void TIM2_IRQHandler(void)
{
   if (TIM_GetITStatus(TIM2, TIM_IT_CC4) != RESET)    //判断中断来源
   {
TIM_ClearITPendingBit(TIM2, TIM_IT_CC4);          //清除中断标志
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_11)));//变换LED色彩
IC4value = TIM_GetCapture4(TIM2);                //获取捕捉数值
   }  
}

i) 简单应用:
//改变占空比
TIM_SetCompare4(TIM3, 变量);

j) 注意事项:
管脚的IO输出模式是根据应用来定,比如如果用PWM输出驱动LED则应该将相应管脚设为AF_PP,否则单片机没有输出。

STM32学习笔记之 串口通讯

2009年08月28日 星期五 16:00





参考开发板例子写的串口通讯的小程序。主要功能是把PC机发送的数据接收后再返回给PC机参数9600,8,1,N。


/************************************************************************
     Copyright (c) 2008 wormchen             
     All rights reserved            
文 件 名: main.c             
说    明: 串口发送接收数据 将PC端发来的数据返回给PC         
主要硬件: EMSTM32V1+miniSTMV100(外部8MRC)        
编译环境: MDK3.10             
当前版本: 1.0                     
************************************************************************/
#include



void RCC_Config(void);
void GPIO_Config(void);
void USART_Config(void);
void Put_String(u8 *p);


int main(void)
{
RCC_Config();
GPIO_Config();
USART_Config();
Put_String("\r\n请发送数据_\r\n");
while(1)
{
   while(1)  
   {
    if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET)
    {
     
      USART_SendData(USART2, USART_ReceiveData(USART2));
    }
   }
}
}
/*************************************************
函数: void RCC_Config(void)
功能: 配置系统时钟
参数: 无
返回: 无
**************************************************/
void RCC_Config(void)
{
ErrorStatus HSEStartUpStatus;//定义外部高速晶体启动状态枚举变量
RCC_DeInit();//复位RCC外部设备寄存器到默认值
RCC_HSEConfig(RCC_HSE_ON); //打开外部高速晶振
HSEStartUpStatus = RCC_WaitForHSEStartUp();//等待外部高速时钟准备好
if(HSEStartUpStatus == SUCCESS)//外部高速时钟已经准别好
   {


    RCC_HCLKConfig(RCC_SYSCLK_Div1);//配置AHB(HCLK)时钟等于==SYSCLK
    RCC_PCLK2Config(RCC_HCLK_Div1); //配置APB2(PCLK2)钟==AHB时钟
    RCC_PCLK1Config(RCC_HCLK_Div2);//配置APB1(PCLK1)钟==AHB1/2时钟
    
     
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
    //配置PLL时钟 == 外部高速晶体时钟*9


      RCC_PLLCmd(ENABLE);//使能PLL时钟
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) //等待PLL时钟就绪
       {
       }
      RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);//配置系统时钟 = PLL时钟
  
      while(RCC_GetSYSCLKSource() != 0x08) //检查PLL时钟是否作为系统时钟
       {
       }
   }
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE);
   //打开GPIOD和AFIO时钟
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);//使能串口2时钟
}
/*************************************************
函数: void GPIO_Config(void)
功能: GPIO配置
参数: 无
返回: 无
**************************************************/
void GPIO_Config(void)
{
//设置RTS(PD.04),Tx(PD.05)为推拉输出模式
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO初始化结构体
GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE);//使能GPIO端口映射USART2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;//选择PIN4 PIN5
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //引脚频率50M
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//引脚设置推拉输出
GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化GPIOD


//配置CTS (PD.03),USART2 Rx (PD.06)为浮点输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOD, &GPIO_InitStructure);
}
/*************************************************
函数: void USART_Config(void)
功能: USART配置
参数: 无
返回: 无
**************************************************/
void USART_Config(void)
{
USART_InitTypeDef USART_InitStructure; //定义串口初始化结构体
USART_InitStructure.USART_BaudRate = 9600;//波特率9600
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据
USART_InitStructure.USART_StopBits = USART_StopBits_1;//1个停止位
USART_InitStructure.USART_Parity = USART_Parity_No ; //无校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
//禁用RTSCTS硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//使能发送接收
USART_InitStructure.USART_Clock = USART_Clock_Disable; //串口时钟禁止
USART_InitStructure.USART_CPOL = USART_CPOL_Low; //时钟下降沿有效
USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;//数据在第二个时钟沿捕捉
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
//最后数据位的时钟脉冲不输出到SCLK引脚
USART_Init(USART2, &USART_InitStructure);//初始化串口2
USART_Cmd(USART2, ENABLE);//串口2使能
}
/*************************************************
函数: void Put_String(void)
功能: 向串口输出字符串
参数: 无
返回: 无
**************************************************/
void Put_String(u8 *p)
{
while(*p)
{
   USART_SendData(USART2, *p++);
   while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET)
   {
   
   }
}
}


STM32的学习心得之TIMx(通用定时器)基本的配置

2009年08月27日 星期四 15:21



定时器的基本设置

1、TIM_TimeBaseStructure.TIM_Prescaler = 0x0;//时钟预分频数 例如:时钟频率=72/(时钟预分频+1)
2、TIM_TimeBaseStructure.TIM_CounterMode = TIM1_CounterMode_Up; //定时器模式 向上计数
3、TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 自动重装载寄存器周期的值(定时时间) 累计 0xFFFF个频率后产生个更新或者中断(也是说定时时间到)
4、TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; //时间分割值
5、TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);//初始化定时器2


6、TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //打开中断 溢出中断


7、TIM_Cmd(TIM2, ENABLE);//打开定时器


此外要记住一定要打开定时器的时钟(RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);),定时器的频率的可以编程的,有对应的模式设置和中断处理。


STM32的时钟系统分析(程序)

2009年08月27日 星期四 15:17


STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。

  ①、HSI是高速内部时钟,RC振荡器,频率为8MHz。


  ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。


  ③、LSI是低速内部时钟,RC振荡器,频率为40kHz。


  ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。


  ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。


  其中40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外,实时时钟RTC的时钟源还可以选择LSE,或者是HSE的128分频。RTC的时钟源通过RTCSEL[1:0]来选择。


  STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。


  另外,STM32还可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。


  系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可选择为PLL输出、HSI或者HSE。系统时钟最大频率为72MHz,它通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。其中AHB分频器输出的时钟送给5大模块使用:


  ①、送给AHB总线、内核、内存和DMA使用的HCLK时钟。


  ②、通过8分频后送给Cortex的系统定时器时钟。


  ③、直接送给Cortex的空闲运行时钟FCLK。


  ④、送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。


  ⑤、送给APB2分频器。APB2分频器可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer)1倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器1使用。另外,APB2分频器还有一路输出供ADC分频器使用,分频后送给ADC模块使用。ADC分频器可选择为2、4、6、8分频。


  在以上的时钟输出中,有很多是带使能控制的,例如AHB总线时钟、内核时钟、各种APB1外设、APB2外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。


  需要注意的是定时器的倍频器,当APB的分频为1时,它的倍频值为1,否则它的倍频值就为2。


  连接在APB1(低速外设)上的设备有:电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3、SPI2、窗口看门狗、Timer2、Timer3、Timer4。注意USB模块虽然需要一个单独的48MHz时钟信号,但它应该不是供USB模块工作的时钟,而只是提供给串行接口引擎(SIE)使用的时钟。USB模块工作的时钟应该是由APB1提供的。


  连接在APB2(高速外设)上的设备有:UART1、SPI1、Timer1、ADC1、ADC2、所有普通IO口(PA~PE)、第二功能IO口。


/*******************************************************************************
* Function Name : RCC_Configuration
* Description    : Configures the different system clocks.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void RCC_Configuration(void)
{
ErrorStatus HSEStartUpStatus;


/* RCC system reset(for debug purpose) */
// RCC_DeInit();


/* Enable HSE */
RCC_HSEConfig(RCC_HSE_ON);


/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();


if(HSEStartUpStatus == SUCCESS)
{
    /* HCLK = SYSCLK */
    RCC_HCLKConfig(RCC_SYSCLK_Div1);


    /* PCLK2 = HCLK */
    RCC_PCLK2Config(RCC_HCLK_Div1);


    /* PCLK1 = HCLK/2 */
    RCC_PCLK1Config(RCC_HCLK_Div2);


    /* ADCCLK = PCLK2/6 */
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);


    /* Flash 2 wait state */
    FLASH_SetLatency(FLASH_Latency_2);


    /* Enable Prefetch Buffer */
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);


    /* PLLCLK = 8MHz * 9 = 72 MHz */
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);//Pll在最后设置


    /* Enable PLL */
    RCC_PLLCmd(ENABLE);


    /* Wait till PLL is ready */
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
    {
    }


    /* Select PLL as system clock source */
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);


    /* Wait till PLL is used as system clock source */
    while(RCC_GetSYSCLKSource() != 0x08)
    {
    }
}


/* Enable GPIOA, GPIOB, GPIOC, GPIOD, GPIOE and AFIO clocks */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC
         | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE);


/* TIM2 clocks enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);


/* CAN Periph clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN, ENABLE);
}


  下图是STM32用户手册中的时钟系统结构图,通过该图可以从总体上掌握STM32的时钟系统。




874542f344f60eea0a46e0ce.jpg

STM32时钟控制RCC

2009年08月27日 星期四 15:15



对于单片机系统来说,CPU和总线以及外设的时钟设置是非常重要的,因为没有时钟就没有时序,组合电路能干什么想必各位心里都清楚。其实时钟的学习这部分应该提前一些,但由于一开始时间比较短,有些急于求成,所以直接使用了万利给的例程,姑且跳过了这一步。介于下面我计划要学习的任务都涉及到兆级的高速传输,例如全速USB,DMA等等,所以不能再忽略时钟啦,必须要仔细研究一下。

我学习RCC的参考资料:
技术文档0427及其中文翻译版STM32F10xxx_Library_Manual_ChineseV2的第十五章和RM0008_CH参考手册。

准备知识:
片上总线标准种类繁多,而由ARM公司推出的AMBA片上总线受到了广大IP开发商和SoC系统集成者的青睐,已成为一种流行的工业标准片上结构。AMBA规范主要包括了AHB(Advanced High performance Bus)系统总线和APB(Advanced Peripheral Bus)外围总线。二者分别适用于高速与相对低速设备的连接。


由于时钟是一个由内而外的东西,具体设置要从寄存器开始。

RCC 寄存器结构,RCC_TypeDeff,在文件“stm32f10x_map.h”中定义如下:
typedef struct
{
vu32 CR;
vu32 CFGR;
vu32 CIR;
vu32 APB2RSTR;
vu32 APB1RSTR;
vu32 AHBENR;
vu32 APB2ENR;
vu32 APB1ENR;
vu32 BDCR;
vu32 CSR;
} RCC_TypeDef;

这些寄存器的具体定义和使用方式参见芯片手册,在此不赘述,因为C语言的开发可以不和他们直接打交道,当然如果能够加以理解和记忆,无疑是百利而无一害。

相信细心的朋友早就发现板子上只有8Mhz的晶振,而增强型最高工作频率为72Mhz,显然需要用PLL倍频9倍,这些设置都需要在初始化阶段完成。为了方便说明,我借用一下例程的RCC设置函数,并用中文注释的形式加以说明:

/*******************************************************************************
* Function Name : Set_System
* Description    : Configures Main system clocks & power
* Input          : None.
* Return         : None.
*******************************************************************************/
//在此指出上面的注释头应该是复制过来的,写错了...不过没关系,反正没参数需要说明,重要的是函数体。
static void RCC_Config(void)
{


/* 这里是重置了RCC的设置,类似寄存器复位 */
RCC_DeInit();


/* 使能外部高速晶振 */
RCC_HSEConfig(RCC_HSE_ON);


/* 等待高速晶振稳定 */
HSEStartUpStatus = RCC_WaitForHSEStartUp();


if (HSEStartUpStatus == SUCCESS)
{
    /* 使能flash预读取缓冲区 */
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);


    /* 令Flash处于等待状态,2是针对高频时钟的,这两句跟RCC没直接关系,可以暂且略过 */
    FLASH_SetLatency(FLASH_Latency_2);


    /* HCLK = SYSCLK 设置高速总线时钟=系统时钟*/
    RCC_HCLKConfig(RCC_SYSCLK_Div1);


    /* PCLK2 = HCLK 设置低速总线2时钟=高速总线时钟*/
    RCC_PCLK2Config(RCC_HCLK_Div1);


    /* PCLK1 = HCLK/2 设置低速总线1的时钟=高速时钟的二分频*/
    RCC_PCLK1Config(RCC_HCLK_Div2);


    /* ADCCLK = PCLK2/6 设置ADC外设时钟=低速总线2时钟的六分频*/
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);


    /* Set PLL clock output to 72MHz using HSE (8MHz) as entry clock */
    //上面这句例程中缺失了,但却很关键
    /* 利用锁相环讲外部8Mhz晶振9倍频到72Mhz */
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);


    /* Enable PLL 使能锁相环*/
    RCC_PLLCmd(ENABLE);



    /* Wait till PLL is ready 等待锁相环输出稳定*/
    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
    {}


    /* Select PLL as system clock source 将锁相环输出设置为系统时钟 */
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);


    /* Wait till PLL is used as system clock source 等待校验成功*/
    while (RCC_GetSYSCLKSource() != 0x08)
    {}
}

    /* Enable FSMC, GPIOD, GPIOE, GPIOF, GPIOG and AFIO clocks */
    //使能外围接口总线时钟,注意各外设的隶属情况,不同芯片的分配不同,到时候查手册就可以
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE |
                         RCC_APB2Periph_GPIOF | RCC_APB2Periph_GPIOG |
                         RCC_APB2Periph_AFIO, ENABLE);
}


由上述程序可以看出系统时钟的设定是比较复杂的,外设越多,需要考虑的因素就越多。同时这种设定也是有规律可循的,设定参数也是有顺序规范的,这是应用中应当注意的,例如PLL的设定需要在使能之前,一旦PLL使能后参数不可更改。

经过此番设置后,由于我的电路板上是8Mhz晶振,所以系统时钟为72Mhz,高速总线和低速总线2都为72Mhz,低速总线1为36Mhz,ADC时钟为12Mhz,USB时钟经过1.5分频设置就可以实现48Mhz的数据传输。

一般性的时钟设置需要先考虑系统时钟的来源,是内部RC还是外部晶振还是外部的振荡器,是否需要PLL。然后考虑内部总线和外部总线,最后考虑外设的时钟信号。遵从先倍频作为CPU时钟,然后在由内向外分频,下级迁就上级的原则有点儿类似PCB制图的规范化要求,在这里也一样。

 


STM32资料三(转载)

2009年08月27日 星期四 13:26




STM32笔记之十二:时钟不息工作不止,systic时钟应用


a) 目的:使用系统时钟来进行两项实验——周期执行代码与精确定时延迟。


b) 初始化函数定义:


void SysTick_Configuration(void);


c) 初始化函数调用:


SysTick_Configuration();


d) 初始化函数:


void SysTick_Configuration(void)


{


   SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);//时钟除8


   SysTick_SetReload(250000);                                                 //计数周期长度


   SysTick_CounterCmd(SysTick_Counter_Enable);                //启动计时器


   SysTick_ITConfig(ENABLE);                                                     //打开中断


}


e) 在NVIC的初始化函数里面增加以下代码打开相关中断:


NVIC_SystemHandlerPriorityConfig(SystemHandler_SysTick, 1, 0);//中断等级设置,一般设置的高一些会少受其他影响


f) 在stm32f10x_it.c文件中找到void SysTickHandler 函数


void SysTickHandler(void)


{


执行代码


}


g) 简单应用:精确延迟函数,因为systic中断往往被用来执行周期循环代码,所以一些例程中使用其中断的启动和禁止来编写的精确延时函数实际上不实用,我自己编写了精确计时函数反而代码更精简,思路更简单。思路是调用后,变量清零,然后使用时钟来的曾变量,不断比较变量与延迟的数值,相等则退出函数。代码和步骤如下:


i. 定义通用变量:u16 Tic_Val=0; //变量用于精确计时


ii. 在stm32f10x_it.c文件中相应定义:


extern u16 Tic_Val;//在本文件引用MAIN.c定义的精确计时变量


iii. 定义函数名称:void Tic_Delay(u16 Tic_Count);//精确延迟函数


iv. 精确延时函数:


void Tic_Delay(u16 Tic_Count)             //精确延时函数


{ Tic_Val=0;                         //变量清零


   while(Tic_Val != Tic_Count){printf("");}//计时


}


v. 在stm32f10x_it.c文件中void SysTickHandler 函数里面添加


    Tic_Val++;//变量递增


vi. 调用代码:Tic_Delay(10); //精确延时


vii. 疑问:如果去掉计时行那个没用的printf("");函数将停止工作,这个现象很奇怪


C语言功底问题。是的,那个“注意事项”最后的疑问的原因就是这个


Tic_Val应该改为vu16


while(Tic_Val != Tic_Count){printf("");}//计时


就可以改为:


while(Tic_Val != Tic_Count);              //检查变量是否计数到位


STM32笔记之十三:恶搞,两只看门狗


a) 目的:


了解两种看门狗(我叫它:系统运行故障探测器和独立系统故障探测器,新手往往被这个并不形象的象形名称搞糊涂)之间的区别和基本用法。


b) 相同:


都是用来探测系统故障,通过编写代码定时发送故障清零信号(高手们都管这个代码叫做“喂狗”),告诉它系统运行正常。一旦系统故障,程序清零代码(“喂狗”)无法执行,其计数器就会计数不止,直到记到零并发生故障中断(狗饿了开始叫唤),控制CPU重启整个系统(不行啦,开始咬人了,快跑……)。


c) 区别:


独立看门狗Iwdg——我的理解是独立于系统之外,因为有独立时钟,所以不受系统影响的系统故障探测器。(这条狗是借来的,见谁偷懒它都咬!)主要用于监视硬件错误。


窗口看门狗wwdg——我的理解是系统内部的故障探测器,时钟与系统相同。如果系统时钟不走了,这个狗也就失去作用了。(这条狗是老板娘养的,老板不干活儿他不管!)主要用于监视软件错误。


d) 初始化函数定义:鉴于两只狗作用差不多,使用过程也差不多初始化函数栓一起了,用的时候根据情况删减。


void WDG_Configuration(void);


e) 初始化函数调用:


WDG_Configuration();


f) 初始化函数


void WDG_Configuration()             //看门狗初始化


{


//软件看门狗初始化


   WWDG_SetPrescaler(WWDG_Prescaler_8); //时钟8分频4ms


// (PCLK1/4096)/8= 244 Hz (~4 ms)


WWDG_SetWindowValue(65);             //计数器数值


   WWDG_Enable(127);                //启动计数器,设置喂狗时间


// WWDG timeout = ~4 ms * 64 = 262 ms


WWDG_ClearFlag();                //清除标志位


   WWDG_EnableIT();                    //启动中断


//独立看门狗初始化


   IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);//启动寄存器读写


   IWDG_SetPrescaler(IWDG_Prescaler_32);//40K时钟32分频


   IWDG_SetReload(349);                 //计数器数值


   IWDG_ReloadCounter();             //重启计数器


   IWDG_Enable();                       //启动看门狗


}


g) RCC初始化:只有软件看门狗需要时钟初始化,独立看门狗有自己的时钟不需要但是需要systic工作相关设置。


RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);


h) 独立看门狗使用systic的中断来喂狗,所以添加systic的中断打开代码就行了。软件看门狗需要在NVIC打开中断添加如下代码:


   NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQChannel; //通道


   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //占先中断等级


   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;    //响应中断优先级


   NVIC_Init(&NVIC_InitStructure);                      //打开中断


i) 中断程序,软件看门狗在自己的中断中喂狗,独立看门狗需要使用systic的定时中断来喂狗。以下两个程序都在stm32f10x_it.c文件中。


void WWDG_IRQHandler(void)


{


   WWDG_SetCounter(0x7F);       //更新计数值


WWDG_ClearFlag();             //清除标志位


}


void SysTickHandler(void)


{   IWDG_ReloadCounter();       //重启计数器(喂狗)


}


j) 注意事项:


i. 有狗平常没事情可以不理,但是千万别忘了喂它,否则死都不知道怎么死的!


ii. 初始化程序的调用一定要在systic的初始化之后。


iii. 独立看门狗需要systic中断来喂,但是systic做别的用处不能只做这件事,所以我写了如下几句代码,可以不影响systic的其他应用,其他systic周期代码也可参考:


第一步:在stm32f10x_it.c中定义变量


int Tic_IWDG;           //喂狗循环程序的频率判断变量


第二步:将SysTickHandler中喂狗代码改为下面:


Tic_IWDG++;          //变量递增


if(Tic_IWDG>=100)    //每100个systic周期喂狗


{   IWDG_ReloadCounter();//重启计数器(喂狗)


   Tic_IWDG=0;       //变量清零


}


 


STM32资料二(转载)

2009年08月27日 星期四 13:25




STM32笔记之七:让它跑起来,基本硬件功能的建立


0、 实验之前的准备


a) 接通串口转接器


b) 下载IO与串口的原厂程序,编译通过保证调试所需硬件正常。


1、 flash,lib,nvic,rcc和GPIO,基础程序库编写


a) 这几个库函数中有一些函数是关于芯片的初始化的,每个程序中必用。为保障程序品质,初学阶段要求严格遵守官方习惯。注意,官方程序库例程中有个platform_config.h文件,是专门用来指定同类外设中第几号外设被使用,就是说在main.c里面所有外设序号用x代替,比如USARTx,程序会到这个头文件中去查找到底是用那些外设,初学的时候参考例程别被这个所迷惑住。


b) 全部必用代码取自库函数所带例程,并增加逐句注释。


c) 习惯顺序——Lib(debug),RCC(包括Flash优化),NVIC,GPIO


d) 必用模块初始化函数的定义:


void RCC_Configuration(void); //定义时钟初始化函数


void GPIO_Configuration(void); //定义管脚初始化函数


void NVIC_Configuration(void); //定义中断管理初始化函数


void Delay(vu32 nCount); //定义延迟函数


e) Main中的初始化函数调用:


RCC_Configuration(); //时钟初始化函数调用


NVIC_Configuration(); //中断初始化函数调用


GPIO_Configuration(); //管脚初始化函数调用


f) Lib注意事项:


属于Lib的Debug函数的调用,应该放在main函数最开始,不要改变其位置。


g) RCC注意事项:


Flash优化处理可以不做,但是两句也不难也不用改参数……


根据需要开启设备时钟可以节省电能


时钟频率需要根据实际情况设置参数


h) NVIC注意事项


注意理解占先优先级和响应优先级的分组的概念


i) GPIO注意事项


注意以后的过程中收集不同管脚应用对应的频率和模式的设置。


作为高低电平的I/O,所需设置:RCC初始化里面打开RCC_APB2


PeriphClockCmd(RCC_APB2Periph_GPIOA);GPIO里面管脚设定:IO输出(50MHz,Out_PP);IO输入(50MHz,IPU);


j) GPIO应用


GPIO_WriteBit(GPIOB, GPIO_Pin_2, Bit_RESET);//重置


GPIO_WriteBit(GPIOB, GPIO_Pin_2, (BitAction)0x01);//写入1


GPIO_WriteBit(GPIOB, GPIO_Pin_2, (BitAction)0x00);//写入0


GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) ;//读入IO


k) 简单Delay函数


void Delay(vu32 nCount)//简单延时函数


{for(; nCount != 0; nCount--);}


实验步骤:


RCC初始化函数里添加:RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB , ENABLE);


不用其他中断,NVIC初始化函数不用改


GPIO初始化代码:


//IO输入,GPIOB的2、10、11脚输出


   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 ;//管脚号


   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速度


   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输入输出模式


    GPIO_Init(GPIOB, &GPIO_InitStructure);              //初始化


简单的延迟函数:


void Delay(vu32 nCount)                   //简单延时函数


{ for (; nCount != 0; nCount--);}           //循环计数延时


完成之后再在main.c的while里面写一段:


GPIO_WriteBit(GPIOB, GPIO_Pin_2, (BitAction)0x01);//写入1


Delay(0xffff);


GPIO_WriteBit(GPIOB, GPIO_Pin_2, (BitAction)0x00);//写入0


Delay(0xffff);


就可以看到连接在PB2脚上的LED闪烁了,单片机就跑起来了。


STM32笔记之八:来跟PC打个招呼,基本串口通讯


a) 目的:在基础实验成功的基础上,对串口的调试方法进行实践。硬件代码顺利完成之后,对日后调试需要用到的printf重定义进行调试,固定在自己的库函数中。


b) 初始化函数定义:


void USART_Configuration(void);        //定义串口初始化函数


c) 初始化函数调用:


void UART_Configuration(void); //串口初始化函数调用


初始化代码:


void USART_Configuration(void)                      //串口初始化函数


{


//串口参数初始化  


   USART_InitTypeDef USART_InitStructure;              //串口设置恢复默认参数


//初始化参数设置


   USART_InitStructure.USART_BaudRate = 9600;                                  //波特率9600


USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长8位


   USART_InitStructure.USART_StopBits = USART_StopBits_1;                 //1位停止字节


   USART_InitStructure.USART_Parity = USART_Parity_No;                   //无奇偶校验


   USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无流控制


   USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//打开Rx接收和Tx发送功能


USART_Init(USART1, &USART_InitStructure);                                         //初始化


   USART_Cmd(USART1, ENABLE);                                                       //启动串口


}


RCC中打开相应串口


RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE);


GPIO里面设定相应串口管脚模式


//串口1的管脚初始化  


   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;                      //管脚9


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽输出


   GPIO_Init(GPIOA, &GPIO_InitStructure);                            //TX初始化


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;                    //管脚10


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入


   GPIO_Init(GPIOA, &GPIO_InitStructure);                             //RX初始化


d) 简单应用:


发送一位字符


USART_SendData(USART1, 数据); //发送一位数据


while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){} //等待发送完毕


接收一位字符


while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET){} //等待接收完毕


变量= (USART_ReceiveData(USART1)); //接受一个字节


发送一个字符串


先定义字符串:char rx_data[250];


   然后在需要发送的地方添加如下代码


   int i;                                                                   //定义循环变量


while(rx_data!='\0')                                        //循环逐字输出,到结束字'\0'


{USART_SendData(USART1, rx_data);          //发送字符


    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){} //等待字符发送完毕


    i++;}


e) USART注意事项:


发动和接受都需要配合标志等待。


只能对一个字节操作,对字符串等大量数据操作需要写函数


使用串口所需设置:RCC初始化里面打开RCC_APB2PeriphClockCmd


(RCC_APB2Periph_USARTx);GPIO里面管脚设定:串口RX(50Hz,IN_FLOATING);串口TX(50Hz,AF_PP);


f) printf函数重定义(不必理解,调试通过以备后用)


(1) 需要c标准函数:


#include "stdio.h"


(2) 粘贴函数定义代码


#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)   //定义为putchar应用


(3) RCC中打开相应串口


(4) GPIO里面设定相应串口管脚模式


(6) 增加为putchar函数。


int putchar(int c)                                           //putchar函数


{


   if (c == '\n'){putchar('\r');}                                //将printf的\n变成\r


   USART_SendData(USART1, c);                                  //发送字符


   while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){} //等待发送结束


   return c;                                                     //返回值


}


(8) 通过,试验成功。printf使用变量输出:%c字符,%d整数,%f浮点数,%s字符串,/n或/r为换行。注意:只能用于main.c中。


3、 NVIC串口中断的应用


a) 目的:利用前面调通的硬件基础,和几个函数的代码,进行串口的中断输入练习。因为在实际应用中,不使用中断进行的输入是效率非常低的,这种用法很少见,大部分串口的输入都离不开中断。


b) 初始化函数定义及函数调用:不用添加和调用初始化函数,在指定调试地址的时候已经调用过,在那个NVIC_Configuration里面添加相应开中断代码就行了。


c) 过程:


i. 在串口初始化中USART_Cmd之前加入中断设置:


USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//TXE发送中断,TC传输完成中断,RXNE接收中断,PE奇偶错误中断,可以是多个。


ii. RCC、GPIO里面打开串口相应的基本时钟、管脚设置


iii. NVIC里面加入串口中断打开代码:


NVIC_InitTypeDef NVIC_InitStructure;//中断默认参数


NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;//通道设置为串口1中断


NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //中断占先等级0


NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;              //中断响应优先级0


NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;          //打开中断


NVIC_Init(&NVIC_InitStructure);                                              //初始化


iv. 在stm32f10x_it.c文件中找到void USART1_IRQHandler函数,在其中添入执行代码。一般最少三个步骤:先使用if语句判断是发生那个中断,然后清除中断标志位,最后给字符串赋值,或做其他事情。


void USART1_IRQHandler(void)                            //串口1中断


{


char RX_dat;                                                       //定义字符变量


   if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)   //判断发生接收中断


   {USART_ClearITPendingBit(USART1,   USART_IT_RXNE);       //清除中断标志


    GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)0x01);          //开始传输


RX_dat=USART_ReceiveData(USART1) & 0x7F;                       //接收数据,整理除去前两位


USART_SendData(USART1, RX_dat);                                     //发送数据


while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){}//等待发送结束


   }


}


d) 中断注意事项:


可以随时在程序中使用USART_ITConfig(USART1, USART_IT_TXE, DISABLE);来关闭中断响应。


NVIC_InitTypeDef NVIC_InitStructure定义一定要加在NVIC初始化模块的第一句。


全局变量与函数的定义:在任意.c文件中定义的变量或函数,在其它.c文件中使用extern+定义代码再次定义就可以直接调用了。


STM32笔记之九:打断它来为我办事,EXIT (外部I/O中断)应用


a) 目的:跟串口输入类似,不使用中断进行的IO输入效率也很低,而且可以通过EXTI插入按钮事件,本节联系EXTI中断。


b) 初始化函数定义:


void EXTI_Configuration(void); //定义IO中断初始化函数


c) 初始化函数调用:


EXTI_Configuration();//IO中断初始化函数调用简单应用:


d) 初始化函数:


void EXTI_Configuration(void)


{ EXTI_InitTypeDef EXTI_InitStructure; //EXTI初始化结构定义


EXTI_ClearITPendingBit(EXTI_LINE_KEY_BUTTON);//清除中断标志


GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);//管脚选择


GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4);


    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5);


    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6);


EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//事件选择


   EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//触发模式


   EXTI_InitStructure.EXTI_Line = EXTI_Line3 | EXTI_Line4; //线路选择


   EXTI_InitStructure.EXTI_LineCmd = ENABLE;//启动中断


   EXTI_Init(&EXTI_InitStructure);//初始化


}


e) RCC初始化函数中开启I/O时钟


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);


GPIO初始化函数中定义输入I/O管脚。


//IO输入,GPIOA的4脚输入


   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;       //上拉输入


   GPIO_Init(GPIOA, &GPIO_InitStructure);             //初始化


f) 在NVIC的初始化函数里面增加以下代码打开相关中断:


   NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQChannel; //通道


   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//占先级


   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应级


   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //启动


   NVIC_Init(&NVIC_InitStructure); //初始化


g) 在stm32f10x_it.c文件中找到void USART1_IRQHandler函数,在其中添入执行代码。一般最少三个步骤:先使用if语句判断是发生那个中断,然后清除中断标志位,最后给字符串赋值,或做其他事情。


   if(EXTI_GetITStatus(EXTI_Line3) != RESET)                               //判断中断发生来源


{ EXTI_ClearITPendingBit(EXTI_Line3);                                        //清除中断标志


USART_SendData(USART1, 0x41);                                           //发送字符“a”


GPIO_WriteBit(GPIOB, GPIO_Pin_2, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_2)));//LED发生明暗交替


}


h) 中断注意事项:


中断发生后必须清除中断位,否则会出现死循环不断发生这个中断。然后需要对中断类型进行判断再执行代码。


使用EXTI的I/O中断,在完成RCC与GPIO硬件设置之后需要做三件事:初始化EXTI、NVIC开中断、编写中断执行代码。


STM32笔记之十:工作工作,PWM输出


a) 目的:基础PWM输出,以及中断配合应用。输出选用PB1,配置为TIM3_CH4,是目标板的LED6控制脚。


b) 对于简单的PWM输出应用,暂时无需考虑TIM1的高级功能之区别。


c) 初始化函数定义:


void TIM_Configuration(void);   //定义TIM初始化函数


d) 初始化函数调用:


TIM_Configuration();   //TIM初始化函数调用


e) 初始化函数,不同于前面模块,TIM的初始化分为两部分——基本初始化和通道初始化:


void TIM_Configuration(void)//TIM初始化函数


{


   TIM_TimeBaseInitTypeDef   TIM_TimeBaseStructure;//定时器初始化结构


   TIM_OCInitTypeDef   TIM_OCInitStructure;//通道输出初始化结构


//TIM3初始化


   TIM_TimeBaseStructure.TIM_Period = 0xFFFF;        //周期0~FFFF


   TIM_TimeBaseStructure.TIM_Prescaler = 5;       //时钟分频


   TIM_TimeBaseStructure.TIM_ClockDivision = 0;    //时钟分割


   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//模式


   TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //基本初始化


   TIM_ITConfig(TIM3, TIM_IT_CC4, ENABLE);//打开中断,中断需要这行代码


   //TIM3通道初始化


   TIM_OCStructInit(& TIM_OCInitStructure);                                              //默认参数


   TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                    //工作状态


   TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;       //设定为输出,需要PWM输出才需要这行代码


   TIM_OCInitStructure.TIM_Pulse = 0x2000;                   //占空长度


   TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;                 //高电平


   TIM_OC4Init(TIM3, &TIM_OCInitStructure);                   //通道初始化


TIM_Cmd(TIM3, ENABLE);                                                                      //启动TIM3


}


f) RCC初始化函数中加入TIM时钟开启:


RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM3, ENABLE);


g) GPIO里面将输入和输出管脚模式进行设置。信号:AF_PP,50MHz。


h) 使用中断的话在NVIC里添加如下代码:


//打开TIM2中断


   NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel;   //通道


   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;//占先级


   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;           //响应级


   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;       //启动


   NVIC_Init(&NVIC_InitStructure);                                            //初始化


中断代码:


void TIM2_IRQHandler(void)


{


   if (TIM_GetITStatus(TIM2, TIM_IT_CC4) != RESET)    //判断中断来源


   {


TIM_ClearITPendingBit(TIM2, TIM_IT_CC4);          //清除中断标志


GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_11)));//变换LED色彩


IC4value = TIM_GetCapture4(TIM2);                //获取捕捉数值


   }  


}


i) 简单应用:


//改变占空比


TIM_SetCompare4(TIM3, 变量);


j) 注意事项:


管脚的IO输出模式是根据应用来定,比如如果用PWM输出驱动LED则应该将相应管脚设为AF_PP,否则单片机没有输出


我的测试程序可以发出不断循环三种波长并捕获,对比结果如下:


捕捉的稳定性很好,也就是说,同样的方波捕捉到数值相差在一两个数值。


捕捉的精度跟你设置的滤波器长度有关,在这里


TIM_ICInitStructure.TIM_ICFilter = 0x4;        //滤波设置,经历几个周期跳变认定波形稳定0x0~0xF


这个越长就会捕捉数值越小,但是偏差几十个数值,下面是0、4、16个周期滤波的比较,out是输出的数值,in是捕捉到的。


现在有两个疑问:


1、在TIM2的捕捉输入通道初始化里面这句


TIM_SelectInputTrigger(TIM2, TIM_TS_TI2FP2); //选择时钟触发源


按照硬件框图,4通道应该对应TI4FP4。可是实际使用TI1FP1,TI2FP2都行,其他均编译错误未注册。这是为什么?


2、关闭调试器和IAR程序,直接供电跑出来的结果第一个周期很正常,当输出脉宽第二次循环变小后捕捉的数值就差的远了。不知道是为什么


 


STM32资料一(转载)

2009年08月27日 星期四 13:24




注:下面是一些常用的代码,网上很多但是大多注释不全。高手看没问题,对于我们这些新手就费劲了……所以我把这些代码集中,进行了逐句注释,希望对新手们有价值。


flash: 芯片内部存储器flash操作函数


我的理解——对芯片内部flash进行操作的函数,包括读取,状态,擦除,写入等等,可以允许程序去操作flash上的数据。


基础应用1,FLASH时序延迟几个周期,等待总线同步操作。推荐按照单片机系统运行频率,0—24MHz时,取Latency=0;24—48MHz时,取Latency=1;48~72MHz时,取Latency=2。所有程序中必须的


用法:FLASH_SetLatency(FLASH_Latency_2);


位置:RCC初始化子函数里面,时钟起振之后。


基础应用2,开启FLASH预读缓冲功能,加速FLASH的读取。所有程序中必须的


用法:FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);


位置:RCC初始化子函数里面,时钟起振之后。


3、         lib:调试所有外设初始化的函数。


我的理解——不理解,也不需要理解。只要知道所有外设在调试的时候,EWRAM需要从这个函数里面获得调试所需信息的地址或者指针之类的信息。


基础应用1,只有一个函数debug。所有程序中必须的。


用法:        #ifdef DEBUG


               debug();


#endif


       位置:main函数开头,声明变量之后。


4、         nvic:系统中断管理。


我的理解——管理系统内部的中断,负责打开和关闭中断。


基础应用1,中断的初始化函数,包括设置中断向量表位置,和开启所需的中断两部分。所有程序中必须的。


用法:        void NVIC_Configuration(void)


{


NVIC_InitTypeDef NVIC_InitStructure;                   //中断管理恢复默认参数


#ifdef   VECT_TAB_RAM   //如果C/C++ Compiler\Preprocessor\Defined symbols中的定义了VECT_TAB_RAM(见程序库更改内容的表格)


NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0); //则在RAM调试


#else                                                                   //如果没有定义VECT_TAB_RAM


NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);//则在Flash里调试


#endif                                                                   //结束判断语句


//以下为中断的开启过程,不是所有程序必须的。


//NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);


//设置NVIC优先级分组,方式。


//注:一共16个优先级,分为抢占式和响应式。两种优先级所占的数量由此代码确定,NVIC_PriorityGroup_x可以是0、1、2、3、4,分别代表抢占优先级有1、2、4、8、16个和响应优先级有16、8、4、2、1个。规定两种优先级的数量后,所有的中断级别必须在其中选择,抢占级别高的会打断其他中断优先执行,而响应级别高的会在其他中断执行完优先执行。


//NVIC_InitStructure.NVIC_IRQChannel = 中断通道名; //开中断,中断名称见函数库


//NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级


//NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;         //响应优先级


//NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //启动此通道的中断


//NVIC_Init(&NVIC_InitStructure);                                     //中断初始化


}


5、         rcc:单片机时钟管理。


我的理解——管理外部、内部和外设的时钟,设置、打开和关闭这些时钟。


基础应用1:时钟的初始化函数过程——


用法:void RCC_Configuration(void)                                //时钟初始化函数


{


   ErrorStatus HSEStartUpStatus;                                     //等待时钟的稳定


   RCC_DeInit();                                                                //时钟管理重置


   RCC_HSEConfig(RCC_HSE_ON);                                  //打开外部晶振


   HSEStartUpStatus = RCC_WaitForHSEStartUp();          //等待外部晶振就绪


if (HSEStartUpStatus == SUCCESS)


   {


FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);


//flash读取缓冲,加速


FLASH_SetLatency(FLASH_Latency_2);                      //flash操作的延时


RCC_HCLKConfig(RCC_SYSCLK_Div1);                       //AHB使用系统时钟


RCC_PCLK2Config(RCC_HCLK_Div2);                          //APB2(高速)为HCLK的一半


RCC_PCLK1Config(RCC_HCLK_Div2);                          //APB1(低速)为HCLK的一半


//注:AHB主要负责外部存储器时钟。PB2负责AD,I/O,高级TIM,串口1。APB1负责DA,USB,SPI,I2C,CAN,串口2345,普通TIM。


RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); //PLLCLK = 8MHz * 9 = 72 MH


RCC_PLLCmd(ENABLE);                                              //启动PLL


while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){}     //等待PLL启动


RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //将PLL设置为系统时钟源


while (RCC_GetSYSCLKSource() != 0x08){}     //等待系统时钟源的启动


   }


//RCC_AHBPeriphClockCmd(ABP2设备1 | ABP2设备2 |, ENABLE); //启动AHP设备


//RCC_APB2PeriphClockCmd(ABP2设备1 | ABP2设备2 |, ENABLE);//启动ABP2设备


   //RCC_APB1PeriphClockCmd(ABP2设备1 | ABP2设备2 |, ENABLE); //启动ABP1设备


}


6、        exti:外部设备中断函数


我的理解——外部设备通过引脚给出的硬件中断,也可以产生软件中断,19个上升、下降或都触发。EXTI0~EXTI15连接到管脚,EXTI线16连接到PVD(VDD监视),EXTI线17连接到RTC(闹钟),EXTI线18连接到USB(唤醒)。


基础应用1,设定外部中断初始化函数。按需求,不是必须代码。


用法: void EXTI_Configuration(void)


{


EXTI_InitTypeDef EXTI_InitStructure;                               //外部设备中断恢复默认参数


EXTI_InitStructure.EXTI_Line = 通道1|通道2; //设定所需产生外部中断的通道,一共19个。


EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;     //产生中断


EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //上升下降沿都触发


EXTI_InitStructure.EXTI_LineCmd = ENABLE;             //启动中断的接收


EXTI_Init(&EXTI_InitStructure);             //外部设备中断启动


}


7、        dma:通过总线而越过CPU读取外设数据


我的理解——通过DMA应用可以加速单片机外设、存储器之间的数据传输,并在传输期间不影响CPU进行其他事情。这对于入门开发基本功能来说没有太大必要,这个内容先行跳过。


8、        systic:系统定时器


我的理解——可以输出和利用系统时钟的计数、状态。


基础应用1,精确计时的延时子函数。推荐使用的代码。


用法:


static vu32 TimingDelay;                                                                               //全局变量声明


void SysTick_Config(void)                                                                            //systick初始化函数


{


SysTick_CounterCmd(SysTick_Counter_Disable);                                  //停止系统定时器


SysTick_ITConfig(DISABLE); //停止systick中断


    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //systick使用HCLK作为时钟源,频率值除以8。


SysTick_SetReload(9000);                                                                       //重置时间1毫秒(以72MHz为基础计算)


SysTick_ITConfig(ENABLE);                                                                   //开启systic中断


}


void Delay (u32 nTime)                                                                                   //延迟一毫秒的函数


{


SysTick_CounterCmd(SysTick_Counter_Enable);                      //systic开始计时


   TimingDelay = nTime;                      //计时长度赋值给递减变量


while(TimingDelay != 0); //检测是否计时完成


SysTick_CounterCmd(SysTick_Counter_Disable);                                       //关闭计数器


SysTick_CounterCmd(SysTick_Counter_Clear);                      //清除计数值


}


void TimingDelay_Decrement(void) //递减变量函数,函数名由“stm32f10x_it.c”中的中断响应函数定义好了。


{


if (TimingDelay != 0x00) //检测计数变量是否达到0


{ TimingDelay--; //计数变量递减


}


}


注:建议熟练后使用,所涉及知识和设备太多,新手出错的可能性比较大。新手可用简化的延时函数代替:


void Delay(vu32 nCount)                                                                               //简单延时函数


{


   for(; nCount != 0; nCount--);                                                                          //循环变量递减计数


}


当延时较长,又不需要精确计时的时候可以使用嵌套循环:


void Delay(vu32 nCount)                      //简单的长时间延时函数


{int i; //声明内部递减变量


   for(; nCount != 0; nCount--)                                                             //递减变量计数


{for (i=0; i<0xffff; i++)}                                                                                  //内部循环递减变量计数


}


9、        gpio:I/O设置函数


我的理解——所有输入输出管脚模式设置,可以是上下拉、浮空、开漏、模拟、推挽模式,频率特性为2M,10M,50M。也可以向该管脚直接写入数据和读取数据。


基础应用1,gpio初始化函数。所有程序必须。


用法:void GPIO_Configuration(void)


{


GPIO_InitTypeDef GPIO_InitStructure;                      //GPIO状态恢复默认参数


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_标号 | GPIO_Pin_标号 ; //管脚位置定义,标号可以是NONE、ALL、0至15。


GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;//输出速度2MHz


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入模式


GPIO_Init(GPIOC, &GPIO_InitStructure); //C组GPIO初始化


//注:以上四行代码为一组,每组GPIO属性必须相同,默认的GPIO参数为:ALL,2MHz,FLATING。如果其中任意一行与前一组相应设置相同,那么那一行可以省略,由此推论如果前面已经将此行参数设定为默认参数(包括使用GPIO_InitTypeDef GPIO_InitStructure代码),本组应用也是默认参数的话,那么也可以省略。以下重复这个过程直到所有应用的管脚全部被定义完毕。


……


}


基础应用2,向管脚写入0或1


用法:GPIO_WriteBit(GPIOB, GPIO_Pin_2, (BitAction)0x01);    //写入1


基础应用3,从管脚读入0或1


用法:GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6)


使用BSRR和BRR寄存器直接操作STM32的I/O端口


发布时间:2009-11-26 16:50       发布者:STM32      阅读:117

STM32的每个GPIO端口都有两个特别的寄存器,GPIOx_BSRR和GPIOx_BRR寄存器,通过这两个寄存器可以直接对对应的GPIOx端口置'1'或置'0'。

GPIOx_BSRR的高16位中每一位对应端口x的每个位,对高16位中的某位置'1'则端口x的对应位被清'0';寄存器中的位置'0',则对它对应的位不起作用。

GPIOx_BSRR的低16位中每一位也对应端口x的每个位,对低16位中的某位置'1'则它对应的端口位被置'1';寄存器中的位置'0',则对它对应的端口不起作用。

简单地说GPIOx_BSRR的高16位称作清除寄存器,而GPIOx_BSRR的低16位称作设置寄存器。另一个寄存器GPIOx_BRR只有低16位有效,与GPIOx_BSRR的高16位具有相同功能。

举个例子说明如何使用这两个寄存器和所体现的优势。例如GPIOE的16个IO都被设置成输出,而每次操作仅需要改变低8位的数据而保持高8位不变,假设新的8位数据在变量Newdata中,

这个要求可以通过操作这两个寄存器实现,STM32的固件库中有两个函数GPIO_SetBits()和GPIO_ResetBits()使用了这两个寄存器操作端口。

上述要求可以这样实现:

GPIO_SetBits(GPIOE, Newdata & 0xff);
GPIO_ResetBits(GPIOE, (~Newdata & 0xff));

也可以直接操作这两个寄存器:

GPIOE->BSRR = Newdata & 0xff;
GPIOE->BRR = ~Newdata & 0xff;

当然还可以一次完成对8位的操作:

GPIOE->BSRR = (Newdata & 0xff) | (~Newdata & 0xff)<<16;

从最后这个操作可以看出使用BSRR寄存器,可以实现8个端口位的同时修改操作。

如果不是用BRR和BSRR寄存器,则上述要求就需要这样实现:

GPIOE->ODR = GPIOE->ODR & 0xff00 | Newdata;

使用BRR和BSRR寄存器可以方便地快速地实现对端口某些特定位的操作,而不影响其它位的状态。

比如希望快速地对GPIOE的位7进行翻转,则可以:

GPIOE->BSRR = 0x80; // 置'1'
GPIOE->BRR = 0x80; // 置'0'

如果使用常规'读-改-写'的方法:

GPIOE->ODR = GPIOE->ODR | 0x80; // 置'1'
GPIOE->ODR = GPIOE->ODR & 0xFF7F; // 置'0'

有人问是否BSRR的高16位是多余的,请看下面这个例子:

假如你想在一个操作中对GPIOE的位7置'1',位6置'0',则使用BSRR非常方便:
  GPIOE->BSRR = 0x4080;

如果没有BSRR的高16位,则要分2次操作,结果造成位7和位6的变化不同步!
  GPIOE->BSRR = 0x80;
  GPIOE->BRR = 0x40;

发表于2009/11/12



STM32定时器的输入滤波机制


发布时间:2009-11-26 16:48       发布者:STM32      阅读:136
STM32的定时器输入通道都有一个滤波单元,分别位于每个输入通路上(下图中的黄色框)和外部触发输入通路上(下图中的兰色框),它们的作用是滤除输入信号上的高频干扰。

attachimg.gif 点击看大图
下载 (68.89 KB)

2009-11-26 16:48


具体操作原理如下:

在TIMx_CR1中的CKD[1:0]可以由用户设置对输入信号的采样频率基准,有三种选择:
1)采样频率基准fDTS=定时器输入频率fCK_INT
2)采样频率基准fDTS=定时器输入频率fCK_INT/2
3)采样频率基准fDTS=定时器输入频率fCK_INT/4

然后使用上述频率作为基准对输入信号进行采样,当连续采样到N次个有效电平时,认为一次有效的输入电平。

实际的采样频率和采样次数可以由用户程序根据需要选择;外部触发输入通道的滤波参数在从模式控制寄存器(TIMx_SMCR)的ETF[3:0]中设置;每个输入通道的滤波参数在捕获/比较模式寄存器1(TIMx_CCMR1)或捕获/比较模式寄存器2(TIMx_CCMR2)的 IC1F[3:0]、IC2F[3:0]、IC3F[3:0]和IC4F[3:0]中设置。

attachimg.gif 点击看大图
下载 (17.49 KB)

2009-11-26 16:48


attachimg.gif 点击看大图
下载 (20.6 KB)

2009-11-26 16:48


例如:当fCK_INT=72MHz时,选择fDTS=fCK_INT/2=36MHz,采样频率fSAMPLING=fDTS/2=18MHz且N=6,则频率高于3MHz的信号将被这个滤波器滤除,有效地屏蔽了高于3MHz的干扰。

比如,结合输入捕获的中断,可以轻松地实现按键的去抖动功能,而不需要软件的干预;这可是由硬件实现的去抖动功能,大大节省了软件的开销和程序代码的长度。

每个定时器最多可以实现4个按键的输入,这个方法也可以用于键盘矩阵的扫描,而且因为是通过中断实现,软件不需频繁的进行扫描动作。

发表于2009/9/20



STM32复用的GPIO引脚配置方式


发布时间:2009-11-26 16:42       发布者:STM32      阅读:147
STM32所有内置外设的外部引脚都是与标准GPIO引脚复用的,例如对于STM32F103VBT6,47引脚为PB10,它的复用功能是 I2C2_SCL和USART3_TX,表示在上电之后它的默认功能为PB10,而I2C2的SCL和USART3的TX为它的复用功能;另外在TIM2 的引脚重映射后,TIM2_CH3也成为这个引脚的复用功能。

STM32基本上每个引脚都有8种配置模式:

1)浮空输入
2)带弱上拉输入
3)带弱下拉输入
4)模拟输入
5)推挽输出
6)开漏输出
7)复用推挽输出
8)复用开漏输出

通常有5种方式使用某个引脚功能,它们的配置方式如下:

1)作为普通GPIO输入:根据需要配置该引脚为浮空输入、带弱上拉输入或带弱下拉输入,同时不要使能该引脚对应的所有复用功能模块。
2)作为普通GPIO输出:根据需要配置该引脚为推挽输出或开漏输出,同时不要使能该引脚对应的所有复用功能模块。
3)作为普通模拟输入:配置该引脚为模拟输入模式,同时不要使能该引脚对应的所有复用功能模块。
4)作为内置外设的输入:根据需要配置该引脚为浮空输入、带弱上拉输入或带弱下拉输入,同时使能该引脚对应的某个复用功能模块。
5)作为内置外设的输出:根据需要配置该引脚为复用推挽输出或复用开漏输出,同时使能该引脚对应的所有复用功能模块。

注意如果有多个复用功能模块对应同一个引脚,只能使能其中之一,其它模块保持非使能状态。

比如要使用STM32F103VBT6的47、48脚的USART3功能,则需要配置47脚为复用推挽输出或复用开漏输出,配置48脚为某种输入模式,同时使能USART3并保持I2C2的非使能状态。

如果要使用STM32F103VBT6的47脚作为TIM2_CH3,则需要对TIM2进行重映射,然后再按复用功能的方式配置对应引脚。

发表于2009/7/25



关于RTC备份数据区数据丢失问题的讨论


发布时间:2009-11-26 16:41       发布者:STM32      阅读:114
问:最近在使用STM32F103RB时,出现了断电后再上电时备份数据区数据丢失的现象,不知道是什么原因,最初在设计的时候芯片核的电源(数字部分电源)和ADC的电源是相连的,备份数据区的数据没有丢失,现在将ADC的电源单独采用一个电压基准芯片供电后,断电再上电发现备份数据区的数据丢失了,全部为0包括时间。如果我将现在的电路板的ADC电源链接到数字电源上,数据就不丢失了。为提高ADC精度,又不丢失备份数据区数据,哪位仁兄有好的建议,感谢!!

答:在断电时,请保证同时切断VDD和VDDA 请用示波器看看,怀疑断电时你的VDDA下降的太慢,而VDD很快消失。在断电时,应该保证同时切断VDD和VDDA。

问:RTC备份数据区与ADC电源关系???不管是VDDA下降慢,还是VDD下降慢,我认为这个芯片的工作与否不应该受VddA的影响,如果说备份电池数据受VDDA的影响的话,应该在手册中有说明,我还认为当不使用ADC时我们可以不给ADC供电,为什么说呢,当模拟、数字电路分离时,模拟电源可能故障失电,数字部分存在电源,当然正常工作的CPU可以通过程序检查到ADC部分出错,现在根据实验发现,ADC失电后芯片根本不工作。如果说一定要同时切断电源的话就必须连接到同一电源上,要不肯定丢数据呀,香主你说呢。

香主分析的对,但是我就是不明白VDDA必须要先断电,这是为什么呢。我总是认为这个问题不应该出现,至少我认为VDDA自然为独立电源引脚,为什么内核工作受VDDA的影响呢?

答:RTC备份数据区与ADC电源关系,以及VDD与VDDA的关系最近正在翻译大容量的STM32F103数据手册,正好供电部分的说明与中等容量STM32F103说明一样,下面贴图中的2.3.11节与STM32F103RB数据手册中的2.3.9节内容相同,你可以对照着看:

attachimg.gif 点击看大图
下载 (37.55 KB)

2009-11-26 16:41


从关于VDDA的说明中可以看到,VDDA不单是为ADC供电,它为所有的模拟部分供电,特别是复位模块,这就回答了你的“不使用ADC时我们可以不给ADC供电”这个问题。关于VBAT部分说明了当关闭VDD时,通过内部电源切换器,VBAT为RTC、外部32kHz振荡器和后备寄存器供电。这里说的内部电源切换器就是下图中靠近VBAT的“供电开关”,下面这张图就是上面贴图中提到的图12:

attachimg.gif 点击看大图
下载 (18.41 KB)

2009-11-26 16:41


这张图中没有说明这个供电开关是由哪个电源控制,但从图上看可以认为是VDD控制。

关于你的问题(包括在前面提的问题),我是这样分析的:VDD先掉电,在VDDA还没有完全掉电时,因为内部漏电流,上述供电开关并没有切换到 VBAT,而此时VDD已经消失,造成RTC内容丢失。正因为如此,数据手册上才会要求VDD应与VDDA同源,同时他们之间的电压差不能超过 300mV。

如果这个供电开关是由VDDA控制,那么你看到的现象就是显而易见了。不过这一点我需要确认。

再答:"AN2586-STM32F10xxx硬件开发入门"中说明:供电开关由PDR控制在应用笔记《AN2586-STM32F10xxx硬件开发入门》中,我找到了上面所提到的供电开关是由VDDA控制的清楚说明。

这里是有关部分截图,这个文档的中文译文在ST MCU中文网页上:

attachimg.gif 点击看大图
下载 (44.51 KB)

2009-11-26 16:41


文中红线划出的部分写明:VBAT的开关由复位模块内的掉电复位(PDR)电路控制。再根据上面提到的部分,复位模块由VDDA供电。所以当VDDA晚于VDD调电时,VBAT的开关还没有切换到VBAT,VDD已经消失了,RTC的内容自然也就消失了。

关于前面提到的多电源问题,手册上已经明确写明,VDD必须与VDDA同源,所以不应理解为是多电源供电。

至于VBAT缺失和电池没有电是2个问题,VBAT缺失的情况时,电路上有相应处理,所以不会有问题;而电池没有电的情况下,并不表示VBAT信号悬空,此时VBAT是通过电池接地,也不会有问题,只不过RTC的内容不能继续维持。电池没有电时好像应该是等效于一个电容加一个电阻,具体什么样的参数我不太清楚,但我可以肯定不是一个无穷大的电阻。

问:现在看来对于STM32的设计那是相当的不一样,大家以后设计的时候不要想当然,一定要仔细阅读说明文档的每个细节,要不,设计会走弯路,当然也会带来一个麻烦就是不同用途的地方很多要求不一样,我从来都不会想象到PLL会挂在VDDA上,常理上PLL电路应该属于数字部分。可偏偏挂在了 VDDA上。

由于Vdd的电压不稳定,在大量的IO来回动作的情况下,VDD的电压极度不稳定,当然如果VddA也连接到VDD的话,对于48脚和64脚封装的芯片来讲Vdda连接到VDD对于要求高精度的应用来讲,这个应该是不充许的,我使用的就是103VB,如果将VDDA连接到VDD,此时的AD采样误差明显低于VDDA与VDD分离的误差,我将VDDA连接到一个3.3V的参考电压芯片上了。一般的芯片VDDA和VDD都是可以分离供电的,至少我看见过的,ST的除外。

第三者答:PLL电路似乎不可能是纯数字电路,至少VCO应该用模拟电路。

发表于2009/7/21


STM32内置参照电压的使用


发布时间:2009-11-26 16:38       发布者:STM32      阅读:90
每个STM32芯片都有一个内部的参照电压,相当于一个标准电压测量点,在芯片内部连接到ADC1的通道17。

根据数据手册中的数据,这个参照电压的典型值是1.20V,最小值是1.16V,最大值是1.24V。这个电压基本不随外部供电电压的变化而变化。

不少人把这个参照电压与ADC的参考电压混淆。ADC的参考电压都是通过Vref+提供的。100脚以上的型号,Vref+引到了片外,引脚名称为Vref+;64脚和小于64脚的型号,Vref+在芯片内部与VCC信号线相连,没有引到片外,这样AD的参考电压就是VCC上的电压。

在ADC的外部参考电压波动,或因为Vref+在芯片内部与VCC相连而VCC变化的情况下,如果对于ADC测量的准确性要求不高时,可以使用这个内部参照电压得到ADC测量的电压值。

具体方法是在测量某个通道的电压值之前,先读出参照电压的ADC测量数值,记为ADrefint;再读出要测量通道的ADC转换数值,记为ADchx;则要测量的电压为:

Vchx = Vrefint * (ADchx/ADrefint)

其中Vrefint为参照电压=1.20V。

上述方法在使用内置温度传感器对因为温度变化,对系统参数进行补偿时就十分有效。


STM32的ADC输入通道配置


发布时间:2009-11-26 11:44       发布者:STM32      阅读:89
STM32中最多有3个ADC模块,每个模块对应的通道不完全重叠。

下图是STM32F103CDE数据手册中的总框图的左下角,图中可以看出有8个外部ADC管脚分别接到了3个ADC模块,有8个外部ADC管脚只分别接到了2个ADC模块,还有5个外部ADC管脚只接到了ADC3模块,这样总共是21个通道。

attachimg.gif 1
下载 (10.1 KB)

2009-11-26 11:44


下表是这些ADC管脚与每个ADC模块的对应关系,表中可以看出ADC1还有2个内部通道,分别接到内部的温度传感器和内部的参照电压:

attachimg.gif 2
下载 (10.33 KB)

2009-11-26 11:44


发表于2009/7/2



STM32 ADC的规则通道和注入通道有什么区别


发布时间:2009-11-26 11:31       发布者:STM32      阅读:74
STM32的每个ADC模块通过内部的模拟多路开关,可以切换到不同的输入通道并进行转换。STM32特别地加入了多种成组转换的模式,可以由程序设置好之后,对多个模拟通道自动地进行逐个地采样转换。

有2种划分转换组的方式:规则通道组和注入通道组。通常规则通道组中可以安排最多16个通道,而注入通道组可以安排最多4个通道。

在执行规则通道组扫描转换时,如有例外处理则可启用注入通道组的转换。

一个不太恰当的比喻是:规则通道组的转换好比是程序的正常执行,而注入通道组的转换则好比是程序正常执行之外的一个中断处理程序。

再举一个不一定使用的例子:

假如你在家里的院子内放了5个温度探头,室内放了3个温度探头;你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则通道组循环扫描室外的5个探头并显示AD转换结果,当你想看室内温度时,通过一个按钮启动注入转换组(3个室内探头)并暂时显示室内温度,当你放开这个按钮后,系统又会回到规则通道组继续检测室外温度。

从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再变更循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规则组和注入组的划分,当你按下按钮后,需要从新配置AD循环扫描的通道,然后在施放按钮后需再次配置AD循环扫描的通道。

——————————————
上面的例子因为速度较慢,不能完全体现这样区分(规则组和注入组)的好处,但在工业应用领域中有很多检测和监视探头需要较快地处理,这样对AD转换的分组将简化事件处理的程序并提高事件处理的速度。

发表于                            2009/4/29



关于STM32 ADC速度的问题


发布时间:2009-11-26 11:29       发布者:STM32      阅读:83
STM32F103xx系列称为增强型产品,增强型产品的最高时钟频率可以达到72MHz。增强型产品的英文名称为Performance Line。
STM32F101xx系列称为基本型产品,基本型产品的最高时钟频率可以达到36MHz。基本型产品的英文名称为Access Line。

根据设计,当ADC模块的频率为14MHz时,可以达到ADC的最快采样转换速度。

要得到14MHz的ADC频率,就要求SYSCLK的频率是14MHz的倍数,即14MHz、28MHz、42MHz、56MHz、70MHz、 84MHz等;对于基本型产品14MHz和28MHz处于它的最大允许频率范围内;对于增强型产品,14MHz、28MHz、42MHz、56MHz和 70MHz几种频率都在它的最大允许频率范围内,但因为ADC预分频器的分频系数只有2、4、6、8这几个,使用70MHz不能得到最大的14MHz,所以要想得到最快的ADC转换速度,在增强型产品上能用的最快SYSCLK频率是56MHz。

ADC的速度由2个参数决定,它是采样时间和转换时间之和:

  即:TCONV = 采样时间 + 12.5个ADC时钟周期

在STM32中,ADC的采样时间是由用户程序在一组预定的数值中选择,按照ADC的时钟周期计算,共有8种选择:

    1.5、7.5、13.5、28.5、41.5、55.5、71.5和239.5

按最小的1.5个时钟周期的采样时间计算,最短的TCONV等于14个时钟周期,如果ADC的时钟频率是14MHz,则ADC的速度为每秒100万次。

注意:当ADC的时钟频率超过14MHz时,ADC的精度将会显著下降。

发表于                            2009/4/29



STM32内置CRC模块的使用


发布时间:2009-11-26 11:24       发布者:STM32      阅读:69
所有的STM32芯片都内置了一个硬件的CRC计算模块,可以很方便地应用到需要进行通信的程序中,这个CRC计算模块使用常见的、在以太网中使用的计算多项式:

  X32 + X26 + X23 + X22 + X16 + X12 + X11 + X10 +X8 + X7 + X5 + X4 + X2 + X + 1

写成16进制就是:0x04C11DB7

使用这个内置CRC模块的方法非常简单,既首先复位CRC模块(设置CRC_CR=0x01),这个操作把CRC计算的余数初始化为 0xFFFFFFFF;然后把要计算的数据按每32位分割为一组数据字,并逐个地把这组数据字写入CRC_DR寄存器(既下图中的绿色框),写完所有的数据字后,就可以从CRC_DR寄存器(既下图中的兰色框)读出计算的结果。

注意:虽然读写操作都是针对CRC_DR寄存器,但实际上是访问的不同物理寄存器。

attachimg.gif 1
下载 (11.59 KB)

2009-11-26 11:24


下面是用C语言描述的这个计算模块的算法,大家可以把它放在通信的另一端,对通信的正确性进行验证:

DWORD dwPolynomial = 0x04c11db7;
DWORD cal_crc(DWORD *ptr, int len)
{
DWORD xbit;
DWORD data;
DWORD CRC = 0xFFFFFFFF; // init
while (len--) {
  xbit = 1 << 31;

  data = *ptr++;
  for (int bits = 0; bits < 32; bits++) {
   if (CRC & 0x80000000) {
    CRC <<= 1;
    CRC ^= dwPolynomial;
   }
   else
    CRC <<= 1;
   if (data & xbit)
    CRC ^= dwPolynomial;

   xbit >>= 1;
  }
}
return CRC;
}

有几点需要说明:

1)上述算法中变量CRC,在每次循环结束包含了计算的余数,它始终是向左移位(既从最低位向最高位移动),溢出的数据位被丢弃。

2)输入的数据始终是以32位为单位,如果原始数据少于32位,需要在低位补0,当然也可以高位补0。

3)假定输入的DWORD数组中每个分量是按小端存储。

4)输入数据是按照最高位最先计算,最低位最后计算的顺序进行。

例如:
如果输入0x44434241,内存中按字节存放的顺序是:0x41, 0x42, 0x43, 0x44。计算的结果是:0xCF534AE1
如果输入0x41424344,内存中按字节存放的顺序是:0x44, 0x43, 0x42, 0x41。计算的结果是:0xABCF9A63

发表于                            2009/4/14

 



STM32中定时器的时钟源


发布时间:2009-11-26 11:16       发布者:STM32      阅读:66
STM32中有多达8个定时器,其中TIM1和TIM8是能够产生三对PWM互补输出的高级定时器,常用于三相电机的驱动,它们的时钟由APB2的输出产生。其它6个为普通定时器,时钟由APB1的输出产生。

下图是STM32参考手册上时钟分配图中,有关定时器时钟部分的截图:

attachimg.gif 1
下载 (17.64 KB)

2009-11-26 11:16


从图中可以看出,定时器的时钟不是直接来自APB1或APB2,而是来自于输入为APB1或APB2的一个倍频器,图中的蓝色部分。

下面以定时器2~7的时钟说明这个倍频器的作用:当APB1的预分频系数为1时,这个倍频器不起作用,定时器的时钟频率等于APB1的频率;当 APB1的预分频系数为其它数值(即预分频系数为2、4、8或16)时,这个倍频器起作用,定时器的时钟频率等于APB1的频率两倍。

假定AHB=36MHz,因为APB1允许的最大频率为36MHz,所以APB1的预分频系数可以取任意数值;当预分频系数=1 时,APB1=36MHz,TIM2~7的时钟频率=36MHz(倍频器不起作用);当预分频系数=2时,APB1=18MHz,在倍频器的作用下,TIM2~7的时钟频率=36MHz。

有人会问,既然需要TIM2~7的时钟频率=36MHz,为什么不直接取APB1的预分频系数=1?答案是:APB1不但要为TIM2~7提供时钟,而且还要为其它外设提供时钟;设置这个倍频器可以在保证其它外设使用较低时钟频率时,TIM2~7仍能得到较高的时钟频率。

再举个例子:当AHB=72MHz时,APB1的预分频系数必须大于2,因为APB1的最大频率只能为36MHz。如果APB1的预分频系数=2,则因为这个倍频器,TIM2~7仍然能够得到72MHz的时钟频率。能够使用更高的时钟频率,无疑提高了定时器的分辨率,这也正是设计这个倍频器的初衷。

发表于                            2009/3/30



在STM32上如果不使用外部晶振,OSC_IN和OSC_OUT的接法


发布时间:2009-11-26 11:14       发布者:STM32      阅读:76
如果使用内部RC振荡器而不使用外部晶振,请按照下面方法处理:

1)对于100脚或144脚的产品,OSC_IN应接地,OSC_OUT应悬空。
2)对于少于100脚的产品,有2种接法:
  2.1)OSC_IN和OSC_OUT分别通过10K电阻接地。此方法可提高EMC性能。
  2.2)分别重映射OSC_IN和OSC_OUT至PD0和PD1,再配置PD0和PD1为推挽输出并输出'0'。此方法可以减小功耗并(相对上面2.1)节省2个外部电阻。

以下是在ST英文网站FAQ上抄下的详细解释:

1) In 100 and 144 packages,  the OSC_IN /OSC_Out pins are a dedicated pins for HSE and mapped respectively on pins 12, 13 and pins 23, 24 for LQFP packages. Although, for BGA 100 and BGA 144 packages, they are mapped respectively on pins C1,D1 and pins D1,E1. In this case the recommended configuration is:

OSC_In pin must be connected to ground as it is always an input pin
OSC_Out must be left not connected as it is always an output pin.

2) In LQFP48 and LQFP64 packages,  the OSC_IN/OSC_Out pins are mapped on pins 5,6 whereas in the VFQFPN36 package they are mapped on pins 2,3. However the PD0 and PD1 functionality can be remapped by software on these pins.  There are two possible configurations:

Hardware configuration: Connect the OSC_IN/OSC_Out pins to ground through a 10K pull down resistors.

Software configuration: First, the OSC_IN/OSC_Out pins are left not connected on PCB layout. Then, remap the PD0 and PD1 functionality by software on these pins and configure the PD0/PD1 to Output push-pull forced to 0 logic level(Data Register = 0). This will maintain those I/Os to a fixed level minimizing Noise and external stress on these pins.

The software configuration is recommended to reduce the power consumption and saves cost by avoiding the use of external pull down resistors as the I/O is already connected to the ground internally. Whereas the hardware configuration is recommended to increase the EMC performance.

发表于                            2009/3/28

 



关于STM32的CPU为32位,定时器却为16位的探讨



楼主: STM32 明明是32位的CPU,却将定时器设计成16位的!
真不明白,明明是32位的CPU,却将定时器设计成16位的,对有些应用32位的定时器可是很重要的悠!
希望下一个版本能有所改进。ST加油!!!!

香水城 发表于 2007-12-27
2楼: 哈哈,以前就有人问过这个问题
先做个记号,楼主也可以先看看以前这个帖子,请各位先帮忙对帖子中6楼的问题给点提示;等我们整理一下来回答楼主这个问题。

STM32F103增强型产品的基本信息

hotpower 发表于 2007-12-28
3楼: 哈哈~~~可能有时8位的更好~~~这和延时的长短有关~~~
与32位真没关系~~~

computer00 发表于 2007-12-28
4楼: 我比较喜欢32位的计数器。

s99060 发表于 2007-12-28
5楼: 既然有编码器接口功能是应该不止16位的了
2500线的编码器1圈就是2500/5000/10000个脉冲,最多能检测26圈多点
就算400线的要用的话应该用1600个脉冲了,也才41圈不到

hotpower 发表于 2007-12-28
6楼: 哈哈~~~楼上肯定想要大位数的计数器了~~~

hexenzhou 发表于 2007-12-28
7楼: Atmel、Luminary也是16位的定时器,很不爽!
自从用了NXP的32位定时器后就喜欢上它,非常适合高频率的脉冲测量。不过STM32好像可以把两个16位定时器进行级联形成32位的定时器,不知好用否?

cauthy 发表于 2007-12-28
8楼: 有预分频器
有预分频器,定时起多少位影响不大

香水城 发表于 2007-12-28
9楼: 8楼点出了问题的关键

handerson 发表于 2007-12-28
10楼: 以前就有人问过这个问题

s99060 发表于 2007-12-28
11楼: 哈哈,预分频器相当于2500线的编码器换成25线的
那倒是可以省大笔钱了,25线的俺就自己做了哦

香水城 发表于 2007-12-28
12楼: 关于STM32的CPU为32位,定时器却为16位的探讨
首先,感谢dxfshsh提出了这个很好的问题,使我们可以就这个问题有一个交流的机会。

STM32的通用定时器可以实现很多功能,例如:定时计数、测量外部信号脉冲宽度、产生PWM波形、测量输入的PWM波形等。在所有这些操作中,定时器的位数主要影响两个参数,一个是定时或测量的精度,另一个是定时的时间长度。下面我们以一个列表看一下定时的精度和定时的长度有多少:

attachimg.gif 点击看大图
下载 (15.67 KB)

2009-11-26 11:07


关于各个预分频器的作用请参考下图的右半部分:

attachimg.gif 点击看大图
下载 (40.81 KB)

2009-11-26 11:07


从表中可以看出,在最高精度下(14ns)定时长度只有0.91ms,在精度为250ns(即4MHz)时定时长度可达16.38ms。这是仅使用了定时器的独立工作模式的情况。

对于需要高精度并且长延时的应用,16位的定时(上述精度和时间长度)就不够了,这个问题可以有两种解决办法;第一个办法是通过软件的接力完成,这个方法的可行性在于定时时间较长,允许软件有足够的时间介入计数,这种办法非常方便,多数情况都可使用。第二种办法是使用STM32特有的定时器级联功能,实现32位的计数效果,因为级联是由硬件触发的,当设置好各项寄存器后,软件不必中途干预,可以达到高精度长延时的要求。进一步地,STM32最多有四个定时器,如果串联起来,甚至可以实现4*16=64位的计数效果。

简单地说级联功能,即是一个定时器的定时条件满足后,可以产生一个触发信号启动另一个定时器的定时操作。

在ST的网站上有一个应用笔记和对应的例子程序,详细说明和演示了如何使用STM32的级联功能实现32位的输入捕获和32位的输出比较功能,各位可以研究一下:
   应用笔记下载地址:http://www.st.com/stonline/products/literature/an/13711.pdf
   演示程序下载地址:http://www.st.com/stonline/products/support/micro/files/an2592.zip

这是该应用笔记的摘要:
【AN2592 如何使用STM32F101xx和STM32F103xx的时钟链接功能实现定时器的32位精度】 (2007年8月)
  许多应用需要32位的精度,用于测量超过几百秒的外部信号的周期并产生延迟或较大间隔的周期信号。
  STM32F101xx和STM32F103xx提供了链接两个16位定时器借以获得32位精度的能力,这是使用了定时器的一种特殊配置和链接机制。
  本文给出了模拟一个32位定时器的基本原则;介绍了两个基本的操作模式:输入捕获模式和输出比较模式。每个模式都是单独介绍并附有实例。


最后希望各位朋友能够帮助我们更加深入地了解应用的需求,对于以工业控制和嵌入式控制仪器而言,因为我们接触的应用有限,不是很清楚哪里需要这样的高精度定时,如果方便我们可以做几个实例分析,这样更有利于我们对今后产品的升级和定位。

hotpower 发表于 2007-12-28
13楼: 这个问题软件很好解决~~~原理和环型计数器相同

computer00 发表于 2007-12-28
14楼: 总之就是不爽。增加到32位又复杂不了多少。

香水城 发表于 2007-12-28
15楼: 硬件解决对于使用者来说肯定是最简单方便的
但从硬件设计上讲,16位变32位就意味着芯片面积不只是成倍地增加了,结果必然是成本的上升,搞不好这款芯片的成本优势都没了。

cauthy 发表于 2007-12-28
16楼: 香版言之甚有理
相信做过CPLD/FPGA的,都有这种感觉

computer00 发表于 2007-12-28
17楼: 不会吧?这么夸张?就几个计数器变成32位的,芯片面积就要翻倍?
没搞过IC设计,不知道怎么会这样...

不就是多几个触发器而已吗?

从3个计数器增加到6个计数器芯片面积就要翻倍了?

john78 发表于 2007-12-28
18楼: 就是,不知道怎么搞的str9也是16位的,郁闷
就是,不知道怎么搞的str9也是16位的,郁闷

s99060 发表于 2007-12-28
19楼: 能级联倒是可以解决一下,但不能是预分频那种模式的
但这样对付一只编码器也要干掉3个定时器了:
两个串联成32bit编码器方式对外计数,1个产生固定时间触发捕捉寄存器用来测量速度

这种地方用软件就不方便了,电机可能刚好在进位处快速抖动着,1会儿加1会儿减的

xwj 发表于 2007-12-28
20楼: 不足就是不足,不同意预分频器的解释
这一点上不知道ST是怎么想的...

computer00 发表于 2007-12-28
21楼: 既然预分频器都做了,不如把预分频合并进来,不就有32位了?

john78 发表于 2007-12-28
22楼: 是呀!
不足就是不足,用32BIT的耗费什么来着,多用个定时器就不浪费了.

希望能有改进.

mohanwei 发表于 2007-12-28
23楼: 如果做过高精度超声波测距什么的,就知道32位定时器的好了

lpf336 发表于 2007-12-28
24楼: 羡慕啊! 有机会也玩玩

香水城 发表于 2007-12-28
25楼: 呵呵,不当家不知柴米油盐贵呀
我并不是说32位的定时器没有必要,我们设计一个产品首先是要有一个合理的定位,目前这款产品定位于那些不必使用32位定时器的应用,但也适当地预留了32位定时的可能。至于什么时候需要设计具有32位定时器的产品,需要广泛的市场调查,所以我一再希望大家能够提供一些思路和应用实例,便于我们做出正确的判断。毕竟32位的单片机推出的时间不长,我们也需要时间不断地发掘应用领域,并不断推出适合市场需求的产品。

非常感谢各位对这款产品的关注,也希望各位能够给我们更多的反馈,使以后新的产品做得更好。

好下面继续讨论。。。。

* - 本贴最后修改时间:2007-12-28 21:34:54 修改者:香水城

香水城 发表于 2007-12-28
26楼: 回19楼,级联不必用3个定时器,所有定时器都可以自行触发捕捉
不知道你看没看ST的手册,不要用其他单片机的概念来套,我在12楼给出的应用笔记中和程序中已经讲得足够清楚,如果你看了那个例子后还有疑问,我们再讨论。

s99060 发表于 2007-12-28
27楼: 呵呵,触发捕捉是指连续测量在同样一个固定时间内计数器的计数
以此得到马达转速.又要测转角又要测转速.
转角当然就直接计数了,转速就是以固定时间连续读取算出来的

俺控制电机的

香水城 发表于 2007-12-29
28楼: 好啊,LS如果需要用STM32做电机控制项目,可免费参加我们的培训
STM32的TIM1是专门设计用于变频电机控制的。

s99060 发表于 2007-12-29
29楼: 不好意思,俺不是做变频,只是控制一下而已
TIM1是适合做变频的,一般变频也不需要一定要带编码器

俺只要控制3~4只普通的伺服电机,所以手头这个项目暂时选了LM带2路编码器接口的片子(千万别发火,俺只知道选适合我的,反正都是M3的核,只是外设不同),但俺肯定要关注ST的,也不是哄你玩而是另一个项目就可能选ST的了---还是因为外设的原因.

有个统一的"好核"就是好啊!

香水城 发表于 2007-12-29
30楼: 这样吧,我先把STM32手册中有关编码器的说明贴出来
对我来说泛泛地谈各种功能意义不大,不如我们读一下手册看看STM32是如何实现那些功能的。

下面是STM32技术参考手册中有关编码器部分的摘录,STM32最多有4个定时器,每个定时器都有一个这样的编码器,同时每个定时器有四路输出,可产生多达4x4=16路PWM输出....,我就不一一列举了,各位最好能够看一下手册。

attachimg.gif 点击看大图
下载 (137.68 KB)

2009-11-26 11:07



s99060 发表于 2007-12-29
31楼: (捕获信号必须是周期的并且可以由另一个定时器产生)
国文手册已经看过N遍了(至少是关心的部分)

香板放心,就冲你们这么负责地维护着这个坛子俺也要努力使用ST的片子(LM也挺努力不是吗,否则ST手册可能是看不了N遍的)

net2wo 发表于 2007-12-30
32楼: 这句不妥
不当家不知柴米油盐贵呀

节流绝对不放在产品身上!!!
绝大多数应用可能用51也能搞定,但是客户高兴就是用2440 2812来做也未尝不可.对外高速计数当然希望大位数和中断源齐全&快速.
    定位于32位搞成半32位,够晕的!
    没错软件是能搞定,但生意不是这么个做法!!!
    电机控制不知比33系列如何,人家只是16位而已了.

    天堂:款已到帐;
    地狱:你这东西不好用(或:不能用)

抱歉,说歪了.

dxfshsh 发表于 2007-12-30
33楼: 关于俗手与手筋
首先,感谢香版花大篇幅回答我提出的问题,看来ST真的动真格的了,想做好中国这块单片机市场,不象710刚刚推出时的场景了,关于32位CPU,16位定时器,香版以为是市场定位问题,不是技术问题,本人表示苟同,但ST正式在这里犯了一个小错误,在ST的推广会上无论是厂家还是商家总是拿32与51比,如何如何!!!我想每一个经理人都会给你一个这样的回答,“凡是51能做到的决不会用32。”因此32的市场定位一定要高于51,是51所不能及处,为此设计32位的定时器正是本局的手筋,而分频器只能算是俗手。不知香版以为如何?

关于成本是提高了一点,但没有香版说的那么严重,面对更广阔的市场这点成本早被消化了。

还有,就是我们欢迎国外的厂家把优秀的产品介绍到中国来,共同发展,但也希望不要把这里只当成一块“试验田”,要做好产品的售后服务,发现BUG及时通过官方渠道公布并给出解决方案。
                                  谢谢!!!

hotpower 发表于 2007-12-30
34楼: "愚形"是可以用软件中断扩展定时计数器的位数的~~~

dxfshsh 发表于 2007-12-30
35楼: “愚形”被迫求活,非本手,多了,全盘皆输!

香水城 发表于 2007-12-30
36楼: 谢谢33楼的分析
首先,要声明的是ST决不是把中国市场当作试验田,ST在中国的销售额已经占到公司全球业务的四分之一以上,我想没有人会拿出1/4的市场做实验吧。

关于STM32这个产品,我们是对它给予了很高希望的,也许知道的人不多,STM32产品是在中国首先发布的,我们的市场部总监从欧洲专程飞到北京,发布了STM32之后,再飞到韩国、日本进行发布,这足以证明ST对中国市场的重视。

关于STM32或Cortex-M3的市场定位问题,我部分同意33楼的观点,“凡是51能做到的决不会用32。因此32的市场定位一定要高于51,是51所不能及处。”,但是我不同意STM32的定时器不是32位是个失误、是个BUG。说“分频器只能算是俗手”我也不大赞成,这是因为分频器不是在STM32中为了弥补定时器仅为16位而作的补救,如果您注意一下ST的其它单片机,您可以发现几乎所有单片机的定时器中都有分频器存在,因此在STM32的定时器中出现分频器只是ST单片机传统的延续。

当然我非常理解各位,从应用者的角度考虑,硬件能够提供越多的功能、越强的功能,系统或固件设计者就可以节省很多精力;实际上在与各位就此问题进行讨论时,我也在思考如何向设计部门建议新的功能和更强的功能,这是我们工作的一部分,但是我们做任何的决定,做任何新的建议必须有事实作为依据,只是一味地说32位定时器好是没有足够说服力的。希望各位能够理解我的出发点,这也是为什么我一直强调具体案例的原因,如果各位不方便在此交流,可以发邮件到mcu.china@st.com,我们会积极与您联络,更加深入地探讨这个问题。

dxfshsh 发表于 2007-12-30
37楼: 我并没有说16位定时器,就是BUG呀!
香版,理解有误,“试验田”也没有专门对ST,从地上跑的汽车到天上跑的飞机,对于中国能力尚落后的情况下,外国公司欺负中国人的案例还少吗?我只是作为一名普通靠吃“管腿”养家糊口的人,的一点基本“要求”而已,更何况我在“HDLC”上,已经吃到苦头了。

香水城 发表于 2007-12-30
38楼: 哈哈,我的论述不是针对您的
关于试验田的讨论不是针对您的发言,我实际上是借此说明ST是非常重视中国市场的。我当然也知道有不少国外的公司把一些不成熟的技术拿到中国来做实验,但在半导体行业,尤其是应用技术方面,已经比较成熟了而且竞争也很激烈,我们可不敢冒这个险啊。

您的发言我认为是很中肯的,也看得出您没有暗示BUG的意思,但如果您回顾一下之前的帖子,应该可以看出还是有人这样暗示的。

我只是想就事论事,绝没有针对任何人之意,如果某些话造成了误会,还请多多包涵。

再次感谢33楼的“俗手与手筋”之说,不管观点如何,但我认为那是真诚的表白。

dxfshsh 发表于 2007-12-31
39楼: 再过几个小时就2008年了
再过几个小时就2008年了,对于每一个中国人来讲好像2008是非常不一般的,
香版依然在线,精神可嘉,在这里你一个新年的问候!

香水城 发表于 2007-12-31
40楼: 谢谢大侠支持,也祝各位新年好
在2008年更上一层楼,大家共同发财,哈哈哈~~~~

hq_y 发表于 2007-12-31
41楼: 强烈要求st给香版发加班工资,呵呵~~~~~~~

hotpower 发表于 2007-12-31
42楼: 顶一个~~~这样的好斑竹是我们STM32菜鸟的福分~~~
敬礼!!!!!!!!!!!


为敬业---敬礼!!!!!!!!

香水城 发表于 2007-12-31
43楼: 托楼上二位的福,咱们大家新年共同发财
俺老板是法国人,您得跟他说法语,至少是英语,否则~~~~,哈哈哈~~~~

hq_y 发表于 2007-12-31
44楼: 给个信箱,俺别的不行,英语还能叨叨2句;

phoenixmy 发表于 2007-12-31
45楼: 以前有幸听过ST的法式英语~~~~~~~~~~


实在是难懂啊。。。。。

总是在单词最后吧音往上翘一下~~~~~~~~~~~

hq_y 发表于 2007-12-31
46楼: 呵呵俺去过法兰西~~~~~~~~~~
晚上坐在巴黎的街头喝啤酒
法兰西小妞的身材一级棒~~~~~~~~~

香水城 发表于 2007-12-31
47楼: 45楼提到的“总是在单词最后把音往上翘一下”的英语好像是意式的
说老实话,法国人的英语比意大利人说得好,只不过法国人不愿说。

坐在巴黎的街头喝啤酒、喝咖啡确实是一种享受,躺在卢浮宫前花园的长椅上晒太阳,看着美女经过,您就流口水吧~~~,哈哈哈~~~~~

hemingxian 发表于 2008-1-1
48楼: 12楼说的定时的精度和定时的长度表欠妥当
    定时的精度和定时的长度还应该考虑定时器的预分频器TIMx_PSC,所以定时的精度和定时的长度还可以乘以2~65535之间的任意常数。所以,只要定时精度不超过定时长度的65536分之1,16位的定时器就够用了,否则就要串联成32位的来用。

香水城 发表于 2008-1-1
49楼: 谢谢48楼,确实忘了考虑定时器自己的预分频器

dxfshsh 发表于 2008-1-2
50楼: 悲哀
48楼的说法没错,显然君没有做过精确定时同步设备,49楼应该更加了解32。此为最后一贴,今后不再讨论!!!

lonlmjs 发表于 2008-4-3
51楼: 32位定时器的CORTEX-M3到了
据了解NXP即将退出CORTEX-M3  LPC1000系列,12位AD,和他的ARM7一样32位外设,32位定时器。让ST慢慢研究需求吧!



STM32中外部中断与外部事件


发布时间:2009-11-26 10:46       发布者:STM32      阅读:79
attachimg.gif 点击看大图
下载 (89.47 KB)

2009-11-26 10:46


这张图是一条外部中断线或外部事件线的示意图,图中信号线上划有一条斜线,旁边标志19字样的注释,表示这样的线路共有19套。

图中的蓝色虚线箭头,标出了外部中断信号的传输路径,首先外部信号从编号1的芯片管脚进入,经过编号2的边沿检测电路,通过编号3的或门进入中断 “挂起请求寄存器”,最后经过编号4的与门输出到NVIC中断控制器;在这个通道上有4个控制选项,外部的信号首先经过边沿检测电路,这个边沿检测电路受上升沿或下降沿选择寄存器控制,用户可以使用这两个寄存器控制需要哪一个边沿产生中断,因为选择上升沿或下降沿是分别受2个平行的寄存器控制,所以用户可以同时选择上升沿或下降沿,而如果只有一个寄存器控制,那么只能选择一个边沿了。

接下来是编号3的或门,这个或门的另一个输入是“软件中断/事件寄存器”,从这里可以看出,软件可以优先于外部信号请求一个中断或事件,既当“软件中断/事件寄存器”的对应位为“1”时,不管外部信号如何,编号3的或门都会输出有效信号。

一个中断或事件请求信号经过编号3的或门后,进入挂起请求寄存器,到此之前,中断和事件的信号传输通路都是一致的,也就是说,挂起请求寄存器中记录了外部信号的电平变化。

外部请求信号最后经过编号4的与门,向NVIC中断控制器发出一个中断请求,如果中断屏蔽寄存器的对应位为“0”,则该请求信号不能传输到与门的另一端,实现了中断的屏蔽。

明白了外部中断的请求机制,就很容易理解事件的请求机制了。图中红色虚线箭头,标出了外部事件信号的传输路径,外部请求信号经过编号3的或门后,进入编号5的与门,这个与门的作用与编号4的与门类似,用于引入事件屏蔽寄存器的控制;最后脉冲发生器把一个跳变的信号转变为一个单脉冲,输出到芯片中的其它功能模块。

在这张图上我们也可以知道,从外部激励信号来看,中断和事件是没有分别的,只是在芯片内部分开,一路信号会向CPU产生中断请求,另一路信号会向其它功能模块发送脉冲触发信号,其它功能模块如何相应这个触发信号,则由对应的模块自己决定。

在图上部的APB总线和外设模块接口,是每一个功能模块都有的部分,CPU通过这样的接口访问各个功能模块,这里就不再赘述了。

最初发表日期:2009-3-9



STM32的USART发送数据时如何使用TXE和TC标志


发布时间:2009-11-26 10:32       发布者:STM32      阅读:88
在USART的发送端有2个寄存器,一个是程序可以看到的USART_DR寄存器(下图中阴影部分的TDR),另一个是程序看不到的移位寄存器(下图中阴影部分Transmit Shift Register)。

对应USART数据发送有两个标志,一个是TXE=发送数据寄存器空,另一个是TC=发送结束;对照下图,当TDR中的数据传送到移位寄存器后,TXE被设置,此时移位寄存器开始向TX信号线按位传输数据,但因为TDR已经变空,程序可以把下一个要发送的字节(操作USART_DR)写入TDR中,而不必等到移位寄存器中所有位发送结束,所有位发送结束时(送出停止位后)硬件会设置TC标志。

另一方面,在刚刚初始化好USART还没有发送任何数据时,也会有TXE标志,因为这时发送数据寄存器是空的。

TXEIE和TCIE的意义很简单,TXEIE允许在TXE标志为'1'时产生中断,而TCIE允许在TC标志为'1'时产生中断。

至于什么时候使用哪个标志,需要根据你的需要自己决定。但我认为TXE允许程序有更充裕的时间填写TDR寄存器,保证发送的数据流不间断。TC可以让程序知道发送结束的确切时间,有利于程序控制外部数据流的时序。

STM32设置了很多非常有用和灵活的控制和状态位,只要你很好地掌握了它们的用法,可以让你的应用更加精确和高效。

这是STM32技术参考手册中的一页:

attachimg.gif 点击看大图
下载 (37.01 KB)

2009-11-26 10:32


最初发表时间:2008-11-26



在STM32中如何配置片内外设使用的IO端口


发布时间:2009-11-26 10:17       发布者:STM32      阅读:56
首先,一个外设经过配置输入的时钟和初始化后即被激活(开启)。

如果需要使用该外设的输入输出管脚,则需要配置相应的GPIO端口;否则该外设对应的输入输出管脚可以做普通GPIO管脚使用。

STM32的输入输出管脚有下面8种可能的配置:

  1. 浮空输入
  2. 带上拉输入
  3. 带下拉输入
  4. 模拟输入
  5. 开漏输出
  6. 推挽输出
  7. 复用功能的推挽输出
  8. 复用功能的开漏输出

对应到外设的输入输出功能有下述三种情况:

一、外设对应的管脚为输出:需要根据外围电路的配置选择对应的管脚为复用功能的推挽输出或复用功能的开漏输出。
二、外设对应的管脚为输入:则根据外围电路的配置可以选择浮空输入、带上拉输入或带下拉输入。
三、ADC对应的管脚:配置管脚为模拟输入。

这一点在手册中没有十分明确地说明,我们已经要求写手册的人在下一版本的手册中加入。

最初发表日期:2008-8-29

 
PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
3
关闭 站长推荐上一条 /3 下一条