TA的每日心情 | 开心 2013-7-2 13:29 |
---|
签到天数: 1 天 连续签到: 1 天 [LV.1]初来乍到
|
今天开始调试STM32F407使用模块移植。
这里今天就移植一下简单的AD模块。
PCF8591与STM32F407通讯。
模块有图为证。
分割线————————————————
准备好STM32F4-Discovery_FW_V1.1.0的官方库随机找个例程打开。
和网上的一些PCF8591的C程序,(当然你也可以根据他的datasheet来编写驱动,但是没必要做这个对不对)
我就用网上的C51程序吧
这里下面会给出代码。
首先先看看C51的模块使用代码如下:
#define PCF8591 0x90 //PCF8591 地址
下面就可以得到值到D[2]中。
ISendByte(PCF8591,0x43);
D[2]=IRcvByte(PCF8591); //ADC3 模数转换4 可调0-5v
AD3_mV = (uint)(D[2]*196)/10;
在仔细看 ISendByte(PCF8591,0x43);和D[2]=IRcvByte(PCF8591); 这个函数:
/*ADC发送字节[命令]数据函数*/
bit ISendByte(unsigned char sla,unsigned char c)//这里很容易看出往里面某个地址写入数值
{
Start_I2c(); //启动总线
SendByte2(sla); //发送器件地址
if(ack==0)return(0);
SendByte2(c); //发送数据
if(ack==0)return(0);
Stop_I2c(); //结束总线
return(1);
}
/*ADC读字节数据函数*/
unsigned char IRcvByte(unsigned char sla)
{ unsigned char c;
Start_I2c(); //启动总线
SendByte2(sla+1); //发送器件地址
if(ack==0)return(0);
c=RcvByte(); //读取数据0
Ack_I2c(1); //发送非就答位
Stop_I2c(); //结束总线
return(c);
}
这个完全是由c写,所以可以不用改。
再看上面两个函数里面的几个函数Start_I2c(); SendByte2(X); c=RcvByte(); Ack_I2c(1); Stop_I2c();
继续贴代码:
#define NOP() _nop_() /* 定义空指令 */
#define _Nop() _nop_() /*定义空指令*/
bit ack; /*应答标志位*/
/*******************************************************************
起动总线函数
函数原型: void Start_I2c();
功能: 启动I2C总线,即发送I2C起始条件.
********************************************************************/
void Start_I2c()
{
SDA2=1; /*发送起始条件的数据信号*/
_Nop();
SCK2=1;
_Nop(); /*起始条件建立时间大于4.7us,延时*/
_Nop();
_Nop();
_Nop();
_Nop();
SDA2=0; /*发送起始信号*/
_Nop(); /* 起始条件锁定时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
SCK2=0; /*钳住I2C总线,准备发送或接收数据 */
_Nop();
_Nop();
}
/*===========================================结束总线函数 ================================================*/
/*******************************************************************
结束总线函数
函数原型: void Stop_I2c();
功能: 结束I2C总线,即发送I2C结束条件.
********************************************************************/
void Stop_I2c()
{
SDA2=0; /*发送结束条件的数据信号*/
_Nop(); /*发送结束条件的时钟信号*/
SCK2=1; /*结束条件建立时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
_Nop();
SDA2=1; /*发送I2C总线结束信号*/
_Nop();
_Nop();
_Nop();
_Nop();
}
/*========================================== 字节数据发送函数 ================================================*/
/*******************************************************************
字节数据发送函数
函数原型: void SendByte(UCHAR c);
功能: 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
此状态位进行操作.(不应答或非应答都使ack=0)
发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
********************************************************************/
void SendByte2(unsigned char c)
{
unsigned char BitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++) /*要传送的数据长度为8位*/
{
if((c<<BitCnt)&0x80)SDA2=1; /*判断发送位*/
else SDA2=0;
_Nop();
SCK2=1; /*置时钟线为高,通知被控器开始接收数据位*/
_Nop();
_Nop(); /*保证时钟高电平周期大于4μs*/
_Nop();
_Nop();
_Nop();
SCK2=0;
}
_Nop();
_Nop();
SDA2=1; /*8位发送完后释放数据线,准备接收应答位*/
_Nop();
_Nop();
SCK2=1;
_Nop();
_Nop();
_Nop();
if(SDA2==1)ack=0;
else ack=1; /*判断是否接收到应答信号*/
SCK2=0;
_Nop();
_Nop();
}
/*========================================== 字节数据接收函数 ================================================*/
/*******************************************************************
字节数据接收函数
函数原型: UCHAR RcvByte();
功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号),
发完后请用应答函数应答从机。
********************************************************************/
unsigned char RcvByte()
{
unsigned char retc;
unsigned char BitCnt;
retc=0;
SDA2=1; /*置数据线为输入方式*/
for(BitCnt=0;BitCnt<8;BitCnt++)
{
_Nop();
SCK2=0; /*置时钟线为低,准备接收数据位*/
_Nop();
_Nop(); /*时钟低电平周期大于4.7μs*/
_Nop();
_Nop();
_Nop();
SCK2=1; /*置时钟线为高使数据线上数据有效*/
_Nop();
_Nop();
retc=retc<<1;
if(SDA2==1)retc=retc+1; /*读数据位,接收的数据位放入retc中 */
_Nop();
_Nop();
}
SCK2=0;
_Nop();
_Nop();
return(retc);
}
/*========================================== 应答子函数 ================================================*/
/********************************************************************
应答子函数
函数原型: void Ack_I2c(bit a);
功能: 主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)
********************************************************************/
void Ack_I2c(bit a)
{
if(a==0)SDA2=0; /*在此发出应答或非应答信号 */
else SDA2=1;
_Nop();
_Nop();
_Nop();
SCK2=1;
_Nop();
_Nop(); /*时钟低电平周期大于4μs*/
_Nop();
_Nop();
_Nop();
SCK2=0; /*清时钟线,钳住I2C总线以便继续接收*/
_Nop();
_Nop();
}
这里要说明一些东西,首先_Nop();在这里的C51中,大概是1US,所以在移植时最好遵守以下,免得出问题。
好了,到这里我们已经知道了PCF8591的C51代码,现在要做的就是把它弄到STM32中,那么我们可以选用本身自带的IIc设备,这里我就不用了。
我选用用IO模拟,这样不占用资源。当然兴趣的朋友可以改成自带的iic设备,这样只要改ISendByte(PCF8591,0x43);和 D[2]=IRcvByte(PCF8591); 中的IIc的使用就行了。
好下面开始说怎么样更改成STM32的。
1.打开随机一例程(主要怕麻烦要建新的,还要包涵什么的,所以直接复制一个清空)
2.开始讲GPIO(普通IO的使用)
2.1
/* Private typedef -----------------------------------------------------------*/
GPIO_InitTypeDef GPIO_InitStructure;
新建一个GPIO结构体。
/* GPIOD Periph clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
使能某一个IO口如PA,PB,PC等等这里开了D的
/* Configure PD12, PD13 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOD, &GPIO_InitStructure);
上面的就是例程的一个GPIO的初始化例程,那么这里我们要做一个IIC模拟的管脚管脚初始化函数:
/*初始化IIC -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- -----*/
---------函数名IIC_Init
---------输入无
---------输出无
---------
---------
/* 初始化IIC -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- ----- */
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
这个是GPIO设置的几种联合体:
typedef enum
{
GPIO_Mode_IN = 0x00, /*!< GPIO Input Mode */
GPIO_Mode_OUT = 0x01, /*!< GPIO Output Mode */
GPIO_Mode_AF = 0x02, /*!< GPIO Alternate function Mode */
GPIO_Mode_AN = 0x03 /*!< GPIO Analog Mode */
}GPIOMode_TypeDef;
模式选择:输入模式;输出模式; 交错功能模式 ;模拟模式;
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_IN) || ((MODE) == GPIO_Mode_OUT) || \
((MODE) == GPIO_Mode_AF)|| ((MODE) == GPIO_Mode_AN))
/**
* @brief GPIO Output type enumeration
*/
typedef enum
{
GPIO_OType_PP = 0x00,
GPIO_OType_OD = 0x01
}GPIOOType_TypeDef;
#define IS_GPIO_OTYPE(OTYPE) (((OTYPE) == GPIO_OType_PP) || ((OTYPE) == GPIO_OType_OD))
输出模式:推挽输出,开漏输出
/**
* @brief GPIO Output Maximum frequency enumeration
*/
typedef enum
{
GPIO_Speed_2MHz = 0x00, /*!< Low speed */
GPIO_Speed_25MHz = 0x01, /*!< Medium speed */
GPIO_Speed_50MHz = 0x02, /*!< Fast speed */
GPIO_Speed_100MHz = 0x03 /*!< High speed on 30 pF (80 MHz Output max speed on 15 pF) */
}GPIOSpeed_TypeDef;
#define IS_GPIO_SPEED(SPEED) (((SPEED) == GPIO_Speed_2MHz) || ((SPEED) == GPIO_Speed_25MHz) || \
((SPEED) == GPIO_Speed_50MHz)|| ((SPEED) == GPIO_Speed_100MHz))
管脚模式:最大响应频率:2M;25M;50M;100M;
/**
* @brief GPIO Configuration PullUp PullDown enumeration
*/
typedef enum
{
GPIO_PuPd_NOPULL = 0x00,
GPIO_PuPd_UP = 0x01,
GPIO_PuPd_DOWN = 0x02
}GPIOPuPd_TypeDef;
这里应该是:悬空;上拉;下拉;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//悬空
GPIO_Init(GPIOB, &GPIO_InitStructure);
这里管脚置一 为下面的代码:
GPIO_SetBits(GPIOD, GPIO_Pin_1);
这里管脚清零 为下面的代码 :
GPIO_ResetBits(GPIOD, GPIO_Pin_1);
我们需要用宏定义来改变:因为代码里的置一和STM32的有很大区别,为了以后好移植,就用宏定义:
有两个管脚,第一个为SCL,另一个为SDA;(这里改成宏定义操作寄存器就比库函数来的快了所以先给出操作寄存器)
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint16_t BSRRL; /*!< GPIO port bit set/reset low register, Address offset: 0x18 */
__IO uint16_t BSRRH; /*!< GPIO port bit set/reset high register, Address offset: 0x1A */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
这个结构体给出了所有寄存器。
再看官方初始化函数中:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
中的模式设置:
GPIOx->MODER &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));
#define GPIO_MODER_MODER0 ((uint32_t)0x00000003)
pinpos 是要设置的管脚位置。数值为0~15;
可想而知MODER 是寄存器的模式选择,并且调用的是先给要控制的位置清零,然后再设置模式
模式为
GPIO_Mode_IN = 0x00, /*!< GPIO Input Mode */ GPIO_Mode_OUT = 0x01, /*!< GPIO Output Mode */
GPIO_Mode_AF = 0x02, /*!< GPIO Alternate function Mode */
GPIO_Mode_AN = 0x03 /*!< GPIO Analog Mode */
到这里我们也就学会了寄存器操作了,是不是so easy?
下面回到IC管脚的设置(SCL管脚是输出的,但是SDA有时候是输出,有时候是输入,所以要设置输出和输入模式。不过好像STM32F407中欧交错模式可能就是这个,但是我不太了解等会再用)
那么SDA 要设置输出和输入两个模式。
//IO方向设置宏定义
#define SDA _pin 1
#define GPIOXX GPIOD
#define SDA_IN() {GPIOXX->MODER &=~((0x00000003)<<(2*pinpos )); GPIOXX-> MODER |= (((uint32_t)0x00 ) << (SDA _pin * 2)); } //这里GPIOXX-> MODER选的是D的
#define SDA_OUT() {GPIOXX-> MODER &=~((0x00000003)<<(2* pinpos )); GPIOXX-> MODER |= (((uint32_t) 0x01 ) << ( SDA _pin * 2)); } //这里GPIO XX-> MODER选的是D的
那么对于输出和输入管脚的值位寄存器是哪个呢、?
看官方局函数中的置位函数:
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BSRRL = GPIO_Pin;
}
可以看出是寄存器BSRRL 中来设置的置位的。
看官方局函数中的清零函数:
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BSRRH = GPIO_Pin;
}
可以看出是寄存器BSRRH 中来设置的清零的。
而看程序的化,我们不需要更改很多。
看官方局函数中的读输入值函数:
其中某个Pin
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t bitstatus = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
{
bitstatus = (uint8_t)Bit_SET;
}
else
{
bitstatus = (uint8_t)Bit_RESET;
}
return bitstatus;
}
整个IO口
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
return ((uint16_t)GPIOx->IDR);
}
可以看出是寄存器 IDR 中来读输入值 的。
//IO操作函数宏定义
#define IIC_SCL_1 GPIO_SetBits(GPIOD, GPIO_Pin_0)
#define IIC_SCL_0 GPIO_ResetBits(GPIOD, GPIO_Pin_0)
#define IIC_SDA_1 GPIO_SetBits(GPIOD, GPIO_Pin_1)
#define IIC_SDA_0 GPIO_ResetBits(GPIOD, GPIO_Pin_1) //输入SDA #define READ_SDA GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_1)
IIC_SCL_1;
IIC_SDA_1;
}
好了整个初始化程序出来了:
/* 宏定义 -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- ----- */
//IO方向设置宏定义
#define SDA _pin 1
#define GPIOXX GPIOD
#define SDA_IN() {GPIOXX-> MODER &=~((0x00000003)<<(2* pinpos )); GPIOXX-> MODER |= (((uint32_t) 0x00 ) << ( SDA _pin * 2)); } //这里GPIO XX-> MODER选的是D的
#define SDA_OUT() {GPIOXX-> MODER &=~((0x00000003)<<(2* pinpos )); GPIOXX-> MODER |= (((uint32_t) 0x01 ) << ( SDA _pin * 2)); } //这里GPIO XX-> MODER选的是D的
//IO操作函数宏定义
#define IIC_SCL_1 GPIO_SetBits(GPIOD, GPIO_Pin_0)
#define IIC_SCL_0 GPIO_ResetBits(GPIOD, GPIO_Pin_0)
#define IIC_SDA_1 GPIO_SetBits(GPIOD, GPIO_Pin_1)
#define IIC_SDA_0 GPIO_ResetBits(GPIOD, GPIO_Pin_1) //输入SDA #define READ_SDA GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_1)
/* 初始化IIC -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- ----- */
---------函数名 IIC_Init
---------输入无
---------输出无
---------
---------
/* 初始化IIC -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- ----- */
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//悬空
GPIO_Init(GPIOB, &GPIO_InitStructure);
IIC_SCL_1;//管脚置高
IIC_SDA_1; //管脚置高
}
好了,今天就写到这里。
总结一下,这一章的主要内容是移植一个模块的驱动函数。那么今天主要从官方库中介绍了GPIO的操作,以及从底层中看到GPIO寄存器的功能。
宏定义了一下。
2013年8月19日16:21:59 记
|
-
随机找个例程打开
-
两个模块
|