(转)Hid Report Descriptor 报告描述符的详细介绍

admin 发表了文章 • 0 个评论 • 6416 次浏览 • 2016-05-24 15:14 • 来自相关话题

在USB中,USB HOST是通过各种描述符来识别设备的,有设备描述符,配置描述符,接口描述符,端点描述符,字符串描述符,报告描述符等等。USB报告描述符(Report Descriptor)是HID设备中的一个描述符,它是比较复杂的一个描述符。

USB HID设备是通过报告来给传送数据的,报告有输入报告和输出报告。输入报告是USB设备发送给主机的,例如USB鼠标将鼠标移动和鼠标点击等信息返回给电脑,键盘将按键数据数据返回给电脑等;输出报告是主机发送给USB设备的,例如键盘上的数字键盘锁定灯和大写字母锁定灯等。报告是一个数据包,里面包含的是所要传送的数据。输入报告是通过中断输入端点输入的,而输出报告有点区别,当没有中断输出端点时,可以通过控制输出端点0发送,当有中断输出端点时,通过中断输出端点发出。

而报告描述符,是描述一个报告以及报告里面的数据是用来干什么用的。通过它,USB HOST可以分析出报告里面的数据所表示的意思。它通过控制输入端点0返回,主机使用获取报告描述符命令来获取报告描述符,注意这个请求
发送到接口的,而不是到设备。一个报告描述符可以描述多个报告,不同的报告通过报告ID来识别,报告ID在报告最前面,即第一个字节。当报告描述符中没有规定报告ID时,报告中就没有ID字段,开始就是数据。更详细的说明请参看USB HID协议,该协议可从Http://www.usb.org下载。

下面通过由HID Descriptor tool生成的USB鼠标和USB键盘来说明一下报告描述符和报告。code char KeyBoardReportDescriptor[63] = {
//表示用途页为通用桌面设备
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//表示用途为键盘
0x09, 0x06, // USAGE (Keyboard)

//表示应用集合,必须要以END_COLLECTION来结束它,见最后的END_COLLECTION
0xa1, 0x01, // COLLECTION (Application)

//表示用途页为按键
0x05, 0x07, // USAGE_PAGE (Keyboard)
//用途最小值,这里为左ctrl键
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
//用途最大值,这里为右GUI键,即window键
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
//逻辑最小值为0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值为1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//报告大小(即这个字段的宽度)为1bit,所以前面的逻辑最小值为0,逻辑最大值为1
0x75, 0x01, // REPORT_SIZE (1)
//报告的个数为8,即总共有8个bits
0x95, 0x08, // REPORT_COUNT (8)
//输入用,变量,值,绝对值。像键盘这类一般报告绝对值,
//而鼠标移动这样的则报告相对值,表示鼠标移动多少
0x81, 0x02, // INPUT (Data,Var,Abs)
//上面这这几项描述了一个输入用的字段,总共为8个bits,每个bit表示一个按键
//分别从左ctrl键到右GUI键。这8个bits刚好构成一个字节,它位于报告的第一个字节。
//它的最低位,即bit-0对应着左ctrl键,如果返回的数据该位为1,则表示左ctrl键被按下,
//否则,左ctrl键没有按下。最高位,即bit-7表示右GUI键的按下情况。中间的几个位,
//需要根据HID协议中规定的用途页表(HID Usage Tables)来确定。这里通常用来表示
//特殊键,例如ctrl,shift,del键等

//这样的数据段个数为1
0x95, 0x01, // REPORT_COUNT (1)
//每个段长度为8bits
0x75, 0x08, // REPORT_SIZE (8)
//输入用,常量,值,绝对值
0x81, 0x03, // INPUT (Cnst,Var,Abs)

//上面这8个bit是常量,设备必须返回0

//这样的数据段个数为5
0x95, 0x05, // REPORT_COUNT (5)
//每个段大小为1bit
0x75, 0x01, // REPORT_SIZE (1)
//用途是LED,即用来控制键盘上的LED用的,因此下面会说明它是输出用
0x05, 0x08, // USAGE_PAGE (LEDs)
//用途最小值是Num Lock,即数字键锁定灯
0x19, 0x01, // USAGE_MINIMUM (Num Lock)
//用途最大值是Kana,这个是什么灯我也不清楚^_^
0x29, 0x05, // USAGE_MAXIMUM (Kana)
//如前面所说,这个字段是输出用的,用来控制LED。变量,值,绝对值。
//1表示灯亮,0表示灯灭
0x91, 0x02, // OUTPUT (Data,Var,Abs)
//这样的数据段个数为1
0x95, 0x01, // REPORT_COUNT (1)
//每个段大小为3bits
0x75, 0x03, // REPORT_SIZE (3)
//输出用,常量,值,绝对
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
//由于要按字节对齐,而前面控制LED的只用了5个bit,
//所以后面需要附加3个不用bit,设置为常量。

//报告个数为6
0x95, 0x06, // REPORT_COUNT (6)
//每个段大小为8bits
0x75, 0x08, // REPORT_SIZE (8)
//逻辑最小值0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值255
0x25, 0xFF, // LOGICAL_MAXIMUM (255)
//用途页为按键
0x05, 0x07, // USAGE_PAGE (Keyboard)
//使用最小值为0
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
//使用最大值为0x65
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
//输入用,变量,数组,绝对值
0x81, 0x00, // INPUT (Data,Ary,Abs)
//以上定义了6个8bit宽的数组,每个8bit(即一个字节)用来表示一个按键,所以可以同时
//有6个按键按下。没有按键按下时,全部返回0。如果按下的键太多,导致键盘扫描系统
//无法区分按键时,则全部返回0x01,即6个0x01。如果有一个键按下,则这6个字节中的第一
//个字节为相应的键值(具体的值参看HID Usage Tables),如果两个键按下,则第1、2两个
//字节分别为相应的键值,以次类推。

//关集合,跟上面的对应
0xc0 // END_COLLECTION
};通过上面的分析,我们知道这个报告中只有一个报告,所以没有报告ID,因此返回的都是实际使用的数据。总共有8字节输入,1字节输出。其中输入的第一字节用来表示特殊按键,第二字节保留,后面的六字节为普通按键。如果只有左ctrl键按下,则返回01 00 00 00 00 00 00 00(十六进制),如果只有数字键1 按下,则返回00 00 59 00 00 00 00 00,如果数字键1 和2 同时按下,则返回00 00 59 5A 00 00 00 00,如果再按下左shift 键,则返回02 00 59 5A 00 00 00 00,然后再释放1   键,则返回02 00 5A 00 00 00 00 00,然后全部按键释放,则返回00 00 00 00 00 00 00 00。这些数据(即报告)都是通过中断端点返回的。当按下Num Lock键时,PC会发送输出报告,从报告描述符中我们知道,Num Lock的LED对应着输出报告的最低位,当数字小键盘打开时,输出xxxxxxx1(二进制,打x的由其它的LED状态决定);
当数字小键盘关闭时,输出xxxxxxx0(同前)。取出最低位就可以控制数字键锁定LED了。


下面这个报告描述符是USB鼠标报告描述符,比起键盘的来说要简单些。它描述了4个字节,第一个字节表示按键,第二个字节表示x轴(即鼠标左右移动,0表示不动,正值表示往右移,负值表示往左移),第三个字节表示y轴(即鼠标上下移动,0表示不动,正值表示往下移动,负值表示往上移动),第四个字节表示鼠标滚轮(正值为往上滚动,负值为往下滚动)。code char MouseReportDescriptor[52] = {
//通用桌面设备
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//鼠标
0x09, 0x02, // USAGE (Mouse)
//集合
0xa1, 0x01, // COLLECTION (Application)
//指针设备
0x09, 0x01, // USAGE (Pointer)
//集合
0xa1, 0x00, // COLLECTION (Physical)
//按键
0x05, 0x09, // USAGE_PAGE (Button)
//使用最小值1
0x19, 0x01, // USAGE_MINIMUM (Button 1)
//使用最大值3。1表示左键,2表示右键,3表示中键
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
//逻辑最小值0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//数量为3
0x95, 0x03, // REPORT_COUNT (3)
//大小为1bit
0x75, 0x01, // REPORT_SIZE (1)
//输入,变量,数值,绝对值
//以上3个bit分别表示鼠标的三个按键情况,最低位(bit-0)为左键
//bit-1为右键,bit-2为中键,按下时对应的位值为1,释放时对应的值为0
0x81, 0x02, // INPUT (Data,Var,Abs)
//填充5个bit,补足一个字节
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x05, // REPORT_SIZE (5)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
//用途页为通用桌面
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//用途为X
0x09, 0x30, // USAGE (X)
//用途为Y
0x09, 0x31, // USAGE (Y)
//用途为滚轮
0x09, 0x38, // USAGE (Wheel)
//逻辑最小值为-127
0x15, 0x81, // LOGICAL_MINIMUM (-127)
//逻辑最大值为+127
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
//大小为8个bits
0x75, 0x08, // REPORT_SIZE (8)
//数量为3个,即分别代表x,y,滚轮
0x95, 0x03, // REPORT_COUNT (3)
//输入,变量,值,相对值
0x81, 0x06, // INPUT (Data,Var,Rel)
//关集合
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};


通过对上面的报告分析,我们知道报告返回4个字节,没有报告ID。如果鼠标左键按下,则返回01 00 00 00(十六进制值),如果右键按下,则返回02 00 00 00,如果中键按下,则返回04 00 00 00,如果三个键同时按下,则返回07 00 00 00。如果鼠标往右移动则第二字节返回正值,值越大移动速度越快。其它的类推。


这里只对报告描述符做一个简单的介绍,更详细的资料请参看USB HID协议以及HID Usage Tables
USB报告描述符可以通过使用HID Descriptor tool来生成
 
>>本文引用自:http://www.cnblogs.com/wzh206/ ... .html 查看全部
在USB中,USB HOST是通过各种描述符来识别设备的,有设备描述符,配置描述符,接口描述符,端点描述符,字符串描述符,报告描述符等等。USB报告描述符(Report Descriptor)是HID设备中的一个描述符,它是比较复杂的一个描述符。

USB HID设备是通过报告来给传送数据的,报告有输入报告和输出报告。输入报告是USB设备发送给主机的,例如USB鼠标将鼠标移动和鼠标点击等信息返回给电脑,键盘将按键数据数据返回给电脑等;输出报告是主机发送给USB设备的,例如键盘上的数字键盘锁定灯和大写字母锁定灯等。报告是一个数据包,里面包含的是所要传送的数据。输入报告是通过中断输入端点输入的,而输出报告有点区别,当没有中断输出端点时,可以通过控制输出端点0发送,当有中断输出端点时,通过中断输出端点发出。

而报告描述符,是描述一个报告以及报告里面的数据是用来干什么用的。通过它,USB HOST可以分析出报告里面的数据所表示的意思。它通过控制输入端点0返回,主机使用获取报告描述符命令来获取报告描述符,注意这个请求
发送到接口的,而不是到设备。一个报告描述符可以描述多个报告,不同的报告通过报告ID来识别,报告ID在报告最前面,即第一个字节。当报告描述符中没有规定报告ID时,报告中就没有ID字段,开始就是数据。更详细的说明请参看USB HID协议,该协议可从Http://www.usb.org下载。

下面通过由HID Descriptor tool生成的USB鼠标和USB键盘来说明一下报告描述符和报告。
code char KeyBoardReportDescriptor[63] = {
//表示用途页为通用桌面设备
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//表示用途为键盘
0x09, 0x06, // USAGE (Keyboard)

//表示应用集合,必须要以END_COLLECTION来结束它,见最后的END_COLLECTION
0xa1, 0x01, // COLLECTION (Application)

//表示用途页为按键
0x05, 0x07, // USAGE_PAGE (Keyboard)
//用途最小值,这里为左ctrl键
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
//用途最大值,这里为右GUI键,即window键
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
//逻辑最小值为0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值为1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//报告大小(即这个字段的宽度)为1bit,所以前面的逻辑最小值为0,逻辑最大值为1
0x75, 0x01, // REPORT_SIZE (1)
//报告的个数为8,即总共有8个bits
0x95, 0x08, // REPORT_COUNT (8)
//输入用,变量,值,绝对值。像键盘这类一般报告绝对值,
//而鼠标移动这样的则报告相对值,表示鼠标移动多少
0x81, 0x02, // INPUT (Data,Var,Abs)
//上面这这几项描述了一个输入用的字段,总共为8个bits,每个bit表示一个按键
//分别从左ctrl键到右GUI键。这8个bits刚好构成一个字节,它位于报告的第一个字节。
//它的最低位,即bit-0对应着左ctrl键,如果返回的数据该位为1,则表示左ctrl键被按下,
//否则,左ctrl键没有按下。最高位,即bit-7表示右GUI键的按下情况。中间的几个位,
//需要根据HID协议中规定的用途页表(HID Usage Tables)来确定。这里通常用来表示
//特殊键,例如ctrl,shift,del键等

//这样的数据段个数为1
0x95, 0x01, // REPORT_COUNT (1)
//每个段长度为8bits
0x75, 0x08, // REPORT_SIZE (8)
//输入用,常量,值,绝对值
0x81, 0x03, // INPUT (Cnst,Var,Abs)

//上面这8个bit是常量,设备必须返回0

//这样的数据段个数为5
0x95, 0x05, // REPORT_COUNT (5)
//每个段大小为1bit
0x75, 0x01, // REPORT_SIZE (1)
//用途是LED,即用来控制键盘上的LED用的,因此下面会说明它是输出用
0x05, 0x08, // USAGE_PAGE (LEDs)
//用途最小值是Num Lock,即数字键锁定灯
0x19, 0x01, // USAGE_MINIMUM (Num Lock)
//用途最大值是Kana,这个是什么灯我也不清楚^_^
0x29, 0x05, // USAGE_MAXIMUM (Kana)
//如前面所说,这个字段是输出用的,用来控制LED。变量,值,绝对值。
//1表示灯亮,0表示灯灭
0x91, 0x02, // OUTPUT (Data,Var,Abs)
//这样的数据段个数为1
0x95, 0x01, // REPORT_COUNT (1)
//每个段大小为3bits
0x75, 0x03, // REPORT_SIZE (3)
//输出用,常量,值,绝对
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
//由于要按字节对齐,而前面控制LED的只用了5个bit,
//所以后面需要附加3个不用bit,设置为常量。

//报告个数为6
0x95, 0x06, // REPORT_COUNT (6)
//每个段大小为8bits
0x75, 0x08, // REPORT_SIZE (8)
//逻辑最小值0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值255
0x25, 0xFF, // LOGICAL_MAXIMUM (255)
//用途页为按键
0x05, 0x07, // USAGE_PAGE (Keyboard)
//使用最小值为0
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
//使用最大值为0x65
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
//输入用,变量,数组,绝对值
0x81, 0x00, // INPUT (Data,Ary,Abs)
//以上定义了6个8bit宽的数组,每个8bit(即一个字节)用来表示一个按键,所以可以同时
//有6个按键按下。没有按键按下时,全部返回0。如果按下的键太多,导致键盘扫描系统
//无法区分按键时,则全部返回0x01,即6个0x01。如果有一个键按下,则这6个字节中的第一
//个字节为相应的键值(具体的值参看HID Usage Tables),如果两个键按下,则第1、2两个
//字节分别为相应的键值,以次类推。

//关集合,跟上面的对应
0xc0 // END_COLLECTION
};
通过上面的分析,我们知道这个报告中只有一个报告,所以没有报告ID,因此返回的都是实际使用的数据。总共有8字节输入,1字节输出。其中输入的第一字节用来表示特殊按键,第二字节保留,后面的六字节为普通按键。如果只有左ctrl键按下,则返回01 00 00 00 00 00 00 00(十六进制),如果只有数字键1 按下,则返回00 00 59 00 00 00 00 00,如果数字键1 和2 同时按下,则返回00 00 59 5A 00 00 00 00,如果再按下左shift 键,则返回02 00 59 5A 00 00 00 00,然后再释放1   键,则返回02 00 5A 00 00 00 00 00,然后全部按键释放,则返回00 00 00 00 00 00 00 00。这些数据(即报告)都是通过中断端点返回的。当按下Num Lock键时,PC会发送输出报告,从报告描述符中我们知道,Num Lock的LED对应着输出报告的最低位,当数字小键盘打开时,输出xxxxxxx1(二进制,打x的由其它的LED状态决定);
当数字小键盘关闭时,输出xxxxxxx0(同前)。取出最低位就可以控制数字键锁定LED了。


下面这个报告描述符是USB鼠标报告描述符,比起键盘的来说要简单些。它描述了4个字节,第一个字节表示按键,第二个字节表示x轴(即鼠标左右移动,0表示不动,正值表示往右移,负值表示往左移),第三个字节表示y轴(即鼠标上下移动,0表示不动,正值表示往下移动,负值表示往上移动),第四个字节表示鼠标滚轮(正值为往上滚动,负值为往下滚动)。
code char MouseReportDescriptor[52] = {
//通用桌面设备
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//鼠标
0x09, 0x02, // USAGE (Mouse)
//集合
0xa1, 0x01, // COLLECTION (Application)
//指针设备
0x09, 0x01, // USAGE (Pointer)
//集合
0xa1, 0x00, // COLLECTION (Physical)
//按键
0x05, 0x09, // USAGE_PAGE (Button)
//使用最小值1
0x19, 0x01, // USAGE_MINIMUM (Button 1)
//使用最大值3。1表示左键,2表示右键,3表示中键
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
//逻辑最小值0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//数量为3
0x95, 0x03, // REPORT_COUNT (3)
//大小为1bit
0x75, 0x01, // REPORT_SIZE (1)
//输入,变量,数值,绝对值
//以上3个bit分别表示鼠标的三个按键情况,最低位(bit-0)为左键
//bit-1为右键,bit-2为中键,按下时对应的位值为1,释放时对应的值为0
0x81, 0x02, // INPUT (Data,Var,Abs)
//填充5个bit,补足一个字节
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x05, // REPORT_SIZE (5)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
//用途页为通用桌面
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//用途为X
0x09, 0x30, // USAGE (X)
//用途为Y
0x09, 0x31, // USAGE (Y)
//用途为滚轮
0x09, 0x38, // USAGE (Wheel)
//逻辑最小值为-127
0x15, 0x81, // LOGICAL_MINIMUM (-127)
//逻辑最大值为+127
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
//大小为8个bits
0x75, 0x08, // REPORT_SIZE (8)
//数量为3个,即分别代表x,y,滚轮
0x95, 0x03, // REPORT_COUNT (3)
//输入,变量,值,相对值
0x81, 0x06, // INPUT (Data,Var,Rel)
//关集合
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};


通过对上面的报告分析,我们知道报告返回4个字节,没有报告ID。如果鼠标左键按下,则返回01 00 00 00(十六进制值),如果右键按下,则返回02 00 00 00,如果中键按下,则返回04 00 00 00,如果三个键同时按下,则返回07 00 00 00。如果鼠标往右移动则第二字节返回正值,值越大移动速度越快。其它的类推。


这里只对报告描述符做一个简单的介绍,更详细的资料请参看USB HID协议以及HID Usage Tables
USB报告描述符可以通过使用HID Descriptor tool来生成
 
>>本文引用自:http://www.cnblogs.com/wzh206/ ... .html

【HAL库每天一例】第000例:STCubeMX软件使用方法

yingshi 发表了文章 • 4 个评论 • 5984 次浏览 • 2016-05-24 10:56 • 来自相关话题

【HAL库每天一例】系列例程从今天开始持续更新。。。。。
我们将坚持每天至少发布一个基于YS-F1Pro开发板的HAL库例程,
该系列例程将带领大家从零开始使用HAL库,后面会持续添加模块应用例程。
同样的,我们还程序发布基于HAL库的指导文档和视频教程,欢迎持续关注,并提出改进意见。

例程下载:
资料包括程序、相关说明资料以及软件使用截图
链接:http://pan.baidu.com/s/1i574oPv
密码:r3s3
(硬石YS-F1Pro开发板HAL库例程持续更新\1. 软件设计之基本裸机例程(HAL库版本)\YSF1_HAL-000. STCubeMX软件使用)
STCubeMX软件新建Keil和IAR工程使用步骤:
首先是软件下载(也可在我们的百度云下载):
1.STCubeMX下载地址:http://www.stmicroelectronics.com.cn/web/cn/catalog/tools/PF259242






2.STM32CubeF1下载地址:http://www.stmicroelectronics.com.cn/web/cn/catalog/tools/PF260820






3.Patch_CubeFW_F1下载地址:http://www.stmicroelectronics.com.cn/web/cn/catalog/tools/PF263153






4.下载完,自行安装STCubeMX,可能还需要安装Java。































































































  查看全部
【HAL库每天一例】系列例程从今天开始持续更新。。。。。
我们将坚持每天至少发布一个基于YS-F1Pro开发板的HAL库例程,
该系列例程将带领大家从零开始使用HAL库,后面会持续添加模块应用例程。
同样的,我们还程序发布基于HAL库的指导文档和视频教程,欢迎持续关注,并提出改进意见。

例程下载:
资料包括程序、相关说明资料以及软件使用截图
链接:http://pan.baidu.com/s/1i574oPv
密码:r3s3
(硬石YS-F1Pro开发板HAL库例程持续更新\1. 软件设计之基本裸机例程(HAL库版本)\YSF1_HAL-000. STCubeMX软件使用)
STCubeMX软件新建Keil和IAR工程使用步骤:
首先是软件下载(也可在我们的百度云下载):
1.STCubeMX下载地址:http://www.stmicroelectronics.com.cn/web/cn/catalog/tools/PF259242

CubeMX_1.jpg


2.STM32CubeF1下载地址:http://www.stmicroelectronics.com.cn/web/cn/catalog/tools/PF260820

CubeMX_2.jpg


3.Patch_CubeFW_F1下载地址:http://www.stmicroelectronics.com.cn/web/cn/catalog/tools/PF263153

CubeMX_3.jpg


4.下载完,自行安装STCubeMX,可能还需要安装Java。

CubeMX_4.jpg


CubeMX_5.jpg


CubeMX_6.jpg


CubeMX_7.jpg


CubeMX_8.jpg


CubeMX_9.jpg


CubeMX_10.jpg


CubeMX_11.jpg


CubeMX_12.jpg


CubeMX_13.jpg


CubeMX_14.jpg


CubeMX_15.jpg


CubeMX_16.jpg


CubeMX_17.jpg


CubeMX_18.jpg


CubeMX_19.jpg


CubeMX_20.jpg


CubeMX_21.jpg


CubeMX_22.jpg

 

(转)核心常用数据结构-【双向链表】-基于【ucos】

回复

admin 发起了问题 • 1 人关注 • 0 个回复 • 2725 次浏览 • 2016-04-08 13:08 • 来自相关话题

(转)正交编码器在stm32中的应用 Quadrature encoders with the STM32F4

回复

admin 回复了问题 • 1 人关注 • 6 个回复 • 4635 次浏览 • 2016-04-03 12:33 • 来自相关话题

(转)Using USART1 on the STM32F4Discovery

回复

admin 发起了问题 • 1 人关注 • 0 个回复 • 2171 次浏览 • 2016-04-03 11:49 • 来自相关话题

(转)TIM3 Output Compare on the STM32 Family

admin 发表了文章 • 0 个评论 • 4102 次浏览 • 2016-03-13 23:34 • 来自相关话题

好久没时间继续更新stm32cube的程序了.
先转一些歪果仁对stm32的分析文章
TIM3 is a general purpose timer found on all the STM32 family processors. Among other features, it has four capture compare channels that can be used to generate regular interrupts. In this article I will show you how to set up simple interrupt events based on these features.

This is part of a series of articles about the TIM3 general purpose timer. In the preceding parts I introduced the TIM3 timer features and showed you how to identify the timer clock and set up the prescaler and reload register.You can see all the STM32 TIM3 posts here. Code is written using the Standard Peripheral Library for the STM32F4Discovery board. Timer related portions should run directly on other STM32 family members since they all have a TIM3

In this part, I will explain the basic configuration of the capture compare registers, describe how they can be used to generate regular interrupts and give you sample code to demonstrate the facility.
 
Capture Compare Registers
Here is a modified view of the TIM3 timer taken from the reference manual RM0090.





 
You can see that there are four registers associated with the timer TIM3. These are referred to as CCR1, CCR2, CCR3 and CCR4 or CCRx in general. Each channel is actually a pretty complex affair I am only going to concern myself with its behaviour in output compare modes. The reference manual RM0090 has this diagram of the output configuration:





 A look at the reference manual reveals that in basic Output Compare mode, I can program the output mode controller to one of four different states on a match. It can be:
frozen – that is, nothing happensset active (forced high)set inactive (forced low)toggled 
Other results are possible in other modes such as PWM.

In this article though, I just want to look at triggering an interrupt event. Although there are four Output Compare channels, and each can generate its own interrupt, they all get handled by the same Interrupt Service Routine (ISR). That means a series of tests will be needed in the ISR to determine exactly which channel caused the interrupt. I will just use two channels to illustrate how all this works.

Example Problem Statement

For this example, I want to set up two Output Compare channels on TIM3. Each channel will simply generate an interrupt. The ISR for that channel will toggle an LED on the target board. Each channel gets its own LED. Channel 1 will look after the blue LED and flash it  4 times per second. Channel 2 will look after the orange LED and flash it 2 times a second. These are simple numbers and the interrupt rate is quite low so that it is easy to see what is happening. Adapt the code for your needs.

Set up the Timer Prescale and Auto Reload

As with most timer problems, the first task is to set up the basic counter activity. This problem needs counts that are some handy but fairly small fraction of a second. Since the problem statement mentions numbers like 4 times per second and 8 times per second, it seems reasonable to try and arrange the counting frequency to be a convenient power of 2. Something like 256Hz or 128kHz of 3200Hz. Whatever, some number that lets me easily get binary fractions.

I have already established that the input frequency to the prescaler is 72MHz. This is an inconvenient value since there are no divisors that give me the kind of counter frequency that I was looking for. After a bit of fiddling with some of the factors of 72,000,000 I discovered that if I divide it by 28,125 I get 2560. Now that is a handy value for the counter since I can easily get many multiples of 2 and 5 out of it. Multiples of 3 would be more tricky but that is not part of my problem. The actual frequency for the counter is not critical in this case so long as it is high enough to give me the resolution I require.

Note that it is not sufficient to just pick a frequency. You must choose an value that divides exactly into the clock frequency if the results are to be accurate. the TIM3 counter frequency gets stored in a variable so that we can use it when calculating the intervals for the CCRx registers.
uint32_t TIM3COUNTER_Frequency = 2560;Now I need to set the prescaler to (28125-1) = 28124 so that my counter will increment at 2560Hz. The prescaler value is calculated from the timer input clock frequency (TIM3CLK_Frequency) and the desired TIM3COUNTER_Frequency.

When using the Output Compare channels, I need the CNT register to wrap around completely for reasons that should be clear shortly. To do this, I need to load the ARR register with 65535.

The update event interrupt is not needed so basic timer configuration looks like this:
void timer_init (void)
{
uint32_t TIM3CLK_Frequency = get_timer_clock_frequency();
uint16_t prescaler = (TIM3CLK_Frequency / TIM3COUNTER_Frequency) - 1;
/* allow the timer to wrap around naturally */
uint16_t reload = 65535;

/* make sure the peripheral is clocked */
RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* set everything back to default values */
TIM_TimeBaseStructInit (&TIM_TimeBaseStructure);
/* only changes from the defaults are needed */
TIM_TimeBaseStructure.TIM_Period = reload;
TIM_TimeBaseStructure.TIM_Prescaler = prescaler;
TIM_TimeBaseInit (TIM3, &TIM_TimeBaseStructure);
}Once the timer is started, it will free run at the specified frequency and then I have to think about how to configure the Output Compare registers.

Configure the Output Compare Function

Along with all the other configuration, I have to tell the timer to use the CCRx registers in output compare mode. The Standard Peripheral Library makes all this super easy:
void timer_ccr_init (void)
{
TIM_OCInitTypeDef TIM_OCInitStructure;
/* always initialise local variables before use */
TIM_OCStructInit (&TIM_OCInitStructure);
/* just use basic Output Compare Mode*/
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Active;
/* set the initial match interval for CC1 */
TIM_OCInitStructure.TIM_Pulse = CCR1_Interval;
TIM_OC1Init (TIM3, &TIM_OCInitStructure);
/* and then for CC2 */
TIM_OCInitStructure.TIM_Pulse = CCR2_Interval;
TIM_OC2Init (TIM3, &TIM_OCInitStructure);
}Each channel is configured separately and I need only configure the channels I need. They are completely independent. The initial interval must be loaded into the register when using the library code but that value is only good for the first match and assumes that the CNT register starts at zero. When a match between CNT and the CCRx register is made, the CNT register carries on counting. That leaves me with the problem of how to update the CCRx registers after each match. The value used for the interval will be the number of CNT counts between interrupts. That gets calculated depending on the problem requirements.

Updating the Output Compare Registers

There is nothing in the Output Compare that will reset the CNT register. Even if there was, it would be no good to me because I want to use the CNT register contents to match against up to four CCRx registers. So, how can I get recurring intervals?

The CNT register, by default, will just count upwards at a fixed rate, wrapping around to zero again when it gets to the end. I just made sure of that in the timer setup. Each CCRx register is just compared with CNT for equality – it does not care what the actual value is. Every time the ISR runs then, I just need to add a fixed interval to the CCRx register. When the CNT value catches up with the CCRx again another interrupt will be generated. Overflows will not matter since both registers overflow in the same way and a match must eventually occur. Updating the CCR is now very simple. This code fragment deals with channel 1. The other channels are similar:

uint16_t CCR1_Current = TIM_GetCapture1 (TIM3);
TIM_SetCompare1 (TIM3, CCR1_Current + CCR1_Interval);In the problem statement, I wanted the blue LED to flash 4 times a second using channel 1 and the orange LED to flash 2 times a second using channel 2. To get two flashes per second, I set the interval to a quarter second since, at each interrupt, I will toggle the LED pin. Similarly, I use 8 ticks per second for the blue LED
uint16_t CCR1_Interval = TIM3COUNTER_Frequency / 8; // blue LED
uint16_t CCR2_Interval = TIM3COUNTER_Frequency / 4; // orange LEDVariable Update Intervals

There is no reason why the same interval has to be used each time the interrupt fires. If I want to drive stepper motors, I can arrange to have a steadily diminishing interval as the motor accelerates. The values could be calculated or looked up in a table. Because I can have four channels and each is independent, I could easily drive four stepper motors from the same timer and have different motion profiles on each one.

Servicing the Output Compare Interrupt

Recall that several channels are all sharing the same ISR so in there I must poll each of the possible interrupt sources, act on any that are active and reset their flags ready for the next time. The entire ISR for my two channel problem looks like this.
void TIM3_IRQHandler (void)
{
/* run through the interrupt sources looking for a hit */
if (TIM_GetITStatus (TIM3, TIM_IT_CC1) != RESET) {
GPIO_ToggleBits (BLUE_LED);

uint16_t CCR1_Current = TIM_GetCapture1 (TIM3);
TIM_SetCompare1 (TIM3, CCR1_Current + CCR1_Interval);

TIM_ClearITPendingBit (TIM3, TIM_IT_CC1);
}
if (TIM_GetITStatus (TIM3, TIM_IT_CC2) != RESET) {
GPIO_ToggleBits (ORANGE_LED);

uint16_t CCR2_Current = TIM_GetCapture2 (TIM3);
TIM_SetCompare2 (TIM3, CCR2_Current + CCR2_Interval);

TIM_ClearITPendingBit (TIM3, TIM_IT_CC2);
}
}The action associated with each channel here is just toggling an LED on and off. It could just as easily be setting the pattern of bits needed to drive the phases of a stepper motor. Just try to keep the ISR code as short and simple as you can. All that remains is to enable the interrupt and let it loose.

Enabling the Output Compare Interrupt

Although all the Output Compare channels share an ISR, each has its own interrupt enable flag so they can be turned on and off individually. Again, the Standard Peripheral Library makes this easy:
void timer_interrupt_enable (void)
{
/*
* It is important to clear any pending interrupt flags since the timer
* has been free-running since we last used it and that may generate
* interrupts on match even though the associated interrupt event has
* not been enabled.
*/
TIM_ClearITPendingBit (TIM3, TIM_IT_CC1 | TIM_IT_CC2);
/* put the counter into a known state */
TIM_SetCounter (TIM3, 0);
/* enable the interrupt for CC1 and CC2 only */
TIM_ITConfig (TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
}Once the timer is started, my ISR should run and the LEDs will flash in the required way.

Putting it Together

Here is a complete code listing demonstrating how to do all these things with the STM32F4 Discovery
#include "stm32f4xx.h"
#include "systick.h"

#define MHz 1000000L
#define KHz 1000L


// The LED indicators on the STM32F4Discovery board
#define LED_PORT GPIOD
#define LED_PORT_CLOCK RCC_AHB1Periph_GPIOD
#define GREEN_PIN GPIO_Pin_12
#define ORANGE_PIN GPIO_Pin_13
#define RED_PIN GPIO_Pin_14
#define BLUE_PIN GPIO_Pin_15
#define ALL_LED_PINS GREEN_PIN | ORANGE_PIN | RED_PIN | BLUE_PIN
#define GREEN_LED LED_PORT,GREEN_PIN
#define ORANGE_LED LED_PORT,ORANGE_PIN
#define RED_LED LED_PORT,RED_PIN
#define BLUE_LED LED_PORT,BLUE_PIN
#define ALL_LEDS LED_PORT,ALL_LED_PINS

/* unfortunate globals because they get used in the ISR */
uint32_t TIM3COUNTER_Frequency = 2560;
/* determine the correct counter intervals */
uint16_t CCR1_Interval = TIM3COUNTER_Frequency / 8; // blue LED
uint16_t CCR2_Interval = TIM3COUNTER_Frequency / 4; // orange LED


void timer_interrupt_init (void)
{
NVIC_InitTypeDef NVIC_InitStructure;
RCC_ClocksTypeDef RCC_Clocks;
/* Enable the timer global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init (&NVIC_InitStructure);
}

uint32_t get_timer_clock_frequency (void)
{
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq (&RCC_Clocks);
uint32_t multiplier;
if (RCC_Clocks.PCLK1_Frequency == RCC_Clocks.SYSCLK_Frequency) {
multiplier = 1;
} else {
multiplier = 2;
}
return multiplier * RCC_Clocks.PCLK1_Frequency;
}

void timer_clock_init (void)
{
uint32_t TIM3CLK_Frequency = get_timer_clock_frequency();
uint16_t prescaler = (TIM3CLK_Frequency / TIM3COUNTER_Frequency) - 1;
/* allow the timer to wrap around naturally */
uint16_t reload = 65535;

/* make sure the peripheral is clocked */
RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* set everything back to default values */
TIM_TimeBaseStructInit (&TIM_TimeBaseStructure);
/* only changes from the defaults are needed */
TIM_TimeBaseStructure.TIM_Period = reload;
TIM_TimeBaseStructure.TIM_Prescaler = prescaler;
TIM_TimeBaseInit (TIM3, &TIM_TimeBaseStructure);
}

void timer_start (void)
{
TIM_Cmd (TIM3, ENABLE);
}

void timer_stop (void)
{
TIM_Cmd (TIM3, DISABLE);
}

void timer_interrupt_enable (void)
{
/*
* It is important to clear any pending interrupt flags since the timer
* has been free-running since we last used it and that may generate
* interrups on overflow even though the associated interrupt event has
* not been enabled.
*/
TIM_ClearITPendingBit (TIM3, TIM_IT_CC1 | TIM_IT_CC2);
/* put the counter into a known state */
//TIM_SetCounter (TIM3, 0);
/* enable the interrupt for CC1 and CC2 only */
TIM_ITConfig (TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
}

// for C++ ensure the interrupt handler is linked as a C function
#ifdef __cplusplus
extern "C" {
#endif

void TIM3_IRQHandler (void)
{
/* run through the interrupt sources looking for a hit */
if (TIM_GetITStatus (TIM3, TIM_IT_CC1) != RESET) {
GPIO_ToggleBits (BLUE_LED);

uint16_t CCR1_Current = TIM_GetCapture1 (TIM3);
TIM_SetCompare1 (TIM3, CCR1_Current + CCR1_Interval);

TIM_ClearITPendingBit (TIM3, TIM_IT_CC1);
}
if (TIM_GetITStatus (TIM3, TIM_IT_CC2) != RESET) {
GPIO_ToggleBits (ORANGE_LED);

uint16_t CCR2_Current = TIM_GetCapture2 (TIM3);
TIM_SetCompare2 (TIM3, CCR2_Current + CCR2_Interval);

TIM_ClearITPendingBit (TIM3, TIM_IT_CC2);
}
}

#ifdef __cplusplus
}
#endif

void timer_ccr_init (void)
{
TIM_OCInitTypeDef TIM_OCInitStructure;
/* always initialise local variables before use */
TIM_OCStructInit (&TIM_OCInitStructure);
/* just use basic Output Compare Mode*/
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Active;
/* set the initial match interval for CC1 */
TIM_OCInitStructure.TIM_Pulse = CCR1_Interval;
TIM_OC1Init (TIM3, &TIM_OCInitStructure);
/* and then for CC2 */
TIM_OCInitStructure.TIM_Pulse = CCR2_Interval;
TIM_OC2Init (TIM3, &TIM_OCInitStructure);
}


// these are the LEDs on the STM32F4Discovery
void board_leds_init (void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// always do this with an auto structure as it is undefined
GPIO_StructInit (&GPIO_InitStructure);
RCC_AHB1PeriphClockCmd (LED_PORT_CLOCK, ENABLE);
GPIO_StructInit (&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = RED_PIN + GREEN_PIN + BLUE_PIN + ORANGE_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init (LED_PORT, &GPIO_InitStructure);
GPIO_ResetBits (LED_PORT, RED_PIN + GREEN_PIN + BLUE_PIN + ORANGE_PIN);
}

void flash_green_led_forever (void)
{
while (1) {
GPIO_ToggleBits (GREEN_LED);
delay_ms (500);
}
}

int main (void)
{
systickInit (1000);
board_leds_init();
timer_clock_init();
timer_ccr_init();
timer_interrupt_init();
timer_interrupt_enable();
timer_start();
flash_green_led_forever();
return 0; // there is no going back but keep the compiler happy
}

Happy Coding
 
本文引用自:
http://www.micromouseonline.co ... mily/ 查看全部
好久没时间继续更新stm32cube的程序了.
先转一些歪果仁对stm32的分析文章
TIM3 is a general purpose timer found on all the STM32 family processors. Among other features, it has four capture compare channels that can be used to generate regular interrupts. In this article I will show you how to set up simple interrupt events based on these features.

This is part of a series of articles about the TIM3 general purpose timer. In the preceding parts I introduced the TIM3 timer features and showed you how to identify the timer clock and set up the prescaler and reload register.You can see all the STM32 TIM3 posts here. Code is written using the Standard Peripheral Library for the STM32F4Discovery board. Timer related portions should run directly on other STM32 family members since they all have a TIM3

In this part, I will explain the basic configuration of the capture compare registers, describe how they can be used to generate regular interrupts and give you sample code to demonstrate the facility.
 
Capture Compare Registers
Here is a modified view of the TIM3 timer taken from the reference manual RM0090.

TIM3-diagram-Fig119-RM0090-1187x1200.png

 
You can see that there are four registers associated with the timer TIM3. These are referred to as CCR1, CCR2, CCR3 and CCR4 or CCRx in general. Each channel is actually a pretty complex affair I am only going to concern myself with its behaviour in output compare modes. The reference manual RM0090 has this diagram of the output configuration:

STM32F4-TIM3-capture-compare-output.png

 A look at the reference manual reveals that in basic Output Compare mode, I can program the output mode controller to one of four different states on a match. It can be:
  • frozen – that is, nothing happensset active (forced high)set inactive (forced low)toggled
  •  

Other results are possible in other modes such as PWM.

In this article though, I just want to look at triggering an interrupt event. Although there are four Output Compare channels, and each can generate its own interrupt, they all get handled by the same Interrupt Service Routine (ISR). That means a series of tests will be needed in the ISR to determine exactly which channel caused the interrupt. I will just use two channels to illustrate how all this works.

Example Problem Statement

For this example, I want to set up two Output Compare channels on TIM3. Each channel will simply generate an interrupt. The ISR for that channel will toggle an LED on the target board. Each channel gets its own LED. Channel 1 will look after the blue LED and flash it  4 times per second. Channel 2 will look after the orange LED and flash it 2 times a second. These are simple numbers and the interrupt rate is quite low so that it is easy to see what is happening. Adapt the code for your needs.

Set up the Timer Prescale and Auto Reload

As with most timer problems, the first task is to set up the basic counter activity. This problem needs counts that are some handy but fairly small fraction of a second. Since the problem statement mentions numbers like 4 times per second and 8 times per second, it seems reasonable to try and arrange the counting frequency to be a convenient power of 2. Something like 256Hz or 128kHz of 3200Hz. Whatever, some number that lets me easily get binary fractions.

I have already established that the input frequency to the prescaler is 72MHz. This is an inconvenient value since there are no divisors that give me the kind of counter frequency that I was looking for. After a bit of fiddling with some of the factors of 72,000,000 I discovered that if I divide it by 28,125 I get 2560. Now that is a handy value for the counter since I can easily get many multiples of 2 and 5 out of it. Multiples of 3 would be more tricky but that is not part of my problem. The actual frequency for the counter is not critical in this case so long as it is high enough to give me the resolution I require.

Note that it is not sufficient to just pick a frequency. You must choose an value that divides exactly into the clock frequency if the results are to be accurate. the TIM3 counter frequency gets stored in a variable so that we can use it when calculating the intervals for the CCRx registers.
uint32_t TIM3COUNTER_Frequency =  2560;
Now I need to set the prescaler to (28125-1) = 28124 so that my counter will increment at 2560Hz. The prescaler value is calculated from the timer input clock frequency (TIM3CLK_Frequency) and the desired TIM3COUNTER_Frequency.

When using the Output Compare channels, I need the CNT register to wrap around completely for reasons that should be clear shortly. To do this, I need to load the ARR register with 65535.

The update event interrupt is not needed so basic timer configuration looks like this:
void timer_init (void)
{
uint32_t TIM3CLK_Frequency = get_timer_clock_frequency();
uint16_t prescaler = (TIM3CLK_Frequency / TIM3COUNTER_Frequency) - 1;
/* allow the timer to wrap around naturally */
uint16_t reload = 65535;

/* make sure the peripheral is clocked */
RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* set everything back to default values */
TIM_TimeBaseStructInit (&TIM_TimeBaseStructure);
/* only changes from the defaults are needed */
TIM_TimeBaseStructure.TIM_Period = reload;
TIM_TimeBaseStructure.TIM_Prescaler = prescaler;
TIM_TimeBaseInit (TIM3, &TIM_TimeBaseStructure);
}
Once the timer is started, it will free run at the specified frequency and then I have to think about how to configure the Output Compare registers.

Configure the Output Compare Function

Along with all the other configuration, I have to tell the timer to use the CCRx registers in output compare mode. The Standard Peripheral Library makes all this super easy:
void timer_ccr_init (void)
{
TIM_OCInitTypeDef TIM_OCInitStructure;
/* always initialise local variables before use */
TIM_OCStructInit (&TIM_OCInitStructure);
/* just use basic Output Compare Mode*/
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Active;
/* set the initial match interval for CC1 */
TIM_OCInitStructure.TIM_Pulse = CCR1_Interval;
TIM_OC1Init (TIM3, &TIM_OCInitStructure);
/* and then for CC2 */
TIM_OCInitStructure.TIM_Pulse = CCR2_Interval;
TIM_OC2Init (TIM3, &TIM_OCInitStructure);
}
Each channel is configured separately and I need only configure the channels I need. They are completely independent. The initial interval must be loaded into the register when using the library code but that value is only good for the first match and assumes that the CNT register starts at zero. When a match between CNT and the CCRx register is made, the CNT register carries on counting. That leaves me with the problem of how to update the CCRx registers after each match. The value used for the interval will be the number of CNT counts between interrupts. That gets calculated depending on the problem requirements.

Updating the Output Compare Registers

There is nothing in the Output Compare that will reset the CNT register. Even if there was, it would be no good to me because I want to use the CNT register contents to match against up to four CCRx registers. So, how can I get recurring intervals?

The CNT register, by default, will just count upwards at a fixed rate, wrapping around to zero again when it gets to the end. I just made sure of that in the timer setup. Each CCRx register is just compared with CNT for equality – it does not care what the actual value is. Every time the ISR runs then, I just need to add a fixed interval to the CCRx register. When the CNT value catches up with the CCRx again another interrupt will be generated. Overflows will not matter since both registers overflow in the same way and a match must eventually occur. Updating the CCR is now very simple. This code fragment deals with channel 1. The other channels are similar:

uint16_t CCR1_Current = TIM_GetCapture1 (TIM3);
TIM_SetCompare1 (TIM3, CCR1_Current + CCR1_Interval);
In the problem statement, I wanted the blue LED to flash 4 times a second using channel 1 and the orange LED to flash 2 times a second using channel 2. To get two flashes per second, I set the interval to a quarter second since, at each interrupt, I will toggle the LED pin. Similarly, I use 8 ticks per second for the blue LED
uint16_t CCR1_Interval = TIM3COUNTER_Frequency / 8; // blue LED
uint16_t CCR2_Interval = TIM3COUNTER_Frequency / 4; // orange LED
Variable Update Intervals

There is no reason why the same interval has to be used each time the interrupt fires. If I want to drive stepper motors, I can arrange to have a steadily diminishing interval as the motor accelerates. The values could be calculated or looked up in a table. Because I can have four channels and each is independent, I could easily drive four stepper motors from the same timer and have different motion profiles on each one.

Servicing the Output Compare Interrupt

Recall that several channels are all sharing the same ISR so in there I must poll each of the possible interrupt sources, act on any that are active and reset their flags ready for the next time. The entire ISR for my two channel problem looks like this.
void TIM3_IRQHandler (void)
{
/* run through the interrupt sources looking for a hit */
if (TIM_GetITStatus (TIM3, TIM_IT_CC1) != RESET) {
GPIO_ToggleBits (BLUE_LED);

uint16_t CCR1_Current = TIM_GetCapture1 (TIM3);
TIM_SetCompare1 (TIM3, CCR1_Current + CCR1_Interval);

TIM_ClearITPendingBit (TIM3, TIM_IT_CC1);
}
if (TIM_GetITStatus (TIM3, TIM_IT_CC2) != RESET) {
GPIO_ToggleBits (ORANGE_LED);

uint16_t CCR2_Current = TIM_GetCapture2 (TIM3);
TIM_SetCompare2 (TIM3, CCR2_Current + CCR2_Interval);

TIM_ClearITPendingBit (TIM3, TIM_IT_CC2);
}
}
The action associated with each channel here is just toggling an LED on and off. It could just as easily be setting the pattern of bits needed to drive the phases of a stepper motor. Just try to keep the ISR code as short and simple as you can. All that remains is to enable the interrupt and let it loose.

Enabling the Output Compare Interrupt

Although all the Output Compare channels share an ISR, each has its own interrupt enable flag so they can be turned on and off individually. Again, the Standard Peripheral Library makes this easy:
void timer_interrupt_enable (void)
{
/*
* It is important to clear any pending interrupt flags since the timer
* has been free-running since we last used it and that may generate
* interrupts on match even though the associated interrupt event has
* not been enabled.
*/
TIM_ClearITPendingBit (TIM3, TIM_IT_CC1 | TIM_IT_CC2);
/* put the counter into a known state */
TIM_SetCounter (TIM3, 0);
/* enable the interrupt for CC1 and CC2 only */
TIM_ITConfig (TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
}
Once the timer is started, my ISR should run and the LEDs will flash in the required way.

Putting it Together

Here is a complete code listing demonstrating how to do all these things with the STM32F4 Discovery
#include "stm32f4xx.h"
#include "systick.h"

#define MHz 1000000L
#define KHz 1000L


// The LED indicators on the STM32F4Discovery board
#define LED_PORT GPIOD
#define LED_PORT_CLOCK RCC_AHB1Periph_GPIOD
#define GREEN_PIN GPIO_Pin_12
#define ORANGE_PIN GPIO_Pin_13
#define RED_PIN GPIO_Pin_14
#define BLUE_PIN GPIO_Pin_15
#define ALL_LED_PINS GREEN_PIN | ORANGE_PIN | RED_PIN | BLUE_PIN
#define GREEN_LED LED_PORT,GREEN_PIN
#define ORANGE_LED LED_PORT,ORANGE_PIN
#define RED_LED LED_PORT,RED_PIN
#define BLUE_LED LED_PORT,BLUE_PIN
#define ALL_LEDS LED_PORT,ALL_LED_PINS

/* unfortunate globals because they get used in the ISR */
uint32_t TIM3COUNTER_Frequency = 2560;
/* determine the correct counter intervals */
uint16_t CCR1_Interval = TIM3COUNTER_Frequency / 8; // blue LED
uint16_t CCR2_Interval = TIM3COUNTER_Frequency / 4; // orange LED


void timer_interrupt_init (void)
{
NVIC_InitTypeDef NVIC_InitStructure;
RCC_ClocksTypeDef RCC_Clocks;
/* Enable the timer global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init (&NVIC_InitStructure);
}

uint32_t get_timer_clock_frequency (void)
{
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq (&RCC_Clocks);
uint32_t multiplier;
if (RCC_Clocks.PCLK1_Frequency == RCC_Clocks.SYSCLK_Frequency) {
multiplier = 1;
} else {
multiplier = 2;
}
return multiplier * RCC_Clocks.PCLK1_Frequency;
}

void timer_clock_init (void)
{
uint32_t TIM3CLK_Frequency = get_timer_clock_frequency();
uint16_t prescaler = (TIM3CLK_Frequency / TIM3COUNTER_Frequency) - 1;
/* allow the timer to wrap around naturally */
uint16_t reload = 65535;

/* make sure the peripheral is clocked */
RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* set everything back to default values */
TIM_TimeBaseStructInit (&TIM_TimeBaseStructure);
/* only changes from the defaults are needed */
TIM_TimeBaseStructure.TIM_Period = reload;
TIM_TimeBaseStructure.TIM_Prescaler = prescaler;
TIM_TimeBaseInit (TIM3, &TIM_TimeBaseStructure);
}

void timer_start (void)
{
TIM_Cmd (TIM3, ENABLE);
}

void timer_stop (void)
{
TIM_Cmd (TIM3, DISABLE);
}

void timer_interrupt_enable (void)
{
/*
* It is important to clear any pending interrupt flags since the timer
* has been free-running since we last used it and that may generate
* interrups on overflow even though the associated interrupt event has
* not been enabled.
*/
TIM_ClearITPendingBit (TIM3, TIM_IT_CC1 | TIM_IT_CC2);
/* put the counter into a known state */
//TIM_SetCounter (TIM3, 0);
/* enable the interrupt for CC1 and CC2 only */
TIM_ITConfig (TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
}

// for C++ ensure the interrupt handler is linked as a C function
#ifdef __cplusplus
extern "C" {
#endif

void TIM3_IRQHandler (void)
{
/* run through the interrupt sources looking for a hit */
if (TIM_GetITStatus (TIM3, TIM_IT_CC1) != RESET) {
GPIO_ToggleBits (BLUE_LED);

uint16_t CCR1_Current = TIM_GetCapture1 (TIM3);
TIM_SetCompare1 (TIM3, CCR1_Current + CCR1_Interval);

TIM_ClearITPendingBit (TIM3, TIM_IT_CC1);
}
if (TIM_GetITStatus (TIM3, TIM_IT_CC2) != RESET) {
GPIO_ToggleBits (ORANGE_LED);

uint16_t CCR2_Current = TIM_GetCapture2 (TIM3);
TIM_SetCompare2 (TIM3, CCR2_Current + CCR2_Interval);

TIM_ClearITPendingBit (TIM3, TIM_IT_CC2);
}
}

#ifdef __cplusplus
}
#endif

void timer_ccr_init (void)
{
TIM_OCInitTypeDef TIM_OCInitStructure;
/* always initialise local variables before use */
TIM_OCStructInit (&TIM_OCInitStructure);
/* just use basic Output Compare Mode*/
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Active;
/* set the initial match interval for CC1 */
TIM_OCInitStructure.TIM_Pulse = CCR1_Interval;
TIM_OC1Init (TIM3, &TIM_OCInitStructure);
/* and then for CC2 */
TIM_OCInitStructure.TIM_Pulse = CCR2_Interval;
TIM_OC2Init (TIM3, &TIM_OCInitStructure);
}


// these are the LEDs on the STM32F4Discovery
void board_leds_init (void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// always do this with an auto structure as it is undefined
GPIO_StructInit (&GPIO_InitStructure);
RCC_AHB1PeriphClockCmd (LED_PORT_CLOCK, ENABLE);
GPIO_StructInit (&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = RED_PIN + GREEN_PIN + BLUE_PIN + ORANGE_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init (LED_PORT, &GPIO_InitStructure);
GPIO_ResetBits (LED_PORT, RED_PIN + GREEN_PIN + BLUE_PIN + ORANGE_PIN);
}

void flash_green_led_forever (void)
{
while (1) {
GPIO_ToggleBits (GREEN_LED);
delay_ms (500);
}
}

int main (void)
{
systickInit (1000);
board_leds_init();
timer_clock_init();
timer_ccr_init();
timer_interrupt_init();
timer_interrupt_enable();
timer_start();
flash_green_led_forever();
return 0; // there is no going back but keep the compiler happy
}

Happy Coding
 
本文引用自:
http://www.micromouseonline.co ... mily/

(转)分享一篇关于ADC的国外文章,Simple ADC use on the STM32

admin 发表了文章 • 9 个评论 • 2595 次浏览 • 2016-03-04 13:05 • 来自相关话题

Here is a bit of a look at how to use the ADC on the STM32 for simple applications. The ADC peripheral on the STM32 processor is a flexible but complex beast. The peripheral driver library should make it relatively easy to use. After all, there is no need to remember the names of all those pesky registers and bitfields. There are anything up to 18 multiplexed channels that can be converted singly, continually, scanned or discontinuously. The results can be transferred to memory through DMA and some devices have two or more ADCs for simultaneous conversions. With all those options, simple reading of an ADC port is a little tricky…

The first thing to note is that, on the 64 pin packages, Vref is not connected to a package pin but is connected internally to Vdda the analogue supply voltage. If you have one of the larger, 100 pin devices, Vref is tied to a pin.

My test setup is the ETT STM32 Stamp which has an 8MHz external crystal and runs the PLL to give a system clock of 72MHz.

The clock for the ADC is provided by a prescaler fed from PCLK2, the APB2 clock. By default, in the peripheral library, this is the same speed as the system clock so, on my board, that is 72MHz. According to the datasheet, the ADC clock should be in the range 600kHz to 14MHz so I will need to set a prescaler of 6 to bring the ADC clock within that range from the 72MHz system clock. the function that sets this is part of the RCC peripheral code (RCC_ADCCLKConfig()) rather than the ADC driver. The available divisors are 2, 4, 6 and 8 so that should present no problems. Running the ADC overspeed is possible but I would expect accuracy to suffer. Since the fastest conversion takes 12.5+1.5 = 14 cycles, I would expect to get about 857,000 conversions per second on my board. The maximum rate within the given limits is 1 million samples per second.

The master ADC (ADC1) has two additional channels. These are connected to the internal temperature sensor (ADC1_IN16) and the internal reference voltage (ADC1_IN17). the temperature sensor is said to be reliable in terms of the slope of its response but with an offset than can vary my as much as 40 degrees from chip to chip. For that reason, it is said to be better for measuring temperature change than absolute temperature. The internal voltage reference is not used by the ADC but can be used as a comparator for zero crossing detection. It may also be possible to use it to calibrate external readings or the Vref+ pin.

Once configured, the ADC can be turned on by setting the ADON bit in CR2. After a stabilisation period, the peripheral is ready to begin conversions. From the datasheet for the medium density devices, the stabilisation period will not exceed 1us.

The ADC can self-calibrate to reduce errors due to internal capacitor variations. the reference manual suggests that the ADC be self calibrated once after power up. Before starting the self calibration, the ADC must have been in the power-down state (ADON=0) for at least two clock cycles. the end of calibration is indicated by the CAL flag going low.

The result of a conversion may be optionally aligned left or right in the 16 bit result register. Only 12 bits of the result are significant. In regular mode, the other bits are filled with zeros.

Here is the configuration code I have used:
void ADC_Configuration(void)
{
ADC_InitTypeDef ADC_InitStructure;
/* PCLK2 is the APB2 clock */
/* ADCCLK = PCLK2/6 = 72/6 = 12MHz*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6);

/* Enable ADC1 clock so that we can talk to it */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
/* Put everything back to power-on defaults */
ADC_DeInit(ADC1);

/* ADC1 Configuration ------------------------------------------------------*/
/* ADC1 and ADC2 operate independently */
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
/* Disable the scan conversion so we do one at a time */
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
/* Don't do contimuous conversions - do them on demand */
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
/* Start conversin by software, not an external trigger */
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
/* Conversions are 12 bit - put them in the lower 12 bits of the result */
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
/* Say how many channels would be used by the sequencer */
ADC_InitStructure.ADC_NbrOfChannel = 1;

/* Now do the setup */
ADC_Init(ADC1, &ADC_InitStructure);
/* 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));
}Single conversion mode is started in software by writing a 1 to the ADON bit in ADC_CR2 with the CONT bit set to zero. Note that the ADON bit will already be set to one when the ADC is powered up. Reading the ADON bit serves to tell the program that the ADC is powered up. To prevent false triggering, a conversion will not start if any bit other than ADON is changed at the same time as ADON.

Conversions take 12.5 clock cycles but the time the ADC spend sampling the input can be varied on a channel-by-channel basis from 1.5 to 239.5 cycles. This value is set in the ADC_SMPR1 and ADC_SMPR2 registers.

The end of conversion is signalled by the setting of the EOC flag, an interrupt is optionally generated and the result is placed in the ADC_DR register. The EOC flag should be reset by software before another conversion is started. Reading the result from ADC_DR clears the EOC flag automatically.

Here is the function that reads a single ADC channel:
u16 readADC1(u8 channel)
{
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_1Cycles5);
// Start the conversion
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// Wait until conversion completion
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
// Get the conversion value
return ADC_GetConversionValue(ADC1);
}This function includes a call to configure the channel and sets the sample time to its minimum. It takes 3.5us to run on my board. That could be substantially reduced by using the more sophisticated conversion modes available and by using better code. The Channel config call alone takes 1.3us. Where high speed is essential, there is DMA and automatic sequencing available. However, if all you want to do is read a few ADC channels, the speed is pretty good. All 16 channels could be read in under 60us. Note that the sampling time may need adjusting according to the nature of the signal source. A good. low, impedance source will need a shorter sample time.

When I need the more clever methods of reading the ADC, I will look into them. In the meantime, this will do me very nicely.
 
本文来源:http://www.micromouseonline.com/2009/05/26/simple-adc-use-on-the-stm32/
感谢作者分享! 查看全部
Here is a bit of a look at how to use the ADC on the STM32 for simple applications. The ADC peripheral on the STM32 processor is a flexible but complex beast. The peripheral driver library should make it relatively easy to use. After all, there is no need to remember the names of all those pesky registers and bitfields. There are anything up to 18 multiplexed channels that can be converted singly, continually, scanned or discontinuously. The results can be transferred to memory through DMA and some devices have two or more ADCs for simultaneous conversions. With all those options, simple reading of an ADC port is a little tricky…

The first thing to note is that, on the 64 pin packages, Vref is not connected to a package pin but is connected internally to Vdda the analogue supply voltage. If you have one of the larger, 100 pin devices, Vref is tied to a pin.

My test setup is the ETT STM32 Stamp which has an 8MHz external crystal and runs the PLL to give a system clock of 72MHz.

The clock for the ADC is provided by a prescaler fed from PCLK2, the APB2 clock. By default, in the peripheral library, this is the same speed as the system clock so, on my board, that is 72MHz. According to the datasheet, the ADC clock should be in the range 600kHz to 14MHz so I will need to set a prescaler of 6 to bring the ADC clock within that range from the 72MHz system clock. the function that sets this is part of the RCC peripheral code (RCC_ADCCLKConfig()) rather than the ADC driver. The available divisors are 2, 4, 6 and 8 so that should present no problems. Running the ADC overspeed is possible but I would expect accuracy to suffer. Since the fastest conversion takes 12.5+1.5 = 14 cycles, I would expect to get about 857,000 conversions per second on my board. The maximum rate within the given limits is 1 million samples per second.

The master ADC (ADC1) has two additional channels. These are connected to the internal temperature sensor (ADC1_IN16) and the internal reference voltage (ADC1_IN17). the temperature sensor is said to be reliable in terms of the slope of its response but with an offset than can vary my as much as 40 degrees from chip to chip. For that reason, it is said to be better for measuring temperature change than absolute temperature. The internal voltage reference is not used by the ADC but can be used as a comparator for zero crossing detection. It may also be possible to use it to calibrate external readings or the Vref+ pin.

Once configured, the ADC can be turned on by setting the ADON bit in CR2. After a stabilisation period, the peripheral is ready to begin conversions. From the datasheet for the medium density devices, the stabilisation period will not exceed 1us.

The ADC can self-calibrate to reduce errors due to internal capacitor variations. the reference manual suggests that the ADC be self calibrated once after power up. Before starting the self calibration, the ADC must have been in the power-down state (ADON=0) for at least two clock cycles. the end of calibration is indicated by the CAL flag going low.

The result of a conversion may be optionally aligned left or right in the 16 bit result register. Only 12 bits of the result are significant. In regular mode, the other bits are filled with zeros.

Here is the configuration code I have used:
void ADC_Configuration(void)
{
ADC_InitTypeDef ADC_InitStructure;
/* PCLK2 is the APB2 clock */
/* ADCCLK = PCLK2/6 = 72/6 = 12MHz*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6);

/* Enable ADC1 clock so that we can talk to it */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
/* Put everything back to power-on defaults */
ADC_DeInit(ADC1);

/* ADC1 Configuration ------------------------------------------------------*/
/* ADC1 and ADC2 operate independently */
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
/* Disable the scan conversion so we do one at a time */
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
/* Don't do contimuous conversions - do them on demand */
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
/* Start conversin by software, not an external trigger */
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
/* Conversions are 12 bit - put them in the lower 12 bits of the result */
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
/* Say how many channels would be used by the sequencer */
ADC_InitStructure.ADC_NbrOfChannel = 1;

/* Now do the setup */
ADC_Init(ADC1, &ADC_InitStructure);
/* 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));
}
Single conversion mode is started in software by writing a 1 to the ADON bit in ADC_CR2 with the CONT bit set to zero. Note that the ADON bit will already be set to one when the ADC is powered up. Reading the ADON bit serves to tell the program that the ADC is powered up. To prevent false triggering, a conversion will not start if any bit other than ADON is changed at the same time as ADON.

Conversions take 12.5 clock cycles but the time the ADC spend sampling the input can be varied on a channel-by-channel basis from 1.5 to 239.5 cycles. This value is set in the ADC_SMPR1 and ADC_SMPR2 registers.

The end of conversion is signalled by the setting of the EOC flag, an interrupt is optionally generated and the result is placed in the ADC_DR register. The EOC flag should be reset by software before another conversion is started. Reading the result from ADC_DR clears the EOC flag automatically.

Here is the function that reads a single ADC channel:
u16 readADC1(u8 channel)
{
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_1Cycles5);
// Start the conversion
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// Wait until conversion completion
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
// Get the conversion value
return ADC_GetConversionValue(ADC1);
}
This function includes a call to configure the channel and sets the sample time to its minimum. It takes 3.5us to run on my board. That could be substantially reduced by using the more sophisticated conversion modes available and by using better code. The Channel config call alone takes 1.3us. Where high speed is essential, there is DMA and automatic sequencing available. However, if all you want to do is read a few ADC channels, the speed is pretty good. All 16 channels could be read in under 60us. Note that the sampling time may need adjusting according to the nature of the signal source. A good. low, impedance source will need a shorter sample time.

When I need the more clever methods of reading the ADC, I will look into them. In the meantime, this will do me very nicely.
 
本文来源:http://www.micromouseonline.com/2009/05/26/simple-adc-use-on-the-stm32/
感谢作者分享!

(转)涨姿势,关于类似*(uint32_t*)&GPIOx这样形式的讨论

admin 发表了文章 • 2 个评论 • 5620 次浏览 • 2016-02-20 10:00 • 来自相关话题

看了看下面大神的讨论,有些茅厕顿开的感觉,特此搬砖过来
void GPIO_DeInit(GPIO_TypeDef* GPIOx)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));

switch (*(uint32_t*)&GPIOx)
{
case GPIOA_BASE:
RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA, DISABLE);
break;.....
//不明白switch行中GPIOx为什么要取址,GPIOx本来不就是地址么?
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)
//求解释首先,从 GPIOx 说起

这是ST库里的经典映射手法。
具体就是

和PC上的程序不一样,在STM32上,可以通过
*(int *)0x08001000 = 34;在地址为 0x80010000上写入 34 这个内容。
——这里不考虑什么 FLASH RAM之类的问题。

所以,ST库映射寄存器的典型手法就是

比如说 GPIOA的寄存器地址,如果是从 0x20001000开始存放什么 ODR IDR之类的。
因为 结构体成员在内存上也是按序排放的,所以,它就把

ODR IDR等等 寄存器 按顺序 定义成 GPIO 这个结构体。

形如 
typedef GPIO
{
odr;
idr;
};这一部分具体可以去看 stm32fxxx.h,我就不多说了。

最后,GPIOA GPIOB GPIOC都会有一个 GPIOA_Base GPIOB_Base
这个基地址,指的就是 每个port端口 寄存器的 起始地址。ABCDEFGI口各自按顺序排好。
所以只要找到头,再借助这个结构体,就可以直接通过

GPIOA->ODR这样的写法非常简单直观的 寻知道 GPIOA的ODR地址。

非常形象,非常生动。
而且很简洁,完全的利用了C语法本身的特性。

是以,从我个人的角度看,这是一个非常不错的 映射手法。

这基本也是那天晚上我语无伦次 发语音说的重点。
 
现在,先来回答原来那个帖子的问题。
*(uint32_t*)&GPIOx这句话到底是什么意思?

其实问题还是在上一个帖子里提到的 GPIOx的定义里
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)其实我为什么第一反应会觉得这个东西写的挺新鲜,因为以前我曾小小纠结过如何通过一个函数,让函数自己区分 GPIOA GPIOB这个问题。
而这里提供了一个 非常直接简单的方法:
*(uint32_t*)&GPIOx对这种较复杂的表达式或者宏,解决的思路很简单,就是一步一步展开。但这个过于简单,而且这个话题也太口水了,我就直接带过去不罗嗦了,罗嗦了你们还以为是我无知大惊小怪......(多怨啊我,我只是一个喜欢 详细解释的好版主)

GPIOx 是传递进来的形参,它的可能值就是 GPIOA GPIOB之类的
那也就是
((GPIO_TypeDef*)GPIOA_BASE)GPIOA_BASE是一个数值,代表的是 GPIOA的寄存器的起始地址
*(uint32_t*)&GPIOx这个操作,等于,把 GPIOA_BASE 这个最初宏定义的数值,就是说,这是一个常数。

所以这个时候,就可以很方便的使用 switch-case结构了
因为case后面跟的只能是常数,而不能是变量或者其他任何数值。

这就是这个问题的所有答案
 
水果君在那个帖子里认为,其实这个地方是多此一举,可以直接写成
(uint32)GPIOx
说实在的,这个,和 那个显得很麻烦的写法
*(uint32_t*)&GPIOx效果确实是一样的。

只是,我强调 后面这种写法,我的理由在于:

可读性。

看到前者,你不会联想到 GPIOx是一个地址,而看到后者,稍微有点经验的C程序员都马上会领悟到这一点。
这就很重要。
为什么,因为,在类似的环境下,我就会搞懵。
比如说,最开始主楼贴 的那个图。

那是 人民币君发的。

我一开始因为直接联想到 这个放假前的讨论,因此我想都没想,就直接说,这两个效果是一样的。

然而,果真是一样吗?呵呵,那还真不是。
比如说





 
{
*(int *)0x80001000 = 34;
(int)0x80001000
while(1);
}写到这里,我就懵逼了,看出问题了没?
一个是那个数其实是地址值,要去操作那个地址上的内容
另一个,压根就只是一个常数。

这两个操作的出来的结果和影响完全不一样。

虽然这可以解释为看走眼,糊涂,当然你也可以认为我经常犯懵,但是我个人的经验是——
不要盲目自信你不会犯懵。

因为一旦犯懵的时候,你可能要付出两三天或者两三个加班的夜晚去解决问题。
试问值得不值得?

而这也是我和水果君争论的核心所在:
虽然我的写法复杂一点,但是,我觉得我可以很明确的告诉别人这就是一个地址。

正如我发帖子或者在q群里讨论,总是不厌其烦,说的大白菜一样。
而不是假设你和我一样知道一些东西。

这样虽然我可以省事很多,少说很多话,打少很多字,但是却极有可能产生沟通的误解,因为双方很可能
不是建立在相同的已知上。

这也是我为什么非常厌恶和吐槽一些半导体厂商提供的文档的理由。
因为他们说的根本不是人话,不是站在开发者使用者的角度去说事。

似乎他们很希望和你分享他们设计芯片和外设原理一样,却不知道这样把我们搞的是一塌糊涂。
我们只不过想学会如何配置和使用,它们却罗罗嗦嗦说另一个方面的问题。

好了,就此结束。
 
-----------------------------------------------------------------------
我来对号入座了,我就是文中提到的水果君。
首先来讨论一个我认为很有意思的问题,就是这两个强制类型转换:
*(uint32_t*)&AAA 是否等价于 (uint32_t)AAA ?(假设我们不知道AAA的类型,变量还是常量)
为了便于讨论,我们假设变量uint32_t X=*(uint32_t*)&AAA, Y=(uint32_t)AAA;
乍一看,好像是有点等价的意思,但是仔细想想,又不是那么回事,这还取决于AAA的类型。
(1).现在假设AAA是2字节short型=0x1234;
那么X的结果是强制从AAA的地址中取走4字节,其中2字节未知:
X=0xXXXX1234 (小端情况下)
Y的结果就比较确定,编译器帮他把高位填0, Y=0x00001234;
(2).假设AAA是64位long long类型=0x1234;前面的0我就不写了。
那么X还是取走4个字节,根据大小端而异,可能是高4字节,也可能是低四字节。
Y只是简单的舍弃了高四字节,结果比较确定。
(3). 假设AAA是个数组,这个情况比较特殊,数组名本身就是数组的首地址,取地址后还是相同的值:
因此X会取出数组中的元素
而Y却还是一个地址值。
(4). 一个不靠谱的假设AAA是个结构体
那么X可以取到结构体的成员
而Y的写法就直接报错了,不允许强制类型转换。
(5). AAA是uint32_t类型,那么没什么问题,X与Y等价。

由此可见:
X写法确实是无条件强制转换,基本是无所不能转,但是如果类型不匹配就很容出错了。
Y写法是有限制的编译器参与的半智能转换,因为编译器知道源类型与目标类型,会帮忙参与转换,或者类型转换不太靠谱的话,直接报错。

由此可以得出结论,在使用强制类型转换的时候,必须具有可行性,同时也必须清楚转换后的结果,也就是说,程序员(写程序的人)必须清清楚楚地知道源类型和目标类型到底是什么,否则还是去读书深造的好。

现在我来说说为什么我觉得uint32_t X=*(uint32_t*)&GPIOx有脱裤子放屁的嫌疑(从阅读程序的角度来说)。
首先有个变量GPIOx,是个指针,也就是个地址1,此地址上面存放的类型是GPIO_TypeDef。
然后对这个地址1取了个地址2,那么这个地址2的类型是GPIO_TypeDef **
现在对地址2进行强制类型转换,转成uint32_t*,也就是间接说明,不再把地址1当做地址(指针)看待,而是作为一个uint32_t类型。
然后在地址2中的uint32_t数据取出来,完毕。牛逼的程序员也看出来了,这就是拐外抹角的把GPIOx转成uint32_t类型。
一般刚入门的程序员看了会不会很懵逼?

好,说的有点乱,我们按教主的思路重新捋一下:
我们假设不知道GPIOx到底是个什么东西,就当做是一个不知道类型的普通变量。
引用:“——————————————————————————————————
水果君在那个帖子里认为,其实这个地方是多此一举,可以直接写成
(uint32)GPIOx
说实在的,这个,和 那个显得很麻烦的写法
*(uint32_t*)&GPIOx
效果确实是一样的。
只是,我强调 后面这种写法,我的理由在于:
可读性。
看到前者,你不会联想到 GPIOx是一个地址,而看到后者,稍微有点经验的C程序员都马上会领悟到这一点。
————————————————————————————————引用结束”
看看我对脱裤子的理解:
首先一个变量GPIOx,不知道其类型
然后对此变量取了个地址,此地址类型也未知。
然后对这个地址进行强制转换成uint32_t类型的一个地址(此处影射GPIOx是个uint32_t类型)
最后,从这个地址中取出了一个uint32_t类型的变量,完成了最终这个语句的使命。
这样理解,也根本看不出GPIOx有地址的意思(只是明确了变量的地址是个具体类型的地址)。只是拐了个弯,把GPIOx强制转成uint32_t类型而已。

然而,把一个地址(指针)转成一个整形数,就很常见不过了。uint32_t Y=(uint32_t)GPIOx。

由此,可以看到两个转换的最终区别:脱裤子那种,是强制转换的变量地址的类型,间接对数据类型进行转换,而直接转换就是直接对变量进行转换。
-------------------------------------------------------------
看到你回这么长的贴,我也是挺感动的,然而很不好意思的告诉你。
虽然你没错,但我还是要比你更正确......

很简单,看好了。

在32位机器下,你是对的,你还是更简洁的。
然而如果这种写法,在16位机或者64位机 等非32位机下就会出错。
why?
很简单,,非32位机的 地址非4字节,而是 16位机的2字节,64位机的8字节。
这种情况下,对应的指针字长也就成了 2字节 8字节。

于是结果已经很明显。
你每次都uint32去强转地址,问题是,你强转的是一个地址.......那也就是说。
对16位机,你多转了后面未知的2字节,对64位机,你少转了后面需要的4字节。
所以,必然是错的。

而原来那种看起来复杂的写法呢?
木有错,为毛?
因为,,uint32_t * 也是一个指针,或者地址(指针或地址随你叫吧)
因为在同一机器下,任何类型指针的字长都是一样的。
所以这种情况下,我读到的地址值永远不会少或者多。

这个问题意味着。
在你的写法里,你需要去假设指针字长,比如uint32,但这永远只能对一种机器字长适应。
而那个复杂的写法,则无此需求,不需要作任何假设,也就不会受限于任何机器字长的限制。

事实上,我写成

*(uint8_t *)
*(uint16_t *)
.....
都木有任何关系
-----------------------------------------------
(uint32) GPIOx  还是  *(uint32_t*)&GPIOx 
如果GPIOx是形参,无论哪种写法,得出的汇编都一样,都是直接取R0,

如果GPIOx 是全局变量,汇编结果也是一样,都是取GPIOx变量的内容
如果GPIOx变量存放到0x10001000,直接取0x10001000里面的内容

编译器非常聪明,认为对指针变量X取地址A再取A里面的内容和直接取X里面的内容是一样的!
----------------------------------
我也觉得代码这东西没有绝对的对与错,虽然那种写法看起来很复杂并且高大上,一个语句展现了好几个C语言的知识点,而且结果正确,一点毛病没有,只是让人读起来需要有个停顿思考的时间。

C语言在程序移植这里确实存在许多诟病,在不同硬件平台上,数据类型的长度并不统一。
例如通常int在16位机为2字节,32位机为4字节,指针也一样,根据机型有2字节,4字节或者8字节的长度,记得还有3字节的……因为硬件平台种类实在是太多了,五花八门。
为了应付这类问题,C99标准出台了更具体的类型,如int16_t,uint32_t这样的具体长度类型,使程序在不同硬件平台更容易移植。但是……。
遗憾的是,这些类型中没有指针,我觉得因为指针也没法具体化。因此,教主提出了他的问题:在不同平台上如何用整型来表示指针?
其实这问题的根源完全来自于switch语句,因为在switch中只能使用整型数据类型,不可以是指针,如果非要去比较指针,那么只能转成整型。因此,如果把指针转为整型成了讨论的重点,但是很明显,教主的结论是错误的,他的写法并不能实现他要的结果。
大家都应该知道,指针是有类型的,解引用的时候会得到相应的类型:
*(uint32_t*)xxx  结果将是uint32_t
*(int16_t*)xxx 结果就是int16_t
根本得不到他所想要的与平台相关的数据类型。
因此在C语言中,想要得到指针所对应的整型类型,只能通过手动指定,例如微软就是这么干的
#ifdef _WIN64
    typedef __int64          intptr_t;
#else
    typedef int              intptr_t;
#endif
这样intptr_t就可以确保能保存指针类型。
但是可惜这只是某些厂家这么干,ST的库中并没有这样的类型,否则这事就好办了(当然了,ST目前可能也没考虑推出64位的单片机)
我不知道*(uint32_t*)&GPIOx这样的代码是否是出自ST的标准库,我没有去考证,如果真这样写的话他们自己可能也看着别扭,所以我看到st的某个版本库里面看到的是直接比较指针,当然了,就不能使用switch语句了,而是if语句,像这样:
if (GPIOx == GPIOA) xxx_statement 。反正我觉得这样写是最直观最易读的了,给他们点个赞!

c语言的争议太多了,就像#define与typedef之争,#define与const之争,程序员的理论就是运行结果没错那就都不是大问题,不说了,累,继续搬砖了。 查看全部
看了看下面大神的讨论,有些茅厕顿开的感觉,特此搬砖过来

void GPIO_DeInit(GPIO_TypeDef* GPIOx)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));

switch (*(uint32_t*)&GPIOx)
{
case GPIOA_BASE:
RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA, DISABLE);
break;.....
//不明白switch行中GPIOx为什么要取址,GPIOx本来不就是地址么?
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)
//求解释
首先,从 GPIOx 说起

这是ST库里的经典映射手法。
具体就是

和PC上的程序不一样,在STM32上,可以通过
*(int *)0x08001000 = 34;
在地址为 0x80010000上写入 34 这个内容。
——这里不考虑什么 FLASH RAM之类的问题。

所以,ST库映射寄存器的典型手法就是

比如说 GPIOA的寄存器地址,如果是从 0x20001000开始存放什么 ODR IDR之类的。
因为 结构体成员在内存上也是按序排放的,所以,它就把

ODR IDR等等 寄存器 按顺序 定义成 GPIO 这个结构体。

形如 
typedef GPIO
{
odr;
idr;
};
这一部分具体可以去看 stm32fxxx.h,我就不多说了。

最后,GPIOA GPIOB GPIOC都会有一个 GPIOA_Base GPIOB_Base
这个基地址,指的就是 每个port端口 寄存器的 起始地址。ABCDEFGI口各自按顺序排好。
所以只要找到头,再借助这个结构体,就可以直接通过

GPIOA->ODR这样的写法非常简单直观的 寻知道 GPIOA的ODR地址。

非常形象,非常生动。
而且很简洁,完全的利用了C语法本身的特性。

是以,从我个人的角度看,这是一个非常不错的 映射手法。

这基本也是那天晚上我语无伦次 发语音说的重点。
 
现在,先来回答原来那个帖子的问题。
*(uint32_t*)&GPIOx
这句话到底是什么意思?

其实问题还是在上一个帖子里提到的 GPIOx的定义里
#define GPIOA    ((GPIO_TypeDef*)GPIOA_BASE)
其实我为什么第一反应会觉得这个东西写的挺新鲜,因为以前我曾小小纠结过如何通过一个函数,让函数自己区分 GPIOA GPIOB这个问题。
而这里提供了一个 非常直接简单的方法:
*(uint32_t*)&GPIOx
对这种较复杂的表达式或者宏,解决的思路很简单,就是一步一步展开。但这个过于简单,而且这个话题也太口水了,我就直接带过去不罗嗦了,罗嗦了你们还以为是我无知大惊小怪......(多怨啊我,我只是一个喜欢 详细解释的好版主)

GPIOx 是传递进来的形参,它的可能值就是 GPIOA GPIOB之类的
那也就是
((GPIO_TypeDef*)GPIOA_BASE)
GPIOA_BASE是一个数值,代表的是 GPIOA的寄存器的起始地址
*(uint32_t*)&GPIOx
这个操作,等于,把 GPIOA_BASE 这个最初宏定义的数值,就是说,这是一个常数。

所以这个时候,就可以很方便的使用 switch-case结构了
因为case后面跟的只能是常数,而不能是变量或者其他任何数值。

这就是这个问题的所有答案
 
水果君在那个帖子里认为,其实这个地方是多此一举,可以直接写成
(uint32)GPIOx
说实在的,这个,和 那个显得很麻烦的写法
*(uint32_t*)&GPIOx
效果确实是一样的。

只是,我强调 后面这种写法,我的理由在于:

可读性。

看到前者,你不会联想到 GPIOx是一个地址,而看到后者,稍微有点经验的C程序员都马上会领悟到这一点。
这就很重要。
为什么,因为,在类似的环境下,我就会搞懵。
比如说,最开始主楼贴 的那个图。

那是 人民币君发的。

我一开始因为直接联想到 这个放假前的讨论,因此我想都没想,就直接说,这两个效果是一样的。

然而,果真是一样吗?呵呵,那还真不是。
比如说

165010.jpg

 
{
*(int *)0x80001000 = 34;
(int)0x80001000
while(1);
}
写到这里,我就懵逼了,看出问题了没?
一个是那个数其实是地址值,要去操作那个地址上的内容
另一个,压根就只是一个常数。

这两个操作的出来的结果和影响完全不一样。

虽然这可以解释为看走眼,糊涂,当然你也可以认为我经常犯懵,但是我个人的经验是——
不要盲目自信你不会犯懵。

因为一旦犯懵的时候,你可能要付出两三天或者两三个加班的夜晚去解决问题。
试问值得不值得?

而这也是我和水果君争论的核心所在:
虽然我的写法复杂一点,但是,我觉得我可以很明确的告诉别人这就是一个地址。

正如我发帖子或者在q群里讨论,总是不厌其烦,说的大白菜一样。
而不是假设你和我一样知道一些东西。

这样虽然我可以省事很多,少说很多话,打少很多字,但是却极有可能产生沟通的误解,因为双方很可能
不是建立在相同的已知上。

这也是我为什么非常厌恶和吐槽一些半导体厂商提供的文档的理由。
因为他们说的根本不是人话,不是站在开发者使用者的角度去说事。

似乎他们很希望和你分享他们设计芯片和外设原理一样,却不知道这样把我们搞的是一塌糊涂。
我们只不过想学会如何配置和使用,它们却罗罗嗦嗦说另一个方面的问题。

好了,就此结束。
 
-----------------------------------------------------------------------
我来对号入座了,我就是文中提到的水果君。
首先来讨论一个我认为很有意思的问题,就是这两个强制类型转换:
*(uint32_t*)&AAA 是否等价于 (uint32_t)AAA ?(假设我们不知道AAA的类型,变量还是常量)
为了便于讨论,我们假设变量uint32_t X=*(uint32_t*)&AAA, Y=(uint32_t)AAA;
乍一看,好像是有点等价的意思,但是仔细想想,又不是那么回事,这还取决于AAA的类型。
(1).现在假设AAA是2字节short型=0x1234;
那么X的结果是强制从AAA的地址中取走4字节,其中2字节未知:
X=0xXXXX1234 (小端情况下)
Y的结果就比较确定,编译器帮他把高位填0, Y=0x00001234;
(2).假设AAA是64位long long类型=0x1234;前面的0我就不写了。
那么X还是取走4个字节,根据大小端而异,可能是高4字节,也可能是低四字节。
Y只是简单的舍弃了高四字节,结果比较确定。
(3). 假设AAA是个数组,这个情况比较特殊,数组名本身就是数组的首地址,取地址后还是相同的值:
因此X会取出数组中的元素
而Y却还是一个地址值。
(4). 一个不靠谱的假设AAA是个结构体
那么X可以取到结构体的成员
而Y的写法就直接报错了,不允许强制类型转换。
(5). AAA是uint32_t类型,那么没什么问题,X与Y等价。

由此可见:
X写法确实是无条件强制转换,基本是无所不能转,但是如果类型不匹配就很容出错了。
Y写法是有限制的编译器参与的半智能转换,因为编译器知道源类型与目标类型,会帮忙参与转换,或者类型转换不太靠谱的话,直接报错。

由此可以得出结论,在使用强制类型转换的时候,必须具有可行性,同时也必须清楚转换后的结果,也就是说,程序员(写程序的人)必须清清楚楚地知道源类型和目标类型到底是什么,否则还是去读书深造的好。

现在我来说说为什么我觉得uint32_t X=*(uint32_t*)&GPIOx有脱裤子放屁的嫌疑(从阅读程序的角度来说)。
首先有个变量GPIOx,是个指针,也就是个地址1,此地址上面存放的类型是GPIO_TypeDef。
然后对这个地址1取了个地址2,那么这个地址2的类型是GPIO_TypeDef **
现在对地址2进行强制类型转换,转成uint32_t*,也就是间接说明,不再把地址1当做地址(指针)看待,而是作为一个uint32_t类型。
然后在地址2中的uint32_t数据取出来,完毕。牛逼的程序员也看出来了,这就是拐外抹角的把GPIOx转成uint32_t类型。
一般刚入门的程序员看了会不会很懵逼?

好,说的有点乱,我们按教主的思路重新捋一下:
我们假设不知道GPIOx到底是个什么东西,就当做是一个不知道类型的普通变量。
引用:“——————————————————————————————————
水果君在那个帖子里认为,其实这个地方是多此一举,可以直接写成
(uint32)GPIOx
说实在的,这个,和 那个显得很麻烦的写法
*(uint32_t*)&GPIOx
效果确实是一样的。
只是,我强调 后面这种写法,我的理由在于:
可读性。
看到前者,你不会联想到 GPIOx是一个地址,而看到后者,稍微有点经验的C程序员都马上会领悟到这一点。
————————————————————————————————引用结束”
看看我对脱裤子的理解:
首先一个变量GPIOx,不知道其类型
然后对此变量取了个地址,此地址类型也未知。
然后对这个地址进行强制转换成uint32_t类型的一个地址(此处影射GPIOx是个uint32_t类型)
最后,从这个地址中取出了一个uint32_t类型的变量,完成了最终这个语句的使命。
这样理解,也根本看不出GPIOx有地址的意思(只是明确了变量的地址是个具体类型的地址)。只是拐了个弯,把GPIOx强制转成uint32_t类型而已。

然而,把一个地址(指针)转成一个整形数,就很常见不过了。uint32_t Y=(uint32_t)GPIOx。

由此,可以看到两个转换的最终区别:脱裤子那种,是强制转换的变量地址的类型,间接对数据类型进行转换,而直接转换就是直接对变量进行转换。
-------------------------------------------------------------
看到你回这么长的贴,我也是挺感动的,然而很不好意思的告诉你。
虽然你没错,但我还是要比你更正确......

很简单,看好了。

在32位机器下,你是对的,你还是更简洁的。
然而如果这种写法,在16位机或者64位机 等非32位机下就会出错。
why?
很简单,,非32位机的 地址非4字节,而是 16位机的2字节,64位机的8字节。
这种情况下,对应的指针字长也就成了 2字节 8字节。

于是结果已经很明显。
你每次都uint32去强转地址,问题是,你强转的是一个地址.......那也就是说。
对16位机,你多转了后面未知的2字节,对64位机,你少转了后面需要的4字节。
所以,必然是错的。

而原来那种看起来复杂的写法呢?
木有错,为毛?
因为,,uint32_t * 也是一个指针,或者地址(指针或地址随你叫吧)
因为在同一机器下,任何类型指针的字长都是一样的。
所以这种情况下,我读到的地址值永远不会少或者多。

这个问题意味着。
在你的写法里,你需要去假设指针字长,比如uint32,但这永远只能对一种机器字长适应。
而那个复杂的写法,则无此需求,不需要作任何假设,也就不会受限于任何机器字长的限制。

事实上,我写成

*(uint8_t *)
*(uint16_t *)
.....
都木有任何关系
-----------------------------------------------
(uint32) GPIOx  还是  *(uint32_t*)&GPIOx 
如果GPIOx是形参,无论哪种写法,得出的汇编都一样,都是直接取R0,

如果GPIOx 是全局变量,汇编结果也是一样,都是取GPIOx变量的内容
如果GPIOx变量存放到0x10001000,直接取0x10001000里面的内容

编译器非常聪明,认为对指针变量X取地址A再取A里面的内容和直接取X里面的内容是一样的!
----------------------------------
我也觉得代码这东西没有绝对的对与错,虽然那种写法看起来很复杂并且高大上,一个语句展现了好几个C语言的知识点,而且结果正确,一点毛病没有,只是让人读起来需要有个停顿思考的时间。

C语言在程序移植这里确实存在许多诟病,在不同硬件平台上,数据类型的长度并不统一。
例如通常int在16位机为2字节,32位机为4字节,指针也一样,根据机型有2字节,4字节或者8字节的长度,记得还有3字节的……因为硬件平台种类实在是太多了,五花八门。
为了应付这类问题,C99标准出台了更具体的类型,如int16_t,uint32_t这样的具体长度类型,使程序在不同硬件平台更容易移植。但是……。
遗憾的是,这些类型中没有指针,我觉得因为指针也没法具体化。因此,教主提出了他的问题:在不同平台上如何用整型来表示指针?
其实这问题的根源完全来自于switch语句,因为在switch中只能使用整型数据类型,不可以是指针,如果非要去比较指针,那么只能转成整型。因此,如果把指针转为整型成了讨论的重点,但是很明显,教主的结论是错误的,他的写法并不能实现他要的结果。
大家都应该知道,指针是有类型的,解引用的时候会得到相应的类型:
*(uint32_t*)xxx  结果将是uint32_t
*(int16_t*)xxx 结果就是int16_t
根本得不到他所想要的与平台相关的数据类型。
因此在C语言中,想要得到指针所对应的整型类型,只能通过手动指定,例如微软就是这么干的
#ifdef _WIN64
    typedef __int64          intptr_t;
#else
    typedef int              intptr_t;
#endif
这样intptr_t就可以确保能保存指针类型。
但是可惜这只是某些厂家这么干,ST的库中并没有这样的类型,否则这事就好办了(当然了,ST目前可能也没考虑推出64位的单片机)
我不知道*(uint32_t*)&GPIOx这样的代码是否是出自ST的标准库,我没有去考证,如果真这样写的话他们自己可能也看着别扭,所以我看到st的某个版本库里面看到的是直接比较指针,当然了,就不能使用switch语句了,而是if语句,像这样:
if (GPIOx == GPIOA) xxx_statement 。反正我觉得这样写是最直观最易读的了,给他们点个赞!

c语言的争议太多了,就像#define与typedef之争,#define与const之争,程序员的理论就是运行结果没错那就都不是大问题,不说了,累,继续搬砖了。

(转)STM32F407discovery板实现USB主机(U盘)和USB设备(虚拟串口CDC/VCP)双角色自动切换

admin 发表了文章 • 4 个评论 • 11429 次浏览 • 2016-01-25 14:33 • 来自相关话题

官方CUBE库里面没有USB主机兼USB设备的例子,因项目要求同一个USB口能支持U盘读写和与PC通信,自己就按照官方例子写了个例程。
  程序实现了MSC主机和CDC设备的功能。设备处于哪种角色取决于ID线的状态:当ID线接地或插入microA接口时,设备工作于MSC主机模式;当ID线悬空或插入microB接口时,设备工作于CDC设备模式,在上电状态也可以热切换。

  设备工作于MSC主机模式时,会在插入的U盘根目录创建“STM32.TXT”文件,并写入“This is STM32 working with FatFs ”文本。
  设备工作于CDC设备模式时,在PC端会形成一个虚拟串口,PC通过串口发送给设备的数据,设备会回复给PC。

























程序下载:http://pan.baidu.com/s/1gdhcja7
网盘中名称:STM32F407discovery板实现USB主机(U盘)和USB设备(虚拟串口CDC)双角色自动切换.zip
 
本文转自:http://www.stmcu.org/module/forum/forum.php?mod=viewthread&tid=604951&extra=page%3D&page=1
感谢与非网会员:zhenzhen 分享代码!
 
  查看全部
官方CUBE库里面没有USB主机兼USB设备的例子,因项目要求同一个USB口能支持U盘读写和与PC通信,自己就按照官方例子写了个例程。
  程序实现了MSC主机和CDC设备的功能。设备处于哪种角色取决于ID线的状态:当ID线接地或插入microA接口时,设备工作于MSC主机模式;当ID线悬空或插入microB接口时,设备工作于CDC设备模式,在上电状态也可以热切换。

  设备工作于MSC主机模式时,会在插入的U盘根目录创建“STM32.TXT”文件,并写入“This is STM32 working with FatFs ”文本。
  设备工作于CDC设备模式时,在PC端会形成一个虚拟串口,PC通过串口发送给设备的数据,设备会回复给PC。

101730ncro3z35goc3clon.jpg


102007v8k1mwk82yw1gl9h.png


102013m4tyo5wzezz44qwk.png


102029fc6daupu89cgu8dn.jpg


102035hwm7o3f1kmkwokw7.png

程序下载:http://pan.baidu.com/s/1gdhcja7
网盘中名称:STM32F407discovery板实现USB主机(U盘)和USB设备(虚拟串口CDC)双角色自动切换.zip
 
本文转自:http://www.stmcu.org/module/forum/forum.php?mod=viewthread&tid=604951&extra=page%3D&page=1
感谢与非网会员:zhenzhen 分享代码!
 
 

(转) Keil中编译成功后,提示 Program Size: Code RO-data RW-data ZI-data 所代表的意思

admin 发表了文章 • 1 个评论 • 2840 次浏览 • 2016-01-08 11:47 • 来自相关话题

在Keil中编译工程成功后,在下面的Bulid Ouput窗口中会输出下面这样一段信息:
Program Size: Code=6320 RO-data=4864 RW-data=44 ZI-data=1636

代表的意思:
Code :是程序中代码所占字节大小
RO-data :程序中所定义的指令和常量大小 (个人理解 :Read Only)
RW-data :程序中已初始化的变量大小 (个人理解”:Read/Write)
ZI-Data :程序中未初始化的变量大小 (个人理解 :Zero Initialize)





ROM(Flash) size = Code+RO-data+RW-data;

RAM size = RW-data+ZI-data

可以通过.map查看占用的flash和ram大小

>>转自:http://blog.csdn.net/ropai/article/details/6971792 查看全部
在Keil中编译工程成功后,在下面的Bulid Ouput窗口中会输出下面这样一段信息:
Program Size: Code=6320 RO-data=4864 RW-data=44 ZI-data=1636

代表的意思:
Code :是程序中代码所占字节大小
RO-data :程序中所定义的指令和常量大小 (个人理解 :Read Only)
RW-data :程序中已初始化的变量大小 (个人理解”:Read/Write)
ZI-Data :程序中未初始化的变量大小 (个人理解 :Zero Initialize)





ROM(Flash) size = Code+RO-data+RW-data;

RAM size = RW-data+ZI-data

可以通过.map查看占用的flash和ram大小

>>转自:http://blog.csdn.net/ropai/article/details/6971792

转载STM32F4Cube库CDC类试用手记(含USB全速/高速和VC测试程序)

吉米 发表了文章 • 1 个评论 • 5330 次浏览 • 2015-11-20 11:07 • 来自相关话题

原文位于http://bbs.21ic.com/icview-811704-1-1.html,就是觉得这个说的比较清楚,而且程序是直接用HAL改的,并且说了与cube自动生成的usb的区别,而且有源码直接可用,赞:
需要源码的请去原文下载,因为文件尺寸过大, 最大允许尺寸为 512 KB........
------------------------------------------------
自动生成的cdc不太好用,一开始我也是自动生成,发现没法运行。
我一开始就是想省事儿,不去看USB规范,直接使用自动生成的程序。但发现Cube自动生成的程序也不够给力,后来还是不得不把USB规范梳理了一遍。
我发这个贴的一个重要目的就是给那些需要使用USB传输数据,又没时间钻研USB规范的朋友一个方便。让他们拿上程序就可以直接用,就像使用串口一样方便。
本来就是传输个数据嘛,整这么多麻烦事儿没必要。
自动生成的cdc是单缓存OUT传输。为了提高速度,本贴的程序还用了双缓存接收数据。也是为了方便需要的朋友。
-------------------------------------------------------------------------------------------------
HAL库比以前的固件库好多了,其中的USB设备库有质的飞跃。但是,最主要的问题还是文档写得不好,初上手时,完全不知道该从哪儿开始。如果没有把HAL USB设备库仔细地梳理过一遍,根本就不知道自动生成的程序里,收发数据的函数在usbd_cdc_if.c文件中,也无从知道该从哪里下手修改,哪些该改,哪些不该改。
其实这个问题也很简单,就俩函数,在readme.txt或者main.c里带一句就行。可是,ST就是不说。

首先说整体主观感受:比较起以前的USB设备库,HAL库的USB库有诸多改进,看来ST发心还是蛮大的。总体上是个好的库,说明文档需要再完善。
USB库提供了一些接口函数,你可以根据需要填充这些函数,来完成你需要的功能。对使用者来说,这个库是以“完形填空”的方式设计的架构。也就是说,必须把你的需要嵌合到给定的架构中,不要越雷池半步。
这个库的说明文档已经默认读者熟知USB规范了,所以在阅读和使用HAL库之前,最好先读一下USB规范。
-------------------------------------------------------------------------------------------------------------
下面跟帖是我试用CDC设备类的笔记,与大家一起分享。如果有人只需要用USB收发数据,可以直接使用本示例的收发模块,无须研读USB规范。
程序改自STM324xG_EVAL的CDC Device示例,可以在STM32F4-Discovery开发板(全速)和自制高速开发板上(也可以用STM32F4-Discovery开发板,加一个PHY模块就行)运行。
实测传输效果,全速下OUT速度约为950KB/s,IN速度约为820KB/s。高速下OUT速度约29MB/s,IN速度约16MB/s,条件好时可以达到40MB/s。

(因为以前的版本在压缩包里没有带库,有些朋友用起来比较困难,因此将最新版的STM32CubeF4 v1.8.0库打包进附件中了。)

为了方便使用,对HAL库的USB设备库例子进行了改造,只保留最核心的USB接收和发送功能。如果只是使用USB收发数据,可以直接使用本例程,而不用去详细学习USB规范。下面是改造过程及使用方法的说明。

一、初始化
HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd)函数是初始化GPIO接口硬件和时钟的,包括高速模式下的ULPI硬件时钟。该函数在usbd_conf.c文件中。

二、数据发送
USB库架构中的数据发送过程 首先,用户数据产生之后,必须要先保存到一个数据缓冲区;然后将缓冲区地址告知USB库,并让USB库发送数据(不一定每次都成功)。所谓的数据发送过程,实际上是上述过程的后半部分。由于启动新的发送数据过程的时候,USB控制器可能还没忙完上次分配的任务,这将导致发送过程失败,需要多次重试,直到成功为止。所以数据产生的过程与数据发送的过程必须相对独立。

数据发送过程又可以分成两个部分:1、用户程序将数据缓冲区及数据长度告知USB库,启动数据发送过程;2、USB库在合适的时机自动将缓冲区的数据经由USB线发送到主机。第2步用户可以暂时不用管,USB库已经完成了。在用户程序中只用管第1步,这个步骤是通过下面两个函数实现的。

USBD_CDC_SetTxBuffer()和USBD_CDC_TransmitPacket()是启动数据发送过程的一组函数,要成对使用。USBD_CDC_SetTxBuffer(USBD_HandleTypeDef *pdev, uint8_t *pbuff, uint16_t length)函数的作用是将缓存的指针和大小放到USBD_CDC_HandleTypeDef结构变量中暂存。USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)函数的作用是在USB闲下来以后,将暂存的缓存的指针和数据大小告知USB发送器,USB就会自动发送这些数据。

启动数据发送的时机 数据发送的过程暗示了USB控制器什么时候发送数据是用户无法控制的(实际上这是由主机决定的),这与常见的USART通讯有很大的不同。用户程序可以在任意时候调用这两个函数将待发送数据提交给USB库。CDC的例子程序是通过一个Timer定时调用这两个函数发送数据的。也有人通过SOF同步帧中断回调函数调用这两个函数发送数据的。只要不把CPU堵死,频繁点好,这样数据发送的“实时性”略高。

按:从USB库的例子看,这两个函数从来就是成对使用的,完全没有必要分成两个函数。而且UM1734 USB设备库使用说明书中的使用方法中也仅提到了用USBD_CDC_SetTxBuffer()函数发送数据。可能函数库在设计的初始阶段就没有USBD_CDC_TransmitPacket()函数。很多这样的细节缺陷,让人感觉在设计这个库的架构时,随意性比较大,不够严谨。也许法国人天生浪漫,系统分析师工作时也在任意挥洒奔放的思绪。

注意:USBD_CDC_SetTxBuffer()总是成功的,而USBD_CDC_TransmitPacket()会失败的。只有在USBD_CDC_TransmitPacket()函数成功返回之后,数据才会经USB发送出去。

STM32F4Cube库的CDC类例子里有一个小坑:没有对发送结果做任何处理。在CDC类的例子里使用的是环形缓存,USB忙的时候发送失败了也没有问题,因为CDC例子中的缓存指针没有修改,下次USBD_CDC_SetTxBuffer()还会把没有传送的数据再传送一次。但是,如果实用的是乒乓缓存,或者是多个缓存,就不能认为调用过USBD_CDC_SetTxBuffer()发送数据就当完事了,一定要在USBD_CDC_TransmitPacket()函数成功返回之后才可以切换缓冲数据块,否则那个缓冲区的指针就会在下次调用USBD_CDC_SetTxBuffer()被覆盖掉,数据也就神秘失踪了。CDC类的例子里使用最简单的环形缓存存储数据,其缺点是当数据填充到缓存的速度大于发送速度时,旧的数据会被覆盖,而发送过程对此一无所知,数据会神秘失踪。

三、数据接收
要实现数据接收,首先在初始化的时候要通过USBD_CDC_SetRxBuffer(USBD_HandleTypeDef *pdev, uint8_t *pbuff)函数给USB库指定一个接收缓冲区,让USB控制器收到数据以后可以往里填。

接收数据是通过响应CDC_Itf_Receive(uint8_t* Buf, uint32_t *Len)回调函数实现的,每次USB控制器收到数据后都会调用这个回调函数,从USB收到的数据就存放在参数表指向的数据缓冲区,参数还指明了收到的数据长度。CDC_Itf_Receive()函数在接收标记完这些数据之前别急着返回,不然你可能就摸不到剩下的数据了。USBD_CDC_ReceivePacket()函数的作用是复位OUT端点接收缓冲区,CDC_Itf_Receive()函数在接收完数据之后要调用该函数复位缓冲区。

有一点需要注意,CDC_Itf_Receive()函数是USB中断回调函数,因此应该尽快返回,只能在函数内对数据做简单的标记或处理。如果要对数据进行繁重的处理,应该在main()函数或工作进程内处理。

请忽略UM1734 USB设备库使用说明书(May 2014,Rev 1)第49页关于接收数据的说明文档,呵呵,浪漫的法国人。

四、使用方法示例

1、项目文件的存放位置
<CDC_Standalone>目录下为STM32F407程序源代码。Keil项目文件为CDC_Standalone\ MDK-ARM\Project.uvproj。如果是阅读和修改程序,建议使用Visual Studio 2010打开CDC_Standalone\Visual Studio Project\Project.sln文件。装备了Visual Assist的VS开发环境,其对编程的辅助作用是Keil望尘莫及的,特此鸣谢Ka_Chen,他做的Keil项目转VS项目工具软件用起来非常地方便。
<SerialApp>目录下为上位机通信测试程序,用VS2010编写。
<MSTM32Cube_FW_F4_V1.8.0>目录下是删节版STM32F4Cube HAL库,删除了很多与本示例无关的文件。

2、USB-->PC发送数据
发送数据相对比较简单,用UsbSendData()函数发送数据。该函数在main.c文件中。只有在UsbSendData()函数返回值为USBD_OK时,发送才算完成。

3、PC-->USB接收数据
这个过程略微复杂,需要从回调函数CDC_Itf_Receive()获取PC发送来的数据,并在合适的地方处理数据。我的例子中是在main()函数中处理。CDC_Itf_Receive()函数在usbd_cdc_interface.c文件中。

4、实测效果
USB_Device\SerialApp目录下有通过虚拟串口传输数据的VC++例子。使用USB_Device\SerialApp\Release\SerialApp.exe程序可以测试串口读写的速度。
全速模式,使用STM32F4-Discovery开发板。OUT速度约为950KB/s,IN速度约为820KB/s。
高速模式,使用自制的开发板,以STM32F407IGT6为核心,以USB3300为PHY,使用24M主晶振,并通过MCO1向USB3300提供时钟信号。OUT速度约29MB/s,IN速度约16MB/s。
(USB2.0提速贴:http://itbbs.pconline.com.cn/soft/16281908.html。试验过,没有效果。)

五、修改内容清单
本程序改自STM324xG_EVAL的CDC Device示例。以下罗列了修改的内容:

1、usbd_cdc_interface.c
删除USART相关配置
CDC_Itf_Init(void)仅保留USBD_CDC_SetRxBuffer()配置USB接收缓冲区。
CDC_Itf_DeInit(void)内容全部删除。
CDC_Itf_Control(void)是CDC类的控制处理,保留原状。
CDC_Itf_Receive()是接收数据的回调函数,一定要根据需要修改。

2、usbd_conf.c
HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd)是初始化USB相关硬件接口的,包括GPIO接口硬件和时钟(含UPLI硬件时钟)。
USBD_LL_Init(USBD_HandleTypeDef *pdev)设置内核初始化参数,在此处设置DMA选项。

3、usbd_desc.c
不用修改。

4、stm32f4xx_it.c
删除不再使用的中断函数。包括:
USARTx_DMA_TX_IRQHandler(void)
USARTx_IRQHandler(void)
USARTx_IRQHandler(void)

5、system_stm32f4xx.c
要在这里面设置晶振频率HSE_VALUE。除了这里,还有两个位置要设置晶振频率相关内容:
① main.c中的SystemClock_Config(void)函数中要设置与晶振匹配的RCC_OscInit.PLL.PLLM
② 项目设置-->Target栏中修改晶振频率Xtal。

6、stm32f4xx_hal_msp.c
其它硬件初始化用的。现在不需要使用USB以外的硬件,就用不着它了,从项目中删除

7、startup_stm32f407xx.s
HAL库默认的Heap_Size为0x200,要扩大为0x1500。实际上STM32F4Cube HAL库的USB设备库例子已经对此做了修改,但是,如果要自己用Stm32CubeMx生成CDC类程序的时侯,一定要记得手动修改Heap_Size。
HAL库默认的Stack_Size也从0x400扩大为0x500。 查看全部
原文位于http://bbs.21ic.com/icview-811704-1-1.html,就是觉得这个说的比较清楚,而且程序是直接用HAL改的,并且说了与cube自动生成的usb的区别,而且有源码直接可用,赞:
需要源码的请去原文下载,因为文件尺寸过大, 最大允许尺寸为 512 KB........
------------------------------------------------
自动生成的cdc不太好用,一开始我也是自动生成,发现没法运行。
我一开始就是想省事儿,不去看USB规范,直接使用自动生成的程序。但发现Cube自动生成的程序也不够给力,后来还是不得不把USB规范梳理了一遍。
我发这个贴的一个重要目的就是给那些需要使用USB传输数据,又没时间钻研USB规范的朋友一个方便。让他们拿上程序就可以直接用,就像使用串口一样方便。
本来就是传输个数据嘛,整这么多麻烦事儿没必要。
自动生成的cdc是单缓存OUT传输。为了提高速度,本贴的程序还用了双缓存接收数据。也是为了方便需要的朋友。
-------------------------------------------------------------------------------------------------
HAL库比以前的固件库好多了,其中的USB设备库有质的飞跃。但是,最主要的问题还是文档写得不好,初上手时,完全不知道该从哪儿开始。如果没有把HAL USB设备库仔细地梳理过一遍,根本就不知道自动生成的程序里,收发数据的函数在usbd_cdc_if.c文件中,也无从知道该从哪里下手修改,哪些该改,哪些不该改。
其实这个问题也很简单,就俩函数,在readme.txt或者main.c里带一句就行。可是,ST就是不说。

首先说整体主观感受:比较起以前的USB设备库,HAL库的USB库有诸多改进,看来ST发心还是蛮大的。总体上是个好的库,说明文档需要再完善。
USB库提供了一些接口函数,你可以根据需要填充这些函数,来完成你需要的功能。对使用者来说,这个库是以“完形填空”的方式设计的架构。也就是说,必须把你的需要嵌合到给定的架构中,不要越雷池半步。
这个库的说明文档已经默认读者熟知USB规范了,所以在阅读和使用HAL库之前,最好先读一下USB规范。
-------------------------------------------------------------------------------------------------------------
下面跟帖是我试用CDC设备类的笔记,与大家一起分享。如果有人只需要用USB收发数据,可以直接使用本示例的收发模块,无须研读USB规范。
程序改自STM324xG_EVAL的CDC Device示例,可以在STM32F4-Discovery开发板(全速)和自制高速开发板上(也可以用STM32F4-Discovery开发板,加一个PHY模块就行)运行。
实测传输效果,全速下OUT速度约为950KB/s,IN速度约为820KB/s。高速下OUT速度约29MB/s,IN速度约16MB/s,条件好时可以达到40MB/s。

(因为以前的版本在压缩包里没有带库,有些朋友用起来比较困难,因此将最新版的STM32CubeF4 v1.8.0库打包进附件中了。)

为了方便使用,对HAL库的USB设备库例子进行了改造,只保留最核心的USB接收和发送功能。如果只是使用USB收发数据,可以直接使用本例程,而不用去详细学习USB规范。下面是改造过程及使用方法的说明。

一、初始化
HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd)函数是初始化GPIO接口硬件和时钟的,包括高速模式下的ULPI硬件时钟。该函数在usbd_conf.c文件中。

二、数据发送
USB库架构中的数据发送过程 首先,用户数据产生之后,必须要先保存到一个数据缓冲区;然后将缓冲区地址告知USB库,并让USB库发送数据(不一定每次都成功)。所谓的数据发送过程,实际上是上述过程的后半部分。由于启动新的发送数据过程的时候,USB控制器可能还没忙完上次分配的任务,这将导致发送过程失败,需要多次重试,直到成功为止。所以数据产生的过程与数据发送的过程必须相对独立。

数据发送过程又可以分成两个部分:1、用户程序将数据缓冲区及数据长度告知USB库,启动数据发送过程;2、USB库在合适的时机自动将缓冲区的数据经由USB线发送到主机。第2步用户可以暂时不用管,USB库已经完成了。在用户程序中只用管第1步,这个步骤是通过下面两个函数实现的。

USBD_CDC_SetTxBuffer()和USBD_CDC_TransmitPacket()是启动数据发送过程的一组函数,要成对使用。USBD_CDC_SetTxBuffer(USBD_HandleTypeDef *pdev, uint8_t *pbuff, uint16_t length)函数的作用是将缓存的指针和大小放到USBD_CDC_HandleTypeDef结构变量中暂存。USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)函数的作用是在USB闲下来以后,将暂存的缓存的指针和数据大小告知USB发送器,USB就会自动发送这些数据。

启动数据发送的时机 数据发送的过程暗示了USB控制器什么时候发送数据是用户无法控制的(实际上这是由主机决定的),这与常见的USART通讯有很大的不同。用户程序可以在任意时候调用这两个函数将待发送数据提交给USB库。CDC的例子程序是通过一个Timer定时调用这两个函数发送数据的。也有人通过SOF同步帧中断回调函数调用这两个函数发送数据的。只要不把CPU堵死,频繁点好,这样数据发送的“实时性”略高。

按:从USB库的例子看,这两个函数从来就是成对使用的,完全没有必要分成两个函数。而且UM1734 USB设备库使用说明书中的使用方法中也仅提到了用USBD_CDC_SetTxBuffer()函数发送数据。可能函数库在设计的初始阶段就没有USBD_CDC_TransmitPacket()函数。很多这样的细节缺陷,让人感觉在设计这个库的架构时,随意性比较大,不够严谨。也许法国人天生浪漫,系统分析师工作时也在任意挥洒奔放的思绪。

注意:USBD_CDC_SetTxBuffer()总是成功的,而USBD_CDC_TransmitPacket()会失败的。只有在USBD_CDC_TransmitPacket()函数成功返回之后,数据才会经USB发送出去。

STM32F4Cube库的CDC类例子里有一个小坑:没有对发送结果做任何处理。在CDC类的例子里使用的是环形缓存,USB忙的时候发送失败了也没有问题,因为CDC例子中的缓存指针没有修改,下次USBD_CDC_SetTxBuffer()还会把没有传送的数据再传送一次。但是,如果实用的是乒乓缓存,或者是多个缓存,就不能认为调用过USBD_CDC_SetTxBuffer()发送数据就当完事了,一定要在USBD_CDC_TransmitPacket()函数成功返回之后才可以切换缓冲数据块,否则那个缓冲区的指针就会在下次调用USBD_CDC_SetTxBuffer()被覆盖掉,数据也就神秘失踪了。CDC类的例子里使用最简单的环形缓存存储数据,其缺点是当数据填充到缓存的速度大于发送速度时,旧的数据会被覆盖,而发送过程对此一无所知,数据会神秘失踪。

三、数据接收
要实现数据接收,首先在初始化的时候要通过USBD_CDC_SetRxBuffer(USBD_HandleTypeDef *pdev, uint8_t *pbuff)函数给USB库指定一个接收缓冲区,让USB控制器收到数据以后可以往里填。

接收数据是通过响应CDC_Itf_Receive(uint8_t* Buf, uint32_t *Len)回调函数实现的,每次USB控制器收到数据后都会调用这个回调函数,从USB收到的数据就存放在参数表指向的数据缓冲区,参数还指明了收到的数据长度。CDC_Itf_Receive()函数在接收标记完这些数据之前别急着返回,不然你可能就摸不到剩下的数据了。USBD_CDC_ReceivePacket()函数的作用是复位OUT端点接收缓冲区,CDC_Itf_Receive()函数在接收完数据之后要调用该函数复位缓冲区。

有一点需要注意,CDC_Itf_Receive()函数是USB中断回调函数,因此应该尽快返回,只能在函数内对数据做简单的标记或处理。如果要对数据进行繁重的处理,应该在main()函数或工作进程内处理。

请忽略UM1734 USB设备库使用说明书(May 2014,Rev 1)第49页关于接收数据的说明文档,呵呵,浪漫的法国人。

四、使用方法示例

1、项目文件的存放位置
<CDC_Standalone>目录下为STM32F407程序源代码。Keil项目文件为CDC_Standalone\ MDK-ARM\Project.uvproj。如果是阅读和修改程序,建议使用Visual Studio 2010打开CDC_Standalone\Visual Studio Project\Project.sln文件。装备了Visual Assist的VS开发环境,其对编程的辅助作用是Keil望尘莫及的,特此鸣谢Ka_Chen,他做的Keil项目转VS项目工具软件用起来非常地方便。
<SerialApp>目录下为上位机通信测试程序,用VS2010编写。
<MSTM32Cube_FW_F4_V1.8.0>目录下是删节版STM32F4Cube HAL库,删除了很多与本示例无关的文件。

2、USB-->PC发送数据
发送数据相对比较简单,用UsbSendData()函数发送数据。该函数在main.c文件中。只有在UsbSendData()函数返回值为USBD_OK时,发送才算完成。

3、PC-->USB接收数据
这个过程略微复杂,需要从回调函数CDC_Itf_Receive()获取PC发送来的数据,并在合适的地方处理数据。我的例子中是在main()函数中处理。CDC_Itf_Receive()函数在usbd_cdc_interface.c文件中。

4、实测效果
USB_Device\SerialApp目录下有通过虚拟串口传输数据的VC++例子。使用USB_Device\SerialApp\Release\SerialApp.exe程序可以测试串口读写的速度。
全速模式,使用STM32F4-Discovery开发板。OUT速度约为950KB/s,IN速度约为820KB/s。
高速模式,使用自制的开发板,以STM32F407IGT6为核心,以USB3300为PHY,使用24M主晶振,并通过MCO1向USB3300提供时钟信号。OUT速度约29MB/s,IN速度约16MB/s。
(USB2.0提速贴:http://itbbs.pconline.com.cn/soft/16281908.html。试验过,没有效果。)

五、修改内容清单
本程序改自STM324xG_EVAL的CDC Device示例。以下罗列了修改的内容:

1、usbd_cdc_interface.c
删除USART相关配置
CDC_Itf_Init(void)仅保留USBD_CDC_SetRxBuffer()配置USB接收缓冲区。
CDC_Itf_DeInit(void)内容全部删除。
CDC_Itf_Control(void)是CDC类的控制处理,保留原状。
CDC_Itf_Receive()是接收数据的回调函数,一定要根据需要修改。

2、usbd_conf.c
HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd)是初始化USB相关硬件接口的,包括GPIO接口硬件和时钟(含UPLI硬件时钟)。
USBD_LL_Init(USBD_HandleTypeDef *pdev)设置内核初始化参数,在此处设置DMA选项。

3、usbd_desc.c
不用修改。

4、stm32f4xx_it.c
删除不再使用的中断函数。包括:
USARTx_DMA_TX_IRQHandler(void)
USARTx_IRQHandler(void)
USARTx_IRQHandler(void)

5、system_stm32f4xx.c
要在这里面设置晶振频率HSE_VALUE。除了这里,还有两个位置要设置晶振频率相关内容:
① main.c中的SystemClock_Config(void)函数中要设置与晶振匹配的RCC_OscInit.PLL.PLLM
② 项目设置-->Target栏中修改晶振频率Xtal。

6、stm32f4xx_hal_msp.c
其它硬件初始化用的。现在不需要使用USB以外的硬件,就用不着它了,从项目中删除

7、startup_stm32f407xx.s
HAL库默认的Heap_Size为0x200,要扩大为0x1500。实际上STM32F4Cube HAL库的USB设备库例子已经对此做了修改,但是,如果要自己用Stm32CubeMx生成CDC类程序的时侯,一定要记得手动修改Heap_Size。
HAL库默认的Stack_Size也从0x400扩大为0x500。

(转)基于LMC6484A和LM4040A20的传感器电压调制电路实现(将0-10v调至0-3.3v)

feiante 回复了问题 • 4 人关注 • 4 个回复 • 4299 次浏览 • 2015-11-13 15:47 • 来自相关话题

(转)一篇来自国外的libnrf24l01的Libemb库的使用介绍,非常详细!

alvin_ 回复了问题 • 3 人关注 • 5 个回复 • 5683 次浏览 • 2015-11-13 11:02 • 来自相关话题

(转)单片机常用按键电路

admin 发表了文章 • 0 个评论 • 4271 次浏览 • 2015-11-02 12:52 • 来自相关话题

单片机组成的小系统中,有的需要人机交互功能,按键是最常见的输入方式。最常见的按键电路大致有,一对一的直接连接和动态扫描的矩阵式连接两种。
一对一的直接连接就是一个按键直接对应一个CPU的输入口,比如下图






左右两个电路作用一样,区别是左边CPU的输入端常态为高电位,按下按键时为低电位;右边的常态为低电位,按下按键是高电位。
这样的电路简单直接,一个按键独占一个端口,在按键数量较少端口数量富裕时可以直接使用。但很多场合需要的按键数比较多,要尽量少地占用端口就必须使用矩阵式的按键链接。如下图:





图中将按键按行列矩阵的方式排列,其中的每一行公用一根行线,每一列公用一根列线。以此图为例,16个按键,按一对一方式连接的话需要16个端口,而按这样的矩阵方式链接只需要8个端口,所需端口数大量减少。按键数Knum=line*row,而端口数Pnum=line+row,其中的li单片机常用按键电路ne和row分别代表行数和列数。
图中的Px,Py为CPU的IO端口,在本例中可以使用不同的端口也可以使用同一个8位端口。上拉电阻不是必须,单片机IO口内部有上拉电路时此处就可省略。
这个电路的工作原理是采用程序扫描的方式检测某个按键状态。比如将Px口的4位全置为低电平,这时如果没有任何按键按下的话,从Py口读回的4位应全为高,而如果有某一键按下,则对应按下键的那一列的位读回值将为低。这样就能知道按下键所在的列;接着确定按键所处行,把Py口的输入值作为输出,Px全部置高并读取输入,就能得到按键所在行位置,于是就确定了所按按键的行列位置。
扫描可以有两种实现的方法,一种是全行全列扫描,一种是逐行全列扫描。





上面的例子其实就是全行全列扫描方式,见流程框图。
其特点是,一个流程就能到是否有按键按下,并能确定按下按检测行列值,检测步骤简单迅速。但作为行列接口的Px,Py必须是双向的,亦即同时具有输入输出功能,单片机的端口基本都能满足。
但如果端口非双向,或按键数量大,端口数紧张需进一步减少端口时,也许就需要别的方式来解决。
还是拿上面的电路做例子,全行全列扫描是在检测到有按键按下时,先检测列然后再确定行。
换种检测方式,就是先给定行,再检测列。比如行端口Px每次输出不是全部,而是只有一位输出为低,也就是预先给定了行,那么对应行有按键按下时,Py读回的值就代表按键所在列。Px口按位逐一输出低,每次读回Py值,这样的处理方式,更贴近扫描的含义。因为按键是机械动作,相对单片机运行速度来说,一次扫描流程足够检测到按键按下的动作。这种扫描方式就是逐行全列扫描。见流程图。





这种扫描方式的特点是逐行扫描,有多少行就扫多少次,当有按键按下时,行列数就确定了。虽然显得麻烦点,但好处是Px只需是输出而Py只是输入,Px输出每次只有唯一的一位为低,这样的特点就可以对端口数进行简化,比如使用译码器。如图所示:






由图可以看出,同样按键数,增加一个138译码器之后,CPU所用端口数就减为5了。





Px口的3位只需输出0到7,译码器输出就能得到和前面一样的行扫描信号。这时候的程序处理流程,和上面的略有不同,主要是行的表示上不同。上面是行数的对应位表示对应行,下面的是行数的对应值就是对应行。程序框图如所示。
逐行扫描还有另外一个用处,就是当系统中有需要动态扫描的装置比如LED数码管或点阵时,行扫描线就可以为其提供动态扫描信号,这样也是为了减少端口使用数量,达到信号复用并减少代码量的目的。
除了上面提到的几种按键电路,还有一种按键电路,使用更少的端口数量,如图





该电路同矩阵式按键电路一样,所不同的是行列端口使用的是同一个端口,并且矩阵的一条对角线上按键由二极管代替。如此图所示,
按键数Knum=Pnum*(Pnum-1),其中Pnum就是使用的端口数。
以4个端口数为例,
一对一连接方式只能是4个按键;
不带译码器最多4个按键,
使用2-4译码器或3-8译码器方式最多8个按键
而这种电路可以达到12个按键。此电路程序部分和不带译码器的一样,只是注意对角线上被二极管替代的地方没有按键。
>>本文引用自:[新浪博客]http://blog.sina.com.cn/s/blog ... .html 查看全部
单片机组成的小系统中,有的需要人机交互功能,按键是最常见的输入方式。最常见的按键电路大致有,一对一的直接连接和动态扫描的矩阵式连接两种。
一对一的直接连接就是一个按键直接对应一个CPU的输入口,比如下图

QQ图片20150930144328.jpg


左右两个电路作用一样,区别是左边CPU的输入端常态为高电位,按下按键时为低电位;右边的常态为低电位,按下按键是高电位。
这样的电路简单直接,一个按键独占一个端口,在按键数量较少端口数量富裕时可以直接使用。但很多场合需要的按键数比较多,要尽量少地占用端口就必须使用矩阵式的按键链接。如下图:

3.jpg

图中将按键按行列矩阵的方式排列,其中的每一行公用一根行线,每一列公用一根列线。以此图为例,16个按键,按一对一方式连接的话需要16个端口,而按这样的矩阵方式链接只需要8个端口,所需端口数大量减少。按键数Knum=line*row,而端口数Pnum=line+row,其中的li单片机常用按键电路ne和row分别代表行数和列数。
图中的Px,Py为CPU的IO端口,在本例中可以使用不同的端口也可以使用同一个8位端口。上拉电阻不是必须,单片机IO口内部有上拉电路时此处就可省略。
这个电路的工作原理是采用程序扫描的方式检测某个按键状态。比如将Px口的4位全置为低电平,这时如果没有任何按键按下的话,从Py口读回的4位应全为高,而如果有某一键按下,则对应按下键的那一列的位读回值将为低。这样就能知道按下键所在的列;接着确定按键所处行,把Py口的输入值作为输出,Px全部置高并读取输入,就能得到按键所在行位置,于是就确定了所按按键的行列位置。
扫描可以有两种实现的方法,一种是全行全列扫描,一种是逐行全列扫描。

4.jpg

上面的例子其实就是全行全列扫描方式,见流程框图。
其特点是,一个流程就能到是否有按键按下,并能确定按下按检测行列值,检测步骤简单迅速。但作为行列接口的Px,Py必须是双向的,亦即同时具有输入输出功能,单片机的端口基本都能满足。
但如果端口非双向,或按键数量大,端口数紧张需进一步减少端口时,也许就需要别的方式来解决。
还是拿上面的电路做例子,全行全列扫描是在检测到有按键按下时,先检测列然后再确定行。
换种检测方式,就是先给定行,再检测列。比如行端口Px每次输出不是全部,而是只有一位输出为低,也就是预先给定了行,那么对应行有按键按下时,Py读回的值就代表按键所在列。Px口按位逐一输出低,每次读回Py值,这样的处理方式,更贴近扫描的含义。因为按键是机械动作,相对单片机运行速度来说,一次扫描流程足够检测到按键按下的动作。这种扫描方式就是逐行全列扫描。见流程图。

1.jpg

这种扫描方式的特点是逐行扫描,有多少行就扫多少次,当有按键按下时,行列数就确定了。虽然显得麻烦点,但好处是Px只需是输出而Py只是输入,Px输出每次只有唯一的一位为低,这样的特点就可以对端口数进行简化,比如使用译码器。如图所示:

5.jpg


由图可以看出,同样按键数,增加一个138译码器之后,CPU所用端口数就减为5了。

6.jpg

Px口的3位只需输出0到7,译码器输出就能得到和前面一样的行扫描信号。这时候的程序处理流程,和上面的略有不同,主要是行的表示上不同。上面是行数的对应位表示对应行,下面的是行数的对应值就是对应行。程序框图如所示。
逐行扫描还有另外一个用处,就是当系统中有需要动态扫描的装置比如LED数码管或点阵时,行扫描线就可以为其提供动态扫描信号,这样也是为了减少端口使用数量,达到信号复用并减少代码量的目的。
除了上面提到的几种按键电路,还有一种按键电路,使用更少的端口数量,如图

2.jpg

该电路同矩阵式按键电路一样,所不同的是行列端口使用的是同一个端口,并且矩阵的一条对角线上按键由二极管代替。如此图所示,
按键数Knum=Pnum*(Pnum-1),其中Pnum就是使用的端口数。
以4个端口数为例,
一对一连接方式只能是4个按键;
不带译码器最多4个按键,
使用2-4译码器或3-8译码器方式最多8个按键
而这种电路可以达到12个按键。此电路程序部分和不带译码器的一样,只是注意对角线上被二极管替代的地方没有按键。
>>本文引用自:[新浪博客]http://blog.sina.com.cn/s/blog ... .html

(转)教你看懂时序图

回复

admin 回复了问题 • 3 人关注 • 2 个回复 • 7780 次浏览 • 2015-11-02 09:24 • 来自相关话题