这两天准备学习学习沁恒微CH32V307VCT6的SPI外设。以前用过ARM架构单片机的SPI,虽然架构不同,但相信在用法上和开发上应该区别不大。根据应用手册SPI章节,需要使用端口复用机能。在半双工的例程中,使用的GPIO口为A5和A7,在应用手册119页有如下描述:
也就是说,例程中使用PA5作为SPI的SCK用,输出时钟;PA7作为MOSI使用,输出数据。例程中SPI1的初始化处理如下所示(只保留主机模式下的代码,从机模式的已经删除):
void SPI_1Lines_HalfDuplex_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure={0};
SPI_InitTypeDef SPI_InitStructure={0};
NVIC_InitTypeDef NVIC_InitStructure={0};
// 配置时钟总线
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE );
// 设置GPIO口,使用PA5(SCK),PA7(MOSI)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
GPIO_Init( GPIOA, &GPIO_InitStructure );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
GPIO_Init( GPIOA, &GPIO_InitStructure );
// 向外设发数据
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
// 数据位数(为了方便测试,程序中我改成8位)
SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // 时钟平时为低
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 第一个脉冲沿数据有效
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 片选使用软件模式
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 高位数据在前
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init( SPI1, &SPI_InitStructure );
// 配置SPI中断
NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE ;
NVIC_Init(&NVIC_InitStructure);
SPI_Cmd( SPI1, *ENABLE* );
}
主程序:
int main(void) {
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
SystemCoreClockUpdate();
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\\r\\n",SystemCoreClock);
printf( "ChipID:%08x\\r\\n", DBGMCU_GetCHIPID() );
SPI_1Lines_HalfDuplex_Init();
printf("HOST Mode\\r\\n");
Delay_Ms(2000);
SPI_I2S_ITConfig( SPI1, SPI_I2S_IT_TXE , ENABLE );
while(1) {
while( Txval<18 );
while( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET ) {
if(SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_BSY ) == RESET) {
SPI_Cmd( SPI1, DISABLE );
while(1);
}
}
}
}
中断处理:
void SPI1_IRQHandler ( void ) {
if ( SPI_I2S_GetITStatus( SPI1, SPI_I2S_IT_TXE ) != RESET ) {
SPI_I2S_SendData( SPI1, TxData[Txval++] );
if ( Txval == 18 ) {
SPI_I2S_ITConfig( SPI1, SPI_I2S_IT_TXE , DISABLE );
}
}
}
以上是例程中,主机模式下的主要代码部分,我不是很懂,注释是根据含有的英文缩写猜测的。
SPI1的中断状态寄存器:
SPI_I2S_FLAG_TXE=0x0002,对应TXE-发送缓冲区空标志位,执行SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ),就是为了获取发送缓冲区空标志位。如果是RESET值,表示非空,前此数据爱没有发送完。
SPI_I2S_FLAG_BSY=0x0080,对应BSY-忙标志位,执行SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_BSY )是用来判断SPI是否处于忙的状态。如果是RESET值,表示不在通讯状态,可以使用。
为了验证数据是否发送出去,我用74HC164以级联方式做了一个接收用的模块,主要是为了通过LED观察传出来的数据是什么样。
测试时,先直接看运行结果,看看接收模块的LED显示是否有变化,以证明接线又没有错误。
以下是修改后的主程序:
u32 i=0;
// 初始化中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
SystemCoreClockUpdate();
Delay_Init();
// 初始化串口
USART_Printf_Init(115200);
printf ("SystemClk:%d\\r\\n",SystemCoreClock);
printf ( "ChipID:%08x\\r\\n", DBGMCU_GetCHIPID() );
// 初始化SPI外设
SPI_1Lines_HalfDuplex_Init();
Delay_Ms(2000);
SPI_I2S_ITConfig( SPI1, SPI_I2S_IT_TXE , ENABLE );
GPIO_LED_INIT();
while (1)
{
while ( Txval<3 );
while (1) {
i++;
**printf** ("i=%d\\r\\n", i);
TxData[0]=255-((i & 0xff0000)>>16);
TxData[1]=255-((i & 0x00ff00)>>8);
TxData[2]=255-(i & 0x0000ff);
// 开始传输
GPIO_WriteBit(GPIOA, GPIO_Pin_1, 0);
// 启动传输
Txval=0;
SPI_I2S_ITConfig( SPI1, SPI_I2S_IT_TXE , ENABLE );
// 等待数据都发送完
while ( Txval<3 );
// 开始传输
GPIO_WriteBit(GPIOA, GPIO_Pin_1, 1);
Delay_Ms(300);
}
}
}
处理中通过周期改变输出到SPI1外设的数据,在那个扩展板上可以看到二进制加法产生LED规律亮灭。
调试过程中,发现一个很奇怪的现象,如果扩展板使用5V电源,LED现实的也有规律,但已经不是二进制加法。可能是由于接口电平产生的问题。
还有一点,现在程序使用了中断方式,来发送数据。如果不使用中断,而在程序中直接直接发送,可以使用下面的处理:
// 以非中断方式发送 正常 begin
while (1) {
i++;
printf ("i=%d\\r\\n", i);
TxData[0]=255-((i & 0xff0000)>>16);
TxData[1]=255-((i & 0x00ff00)>>8);
TxData[2]=255-(i & 0x0000ff);
GPIO_WriteBit(GPIOA, GPIO_Pin_1, 0);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET ) ; // 等空
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) != RESET ) ; // 等不忙
SPI_I2S_SendData( SPI1, TxData[0]);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET ) ; // 等空
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) != RESET ) ; // 等不忙
SPI_I2S_SendData( SPI1, TxData[1]);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == *RESET* ) ; // 等空
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) != *RESET* ) ; // 等不忙
SPI_I2S_SendData( SPI1, TxData[2]);
GPIO_WriteBit(GPIOA, GPIO_Pin_1, 1);
Delay_Ms(1000);
}
// 以非中断方式发送 正常 end
刚开始用eclipse编写、编译、下载硬件开发用的C语言程序,还不熟,一点点摸索中。开始总是出一些编译错误,代码本身肯定没有任何语法错误,也不明白是啥情况
。害得我一遍又一遍重新导入程序。debug模式也启动不起来。后来就索性下载,靠log输出来分析原因了。