目的 通過CSM32RV20開發平臺,使用硬件SPI接口與Si24R1進行通信,通信成功后,通過串口打印數據。 實現過程: 1.中斷向量表和系統時鐘初始化 在IDE里新建項目后,都會包含CLIC_Init()和System_Clock_Init()兩個函數。中斷向量表初始化,系統中斷初始化,用戶無需關心。系統時鐘初始化函數中,可以方便的選中時鐘源、時鐘分頻系數、外設時鐘使能和RC頻率選擇。 int main(void){ ///----System Init --------------------------------------------------------------------------------------------- CLIC_Init();//中斷向量表初始化 System_Clock_Init();//系統時鐘初始化void System_Clock_Init(void)//系統時鐘初始化{ //時鐘源開關 CMU->SRC_EN = 1<<1 //RCOSC bit[1]:0-off, 1-on |1<<0; //crystal bit[0]:0-off, 1-on //外設和內核時鐘來源選擇 CMU->CLK_SEL = 1<<2 //phripheral bit[3:2]:0-RCOSC, 1-crystal, 2-LSI(3K), 3-reserved |1<<0; //cpu bit[1:0]:0-RCOSC, 1-crystal, 2-LSI(3K), 3-reserved //設置時鐘分頻系數 CMU->CLK_DIV = 0<<10 //RTC bit[14:10]:0-2, 1-2, 2-2, 3-2, 4-4, 5-4, 6-6, 7-6 ...... |0<<5 //phripheral bit[9:5]:0-1, 1-1, 2-2, 3-3, 4-4, 5-5,......31-31 |0<<0; //CPU bit[9:5]:0-1, 1-1, 2-2, 3-3, 4-4, 5-5,......31-31 //外設時鐘使能 CMU->PER_EN = 1; //bit[0]:0-off, 1-on //RC頻率選擇 CMU_RC_DEFAULT->RC_DEFAULT = 0; //bit[0]:0-16MHz, 1-32MHz}2.外設初始化 2.1外設初始化(串口) 串口初始化:選中UART1,UART1即可以用作燒錄使用(開發板串口默認使用UART1),又可以調用打印,方便數據輸出。UART1:PA6:TX1,復用配置AF0(默認)。PA5:RX1,復用配置AF0(默認)。 UART_Init_case1(UART1); //串口初始化由于開發板上使用的晶振為32MHz,那么我們設置0x0116<<8串口波特率就是115200 UARTx->CTRL = 0<<25 //接收中斷使能: 0-off,1-on |0<<24 //發送中斷使能:0-off,1-on |0x0116<<8 //波特率(對應16M時鐘): //0x1a0b-2400,0x0683-9600,0x0341-19200,0x0116-57600,0x008b-115200 //0x0045-230400,0x0023-460800,0x0011-921600,0x000d-1128800 |1<<6 //模式選擇:0-模式0,1-模式1,2/3-模式2 |0<<5 //多處理器使能 |1<<4 //接收使能 |0<<3 //發送數據bit8 |0<<2; //接收數據bit82.2外設初始化(SPI) SPI初始化,選中非中斷模式。Si24R1采用四線制SPI,與MCU連接共6根線。Si24R1芯片引腳介紹(MOSI和MISO直接與MCU的硬件SPI對應連接即可,即MOSI與SPI1_MOSI連接)。CE,芯片開啟信號,激活 RX 或 TX 模式。CSN,SPI 片選信號。SCK,SPI 時鐘信號。MOSI,SPI 輸入信號。MISO,SPI輸出信號。IRQ,可屏蔽中斷信號(可以通過0x00寄存器CONFIG配置屏蔽),低電平有效。 SPI_Init_case1(SPI1); //SPI初始化,非中斷模式 CSM32RV20,硬件SPI1引腳說明:PA2-SPI1_SCK,PA3-SPI1_MISO,PA4_SPI1_MOSI. if(SPIx==SPI1) { //用戶自選CSN,軟件操作片選信號 //配置SCK GPIO_MODE_Init(GPIOA, PIN2, GPIO_MODE_AF); //PA2復用模式 GPIO_AF_Init(GPIOA, PIN2, GPIO_AF0); //PA2復用到SPI1_SCK //配置MISO GPIO_MODE_Init(GPIOA, PIN3, GPIO_MODE_AF); //PA3復用模式 GPIO_AF_Init(GPIOA, PIN3, GPIO_AF0); //PA3復用到SPI1_MISO //配置MOSI GPIO_MODE_Init(GPIOA, PIN4, GPIO_MODE_AF); //PA4復用模式 GPIO_AF_Init(GPIOA, PIN4, GPIO_AF0); //PA4復用到SPI1_MOSI }根據Si24R1的SPI協議,CPHA時鐘相位和CPOL的時鐘極性(SCK空閑時狀態為低電平,上升沿采樣下降沿輸出),選中SPI模式0。SPI速率選擇為8分頻-4MHz。使用軟件CSN控制 SPIx->CTRL = 0x0<<8 //中斷使能:0-關閉,1-開啟 |0x0<<7 //時鐘極性:0-低電平,1-高電平 |0x0<<6 //時鐘相位:0-前沿采樣,后沿輸出,1-前沿輸出,后沿采樣, |0x1<<4 //SPI使能:0-關閉,1-使能 |0x3; //時鐘分頻:0-2分頻,1-2分頻,2-2分頻,3-8分頻,4-16分頻,5-32分頻,6-64分頻,其他:64分頻2.3外設初始化(GPIO) 初始化CE,CSN,IRQ SPI1_CSN_Init_case1();//CFG: CE-GPIO9,CSN-GPIO8,IRQ-GPIO7void SPI1_CSN_Init_case1(void)//CFG: CE-GPIO9,CSN-GPIO8,IRQ-GPIO7{ GPIO_MODE_Init(GPIOA,PIN8,GPIO_MODE_OUTPUT);//CSN GPIO_Write(GPIOA,PIN8,GPIO_SET);//CSN=1 GPIO_MODE_Init(GPIOA,PIN9,GPIO_MODE_OUTPUT);//CE GPIO_Write(GPIOA,PIN9,GPIO_RESET);//CE=0 GPIO_MODE_Init(GPIOA,PIN7,GPIO_MODE_INPUT);//IRQ}2.4外設初始化(中斷) 中斷IRQ引腳,開發板上選擇為PA7。 GPIO_EXIT_Init_case4(GPIOA, PIN7);//檢測下降沿 Interrupt_Enable(EXIT9_5_int_ID);//CLIC使能EXIT中斷 SYS_Interrupt_Enable(); CLIC開總中斷void GPIO_EXIT_Init_case4(GPIO_TypeDef *GPIOx, uint8_t PINx)//檢測下降沿{ GPIO_MODE_Init(GPIOx, PINx, GPIO_MODE_INPUT); GPIO_EXIT_MODE_Init(GPIOx, PINx, GPIO_EXIT_FALLING); GPIO_PULL_Init(GPIOx, PINx, GPIO_PULLUP); //內部上拉 GPIO_INTER_enable(GPIOx, PINx); //GPIO 中斷使能}中斷處理函數: void EXIT9_5_IRQHandler(void){ if(EXTI->ISR&(0x1<<7)) //外部中斷 PA7 { IRQ_flag=1; EXTI->ISR |= 0x1<<7; //IRQ Handler...... }3.Si24R1通信模式介紹 Si24R1通信模式有兩種,一種是Si24R1通信模式,一種是兼容模式,兩者的區別就在于是否有包控制字,包控制可以實現動態負載長度,ACK通信,ACKPAYLAOD通信等。 Si24R1通信模式: 兼容模式: 4.Si24R1模塊 5. SPI函數 SPI讀寫函數:SPI1讀寫一個字節 uint8_t spi_rw_byte(uint8_t byte){ uint8_t a; SPI_Transceive(SPI1,&byte,&a,1); return a;}SPI寫寄存器:寫數據value到reg寄存器,同時返回寄存器值 uint8_t spi_rw_reg(uint8_t reg,uint8_t value) { uint8_t status; reg |= W_REGISTER ; //寫寄存器命令 GPIO_Write(GPIOA,PIN8,GPIO_RESET); status=spi_rw_byte(reg); //選擇寄存器,同時返回狀態字 spi_rw_byte(value); GPIO_Write(GPIOA,PIN8,GPIO_SET); return status; //返回狀態寄存器 }SPI讀寄存器: //========從reg寄存器中讀一個字節的數據========uint8_t spi_rd_reg(uint8_t reg){ uint8_t value; reg |= R_REGISTER ; //讀寄存器命令 GPIO_Write(GPIOA,PIN8,GPIO_RESET); spi_rw_byte(reg); value = spi_rw_byte(0); //從該寄存器中讀數據 GPIO_Write(GPIOA,PIN8,GPIO_SET); return (value ); //返回狀態寄存器}SPI讀BUFF: //函數:spi_read_buf()//功能:從reg寄存器讀出bytes個字節,通常用來讀取接收通道數據 或 接收/發送地址//=====================================================================================uint8_t spi_read_buf(uint8_t reg, uint8_t *pBuf, uint8_t bytes){ uint8_t status; uint8_t i; reg |= R_REGISTER; GPIO_Write(GPIOA,PIN8,GPIO_RESET); status = spi_rw_byte(reg); // 選擇寄存器,同時返回狀態字 for(i = 0; i < bytes; i++) { pBuf = spi_rw_byte(0); // 逐個字節從Si24R1讀出 } GPIO_Write(GPIOA,PIN8,GPIO_SET); // CSN拉高,結束數據傳輸 return(status); // 返回狀態寄存器}SPI寫BUFF: //函數:spi_write_buf()//功能:把pBuf緩存中的數據寫入到Si24R1,通常用來寫入發射通道數據 或 接收/發送地址//=====================================================================================uint8_t spi_write_buf(uint8_t reg, uint8_t *pBuf, uint8_t bytes){ uint8_t status, i; reg |= W_REGISTER; GPIO_Write(GPIOA,PIN8,GPIO_RESET); // CSN置低,開始傳輸數據 status = spi_rw_byte(reg); // 選擇寄存器,同時返回狀態字 for(i = 0; i < bytes; i++) { spi_rw_byte(pBuf); // 逐個字節寫入Si24R1 } GPIO_Write(GPIOA,PIN8,GPIO_SET); // CSN拉高,結束數據傳輸 return(status); // 返回狀態寄存器}6.TX_mode和RX_mode配置 TX_mode: CE拉低后,配置發射地址、發射地址寬度、射頻信道、傳輸速率,發射功率,配置發射模式、CRC、清除STATUS寄存器的標志位!!!(可能在調試程序或者異常退出,沒有清除STATUS,但是芯片沒斷電,可能IRQ的電平一直為低,最好就在初始化時清除STATUS寄存器的標志位。 spi_rw_reg(STATUS,0xff);//Si24R1 NOACK 發射模式void Si24R1_Tx_Mode(void){ GPIO_Write(GPIOA,CE_Pin,GPIO_RESET); spi_write_buf(TX_ADDR, TX_ADDRESS, 5); // 寫入發送地址 spi_rw_reg(FEATURE, 0x01); // 使能 W_TX_PAYLOAD_NOACK 命令 spi_rw_reg(SETUP_AW, 0x03); // 5 byte Address width spi_rw_reg(RF_CH, 2); // 選擇射頻通道0x40 spi_rw_reg(RF_SETUP, 0x0f); // 數據傳輸率 2Mbps spi_rw_reg(CONFIG, 0x0e); //配置為發射模式、CRC 為 2Bytes spi_rw_reg(STATUS,0xff); //GPIO_Write(GPIOA,CE_Pin,GPIO_SET);}RX_mode: 發射端的配置與接收端的配置一致即可 //Si24R1 NOACK 接收模式void Si24R1_Rx_Mode(void){ GPIO_Write(GPIOA,CE_Pin,GPIO_RESET); spi_write_buf(RX_ADDR_P0, TX_ADDRESS, 5); // 寫入接收地址// spi_rw_reg(FEATURE, 0x01); // 使能 W_TX_PAYLOAD_NOACK 命令 spi_rw_reg(EN_RXADDR , 0x01); // 使能接收通道 spi_rw_reg(RF_CH, 2); // 選擇射頻通道0x40 spi_rw_reg(RX_PW_P0 ,TX_PLOAD_WIDTH ); // 設置接收通道0負載數據寬度 spi_rw_reg(SETUP_AW, 0x03); // 5 byte Address width spi_rw_reg(RF_SETUP, 0x0f); // 數據傳輸率 2Mbps spi_rw_reg(CONFIG, 0x0f); //配置為接收方、RC 為 2Bytes spi_rw_reg(STATUS,0xff);//// GPIO_Write(GPIOa,CE_Pin,GPIO_SET);}Si24R1_TxPacket():發射函數,主要是給TX_FIFO填充數據,CE拉高后就會發射出去。其中要注意:發射前最好擦除FIFO,再填寫FIFO,這樣對異常的數據發送可以起到一定的屏蔽作用,否則可能會陷入始終發上一包寫入數據的怪圈。等到IRQ下降沿中斷后,判斷是否為發射完成中斷,完成即返回TX_OK; uint8_t Si24R1_TxPacket(){ uint8_t sta; uint8_t TX_BUF[TX_PLOAD_WIDTH] = {0,7,7,5,8,5,2,1}; IRQ_flag=0; spi_rw_reg(FLUSH_TX,0xff); spi_rw_reg(FLUSH_RX,0xff); //GPIO_Write(GPIOA,CE_Pin,GPIO_RESET); //使用NOACK模式時,應使用命令 W_TX_PAYLOAD_NOACK spi_write_buf(W_TX_PAYLOAD_NOACK,TX_BUF,TX_PLOAD_WIDTH);//寫數據到TX BUF // spi_write_buf(W_TX_PAYLOAD,TX_BUF,TX_PLOAD_WIDTH);//寫數據到TX BUF GPIO_Write(GPIOA,CE_Pin,GPIO_SET);//啟動發送 Delay32M_us(10); while(0==IRQ_flag) { NOP; //切記一定得加NOP指令,由于GCC編譯器優化問題,程序會只調用一次中斷標志。 }//等待發送完成 IRQ_flag=0; sta = spi_rd_reg(STATUS); // 返回狀態寄存器 spi_rw_reg(W_REGISTER+STATUS,sta); //清除TX_DS或MAX_RT中斷標志 if(sta&MAX_RT)//達到最大重發次數 { spi_rw_reg(FLUSH_TX,0xff);//清除TX FIFO寄存器 return MAX_RT; } if(sta&TX_OK)//發送完成 { return TX_OK; } return 0xff;//其他原因發送失敗}其中,需要注意的是:在等待中斷的標志IRQ_flag時,如果直接判斷,由于GCC編譯器優化,我們利用IDE的反匯編功能,查看下兩者的區別: while(0==IRQ_flag); while(0==IRQ_flag) { NOP; } 7.通信判斷 main()函數中,調用Si24R1_TxPacket();函數,判斷返回值是否為發射完成TX_OK標志,閃燈+打印即可。打印這里,雖然庫函數里有printf()和ee_printf(),都支持,但是推薦使用ee_printf()函數,這個是簡化版的printf函數(而不是C運行庫中提供的printf函數),以此生成的代碼體積就會更小。 sta=Si24R1_TxPacket( ); Delay32M_ms(500); if(sta==TX_OK) { GPIO_Write(GPIOA,PIN10,GPIO_RESET); Delay32M_ms(500); GPIO_Write(GPIOA,PIN10,GPIO_SET); ee_printf("Hello,IC農民\r\n"); } else Delay32M_ms(20); }總結 1.注意在等中斷IRQ產生后的IRQ_flag時,需要對while(0==IRQ_flag)處理時,在函數里加入一個NOP指令,以此規避GCC編譯器優化的問題造成IRQ_flag只判斷一次。 2.使用ee_printf()函數,減少代碼體積。 3. 在程序里有使能中斷時,在使能單個中斷后,需要開啟中斷總開關,否則會出現無法進入中斷!!。例如: Interrupt_Enable(EXIT9_5_int_ID);//CLIC使能EXIT中斷 SYS_Interrupt_Enable(); CLIC開總中斷那么,這里,硬件SPI,串口打印,GPIO中斷等外設就操作完了。個人能力有限,歡迎大家批評指正。 |