1 /*
2 This code demonstrates how to use the SPI master half duplex mode to read/write a AT932C46D
3 EEPROM (8-bit mode).
4
5 This example code is in the Public Domain (or CC0 licensed, at your option.)
6
7 Unless required by applicable law or agreed to in writing, this
8 software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
9 CONDITIONS OF ANY KIND, either express or implied.
10 */
11
12 #include "spi_eeprom.h"
13 #include "freertos/FreeRTOS.h"
14 #include "freertos/task.h"
15 #include "freertos/semphr.h"
16 #include "driver/gpio.h"
17 #include <unistd.h>
18 #include "esp_log.h"
19 #include <sys/param.h>
20 #include "sdkconfig.h"
21
22
23 #define EEPROM_BUSY_TIMEOUT_MS 5
24
25 #define EEPROM_CLK_FREQ (1*1000*1000) //When powered by 3.3V, EEPROM max freq is 1MHz
26 #define EEPROM_INPUT_DELAY_NS ((1000*1000*1000/EEPROM_CLK_FREQ)/2+20)
27
28 #define ADDR_MASK 0x7f
29
30 #define CMD_EWDS 0x200
31 #define CMD_WRAL 0x200
32 #define CMD_ERAL 0x200
33 #define CMD_EWEN 0x200
34 #define CMD_CKBS 0x000
35 #define CMD_READ 0x300
36 #define CMD_ERASE 0x380
37 #define CMD_WRITE 0x280
38
39 #define ADD_EWDS 0x00
40 #define ADD_WRAL 0x20
41 #define ADD_ERAL 0x40
42 #define ADD_EWEN 0x60
43
44 /// Context (config and data) of the spi_eeprom
45 struct eeprom_context_t{
46 eeprom_config_t cfg; ///< Configuration by the caller.
47 spi_device_handle_t spi; ///< SPI device handle
48 xSemaphoreHandle ready_sem; ///< Semaphore for ready signal
49 };
50
51 typedef struct eeprom_context_t eeprom_context_t;
52
53 static const char TAG[] = "eeprom";
54
55
56 // Workaround: The driver depends on some data in the flash and cannot be placed to DRAM easily for
57 // now. Using the version in LL instead.
58 #define gpio_set_level gpio_set_level_patch
59 #include "hal/gpio_ll.h"
gpio_set_level_patch(gpio_num_t gpio_num,uint32_t level)60 static inline esp_err_t gpio_set_level_patch(gpio_num_t gpio_num, uint32_t level)
61 {
62 gpio_ll_set_level(&GPIO, gpio_num, level);
63 return ESP_OK;
64 }
65
66
eeprom_simple_cmd(eeprom_context_t * ctx,uint16_t cmd)67 static esp_err_t eeprom_simple_cmd(eeprom_context_t *ctx, uint16_t cmd)
68 {
69 spi_transaction_t t = {
70 .cmd = cmd,
71 .user = ctx
72 };
73 return spi_device_polling_transmit(ctx->spi, &t);
74 }
75
eeprom_wait_done(eeprom_context_t * ctx)76 static esp_err_t eeprom_wait_done(eeprom_context_t* ctx)
77 {
78 //have to keep cs low for 250ns
79 usleep(1);
80 //clear signal
81 if (ctx->cfg.intr_used) {
82 xSemaphoreTake(ctx->ready_sem, 0);
83 gpio_set_level(ctx->cfg.cs_io, 1);
84 gpio_intr_enable(ctx->cfg.miso_io);
85
86 //Max processing time is 5ms, tick=1 may happen very soon, set to 2 at least
87 uint32_t tick_to_wait = MAX(EEPROM_BUSY_TIMEOUT_MS / portTICK_PERIOD_MS, 2);
88 BaseType_t ret = xSemaphoreTake(ctx->ready_sem, tick_to_wait);
89 gpio_intr_disable(ctx->cfg.miso_io);
90 gpio_set_level(ctx->cfg.cs_io, 0);
91
92 if (ret != pdTRUE) return ESP_ERR_TIMEOUT;
93 } else {
94 bool timeout = true;
95 gpio_set_level(ctx->cfg.cs_io, 1);
96 for (int i = 0; i < EEPROM_BUSY_TIMEOUT_MS * 1000; i ++) {
97 if (gpio_get_level(ctx->cfg.miso_io)) {
98 timeout = false;
99 break;
100 }
101 usleep(1);
102 }
103 gpio_set_level(ctx->cfg.cs_io, 0);
104 if (timeout) return ESP_ERR_TIMEOUT;
105 }
106 return ESP_OK;
107 }
108
cs_high(spi_transaction_t * t)109 static void cs_high(spi_transaction_t* t)
110 {
111 ESP_EARLY_LOGV(TAG, "cs high %d.", ((eeprom_context_t*)t->user)->cfg.cs_io);
112 gpio_set_level(((eeprom_context_t*)t->user)->cfg.cs_io, 1);
113 }
114
cs_low(spi_transaction_t * t)115 static void cs_low(spi_transaction_t* t)
116 {
117 gpio_set_level(((eeprom_context_t*)t->user)->cfg.cs_io, 0);
118 ESP_EARLY_LOGV(TAG, "cs low %d.", ((eeprom_context_t*)t->user)->cfg.cs_io);
119 }
120
ready_rising_isr(void * arg)121 void ready_rising_isr(void* arg)
122 {
123 eeprom_context_t* ctx = (eeprom_context_t*)arg;
124 xSemaphoreGive(ctx->ready_sem);
125 ESP_EARLY_LOGV(TAG, "ready detected.");
126 }
127
spi_eeprom_deinit(eeprom_context_t * ctx)128 esp_err_t spi_eeprom_deinit(eeprom_context_t* ctx)
129 {
130 spi_bus_remove_device(ctx->spi);
131 if (ctx->cfg.intr_used) {
132 vSemaphoreDelete(ctx->ready_sem);
133 }
134 free(ctx);
135 return ESP_OK;
136 }
137
spi_eeprom_init(const eeprom_config_t * cfg,eeprom_context_t ** out_ctx)138 esp_err_t spi_eeprom_init(const eeprom_config_t *cfg, eeprom_context_t** out_ctx)
139 {
140 esp_err_t err = ESP_OK;
141 if (cfg->intr_used && cfg->host == SPI1_HOST) {
142 ESP_LOGE(TAG, "interrupt cannot be used on SPI1 host.");
143 return ESP_ERR_INVALID_ARG;
144 }
145
146 eeprom_context_t* ctx = (eeprom_context_t*)malloc(sizeof(eeprom_context_t));
147 if (!ctx) return ESP_ERR_NO_MEM;
148
149 *ctx = (eeprom_context_t) {
150 .cfg = *cfg,
151 };
152
153 spi_device_interface_config_t devcfg={
154 .command_bits = 10,
155 .clock_speed_hz = EEPROM_CLK_FREQ,
156 .mode = 0, //SPI mode 0
157 /*
158 * The timing requirements to read the busy signal from the EEPROM cannot be easily emulated
159 * by SPI transactions. We need to control CS pin by SW to check the busy signal manually.
160 */
161 .spics_io_num = -1,
162 .queue_size = 1,
163 .flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_POSITIVE_CS,
164 .pre_cb = cs_high,
165 .post_cb = cs_low,
166 .input_delay_ns = EEPROM_INPUT_DELAY_NS, //the EEPROM output the data half a SPI clock behind.
167 };
168 //Attach the EEPROM to the SPI bus
169 err = spi_bus_add_device(ctx->cfg.host, &devcfg, &ctx->spi);
170 if (err != ESP_OK) {
171 goto cleanup;
172 }
173
174 gpio_set_level(ctx->cfg.cs_io, 0);
175 gpio_config_t cs_cfg = {
176 .pin_bit_mask = BIT64(ctx->cfg.cs_io),
177 .mode = GPIO_MODE_OUTPUT,
178 };
179 gpio_config(&cs_cfg);
180
181 if (ctx->cfg.intr_used) {
182 ctx->ready_sem = xSemaphoreCreateBinary();
183 if (ctx->ready_sem == NULL) {
184 err = ESP_ERR_NO_MEM;
185 goto cleanup;
186 }
187
188 gpio_set_intr_type(ctx->cfg.miso_io, GPIO_INTR_POSEDGE);
189 err = gpio_isr_handler_add(ctx->cfg.miso_io, ready_rising_isr, ctx);
190 if (err != ESP_OK) {
191 goto cleanup;
192 }
193 gpio_intr_disable(ctx->cfg.miso_io);
194 }
195 *out_ctx = ctx;
196 return ESP_OK;
197
198 cleanup:
199 if (ctx->spi) {
200 spi_bus_remove_device(ctx->spi);
201 ctx->spi = NULL;
202 }
203 if (ctx->ready_sem) {
204 vSemaphoreDelete(ctx->ready_sem);
205 ctx->ready_sem = NULL;
206 }
207 free(ctx);
208 return err;
209 }
210
spi_eeprom_read(eeprom_context_t * ctx,uint8_t addr,uint8_t * out_data)211 esp_err_t spi_eeprom_read(eeprom_context_t* ctx, uint8_t addr, uint8_t* out_data)
212 {
213 spi_transaction_t t = {
214 .cmd = CMD_READ | (addr & ADDR_MASK),
215 .rxlength = 8,
216 .flags = SPI_TRANS_USE_RXDATA,
217 .user = ctx,
218 };
219 esp_err_t err = spi_device_polling_transmit(ctx->spi, &t);
220 if (err!= ESP_OK) return err;
221
222 *out_data = t.rx_data[0];
223 return ESP_OK;
224 }
225
spi_eeprom_erase(eeprom_context_t * ctx,uint8_t addr)226 esp_err_t spi_eeprom_erase(eeprom_context_t* ctx, uint8_t addr)
227 {
228 esp_err_t err;
229 err = spi_device_acquire_bus(ctx->spi, portMAX_DELAY);
230 if (err != ESP_OK) return err;
231
232 err = eeprom_simple_cmd(ctx, CMD_ERASE | (addr & ADDR_MASK));
233
234 if (err == ESP_OK) {
235 err = eeprom_wait_done(ctx);
236 }
237
238 spi_device_release_bus(ctx->spi);
239 return err;
240 }
241
spi_eeprom_write(eeprom_context_t * ctx,uint8_t addr,uint8_t data)242 esp_err_t spi_eeprom_write(eeprom_context_t* ctx, uint8_t addr, uint8_t data)
243 {
244 esp_err_t err;
245 err = spi_device_acquire_bus(ctx->spi, portMAX_DELAY);
246 if (err != ESP_OK) return err;
247
248 spi_transaction_t t = {
249 .cmd = CMD_WRITE | (addr & ADDR_MASK),
250 .length = 8,
251 .flags = SPI_TRANS_USE_TXDATA,
252 .tx_data = {data},
253 .user = ctx,
254 };
255 err = spi_device_polling_transmit(ctx->spi, &t);
256
257 if (err == ESP_OK) {
258 err = eeprom_wait_done(ctx);
259 }
260
261 spi_device_release_bus(ctx->spi);
262 return err;
263 }
264
spi_eeprom_write_enable(eeprom_context_t * ctx)265 esp_err_t spi_eeprom_write_enable(eeprom_context_t* ctx)
266 {
267 return eeprom_simple_cmd(ctx, CMD_EWEN | ADD_EWEN);
268 }
269
spi_eeprom_write_disable(eeprom_context_t * ctx)270 esp_err_t spi_eeprom_write_disable(eeprom_context_t* ctx)
271 {
272 return eeprom_simple_cmd(ctx, CMD_EWDS | ADD_EWDS);
273 }
274
spi_eeprom_erase_all(eeprom_context_t * ctx)275 esp_err_t spi_eeprom_erase_all(eeprom_context_t* ctx)
276 {
277 #if !CONFIG_EXAMPLE_5V_COMMANDS
278 //not supported in 3.3V VCC
279 ESP_LOGE(TAG, "erase all not supported by EEPROM under 3.3V VCC");
280 return ESP_ERR_NOT_SUPPORTED;
281 #endif
282
283 esp_err_t err;
284 err = spi_device_acquire_bus(ctx->spi, portMAX_DELAY);
285 if (err != ESP_OK) return err;
286
287 err = eeprom_simple_cmd(ctx, CMD_ERAL | ADD_ERAL);
288
289 if (err == ESP_OK) {
290 err = eeprom_wait_done(ctx);
291 }
292
293 spi_device_release_bus(ctx->spi);
294 return err;
295 }
296
spi_eeprom_write_all(eeprom_context_t * ctx,uint8_t data)297 esp_err_t spi_eeprom_write_all(eeprom_context_t* ctx, uint8_t data)
298 {
299 #if !CONFIG_EXAMPLE_5V_COMMANDS
300 //not supported in 3.3V VCC
301 ESP_LOGE(TAG, "write all not supported by EEPROM under 3.3V VCC");
302 return ESP_ERR_NOT_SUPPORTED;
303 #endif
304
305 esp_err_t err;
306 err = spi_device_acquire_bus(ctx->spi, portMAX_DELAY);
307 if (err != ESP_OK) return err;
308
309 spi_transaction_t t = {
310 .cmd = CMD_WRAL | ADD_WRAL,
311 .length = 8,
312 .flags = SPI_TRANS_USE_TXDATA,
313 .tx_data = {data},
314 .user = ctx,
315 };
316 err = spi_device_polling_transmit(ctx->spi, &t);
317
318 if (err == ESP_OK) {
319 err = eeprom_wait_done(ctx);
320 }
321
322 spi_device_release_bus(ctx->spi);
323 return err;
324 }
325