当前位置:文档之家› 基于STM32的USB程序开发笔记

基于STM32的USB程序开发笔记

基于STM32的USB程序开发笔记

第一篇:需要准备的一些资料 (1)

基于STM32的USB程序开发笔记(三) ——STM32 USB固件函数的一些介绍 (4)

基于STM32的USB程序开发笔记(四)——USB设备的枚举(上) (12)

第五篇:USB设备的枚举(下) (15)

第六篇:XP下USB驱动开发的初步准备工作 (23)

第七篇:XP下USB驱动开发的最终完成 (30)

第一篇:需要准备的一些资料

1:STM32的参考手册,这对于设备底层USB的硬件配置以及事件驱动机制的了解尤为重要,你需要了解各个寄存器的功能以及如何操作,比如CNTR、ISTR、EPnR、DADDR等等,如果你想学习USB,这个

手册是必须的。

2:USB2.0 协议,这个资料同样必不可少,如果因为英语阅读能力而苦苦寻找中文版的USB2.0协议,建议不要这么做,现在网络中的所谓的中文版的USB2.0协议不是官方撰写的,大多数是一些热心朋友自己翻译的,却不是很全面,如果你在为寻找这类的资料而无所获时,建议认真塌实的看看官方英文版的USB2.0协议,官方协议阐述的十分详细,650多页,一字一句的了解全部协议不太可行,可针对性的重点理解,比如对第9章USB Device Framework的详细理解对于你的USB Device固件开发不可缺少(这里就是

STM32)。

3:ST提供的USB固件库,这个类库较为散乱,但不可不参考

以下是最近这段时间的成果,包含固件、驱动以及应用程序,固件部分有些功能是不被支持的,如

SR_SetDescriptor()、 SR_SynchFrame()等等,在此说明战士不支持非故意如此,而是还没时间仔细深入编写完善,因为这些目前不被支持的部分目前不被使用到。

后序将接着对各个部分进行一些说明,希望朋友们多多支持,同时欢迎朋友们讨论。

如果你使用的是万利的STM3210B-LK1开发板,则可以烧写hex文件后进行测试。

下载该文件请参阅:

https://www.doczj.com/doc/0f2256985.html,/bbs/bbs_content.jsp?bbs_sn=1808061&bbs_page_no=1&bbs_id=3020

第2篇:STM32 USB固件函数的驱动原理

首先需要了解一个概念:

USB 设备(DEVICE)从来只是被动触发,USB主机(HOST)掌握主动权,发送什么数据,什么时候发送,是给设备数据还是从设备请求数据,都是由USB 主机完成的,USB设备只是配合主机完成设备的枚举、数据方向和大小。根据数据特性再决定该不该回复该如何回复、该不该接收该如何接收这些动作。了解这些,再仔细查看STM32的参考手册USB部分以及STM32的中断向量表,从中可以找到两个中断:/*******************************************************************************

* Function Name : USB_HP_CAN_TX_IRQHandler

* Description : This function handles USB High Priority or CAN TX interrupts

* requests.

* Input : None

* Output : None

* Return : None

*******************************************************************************/

void USB_HP_CAN_TX_IRQHandler(void)

{

USB_HPI();

}

/*******************************************************************************

* Function Name : USB_LP_CAN_RX0_IRQHandler

* Description : This function handles USB Low Priority or CAN RX0 interrupts

* requests.

* Input : None

* Output : None

* Return : None

*******************************************************************************/

void USB_LP_CAN_RX0_IRQHandler(void)

{

USB_LPI();

}

即 USB的高、低优先权中断处理函数,这也是整个STM32 USB的事件驱动源,USB_HPI()与USB_LPI()既而转向usb_core(.c,.h)进行相关处理。中断传输(interrupt)、控制传输(control)、大流量传输(bulk)由USB_LPI()响应,大流量传输(bulk)同样可能响应USB_HPI(),同步传输 (isochronous)只响应USB_HPI()。

这样响应USB的所有请求只需要关注usb_core.c文件中的 USB_LPI()与USB_HPI()函数。由于本人也是对USB刚刚有所了解,因而在本例笔记中USB_HPI()函数未做任何处理,在此开源希望大家能完善与纠

正错误并能共享喜悦。以下是USB_LPI()函数:

// *****************************************************************************

// Function Name : USB_LPI.

// Description : Low Priority Interrupt's service routine.

// Input :

// Output :

// Return :

// *****************************************************************************

void USB_LPI(void)

{

unsigned short wValISTR = GetISTR();

#if(CNTR_MASK & ISTR_RESET) // Reset

if(wValISTR & ISTR_RESET & vwInterruptMask)

{

SetISTR(CLR_RESET);

INT_ISTR_RESET();

}

#endif

#if(CNTR_MASK & ISTR_DOVR) // DMA Over/Underrun

if(wValISTR & ISTR_DOVR & vwInterruptMask)

{

SetISTR(CLR_DOVR);

INT_ISTR_DOVR();

}

#endif

#if(CNTR_MASK & ISTR_ERR) // Error

if(wValISTR & ISTR_ERR & vwInterruptMask)

{

SetISTR(CLR_ERR);

INT_ISTR_ERROR();

}

#endif

#if(CNTR_MASK & ISTR_WKUP) // Wakeup

if(wValISTR & ISTR_WKUP & vwInterruptMask)

{

SetISTR(CLR_WKUP);

INT_ISTR_WAKEUP();

}

#endif

#if(CNTR_MASK & ISTR_SUSP) // Suspend

if(wValISTR & ISTR_SUSP & vwInterruptMask)

{

INT_ISTR_SUSPEND();

SetISTR(CLR_SUSP); // must be done after setting of CNTR_FSUSP

}

#endif

#if(CNTR_MASK & ISTR_SOF) // Start Of Frame

if(wValISTR & ISTR_SOF & vwInterruptMask)

{

SetISTR(CLR_SOF);

INT_ISTR_SOF();

}

#endif

#if(CNTR_MASK & ISTR_ESOF) // Expected Start Of Frame

if(wValISTR & ISTR_ESOF & vwInterruptMask)

{

SetISTR(CLR_ESOF);

INT_ISTR_ESOF();

}

#endif

#if(CNTR_MASK & ISTR_CTR) // Correct Transfer

if(wValISTR & ISTR_CTR & vwInterruptMask)

{

INT_ISTR_CTR();

}

#endif

}

// *****************************************************************************

// Function Name : USB_HPI.

// Description : High Priority Interrupt's service routine.

// Input :

// Output :

// Return :

// *****************************************************************************

void USB_HPI(void)

{

}

可以看出,在USB_LPI()函数中,根据STM32 USB的中断状态寄存器(ISTR)的标志位的状态以及定义的USB控制寄存器中断事件屏蔽码,响应各自的中断事件,比如 INT_ISTR_RESET()响应USB的复位中断,一般可在此函数内进行USB的寄存器的初始化;INT_ISTR_CTR()响应一次正确的数据传输中断,故名思意,在完成一次正确的数据传输操作后,就会响应此函数。

具体含义请仔细查阅STM32参考手册,下篇将针对这些响应函数进行逐一的详细介绍。

基于STM32的USB程序开发笔记(三) ——STM32 USB固件函数的一些介绍

接着上篇,详细情况可以查看usb_core(.c/.h),STM32 USB中断事件为以下几种:

void ISTR_CTR(void);

void ISTR_SOF(void);

void ISTR_ESOF(void);

void ISTR_DOVR(void);

void ISTR_ERROR(void);

void ISTR_RESET(void);

void ISTR_WAKEUP(void);

void ISTR_SUSPEND(void);

这些处理函数使能由定义CNTR_MASK决定:

// CNTR mask control

#define CNTR_MASK CNTR_CTRM | CNTR_WKUPM | CNTR_SUSPM | CNTR_ERRM | \

CNTR_SOFM | CNTR_ESOFM | CNTR_RESETM | CNTR_DOVRM \

其中着重说明的是ISTR_RESET()和ISTR_CTR()函数,ISTR_RESET()主要处理USB复位后进行一些初始化任务,ISTR_CTR()则是处理数据正确传输后控制,比如说响应主机。

// *****************************************************************************

// Function Name : INT_ISTR_RESET

// Description : ISTR Reset Interrupt service routines.

// Input :

// Output :

// Return :

// ***************************************************************************** void INT_ISTR_RESET(void)

{

// Set the buffer table address

SetBTABLE(BASEADDR_BTABLE);

// Set the endpoint type: ENDP0

SetEPR_Type(ENDP0, EP_CONTROL);

Clr_StateOut(ENDP0);

// Set the endpoint data buffer address: ENDP0 RX

SetBuffDescTable_RXCount(ENDP0, ENDP0_PACKETSIZE);

SetBuffDescTable_RXAddr(ENDP0, ENDP0_RXADDR);

// Set the endpoint data buffer address: ENDP0 TX

SetBuffDescTable_TXCount(ENDP0, 0);

SetBuffDescTable_TXAddr(ENDP0, ENDP0_TXADDR);

// Initialize the RX/TX status: ENDP0

SetEPR_RXStatus(ENDP0, EP_RX_VALID);

SetEPR_TXStatus(ENDP0, EP_TX_NAK);

// Set the endpoint address: ENDP0

SetEPR_Address(ENDP0, ENDP0);

// --------------------------------------------------------------------- // TODO: Add you code here

// --------------------------------------------------------------------- // Set the endpoint type: ENDP1

SetEPR_Type(ENDP1, EP_INTERRUPT);

Clr_StateOut(ENDP1);

// Set the endpoint data buffer address: ENDP1 RX

SetBuffDescTable_RXCount(ENDP1, ENDP1_PACKETSIZE); SetBuffDescTable_RXAddr(ENDP1, ENDP1_RXADDR);

// Set the endpoint data buffer address: ENDP1 TX

SetBuffDescTable_TXCount(ENDP1, 0);

SetBuffDescTable_TXAddr(ENDP1, ENDP1_TXADDR);

// Initialize the RX/TX status: ENDP1

SetEPR_RXStatus(ENDP1, EP_RX_VALID);

SetEPR_TXStatus(ENDP1, EP_TX_DIS);

// Set the endpoint address: ENDP1

SetEPR_Address(ENDP1, ENDP1);

SetEPR_Type(ENDP2, EP_INTERRUPT);

Clr_StateOut(ENDP2);

// Set the endpoint data buffer address: ENDP2 RX

SetBuffDescTable_RXCount(ENDP2, ENDP2_PACKETSIZE); SetBuffDescTable_RXAddr(ENDP2, ENDP2_RXADDR);

// Set the endpoint data buffer address: ENDP2 TX

SetBuffDescTable_TXCount(ENDP2, 0);

SetBuffDescTable_TXAddr(ENDP2, ENDP2_TXADDR);

// Initialize the RX/TX status: ENDP2

SetEPR_RXStatus(ENDP2, EP_RX_DIS);

SetEPR_TXStatus(ENDP2, EP_TX_VALID);

// Set the endpoint address: ENDP2

SetEPR_Address(ENDP2, ENDP2);

// ---------------------------------------------------------------------

// End of you code

// ---------------------------------------------------------------------

SetDADDR(0x0080 | vsDeviceInfo.bDeviceAddress);

vsDeviceInfo.eDeviceState = DS_DEFAULT;

vsDeviceInfo.bCurrentFeature = 0x00;

vsDeviceInfo.bCurrentConfiguration = 0x00;

vsDeviceInfo.bCurrentInterface = 0x00;

vsDeviceInfo.bCurrentAlternateSetting = 0x00;

https://www.doczj.com/doc/0f2256985.html,tatusInfo.w = 0x0000;

}

在这个ISTR_CTR()函数中,定义了EP0、1、2的传输方式以及各自的缓冲描述符,其中EP0是默认端口,负责完成USB设备的枚举,一般情况是不需要更改的。其他端点配置则需根据实际应用而决定,如何设置请仔细理解STM32的参考手册。

值得说明的是STM32的端点RX/TX缓冲描述表是定义在PMA中的,他是基于分组缓冲区描述报表寄存器(BTABLE)而定位的,各端点RX/TX缓冲描述表说明是数据存储地址以及大小,这个概念需要了解,ST提供的固件很含糊,为此,我在usb_regs.h文件中进行了重新定义,如下:

// USB_IP Packet Memory Area base address

#define PMAAddr (0x40006000L)

// Buffer Table address register

#define BTABLE ((volatile unsigned *)(RegBase + 0x50))

// *****************************************************************************

// Packet memory area: Total 512Bytes

// *****************************************************************************

#define BASEADDR_BTABLE 0x0000

// *****************************************************************************

// PMAAddr + BASEADDR_BTABLE + 0x00000000 : EP0_TX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x00000002 : EP0_TX_COUNT

// PMAAddr + BASEADDR_BTABLE + 0x00000004 : EP0_RX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x00000006 : EP0_RX_COUNT

//

// PMAAddr + BASEADDR_BTABLE + 0x00000008 : EP1_TX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x0000000A : EP1_TX_COUNT

// PMAAddr + BASEADDR_BTABLE + 0x0000000C : EP1_RX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x0000000E : EP1_RX_COUNT

//

// PMAAddr + BASEADDR_BTABLE + 0x00000010 : EP2_TX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x00000012 : EP2_TX_COUNT

// PMAAddr + BASEADDR_BTABLE + 0x00000014 : EP2_RX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x00000016 : EP2_RX_COUNT

//

// PMAAddr + BASEADDR_BTABLE + 0x00000018 : EP3_TX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x0000001A : EP3_TX_COUNT

// PMAAddr + BASEADDR_BTABLE + 0x0000001C : EP3_RX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x0000001E : EP3_RX_COUNT

//

// PMAAddr + BASEADDR_BTABLE + 0x00000020 : EP4_TX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x00000022 : EP4_TX_COUNT

// PMAAddr + BASEADDR_BTABLE + 0x00000024 : EP4_RX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x00000026 : EP4_RX_COUNT

//

// PMAAddr + BASEADDR_BTABLE + 0x00000028 : EP5_TX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x0000002A : EP5_TX_COUNT

// PMAAddr + BASEADDR_BTABLE + 0x0000002C : EP5_RX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x0000002E : EP5_RX_COUNT

//

// PMAAddr + BASEADDR_BTABLE + 0x00000030 : EP6_TX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x00000032 : EP6_TX_COUNT

// PMAAddr + BASEADDR_BTABLE + 0x00000034 : EP6_RX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x00000036 : EP6_RX_COUNT

//

// PMAAddr + BASEADDR_BTABLE + 0x00000038 : EP7_TX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x0000003A : EP7_TX_COUNT

// PMAAddr + BASEADDR_BTABLE + 0x0000003C : EP7_RX_ADDR

// PMAAddr + BASEADDR_BTABLE + 0x0000003E : EP7_RX_COUNT

// ***************************************************************************** //

// PMAAddr + BASEADDR_BTABLE + (0x00000040 - 0x000001FF) : assigned to data buffer

//

// ***************************************************************************** #define BASEADDR_DATA (BASEADDR_BTABLE + 0x00000040)

// ENP0

#define ENDP0_PACKETSIZE 0x40

#define ENDP0_RXADDR BASEADDR_DATA

#define ENDP0_TXADDR (ENDP0_RXADDR + ENDP0_PACKETSIZE)

// ENP1

#define ENDP1_PACKETSIZE 0x40

#define ENDP1_RXADDR (ENDP0_TXADDR + ENDP0_PACKETSIZE)

#define ENDP1_TXADDR (ENDP1_RXADDR + ENDP1_PACKETSIZE)

// ENP2

#define ENDP2_PACKETSIZE 0x40

#define ENDP2_RXADDR (ENDP1_TXADDR + ENDP1_PACKETSIZE)

#define ENDP2_TXADDR (ENDP2_RXADDR + ENDP2_PACKETSIZE)

// ENP3

#define ENDP3_PACKETSIZE 0x40

#define ENDP3_RXADDR (ENDP2_TXADDR + ENDP2_PACKETSIZE)

#define ENDP3_TXADDR (ENDP3_RXADDR + ENDP3_PACKETSIZE)

// ENP4

#define ENDP4_PACKETSIZE 0x40

#define ENDP4_RXADDR (ENDP3_TXADDR + ENDP3_PACKETSIZE)

#define ENDP4_TXADDR (ENDP4_RXADDR + ENDP4_PACKETSIZE)

// ENP5

#define ENDP5_PACKETSIZE 0x40

#define ENDP5_RXADDR (ENDP4_TXADDR + ENDP4_PACKETSIZE)

#define ENDP5_TXADDR (ENDP5_RXADDR + ENDP5_PACKETSIZE)

// ENP6

#define ENDP6_PACKETSIZE 0x40

#define ENDP6_RXADDR (ENDP5_TXADDR + ENDP5_PACKETSIZE)

#define ENDP6_TXADDR (ENDP6_RXADDR + ENDP6_PACKETSIZE)

// ENP7

#define ENDP7_PACKETSIZE 0x40

#define ENDP7_RXADDR (ENDP6_TXADDR + ENDP6_PACKETSIZE)

#define ENDP7_TXADDR (ENDP7_RXADDR + ENDP7_PACKETSIZE)

这样,一般只要在PMA的大小区域内(512Bytes),修改端点EPnR的数据包大小就可以了,当然,实际情况可以根据需要进行更改。

// *****************************************************************************

// Function Name : INT_ISTR_CTR

// Description : ISTR Correct Transfer Interrupt service routine.

// Input :

// Output :

// Return :

// *****************************************************************************

void INT_ISTR_CTR(void)

{

unsigned short wEPIndex;

unsigned short wValISTR;

unsigned short wValENDP;

while( ((wValISTR=GetISTR()) & ISTR_CTR) != 0 )

{

// Get the index number of the endpoints

wEPIndex = wValISTR & ISTR_EP_ID;

if(wEPIndex == 0)

{

// Set endpoint0 RX/TX status: NAK (Negative-Acknowlegment) SetEPR_RXStatus(ENDP0, EP_RX_NAK);

SetEPR_TXStatus(ENDP0, EP_TX_NAK);

// Transfer direction

if((wValISTR & ISTR_DIR) == 0)

{

// DIR=0: IN

// DIR=0 implies that EP_CTR_TX always 1

ClrEPR_CTR_TX(ENDP0);

CTR_IN0();

return;

}

else

{

// DIR=1: SETUP or OUT

// DIR=1 implies that CTR_TX or CTR_RX always 1

wValENDP = GetEPR(ENDP0);

if((wValENDP & EP_CTR_TX) != 0)

{

ClrEPR_CTR_TX(ENDP0);

CTR_IN0();

return;

}

else if((wValENDP & EP_SETUP) != 0)

{

ClrEPR_CTR_RX(ENDP0);

CTR_SETUP0();

return;

}

else if((wValENDP & EP_CTR_RX) != 0)

{

ClrEPR_CTR_RX(ENDP0);

CTR_OUT0();

return;

}

}

}

// Other endpoints

else

{

wValENDP = GetEPR(wEPIndex);

SetEPR_RXStatus(wEPIndex, EP_RX_NAK); SetEPR_TXStatus(wEPIndex, EP_TX_NAK);

if((wValENDP & EP_CTR_TX) != 0)

{

ClrEPR_CTR_TX(wEPIndex);

switch(wEPIndex)

{

case ENDP1: CTR_IN1(); break;

case ENDP2: CTR_IN2(); break;

case ENDP3: CTR_IN3(); break;

case ENDP4: CTR_IN4(); break;

case ENDP5: CTR_IN5(); break;

case ENDP6: CTR_IN6(); break;

case ENDP7: CTR_IN7(); break;

default: break;

}

}

if((wValENDP & EP_CTR_RX) != 0)

{

ClrEPR_CTR_RX(wEPIndex);

switch(wEPIndex)

{

case ENDP1: CTR_OUT1(); break;

case ENDP2: CTR_OUT2(); break;

case ENDP3: CTR_OUT3(); break;

case ENDP4: CTR_OUT4(); break;

case ENDP5: CTR_OUT5(); break;

case ENDP6: CTR_OUT6(); break;

case ENDP7: CTR_OUT7(); break;

default: break;

}

}

}

}

}

INT_ISTR_CTR()函数将各自响应事件提取出来,默认端点EP0也是最为复杂的,这个需要查看STM32的参考手册以及USB协议才能更好了解为何如此。到这里STM32 USB里数据传输事件就指向了各个对应的端点。下篇着重说明USB设备的枚举。

基于STM32的USB程序开发笔记(四)——USB设备的枚举(上)

USB设备能否工作,枚举步骤,用“乡村爱情”里的话说,“必须的!”,网上也有很多资料,圈圈就提供了一份详细的枚举过程,但对STM32是怎么响应的没有说明,一会详细道来,先上圈圈的提供的那个枚举图示,希望圈圈支持,如果不妥,请与我联系,谢谢。

我将此转换成了PDF文件,方便查看。

首先说明一个变量,定义在usb_core.c中:

volatile DEVICE_INFO vsDeviceInfo;

看意思就知道他的作用了,DEVICE_INFO是个结构,定义在usb_type.h中:

// *****************************************************************************

// DEVICE_INFO

// *****************************************************************************

typedef struct _DEVICE_INFO

{

unsigned char bDeviceAddress;

unsigned char bCurrentFeature;

unsigned char bCurrentConfiguration;

unsigned char bCurrentInterface;

unsigned char bCurrentAlternateSetting;

WORD_2BYTE uStatusInfo;

DEVICE_STATE eDeviceState;

RESUME_STATE eResumeState;

CONTROL_STATE eControlState;

SETUP_DATA SetupData;

TRANSFER_INFO TransInfo;

}

DEVICE_INFO,

*PDEVICE_INFO;

在枚举过程中,就是如何处理好SETUP事件,如果STM32 USB接收到正确的SETUP事件,将响应函数CTR_SETUP0(),SETUP事件是特殊的OUT事件,数据方向 Host->Device,SETUP事件数据长度固

定为8,数据定义在DEVICE_INFO.SetupData,其数据结构是(定义在 usb_type.h中):

typedef struct _SETUP_DATA

{

unsigned char bmRequestType; // request type

unsigned char bRequest; // request code

WORD_2BYTE wValue;

WORD_2BYTE wIndex;

WORD_2BYTE wLength;

}

SETUP_DATA,

*PSETUP_DATA;

WORD_2BYTE是定义的一个共用体:

typedef union _WORD_2BYTE

{

unsigned short w;

struct

{

unsigned char LSB;

unsigned char MSB;

}b;

}

WORD_2BYTE;

为什么将SETUP数据结构中的wValue,wIndex,wLength如此定义?

1:USB协议中所有数据传输都是依照低位在先的原则

2:高地位字节可能功能复用

这样在后续的程序编写中就变得十分方便,ST提供的USB固件方法同样如此,但这方面的处理让人有些摸不着头脑,详情可参阅。至于具体的SETUP数据结构含义如何,还是要具备基本知识:了解USB协议

CTR_SETUP0() 函数将SETUP数据提取出来,SETUP数据结构有0长度和非0长度的数据结构,详细参阅USB2.0官方协议第9章。在这将两种区别开来分别执行 SETUP0_NoData()和SETUP0_Data()函数,并返回结果,根据返回结果再响应USB主机

// *****************************************************************************

// Function Name : CTR_SETUP0

// Description :

// Input :

// Output :

// Return :

// *****************************************************************************

void CTR_SETUP0(void)

{

RESULT eResult;

BufferCopy_PMAToUser( (unsigned char *)&vsDeviceInfo.SetupData,

GetBuffDescTable_RXAddr(ENDP0),

GetBuffDescTable_RXCount(ENDP0));

if(vsDeviceInfo.SetupData.wLength.w == 0)

{

eResult = SETUP0_NoData();

}

else

{

eResult = SETUP0_Data();

}

switch(eResult)

{

case RESULT_SUCCESS:

break;

case RESULT_LASTDATA:

break;

case RESULT_ERROR:

case RESULT_UNSUPPORT:

SetEPR_RXStatus(ENDP0, EP_RX_VALID);

SetEPR_TXStatus(ENDP0, EP_TX_STALL);

break;

}

}

SETUP0_Data() 和SETUP0_NoData()函数支持的所有USB请求类型只有罗列的这些,有多少种组合都定义在USB协议中,程序根据请求代码,再去执行对应函数,这样做的目的就是让程序结构明了。其中注释为"// done"的部分表明此部分功能已完成。对于未完成部分,希望大家在交流中完善。

// *****************************************************************************

// Routine Groups: SETUP_Data

// *****************************************************************************

RESULT SETUP0_Data(void)

{

// SetupData.bRequest: request code

switch(vsDeviceInfo.SetupData.bRequest)

{

case SR_GET_STATUS: return SR_GetStatus(); // done

case SR_GET_DESCRIPTOR: return SR_GetDescriptor(); // done

case SR_SET_DESCRIPTOR: return SR_SetDescriptor(); // unsupport

case SR_GET_CONFIGURATION: return SR_GetConfiguration(); // done

case SR_GET_INTERFACE: return SR_GetInterface(); // unsupport

case SR_SYNCH_FRAME: return SR_SynchFrame(); // unsupport

default: return RESULT_UNSUPPORT;

}

}

// *****************************************************************************

// Routine Groups: SETUP_NoData

// *****************************************************************************

RESULT SETUP0_NoData(void)

{

// SetupData.bRequest: request code

switch(vsDeviceInfo.SetupData.bRequest)

{

case SR_CLEAR_FEATURE: return SR_ClearFeature(); // unsupport

case SR_SET_FEATURE: return SR_SetFeature(); // unsupport

case SR_SET_ADDRESS: return SR_SetAddress(); // done

case SR_SET_CONFIGURATION: return SR_SetConfiguration(); // done

case SR_SET_INTERFACE: return SR_SetInterface(); // unsupport

default: return RESULT_UNSUPPORT;

}

}

下篇将介绍上述的各个请求函数如何响应主机。

第五篇:USB设备的枚举(下)

SETUP事件正确接收后,根据该事件提供的请求类型进行对主机的响应。SETUP数据结构的wLength字段说明的是请求返回或者提供的数据长度。

如果判断出的请求信息错误或者说不被支持,STM32 USB设备需要中断此次请求:

SetEPR_RXStatus(ENDP0, EP_RX_V ALID);

SetEPR_TXStatus(ENDP0, EP_TX_STALL);

正确获取到请求信息后,如果wLength为0,设备需要发送一个0长度数据包以响应主机:// ***************************************************************************** // Function Name : SETUP0_Trans0Data

// Description :

// Input :

// Output :

// Return :

// ***************************************************************************** RESULT SETUP0_Trans0Data(void)

{

// Send 0-length data frame as ACK to host

SetBuffDescTable_TXCount(ENDP0, 0);

SetEPR_RXStatus(ENDP0, EP_RX_NAK);

SetEPR_TXStatus(ENDP0, EP_TX_V ALID);

return RESULT_SUCCESS;

}

如果wLength不为0,设备则需要根据请求的数据长度发送数据包以响应主机:

// ***************************************************************************** // Function Name : SETUP0_TransData

// Description :

// Input :

// Output :

// Return :

// ***************************************************************************** RESULT SETUP0_TransData(void)

{

unsigned short wLength = vsDeviceInfo.TransInfo.wLength;

unsigned short wOffset = vsDeviceInfo.TransInfo.wOffset;

unsigned short wMaxSize = vsDeviceInfo.TransInfo.wPacketSize;

if(wLength)

{

if(wLength> wMaxSize)

{

wLength = wMaxSize;

}

// Copy the transfer buffer to the endpoint0's buffer

BufferCopy_UserToPMA( vsDeviceInfo.TransInfo.pBuffer+wOffset, // transfer buffer

GetBuffDescTable_TXAddr(ENDP0), // endpoint 0 TX address

wLength);

SetBuffDescTable_TXCount(ENDP0, wLength);

SetEPR_RXStatus(ENDP0, EP_RX_NAK);

SetEPR_TXStatus(ENDP0, EP_TX_V ALID);

// Update the data lengths

vsDeviceInfo.TransInfo.wLength -= wLength;

vsDeviceInfo.TransInfo.wOffset += wLength;

return RESULT_LASTDA TA;

}

return RESULT_SUCCESS;

}

如果发送的数据长度大于端点设置的最大数据包长度,数据将分割为若干次发送,记录发送数据的状态包含在结构体TRANSFER_INFO中:

// ***************************************************************************** // TRANSFER_INFO

// ***************************************************************************** typedef struct _TRANSFER_INFO

{

unsigned short wLength; // total lengths data will be transmit

unsigned short wOffset; // number of data be transmited

unsigned short wPacketSize; // endpoints packet max size

unsigned char* pBuffer; // address of data buffer

}

TRANSFER_INFO,

*PTRANSFER_INFO;

TRANSFER_INFO.wLength记录发送的数据长度,如果非0,表示有数据需要被发送。TRANSFER_INFO.wOffset记录已发送的数据长度,用以确定数据缓冲

TRANSFER_INFO.pBuffer的偏移量。

需要了解的一点:USB主机向USB设备正确发送一请求后(这部分的处理由硬件完成),USB主机将间隔若干次的向USB设备索取响应数据,STM32 USB TX状态为NAK说明不响应USB主机,USB主机在超时后退出此次请求;TX状态为STLL说明中断此次请求,USB主机将无条件退出请求;TX状态为 V ALID说明设备已准备好数据发送,USB主机将从USB设备读取数据。

以非0长度数据请求的GET_DESCRIPTOR请求为例的响应过程:

CTR_SETUP0()->SETUP0_Data()->SR_GetDescriptor()->SETUP0_TransData()

RESULT SR_GetDescriptor(void)

{

// RequestType: device->host, standard request and device recipient

if(vsDeviceInfo.SetupData.bmRequestType == RT_D2H_STANDARD_DEVICE)

{

// SetupData.wValue.b.MSB: descriptor type

// SetupData.wValue.b.LSB: descriptor index

switch(vsDeviceInfo.SetupData.wV alue.b.MSB)

{

case DESCRIPTOR_DEVICE: return SR_GetDescriptor_Device();

case DESCRIPTOR_CONFIG: return SR_GetDescriptor_Config();

case DESCRIPTOR_STRING: return SR_GetDescriptor_String();

default: return RESULT_UNSUPPORT;

}

}

return RESULT_UNSUPPORT;

}

GET_DESCRIPTOR请求属于USB协议中的标准请求(standard request)并且数据方向为设备至主机(device->host),分设备描述符、配置描述符、字符串描述符三种。已设备描述符为例:

RESULT SR_GetDescriptor_Device(void)

{

// Assigned the device descriptor to the transfer

vsDeviceInfo.TransInfo.wOffset = 0;

vsDeviceInfo.TransInfo.wPacketSize = ENDP0_PACKETSIZE;

vsDeviceInfo.TransInfo.pBuffer = DescBuffer_Device.pBuff;

vsDeviceInfo.TransInfo.wLength = DescBuffer_Device.wLen;

vsDeviceInfo.eControlState = CS_GET_DESCRIPTOR;

if(vsDeviceInfo.TransInfo.wLength> vsDeviceInfo.SetupData.wLength.w)

{

vsDeviceInfo.TransInfo.wLength = vsDeviceInfo.SetupData.wLength.w;

}

return SETUP0_TransData();

}

这里说明了发送数据的长度、缓冲、偏移、端点包大小以及当前的控制状态,并说明了如果发送的数据长度超出请求的数据长度,则将舍弃超出的部分。数据配置好后,调用SETUP0_TransData()进行数据发送。

在USB主机查询到USB设备准备就绪后,将读取出这些数据,完成后,USB设备将产生IN事件,此时将响应CTR_IN0()函数:

// ***************************************************************************** // Function Name : CTR_IN

// Description :

// Input :

// Output :

// Return :

// ***************************************************************************** void CTR_IN0(void)

{

switch(vsDeviceInfo.eControlState)

{

case CS_GET_DESCRIPTOR:

if(SETUP0_TransData() == RESULT_SUCCESS)

{

SetEPR_TXStatus(ENDP0, EP_TX_NAK);

SetEPR_RXStatus(ENDP0, EP_RX_V ALID);

}

break;

case CS_SET_ADDRESS:

SetEPR_TXStatus(ENDP0, EP_TX_NAK);

SetEPR_RXStatus(ENDP0, EP_RX_V ALID);

SetDADDR(0x0080 | vsDeviceInfo.bDeviceAddress);

vsDeviceInfo.eDeviceState = DS_ADDRESSED;

break;

case CS_SET_CONFIGURATION:

SetEPR_TXStatus(ENDP0, EP_TX_NAK);

SetEPR_RXStatus(ENDP0, EP_RX_V ALID);

vsDeviceInfo.eDeviceState = DS_CONFIGURED;

break;

default:

break;

}

}

再这如果响应GET_DESCRIPTOR请求发送的数据如果全部发送完毕,SETUP0_TransData()返回RESULT_SUCCESS,并设置TX状态为NAK;否则返回RESULT_LASTDATA,将继续发送剩余的数据直到数据全部被发送。至此,整个的GET_DESCRIPTOR请求过程完成。0长度的数据请求在发送0长度数据响应后,因为不存在可能还未传送的数据,因而IN事件后直接结束此次请求。

在数据方向为USB主机->USB设备时,如果正确接收到数据,将响应CTR_OUT0()函数,处理过程类同CTR_IN0()函数。

在USB设备的枚举过程中,USB的一些描述符数据结构需要了解,具体在USB协议中有详细的说明,在usb_desc(.c/.h)文件中,定义了这些结构,这些结构是特定的:

设备描述符:长度、格式固定,其中VENDOR_ID与PRODUCT_ID决定上位机驱动的识别。设备分属类别决定了设备的性质,如果为自定义USB设备,设备分属类别值为0,同时上位机驱动必须配合编写;如果为标准USB设备,则必须使用这些标准设备的驱动、数据结构等等,条件是你必须了解这些标准设备的一些信息,好处是省去一些麻烦的驱动编写。

const unsigned char cbDescriptor_Device[DESC_SIZE_DEVICE] =

{

DESC_SIZE_DEVICE, // bLength: 18

DESCRIPTOR_DEVICE, // descriptor type

0x00, // bcdUSB LSB: USB release number -> USB2.0

0x02, // bcdUSB MSB: USB release number -> USB2.0

0x00, // bDeviceClass: Class information in the interface descriptors

0x00, // bDeviceSubClass:

0x00, // bDeviceProtocol:

0x40, // bMaxPacketSize0: LowS(8), FullS(8,16,32,64), HighS(64)

LOWORD(VENDOR_ID), // idVendor LSB:

HIWORD(VENDOR_ID), // idVendor MSB:

LOWORD(PRODUCT_ID), // idProduct LSB:

HIWORD(PRODUCT_ID), // idProduct MSB:

LOWORD(DEVICE_VERSION), // bcdDevice LSB:

HIWORD(DEVICE_VERSION), // bcdDevice MSB:

0x01, // iManufacturer: Index of string descriptor describing manufacturer

0x02, // iProduct: Index of string descriptor describing product

0x03, // iSerialNumber: Index of string descriptor describing the device serial number

0x01 // bNumConfigurations: number of configurations

};

配置描述符:前9个字节格式固定,后面紧跟的各种描述结构跟实际配置有关,每增加一种描述结构,该描述结构的第一字节说明了结构的长度,第二直接说明了结构的类型。在配置描述符中一般包含配置描述、接口描述、端点描述,如果需要同样可增加自定义的描述。使用标准USB设备类别时,配置描述符的结构也必须满足此类标准设备的数据结构。

const unsigned char cbDescriptor_Config[DESC_SIZE_CONFIG] =

{

// Descriptor of configuration

0x09, // lengths

DESCRIPTOR_CONFIG, // descriptor type

DESC_SIZE_CONFIG, // Total configuration descriptor lengths LSB

0x00, // Total configuration descriptor lengths MSB

0x01, // bNumInterfaces: Total number of interfaces

STM32学习笔记

输入模式初始化GPIOE2,3,4 ①IO口初始化:GPIO_InitTypeDef GPIO_InitStructure; ②使能PORTA,PORTE时钟: RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE); ③PE.2.3.4端口配置:GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; ④设置成(上拉)输入:GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; ⑤GPIO_Init(GPIOE, &GPIO_InitStructure); 输出模式初始化 ①IO口初始化:GPIO_InitTypeDef GPIO_InitStructure; ②使能PB,PE端口时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); ③3LED0-->PB.5 端口配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; ④设置(推挽)输出模式GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; ⑤设置IO口速度为50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; ⑥说明初始化哪个端口GPIO_Init(GPIOB, &GPIO_InitStructure); 在LED灯试验中初始为高电平灭GPIO_SetBits(GPIOB,GPIO_Pin_5); 再初始化相同发输出模式时③④⑤可省略例如(经实验初始化恰好为不同IO口相同IO序号③可省略,应该不规范吧) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置, 推挽输出GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽输出,IO口速度为50MHz GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高 1,头文件可以定义所用的函数列表,方便查阅你可以调用的函数; 2,头文件可以定义很多宏定义,就是一些全局静态变量的定义,在这样的情况下,只要修改头文件的内容,程序就可以做相应的修改,不用亲自跑到繁琐的代码内去搜索。 3,头文件只是声明,不占内存空间,要知道其执行过程,要看你头文件所申明的函数是在哪个.c文件里定义的,才知道。 4,他并不是C自带的,可以不用。 5,调用了头文件,就等于赋予了调用某些函数的权限,如果你要算一个数的N次方,就要调用Pow()函数,而这个函数是定义在math.c里面的,要用这个函数,就必需调用math.h 这个头文件。

STM32学习笔记_STM32F103ZET6

STM32F103 系列芯片的系统架构: 系统结构: 在每一次复位以后,所有除SRAM 和FLITF 以外的外设都被关闭,在使用一个外设之前,必须设置寄存器RCC_AHBENR 来打开该外设的时钟。

GPIO 输入输出,外部中断,定时器,串口。理解了这四个外设,基本就入门了一款MCU。 时钟控制RCC: -4~16M 的外部高速晶振 -内部8MHz 的高速RC 振荡器 -内部40KHz低速RC 振荡器,看门狗时钟 -内部锁相环(PLL,倍频),一般系统时钟都是外部或者内部高速时钟经过PLL 倍频后得到 - 外部低速32.768K 的晶振,主要做RTC 时钟源

ARM存储器映像: 数据字节以小端格式存放在存储器中。一个字里的最低地址字节被认为是该字的最低有效字节,而最高地址字节是最高有效字节。

存储器映像与寄存器映射: ARM 存储器映像 4GB 0X0000 00000X1FFF FFFF 0X2000 00000X3FFF FFFF 0X4000 00000X5FFF FFFF

寄存器说明: 寄存器名称 相对外设基地址的偏移值 编号 位表 读写权限 寄存器位 功能说明 使用C语言封装寄存器: 1、总线和外设基地址封装利用地址偏移 (1)定义外设基地址(Block2 首地址) (2)定义APB2总线基地址(相对外设基地址偏移固定) (3)定义GPIOX外设基地址(相对APB2总线基地址偏移固定)(4)定义GPIOX寄存器地址(相对GPIOX外设基地址偏移固定)(5)使用 C 语言指针操作寄存器进行读/写 //定义外设基地址 #define PERIPH_BASE ((unsigned int)0x40000000) 1) //定义APB2 总线基地址 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) 2) //定义GPIOC 外设基地址 #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800) 3) //定义寄存器基地址这里以GPIOC 为例 #define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00) 4) #define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04) #define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08) #define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C) #define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10) #define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14) #define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18) //控制GPIOC 第0 管脚输出一个低电平5) GPIOC_BSRR = (0x01<<(16+0)); //控制GPIOC 第0 管脚输出一个高电平 GPIOC_BSRR = (0x01<<0);

stm32学习 c语言笔记

这是前段时间做彩屏显示时候遇到的难题, *(__IO uint16_t *) (Bank1_LCD_C)这个就是将后面的数据转换为地址,然后对地址单元存放数据。可如下等效: __IO uint16_t *addr; addr = (__IO uint16_t *) Bank1_LCD_C; #ifdef和#elif连用,语法和if。。。else if语句一样 推挽输出增加驱动,可以驱动LED起来 static int count=0 count++ 这个语句中,count仅仅被初始化一次 以后加加一次期中的值就不会变化了 SysTick_CTRL(控制和状态寄存器) SysTick_LOAD(重装载寄存器) SysTick_VAL(当前值寄存器) SysTick_CALIB(校准值寄存器)

TFT经验:弄多大的相片,必须先把那个相片的尺寸改掉,再去取模,才可以,要不会有重影的嘿嘿嘿嘿 VBAT 是电池供电的引脚 VBAT和ADD同时都掉电时才能让备份区复位。 volatile一个变量的存储单元可以在定义该变量的程序之外的某处被引用。 volatile主要是程序员要告诉编译器不要对其定义的这个变量进行优化,防止其不能被引用,不能被改变。 VDDA>2.4V ADC才能工作 VDDA>2.7V USB才能工作 VDD(1.8-3.6v) VBAT=1.8-3.6v VSS VSSA VREF必须接到地线 没有外部电源供电时必须VBAT接上VDD 使用PLL时,VDDA必须供电

printf("abs(x)=%d\n",x<0?(-1)*x:x) 条件编译是问号前边为真则取冒号前边的值,为假的,则取后边的值。 所以说上边这条打印的语句是打印x的绝对值。 //stm32f10x_nvic.c stm32f10x_lib.c stm32f10x_gpio.c stm32f10x_flash.c stm32f10x_rcc.c TIM6 TIM7基本定时器 (只有这两个定时器不能产生PWM) TIM1 TIM8高级控制定时器 TIM2 TIM3 TIM4 TIM5为通用定时器 其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,这样,STM32最多可以同时产生30路PWM输出! 修改和自己写代码时候

STM32F407学习资料

使用心得: STM32F4与STM32F1在ADC方面的区别: 通常,在STM32F1中需要加自动校准的程序,如下: // 使能ADC1自动校准功能 ADC_ResetCalibration(ADC1); //检查ADC1自校准的状态位 while(ADC_GetResetCalibrationStatus(ADC1)); //启动ADC1自校准 ADC_StartCalibration(ADC1); //检查ADC1自校准是否结束 while(ADC_GetCalibrationStatus(ADC1)); // ADC自动校准结束--------------- 然而,STM32F4中无需此程序,给出STM32F407的ADC3和DMA方式的官方程序如下:/** ****************************************************************************** * @file ADC3_DMA/main.c * @author MCD Application Team * @version V1.0.0 * @date 19-September-2011 * @brief Main program body ****************************************************************************** * @attention * * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS A T PROVIDING CUSTOMERS * WITH CODING INFORMA TION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SA VE * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY

STM32学习笔记

STM32学习笔记——时钟频率 ******************************** 本学习笔记基于STM32固件库V3.0 使用芯片型号:STM32F103 开发环境:MDK ******************************** 第一课时钟频率 STM32F103内部8M的内部震荡,经过倍频后最高可以达到72M。目前TI的M3系列芯片最高频率可以达到80M。 在stm32固件库3.0中对时钟频率的选择进行了大大的简化,原先的一大堆操作都在后台进行。系统给出的函数为SystemInit()。但在调用前还需要进行一些宏定义的设置,具体的设置在system_stm32f10x.c文件中。 文件开头就有一个这样的定义: //#define SYSCLK_FREQ_HSE HSE_Value //#define SYSCLK_FREQ_20MHz 20000000 //#define SYSCLK_FREQ_36MHz 36000000 //#define SYSCLK_FREQ_48MHz 48000000 //#define SYSCLK_FREQ_56MHz 56000000 #define SYSCLK_FREQ_72MHz 72000000 ST 官方推荐的外接晶振是8M,所以库函数的设置都是假定你的硬件已经接了8M 晶振来运算的.以上东西就是默认晶振8M 的时候,推荐的CPU 频率选择.在这里选择了: #define SYSCLK_FREQ_72MHz 72000000 也就是103系列能跑到的最大值72M 然后这个C文件继续往下看 #elif defined SYSCLK_FREQ_72MHz const uint32_t SystemFrequency = SYSCLK_FREQ_72MHz; const uint32_t SystemFrequency_SysClk = SYSCLK_FREQ_72MHz; const uint32_t SystemFrequency_AHBClk = SYSCLK_FREQ_72MHz; const uint32_t SystemFrequency_APB1Clk = (SYSCLK_FREQ_72MHz/2); const uint32_t SystemFrequency_APB2Clk = SYSCLK_FREQ_72MHz; 这就是在定义了CPU跑72M的时候,各个系统的速度了.他们分别是:硬件频率,系统时 钟,AHB总线频率,APB1总线频率,APB2总线频率.再往下看,看到这个了: #elif defined SYSCLK_FREQ_72MHz static void SetSysClockTo72(void); 这就是定义72M 的时候,设置时钟的函数.这个函数被SetSysClock ()函数调用,而SetSysClock ()函数则是被SystemInit()函数调用.最后SystemInit()函数,就是被你调用的了

STM32的学习速成

STM32入门系列教程如何提高STM32的学习效率 Revision0.01 (2010-04-08)

目录 第一章笔者的入门总结 (2) 1.1为什么要把时间花在“犹豫”上? (2) 1.2看资料需要计划、耐心和速度 (2) 1.3学STM32必备开发板 (3) 1.4熟悉开发板并试图写程序 (3) 第二章STM32入门方法谈 (4) 2.1拿到开发板我该做什么? (4) 2.2我的时间如何安排 (5) 2.3碰到问题怎么办? (5) 第三章STM32学习步骤 (6) 3.1关于STM32文档学习 (6) 3.230天上手STM32计划 (7) 3.2.1第1步:熟悉调试软件 (8) 3.2.2第2步:GPIO编程 (8) 3.2.3开始全新的STM32深入研究 (9) 福州芯达工作室简介 (9)

第一章笔者的入门总结 1.1为什么要把时间花在 犹豫””上? 为什么要把时间花在““犹豫 每当我们在入门之前(ARM是这样,DSP也一样),总会会有很多疑问,会有很多顾虑。我们渴望知道学习STM32前景如何?需要啥基础?难不难?适不适合我?但是什么时候能心潮澎湃地、相当着急地开始学STM32?日子在一天一天过去!你开始行动了吗?没有行动的思索,永远都不可能入门!把这些时间用来看书吧,效果能好一万倍。 大家一般都是从51单片机过来的,回想一下,我们之前学单片机时如何入门呢?实际上都是先看书(理论),再玩板子(实践)。严格地说,应该是模仿实验。熟悉之后才会自己写程序代码实现某个功能。因此,如果你正在咨询STM32;如果你正对STM32心潮澎湃;如果你想入门STM32;那么,从现在开始,不要犹豫了,不要想再详细地了解STM32的前景了。做一个可能影响你一生的决定吧!不用咨询,不用兴奋,开始看书籍(文档)吧!!每个人都是这么走过来的。 1.2看资料需要计划、耐心和速度 这里所谓的“资料”包括STM32书籍、文档。因为STM32有个特点,datasheet 很多都是中文的,有些同学就没有去买书籍,直接看STM32的用户手册,也是可以的。但是不管看书籍还是文档,我们是需要计划的。不是今天看3页,明天看5页。一本书看了两个月,还在磨蹭。请记住,你学的不是寂寞,是STM32!看书或文档不是用来消遣时间的。背水一战吧,给自己规定一个底线:两周内把一定粗略地过一遍!不要求都看懂,事实上,不可能都看懂。但我们必须理解基本知识,对难度高的知识有一个印象,至少以后碰到问题的时候,我们会似曾相识,感觉在哪里见到过,于是翻资料上网找答案——带着问题的时候,效率才是超高的。 两周过去了,STM32的知识你过一遍了吗?没看完?那么,你应该考虑这些天中,你是否尽力了?是否真的想学STM32?或者还是看到STM32人气很高,也想跟下时髦?是不是碰到问题没法解决就想放弃了?现在是你做第二个决定的时候了,请决定你是否继续攻读STM32。如果是一时冲动想跟时髦,请马上放弃——你已经浪费两周时间了,你还想再浪费14天吗?如果想吃得苦中苦,方为人上人,OK,请继续往下看:) 1.3学STM32必备开发板 稍微了解一些理论知识后,现在摆在你面前,有两条路: 第一,自己画PCB制板、焊接、调试。 第二,购买一套性价比高的开发板。 笔者之前做过几次PCB设计,而且当时在实验室可以报销所有费用,就自

STM32入门基本知识

STM32学前班教程之一:选择他的理由 经过几天的学习,基本掌握了STM32的调试环境和一些基本知识。想拿出来与大家共享,笨教程本着最大限度简化删减STM32入门的过程的思想,会把我的整个入门前的工作推荐给大家。就算是给网上的众多教程、笔记的一种补充吧,所以叫学前班教程。其中涉及产品一律隐去来源和品牌,以防广告之嫌。全部汉字内容为个人笔记。所有相关参考资料也全部列出。:lol 教程会分几篇,因为太长啦。今天先来说说为什么是它——我选择STM32的原因。 我对未来的规划是以功能性为主的,在功能和面积之间做以平衡是我的首要选择,而把运算放在第二位,这根我的专业有关系。里面的运算其实并不复杂,在入门阶段想尽量减少所接触的东西。 不过说实话,对DSP的外设并和开发环境不满意,这是为什么STM32一出就转向的原因。下面是我自己做过的两块DSP28的全功能最小系统板,在做这两块板子的过程中发现要想尽力缩小DSP的面积实在不容易(目前只能达到50mm×45mm,这还是没有其他器件的情况下),尤其是双电源的供电方式和的电源让人很头疼。 后来因为一个项目,接触了LPC2148并做了一块板子,发现小型的ARM7在外设够用的情况下其实很不错,于是开始搜集相关芯片资料,也同时对小面积的AVR和51都进行了大致的比较,这个时候发现了CortexM3的STM32,比2148拥有更丰富和灵活的外设,性能几乎是2148两倍(按照MIPS值计算)。正好2148我还没上手,就直接转了这款STM32F103。 与2811相比较(核心供电情况下),135MHz×1MIPS。现在用STM32F103,72MHz×,性能是DSP的66%,STM32F103R型(64管脚)芯片面积只有2811的51%,STM32F103C型(48管脚)面积是2811的25%,最大功耗是DSP的20%,单片价格是DSP的30%。且有更多的串口,CAP和PWM,这是有用的。高端型号有SDIO,理论上比SPI速度快。 由以上比较,准备将未来的拥有操作系统的高端应用交给DSP的新型浮点型单片机28335,而将所有紧凑型小型、微型应用交给STM32。 STM32学前班教程:怎么开发 sw笨笨的STM32学前班教程之二:怎么开发目前手头的入门阶段使用的开发器概述 该产品为简易STM32调试器和DEMO板一体化的调试学习设备,价格在一百多块。 2、硬件配置

STM32学习笔记(5)通用定时器PWM输出

STM32学习笔记(5):通用定时器PWM输出 2011年3月30日TIMER输出PWM 1.TIMER输出PWM基本概念 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制。一般用来控制步进电机的速度等等。 STM32的定时器除了TIM6和TIM7之外,其他的定时器都可以用来产生PWM输出,其中高级定时器TIM1和TIM8可以同时产生7路的PWM输出,而通用定时器也能同时产生4路的PWM输出。 1.1PWM输出模式 STM32的PWM输出有两种模式,模式1和模式2,由TIMx_CCMRx寄存器中的OCxM位确定的(“110”为模式1,“111”为模式2)。模式1和模式2的区别如下: 110:PWM模式1-在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。 111:PWM模式2-在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为有效电平,否则为无效电平。 由此看来,模式1和模式2正好互补,互为相反,所以在运用起来差别也并不太大。 而从计数模式上来看,PWM也和TIMx在作定时器时一样,也有向上计数模式、向下计数模式和中心对齐模式,关于3种模式的具体资料,可以查看《STM32参考手册》的“14.3.9 PWM模式”一节,在此就不详细赘述了。 1.2PWM输出管脚 PWM的输出管脚是确定好的,具体的引脚功能可以查看《STM32参考手册》的“8.3.7 定时器复用功能重映射”一节。在此需要强调的是,不同的TIMx有分配不同的引脚,但是考虑到管脚复用功能,STM32提出了一个重映像的概念,就是说通过设置某一些相关的寄存器,来使得在其他非原始指定的管脚上也能输出PWM。但是这些重映像的管脚也是由参考手册给出的。比如

STM32入门C语言详解精编版

阅读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; //响应优先级

STM32学习笔记

1、GPIO函数: 输出: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);//此例以PA12口为例 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_SET); //此例以PA12口为例 HAL_GPIO_ TogglePin(GPIOA,GPIO_PIN_12); //此例以PA12口为例 2、串口函数: 1、串口发送/接收函数 HAL_UART_Transmit();串口轮询模式发送,使用超时管理机制 HAL_UART_Receive();串口轮询模式接收,使用超时管理机制 HAL_UART_Transmit_IT();串口中断模式发送 HAL_UART_Receive_IT();串口中断模式接收 HAL_UART_Transmit_DMA();串口DMA模式发送 HAL_UART_Transmit_DMA();串口DMA模式接收 2、串口中断函数 HAL_UART_TxHalfCpltCallback();一半数据发送完成时调用 HAL_UART_TxCpltCallback();数据完全发送完成后调用 HAL_UART_RxHalfCpltCallback();一半数据接收完成时调用 HAL_UART_RxCpltCallback();数据完全接受完成后调用 HAL_UART_ErrorCallback();传输出现错误时调用 例程:串口接收中断 uint8_t aTxStartMessages[] = "\r\n******UART commucition using IT******\r\nPlease enter 10 characters:\r\n"; uint8_t aRxBuffer[20]; 2、在main函数中添加两个语句通过串口中断发送aTxStartMessage数组的数据和接收数据10个字符,保存在数组aRxBuffer中 HAL_UART_Transmit_IT(&huart1 ,(uint8_t*)aTxStartMessages,sizeof(aTxStartMessages)); //sizeof()可读取目标长度 HAL_UART_Receive_IT(&huart1,(uint8_t*)aRxBuffer,10); 3、在main.c文件后面添加中断接收完成函数,将接收到的数据又通过串口发送回去。 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { UNUSED(huart); HAL_UART_Transmit(&huart1,(uint8_t*)aRxBuffer,10,0xFFFF);//(uint8_t*)aRxBuffer为字符串地址,10为字符串长度,0xFFFF为超时时可以在中间加任何可执行代码。 }

STM32自学笔记

一、原子位操作: 原子位操作定义在文件中。令人感到奇怪的是位操作函数是对普通的内存地址进行操作的。原子位操作在多数情况下是对一个字长的内存访问,因而位号该位于0-31之间(在64位机器上是0-63之间),但是对位号的范围没有限制。 原子操作中的位操作部分函数如下: void set_bit(int nr, void *addr)原子设置addr所指的第nr位 void clear_bit(int nr, void *addr)原子的清空所指对象的第nr位 void change_bit(nr, void *addr)原子的翻转addr所指的第nr位int test_bit(nr, void *addr)原子的返回addr位所指对象nr位int test_and_set_bit(nr, void *addr)原子设置addr所指对象的第nr位,并返回原先的值 int test_and_clear_bit(nr, void *addr)原子清空addr所指对象的第nr位,并返回原先的值 int test_and_change_bit(nr, void *addr)原子翻转addr所指对象的第nr位,并返回原先的值 unsigned long word = 0; set_bit(0, &word); /*第0位被设置*/ set_bit(1, &word); /*第1位被设置*/ clear_bit(1, &word); /*第1位被清空*/ change_bit(0, &word); /*翻转第0位*/ 二、STM32的GPIO锁定: 三、中断挂起: 因为某种原因,中断不能马上执行,所以“挂起”等待。比如有高、低级别的中断同时发生,就挂起低级别中断,等高级别中断程序执行完,在执行低级别中断。四、固文件: 固件(Firmware)就是写入EROM(可擦写只读存储器)或EEPROM(电可擦可编程只读存储器)中的程序。 五、固件库:包含各个外设或者内核的驱动头文件和C文件。 六、TIx的输入捕获滤波器(消抖): 采样频率fSAMPLING,采样次数N,如果以采样频率对一脉冲进行采样时,如果在N个采样方波里该脉宽不变,则视为一次有效的脉冲,否则视为无效的脉冲。 七、高级定时器的PWM互补输出: 常用于X相电机驱动,其中的互补输出则防止电机的死区出现。

稀里糊涂学习STM32全本

J^dt ? ? ? ?? ???? ? ????? ? q?670 ? ?8 ?Yū ?Yū ? ū a ?q? 1 /? ? ? % - ? $ L ? ) 670 &RUWH[ 0 #&RUWH[ 0 ? $50Y 0 8 ? )??? ?) b?0 ) #0 0 ? $50? 0 # 0 ?? $50 2 (? #) 8? ?? 3 δ . (?)?- ? δ 8?b q? ? - /? ??? ? ? ? ?-? -? ? -?670 ?? " $ ? -? / ) ? -?670 q ? ǎ ? ? 670 0) Ρ? ? ???p ? - ? ?? ?- # ? ) ?μ-670 )-1 ? -? - ? ? ?+ ??? ? ?? ?) ??(? C ? - ?

J^dt %- (0? ) ? (??/ ?W ? μ - d /??q670 e $% $% " - ? ?p ?" ? ?? 8 '0$ ? c? q? G>1" 3 ? $+% ?- ??? ? ? ? ? $+%"? ? $3% $3% ?P ?? ? ?? ? ! 8? $ $3% ?ǎ2-? #$3% ?ǎ?-? .1" (-? v 670 " ? 8 ?)? ?v b? 50 670 ) [[[?#μ?? 9

J^dt ? ) 670 ? ?? " ?? . 5HJ K # - 0$7 ; K - ? ? 670 ? ? %3 ? ? ? d K e d670 ) [B6WG3HULSKB/LEB9 e? LQF ?-/ / ?? 0) 670 ? ?< ? - ?) - % ? +- ?) ?> ? / -? ?? ??? ??670 t " ?J ? )- >- .? )8 t)?? ? ? - ? ? */ - -? -% + ) ? ?? % - ? ? 2 ??9 ?a- ǎ ǎ? a

STM32学习心得笔记

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使用。

STM32经典学习资料——SPI

SPI总线 与IIC类似,SPI也是一种通信协议。今天我们就以WX25X16芯片为例来介绍SPI.首先我们来看下硬件连接。 、从原理图可以看到该芯片需要单片机控制的管脚有4个,非别是CS,DO,DIO,CLK.其中CS 是片选信号,只有将该位拉低才能选中该芯片。DO,DIO分别是输出和输入。CLK是时钟信号。SPI通信的步骤如下所示: 1)获取地址1 2)获取地址2 3)擦除扇区 4)写入数据 好的,下面我们对每个步骤进行分析 (1)在对芯片操作前先要对端口及SPI外设进行相应的设置: /* 函数名:SPI_FLASH_Init(void) 功能:对端口和SPI初始化 输入:无 输出:无 调用:被主函数调用 */ void SPI_FLASH_Init(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* Enable SPI1 and GPIO clocks */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE); /*!< SPI_FLASH_SPI Periph clock enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); /*将PA5(CLK)配置成复用推挽输出*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); /*将PA6(DO)设置成浮空输入*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOA, &GPIO_InitStructure); /将PA7(DIO)设为浮空输入/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_Init(GPIOA, &GPIO_InitStructure); /将PA4(CS)设为推挽输出/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); /拉高CS,失能芯片,该语句是宏定义,就是置高PA4/ SPI_FLASH_CS_HIGH(); /* SPI配置/ // W25X16: data input on the DIO pin is sampled on the rising edge of the CLK. // Data on the DO and DIO pins are clocked out on the falling edge of CLK. /*将SPI设为全双工模式*/ SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; /*将SPI设为主模式*/ SPI_InitStructure.SPI_Mode = SPI_Mode_Master; /*将SPI通信的数据大小设为8位*/ SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; /*将CLK的高电平设为空闲*/ SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; /*设置在第二个时钟沿捕获数据*/ SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; /*指定NSS信号由软件管理*/ SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; /SPI_BaudRatePrescaler用来定义波特率预分频的值,这个值用以设置发送和接收的SCK时钟/ SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; /SPI_FirstBit指定了数据传输从高位还是低位开始/ SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; /SPI_CRCPolynomial定义了用于CRC值计算的多项式/ SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); /* Enable SPI1 */ SPI_Cmd(SPI1, ENABLE); } (2)获取器件地址1

STM32学习笔记(18)-数据的保存和毁灭

数据的保存和毁灭(2) 和以前学到的有关数据保存不同,这里的数据保存还有“保密”之意,即一旦受到意外的侵入,STM32将毁灭数据。这是通过Tamper机制来实现的。 以下是数据手册中的有关说明: 5.3.1 侵入检测 当TAMPER引脚上的信号从0变成1或者从1变成0(取决于备份控制寄存器BKP_CR的TPAL 位),会产生一个侵入检测事件。侵入检测事件将所有数据备份寄存器内容清除。然而为了避免丢失侵入事件,侵入检测信号是边沿检测的信号与侵入检测允许位的逻辑与,从而在侵入检测引脚被允许前发生的侵入事件也可以被检测到。 ●当 TPAL=0 时:如果在启动侵入检测TAMPER引脚前(通过设置TPE位)该引脚已经为高电平,一旦启动侵入检测功能,则会产生一个额外的侵入事件(尽管在TPE位置’1’后并没有出现上升沿)。 ●当 TPAL=1 时:如果在启动侵入检测引脚TAMPER前(通过设置TPE位)该引脚已经为低电平,一旦启动侵入检测功能,则会产生一个额外的侵入事件(尽管在TPE位置’1’后并没有出现下降沿)。 设置BKP_CSR寄存器的TPIE位为’1’,当检测到侵入事件时就会产生一个中断。 在一个侵入事件被检测到并被清除后,侵入检测引脚TAMPER应该被禁止。然后,在再次写入备份数据寄存器前重新用TPE位启动侵入检测功能。这样,可以阻止软件在侵入检测引脚上仍然有侵入事件时对备份数据寄存器进行写操作。这相当于对侵入引脚TAMPER进行电平检测。 注:当V DD电源断开时,侵入检测功能仍然有效。为了避免不必要的复位数据备份寄存器,TAMPER引脚应该在片外连接到正确的电平。 显然,Tamper需要硬件与之配合。以上数据手册描述了硬件配置时的一些注意事项。 (1)可以是把引脚由低电平到高电平认为是一次侵入,也可以把引脚从高电平变到低电平认为是一次侵入,这通过TPAL来设置。

STM32学习心得(新手必看)

STM32学习心得(新手必看) (作者:logokfu 邮箱:g535343589@https://www.doczj.com/doc/0f2256985.html, ) 在这里说下我的学习心得体会(照顾下新手,老鸟都表笑哦,呵呵)。 说下关于开发环境的建立,都说万事开头难,每种芯片都有它的开发环 境,首先得熟悉STM32的开发环境。用的最多就是MDK 和IAR 了,关于MDK ,这个用过51单片机的筒子肯定都知道keil uvision 。这个MDK 其实就是专门开发ARM 芯片的工具。开发51单片机的那个叫C51 。这个C51和MDK 共同使用keil uvsion 这个UI 界面。也是说C51和MDK 共同使用keil uvsion 这个外壳。好了,关于开发软件的介绍就介绍这么多,有什么还不清楚的,筒子们可以邮箱联系我。当然支持STM32的集成开发环境(IDE )还不止MDK 和IAR ,只不过这两个使用的人相对其他工具来说用的人比较多吧。另外 RIDE, HiTOP , TrueSTUDIO 这个三个开发工具也支持STM32的开发(可能还有其他的工具,不过我不知道)。有兴趣的盆友可以使用下尝尝鲜。 说下关于ST 官方为我们提供的固件库的使用问题。不要觉得固件库是 这个什么可怕的东西,固件库是ST 为用户提供的函数库,这些函数帮 我们一次性解决多个寄存器的设置问题。如果没有固件库的话,那么我们就需要像使用51单片机那样直接设置要使用的寄存器,在51单片机上为寄存器直接赋值可能没什么的,但是由于STM32的寄存器太多,如果一个个设置的话会很麻烦,有时候还会忘掉某些寄存器的设置,ST 提供的固件库正是为我们提供了这些方便。我们只需要为相关函数指定参数就可以完成寄存器的设置了。为产品的快速开发提供了保障。当然新固件库是好,但是却会对新手理解硬件结构造成一定的影响。有的人喜欢直接为STM32的寄存器直接赋值,说这样子比较直观,有的人喜欢使用固件库。当然这个是个人喜好,大家可以根据自己的喜好进行选择。我的建议是可以直接用固件库,虽然我对硬件结构还没了解很多,用固件库的话可能会觉得有点学习的不是很踏实,因此我们可以借助开发工具的go to definition 功能,顺藤摸瓜。在最终的函数中,我们就能发现这些函数到底为哪些寄存器设置了什么值。这样看多了,时间久了各个寄存器也就了解的差不多了。当然进行下随着产品的不断推出,固件库版本可能会不断的更新。之前的固件库版 首先 其次

相关主题
文本预览
相关文档 最新文档