STM32F407用wk2124芯片编写SPI转四路串口驱动
因为项目用到了wk2124芯片来进行串口扩展,网上找了好多资料没有现成的,根据商家提供的demo,它是基于103写的,所以根据自己板子的实际情况进行了改写,并且学习一下里面的主要函数及我对函数的理解
原理图
用到5个引脚,片选CS、时钟SCK、MOSI、MISO和中断IRQ,因为RST设置的是板子连电后电容充电,所以会自动复位,不用这个引脚。
一. SPI
1.GPIO初始化设置
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//使能SPI2时钟,在APB1时钟里面
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
//PB13-15分别复用为SCK,MISO,MOSI
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
2.SPI参数配置
因为发送接收是同时进行,所以选择全双工,SPI设置为主模式 ,大小为8bit,时钟默认拉低,第二个时钟沿开始捕捉,数据格式SPI_FirstBit(选择是MSB还是LSB),选择MSB方式,举个例子假设用16bit,就是[15,0]这样,如果是LSB就是[0,15]这样。
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟悬空低
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由软件(使用SSI位)管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
SPI2_ReadWriteByte(0xff);//启动传输
3.读写函数
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //发送缓存空标志位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)//接受缓存非空标志位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
读写函数中,用SPI_I2S_GetFlagStatus不断去获取TXE和RXNE的状态,如果是非空就继续,如果为空就调用Send和Rece函数去发送接收数据,当retry标志连续超过200次,就返回0,发送/接收失败。
4.速度设置
void SPI2_SetSpeed(u8 SpeedSet)
{
SPI_InitStructure.SPI_BaudRatePrescaler = SpeedSet ;
SPI_Init(SPI2, &SPI_InitStructure);
SPI_Cmd(SPI2,ENABLE);
}
二、WK2124
引脚对应关系
CS PB12
SCK PB13
MOSI PB15
MISO PB14
IRQ PB1
1.片选初始化
void SPI_CS_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); //使能PB端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PB12端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //IO口速度为100MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB12
GPIO_SetBits(GPIOB,GPIO_Pin_12); //PB12 输出高
}
2. 写寄存器函数
void WK2124WriteReg(unsigned char port,unsigned char reg,unsigned char dat)
{
SPI_CS_L();//片选使能
SPI2_ReadWriteByte(((port-1)<<4)+reg); //写控制字节
SPI2_ReadWriteByte(dat); //写数据
SPI_CS_H();//片选无效
}
首先把片选信号拉低,然后写入控制命令,再写入数据,写完之后再拉高片选,对于其中的控制字节((port-1)<<4)+reg可以根据数据手册得知,port:为子串口的端口号(C0\C1),reg是目标寄存器,dat是数据。
6,7位为默认为0,0,C1,C0为串口通道,可选值为00,01,10,11分别表示4个串口,默认设置为00,通过传入参数port(port设置的是1,2,3,4,但是因为从0开始,所以要port-1)来进行改变,然后左移4位变成00 0000,然后加上初始的默认值0000 0000,最后加上要写入的寄存器,所以其实是0x00+(port-1)<<4)+reg,然后将这个cmd传入spi读写函数进行读写。
3. 读寄存器函数
unsigned char WK2124ReadReg(unsigned char port,unsigned char reg)
{
unsigned char rec_data;
SPI_CS_L(); //片选使能
SPI2_ReadWriteByte(0x40+((port-1)<<4)+reg);//写控制字节
rec_data=SPI2_ReadWriteByte(0);//接收返回的数据
SPI_CS_H(); //片选无效
return rec_data;
}
4. 写FIFO函数
void WK2124WriteFifo(unsigned char port,unsigned char *wbuf,unsigned int len)
{ unsigned char n;
SPI_CS_L(); // 片选有效
SPI2_ReadWriteByte(0x80+((port-1)<<4)); //写FIFO控制指令
for(n=0;n<len;n++)
{
SPI2_ReadWriteByte(*(wbuf+n));
}
SPI_CS_H(); //片选无效
}
port:为子串口的端口号(C0\C1), *wbuf:写入数据部分, len: 写入数据长度。
大部分和读写寄存器相同,主要控制字节不同,其次可以一次写入多个数据。
控制字节的变化主要是初始SPI的CMD为1000 0000,也就是0x80,其他一样。
5.读FIFO函数
void WK2124WriteFifo(unsigned char port,unsigned char *wbuf,unsigned int len)
{ unsigned char n;
SPI_CS_L(); // 片选有效
SPI2_ReadWriteByte(0x80+((port-1)<<4)); //写FIFO控制指令
for(n=0;n<len;n++)
{
SPI2_ReadWriteByte(*(wbuf+n));
}
SPI_CS_H(); //片选无效
}
6. WK2124初始化函数
首先使能子串口时钟
unsigned char gena,grst,gier,sier,scr;
//使能子串口时钟
gena=WK2124ReadReg(WK2124_GPORT,WK2124_GENA);
switch (port)
{
case 1://使能子串口1的时钟
gena|=WK2124_UT1EN;
WK2124WriteReg(WK2124_GPORT,WK2124_GENA,gena);
break;
case 2://使能子串口2的时钟
gena|=WK2124_UT2EN;
WK2124WriteReg(WK2124_GPORT,WK2124_GENA,gena);
break;
case 3://使能子串口3的时钟
gena|=WK2124_UT3EN;
WK2124WriteReg(WK2124_GPORT,WK2124_GENA,gena);
break;
case 4://使能子串口4的时钟
gena|=WK2124_UT4EN;
WK2124WriteReg(WK2124_GPORT,WK2124_GENA,gena);
break;
}
在头文件中对GENA全局寄存器进行了宏定义:
#define WK2124_UT4EN 0x08
#define WK2124_UT3EN 0x04
#define WK2124_UT2EN 0x02
#define WK2124_UT1EN 0x01
根据数据手册说明,低位的0,1,2,3位置1,就使能哪个子串口,如ut2使能就是0000 0010,也就是0x02。
接下来进行串口复位
//软件复位子串口
grst=WK2124ReadReg(WK2124_GPORT,WK2124_GRST);
switch (port)
{
case 1://软件复位子串口1
grst|=WK2124_UT1RST;
WK2124WriteReg(WK2124_GPORT,WK2124_GRST,grst);
break;
case 2://软件复位子串口2
grst|=WK2124_UT2RST;
WK2124WriteReg(WK2124_GPORT,WK2124_GRST,grst);
break;
case 3://软件复位子串口3
grst|=WK2124_UT3RST;
WK2124WriteReg(WK2124_GPORT,WK2124_GRST,grst);
break;
case 4://软件复位子串口4
grst|=WK2124_UT4RST;
WK2124WriteReg(WK2124_GPORT,WK2124_GRST,grst);
break;
}
与上面一样,第四位哪个置1,表示哪个子串口进行复位。
接下来使能串口中断
//使能子串口中断,包括子串口总中断和子串口内部的接收中断,和设置中断触点
gier=WK2124ReadReg(WK2124_GPORT,WK2124_GIER);
switch (port)
{
case 1://子串口1中断使能
gier|=WK2124_UT1IE;
WK2124WriteReg(WK2124_GPORT,WK2124_GIER,gier);
break;
case 2://子串口2中断使能
gier|=WK2124_UT2IE;
WK2124WriteReg(WK2124_GPORT,WK2124_GIER,gier);
break;
case 3://子串口3中断使能
gier|=WK2124_UT3IE;
WK2124WriteReg(WK2124_GPORT,WK2124_GIER,gier);
break;
case 4://子串口4中断使能
gier|=WK2124_UT4IE;
WK2124WriteReg(WK2124_GPORT,WK2124_GIER,gier);
break;
}
寄存器设置与上面一样。
//使能子串口接收触点中断和超时中断
sier=WK2124ReadReg(port,WK2124_SIER);
sier |= WK2124_RFTRIG_IEN;
WK2124WriteReg(port,WK2124_SIER,sier);
// 初始化FIFO和设置固定中断触点
WK2124WriteReg(port,WK2124_FCR,0XFF);
//设置任意中断触点,如果下面的设置有效,那么上面FCR寄存器中断的固定中断触点将失效
WK2124WriteReg(port,WK2124_SPAGE,1);//切换到page1
WK2124WriteReg(port,WK2124_RFTL,0X08);//设置接收触点8个字节
WK2124WriteReg(port,WK2124_TFTL,0X10);//设置发送触点为16个字节
WK2124WriteReg(port,WK2124_SPAGE,0);//切换到page0
//使能子串口的发送和接收使能
scr=WK2124ReadReg(port,WK2124_SCR);
scr|=WK2124_TXEN|WK2124_RXEN;
WK2124WriteReg(port,WK2124_SCR,scr);
剩下的这些触点,页面设置,使能都一样,根据数据手册进行设置即可。
7.wk2124关闭及复位函数
void WK2124Close(unsigned char port)
{
unsigned char gena,grst;
//复位子串口
grst=WK2124ReadReg(WK2124_GPORT,WK2124_GRST);
switch (port)
{
case 1://软件复位子串口1
grst|=WK2124_UT1RST;
WK2124WriteReg(WK2124_GPORT,WK2124_GRST,grst);
break;
case 2://软件复位子串口2
grst|=WK2124_UT2RST;
WK2124WriteReg(WK2124_GPORT,WK2124_GRST,grst);
break;
case 3://软件复位子串口3
grst|=WK2124_UT3RST;
WK2124WriteReg(WK2124_GPORT,WK2124_GRST,grst);
break;
case 4://软件复位子串口4
grst|=WK2124_UT4RST;
WK2124WriteReg(WK2124_GPORT,WK2124_GRST,grst);
break;
}
//关闭子串口时钟
gena=WK2124ReadReg(WK2124_GPORT,WK2124_GENA);
switch (port)
{
case 1://使能子串口1的时钟
gena&=~WK2124_UT1EN;
WK2124WriteReg(WK2124_GPORT,WK2124_GENA,gena);
break;
case 2://使能子串口2的时钟
gena&=~WK2124_UT2EN;
WK2124WriteReg(WK2124_GPORT,WK2124_GENA,gena);
break;
case 3://使能子串口3的时钟
gena&=~WK2124_UT3EN;
WK2124WriteReg(WK2124_GPORT,WK2124_GENA,gena);
break;
case 4://使能子串口4的时钟
gena&=~WK2124_UT4EN;
WK2124WriteReg(WK2124_GPORT,WK2124_GENA,gena);
break;
}
}
先对GRST寄存器进行操作使其复位,然后利用~运算符关闭串口时钟,同时可以减少参数量。
8. 波特率设置函数
void WK2124SetBaud(unsigned char port,int baud)
{
unsigned char baud1,baud0,pres,scr;
//如下波特率相应的寄存器值,是在外部时钟为11.0592的情况下计算所得,如果使用其他晶振,需要重新计算
switch (baud)
{
case 600:
baud1=0x4;
baud0=0x7f;
pres=0;
break;
case 1200:
baud1=0x2;
baud0=0x3F;
pres=0;
break;
case 2400:
baud1=0x1;
baud0=0x1f;
pres=0;
break;
case 4800:
baud1=0x00;
baud0=0x8f;
pres=0;
break;
case 9600:
baud1=0x00;
baud0=0x47;
pres=0;
break;
case 19200:
baud1=0x00;
baud0=0x23;
pres=0;
break;
case 38400:
baud1=0x00;
baud0=0x11;
pres=0;
break;
case 76800:
baud1=0x00;
baud0=0x08;
pres=0;
break;
case 1800:
baud1=0x01;
baud0=0x7f;
pres=0;
break;
case 3600:
baud1=0x00;
baud0=0xbf;
pres=0;
break;
case 7200:
baud1=0x00;
baud0=0x5f;
pres=0;
break;
case 14400:
baud1=0x00;
baud0=0x2f;
pres=0;
break;
case 28800:
baud1=0x00;
baud0=0x17;
pres=0;
break;
case 57600:
baud1=0x00;
baud0=0x0b;
pres=0;
break;
case 115200:
baud1=0x00;
baud0=0x05;
pres=0;
break;
case 230400:
baud1=0x00;
baud0=0x02;
pres=0;
break;
default:
baud1=0x00;
baud0=0x00;
pres=0;
}
//关掉子串口收发使能
scr=WK2124ReadReg(port,WK2124_SCR);
WK2124WriteReg(port,WK2124_SCR,0);
//设置波特率相关寄存器
WK2124WriteReg(port,WK2124_SPAGE,1);//切换到page1
WK2124WriteReg(port,WK2124_BAUD1,baud1);
WK2124WriteReg(port,WK2124_BAUD0,baud0);
WK2124WriteReg(port,WK2124_PRES,pres);
WK2124WriteReg(port,WK2124_SPAGE,0);//切换到page0
//使能子串口收发使能
WK2124WriteReg(port,WK2124_SCR,scr);
}
计算公式:
Reg整数部分减一并换算成16进制写入{BAUD1,BAUA0};如果还有小数部分,则小数部分*16,然后4舍5入后取整写入PRES。如果没有小数部分,只需把整数部分写入 { BAUD1,BAUA0},PRES写入0即可。
例子:
根据具体晶振去设置即可。
8.发送字节函数
unsigned int WK2124SendBuf(unsigned char port,unsigned char *sendbuf,unsigned int len)
{
unsigned int ret,tfcnt,sendlen;
unsigned char fsr;
fsr=WK2124ReadReg(port,WK2124_FSR);
if(~fsr&WK2124_TFULL )//子串口发送FIFO未满
{
tfcnt=WK2124ReadReg(port,WK2124_TFCNT);//读子串口发送fifo中数据个数
sendlen=256-tfcnt;//FIFO能写入的最多字节数
if(sendlen<len)
{
ret=sendlen;
WK2124WriteFifo(port,sendbuf,sendlen);
}
else
{
WK2124WriteFifo(port,sendbuf,len);
ret=len;
}
}
return ret;
}
9. 接收数据函数
unsigned int WK2124GetBuf(unsigned char port,unsigned char *getbuf)
{
unsigned int ret=0,rfcnt;
unsigned char fsr;
fsr=WK2124ReadReg(port,WK2124_FSR);
if(fsr&WK2124_RDAT )//子串口发送FIFO未满
{
rfcnt=WK2124ReadReg(port,WK2124_RFCNT);//读子串口发送fifo中数据个数
if(rfcnt==0)//当RFCNT寄存器为0的时候,有两种情况,可能是256或者是0,这个时候通过FSR来判断,如果FSR显示接收FIFO不为空,就为256个字节
{rfcnt=256;}
WK2124ReadFifo(port,getbuf,rfcnt);
ret=rfcnt;
}
return ret;
}
最后有一点,UART端口输出为TTL电平,如果需要232或485电平的,需要加相应的电平转换芯片。
这个驱动的大部分代码可以复用,F103和F4的都写了,源码连带数据手册,原理图,linux驱动什么的一会一起打包上传,没有积分的可以留下邮箱,我看见了就会发的,如果有什么疑问或者我哪里写的不对可以留言一起沟通。