使用Platformio平台的libopencm3开发框架来开发STM32G0,以下使用软件模拟I2C总线时序,并用它来读取GXHT30温湿度数据。
1 新建项目
建立gxht30项目
在PIO的Home页面新建项目,项目名称gxht30,选择开发板为 MonkeyPi_STM32_G070RB,开发框架选择libopencm3;
项目建立完成后在src目录下新建main.c主程序文件;
修改下载和调试方式,这里开发板使用的是DAPLink仿真器,因此修改platformio.ini文件如下:
1upload_protocol = cmsis-dap
2debug_tool = cmsis-dap
2 I2C软件模拟
2.1 文件结构
在lib目录新建 sw_i2c 文件夹,并新建如下文件:
2.2 sw_i2c_port.h 与底层IO读写相关
1/**
2 * @file sw_i2c_port.h
3 * @author MakerInChina (makerinchina.cn)
4 * @brief
5 * @version 0.01
6 * @date 2022-09-25
7 *
8 * @copyright Copyright (c) 2022
9 *
10 */
11
12#ifndef _SW_I2C_PORT_HEAD_H_
13#define _SW_I2C_PORT_HEAD_H_
14
15#include 3/stm32/rcc.h>
16#include 3/stm32/gpio.h>
17
18#define SW_I2C_SCL_CLOCK RCC_GPIOB
19#define SW_I2C_SCL_PORT GPIOB
20#define SW_I2C_SCL_PIN GPIO13
21
22#define SW_I2C_SDA_CLOCK RCC_GPIOB
23#define SW_I2C_SDA_PORT GPIOB
24#define SW_I2C_SDA_PIN GPIO14
25
26#define sw_i2c_scl_high() gpio_set(SW_I2C_SCL_PORT, SW_I2C_SCL_PIN)
27#define sw_i2c_scl_low() gpio_clear(SW_I2C_SCL_PORT, SW_I2C_SCL_PIN)
28#define sw_i2c_sda_high() gpio_set(SW_I2C_SDA_PORT, SW_I2C_SDA_PIN)
29#define sw_i2c_sda_low() gpio_clear(SW_I2C_SDA_PORT, SW_I2C_SDA_PIN)
30
31#define sw_i2c_sda_input() gpio_mode_setup(SW_I2C_SDA_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SW_I2C_SDA_PIN)
32#define sw_i2c_sda_output() gpio_mode_setup(SW_I2C_SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SW_I2C_SDA_PIN)
33
34// #define sw_i2c_delay() delay_us(5)
35#define sw_i2c_delay() do{
36 for (int i=0; i<58; i++) {
37 __asm__ volatile ("nop");
38 }
39 }while(0)
40static bool sw_i2c_sda_get(void)
41{
42 return (gpio_get(SW_I2C_SDA_PORT, SW_I2C_SDA_PIN) != 0) ? true:false;
43}
44
45static void sw_i2c_port_init()
46{
47 /* 打开GPIO时钟 */
48 rcc_periph_clock_enable(SW_I2C_SCL_CLOCK);
49 rcc_periph_clock_enable(SW_I2C_SDA_CLOCK);
50
51 /* 禁用默认上拉,使SCL, SDA保持高阻状态, 设置为 OD 模式 */
52 gpio_mode_setup(SW_I2C_SCL_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SW_I2C_SCL_PIN);
53 gpio_mode_setup(SW_I2C_SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SW_I2C_SDA_PIN);
54 gpio_set_output_options(SW_I2C_SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, SW_I2C_SCL_PIN);
55 gpio_set_output_options(SW_I2C_SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, SW_I2C_SDA_PIN);
56
57 /* 空闲: 拉高SCL和SDA */
58 gpio_set(SW_I2C_SCL_PORT, SW_I2C_SCL_PIN);
59 gpio_set(SW_I2C_SDA_PORT, SW_I2C_SDA_PIN);
60}
61
62#endif //!_SW_I2C_PORT_HEAD_H_
i2c时序中的延时这里使用软件延时,模拟的是 100KHz的频率;
2.3 sw_i2c_private.h 实现i2c的基本时序
1/**
2 * @file sw_i2c_private.h
3 * @author MakerInChina (makerinchina.cn)
4 * @brief
5 * @version 0.01
6 * @date 2022-09-25
7 *
8 * @copyright Copyright (c) 2022
9 *
10 */
11
12#ifndef _SW_I2C_PRIVATE_HEAD_H_
13#define _SW_I2C_PRIVATE_HEAD_H_
14
15#include "sw_i2c_port.h"
16
17static void i2c_start(void);
18static void i2c_stop(void);
19static bool i2c_wait_ack(void);
20static void i2c_send_ack(void);
21static void i2c_send_nack(void);
22static void i2c_send_byte(uint8_t data);
23static uint8_t i2c_recv_byte(bool ack);
24
25/**
26 * @brief I2C总线启动信号
27 */
28static void i2c_start(void)
29{
30 /* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
31 sw_i2c_sda_high();
32 sw_i2c_scl_high();
33 sw_i2c_delay();
34 sw_i2c_sda_low();
35 sw_i2c_delay();
36 sw_i2c_scl_low();
37 sw_i2c_delay();
38}
39
40/**
41 * @brief I2C总线停止信号
42 */
43static void i2c_stop(void)
44{
45 /* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
46 sw_i2c_sda_low();
47 sw_i2c_delay();
48 sw_i2c_scl_high();
49 sw_i2c_delay();
50 sw_i2c_sda_high();
51}
52
53/**
54 * @brief 向I2C总线设备发送1个字节
55 * @param data 等待发送的字节
56 */
57static void i2c_send_byte(uint8_t data)
58{
59 uint8_t i;
60
61 /* 先发送字节的高位bit7 */
62 for (i = 0; i < 8; i++) {
63 sw_i2c_delay();
64 sw_i2c_scl_low();
65
66 if (data & 0x80) {
67 sw_i2c_sda_high();
68 } else {
69 sw_i2c_sda_low();
70 }
71
72 sw_i2c_delay();
73 sw_i2c_scl_high();
74
75 data <<= 1; /* 左移一个bit */
76 }
77}
78
79/**
80 * @brief 产生一个时钟,并读取器件的ACK应答信号
81 * @return 返回true表示正确应答,false表示无器件响应
82 */
83static bool i2c_wait_ack(void)
84{
85 bool res;
86
87 sw_i2c_delay();
88 sw_i2c_scl_low();
89
90 sw_i2c_sda_input();
91
92 sw_i2c_delay();
93 sw_i2c_scl_high(); /* 驱动SCL = 1, 此时器件会返回ACK应答 */
94 sw_i2c_delay();
95 if (sw_i2c_sda_get() == false) { /* 读取SDA口线状态 */
96 res = true;
97 } else {
98 res = false;
99 }
100 sw_i2c_scl_low();
101 sw_i2c_sda_high(); /* 释放SDA总线 */
102 sw_i2c_sda_output();
103 sw_i2c_delay();
104
105 return res;
106}
107
108/**
109 * @brief 产生一个ACK信号
110 */
111static void i2c_send_ack(void)
112{
113 sw_i2c_sda_low(); /* CPU驱动SDA = 0 */
114 sw_i2c_delay();
115 sw_i2c_scl_high(); /* CPU产生1个时钟 */
116 sw_i2c_delay();
117 sw_i2c_scl_low();
118 sw_i2c_delay();
119 sw_i2c_sda_high(); /* CPU释放SDA总线 */
120}
121
122/**
123 * @brief CPU产生1个NACK信号
124 */
125static void i2c_send_nack(void)
126{
127 sw_i2c_sda_high(); /* CPU驱动SDA = 1 */
128 sw_i2c_delay();
129 sw_i2c_scl_high(); /* CPU产生1个时钟 */
130 sw_i2c_delay();
131 sw_i2c_scl_low();
132 sw_i2c_delay();
133}
134
135/**
136 * @brief CPU从I2C总线设备读取8bit数据
137 * 读1个字节,ack=1时,发送ACK,ack=0,发送nACK
138 * @return
139 */
140static uint8_t i2c_recv_byte(bool ack)
141{
142 uint8_t i;
143 uint8_t value;
144
145 /* 读到第1个bit为数据的bit7 */
146 value = 0;
147 for (i = 0; i < 8; i++) {
148 value <<= 1;
149 sw_i2c_scl_high();
150 sw_i2c_delay();
151 if (sw_i2c_sda_get()==true) {
152 value++;
153 }
154 sw_i2c_scl_low();
155 sw_i2c_delay();
156 }
157
158 if (ack) {
159 i2c_send_ack(); //发送ACK
160 } else {
161 i2c_send_nack();//发送nACK
162 }
163
164 return value;
165}
166
167#endif //!_SW_I2C_PRIVATE_HEAD_H_
2.4 sw_i2c 读写实现
1/**
2 * @file sw_i2c.c
3 * @author MakerInChina (makerinchina.cn)
4 * @brief
5 * @version 0.01
6 * @date 2022-09-25
7 *
8 * @copyright Copyright (c) 2022
9 *
10 */
11
12#include "sw_i2c.h"
13#include "sw_i2c_port.h"
14#include "sw_i2c_private.h"
15
16void sw_i2c_init()
17{
18 sw_i2c_port_init();
19}
20
21/* Function to setup and execute I2C transfer request */
22bool sw_i2c_transfer(uint8_t dev_addr, uint8_t *tx_buffer,uint16_t tx_size,uint8_t *rx_buffer,uint16_t rx_size)
23{
24 uint16_t i;
25
26 if (tx_size > 0) {
27 /* start */
28 i2c_start();
29 /* address + write */
30 i2c_send_byte(dev_addr<<1);
31 if (i2c_wait_ack() == false) {
32 goto error_device_nack;
33 }