Compare commits
17 Commits
master
...
spark_stro
Author | SHA1 | Date |
---|---|---|
King Kévin | cfdb9f8977 | |
King Kévin | c6c9c7aed9 | |
King Kévin | 4c2b3e0516 | |
King Kévin | 102e1669d9 | |
King Kévin | 453d35079f | |
King Kévin | e78e67c0e8 | |
King Kévin | ba5ea0a6b2 | |
King Kévin | e9078e831e | |
King Kévin | e6a545cd0f | |
King Kévin | ca4d41e79f | |
King Kévin | 4a96073898 | |
King Kévin | a7aab104ba | |
King Kévin | 2f42659b18 | |
King Kévin | b78af16939 | |
King Kévin | 4f7a173e23 | |
King Kévin | 10c2973ee7 | |
King Kévin | 0bfae07c30 |
67
README.md
67
README.md
|
@ -1,4 +1,4 @@
|
|||
This firmware template is designed for development boards based around [STM32 F1 series micro-controller](http://www.st.com/web/en/catalog/mmc/FM141/SC1169/SS1031).
|
||||
This firmware is for the spark strober, using an [STM32 F1 series micro-controller](http://www.st.com/web/en/catalog/mmc/FM141/SC1169/SS1031).
|
||||
|
||||
project
|
||||
=======
|
||||
|
@ -6,40 +6,63 @@ project
|
|||
summary
|
||||
-------
|
||||
|
||||
*describe project purpose*
|
||||
The spark strober switches mains electricity.
|
||||
This allows to switch on a flood light or stroboscope for a short time to create a light pulse.
|
||||
|
||||
technology
|
||||
----------
|
||||
|
||||
*described electronic details*
|
||||
components:
|
||||
|
||||
board
|
||||
=====
|
||||
|
||||
The current implementation uses a [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board).
|
||||
|
||||
The underlying template also supports following board:
|
||||
|
||||
- [Maple Mini](http://leaflabs.com/docs/hardware/maple-mini.html), based on a STM32F103CBT6
|
||||
- [System Board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#system_board), based on a STM32F103C8T6
|
||||
- [blue pill](ihttps://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6
|
||||
- [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board), based on a STM32F103C8T6
|
||||
|
||||
**Which board is used is defined in the Makefile**.
|
||||
This is required to map the user LED and button provided on the board
|
||||
- [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6: development board to control everything
|
||||
- HLK-PM01 power module: 100-240V AC to 5V DC power supply to power the board and other peripherals (protected with a 0.25A glass fuse)
|
||||
- VS1838b: 38 kHz infrared demodulator to receive the codes from a remote control (connected using a 3.5 mm TRS jack)
|
||||
- G3MB-202P: compact 240V AC 2A solid state relay to switch mains electricity (protected with a 2A fuse)
|
||||
- infrared remote control sending NEC codes (low power version salvages from a multimedia player, high power version from Jedi iDual)
|
||||
|
||||
connections
|
||||
===========
|
||||
|
||||
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
|
||||
Hi-Link HLK-PM01:
|
||||
|
||||
- *list board to preipheral pin connections*
|
||||
- AC: 100-240V AC mains
|
||||
- +Vo: 5V
|
||||
- -Vo: ground
|
||||
|
||||
All pins are configured using `define`s in the corresponding source code.
|
||||
Omron G3MB-202P:
|
||||
|
||||
- 1: 100-240V AC mains line input
|
||||
- 2: 100-240V AC mains line output
|
||||
- 3: 5V with 330 Ohm resistor
|
||||
- 4: blue pill, PB6
|
||||
|
||||
VS1838b:
|
||||
|
||||
- 1, OUT: 3.5 mm TRS jack plug, ring
|
||||
- 2, GND: ground
|
||||
- 3, VCC: 5V (with 100 nF decoupling capacitor)
|
||||
|
||||
3.5 mm TRS jack socket:
|
||||
|
||||
- tip: 5V
|
||||
- ring: blue pill, PB8, TIM4_CH3
|
||||
- sleeve: ground
|
||||
|
||||
blue pill:
|
||||
|
||||
- PB8, TIM4_CH3: IR demodulator OUT
|
||||
- PB6: SSR control
|
||||
- 5V: 5V
|
||||
- GND: ground
|
||||
|
||||
code
|
||||
====
|
||||
|
||||
control
|
||||
-------
|
||||
|
||||
Have a look at the `application.c` *ir_action* to changes the AC on/off switch animations and corresponding remote control code.
|
||||
|
||||
dependencies
|
||||
------------
|
||||
|
||||
|
@ -86,7 +109,3 @@ USB
|
|||
---
|
||||
|
||||
The firmware offers serial communication over USART1 and USB (using the CDC ACM device class).
|
||||
|
||||
You can also reset the board by setting the serial width to 5 bits over USB.
|
||||
To reset the board run `rake reset`.
|
||||
This only works if provided USB CDC ACM is running correctly and the micro-controller isn't stuck.
|
||||
|
|
4
Rakefile
4
Rakefile
|
@ -15,7 +15,7 @@ FIRMWARES = [BOOTLOADER, APPLICATION]
|
|||
|
||||
# which development board is used
|
||||
# supported are: SYSTEM_BOARD, MAPLE_MINI, BLUE_PILL, CORE_BOARD, BUSVOODOO
|
||||
BOARD = ENV["BOARD"] || "CORE_BOARD"
|
||||
BOARD = ENV["BOARD"] || "BLUE_PILL"
|
||||
|
||||
# libopencm3 definitions
|
||||
LIBOPENCM3_DIR = "libopencm3"
|
||||
|
@ -183,7 +183,7 @@ end
|
|||
|
||||
# SWD/JTAG adapter used
|
||||
# supported are : STLINKV2 (ST-Link V2), BMP (Black Magic Probe)
|
||||
SWD_ADAPTER = ENV["SWD_ADAPTER"] || "BMP"
|
||||
SWD_ADAPTER = ENV["SWD_ADAPTER"] || "STLINKV2"
|
||||
# openOCD path to control the adapter
|
||||
OOCD = ENV["OOCD"] || "openocd"
|
||||
# openOCD adapted name
|
||||
|
|
269
application.c
269
application.c
|
@ -12,8 +12,8 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/** STM32F1 application example
|
||||
* @file application.c
|
||||
/** STM32F1 application to strobe electricity
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016-2018
|
||||
*/
|
||||
|
@ -43,8 +43,9 @@
|
|||
#include "usb_cdcacm.h" // USB CDC ACM utilities
|
||||
#include "terminal.h" // handle the terminal interface
|
||||
#include "menu.h" // menu utilities
|
||||
#include "ir_nec.h" // InfraRed NEC decoding utilities
|
||||
|
||||
#define WATCHDOG_PERIOD 10000 /**< watchdog period in ms */
|
||||
#define WATCHDOG_PERIOD 20000 /**< watchdog period in ms */
|
||||
|
||||
/** @defgroup main_flags flag set in interrupts to be processed in main task
|
||||
* @{
|
||||
|
@ -52,6 +53,9 @@
|
|||
volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */
|
||||
/** @} */
|
||||
|
||||
/** if the strobe output should not flicker */
|
||||
static bool flicker_off = true;
|
||||
|
||||
size_t putc(char c)
|
||||
{
|
||||
size_t length = 0; // number of characters printed
|
||||
|
@ -235,6 +239,196 @@ static void process_command(char* str)
|
|||
}
|
||||
}
|
||||
|
||||
#define STROBE_PORT B /**< GPIO port to control strobe light */
|
||||
#define STROBE_PIN 6 /**< GPIO pin to control strobe light */
|
||||
#define STROBE_ON 0 /**< LED is on when pin is low (open-drain allows 5V on) */
|
||||
|
||||
/* strobe animations (on + off times in ms) */
|
||||
static const uint16_t strobe1[] = {100, 0};
|
||||
static const uint16_t strobe2[] = {100, 100, 100, 0};
|
||||
static const uint16_t strobe3[] = {100, 100, 100, 100, 100, 0};
|
||||
static const uint16_t strobe5[] = {50, 50, 50, 50, 50, 50, 50, 50, 50, 0};
|
||||
static const uint16_t strobe10[] = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100};
|
||||
|
||||
/** switch strobe power on */
|
||||
static void strobe_on(void)
|
||||
{
|
||||
#if STROBE_ON
|
||||
gpio_set(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
#else
|
||||
gpio_clear(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
#endif
|
||||
}
|
||||
|
||||
/** switch strobe power off */
|
||||
static void strobe_off(void)
|
||||
{
|
||||
#if STROBE_ON
|
||||
gpio_clear(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
#else
|
||||
gpio_set(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
#endif
|
||||
}
|
||||
|
||||
/** toggle strobe power */
|
||||
static void strobe_toggle(void)
|
||||
{
|
||||
gpio_toggle(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
}
|
||||
|
||||
/** play strobe animation
|
||||
* @param[in] animation on+off timings (in ms)
|
||||
* @param[in] length animation length
|
||||
*/
|
||||
static void strobe_play(const uint16_t* animation, uint16_t length)
|
||||
{
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
iwdg_reset(); // kick the dog
|
||||
if (0 == animation[i]) { // skip animation
|
||||
continue;
|
||||
}
|
||||
if (i % 2) { // odd index if encodes off duration
|
||||
strobe_off();
|
||||
} else {
|
||||
strobe_on();
|
||||
}
|
||||
sleep_ms(animation[i]); // wait for set duration
|
||||
}
|
||||
strobe_off(); // switch off at the end
|
||||
}
|
||||
|
||||
/** perform IR code related action
|
||||
* @warning the codes need to be adjusted to your remote
|
||||
* @param[in] code IR code
|
||||
*/
|
||||
static void ir_action(const struct ir_nec_code_t* code)
|
||||
{
|
||||
if (code->repeat) { // don't handle long button press repeating the code
|
||||
return;
|
||||
}
|
||||
if (0x00f7 == code->address) { // flat LED IR remote
|
||||
switch (code->command) { // choose animation depending on button
|
||||
case 0x00: // UP
|
||||
strobe_toggle();
|
||||
printf("toggle strobe\n");
|
||||
break;
|
||||
case 0x80: // DOWN
|
||||
printf("start flickering\n");
|
||||
flicker_off = false; // let flickering happen in main loop
|
||||
break;
|
||||
case 0xc0: // ON
|
||||
strobe_on();
|
||||
printf("light on\n");
|
||||
break;
|
||||
case 0x40: // OFF
|
||||
strobe_off();
|
||||
flicker_off = true; // stop flickering
|
||||
printf("light off\n");
|
||||
break;
|
||||
case 0x20: // red
|
||||
case 0x10: // orange
|
||||
case 0x30: // orange
|
||||
case 0x08: // orange
|
||||
case 0x28: // organe
|
||||
printf("1 strobe\n");
|
||||
strobe_play(strobe1, LENGTH(strobe1));
|
||||
break;
|
||||
case 0xa0: // green
|
||||
case 0x90: // green
|
||||
case 0xb0: // green
|
||||
case 0x88: // green
|
||||
case 0xa8: // green
|
||||
printf("2 strobes\n");
|
||||
strobe_play(strobe2, LENGTH(strobe2));
|
||||
break;
|
||||
case 0x60: // blue
|
||||
case 0x50: // blue
|
||||
case 0x70: // blue
|
||||
case 0x48: // blue
|
||||
case 0x68: // blue
|
||||
printf("3 strobes\n");
|
||||
strobe_play(strobe3, LENGTH(strobe3));
|
||||
break;
|
||||
case 0xe0: // W
|
||||
case 0xd0: // flash
|
||||
case 0xc8: // fade
|
||||
case 0xf0: // strobe
|
||||
printf("5 strobes\n");
|
||||
strobe_play(strobe5, LENGTH(strobe5));
|
||||
break;
|
||||
case 0xe8: // smooth
|
||||
printf("10 strobes\n");
|
||||
strobe_play(strobe10, LENGTH(strobe10));
|
||||
break;
|
||||
default:
|
||||
printf("unknown code\n");
|
||||
break;
|
||||
}
|
||||
} else if (0x407f == code->address) { // iDual remote
|
||||
switch (code->command) { // choose animation depending on button
|
||||
case 0x08: // brightness down
|
||||
strobe_toggle();
|
||||
printf("toggle strobe\n");
|
||||
break;
|
||||
case 0x90: // brightness up
|
||||
printf("start flickering\n");
|
||||
flicker_off = false; // let flickering happen in main loop
|
||||
break;
|
||||
case 0x80: // ON
|
||||
strobe_on();
|
||||
printf("light on\n");
|
||||
break;
|
||||
case 0x40: // OFF
|
||||
strobe_off();
|
||||
flicker_off = true; // stop flickering
|
||||
printf("light off\n");
|
||||
break;
|
||||
case 0x88: // left
|
||||
case 0x48:
|
||||
case 0xc8:
|
||||
case 0x28:
|
||||
printf("1 strobe\n");
|
||||
strobe_play(strobe1, LENGTH(strobe1));
|
||||
break;
|
||||
case 0x68: // middle-left
|
||||
case 0xe8:
|
||||
case 0x18:
|
||||
case 0x98:
|
||||
printf("2 strobes\n");
|
||||
strobe_play(strobe2, LENGTH(strobe2));
|
||||
break;
|
||||
case 0x50: // middle-right
|
||||
case 0xd0:
|
||||
case 0x30:
|
||||
case 0xb0:
|
||||
printf("3 strobes\n");
|
||||
strobe_play(strobe3, LENGTH(strobe3));
|
||||
break;
|
||||
case 0xc0: // right
|
||||
case 0x20:
|
||||
case 0xa0:
|
||||
case 0x60:
|
||||
printf("5 strobes\n");
|
||||
strobe_play(strobe5, LENGTH(strobe5));
|
||||
break;
|
||||
case 0x70: // circle
|
||||
case 0xa8:
|
||||
case 0x58:
|
||||
case 0xf0:
|
||||
case 0xe0:
|
||||
case 0x10:
|
||||
printf("10 strobes\n");
|
||||
strobe_play(strobe10, LENGTH(strobe10));
|
||||
break;
|
||||
default:
|
||||
printf("unknown code\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
printf("unknown remote\n");
|
||||
}
|
||||
}
|
||||
|
||||
/** program entry point
|
||||
* this is the firmware function started by the micro-controller
|
||||
*/
|
||||
|
@ -243,6 +437,27 @@ void main(void)
|
|||
{
|
||||
rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock
|
||||
|
||||
board_setup(); // setup board
|
||||
uart_setup(); // setup USART (for printing)
|
||||
usb_cdcacm_setup(); // setup USB CDC ACM (for printing and DFU)
|
||||
// setup strobe pin
|
||||
rcc_periph_clock_enable(RCC_GPIO(STROBE_PORT)); // enable clock for GPIO port peripheral
|
||||
#if STROBE_ON
|
||||
gpio_set_mode(GPIO(STROBE_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(STROBE_PIN)); // set pin to output push-pull do drive strobe signal
|
||||
#else
|
||||
gpio_set_mode(GPIO(STROBE_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(STROBE_PIN)); // set pin to output open-drain do enable strobe
|
||||
#endif
|
||||
strobe_off(); // switch off strobe per defaulf
|
||||
ir_nec_setup(true); // setup ID NEC code decoder
|
||||
printf("\nwelcome to the CuVoodoo STM32F1 spark strober\n"); // print welcome message
|
||||
|
||||
// setup RTC
|
||||
printf("setup internal RTC: ");
|
||||
rtc_auto_awake(RCC_LSE, 32768-1); // ensure internal RTC is on, uses the 32.678 kHz LSE, and the prescale is set to our tick speed, else update backup registers accordingly (power off the micro-controller for the change to take effect)
|
||||
rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds"
|
||||
nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt
|
||||
printf("OK\n");
|
||||
|
||||
#if DEBUG
|
||||
// enable functionalities for easier debug
|
||||
DBGMCU_CR |= DBGMCU_CR_IWDG_STOP; // stop independent watchdog counter when code is halted
|
||||
|
@ -256,11 +471,6 @@ void main(void)
|
|||
iwdg_start(); // start independent watchdog
|
||||
#endif
|
||||
|
||||
board_setup(); // setup board
|
||||
uart_setup(); // setup USART (for printing)
|
||||
usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
|
||||
printf("\nwelcome to the CuVoodoo STM32F1 example application\n"); // print welcome message
|
||||
|
||||
#if !(DEBUG)
|
||||
// show watchdog information
|
||||
printf("setup watchdog: %.2fs",WATCHDOG_PERIOD/1000.0);
|
||||
|
@ -273,12 +483,6 @@ void main(void)
|
|||
}
|
||||
#endif
|
||||
|
||||
// setup RTC
|
||||
printf("setup internal RTC: ");
|
||||
rtc_auto_awake(RCC_LSE, 32768-1); // ensure internal RTC is on, uses the 32.678 kHz LSE, and the prescale is set to our tick speed, else update backup registers accordingly (power off the micro-controller for the change to take effect)
|
||||
rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds"
|
||||
nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt
|
||||
printf("OK\n");
|
||||
|
||||
// setup terminal
|
||||
terminal_prefix = ""; // set default prefix
|
||||
|
@ -288,15 +492,16 @@ void main(void)
|
|||
// start main loop
|
||||
bool action = false; // if an action has been performed don't go to sleep
|
||||
button_flag = false; // reset button flag
|
||||
bool flicker_on = false; // if the flicker strobe is currently on
|
||||
while (true) { // infinite loop
|
||||
iwdg_reset(); // kick the dog
|
||||
while (user_input_available) { // user input is available
|
||||
if (user_input_available) { // user input is available
|
||||
action = true; // action has been performed
|
||||
led_toggle(); // toggle LED
|
||||
char c = user_input_get(); // store receive character
|
||||
terminal_send(c); // send received character to terminal
|
||||
}
|
||||
while (button_flag) { // user pressed button
|
||||
if (button_flag) { // user pressed button
|
||||
action = true; // action has been performed
|
||||
printf("button pressed\n");
|
||||
led_toggle(); // toggle LED
|
||||
|
@ -305,13 +510,41 @@ void main(void)
|
|||
}
|
||||
button_flag = false; // reset flag
|
||||
}
|
||||
while (rtc_internal_tick_flag) { // the internal RTC ticked
|
||||
if (rtc_internal_tick_flag) { // the internal RTC ticked
|
||||
rtc_internal_tick_flag = false; // reset flag
|
||||
action = true; // action has been performed
|
||||
#if !defined(BLUE_PILL) // on the blue pill the LED is close to the 32.768 kHz oscillator and heavily influences it
|
||||
led_toggle(); // toggle LED (good to indicate if main function is stuck)
|
||||
//led_toggle(); // toggle LED (good to indicate if main function is stuck)
|
||||
#endif
|
||||
}
|
||||
if (ir_nec_code_received_flag) { // IR code received
|
||||
ir_nec_code_received_flag = false; // reset flag
|
||||
led_on(); // notify user we received a code
|
||||
printf("IR NEC code received: addr=%+04x, cmd=%+02x%s\n", ir_nec_code_received.address, ir_nec_code_received.command, ir_nec_code_received.repeat ? " (repeat)" : "");
|
||||
if (!ir_nec_code_received.repeat) { // ignore repeated codes
|
||||
ir_action(&ir_nec_code_received); // handle IR code
|
||||
}
|
||||
led_off(); // notify user we received a code
|
||||
}
|
||||
if (!flicker_off) {
|
||||
action = true; // prevent going to sleep
|
||||
uint32_t time = rand();
|
||||
if (flicker_on) {
|
||||
time %= 1000;
|
||||
if (time < 100) {
|
||||
time = 100;
|
||||
}
|
||||
strobe_off();
|
||||
} else {
|
||||
time %= 100;
|
||||
if (time < 10) {
|
||||
time = 10;
|
||||
}
|
||||
strobe_on();
|
||||
}
|
||||
sleep_ms(time);
|
||||
flicker_on = !flicker_on;
|
||||
}
|
||||
if (action) { // go to sleep if nothing had to be done, else recheck for activity
|
||||
action = false;
|
||||
} else {
|
||||
|
|
2
global.h
2
global.h
|
@ -20,7 +20,7 @@
|
|||
#pragma once
|
||||
|
||||
/** enable debugging functionalities */
|
||||
#define DEBUG true
|
||||
#define DEBUG false
|
||||
|
||||
/** get the length of an array */
|
||||
#define LENGTH(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
|
|
@ -1,611 +0,0 @@
|
|||
/* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/** library to communicate with an SD card flash memory using the SPI mode (code)
|
||||
* @file flash_sdcard.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: SPI @ref flash_sdcard_spi
|
||||
* @warning all calls are blocking
|
||||
* @implements SD Specifications, Part 1, Physical Layer, Simplified Specification, Version 6.00, 10 April 10 2017
|
||||
* @todo use SPI unidirectional mode, use DMA, force/wait going to idle state when initializing, filter out reserved values, check sector against size
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdlib.h> // general utilities
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/spi.h> // SPI library
|
||||
|
||||
#include "global.h" // global utilities
|
||||
#include "flash_sdcard.h" // SD card header and definitions
|
||||
|
||||
/** @defgroup flash_sdcard_spi SPI used to communication with SD card
|
||||
* @{
|
||||
*/
|
||||
#define FLASH_SDCARD_SPI 1 /**< SPI peripheral */
|
||||
/** @} */
|
||||
|
||||
/** if the card has been initialized successfully */
|
||||
static bool initialized = false;
|
||||
/** maximum N_AC value (in 8-clock cycles) (time between the response token R1 and data block when reading data (see section 7.5.4)
|
||||
* @note this is set to N_CR until we can read CSD (see section 7.2.6)
|
||||
*/
|
||||
static uint32_t n_ac = 8;
|
||||
/** is it a Standard Capacity SD card (true), or High Capacity SD cards (false)
|
||||
* @note this is indicated in the Card Capacity Status bit or OCR (set for high capacity)
|
||||
* @note this is important for addressing: for standard capacity cards the address is the byte number, for high capacity cards it is the 512-byte block number
|
||||
*/
|
||||
static bool sdsc = false;
|
||||
/** size of card in bytes */
|
||||
static uint64_t sdcard_size = 0;
|
||||
/** size of an erase block bytes */
|
||||
static uint32_t erase_size = 0;
|
||||
|
||||
/** table for CRC-7 calculation for the command messages (see section 4.5)
|
||||
* @note faster than calculating the CRC and doesn't cost a lot of space
|
||||
* @note generated using pycrc --width=7 --poly=0x09 --reflect-in=false --reflect-out=false --xor-in=0x00 --xor-out=0x00 --generate=table
|
||||
*/
|
||||
static const uint8_t crc7_table[] = {
|
||||
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
|
||||
0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e,
|
||||
0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
|
||||
0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c,
|
||||
0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13,
|
||||
0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
|
||||
0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
|
||||
0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38,
|
||||
0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
|
||||
0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f,
|
||||
0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04,
|
||||
0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
|
||||
0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52,
|
||||
0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b,
|
||||
0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
|
||||
0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79
|
||||
};
|
||||
|
||||
/** wait one SPI round (one SPI word)
|
||||
*/
|
||||
static void flash_sdcard_spi_wait(void)
|
||||
{
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), 0xffff); // send not command token (i.e. starting with 1)
|
||||
}
|
||||
|
||||
/** read one SPI word
|
||||
* @return SPI word read
|
||||
*/
|
||||
static uint16_t flash_sdcard_spi_read(void)
|
||||
{
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), 0xffff); // send not command token (i.e. starting with 1)
|
||||
(void)SPI_DR(SPI(FLASH_SDCARD_SPI)); // clear RXNE flag (by reading previously received data (not done by spi_read or spi_xref)
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until Tx buffer is empty before clearing the (previous) RXNE flag
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_RXNE)); // wait for next data to be available
|
||||
return SPI_DR(SPI(FLASH_SDCARD_SPI)); // return received adat
|
||||
}
|
||||
|
||||
/** test if card is present
|
||||
* @return if card has been detected
|
||||
* @note this use the SD card detection mechanism (CD/CS is high card is inserted due to the internal 50 kOhm resistor)
|
||||
*/
|
||||
static bool flash_sdcard_card_detect(void)
|
||||
{
|
||||
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(FLASH_SDCARD_SPI)); // enable clock for NSS pin port peripheral for SD card CD signal
|
||||
gpio_set_mode(SPI_NSS_PORT(FLASH_SDCARD_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set NSS pin as input to read CD signal
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // pull pin low to avoid false positive when card in not inserted
|
||||
return (0!=gpio_get(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI))); // read CD signal: is card is present the internal 50 kOhm pull-up resistor will override our 1 MOhm pull-down resistor and set the signal high (see section 6.2)
|
||||
}
|
||||
|
||||
/** transmit command token
|
||||
* @param[in] index command index
|
||||
* @param[in] argument command argument
|
||||
*/
|
||||
static void flash_sdcard_send_command(uint8_t index, uint32_t argument)
|
||||
{
|
||||
uint8_t command[5] = { 0x40+(index&0x3f), argument>>24, argument>>16, argument>>8, argument>>0 }; // commands are 5 bytes long, plus 1 bytes of CRC (see section 7.3.1.1)
|
||||
uint8_t crc7 = 0x00; // CRC-7 checksum for command message
|
||||
// send command
|
||||
for (uint8_t i=0; i<LENGTH(command); i++) {
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), command[i]); // send data
|
||||
crc7 = (crc7_table[((crc7<<1)^command[i])])&0x7f; // update checksum
|
||||
}
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), (crc7<<1)+0x01); // send CRC value (see section 7.3.1.1)
|
||||
}
|
||||
|
||||
/** transmit command token and receive response token
|
||||
* @param[in] index command index
|
||||
* @param[in] argument command argument
|
||||
* @param[out] response response data to read (if no error occurred)
|
||||
* @param[in] size size of response to read
|
||||
* @return response token R1 or 0xff if error occurred or card is not present
|
||||
*/
|
||||
static uint8_t flash_sdcard_command_response(uint8_t index, uint32_t argument, uint8_t* response, size_t size)
|
||||
{
|
||||
// send command token
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
|
||||
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.1.1)
|
||||
flash_sdcard_send_command(index, argument); // send command token
|
||||
|
||||
// get response token R1
|
||||
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
|
||||
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
|
||||
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
|
||||
}
|
||||
if (0x00==(r1&0xfe) && 0!=size && NULL!=response) { // we have to read a response
|
||||
for (size_t i=0; i<size; i++) {
|
||||
response[i] = flash_sdcard_spi_read(); // get byte
|
||||
}
|
||||
}
|
||||
|
||||
// end communication
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
|
||||
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
|
||||
return r1;
|
||||
}
|
||||
|
||||
/** read a data block
|
||||
* @param[out] data data block to read (if no error occurred)
|
||||
* @param[in] size size of response to read (a multiple of 2)
|
||||
* @return 0 if succeeded, else control token (0xff for other errors)
|
||||
*/
|
||||
static uint8_t flash_sdcard_read_block(uint8_t* data, size_t size)
|
||||
{
|
||||
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
uint8_t token = 0xff; // to save the control block token (see section 7.3.3)
|
||||
for (uint32_t i=0; i<n_ac && token==0xff; i++) { // wait for N_AC before reading data block (see section 7.5.2.1)
|
||||
token = flash_sdcard_spi_read(); // get control token (see section 7.3.3)
|
||||
}
|
||||
if (0==(token&0xf0)) { // data error token received (see section 7.3.3.3)
|
||||
if (0==(token&0x0f)) { // unknown error
|
||||
token = 0xff;
|
||||
}
|
||||
} else if (0xfe==token) { // start block token received (see section 7.3.3.2)
|
||||
// switch to 16-bits SPI data frame so we can use use built-in CRC-16
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
|
||||
spi_set_dff_16bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 16 bits
|
||||
SPI_CRC_PR(FLASH_SDCARD_SPI) = 0x1021; // set CRC-16-CCITT polynomial for data blocks (x^16+x^12+x^5+1) (see section 7.2.3)
|
||||
spi_enable_crc(SPI(FLASH_SDCARD_SPI)); // enable and clear CRC
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
// get block data (ideally use DMA, but switching makes it more complex and this part doesn't take too much time)
|
||||
for (size_t i=0; i<size/2; i++) {
|
||||
uint16_t word = flash_sdcard_spi_read(); // get word
|
||||
data[i*2+0] = (word>>8); // save byte
|
||||
data[i*2+1] = (word>>0); // save byte
|
||||
}
|
||||
flash_sdcard_spi_read(); // read CRC (the CRC after the data block should clear the computed CRC)
|
||||
if (SPI_CRC_RXR(FLASH_SDCARD_SPI)) { // CRC is wrong
|
||||
token = 0xff;
|
||||
} else { // no error occurred
|
||||
token = 0;
|
||||
}
|
||||
// switch back to 8-bit SPI frames
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
|
||||
spi_disable_crc(SPI(FLASH_SDCARD_SPI)); // disable CRC since we don't use it anymore (and this allows us to clear the CRC next time we use it)
|
||||
spi_set_dff_8bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 8 bits
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
} else { // start block token not received
|
||||
token = 0xff;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/** write a data block
|
||||
* @param[in] data data block to write
|
||||
* @param[in] size size of response to read (a multiple of 2)
|
||||
* @return data response token (0xff for other errors)
|
||||
*/
|
||||
static uint8_t flash_sdcard_write_block(uint8_t* data, size_t size)
|
||||
{
|
||||
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), 0xfe); // send start block token (see section 7.3.3.2)
|
||||
|
||||
// switch to 16-bits SPI data frame so we can use use built-in CRC-16
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
|
||||
spi_set_dff_16bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 16 bits
|
||||
SPI_CRC_PR(FLASH_SDCARD_SPI) = 0x1021; // set CRC-16-CCITT polynomial for data blocks (x^16+x^12+x^5+1) (see section 7.2.3)
|
||||
spi_enable_crc(SPI(FLASH_SDCARD_SPI)); // enable and clear CRC
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
// send block data (ideally use DMA, but switching makes it more complex and this part doesn't take too much time)
|
||||
for (size_t i=0; i<size/2; i++) {
|
||||
uint16_t word = (data[i*2+0]<<8)+data[i*2+1]; // prepare SPI frame
|
||||
spi_send(SPI(FLASH_SDCARD_SPI), word); // senf data frame
|
||||
}
|
||||
spi_set_next_tx_from_crc(SPI(FLASH_SDCARD_SPI)); // send CRC
|
||||
// switch back to 8-bit SPI frames
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
|
||||
spi_set_next_tx_from_buffer(SPI(FLASH_SDCARD_SPI)); // don't send CRC
|
||||
spi_disable_crc(SPI(FLASH_SDCARD_SPI)); // disable CRC since we don't use it anymore (and this allows us to clear the CRC next time we use it)
|
||||
spi_set_dff_8bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 8 bits
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
|
||||
uint8_t token = 0xff;
|
||||
while (0x01!=(token&0x11)) {
|
||||
token = flash_sdcard_spi_read(); // get data response token (see section 7.3.3.1)
|
||||
}
|
||||
while (0==flash_sdcard_spi_read()); // wait N_EC while the card is busy programming the data
|
||||
|
||||
return token;
|
||||
}
|
||||
/** get card status
|
||||
* @param[out] status SD status (512 bits)
|
||||
* @return response token R2 or 0xffff if error occurred or card is not present
|
||||
*/
|
||||
static uint16_t flash_sdcard_status(uint8_t* status)
|
||||
{
|
||||
// send CMD55 (APP_CMD) to issue following application command (see table 7-4)
|
||||
uint8_t r1 = flash_sdcard_command_response(55, 0, NULL, 0); // (see table 7-3)
|
||||
if ((r1&0xfe)) { // error occurred, not in idle state
|
||||
return false;
|
||||
}
|
||||
|
||||
// send ACMD13 command
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
|
||||
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
|
||||
flash_sdcard_send_command(13, 0); // send ACMD13 (SD_STATUS) (see table 7-4)
|
||||
|
||||
// get response token R2
|
||||
uint16_t r2 = 0xffff; // response token R2 (see section 7.3.2.3)
|
||||
for (uint8_t i=0; i<8 && r2&0x8000; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
|
||||
r2 = (flash_sdcard_spi_read()<<8); // get first byte of response (see section 7.3.2.1)
|
||||
}
|
||||
if (0==(r2&0x8000)) { // got the first byte
|
||||
r2 += flash_sdcard_spi_read(); // read second byte (see 7.3.2.3)
|
||||
}
|
||||
|
||||
// get data block
|
||||
if (0==r2) { // no error
|
||||
if (flash_sdcard_read_block(status, 64)) { // read 512 bits data block containing SD status
|
||||
r2 |= (1<<11); // set communication error
|
||||
}
|
||||
}
|
||||
|
||||
// end communication
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
|
||||
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
|
||||
return r2;
|
||||
}
|
||||
|
||||
/** transmit command token, receive response token and data block
|
||||
* @param[in] index command index
|
||||
* @param[in] argument command argument
|
||||
* @param[out] data data block to read (if no error occurred)
|
||||
* @param[in] size size of data to read (a multiple of 2)
|
||||
* @return response token R1 or 0xff if error occurred or card is not present
|
||||
*/
|
||||
static uint8_t flash_sdcard_data_read(uint8_t index, uint32_t argument, uint8_t* data, size_t size)
|
||||
{
|
||||
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
// send command token
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
|
||||
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
|
||||
flash_sdcard_send_command(index, argument); // send command token
|
||||
|
||||
// get response token R1
|
||||
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
|
||||
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
|
||||
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
|
||||
}
|
||||
|
||||
// get data block
|
||||
if (0x00==r1) { // we can read a data block
|
||||
if (flash_sdcard_read_block(data, size)) { // read data block
|
||||
r1 |= (1<<3); // set communication error
|
||||
}
|
||||
}
|
||||
|
||||
// end communication
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
|
||||
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
|
||||
return r1;
|
||||
}
|
||||
|
||||
/** transmit command token, receive response token and write data block
|
||||
* @param[in] index command index
|
||||
* @param[in] argument command argument
|
||||
* @param[out] data data block to write
|
||||
* @param[in] size size of data to write (a multiple of 2)
|
||||
* @return data response token, or 0xff if error occurred or card is not present
|
||||
* @note at the end of a write operation the SD status should be check to ensure no error occurred during programming
|
||||
*/
|
||||
static uint8_t flash_sdcard_data_write(uint8_t index, uint32_t argument, uint8_t* data, size_t size)
|
||||
{
|
||||
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) write odd number of bytes
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
// send command token
|
||||
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
|
||||
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
|
||||
flash_sdcard_send_command(index, argument); // send command token
|
||||
|
||||
// get response token R1
|
||||
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
|
||||
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
|
||||
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
|
||||
}
|
||||
|
||||
// write data block
|
||||
uint8_t drt = 0xff; // data response token (see section 7.3.3.1)
|
||||
if (0x00==r1) { // we have to write the data block
|
||||
drt = flash_sdcard_write_block(data, size); // write data block
|
||||
}
|
||||
|
||||
// end communication
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
|
||||
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
|
||||
|
||||
return drt;
|
||||
}
|
||||
|
||||
bool flash_sdcard_setup(void)
|
||||
{
|
||||
// reset values
|
||||
initialized = false;
|
||||
n_ac = 8;
|
||||
sdcard_size = 0;
|
||||
erase_size = 0;
|
||||
|
||||
// check if card is present
|
||||
if (!flash_sdcard_card_detect()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// configure SPI peripheral
|
||||
rcc_periph_clock_enable(RCC_SPI_SCK_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for clock signal
|
||||
gpio_set_mode(SPI_SCK_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_SCK_PIN(FLASH_SDCARD_SPI)); // set SCK as output (clock speed will be negotiated later)
|
||||
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for MOSI signal
|
||||
gpio_set_mode(SPI_MOSI_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(FLASH_SDCARD_SPI)); // set MOSI as output
|
||||
rcc_periph_clock_enable(RCC_SPI_MISO_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for MISO signal
|
||||
gpio_set_mode(SPI_MISO_PORT(FLASH_SDCARD_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_MISO_PIN(FLASH_SDCARD_SPI)); // set MISO as input
|
||||
gpio_set(SPI_MISO_PORT(FLASH_SDCARD_SPI), SPI_MISO_PIN(FLASH_SDCARD_SPI)); // pull pin high to detect when the card is not answering (or not present) since responses always start with MSb 0
|
||||
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for NSS (CS) signal
|
||||
gpio_set_mode(SPI_NSS_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set NSS (CS) as output
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
|
||||
rcc_periph_clock_enable(RCC_SPI(FLASH_SDCARD_SPI)); // enable clock for SPI peripheral
|
||||
spi_reset(SPI(FLASH_SDCARD_SPI)); // clear SPI values to default
|
||||
spi_init_master(SPI(FLASH_SDCARD_SPI), SPI_CR1_BAUDRATE_FPCLK_DIV_256, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST); // initialise SPI as master, divide clock by 256 (72E6/256=281 kHz) since maximum SD card clock frequency (fOD, see section 7.8/6.6.6) during initial card-identification mode is 400 kHz (maximum SPI PCLK clock is 72 Mhz, depending on which SPI is used), set clock polarity to idle low (not that important), set clock phase to do bit change on falling edge (from SD card spec, polarity depends on clock phase), use 8 bits frames (as per spec), use MSb first
|
||||
spi_set_full_duplex_mode(SPI(FLASH_SDCARD_SPI)); // ensure we are in full duplex mode
|
||||
spi_enable_software_slave_management(SPI(FLASH_SDCARD_SPI)); // control NSS (CS) manually
|
||||
spi_set_nss_high(SPI(FLASH_SDCARD_SPI)); // set NSS high (internally) so we can output
|
||||
spi_disable_ss_output(SPI(FLASH_SDCARD_SPI)); // disable NSS output since we control CS manually
|
||||
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
|
||||
// sadly we can't use the interrupts as events to sleep (WFE) since sleep disables also communication (e.g. going to sleep until Rx buffer is not empty prevents transmission)
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI
|
||||
|
||||
// start card-identification (see section 7.2.1/4.2)
|
||||
uint8_t r1 = 0;
|
||||
// send CMD0 (GO_IDLE_START) to start the card identification (see section 7.2.1)
|
||||
r1 = flash_sdcard_command_response(0, 0, NULL, 0); // (see table 7-3)
|
||||
if (0x01!=r1) { // error occurred, not in idle state
|
||||
return false;
|
||||
}
|
||||
// send CMD8 (SEND_IF_COND) to inform about voltage (1: 2.7-3.6V, aa: recommended check pattern) (see section 7.2.1)
|
||||
uint8_t r7[4] = {0}; // to store response toke R7 (see section 7.3.2.6)
|
||||
r1 = flash_sdcard_command_response(8, 0x000001aa, r7, sizeof(r7)); // (see table 7-3)
|
||||
if (0x01==r1) { // command supported, in idle state
|
||||
if (!(r7[2]&0x1)) { // 2.7-3.6V not supported (see table 5-1)
|
||||
return false;
|
||||
} else if (0xaa!=r7[3]) { // recommended pattern not returned (see section 4.3.13)
|
||||
return false;
|
||||
}
|
||||
} else if (0x05!=r1) { // illegal command (cards < physical spec v2.0 don't support CMD8) (see section 7.2.1)
|
||||
return false;
|
||||
}
|
||||
// send CMD58 (READ_OCR) to read Operation Conditions Register (see section 7.2.1)
|
||||
uint8_t r3[4] = {0}; // to store response token R3 (see section 7.3.2.4)
|
||||
r1 = flash_sdcard_command_response(58, 0, r3, sizeof(r3)); // (see table 7-3)
|
||||
if (0x01!=r1) { // error occurred, not in idle state
|
||||
return false;
|
||||
} else if (!(r3[1]&0x30)) { // 3.3V not supported (see table 5-1)
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
// send CMD55 (APP_CMD) to issue following application command (see table 7-4)
|
||||
r1 = flash_sdcard_command_response(55, 0, NULL, 0); // (see table 7-3)
|
||||
if (0x01!=r1) { // error occurred, not in idle state
|
||||
return false;
|
||||
}
|
||||
// send ACMD41 (SD_SEND_OP_COND) with Host Capacity Support (0b: SDSC Only Host, 1b: SDHC or SDXC Supported) (see section 7.2.1)
|
||||
r1 = flash_sdcard_command_response(41, 0x40000000, NULL, 0); // (see table 7-4)
|
||||
if (r1&0xfe) { // error occurred
|
||||
return false;
|
||||
}
|
||||
} while (0x00!=r1); // wait until card is ready (see section 7.2.1)
|
||||
// send CMD58 (READ_OCR) to read Card Capacity Status (CCS) (see section 7.2.1)
|
||||
r1 = flash_sdcard_command_response(58, 0, r3, sizeof(r3)); // (see table 7-3)
|
||||
if (r1) { // error occurred
|
||||
return false;
|
||||
}
|
||||
// card power up status bit (bit 31) is set when power up is complete (see table 5-1)
|
||||
if (0x00==(r3[0]&0x80)) {
|
||||
return false;
|
||||
}
|
||||
sdsc = (0==(r3[0]&0x40)); // CCS is bit 30 in OCR (see table 5-1)
|
||||
// now the card identification is complete and we should be in data-transfer mode (see figure 7-1)
|
||||
|
||||
// we can switch clock frequency to fPP (max. 25 MHz) (see section 4.3/6.6.6)
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change clock speed
|
||||
spi_set_baudrate_prescaler(SPI(FLASH_SDCARD_SPI), SPI_CR1_BR_FPCLK_DIV_4); // set clock speed to 18 MHz (72/4=18, < 25 MHz)
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
|
||||
// send CMD9 (SEND_CSD) to get Card Specific Data (CSD) and calculate N_AC (see section 7.2.6)
|
||||
uint8_t csd[16] = {0}; // CSD response (see chapter 7.2.6)
|
||||
r1 = flash_sdcard_data_read(9, 0, csd, sizeof(csd)); // (see table 7-3)
|
||||
if (r1) { // error occurred
|
||||
return false;
|
||||
}
|
||||
// check if CSD structure version matches capacity (see section 5.3.1)
|
||||
if ((sdsc && (csd[0]>>6)) || (!sdsc && 0==(csd[0]>>6))) {
|
||||
return false;
|
||||
}
|
||||
// calculate N_AC value (we use our set minimum frequency 16 MHz to calculate time)
|
||||
if (sdsc) { // calculate N_AC using TAAC and NSAC
|
||||
static const float TAAC_UNITS[] = {1E-9, 10E-9, 100E-9, 1E-6, 10E-6, 100E-6, 1E-3, 10E-3}; // (see table 5-5)
|
||||
static const float TAAC_VALUES[] = {10.0, 1.0, 1.2, 1.3, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 7.0, 8.0}; // (see table 5-5)
|
||||
double taac = TAAC_VALUES[(csd[1]>>2)&0xf]*TAAC_UNITS[csd[1]&0x7]; // time in ns
|
||||
n_ac=100*((taac*16E6)+(csd[2]*100))/8; // (see section 7.5.4)
|
||||
} else { // value is fixed to 100 ms
|
||||
n_ac=100E-3*16E6/8;
|
||||
}
|
||||
// calculate card size
|
||||
if (sdsc) { // see section 5.3.2
|
||||
uint16_t c_size = (((uint16_t)csd[6]&0x03)<<10)+((uint16_t)csd[7]<<2)+(csd[8]>>6);
|
||||
uint8_t c_size_mutl = ((csd[9]&0x03)<<1)+((csd[10]&0x80)>>7);
|
||||
uint8_t read_bl_len = (csd[5]&0x0f);
|
||||
sdcard_size = ((c_size+1)*(1UL<<(c_size_mutl+2)))*(1UL<<read_bl_len);
|
||||
} else { // see section 5.3.3
|
||||
uint32_t c_size = ((uint32_t)(csd[7]&0x3f)<<16)+((uint16_t)csd[8]<<8)+csd[9];
|
||||
sdcard_size = (c_size+1)*(512<<10);
|
||||
}
|
||||
// calculate erase size
|
||||
if (sdsc) { // see section 5.3.2
|
||||
erase_size = (((csd[10]&0x3f)<<1)+((csd[11]&0x80)>>7)+1)<<(((csd[12]&0x03)<<2)+(csd[13]>>6));
|
||||
} else {
|
||||
uint8_t status[64] = {0}; // SD status (see section 4.10.2)
|
||||
uint16_t r2 = flash_sdcard_status(status); // get status (see table 7-4)
|
||||
if (r2) { // error occurred
|
||||
return false;
|
||||
}
|
||||
erase_size = (8192UL<<(status[10]>>4)); // calculate erase size (see table 4-44, section 4.10.2.4)
|
||||
}
|
||||
|
||||
// ensure block length is 512 bytes for SDSC (should be per default) to we match SDHC/SDXC block size
|
||||
if (sdsc) {
|
||||
r1 = flash_sdcard_command_response(16, 512, NULL, 0); // set block size using CMD16 (SET_BLOCKLEN) (see table 7-3)
|
||||
if (r1) { // error occurred
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// try to switch to high speed mode (see section 7.2.14/4.3.10)
|
||||
if (csd[4]&0x40) { // ensure CMD6 is supported by checking if command class 10 is set
|
||||
uint32_t n_ac_back = n_ac; // backup N_AC
|
||||
n_ac = 100E-3*16E6/8; // temporarily set timeout to 100 ms (see section 4.3.10.1)
|
||||
// query access mode (group function 1) to check if high speed is supported (fPP=50MHz at 3.3V, we can be faster)
|
||||
uint8_t fnc[64] = {0}; // function status response (see table 4-12)
|
||||
r1 = flash_sdcard_data_read(6, 0x00fffff1, fnc, sizeof(fnc)); // check high speed function using CMD6 (SWITCH_FUNC) to check (mode 0) access mode (function group 1) (see table 7-3/4-30)
|
||||
if (r1) { // error occurred
|
||||
return false;
|
||||
}
|
||||
if (0x1==(fnc[16]&0x0f)) { // we can to access mode function 1 (see table 4-12)
|
||||
r1 = flash_sdcard_data_read(6, 0x80fffff1, fnc, sizeof(fnc)); // switch to high speed function using CMD6 (SWITCH_FUNC) to switch (mode 1) access mode (function group 1) (see table 7-3/4-30)
|
||||
if (r1) { // error occurred
|
||||
return false;
|
||||
}
|
||||
if (0x1!=(fnc[16]&0x0f)) { // could not switch to high speed
|
||||
return false;
|
||||
}
|
||||
// we can switch clock frequency to fPP (max. 50 MHz) (see section 6.6.7)
|
||||
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
|
||||
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
|
||||
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change clock speed
|
||||
spi_set_baudrate_prescaler(SPI(FLASH_SDCARD_SPI), SPI_CR1_BR_FPCLK_DIV_2); // set clock speed to 36 MHz (72/2=36 < 50 MHz)
|
||||
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
|
||||
n_ac_back /= 2; // since we go twice faster the N_AC timeout has to be halved
|
||||
}
|
||||
n_ac = n_ac_back; // restore N_AC
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
|
||||
return initialized;
|
||||
}
|
||||
|
||||
uint64_t flash_sdcard_size(void)
|
||||
{
|
||||
return sdcard_size;
|
||||
}
|
||||
|
||||
uint32_t flash_sdcard_erase_size(void)
|
||||
{
|
||||
return erase_size;
|
||||
}
|
||||
|
||||
bool flash_sdcard_read_data(uint32_t block, uint8_t* data)
|
||||
{
|
||||
if (NULL==data) {
|
||||
return false;
|
||||
}
|
||||
if (sdsc) { // the address for standard capacity cards must be provided in bytes
|
||||
if (block>UINT32_MAX/512) { // check for integer overflow
|
||||
return false;
|
||||
} else {
|
||||
block *= 512; // calculate byte address from block address
|
||||
}
|
||||
}
|
||||
return (0==flash_sdcard_data_read(17, block, data, 512)); // read single data block using CMD17 (READ_SINGLE_BLOCK) (see table 7-3)
|
||||
}
|
||||
|
||||
bool flash_sdcard_write_data(uint32_t block, uint8_t* data)
|
||||
{
|
||||
if (NULL==data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sdsc) { // the address for standard capacity cards must be provided in bytes
|
||||
if (block>UINT32_MAX/512) { // check for integer overflow
|
||||
return false;
|
||||
} else {
|
||||
block *= 512; // calculate byte address from block address
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t drt = flash_sdcard_data_write(24, block, data, 512); // write single data block using CMD24 (WRITE_SINGLE_BLOCK) (see table 7-3)
|
||||
if (0x05!=(drt&0x1f)) { // write block failed
|
||||
return false;
|
||||
}
|
||||
|
||||
// get status to check if programming succeeded
|
||||
uint8_t r2[1] = {0}; // to store response token R2 (see section 7.3.2.3)
|
||||
uint8_t r1 = flash_sdcard_command_response(13, 0, r2, sizeof(r2)); // get SD status using CMD13 (SEND_STATUS) (see table 7-3)
|
||||
if (0x00!=r1) { // error occurred
|
||||
return false;
|
||||
} else if (r2[0]) { // programming error
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // programming succeeded
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/** library to communicate with an SD card flash memory using the SPI mode (API)
|
||||
* @file flash_sdcard.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: SPI @ref flash_sdcard_spi
|
||||
* @warning all calls are blocking
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** setup communication with SD card
|
||||
* @return if card has been initialized correctly
|
||||
*/
|
||||
bool flash_sdcard_setup(void);
|
||||
/** get size of SD card flash memory
|
||||
* @return size of SD card flash memory (in bytes)
|
||||
*/
|
||||
uint64_t flash_sdcard_size(void);
|
||||
/** get size of a erase block
|
||||
* @return size of a erase block (in bytes)
|
||||
*/
|
||||
uint32_t flash_sdcard_erase_size(void);
|
||||
/** read data on flash of SD card
|
||||
* @param[in] block address of data to read (in block in 512 bytes unit)
|
||||
* @param[out] data data block to read (with a size of 512 bytes)
|
||||
* @return if read succeeded
|
||||
*/
|
||||
bool flash_sdcard_read_data(uint32_t block, uint8_t* data);
|
||||
/** write data on flash of SD card
|
||||
* @param[in] block address of data to write (in block in 512 bytes unit)
|
||||
* @param[in] data data block to write (with a size of 512 bytes)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool flash_sdcard_write_data(uint32_t block, uint8_t* data);
|
633
lib/i2c_master.c
633
lib/i2c_master.c
|
@ -1,633 +0,0 @@
|
|||
/* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/** library to communicate using I2C as master (code)
|
||||
* @file i2c_master.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017-2018
|
||||
* @note peripherals used: I2C
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdlib.h> // general utilities
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/i2c.h> // I2C library
|
||||
|
||||
/* own libraries */
|
||||
#include "global.h" // global utilities
|
||||
#include "i2c_master.h" // I2C header and definitions
|
||||
|
||||
/** get RCC for I2C based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return RCC address for I2C peripheral
|
||||
*/
|
||||
static uint32_t RCC_I2C(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
return RCC_I2C1;
|
||||
break;
|
||||
case I2C2:
|
||||
return RCC_I2C2;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get RCC for GPIO port for SCL pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return RCC GPIO address
|
||||
*/
|
||||
static uint32_t RCC_GPIO_PORT_SCL(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
case I2C2:
|
||||
return RCC_GPIOB;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get RCC for GPIO port for SDA pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return RCC GPIO address
|
||||
*/
|
||||
static uint32_t RCC_GPIO_PORT_SDA(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
case I2C2:
|
||||
return RCC_GPIOB;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get GPIO port for SCL pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return GPIO address
|
||||
*/
|
||||
static uint32_t GPIO_PORT_SCL(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
|
||||
return GPIO_BANK_I2C1_RE_SCL;
|
||||
} else {
|
||||
return GPIO_BANK_I2C1_SCL;
|
||||
}
|
||||
break;
|
||||
case I2C2:
|
||||
return GPIO_BANK_I2C2_SCL;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get GPIO port for SDA pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return GPIO address
|
||||
*/
|
||||
static uint32_t GPIO_PORT_SDA(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
|
||||
return GPIO_BANK_I2C1_RE_SDA;
|
||||
} else {
|
||||
return GPIO_BANK_I2C1_SDA;
|
||||
}
|
||||
break;
|
||||
case I2C2:
|
||||
return GPIO_BANK_I2C2_SDA;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get GPIO pin for SCL pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return GPIO address
|
||||
*/
|
||||
static uint32_t GPIO_PIN_SCL(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
|
||||
return GPIO_I2C1_RE_SCL;
|
||||
} else {
|
||||
return GPIO_I2C1_SCL;
|
||||
}
|
||||
break;
|
||||
case I2C2:
|
||||
return GPIO_I2C2_SCL;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get GPIO pin for SDA pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return GPIO address
|
||||
*/
|
||||
static uint32_t GPIO_PIN_SDA(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
|
||||
return GPIO_I2C1_RE_SDA;
|
||||
} else {
|
||||
return GPIO_I2C1_SDA;
|
||||
}
|
||||
break;
|
||||
case I2C2:
|
||||
return GPIO_I2C2_SDA;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
void i2c_master_setup(uint32_t i2c, uint16_t frequency)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
// configure I2C peripheral
|
||||
rcc_periph_clock_enable(RCC_GPIO_PORT_SCL(i2c)); // enable clock for I2C I/O peripheral
|
||||
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // already put signal high to avoid small pulse
|
||||
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // setup I2C I/O pins
|
||||
rcc_periph_clock_enable(RCC_GPIO_PORT_SDA(i2c)); // enable clock for I2C I/O peripheral
|
||||
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // already put signal high to avoid small pulse
|
||||
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // setup I2C I/O pins
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function
|
||||
rcc_periph_clock_enable(RCC_I2C(i2c)); // enable clock for I2C peripheral
|
||||
i2c_reset(i2c); // reset peripheral domain
|
||||
i2c_peripheral_disable(i2c); // I2C needs to be disable to be configured
|
||||
I2C_CR1(i2c) |= I2C_CR1_SWRST; // reset peripheral
|
||||
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // clear peripheral reset
|
||||
if (0==frequency) { // don't allow null frequency
|
||||
frequency = 1;
|
||||
} else if (frequency>400) { // limit frequency to 400 kHz
|
||||
frequency = 400;
|
||||
}
|
||||
i2c_set_clock_frequency(i2c, rcc_apb1_frequency/1000000); // configure the peripheral clock to the APB1 freq (where it is connected to)
|
||||
if (frequency>100) { // use fast mode for frequencies over 100 kHz
|
||||
i2c_set_fast_mode(i2c); // set fast mode (Fm)
|
||||
i2c_set_ccr(i2c, rcc_apb1_frequency/(frequency*1000*2)); // set Thigh/Tlow to generate frequency (fast duty not used)
|
||||
i2c_set_trise(i2c, (300/(1000/(rcc_apb1_frequency/1000000)))+1); // max rise time for Fm mode (< 400) kHz is 300 ns
|
||||
} else { // use fast mode for frequencies below 100 kHz
|
||||
i2c_set_standard_mode(i2c); // set standard mode (Sm)
|
||||
i2c_set_ccr(i2c, rcc_apb1_frequency/(frequency*1000*2)); // set Thigh/Tlow to generate frequency of 100 kHz
|
||||
i2c_set_trise(i2c, (1000/(1000/(rcc_apb1_frequency/1000000)))+1); // max rise time for Sm mode (< 100 kHz) is 1000 ns (~1 MHz)
|
||||
}
|
||||
i2c_peripheral_enable(i2c); // enable I2C after configuration completed
|
||||
}
|
||||
|
||||
void i2c_master_release(uint32_t i2c)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
i2c_reset(i2c); // reset I2C peripheral configuration
|
||||
i2c_peripheral_disable(i2c); // disable I2C peripheral
|
||||
rcc_periph_clock_disable(RCC_I2C(i2c)); // disable clock for I2C peripheral
|
||||
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN_SCL(i2c)); // put I2C I/O pins back to floating
|
||||
gpio_set_mode(GPIO_PORT_SDA(i2c |