本帖最后由 gaoyang9992006 于 2024-6-12 08:44 编辑
本项目使用到的资源有:GPIO、I2C、SPI。这里I2C与SPI全部采用硬件方式实现。GPIO实现按键功能,以及简单的LED测试功能。
根据手册可知电路如下:
LED是高电平点亮;
按键是板载了上拉电阻,因此配置位输入模式即可使用,按键可以采用中断模式触发,即下降沿触发。
__RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStructure;
//LED引脚配置
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pins = LED1_GPIO_PINS;
GPIO_InitStructure.Speed= GPIO_SPEED_HIGH;
GPIO_Init(LED1_GPIO_PORT,&GPIO_InitStructure);
GPIO_WritePin(LED1_GPIO_PORT,LED1_GPIO_PINS,GPIO_Pin_SET);
GPIO_InitStructure.Pins = LED2_GPIO_PINS;
GPIO_Init(LED2_GPIO_PORT,&GPIO_InitStructure);
GPIO_WritePin(LED2_GPIO_PORT,LED2_GPIO_PINS,GPIO_Pin_SET);
//按键引脚配置
__RCC_GPIOA_CLK_ENABLE();
GPIO_InitStructure.IT = GPIO_IT_FALLING;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.Pins = S1_GPIO_PINS;
GPIO_Init(S1_GPIO_PORT,&GPIO_InitStructure);
GPIO_InitStructure.Pins = S2_GPIO_PINS;
GPIO_Init(S1_GPIO_PORT,&GPIO_InitStructure);
中断处理函数中对全局变量进行修改
void GPIOA_IRQHandler(void)
{
/* USER CODE BEGIN */
if(REGBITS_GET(CW_GPIOA->ISR, GPIOx_ISR_PIN1_Msk) > 0)
{
Key1_flag = 1;
Key2_flag = 0;
//清除CW_GPIO中断标志
GPIOA_INTFLAG_CLR(GPIOx_ICR_PIN1_Msk);
}
if(REGBITS_GET(CW_GPIOA->ISR, GPIOx_ISR_PIN2_Msk) > 0)
{
Key1_flag = 0;
Key2_flag = 1;
//清除CW_GPIO中断标志
GPIOA_INTFLAG_CLR(GPIOx_ICR_PIN2_Msk);
}
/* USER CODE END */
}
接下来是通过I2C操作收音机芯片KT0935
I2C总线是通过上拉电阻稳定电平的,因此应设置为开漏输出模式,同时I2C接口作为复用接口,应配置复用选择寄存器。
KT_I2C_AFSCL;
KT_I2C_AFSDA;
GPIO_InitTypeDef GPIO_InitStructure;
//I2C SCL SDA 复用
GPIO_InitStructure.Pins = KT_I2C_SCL_GPIO_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_Init(KT_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pins = KT_I2C_SDA_GPIO_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_Init(KT_I2C_SDA_GPIO_PORT, &GPIO_InitStructure);
//I2C
#define KT_I2C CW_I2C1
#define KT_I2C_ClkENx __RCC_I2C1_CLK_ENABLE()
#define KT_I2C_DeInit I2C1_DeInit()
#define KT_I2C_AFSCL PB06_AFx_I2C1SCL()
#define KT_I2C_AFSDA PB07_AFx_I2C1SDA()
#define KT_I2C_SCL_CLK_ENABLE() __RCC_GPIOB_CLK_ENABLE()
#define KT_I2C_SDA_CLK_ENABLE() __RCC_GPIOB_CLK_ENABLE()
#define KT_I2C_SCL_GPIO_PORT CW_GPIOB
#define KT_I2C_SCL_GPIO_PIN GPIO_PIN_6
#define KT_I2C_SDA_GPIO_PORT CW_GPIOB
#define KT_I2C_SDA_GPIO_PIN GPIO_PIN_7
KT_I2C_ClkENx;
//I2C初始化
I2C_InitStruct.I2C_BaudEn = ENABLE;
I2C_InitStruct.I2C_Baud = 0x05;//1MHz=(48000000/(8*(5+1))
I2C_InitStruct.I2C_FLT = DISABLE;
I2C_InitStruct.I2C_AA = DISABLE;
KT_I2C_DeInit;
I2C_Master_Init(KT_I2C,&I2C_InitStruct);//初始化模块
I2C_Cmd(KT_I2C,ENABLE); //模块使能
I2C配置总结:I2C作为IO口,首先要进行基于IO口模式的配置,即完成IO时钟启用与模式设置。然后才是I2C功能的配置。
I2C功能配置首先应启用I2C时钟,其次设置I2C的各项参数,并使能模块。
接下来就是利用I2C库函数实现KT读写函数的映射
uint8_t KT_Byte_Read(uint8_t u8Addr)
{
uint8_t dat;
HAL_I2C_Mem_Read(KT_I2C,0x6A,u8Addr,&dat,1);
return dat;
}
void KT_Byte_Write(uint8_t reg,uint8_t dat)
{
HAL_I2C_Mem_Write(KT_I2C,0x6A, reg, &dat, 1);
}
这里的库函数是我改写的CW的库函数,CW库函数的读写没有修改从机地址的参数,不是很方便使用。我对其进行了修改,并重新命名,这样保证原有的库函数仍可以使用:
/**
******************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url] 主机接收函数,适用读取EEPROM方式采用随机读取方式:需要先写入EEPROM的地址
*
* @param I2Cx : I2C1 I2C2
* @param DevAddress : 从机地址,这里为8位的写入地址,也可传递7位地址左移一位后的数据
* @param u8Addr : 读取EEPROM内存器地址
* @param pu8Data : 读取到的EEPROM的数据地址
* @param u32Len : 读取到的EEPROM的数据长度
*
******************************************************************************/
void HAL_I2C_Mem_Read(I2C_TypeDef *I2Cx, uint8_t DevAddress, uint8_t u8Addr, uint8_t *pu8Data, uint32_t u32Len)
{
uint8_t u8i = 0, u8State;
I2C_GenerateSTART(I2Cx, ENABLE);
while (1)
{
while (0 == I2C_GetIrq(I2Cx))
{}
u8State = I2C_GetState(I2Cx);
switch (u8State)
{
case 0x08: //发送完START信号
I2C_GenerateSTART(I2Cx, DISABLE);
I2C_Send7bitAddress(I2Cx, DevAddress, 0X00); //发送从机地址
break;
case 0x18: //发送完SLA+W/R字节
I2C_SendData(I2Cx, u8Addr);
break;
case 0x28: //发送完1字节数据:发送EEPROM中memory地址也会产生,发送后面的数据也会产生
I2Cx->CR_f.STA = 1; //set start //发送重复START信号,START生成函数改写后,会导致0X10状态被略过,故此处不调用函数
break;
case 0x10: //发送完重复起始信号
I2C_GenerateSTART(I2Cx, DISABLE);
I2C_Send7bitAddress(I2Cx, DevAddress, 0X01);
break;
case 0x40: //发送完SLA+R信号,开始接收数据
if (u32Len > 1)
{
I2C_AcknowledgeConfig(I2Cx, ENABLE);
}
break;
case 0x50: //接收完一字节数据,在接收最后1字节数据之前设置AA=0;
pu8Data[u8i++] = I2C_ReceiveData(I2Cx);
if (u8i == u32Len - 1)
{
I2C_AcknowledgeConfig(I2Cx, DISABLE);
}
break;
case 0x58: //接收到一个数据字节,且NACK已回复
pu8Data[u8i++] = I2C_ReceiveData(I2Cx);
I2C_GenerateSTOP(I2Cx, ENABLE);
break;
case 0x38: //主机在发送 SLA+W 阶段或者发送数据阶段丢失仲裁 或者 主机在发送 SLA+R 阶段或者回应 NACK 阶段丢失仲裁
I2C_GenerateSTART(I2Cx, ENABLE);
break;
case 0x48: //发送完SLA+R后从机返回NACK
I2C_GenerateSTOP(I2Cx, ENABLE);
I2C_GenerateSTART(I2Cx, ENABLE);
break;
default:
I2C_GenerateSTART(I2Cx, ENABLE);//其他错误状态,重新发送起始条件
break;
}
I2C_ClearIrq(I2Cx);
if (u8i == u32Len)
{
break;
}
}
}
/**
******************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url] 主机发送函数,适用读取EEPROM方式采用随机读取方式:需要先写入EEPROM的地址
*
* @param I2Cx : I2C1 I2C2
* @param DevAddress : 从机地址,这里为8位的写入地址
* @param u8Addr : 写入EEPROM内存器地址
* @param pu8Data : 写入数据存放地址
* @param u32Len : 写入数据长度
*
******************************************************************************/
void HAL_I2C_Mem_Write(I2C_TypeDef *I2Cx, uint8_t DevAddress, uint8_t u8Addr, uint8_t *pu8Data, uint32_t u32Len)
{
uint8_t u8i = 0, u8State;
I2C_GenerateSTART(I2Cx, ENABLE);
while (1)
{
while (0 == I2C_GetIrq(I2Cx))
{;}
u8State = I2C_GetState(I2Cx);
switch (u8State)
{
case 0x08: //发送完START信号
I2C_GenerateSTART(I2Cx, DISABLE);
I2C_Send7bitAddress(I2Cx, DevAddress, 0X00); //从设备地址发送
break;
case 0x18: //发送完SLA+W信号,ACK已收到
I2C_SendData(I2Cx, u8Addr); //从设备内存地址发送
break;
case 0x28: //发送完1字节数据:发送EEPROM中memory地址也会产生,发送后面的数据也会产生
I2C_SendData(I2Cx, pu8Data[u8i++]);
break;
case 0x20: //发送完SLA+W后从机返回NACK
case 0x38: //主机在发送 SLA+W 阶段或者发送数据阶段丢失仲裁 或者 主机在发送 SLA+R 阶段或者回应 NACK 阶段丢失仲裁
I2C_GenerateSTART(I2Cx, ENABLE);
break;
case 0x30: //发送完一个数据字节后从机返回NACK
I2C_GenerateSTOP(I2Cx, ENABLE);
break;
default:
break;
}
if (u8i > u32Len)
{
I2C_GenerateSTOP(I2Cx, ENABLE);//此顺序不能调换,出停止条件
I2C_ClearIrq(I2Cx);
break;
}
I2C_ClearIrq(I2Cx);
}
}
好了,这是底层的设置与映射都完成了,接下来分享ST7735S使用的SPI接口的配置方式。
SPI的配置应遵循以下模式选择
这里我使用SPI2
对应宏
#define ST7735_RST_Pin GPIO_PIN_10
#define ST7735_RST_GPIO_Port CW_GPIOA
#define ST7735_DC_Pin GPIO_PIN_11
#define ST7735_DC_GPIO_Port CW_GPIOA
#define ST7735_CS_Pin GPIO_PIN_15
#define ST7735_CS_GPIO_Port CW_GPIOB
#define GPIO_PIN_RESET GPIO_Pin_RESET
#define GPIO_PIN_SET GPIO_Pin_SET
#define HAL_GPIO_WritePin GPIO_WritePin
#define HAL_Delay delay10us
#define ST7735_SPI CW_SPI2
#define ST7735_SCK_Pin GPIO_PIN_13
#define ST7735_SCK_GPIO_Port CW_GPIOB
#define ST7735_MOSI_Pin GPIO_PIN_15
#define ST7735_MOSI_GPIO_Port CW_GPIOB
遵循I2C的配置思路,SPI的配置是相似的,先启用GPIO时钟,对IO模式进行配置,再对SPI应用接口配置。
void SPI_Configuration(void)
{
__RCC_GPIOA_CLK_ENABLE();
__RCC_GPIOB_CLK_ENABLE();
PB13_AFx_SPI2SCK();
PB15_AFx_SPI2MOSI();
GPIO_InitTypeDef GPIO_InitStructure;
//推挽输出
GPIO_InitStructure.Pins = ST7735_CS_Pin;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_Init(ST7735_CS_GPIO_Port, &GPIO_InitStructure);
GPIO_InitStructure.Pins = ST7735_DC_Pin;
GPIO_Init(ST7735_DC_GPIO_Port, &GPIO_InitStructure);
GPIO_InitStructure.Pins = ST7735_RST_Pin;
GPIO_Init(ST7735_RST_GPIO_Port, &GPIO_InitStructure);
GPIO_InitStructure.Pins = ST7735_SCK_Pin;
GPIO_Init(ST7735_SCK_GPIO_Port, &GPIO_InitStructure);
GPIO_InitStructure.Pins = ST7735_MOSI_Pin;
GPIO_Init(ST7735_MOSI_GPIO_Port, &GPIO_InitStructure);
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_TxOnly; //单工只发
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 帧数据长度为8bit
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // 时钟空闲电平为高
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 第2个边沿采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 片选信号由SSI寄存器控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 波特率为PCLK的4分频
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 最高有效位 MSB 收发在前
SPI_InitStructure.SPI_Speed = SPI_Speed_Low; // 低速SPI
SPI_Init(ST7735_SPI, &SPI_InitStructure);
SPI_Cmd(ST7735_SPI, ENABLE);
}
同样的也需要时钟配置
RCC_APBPeriphClk_Enable1(RCC_APB1_PERIPH_SPI2, ENABLE);
不过相关设置已经在另外一个初始化函数设置了。
接下来即可使用SPI相关函数实现ST7735S液晶屏的数据发送了,发送函数映射如下
void ST7735_SPI_SendByte(uint8_t byte)
{
while(SPI_GetFlagStatus(ST7735_SPI, SPI_FLAG_TXE) == RESET);
SPI_SendData(ST7735_SPI, byte);
}
对于多个字节发送可以如下方式实现
for(int i=0;i<data_size;i++) ST7735_SPI_SendByte(*(data+i));
接下来即可通过以上相关的底层函数实现简单的按键选择电台的功能了。
由于开发板只有2个按键,这里只作为简单的FM收音机使用,两个按键用于上下切台。如果集成一下,做小,增加按钮,还可以实现全波段的收听功能。
void dis_fr(void)
{
char fr[8];
uint16_t fr_temp;
ST7735_DrawRectangle(0,100,128,10,ST7735_BLACK);
ST7735_DrawString(0,100,"FM:",ST7735_MAGENTA,ST7735_BLACK,&Font_7x10);
fr_temp=current_fre;
fr[0]='0'+(fr_temp/10000); fr_temp=fr_temp%10000;
fr[1]='0'+(fr_temp/1000); fr_temp=fr_temp%1000;
fr[2]='0'+(fr_temp/100); fr_temp=fr_temp%100;
fr[3]='.';
fr[4]='0'+(fr_temp/10); fr_temp=fr_temp%10;
fr[5]='0'+fr_temp;
fr[6]='M';
fr[7]='H';
ST7735_DrawString(25,100,fr,ST7735_MAGENTA,ST7735_BLACK,&Font_7x10);
}
int32_t main(void)
{
RCC_Configuration();
GPIO_Configuration();
NVIC_Configuration();
UART_Configuration();
SPI_Configuration();
KT_I2C_Init();
printf("Hello ! \n");
ST7735_Init();
ST7735_DrawRectangle(0,0,10,5,ST7735_YELLOW);
ST7735_DrawRectangle(15,15,10,20,ST7735_BLUE);
ST7735_DrawRectangle(30,30,10,10,ST7735_RED);
ST7735_DrawString(0,50,"Hello",ST7735_GREEN,ST7735_BLACK,&Font_11x18);
ST7735_DrawString(0,75,"Hello",ST7735_MAGENTA,ST7735_BLACK,&Font_7x10);
printf("KT(00):0x%02x\n",KT_Byte_Read(0x00));
printf("KT(01):0x%02x\n",KT_Byte_Read(0x01));
printf("KT(02):%c\n", KT_Byte_Read(0x02));
printf("KT(03):%c\n", KT_Byte_Read(0x03));
if((KT_AMFM_InitMin)()==1)
{
printf("Power ON\n");
}
KT_AMFM_SetMode(FM_MODE);
KT_FM_Tune(current_fre);
SW_Enable(FALSE);
KT_AMFM_VolumeSet(20);
KT_FM_Deltn(0);
dis_fr();//在液晶屏显示频率
while (1)
{
if(Key1_flag)
{
GPIO_TogglePin(LED1_GPIO_PORT,LED1_GPIO_PINS);
delay1ms(1000);
current_fre=KT_FM_GetFreq();
KT_AMFM_SeekFromCurrentCh(1,¤t_fre);
KT_FM_Tune(current_fre);
KT_AMFM_VolumeSet(20);
Key1_flag=0;
delay1ms(50);
printf("FRE:%d\n",current_fre);
printf("SNR:%d\n",KT_FM_GetSNR());
printf("AFC:%d\n",KT_FM_GetAFC());
int8_t rssi;
KT_FM_ReadRSSI( &rssi );
printf("RSSI:%d\n",rssi);
dis_fr();
}
if(Key2_flag)
{
GPIO_TogglePin(LED2_GPIO_PORT,LED2_GPIO_PINS);
delay1ms(1000);
current_fre=KT_FM_GetFreq();
KT_AMFM_SeekFromCurrentCh(0,¤t_fre);
KT_FM_Tune(current_fre);
KT_AMFM_VolumeSet(20);
Key2_flag=0;
delay1ms(50);
printf("FRE:%d\n",current_fre);
printf("SNR:%d\n",KT_FM_GetSNR());
printf("AFC:%d\n",KT_FM_GetAFC());
int8_t rssi;
KT_FM_ReadRSSI( &rssi );
printf("RSSI:%d\n",rssi);
dis_fr();
}
}
}
|
|