Compare commits

...

15 Commits

Author SHA1 Message Date
21a23e18c1 README: fix lists 2021-01-28 13:17:09 +01:00
93d85322d5 application: add gm1351 decoding 2021-01-28 11:13:34 +01:00
56d540a04c application: disable RTC 2021-01-28 11:13:19 +01:00
9d98ab21fc sensor_gm1351: make decoding user driven 2021-01-28 11:12:32 +01:00
58924500e3 usb_cdcacm: increase TX buffer for more logging 2021-01-28 11:04:32 +01:00
69944a24cf uart: increase UART TX buffer for more logging 2021-01-28 11:04:07 +01:00
df14317924 sensor_gm1351: fix digit 0 decoding 2020-02-23 14:19:52 +01:00
3779e848db remove unused library 2020-02-20 11:56:25 +01:00
fccf0b7c0b README: document project and connections 2020-02-20 11:54:00 +01:00
54bdd92222 global: switch debug off, enabling watchdog 2020-02-20 11:52:59 +01:00
b9e64f4913 application: commit application powering GM1351, reading its measurements, and transmitting over Bluetooth 2020-02-20 11:52:59 +01:00
d4fac33b6e Rakefile: use black pill board for this project 2020-02-20 11:51:56 +01:00
5b0bb4fe10 sensor_gm1351: add library to read GM1351 sound level meter measurment by tapping on the LCD controller input 2020-02-20 11:51:56 +01:00
136927c4cb spp_tx: add library to transmit over Bluetooth using UART 2020-02-20 11:51:56 +01:00
605181cd26 sensor_gm1351: add library to read GM1351 sound level meter measurment by tapping on the LCD controller input 2020-02-20 11:51:56 +01:00
57 changed files with 676 additions and 10219 deletions

100
README.md
View File

@ -1,4 +1,5 @@
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 is the firmware for the sound lever meter.
It will read the measurements from a GM1351 sound level meter and send them over Bluetooth.
project
=======
@ -6,48 +7,81 @@ project
summary
-------
*describe project purpose*
The GM1351 is a digital hand held sound level meter.
It is available under multiple brands.
There is no connector/interface to read the measurements.
Instead I am using test point on the back of the PCB.
This allows me to monitoring the interface to the LCD and read back the data displayed into measurements.
The measurements are send transmitted over Bluetooth using a SPP module.
technology
----------
*described electronic details*
The firmware runs on a [black pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#black_pill) development board.
It is based on a STM32F103C8T6 micro-controller.
This will do all the processing and control the peripherals.
board
=====
The Bluetooth module is a HC-05.
It should be configured to be a slave and offer a SPP profile.
The measurement will be forwarded by the micro-controller over the UART part configured at 115200 bps 8N1.
It will transmit the measurement in the format "123.4 dBa\n".
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](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6
- [black pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#black_pill), based on a STM32F103C8T6
- [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board), based on a STM32F103C8T6
- [ST-LINK V2 mini](https://wiki.cuvoodoo.info/doku.php?id=jtag#mini_st-link_v2), a ST-LINK/V2 clone based on a STM32F101C8T6
- [USB-Blaster](https://wiki.cuvoodoo.info/doku.php?id=jtag#armjishu_usb-blaster), an Altera USB-Blaster clone based on a STM32F101C8T6
**Which board is used is defined in the Makefile**.
This is required to map the user LED and button provided on the board
The ST-LINK V2 mini clone has SWD test points on the board.
Because read protection is enabled, you will first need to remove the protection to be able to flash the firmware.
To remove the read protection (and erase flash), run `rake remove_protection` while a SWD adapter is connected.
The Altera USB-Blaster clone has a pin header for SWD and UART1 on the board.
SWD is disabled in the main firmware, and it has read protection.
To be able to flash using SWD (or the serial port), the BOOT0 pin must be set to 1 to boot the system memory install of the flash memory.
To set BOOT0 to 1, apply 3.3 V on R11, between the resistor and the reference designator, when powering the device.
The red LED should stay off while the green LED is on.
Now you can remove the read protection (and erase flash), run `rake remove_protection` while a SWD adapter is connected.
This does not use a Bluetooth Low Energy (BLE) module because it does not fit the needs.
There is no need to save energy since the device needs permanent power just for the display.
There is a constant stream of data (not fitting BLE principles).
The is a specified Classic Bluetooth profile for serial data: Serial Port Profile (SPP).
BLE module use non-standard GATT characteristics for serial data transfer.
connections
===========
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
Connect the peripherals as described below.
- *list board to peripheral pin connections*
MT3608 voltage regulator module:
- IN-: ground
- IN+: 5V
- OUT-: ground
- OUT+: IDC 2x3 pin 5
Set the MT3608 voltage output to 9V.
This is meant to replace the GM1351 PP3 9V battery.
It kind of also works with 5V, but the LCD is very dim.
Since I don't know if the measurements remain accurate at 5V, I prefer to provide the voltage it was designed for.
HC-05 Bluetooth SPP module:
- STATE: no connect
- RX: USART3_TX/PB10
- TX: no connect
- GND: ground
- VCC: 5V
- EN: no connect
n-channel MOSFET 2N7000:
- 1 source: ground
- 2 gate: PB0, add pull down resistor
- 3 drain: IDC 2x3 pin 2
IDC 2x3 2.54 mm, micro-controller side:
- 1: ground
- 2: PB13, SPI2_SCK
- 3: 2N7000 pin 3
- 4: PB15, SPI2_MOSI
- 5: MT3608 OUT+
- 6: PA8
IDC 2x3 2.54 mm, GM1351 side (solder wires on the PCB):
- 1: B-
- 2: LCD_WR
- 3: D7/D8, on the text side, to control the ON/OFF button
- 4: LCD_DATA
- 5: B+
- 6: LCD_CS
All pins are configured using `define`s in the corresponding source code.
@ -83,7 +117,7 @@ It is up to the application to advertise USB DFU support (i.e. as does the provi
The `bootlaoder` image will be flashed using SWD (Serial Wire Debug).
For that you need an SWD adapter.
The `Makefile` uses a Black Magic Probe (per default), or a ST-Link V2 along OpenOCD software.
The `Makefile` uses a ST-Link V2 along OpenOCD software.
To flash the `booltoader` using SWD run `rake flash_booloader`.
Once the `bootloader` is flashed it is possible to flash the `application` over USB using the DFU protocol by running `rake flash`.

View File

@ -15,7 +15,7 @@ FIRMWARES = [BOOTLOADER, APPLICATION]
# which development board is used
# supported are: SYSTEM_BOARD, MAPLE_MINI, BLUE_PILL, BLACK_PILL, CORE_BOARD, STLINKV2, BLASTER, BUSVOODOO
BOARD = ENV["BOARD"] || "CORE_BOARD"
BOARD = ENV["BOARD"] || "BLACK_PILL"
# libopencm3 definitions
LIBOPENCM3_DIR = "libopencm3"
@ -184,7 +184,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

View File

@ -12,7 +12,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** STM32F1 application example
/** STM32F1 application to read out measurements from GM1351 sound level meter and send the over Bluetooth
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2020
@ -47,6 +47,8 @@
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "terminal.h" // handle the terminal interface
#include "menu.h" // menu utilities
#include "sensor_gm1351.h" // sound level meter interface
#include "spp_tx.h" // to communicate to the Bluetooth module
/** watchdog period in ms */
#define WATCHDOG_PERIOD 10000
@ -54,11 +56,7 @@
/** set to 0 if the RTC is reset when the board is powered on, only indicates the uptime
* set to 1 if VBAT can keep the RTC running when the board is unpowered, indicating the date and time
*/
#if defined(CORE_BOARD)
#define RTC_DATE_TIME 1
#else
#define RTC_DATE_TIME 0
#endif
/** number of RTC ticks per second
* @note use integer divider of oscillator to keep second precision
@ -74,6 +72,20 @@ static time_t time_start = 0;
volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */
/** @} */
/** if we show the received value */
static bool gm1351_show = false;
/** show/hide received sound level
* @param[in] argument not used
*/
static void command_show(void* argument)
{
(void)argument; // we won't use the argument
gm1351_show = !gm1351_show; // toggle setting
puts(gm1351_show ? "show" : "hide");
puts(" decoded sound level\n");
}
size_t putc(char c)
{
size_t length = 0; // number of characters printed
@ -128,6 +140,13 @@ static void command_reset(void* argument);
*/
static void command_bootloader(void* argument);
/** switch/toggle power of GM1351 sound level meter */
static void command_power(void* argument)
{
(void)argument; // we won't use the argument
sensor_gm1351_power_toggle(); // toggle power of meter
}
/** list of all supported commands */
static const struct menu_command_t menu_commands[] = {
{
@ -180,6 +199,23 @@ static const struct menu_command_t menu_commands[] = {
.argument_description = NULL,
.command_handler = &command_bootloader,
},
// custom actions
{
.shortcut = 'p',
.name = "power",
.command_description = "switch power of GM1351 sound level meter",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_power,
},
{
.shortcut = 's',
.name = "show",
.command_description = "show/hide decoded sound level",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_show,
},
};
static void command_help(void* argument)
@ -341,7 +377,7 @@ void main(void)
uart_setup(); // setup USART (for printing)
#endif
usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
puts("\nwelcome to the CuVoodoo STM32F1 example application\n"); // print welcome message
puts("\nwelcome to the CuVoodoo GM1351 sound level meter reader\n"); // print welcome message
#if DEBUG
// show reset cause
@ -394,6 +430,14 @@ void main(void)
time_start = rtc_get_counter_val(); // get start time from internal RTC
puts("OK\n");
puts("setup UART to Bluetooth SPP module: ");
spp_tx_setup();
puts("OK\n");
puts("setup GM1351 sound level meter: ");
sensor_gm1351_setup();
puts("OK\n");
// setup terminal
terminal_prefix = ""; // set default prefix
terminal_process = &process_command; // set central function to process commands
@ -402,6 +446,7 @@ 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
uint32_t gm1351_last = rtc_get_counter_val(); // last time a measurement has been received
while (true) { // infinite loop
iwdg_reset(); // kick the dog
if (user_input_available) { // user input is available
@ -420,8 +465,30 @@ void main(void)
if (rtc_internal_tick_flag) { // the internal RTC ticked
rtc_internal_tick_flag = false; // reset flag
action = true; // action has been performed
if (0 == (rtc_get_counter_val() % RTC_TICKS_SECOND)) { // one seond has passed
if (0 == (rtc_get_counter_val() % RTC_TICKS_SECOND)) { // one second has passed
led_toggle(); // toggle LED (good to indicate if main function is stuck)
if ((rtc_get_counter_val() - gm1351_last) / RTC_TICKS_SECOND >= 3) { // no measurement has been received for some time
sensor_gm1351_power_toggle(); // toggle power of meter
gm1351_last = rtc_get_counter_val(); // restart the wait
puts("no value received from GM1351. trying to power it on\n");
}
}
}
if (sensor_gm1351_received_flag) { // meter data has been received
action = true; // action has been performed
sensor_gm1351_received_flag = false; // reset flag
if (sensor_gm1351_decode()) { // decode received value
led_toggle(); // notify user about activity
gm1351_last = rtc_get_counter_val(); // remember we got a value
char measurement[10 + 1]; // to store the string with the measurement value
snprintf(measurement, LENGTH(measurement), "%u.%u dBa\n", sensor_gm1351_decidba / 10, sensor_gm1351_decidba % 10); // generate string
if (gm1351_show) {
puts(measurement); // display measurement to user
}
// sen measurement to Bluetooth module
for (uint8_t i = 0; i < LENGTH(measurement) && measurement[i] != '\0'; i++) {
spp_tx_putchar_nonblocking( measurement[i]);
}
}
}
if (action) { // go to sleep if nothing had to be done, else recheck for activity

View File

@ -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]))

View File

@ -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
}

View File

@ -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);

View File

@ -1,692 +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 I²C as master (code)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: I2C
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencm3/cm3/systick.h> // SysTick library
#include <libopencm3/cm3/assert.h> // assert utilities
#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> // I²C library
/* own libraries */
#include "global.h" // global utilities
#include "i2c_master.h" // I²C header and definitions
/** get RCC for I²C based on I²C identifier
* @param[in] i2c I²C base address
* @return RCC address for I²C peripheral
*/
static uint32_t RCC_I2C(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
return RCC_I2C1;
break;
case I2C2:
return RCC_I2C2;
break;
default:
return 0;
}
}
/** get RCC for GPIO port for SCL pin based on I²C identifier
* @param[in] i2c I²C base address
* @return RCC GPIO address
*/
static uint32_t RCC_GPIO_PORT_SCL(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
case I2C2:
return RCC_GPIOB;
break;
default:
return 0;
}
}
/** get RCC for GPIO port for SDA pin based on I²C identifier
* @param[in] i2c I²C base address
* @return RCC GPIO address
*/
static uint32_t RCC_GPIO_PORT_SDA(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
case I2C2:
return RCC_GPIOB;
break;
default:
return 0;
}
}
/** get GPIO port for SCL pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PORT_SCL(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == 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:
return 0;
}
}
/** get GPIO port for SDA pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PORT_SDA(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == 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:
return 0;
}
}
/** get GPIO pin for SCL pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PIN_SCL(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == 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:
return 0;
}
}
/** get GPIO pin for SDA pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PIN_SDA(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == 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:
return 0;
}
}
void i2c_master_setup(uint32_t i2c, uint16_t frequency)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// configure I²C peripheral
rcc_periph_clock_enable(RCC_GPIO_PORT_SCL(i2c)); // enable clock for I²C 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 I²C I/O pins
rcc_periph_clock_enable(RCC_GPIO_PORT_SDA(i2c)); // enable clock for I²C 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 I²C I/O pins
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function
rcc_periph_clock_enable(RCC_I2C(i2c)); // enable clock for I²C peripheral
i2c_reset(i2c); // reset peripheral domain
i2c_peripheral_disable(i2c); // I²C 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 I²C after configuration completed
}
void i2c_master_release(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
i2c_reset(i2c); // reset I²C peripheral configuration
i2c_peripheral_disable(i2c); // disable I²C peripheral
rcc_periph_clock_disable(RCC_I2C(i2c)); // disable clock for I²C peripheral
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN_SCL(i2c)); // put I²C I/O pins back to floating
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN_SDA(i2c)); // put I²C I/O pins back to floating
}
bool i2c_master_check_signals(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// enable GPIOs to read SDA and SCL
rcc_periph_clock_enable(RCC_GPIO_PORT_SDA(i2c)); // enable clock for I²C I/O peripheral
rcc_periph_clock_enable(RCC_GPIO_PORT_SCL(i2c)); // enable clock for I²C I/O peripheral
// pull SDA and SDC low to check if there are pull-up resistors
uint32_t sda_crl = GPIO_CRL(GPIO_PORT_SDA(i2c)); // backup port configuration
uint32_t sda_crh = GPIO_CRH(GPIO_PORT_SDA(i2c)); // backup port configuration
uint32_t sda_bsrr = GPIO_BSRR(GPIO_PORT_SDA(i2c)); // backup port configuration
uint32_t scl_crl = GPIO_CRL(GPIO_PORT_SCL(i2c)); // backup port configuration
uint32_t scl_crh = GPIO_CRH(GPIO_PORT_SCL(i2c)); // backup port configuration
uint32_t scl_bsrr = GPIO_BSRR(GPIO_PORT_SCL(i2c)); // backup port configuration
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN_SDA(i2c)); // configure signal as pull down
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN_SCL(i2c)); // configure signal as pull down
gpio_clear(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // pull down
gpio_clear(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // pull down
bool to_return = (0 != gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)) && 0 != gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c))); // check if the signals are still pulled high by external stronger pull-up resistors
GPIO_CRL(GPIO_PORT_SDA(i2c)) = sda_crl; // restore port configuration
GPIO_CRH(GPIO_PORT_SDA(i2c)) = sda_crh; // restore port configuration
GPIO_BSRR(GPIO_PORT_SDA(i2c)) = sda_bsrr; // restore port configuration
GPIO_CRL(GPIO_PORT_SCL(i2c)) = scl_crl; // restore port configuration
GPIO_CRH(GPIO_PORT_SCL(i2c)) = scl_crh; // restore port configuration
GPIO_BSRR(GPIO_PORT_SCL(i2c)) = scl_bsrr; // restore port configuration
return to_return;
}
bool i2c_master_reset(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
bool to_return = true;
// follow procedure described in STM32F10xxC/D/E Errata sheet, Section 2.14.7
i2c_peripheral_disable(i2c); // disable I²C peripheral
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN_SCL(i2c)); // put I²C I/O pins to general output
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set high
to_return &= !gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // ensure it is high
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN_SDA(i2c)); // put I²C I/O pins to general output
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set high
to_return &= !gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is high
gpio_clear(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set low (try first transition)
to_return &= gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is low
gpio_clear(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set low (try first transition)
to_return &= gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // ensure it is low
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set high (try second transition)
to_return &= !gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // ensure it is high
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set high (try second transition)
to_return &= !gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is high
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // set I²C I/O pins back
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // set I²C I/O pins back
I2C_CR1(i2c) |= I2C_CR1_SWRST; // reset device
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // reset device
i2c_peripheral_enable(i2c); // re-enable device
return to_return;
}
enum i2c_master_rc i2c_master_start(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
bool retry = true; // retry after reset if first try failed
enum i2c_master_rc to_return; // return code
uint16_t sr1; // read register once, since reading/writing other registers or other events clears some flags
try:
to_return = I2C_MASTER_RC_NONE; // return code
// send (re-)start condition
if (I2C_CR1(i2c) & (I2C_CR1_START | I2C_CR1_STOP)) { // ensure start or stop operations are not in progress
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
}
// prepare timer in case the peripheral hangs on sending stop condition (see errata 2.14.4 Wrong behavior of I²C peripheral in master mode after a misplaced Stop)
systick_counter_disable(); // disable SysTick to reconfigure it
systick_set_frequency(500, rcc_ahb_frequency); // set timer to 2 ms (that should be long enough to send a start condition)
systick_clear(); // reset SysTick (set to 0)
systick_interrupt_disable(); // disable interrupt to prevent ISR to read the flag
systick_get_countflag(); // reset flag (set when counter is going for 1 to 0)
i2c_send_start(i2c); // send start condition to start transaction
bool timeout = false; // remember if the timeout has been reached
systick_counter_enable(); // start timer
while ((I2C_CR1(i2c) & I2C_CR1_START) && !((sr1 = I2C_SR1(i2c)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until start condition has been accepted and cleared
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
sr1 = I2C_SR1(i2c); // be sure to get the current value
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
}
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_SB | I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout && I2C_MASTER_RC_NONE == to_return) { // wait until start condition is transmitted