[CW32F030系列]

【CW32F030CxTx StartKit开发板】+简易收音机+I2C+SPI

[复制链接]
223|9
手机看帖
扫描二维码
随时随地手机跟帖
gaoyang9992006|  楼主 | 2024-6-11 18:34 | 显示全部楼层 |阅读模式
本帖最后由 gaoyang9992006 于 2024-6-12 08:44 编辑

本项目使用到的资源有:GPIO、I2C、SPI。这里I2C与SPI全部采用硬件方式实现。GPIO实现按键功能,以及简单的LED测试功能。
根据手册可知电路如下:
001.png
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的配置应遵循以下模式选择
002.png
这里我使用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();
                        }

    }
}





使用特权

评论回复
gaoyang9992006|  楼主 | 2024-6-11 18:44 | 显示全部楼层
补充:开发环境的KEIL MDK。下载官网的pack安装即可使用。同时可以下载官网的库函数包。
开发板特性:开发板比较简单好用,上面集成了最小系统,并设置了2个LED灯和2个可编程按键。另外板子上有独立的I2C接口与SPI接口的存储芯片,需要使用跳线接到排针上使用。在拿到板子后,我对这些芯片进行了测试读写操作,很不错。相关程序在官网的资源包可以看到。

使用特权

评论回复
gaoyang9992006|  楼主 | 2024-6-12 08:44 | 显示全部楼层
修改了一下标题方便大家检索相关外设的学习。

使用特权

评论回复
AdaMaYun| | 2024-6-13 19:58 | 显示全部楼层
按键是默认的高电平,灯高电平点亮

使用特权

评论回复
gaoyang9992006|  楼主 | 2024-6-14 10:42 | 显示全部楼层
AdaMaYun 发表于 2024-6-13 19:58
按键是默认的高电平,灯高电平点亮

灯的初始化电平可以设置的。配置为低电平就行了。

使用特权

评论回复
AdaMaYun 发表于 2024-6-13 19:58
按键是默认的高电平,灯高电平点亮

其实如果是自定义IO的话可以进行正反接的

使用特权

评论回复
643757107| | 2024-6-20 21:45 | 显示全部楼层
应该也可以使用内置上拉电阻吧

使用特权

评论回复
643757107| | 2024-6-20 21:45 | 显示全部楼层
一般我自己做设计,如果有内置上拉电阻,都是使用内置的,就不用考虑外部上拉电阻了。

使用特权

评论回复
lidi911| | 2024-6-21 08:00 | 显示全部楼层
专用收音机芯片集成度也很高了

使用特权

评论回复
OKAKAKO| | 2024-6-21 20:49 | 显示全部楼层
收音机是用的什么射频模块?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:西安公路研究院南京院
简介:主要工作从事监控网络与通信网络设计,以及从事基于嵌入式的通信与控制设备研发。擅长单片机嵌入式系统物联网设备开发,音频功放电路开发。

1905

主题

15675

帖子

200

粉丝