一 前言
最近搞了兩個項目,調了一版freertos下基于hal庫得模擬I2C驅動,非常實用,直接拷貝就能用,這里做下記錄,主要用到如下四個文件:
- delay.c
- delay.h
- i2cc.c
- i2cc.h
二 代碼實現
delay.c
#include "main.h"
#include "delay.h"// achieve by DWT
void delay_init(void)
{CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 使能 DWT 外設DWT->CYCCNT = 0; // 清除計數器DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 使能 cycle counter
}void delay_us(uint16_t us)
{uint32_t cycles = us * (SystemCoreClock / 1000000); // 72MHz 時,cycles = us × 72uint32_t start = DWT->CYCCNT;while ((DWT->CYCCNT - start) < cycles);
}void delay_ms(uint16_t ms)
{for(uint32_t i = 0;i < ms;i++)delay_us(1000);
}
delay.h
#ifndef BSP_DELAY_H_
#define BSP_DELAY_H_void delay_init(void); /* 初始化延遲函數 */
void delay_ms(uint16_t nms); /* 延時nms */
void delay_us(uint16_t nus); /* 延時nus */#endif /* BSP_DELAY_H_ */
i2cc.c
#include "i2cc.h"
#include "delay.h"/*** @brief IIC延時函數,用于控制IIC讀寫速度* @param 無* @retval 無*/
static void iic_delay(void)
{delay_us(2); /* 2us的延時, 讀寫速度在250Khz以內 */
}/*** @brief 產生IIC起始信號* @param 無* @retval 無*/
void iic_start(void)
{IIC_SDA(1);IIC_SCL(1);iic_delay();IIC_SDA(0); /* START信號: 當SCL為高時, SDA從高變成低, 表示起始信號 */iic_delay();IIC_SCL(0); /* 鉗住I2C總線,準備發送或接收數據 */iic_delay();
}/*** @brief 產生IIC停止信號* @param 無* @retval 無*/
void iic_stop(void)
{IIC_SDA(0); /* STOP信號: 當SCL為高時, SDA從低變成高, 表示停止信號 */iic_delay();IIC_SCL(1);iic_delay();IIC_SDA(1); /* 發送I2C總線結束信號 */iic_delay();
}/*** @brief 等待應答信號到來* @param 無* @retval 1,接收應答失敗* 0,接收應答成功*/
uint8_t iic_wait_ack(void)
{uint8_t waittime = 0;uint8_t rack = 0;IIC_SDA(1); /* 主機釋放SDA線(此時外部器件可以拉低SDA線) */iic_delay();IIC_SCL(1); /* SCL=1, 此時從機可以返回ACK */iic_delay();while (IIC_READ_SDA) /* 等待應答 */{waittime++;if (waittime > 250){iic_stop();rack = 1;break;}}IIC_SCL(0); /* SCL=0, 結束ACK檢查 */iic_delay();return rack;
}/*** @brief 產生ACK應答* @param 無* @retval 無*/
void iic_ack(void)
{IIC_SDA(0); /* SCL 0 -> 1 時 SDA = 0,表示應答 */iic_delay();IIC_SCL(1); /* 產生一個時鐘 */iic_delay();IIC_SCL(0);iic_delay();IIC_SDA(1); /* 主機釋放SDA線 */iic_delay();
}/*** @brief 不產生ACK應答* @param 無* @retval 無*/
void iic_nack(void)
{IIC_SDA(1); /* SCL 0 -> 1 時 SDA = 1,表示不應答 */iic_delay();IIC_SCL(1); /* 產生一個時鐘 */iic_delay();IIC_SCL(0);iic_delay();
}/*** @brief IIC發送一個字節* @param data: 要發送的數據* @retval 無*/
void iic_send_byte(uint8_t data)
{uint8_t t;for (t = 0; t < 8; t++){IIC_SDA((data & 0x80) >> 7); /* 高位先發送 */iic_delay();IIC_SCL(1);iic_delay();IIC_SCL(0);data <<= 1; /* 左移1位,用于下一次發送 */}IIC_SDA(1); /* 發送完成, 主機釋放SDA線 */
}/*** @brief IIC讀取一個字節* @param ack: ack=1時,發送ack; ack=0時,發送nack* @retval 接收到的數據*/
uint8_t iic_read_byte(uint8_t ack)
{uint8_t i, receive = 0;for (i = 0; i < 8; i++ ) /* 接收1個字節數據 */{receive <<= 1; /* 高位先輸出,所以先收到的數據位要左移 */IIC_SCL(1);iic_delay();if (IIC_READ_SDA){receive++;}IIC_SCL(0);iic_delay();}if (!ack){iic_nack(); /* 發送nACK */}else{iic_ack(); /* 發送ACK */}return receive;
}
i2cc.h
#ifndef _I2CC_H_
#define _I2CC_H_#include "main.h"
#include "data.h"/******************************************************************************************/
/* 引腳 定義 */#define IIC_SCL_GPIO_PORT I2C_SCL_GPIO_Port
#define IIC_SCL_GPIO_PIN I2C_SCL_Pin
#define IIC_SDA_GPIO_PORT I2C_SDA_GPIO_Port
#define IIC_SDA_GPIO_PIN I2C_SDA_Pin
/******************************************************************************************//* IO操作 */
#define IIC_SCL(x) do{ x ? \HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_SET) : \HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_RESET); \}while(0) /* SCL */#define IIC_SDA(x) do{ x ? \HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_SET) : \HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_RESET); \}while(0) /* SDA */#define IIC_READ_SDA HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN) /* 讀取SDA *//* IIC所有操作函數 */
void iic_start(void); /* 發送IIC開始信號 */
void iic_stop(void); /* 發送IIC停止信號 */
void iic_ack(void); /* IIC發送ACK信號 */
void iic_nack(void); /* IIC不發送ACK信號 */
uint8_t iic_wait_ack(void); /* IIC等待ACK信號 */
void iic_send_byte(uint8_t txd);/* IIC發送一個字節 */
uint8_t iic_read_byte(unsigned char ack);/* IIC讀取一個字節 */
void get_tempare(void);#endif /* _I2CC_H_ */
三 總結
使用時,cubeIDE配置相應的GPIO管腳,SDA管腳要配置為開漏,然后main函數里面調用delay_init啟動DWT模塊,再調用I2C相關函數組成操作接口即可。