电源控制:
下面这张表截自STM32F103x8/B的数据手册,对上图的参数给出了具体数值:
下面对上面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
参考开发板例子写的串口通讯的小程序。主要功能是把PC机发送的数据接收后再返回给PC机参数9600,8,1,N。 /************************************************************************ void RCC_Config(void); int main(void) RCC_HCLKConfig(RCC_SYSCLK_Div1);//配置AHB(HCLK)时钟等于==SYSCLK RCC_PLLCmd(ENABLE);//使能PLL时钟 //配置CTS (PD.03),USART2 Rx (PD.06)为浮点输入模式 |
定时器的基本设置 1、TIM_TimeBaseStructure.TIM_Prescaler = 0x0;//时钟预分频数 例如:时钟频率=72/(时钟预分频+1) 6、TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //打开中断 溢出中断 7、TIM_Cmd(TIM2, ENABLE);//打开定时器 此外要记住一定要打开定时器的时钟(RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);),定时器的频率的可以编程的,有对应的模式设置和中断处理。 |
在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口。 /******************************************************************************* /* RCC system reset(for debug purpose) */ /* Enable HSE */ /* Wait till HSE is ready */ if(HSEStartUpStatus == SUCCESS) /* PCLK2 = HCLK */ /* PCLK1 = HCLK/2 */ /* ADCCLK = PCLK2/6 */ /* Flash 2 wait state */ /* Enable Prefetch Buffer */ /* PLLCLK = 8MHz * 9 = 72 MHz */ /* Enable PLL */ /* Wait till PLL is ready */ /* Select PLL as system clock source */ /* Wait till PLL is used as system clock source */ /* Enable GPIOA, GPIOB, GPIOC, GPIOD, GPIOE and AFIO clocks */ /* TIM2 clocks enable */ /* CAN Periph clock enable */ 下图是STM32用户手册中的时钟系统结构图,通过该图可以从总体上掌握STM32的时钟系统。 |
对于单片机系统来说,CPU和总线以及外设的时钟设置是非常重要的,因为没有时钟就没有时序,组合电路能干什么想必各位心里都清楚。其实时钟的学习这部分应该提前一些,但由于一开始时间比较短,有些急于求成,所以直接使用了万利给的例程,姑且跳过了这一步。介于下面我计划要学习的任务都涉及到兆级的高速传输,例如全速USB,DMA等等,所以不能再忽略时钟啦,必须要仔细研究一下。 /* 这里是重置了RCC的设置,类似寄存器复位 */ /* 使能外部高速晶振 */ /* 等待高速晶振稳定 */ if (HSEStartUpStatus == SUCCESS) /* 令Flash处于等待状态,2是针对高频时钟的,这两句跟RCC没直接关系,可以暂且略过 */ /* HCLK = SYSCLK 设置高速总线时钟=系统时钟*/ /* PCLK2 = HCLK 设置低速总线2时钟=高速总线时钟*/ /* PCLK1 = HCLK/2 设置低速总线1的时钟=高速时钟的二分频*/ /* ADCCLK = PCLK2/6 设置ADC外设时钟=低速总线2时钟的六分频*/ /* Set PLL clock output to 72MHz using HSE (8MHz) as entry clock */ /* Enable PLL 使能锁相环*/
/* Select PLL as system clock source 将锁相环输出设置为系统时钟 */ /* Wait till PLL is used as system clock source 等待校验成功*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | 由上述程序可以看出系统时钟的设定是比较复杂的,外设越多,需要考虑的因素就越多。同时这种设定也是有规律可循的,设定参数也是有顺序规范的,这是应用中应当注意的,例如PLL的设定需要在使能之前,一旦PLL使能后参数不可更改。 经过此番设置后,由于我的电路板上是8Mhz晶振,所以系统时钟为72Mhz,高速总线和低速总线2都为72Mhz,低速总线1为36Mhz,ADC时钟为12Mhz,USB时钟经过1.5分频设置就可以实现48Mhz的数据传输。 一般性的时钟设置需要先考虑系统时钟的来源,是内部RC还是外部晶振还是外部的振荡器,是否需要PLL。然后考虑内部总线和外部总线,最后考虑外设的时钟信号。遵从先倍频作为CPU时钟,然后在由内向外分频,下级迁就上级的原则有点儿类似PCB制图的规范化要求,在这里也一样。 |
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笔记之七:让它跑起来,基本硬件功能的建立 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程序,直接供电跑出来的结果第一个周期很正常,当输出脉宽第二次循环变小后捕捉的数值就差的远了。不知道是为什么 |
注:下面是一些常用的代码,网上很多但是大多注释不全。高手看没问题,对于我们这些新手就费劲了……所以我把这些代码集中,进行了逐句注释,希望对新手们有价值。 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) |
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 明明是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波形等。在所有这些操作中,定时器的位数主要影响两个参数,一个是定时或测量的精度,另一个是定时的时间长度。下面我们以一个列表看一下定时的精度和定时的长度有多少: 关于各个预分频器的作用请参考下图的右半部分: 从表中可以看出,在最高精度下(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输出....,我就不一一列举了,各位最好能够看一下手册。
STM32中外部中断与外部事件发布时间:2009-11-26 10:46 发布者:STM32 阅读:79 这张图是一条外部中断线或外部事件线的示意图,图中信号线上划有一条斜线,旁边标志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技术参考手册中的一页: 最初发表时间: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 |
文章评论(0条评论)
登录后参与讨论